ServiceNow Deprecated

Deprecated. Use the ServiceNow v2 integration instead.

Case Management · ServiceNow

Details

IDServiceNow
ProviderServiceNow
CategoryCase Management
From Version5.0.0
Docker Imagedemisto/python:2.7.18.20958
Supported ModulesAttack Surface Management Exposure Management Agentix Cortex Cloud Cloud Runtime Security Cloud Posture Security XSIAM EDR

README

Deprecated. Use the ServiceNow v2 integration instead.

This integration was integrated and tested with the London version of ServiceNow.

Use cases

  1. Get, update, create, delete ServiceNow tickets, in addition to adding links/comments and uploading files to them.
  2. Fetch newly created incidents.
  3. Get, update, create, delete records from any ServiceNow table.

Wrapper Scripts

There are 3 scripts that serve as examples for wrapping the following generic commands:
servicenow-query-table - ServiceNowQueryIncident
servicenow-create-record - ServiceNowCreateIncident
servicenow-update-record - ServiceNowUpdateIncident

You can use these scripts if you want to wrap these commands around a ServiceNow table of your choice. These scripts are wrapped around the incident table, so to wrap them around another table simply copy the scripts and edit the code, arguments and outputs accordingly.

Configure ServiceNow on Cortex XSOAR

To use ServiceNow on Cortex XSOAR, ensure your user account has the rest_api_explorer and web_service_admin roles. These roles are required to make API calls. However, they may not suffice for viewing records in some tables. Please make sure you have the correct role so you have permissions to work with the relevant table.

  1. Navigate to Settings > Integrations > Servers `&` Services.
  2. Search for ServiceNow.
  3. Click Add instance to create and configure a new integration instance.
    • Name: a textual name for the integration instance.
    • ServiceNow URL, in the format https://company.service-now.com/
    • Username
    • Use system proxy settings
    • Trust any certificate (not secure)
    • Default ticket type for running ticket commands and fetching incidents. Can be incident, sc_request, problem or change_reuquest. Commands also take ticket_type argument
    • ServiceNow API Version (e.g. 'v1')
    • Fetch incidents: The query to use when fetching incidents, and how many incidents to fetch each time.
    • The query to use when fetching ServiceNow incidents
    • How many ServiceNow incidents to fetch each time
    • Incident type
    • Get incident attachments
  4. Click Test to validate the URLs, token, and connection.

Cortex XSOAR engine: If relevant, select the engine that acts as a proxy to the IMAP server.
Engines are used when you need to access a remote network segments and there are network devices such as proxies, firewalls, etc. that prevent the Cortex XSOAR server from accessing the remote networks. For more information, see:
Cortex XSOAR 6.13 - Engines
Cortex XSOAR 8 Cloud- Engines
Cortex XSOAR 8.7 On-prem - Engines.

Fetched Incidents Data

The integration fetches newly created tickets according to the following parameters, which you define in the instance configuration: ticket_type, query, and limit. For the first fetch, the integration will fetch incidents that were created 10 minutes earlier. After that, it will fetch incidents that were created after the timestamp of the last fetch.

Data:

{
    "result": [
        {
            "active": "false",
            "activity_due": "",
            "additional_assignee_list": "",
            "approval": "",
            "approval_history": "",
            "approval_set": "",
            "assigned_to": {
                "link": "https://dev56245.service-now.com/api/now/table/sys_user/46b87022a9fe198101a78787e40d7547",
                "value": "46b87022a9fe198101a78787e40d7547"
            },
            "assignment_group": {
                "link": "https://dev56245.service-now.com/api/now/table/sys_user_group/d625dccec0a8016700a222a0f7900d06",
                "value": "d625dccec0a8016700a222a0f7900d06"
            },
            "business_duration": "1970-01-22 21:46:21",
            "business_service": "",
            "business_stc": "1892781",
            "calendar_duration": "1970-04-02 20:46:21",
            "calendar_stc": "7937181",
            "caller_id": {
                "link": "https://dev56245.service-now.com/api/now/table/sys_user/5137153cc611227c000bbd1bd8cd2005",
                "value": "5137153cc611227c000bbd1bd8cd2005"
            },
            "category": "network",
            "caused_by": "",
            "child_incidents": "",
            "close_code": "Closed/Resolved by Caller",
            "close_notes": "Closed before close notes were made mandatory\n\t\t",
            "closed_at": "2018-02-21 23:10:06",
            "closed_by": {
                "link": "https://dev56245.service-now.com/api/now/table/sys_user/9ee1b13dc6112271007f9d0efdb69cd0",
                "value": "9ee1b13dc6112271007f9d0efdb69cd0"
            },
            "cmdb_ci": {
                "link": "https://dev56245.service-now.com/api/now/table/cmdb_ci/b0c4030ac0a800090152e7a4564ca36c",
                "value": "b0c4030ac0a800090152e7a4564ca36c"
            },
            "comments": "",
            "comments_and_work_notes": "",
            "company": "",
            "contact_type": "",
            "correlation_display": "",
            "correlation_id": "",
            "delivery_plan": "",
            "delivery_task": "",
            "description": "User can't access email on mail.company.com.\n\t\t",
            "due_date": "",
            "escalation": "0",
            "expected_start": "",
            "follow_up": "",
            "group_list": "",
            "hold_reason": "",
            "impact": "1",
            "incident_state": "7",
            "knowledge": "false",
            "location": {
                "link": "https://dev56245.service-now.com/api/now/table/cmn_location/1083361cc611227501b682158cabf646",
                "value": "1083361cc611227501b682158cabf646"
            },
            "made_sla": "false",
            "notify": "1",
            "number": "INC0000001",
            "opened_at": "2018-02-20 23:09:51",
            "opened_by": {
                "link": "https://dev56245.service-now.com/api/now/table/sys_user/681ccaf9c0a8016400b98a06818d57c7",
                "value": "681ccaf9c0a8016400b98a06818d57c7"
            },
            "order": "",
            "parent": "",
            "parent_incident": "",
            "priority": "1",
            "problem_id": {
                "link": "https://dev56245.service-now.com/api/now/table/problem/9d3a266ac6112287004e37fb2ceb0133",
                "value": "9d3a266ac6112287004e37fb2ceb0133"
            },
            "reassignment_count": "1",
            "reopen_count": "",
            "reopened_by": "",
            "reopened_time": "",
            "resolved_at": "2018-05-23 19:56:12",
            "resolved_by": {
                "link": "https://dev56245.service-now.com/api/now/table/sys_user/6816f79cc0a8016401c5a33be04be441",
                "value": "6816f79cc0a8016401c5a33be04be441"
            },
            "rfc": "",
            "severity": "1",
            "short_description": "Can't read email",
            "sla_due": "",
            "state": "7",
            "subcategory": "",
            "sys_class_name": "incident",
            "sys_created_by": "pat",
            "sys_created_on": "2016-09-21 18:24:13",
            "sys_domain": {
                "link": "https://dev56245.service-now.com/api/now/table/sys_user_group/global",
                "value": "global"
            },
            "sys_domain_path": "/",
            "sys_id": "9c573169c611228700193229fff72400",
            "sys_mod_count": "21",
            "sys_tags": "",
            "sys_updated_by": "admin",
            "sys_updated_on": "2018-05-23 20:16:07",
            "time_worked": "",
            "u_test": "",
            "upon_approval": "",
            "upon_reject": "",
            "urgency": "1",
            "user_input": "",
            "watch_list": "",
            "work_end": "",
            "work_notes": "",
            "work_notes_list": "",
            "work_start": ""
        },
        {
            "active": "true",
            "activity_due": "2018-05-23 21:51:11",
            "additional_assignee_list": "",
            "approval": "",
            "approval_history": "",
            "approval_set": "",
            "assigned_to": {
                "link": "https://dev56245.service-now.com/api/now/table/sys_user/46ca0887a9fe19810191e08e51927ebf",
                "value": "46ca0887a9fe19810191e08e51927ebf"
            },
            "assignment_group": {
                "link": "https://dev56245.service-now.com/api/now/table/sys_user_group/287ebd7da9fe198100f92cc8d1d2154e",
                "value": "287ebd7da9fe198100f92cc8d1d2154e"
            },
            "business_duration": "",
            "business_service": "",
            "business_stc": "",
            "calendar_duration": "",
            "calendar_stc": "",
            "caller_id": {
                "link": "https://dev56245.service-now.com/api/now/table/sys_user/5137153cc611227c000bbd1bd8cd2005",
                "value": "5137153cc611227c000bbd1bd8cd2005"
            },
            "category": "network",
            "caused_by": "",
            "child_incidents": "",
            "close_code": "",
            "close_notes": "",
            "closed_at": "",
            "closed_by": "",
            "cmdb_ci": {
                "link": "https://dev56245.service-now.com/api/now/table/cmdb_ci/b0c25d1bc0a800090168be1bfcdcd759",
                "value": "b0c25d1bc0a800090168be1bfcdcd759"
            },
            "comments": "",
            "comments_and_work_notes": "",
            "company": "",
            "contact_type": "",
            "correlation_display": "",
            "correlation_id": "",
            "delivery_plan": "",
            "delivery_task": "",
            "description": "User can't get to any of his files on the file server.",
            "due_date": "",
            "escalation": "3",
            "expected_start": "",
            "follow_up": "",
            "group_list": "",
            "hold_reason": "4",
            "impact": "1",
            "incident_state": "3",
            "knowledge": "false",
            "location": {
                "link": "https://dev56245.service-now.com/api/now/table/cmn_location/108486c7c611227500b093211aa88dcc",
                "value": "108486c7c611227500b093211aa88dcc"
            },
            "made_sla": "false",
            "notify": "1",
            "number": "INC0000002",
            "opened_at": "2018-02-14 23:07:12",
            "opened_by": {
                "link": "https://dev56245.service-now.com/api/now/table/sys_user/681ccaf9c0a8016400b98a06818d57c7",
                "value": "681ccaf9c0a8016400b98a06818d57c7"
            },
            "order": "",
            "parent": "",
            "parent_incident": "",
            "priority": "1",
            "problem_id": {
                "link": "https://dev56245.service-now.com/api/now/table/problem/9d3a266ac6112287004e37fb2ceb0133",
                "value": "9d3a266ac6112287004e37fb2ceb0133"
            },
            "reassignment_count": "1",
            "reopen_count": "",
            "reopened_by": "",
            "reopened_time": "",
            "resolved_at": "",
            "resolved_by": "",
            "rfc": "",
            "severity": "1",
            "short_description": "Network file shares access issue",
            "sla_due": "",
            "state": "3",
            "subcategory": "",
            "sys_class_name": "incident",
            "sys_created_by": "pat",
            "sys_created_on": "2016-09-10 22:30:06",
            "sys_domain": {
                "link": "https://dev56245.service-now.com/api/now/table/sys_user_group/global",
                "value": "global"
            },
            "sys_domain_path": "/",
            "sys_id": "9d385017c611228701d22104cc95c371",
            "sys_mod_count": "17",
            "sys_tags": "",
            "sys_updated_by": "admin",
            "sys_updated_on": "2018-05-23 19:51:11",
            "time_worked": "",
            "u_test": "",
            "upon_approval": "",
            "upon_reject": "",
            "urgency": "1",
            "user_input": "",
            "watch_list": "",
            "work_end": "",
            "work_notes": "",
            "work_notes_list": "",
            "work_start": ""
        }
    ]
}

Commands

You can execute these commands from the Cortex XSOAR 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.

  1. Get ticket information: servicenow-get-ticket
  2. Create a ticket: servicenow-create-ticket
  3. Update a ticket: servicenow-update-ticket
  4. Delete a ticket: servicenow-delete-ticket
  5. Add a link to a ticket: servicenow-add-link
  6. Add a comment to a ticket: servicenow-add-comment
  7. Get ticket information for a query: servicenow-query-tickets
  8. Upload a file to a ticket: servicenow-upload-file
  9. Get record information: servicenow-get-record
  10. Query a table: servicenow-query-table
  11. Create a record in a table: servicenow-create-record
  12. Update a record in a table: servicenow-update-record
  13. Delete a record from a table: servicenow-delete-record
  14. List API fields for a table: servicenow-list-table-fields
  15. Query computers: servicenow-query-computers
  16. Query groups: servicenow-query-groups
  17. Query users: servicenow-query-users
  18. Get table names: servicenow-get-table-name
  19. Get ticket notes: servicenow-get-ticket-notes

1. Get ticket information


Retrieves ticket information by the ticket ID.

Base Command

servicenow-get-ticket

Input
Argument Name Description Required
id Ticket system ID to retrieve Optional
ticket_type Ticket type Optional
number Ticket number to retrieve Optional
get_attachments Whether to retrieve ticket attachments, default is false Optional

 

Context Output
Path Type Description
ServiceNow.Ticket.ID string ServiceNow ticket ID
ServiceNow.Ticket.OpenedBy string ServiceNow ticket opener ID
ServiceNow.Ticket.CreatedOn date ServiceNow ticket creation date
ServiceNow.Ticket.Assignee string ServiceNow ticket assignee ID
ServiceNow.Ticket.State string ServiceNow ticket state
ServiceNow.Ticket.Summary string ServiceNow ticket short summary
ServiceNow.Ticket.Number string ServiceNow ticket number
ServiceNow.Ticket.Active boolean ServiceNow ticket active
ServiceNow.Ticket.AdditionalComments string ServiceNow ticket comments
ServiceNow.Ticket.Priority string ServiceNow ticket priority
ServiceNow.Ticket.OpenedAt date ServiceNow ticket opening time
ServiceNow.Ticket.ResolvedBy string ServiceNow ticket resolver ID
ServiceNow.Ticket.CloseCode string ServiceNow ticket close code
File.Info string Attachment file info
File.Name string Attachment file name
File.Size number Attachment file size
File.SHA1 string Attachment file SHA-1
File.SHA256 string Attachment file SHA-256
File.EntryID string Attachment file entry ID
File.Type string Attachment file type
File.MD5 string Attachment file MD5

 

Command Example

!servicenow-get-ticket number=INC0010052

Context Example
{
    "ServiceNow": {
        "Ticket": {
            "Active": "true",
            "CreatedOn": "2018-09-04 08:21:33",
            "ID": "0c23f8d24f102300d316b63ca310c742",
            "Number": "INC0010052",
            "OpenedAt": "2018-09-04 08:21:33",
            "OpenedBy": "6816f79cc0a8016401c5a33be04be441",
            "Priority": "4 - Low",
            "State": "1 - New",
            "Summary": "This is a new ticket"
        }
    }
}
Human Readable Output

image

2. Create a ticket

Creates a new ServiceNow ticket.

Base Command

servicenow-create-ticket

Input
Argument Name Description Required
short_description Short description of the ticket Optional
ticket_type Ticket type Optional
urgency Ticket urgency Optional
severity Ticket severity Optional
impact Ticket impact Optional
active Set ticket as Active Optional
activity_due Set ticket ActivityDue - format "2016-07-02 21:51:11" Optional
additional_assignee_list List of assigned users to the ticket Optional
approval_history Ticket history approval Optional
approval_set Set ticket ApprovalSet - format "2016-07-02 21:51:11" Optional
assigned_to To whom the ticket is assigned Optional
business_duration Format: YYYY-MM-DD HH:MM:SS Optional
business_service Business service Optional
business_stc Business source Optional
calendar_duration Format: YYYY-MM-DD HH:MM:SS Optional
caller_id UID Format Optional
category Category of ticket Optional
caused_by UID Format Optional
close_code Ticket's close code Optional
close_notes Close notes of the ticket Optional
closed_at Format: YYYY-MM-DD HH:MM:SS Optional
closed_by User who closed the ticket Optional
cmdb_ci UID Format Optional
comments Format type journal input Optional
comments_and_work_notes Format type journal input Optional
company UID Format Optional
contact_type Contact type Optional
correlation_display Correlation display Optional
correlation_id Correlation id Optional
delivery_plan UID Format Optional
display If you want to display comments, work_notes... Optional
description Ticket description Optional
due_date Format: YYYY-MM-DD HH:MM:SS Optional
escalation Escalation Optional
expected_start Format: YYYY-MM-DD HH:MM:SS Optional
follow_up Format: YYYY-MM-DD HH:MM:SS Optional
group_list UID format list Optional
knowledge Is the ticket solved in the knowledge base Optional
location Location of the ticket Optional
made_sla SLA of the ticket Optional
notify Notify about this ticket Optional
order Order number Optional
parent UID Format Optional
parent_incident UID Format Optional
problem_id UID Format Optional
reassignment_count How many users included in this ticket before Optional
reopen_count How many time the ticket has been reopened Optional
resolved_at Format: YYYY-MM-DD HH:MM:SS Optional
resolved_by UID Format Optional
rfc UID Optional
sla_due Format: YYYY-MM-DD HH:MM:SS Optional
subcategory Subcategory Optional
sys_updated_by Last updated by Optional
sys_updated_on Format: YYYY-MM-DD HH:MM:SS Optional
user_input Input from the end user Optional
watch_list A list of watched tickets Optional
work_end Format: YYYY-MM-DD HH:MM:SS Optional
work_notes Format journal list Optional
work_notes_list List with UIDs Optional
work_start Date when started to work on the ticket Optional
assignment_group Set AssignmentGroup - sys_id of group Optional
incident_state integer Optional
number Ticket number Optional
priority Priority of the ticket Optional
template Template name to use as a base to create new tickets. Optional
custom_fields Custom(user defined) fields in the format: fieldname1=value;fieldname2=value;... Optional
change_type Type of change request ticket Optional
state State of the ticket, e.g., "Closed" or "7" or "7 - Closed Optional
opened_at Time the ticket was opened (YYYY-MM-DD HH:MM:SS) Optional

 

Context Output
Path Type Description
ServiceNow.Ticket.ID string ServiceNow ticket ID
ServiceNow.Ticket.OpenedBy string ServiceNow ticket opener ID
ServiceNow.Ticket.CreatedOn date ServiceNow ticket creation date
ServiceNow.Ticket.Assignee string ServiceNow ticket assignee ID
ServiceNow.Ticket.State string ServiceNow ticket state
ServiceNow.Ticket.Summary string ServiceNow ticket short summary
ServiceNow.Ticket.Number string ServiceNow ticket number
ServiceNow.Ticket.Active boolean ServiceNow ticket active
ServiceNow.Ticket.AdditionalComments string ServiceNow ticket comments
ServiceNow.Ticket.Priority string ServiceNow ticket priority
ServiceNow.Ticket.OpenedAt date ServiceNow ticket opening time
ServiceNow.Ticket.ResolvedBy string ServiceNow ticket resolver ID
ServiceNow.Ticket.CloseCode string ServiceNow ticket close code

 

Command Example

!servicenow-create short_description="This is a new ticket" ticket_type="incident" type="normal"

Context Example
{
    "ServiceNow": {
        "Ticket": {
            "Active": "true",
            "CreatedOn": "2018-11-27 12:11:39",
            "ID": "bd5d42994f82230021ae045e9310c7bd",
            "Number": "INC0010449",
            "OpenedAt": "2018-11-27 12:11:39",
            "OpenedBy": "6816f79cc0a8016401c5a33be04be441",
            "Priority": "5 - Planning",
            "State": "1 - New",
            "Summary": "This is a new ticket"
        }
    }
}
Human Readable Output

image

3. Update a ticket


Updates a specified ServiceNow ticket.

Base Command

servicenow-update-ticket

Input
Argument Name Description Required
short_description Short description of the ticket Optional
ticket_type Ticket type Optional
urgency Ticket urgency Optional
severity Ticket severity Optional
impact Ticket impact Optional
active Does the ticket active (true/false) Optional
activity_due Format: YYYY-MM-DD HH:MM:SS Optional
additional_assignee_list List of assigned users to the ticket Optional
approval_history Ticket history approval Optional
approval_set Set ticket ApprovalSet - format "2016-07-02 21:51:11" Optional
assigned_to To whom the ticket is assigned Optional
business_duration Format: YYYY-MM-DD HH:MM:SS Optional
business_service Business service Optional
business_stc Business source Optional
calendar_duration Format: YYYY-MM-DD HH:MM:SS Optional
caller_id UID Format Optional
category Category name Optional
caused_by UID Format Optional
close_code Ticket's close code Optional
close_notes Close notes of the ticket Optional
closed_at Format: YYYY-MM-DD HH:MM:SS Optional
closed_by User who closed the ticket Optional
cmdb_ci UID Format Optional
comments Format type journal input Optional
comments_and_work_notes Format type journal input Optional
company UID Format Optional
contact_type Contact type Optional
correlation_display Correlation display Optional
correlation_id Correlation id Optional
delivery_plan UID Format Optional
display If you want to display comments, work_notes... Optional
description Ticket description Optional
due_date Format: YYYY-MM-DD HH:MM:SS Optional
escalation Escalation Optional
expected_start Format: YYYY-MM-DD HH:MM:SS Optional
follow_up Format: YYYY-MM-DD HH:MM:SS Optional
group_list UID format list Optional
knowledge Is the ticket solved in the knowledge base Optional
location Location of the ticket Optional
made_sla SLA of the ticket Optional
notify Notify about this ticket Optional
order Order number Optional
parent UID Format Optional
parent_incident UID Format Optional
problem_id UID Format Optional
reassignment_count How many users included in this ticket before Optional
reopen_count How many time the ticket has been reopened Optional
resolved_at Format: YYYY-MM-DD HH:MM:SS Optional
resolved_by UID Format Optional
rfc UID Optional
sla_due Format: YYYY-MM-DD HH:MM:SS Optional
subcategory Subcategory Optional
sys_updated_by Last updated by Optional
sys_updated_on Format: YYYY-MM-DD HH:MM:SS Optional
user_input Input from the end user Optional
watch_list A list of watched tickets Optional
work_end Format: YYYY-MM-DD HH:MM:SS Optional
work_notes Format journal list Optional
work_notes_list List with UIDs Optional
work_start Date when started to work on the ticket Optional
assignment_group UID Optional
incident_state integer Optional
number Ticket number Optional
priority Priority of the ticket Optional
id System ID of the ticket to update Required
custom_fields Custom(user defined) fields in the format: fieldname1=value;fieldname2=value;... Optional
change_type Type of Change Request ticket Optional
state State of the ticket, e.g., "Closed" or "7" or "7 - Closed Optional

 

Context Output
Path Type Description
ServiceNow.Ticket.ID string ServiceNow ticket ID
ServiceNow.Ticket.OpenedBy string ServiceNow ticket opener ID
ServiceNow.Ticket.CreatedOn date ServiceNow ticket creation date
ServiceNow.Ticket.Assignee string ServiceNow ticket assignee ID
ServiceNow.Ticket.State string ServiceNow ticket state
ServiceNow.Ticket.Summary string ServiceNow ticket short summary
ServiceNow.Ticket.Number string ServiceNow ticket number
ServiceNow.Ticket.Active boolean ServiceNow ticket active
ServiceNow.Ticket.AdditionalComments string ServiceNow ticket comments
ServiceNow.Ticket.Priority string ServiceNow ticket priority
ServiceNow.Ticket.OpenedAt date ServiceNow ticket opening time
ServiceNow.Ticket.ResolvedBy string ServiceNow ticket resolver ID
ServiceNow.Ticket.CloseCode string ServiceNow ticket close code

 

Command Example

!servicenow-update ticket_type="incident" impact="2" id="c34852514fc2230021ae045e9310c7ab"

Context Example
{
    "ServiceNow": {
        "Ticket": {
            "Active": "true",
            "CreatedOn": "2018-11-27 12:59:32",
            "ID": "c34852514fc2230021ae045e9310c7ab",
            "Number": "INC0010452",
            "OpenedAt": "2018-11-27 12:59:32",
            "OpenedBy": "6816f79cc0a8016401c5a33be04be441",
            "Priority": "4 - Low",
            "State": "1 - New"
        }
    }
}
Human Readable Output

image

4. Delete a ticket


Deletes a specified ServiceNow ticket.

Base Command

servicenow-delete-ticket

Input
Argument Name Description Required
id Ticket system ID Required
ticket_type Ticket type Optional

 

Context Output

There is no context output for this command.

Command Example

!servicenow-delete-ticket id=0c23f8d24f102300d316b63ca310c742

Human Readable Output

image

5. Add a link to a ticket


Adds a link to a specified ServiceNow ticket.

Base Command

servicenow-add-link

Input
Argument Name Description Required
id Ticket system ID Required
ticket_type Ticket type Optional
link The actual link to publish in ServiceNow ticket, valid url format, like http://www.demisto.com Required
post-as-comment Publish the link as comment on the ticket, if false will publish the link as WorkNote, format boolean Optional
text The text to represent the link Optional

 

Context Output

There is no context output for this command.

Command Example

!servicenow-add-link id="bd5d42994f82230021ae045e9310c7bd" ticket_type="incident" link="www.demisto.com" text="this is a link"

Human Readable Output

image

6. Add a comment to a ticket


Adds a comment to a specified ticket.

Base Command

servicenow-add-comment

Input
Argument Name Description Required
id Ticket system ID Required
ticket_type Ticket type Optional
comment Comment to add Required
post-as-comment Publish the link as a comment on the ticket, if false will publish the link as WorkNote, format boolean Optional

 

Context Output

There is no context output for this command.

Command Example

!servicenow-add-comment id="0c23f8d24f102300d316b63ca310c742" ticket_type="incident" comment="This is a comment"

Human Readable Output

image

7. Get ticket information from a query


Retrieves ticket information via a query.

Base Command

servicenow-query-tickets

Input
Argument Name Description Required
limit Limit for how many tickets to retrieve Optional
ticket_type Ticket type Optional
query The query to run. To learn about querying in ServiceNow, see the ServiceNow documentation. Optional
offset Starting record index to begin retrieving records from Optional

 

Context Output
Path Type Description
Ticket.ID string The unique ticket identifier
Ticket.Creator string A string field that indicates the user who created the ticket
Ticket.CreatedOn date The date and time when the ticket was created
Ticket.Assignee string Specifies the user assigned to complete the ticket. By default, this field uses a reference qualifier to only display users with the itil role.
Ticket.State string Status of the ticket
Ticket.Summary string A human-readable title for the record
Ticket.Number string The display value of the ticket
Ticket.Active boolean Specifies whether work is still being done on a task or whether the work for the task is complete
Ticket.AdditionalComments unknown Comments about the task record
Ticket.Priority string Specifies how high a priority the ticket should be for the assignee
Ticket.OpenedAt date The date and time when the ticket was opened for the first time
Ticket.Escalation string Indicates how long the ticket has been open

 

Command Example

!servicenow-query-tickets limit="10" query="impact<2^short_descriptionISNOTEMPTY" ticket_type="incident"

Context Example
{
    "ServiceNow": {
        "Ticket": [
            {
                "Active": "false",
                "Assignee": "46b87022a9fe198101a78787e40d7547",
                "CloseCode": "Closed/Resolved by Caller",
                "CreatedOn": "2016-09-21 18:24:13",
                "ID": "9c573169c611228700193229fff72400",
                "Number": "INC0000001",
                "OpenedAt": "2018-02-20 23:09:51",
                "OpenedBy": "681ccaf9c0a8016400b98a06818d57c7",
                "Priority": "1 - Critical",
                "ResolvedBy": "9ee1b13dc6112271007f9d0efdb69cd0",
                "State": "7 - Closed",
                "Summary": "Can't read email"
            },
            {
                "Active": "true",
                "Assignee": "46ca0887a9fe19810191e08e51927ebf",
                "CreatedOn": "2016-09-10 22:30:06",
                "ID": "9d385017c611228701d22104cc95c371",
                "Number": "INC0000002",
                "OpenedAt": "2018-02-14 23:07:12",
                "OpenedBy": "681ccaf9c0a8016400b98a06818d57c7",
                "Priority": "1 - Critical",
                "State": "3 - On Hold",
                "Summary": "Network file shares access issue"
            },
            {
                "Active": "true",
                "Assignee": "46d44a23a9fe19810012d100cca80666",
                "CreatedOn": "2016-09-25 14:41:46",
                "ID": "e8caedcbc0a80164017df472f39eaed1",
                "Number": "INC0000003",
                "OpenedAt": "2018-02-21 23:07:30",
                "OpenedBy": "681b365ec0a80164000fb0b05854a0cd",
                "Priority": "1 - Critical",
                "State": "2 - In Progress",
                "Summary": "Wireless access is down in my area"
            },
            {
                "Active": "false",
                "Assignee": "46c6f9efa9fe198101ddf5eed9adf6e7",
                "CloseCode": "Solved (Work Around)",
                "CreatedOn": "2016-09-21 22:34:12",
                "ID": "9d3c1197c611228701cd1d94bc32d76d",
                "Number": "INC0000004",
                "OpenedAt": "2018-02-27 22:49:22",
                "OpenedBy": "681b365ec0a80164000fb0b05854a0cd",
                "Priority": "1 - Critical",
                "ResolvedBy": "9ee1b13dc6112271007f9d0efdb69cd0",
                "State": "7 - Closed",
                "Summary": "Forgot email password"
            },
            {
                "Active": "false",
                "Assignee": "46c6f9efa9fe198101ddf5eed9adf6e7",
                "CloseCode": "Closed/Resolved by Caller",
                "CreatedOn": "2016-09-25 15:14:01",
                "ID": "e8e875b0c0a80164009dc852b4d677d5",
                "Number": "INC0000005",
                "OpenedAt": "2018-02-16 23:06:52",
                "OpenedBy": "6816f79cc0a8016401c5a33be04be441",
                "Priority": "1 - Critical",
                "ResolvedBy": "6816f79cc0a8016401c5a33be04be441",
                "State": "7 - Closed",
                "Summary": "CPU load high for over 10 minutes"
            },
            {
                "Active": "false",
                "Assignee": "46ca0887a9fe19810191e08e51927ebf",
                "CloseCode": "Closed/Resolved by Caller",
                "CreatedOn": "2016-09-18 20:42:01",
                "ID": "8d6246c7c0a80164012fb063cecd4ace",
                "Number": "INC0000006",
                "OpenedAt": "2018-02-20 23:08:05",
                "OpenedBy": "5136503cc611227c0183e96598c4f706",
                "Priority": "1 - Critical",
                "ResolvedBy": "9ee1b13dc6112271007f9d0efdb69cd0",
                "State": "7 - Closed",
                "Summary": "Hangs when trying to print VISIO document"
            },
            {
                "Active": "true",
                "Assignee": "5137153cc611227c000bbd1bd8cd2007",
                "CreatedOn": "2014-03-10 20:43:10",
                "ID": "8d6353eac0a8016400d8a125ca14fc1f",
                "Number": "INC0000007",
                "OpenedAt": "2015-08-12 23:08:24",
                "OpenedBy": "681ccaf9c0a8016400b98a06818d57c7",
                "Priority": "1 - Critical",
                "State": "3 - On Hold",
                "Summary": "Need access to sales DB for the West"
            },
            {
                "Active": "false",
                "Assignee": "681b365ec0a80164000fb0b05854a0cd",
                "CloseCode": "Closed/Resolved by Caller",
                "CreatedOn": "2016-09-18 20:43:58",
                "ID": "8d641046c0a80164000bc7c0d3ed46a0",
                "Number": "INC0000008",
                "OpenedAt": "2018-02-28 23:08:39",
                "OpenedBy": "681ccaf9c0a8016400b98a06818d57c7",
                "Priority": "1 - Critical",
                "ResolvedBy": "9ee1b13dc6112271007f9d0efdb69cd0",
                "State": "7 - Closed",
                "Summary": "Printer in my office is out of toner"
            },
            {
                "Active": "false",
                "Assignee": "5137153cc611227c000bbd1bd8cd2007",
                "CloseCode": "Closed/Resolved by Caller",
                "CreatedOn": "2018-03-05 22:51:33",
                "ID": "46b66a40a9fe198101f243dfbc79033d",
                "Number": "INC0000009",
                "OpenedAt": "2018-02-27 22:50:23",
                "OpenedBy": "9ee1b13dc6112271007f9d0efdb69cd0",
                "Priority": "1 - Critical",
                "ResolvedBy": "9ee1b13dc6112271007f9d0efdb69cd0",
                "State": "7 - Closed",
                "Summary": "Reset my password"
            },
            {
                "Active": "false",
                "Assignee": "5137153cc611227c000bbd1bd8cd2007",
                "CloseCode": "Solved (Work Around)",
                "CreatedOn": "2018-03-05 23:18:07",
                "ID": "46cebb88a9fe198101aee93734f9768b",
                "Number": "INC0000013",
                "OpenedAt": "2018-03-04 23:15:58",
                "OpenedBy": "9ee1b13dc6112271007f9d0efdb69cd0",
                "Priority": "1 - Critical",
                "ResolvedBy": "9ee1b13dc6112271007f9d0efdb69cd0",
                "State": "7 - Closed",
                "Summary": "EMAIL is slow when an attachment is involved"
            }
        ]
    }
}
Human Readable Output

image

8. Upload a file to a ticket


Uploads a file to a specified ServiceNow ticket.

Base Command

servicenow-upload-file

Input
Argument Name Description Required
id Ticket system ID Required
ticket_type Ticket type Optional
file_id War Room entry ID that includes the file Required
file_name File name of uploaded file to override the existing file name in entry Optional

 

Context Output
Path Type Description
Ticket.File.Filename string Name of the file
Ticket.File.Link string Download link for the file
Ticket.File.SystemID string System ID of the file

 

Command Example

!servicenow-upload-file file_id=8781@cc00e449-9e7b-4609-8a68-1c8c01114562 id=bd5d42994f82230021ae045e9310c7bd

Context Example
{
    "File": {
        "EntryID": "8786@cc00e449-9e7b-4609-8a68-1c8c01114562",
        "Extension": "txt",
        "Info": "text/plain; charset=utf-8",
        "MD5": "ea499981fdaadbd1a26250b0d925bf43",
        "Name": "file.txt",
        "SHA1": "7ca8be259038fbc1c10ab953363114f3e222f842",
        "SHA256": "02790f5a87eaa10abf8023268ea576d7a72acaab77e0a8371e44491b357728bc",
        "SSDeep": "384:fBr9CRIuXUA+m0x/nL8LuyahOMKFE3qw3db27XUS354AI3NUrXG:5r9i0xfL8LuyahOMK+3z3db2L354vdiW",
        "Size": 51080,
        "Type": "ASCII text\n"
    },
    "ServiceNow": {
        "Ticket": {
            "File": {
                "Filename": "file.txt",
                "Link": "https://dev56245.service-now.com/api/now/attachment/8cc29e1d4f82230021ae045e9310c723/file",
                "SystemID": "8cc29e1d4f82230021ae045e9310c723"
            },
            "ID": "bd5d42994f82230021ae045e9310c7bd"
        }
    }
}
Human Readable Output

image

9. Get record information


Retrieves information for a specified record.

Base Command

servicenow-get-record

Input
Argument Name Description Required
id Record system ID Required
fields Comma-separated table fields to display and output to the context, e.g., name,tag,company. ID field is added by default. Optional
table_name The name of the table to get the record from Required

 

Context Output
Path Type Description
ServiceNow.Record.ID string The unique record identifier for the record
ServiceNow.Record.UpdatedBy string A string field that indicates the user who most recently updated the record
ServiceNow.Record.UpdatedAt date A time-stamp field that indicates the date and time of the most recent update
ServiceNow.Record.CreatedBy string A string field that indicates the user who created the record
ServiceNow.Record.CreatedOn date A time-stamp field that indicates when a record was created

 

Command Example

!servicenow-get-record table_name=alm_asset id=00a96c0d3790200044e0bfc8bcbe5dc3 fields=asset_tag,sys_updated_by,display_name

Context Example
{
    "ServiceNow": {
        "Record": {
            "ID": "00a96c0d3790200044e0bfc8bcbe5dc3",
            "asset_tag": "P1000479",
            "display_name": "P1000479 - Apple MacBook Pro 15\"",
            "sys_updated_by": "system"
        }
    }
}
Human Readable Output

image

10. Query a table


Queries a specified table in ServiceNow.

Base Command

servicenow-query-table

Input
Argument Name Description Required
table_name The name of the table to query Required
limit Limit for how many tickets to retrieve Optional
query The query to run. For more information about querying in ServiceNow, see the ServiceNow documentation. Optional
fields Comma-separated table fields to display and output to the context, e.g., name,tag,company. ID field is added by default. Optional
offset Starting record index to begin retrieving records from Optional

 

Context Output
Path Type Description
ServiceNow.Results.ID string The unique record identifier for the record
ServiceNow.Results.UpdatedBy string A string field that indicates the user who most recently updated the record
ServiceNow.Results.UpdatedAt date A time-stamp field that indicates the date and time of the most recent update
ServiceNow.Results.CreatedBy string A string field that indicates the user who created the record
ServiceNow.Results.CreatedOn date A time-stamp field that indicates when a record was created

 

Command Example
!servicenow-query-table table_name=alm_asset fields=asset_tag,sys_updated_by,display_name query=display_nameCONTAINSMacBook
Context Example
{
    "ServiceNow": {
        "Record": [
            {
                "ID": "00a96c0d3790200044e0bfc8bcbe5dc3",
                "asset_tag": "P1000479",
                "display_name": "P1000479 - Apple MacBook Pro 15\"",
                "sys_updated_by": "system"
            },
            {
                "ID": "01a92c0d3790200044e0bfc8bcbe5d36",
                "asset_tag": "P1000807",
                "display_name": "P1000807 - Apple MacBook Pro 17\"",
                "sys_updated_by": "system"
            },
            {
                "ID": "01a9a80d3790200044e0bfc8bcbe5d53",
                "asset_tag": "P1000637",
                "display_name": "P1000637 - Apple MacBook Air 13\"",
                "sys_updated_by": "system"
            },
            {
                "ID": "02a9a80d3790200044e0bfc8bcbe5dba",
                "asset_tag": "P1000412",
                "display_name": "P1000412 - Apple MacBook Pro 17\"",
                "sys_updated_by": "system"
            },
            {
                "ID": "02a9ac0d3790200044e0bfc8bcbe5d5e",
                "asset_tag": "P1000563",
                "display_name": "P1000563 - Apple MacBook Pro 15\"",
                "sys_updated_by": "system"
            },
            {
                "ID": "03a9680d3790200044e0bfc8bcbe5d4c",
                "asset_tag": "P1000626",
                "display_name": "P1000626 - Apple MacBook Air 13\"",
                "sys_updated_by": "system"
            },
            {
                "ID": "03a9ac0d3790200044e0bfc8bcbe5d9d",
                "asset_tag": "P1000551",
                "display_name": "P1000551 - Apple MacBook Pro 15\"",
                "sys_updated_by": "system"
            },
            {
                "ID": "04a96c0d3790200044e0bfc8bcbe5db3",
                "asset_tag": "P1000503",
                "display_name": "P1000503 - Apple MacBook Pro 15\"",
                "sys_updated_by": "system"
            },
            {
                "ID": "04a9a80d3790200044e0bfc8bcbe5d1b",
                "asset_tag": "P1000640",
                "display_name": "P1000640 - Apple MacBook Air 13\"",
                "sys_updated_by": "system"
            },
            {
                "ID": "04a9e80d3790200044e0bfc8bcbe5d41",
                "asset_tag": "P1000815",
                "display_name": "P1000815 - Apple MacBook Pro 17\"",
                "sys_updated_by": "system"
            }
        ]
    }
}
Human Readable Output

image

11. Create a record in a table


Creates a new record in a specified ServiceNow table.

Base Command

servicenow-create-record

Input
Argument Name Description Required
table_name The name of the table to create a record in Required
fields Fields and their values to create the record with, in the format: fieldname1=value;fieldname2=value;... Optional
custom_fields Custom(user defined) fields in the format: fieldname1=value;fieldname2=value;... Optional
template Template name to use as a base to create new records Optional

 

Context Output
Path Type Description
ServiceNow.Record.ID string The unique record identifier for the record
ServiceNow.Record.UpdatedBy string A string field that indicates the user who most recently updated the record
ServiceNow.Record.UpdatedAt date A time-stamp field that indicates the date and time of the most recent update
ServiceNow.Record.CreatedBy string A string field that indicates the user who created the record
ServiceNow.Record.CreatedOn date time-stamp field that indicates when a record was created

 

Command Example
!servicenow-create-record table_name=incident fields="short_description=My description;assigned_to=my user"
Context Example
{
    "ServiceNow": {
        "Record": {
            "CreatedAt": "2018-11-27 12:51:44",
            "CreatedBy": "admin",
            "ID": "748692114fc2230021ae045e9310c7ff",
            "UpdatedAt": "2018-11-27 12:51:44",
            "UpdatedBy": "admin"
        }
    }
}
Human Readable Output

image

12. Update a record in a table


Updates a record in a specified ServiceNow table.

Base Command

servicenow-update-record

Input
Argument Name Description Required
table_name The name of the table to update the record in Required
id The system ID of the ticket to update Required
fields Fields and their values to update in the record, in the format: fieldname1=value;fieldname2=value;... Optional
custom_fields Custom (user-defined) fields and their values to update in the record, in the format: fieldname1=value;fieldname2=value;... Optional

 

Context Output
Path Type Description
ServiceNow.Record.ID string The unique record identifier for the record.
ServiceNow.Record.UpdatedBy string A string field that indicates the user who most recently updated the record.
ServiceNow.Record.UpdatedAt date A time-stamp field that indicates the date and time of the most recent update.
ServiceNow.Record.CreatedBy string A string field that indicates the user who created the record.
ServiceNow.Record.CreatedOn date time-stamp field that indicates when a record was created.

 

Command Example

!servicenow-update-record table_name=incident id=748692114fc2230021ae045e9310c7ff fields="short_description=new desc;assigned_to=new user"

Context Example
{
    "ServiceNow": {
        "Record": {
            "CreatedAt": "2018-11-27 12:51:44",
            "CreatedBy": "admin",
            "ID": "748692114fc2230021ae045e9310c7ff",
            "UpdatedAt": "2018-11-27 12:53:48",
            "UpdatedBy": "admin"
        }
    }
}
Human Readable Output

image

13. Delete a record from a table


Deletes a record from a specified ServiceNow table.

Base Command

servicenow-delete-record

Input
Argument Name Description Required
table_name The table name Required
id The system ID of the ticket to delete Required

 

Context Output

There is no context output for this command.

Command Example
!servicenow-delete-record id=748692114fc2230021ae045e9310c7ff table_name=incident
Human Readable Output

image

14. List API fiels for a table


Lists API fields for a specified ServiceNow table.

Base Command

servicenow-list-table-fields

Input
Argument Name Description Required
table_name Table name Required
 
Context Output
Path Type Description
ServiceNow.Field string Table API field name

 

Command Example
!servicenow-list-table-fields table_name=alm_asset
Context Example
{
    "ServiceNow": {
        "Field": [
            {
                "Name": "reserved_for"
            },
            {
                "Name": "expenditure_type"
            },
            {
                "Name": "request_line"
            },
            {
                "Name": "checked_in"
            },
            {
                "Name": "sys_tags"
            },
            {
                "Name": "sys_created_on"
            },
            {
                "Name": "gl_account"
            },
            {
                "Name": "salvage_value"
            },
            {
                "Name": "ci"
            },
            {
                "Name": "asset_tag"
            },
            {
                "Name": "managed_by"
            },
            {
                "Name": "assigned_to"
            },
            {
                "Name": "install_status"
            },
            {
                "Name": "cost_center"
            },
            {
                "Name": "old_substatus"
            },
            {
                "Name": "pre_allocated"
            },
            {
                "Name": "display_name"
            },
            {
                "Name": "justification"
            },
            {
                "Name": "skip_sync"
            },
            {
                "Name": "sys_updated_by"
            },
            {
                "Name": "sys_created_by"
            },
            {
                "Name": "due"
            },
            {
                "Name": "comments"
            },
            {
                "Name": "beneficiary"
            },
            {
                "Name": "depreciated_amount"
            },
            {
                "Name": "lease_id"
            },
            {
                "Name": "sys_id"
            },
            {
                "Name": "purchase_date"
            },
            {
                "Name": "work_notes"
            },
            {
                "Name": "department"
            },
            {
                "Name": "serial_number"
            },
            {
                "Name": "location"
            },
            {
                "Name": "residual_date"
            },
            {
                "Name": "sys_class_name"
            },
            {
                "Name": "warranty_expiration"
            },
            {
                "Name": "vendor"
            },
            {
                "Name": "parent"
            },
            {
                "Name": "checked_out"
            },
            {
                "Name": "company"
            },
            {
                "Name": "sys_updated_on"
            },
            {
                "Name": "resale_price"
            },
            {
                "Name": "residual"
            },
            {
                "Name": "supported_by"
            },
            {
                "Name": "sys_mod_count"
            },
            {
                "Name": "disposal_reason"
            },
            {
                "Name": "invoice_number"
            },
            {
                "Name": "old_status"
            },
            {
                "Name": "sys_domain_path"
            },
            {
                "Name": "retired"
            },
            {
                "Name": "assigned"
            },
            {
                "Name": "depreciation"
            },
            {
                "Name": "model_category"
            },
            {
                "Name": "po_number"
            },
            {
                "Name": "acquisition_method"
            },
            {
                "Name": "due_in"
            },
            {
                "Name": "depreciation_date"
            },
            {
                "Name": "install_date"
            },
            {
                "Name": "substatus"
            },
            {
                "Name": "order_date"
            },
            {
                "Name": "cost"
            },
            {
                "Name": "support_group"
            },
            {
                "Name": "stockroom"
            },
            {
                "Name": "retirement_date"
            },
            {
                "Name": "sys_domain"
            },
            {
                "Name": "owned_by"
            },
            {
                "Name": "model"
            },
            {
                "Name": "delivery_date"
            },
            {
                "Name": "quantity"
            }
        ]
    }
}
Human Readable Output

image

15. Query computers


Queries the cmdb_ci_computer table in ServiceNow.

Base Command

servicenow-query-computers

Input
Argument Name Description Required
computer_id Query by computer sys_id Optional
computer_name Query by computer name Optional
query Query by specified query, for more information about querying in ServiceNow, see https://docs.servicenow.com/bundle/istanbul-servicenow-platform/page/use/common-ui-elements/reference/r_OpAvailableFiltersQueries.html Optional
asset_tag Query by asset tag Optional
limit Query results limit Optional
offset Starting record index to begin retrieving records from Optional

 

Context Output
Path Type Description
ServiceNow.Computer.ID string Computer sys_id
ServiceNow.Computer.AssetTag string Computer Asset tag
ServiceNow.Computer.Name string Computer name
ServiceNow.Computer.DisplayName string Computer display name
ServiceNow.Computer.SupportGroup string Computer support group
ServiceNow.Computer.OperatingSystem string Computer operating system
ServiceNow.Computer.Company string Computer company sys_id
ServiceNow.Computer.AssignedTo string Computer assigned to user sys_id
ServiceNow.Computer.State string Computer state
ServiceNow.Computer.Cost string Computer cost
ServiceNow.Computer.Comments string Computer comments

 

Command Example

!servicenow-query-computers

Context Example
{
    "ServiceNow": {
        "Computer": [
            {
                "AssetTag": "P1000503",
                "AssignedTo": "92826bf03710200044e0bfc8bcbe5dbb",
                "Company": "81fbfe03ac1d55eb286d832de58ae1fd",
                "Cost": "1799.99 USD",
                "DisplayName": "P1000503 - MacBook Pro 15\"",
                "ID": "00a96c0d3790200044e0bfc8bcbe5db4",
                "Name": "MacBook Pro 15\"",
                "OperatingSystem": "Mac OS 10 (OS/X)",
                "State": "In use"
            },
            {
                "AssetTag": "P1000640",
                "AssignedTo": "3682abf03710200044e0bfc8bcbe5d09",
                "Company": "81fca4cbac1d55eb355b4b6db0e3c80f",
                "Cost": "1599.99 USD",
                "DisplayName": "P1000640 - MacBook Air 13\"",
                "ID": "00a9a80d3790200044e0bfc8bcbe5d1c",
                "Name": "MacBook Air 13\"",
                "OperatingSystem": "Mac OS 10 (OS/X)",
                "State": "In use"
            },
            {
                "AssetTag": "P1000815",
                "AssignedTo": "86826bf03710200044e0bfc8bcbe5d42",
                "Company": "81fd65ecac1d55eb42a426568fc87a63",
                "Cost": "2499.99 USD",
                "DisplayName": "P1000815 - MacBook Pro 17\"",
                "ID": "00a9e80d3790200044e0bfc8bcbe5d42",
                "Name": "MacBook Pro 17\"",
                "OperatingSystem": "Mac OS 10 (OS/X)",
                "State": "In use"
            },
            {
                "AssetTag": "P1000814",
                "AssignedTo": "4e826bf03710200044e0bfc8bcbe5d41",
                "Company": "81fd65ecac1d55eb42a426568fc87a63",
                "Cost": "2499.99 USD",
                "DisplayName": "P1000814 - MacBook Pro 17\"",
                "ID": "00a9e80d3790200044e0bfc8bcbe5de8",
                "Name": "MacBook Pro 17\"",
                "OperatingSystem": "Mac OS 10 (OS/X)",
                "State": "In use"
            },
            {
                "AssetTag": "P1000517",
                "AssignedTo": "12826bf03710200044e0bfc8bcbe5dc3",
                "Company": "81fca4cbac1d55eb355b4b6db0e3c80f",
                "Cost": "1799.99 USD",
                "DisplayName": "P1000517 - MacBook Pro 15\"",
                "ID": "00a9ec0d3790200044e0bfc8bcbe5d06",
                "Name": "MacBook Pro 15\"",
                "OperatingSystem": "Mac OS 10 (OS/X)",
                "State": "In use"
            },
            {
                "AssetTag": "P1000666",
                "AssignedTo": "fe82abf03710200044e0bfc8bcbe5d18",
                "Company": "a66b1fb03710200044e0bfc8bcbe5d08",
                "Cost": "1599.99 USD",
                "DisplayName": "P1000666 - MacBook Air 13\"",
                "ID": "01a9280d3790200044e0bfc8bcbe5d79",
                "Name": "MacBook Air 13\"",
                "OperatingSystem": "Mac OS 10 (OS/X)",
                "State": "In use"
            },
            {
                "AssetTag": "P1000871",
                "AssignedTo": "4e826bf03710200044e0bfc8bcbe5d6f",
                "Company": "31bea3d53790200044e0bfc8bcbe5dec",
                "Cost": "2499.99 USD",
                "DisplayName": "P1000871 - MacBook Pro 17\"",
                "ID": "01a92c0d3790200044e0bfc8bcbe5d27",
                "Name": "MacBook Pro 17\"",
                "OperatingSystem": "Mac OS 10 (OS/X)",
                "State": "In use"
            },
            {
                "AssetTag": "P1000429",
                "AssignedTo": "c6826bf03710200044e0bfc8bcbe5d90",
                "Company": "4b7d13f03710200044e0bfc8bcbe5db6",
                "Cost": "2249.99 USD",
                "DisplayName": "P1000429 - ThinkStation C20",
                "ID": "01a9e40d3790200044e0bfc8bcbe5dab",
                "Name": "ThinkStation C20",
                "OperatingSystem": "Windows XP Professional",
                "State": "In use"
            },
            {
                "AssetTag": "P1000440",
                "AssignedTo": "8a826bf03710200044e0bfc8bcbe5d96",
                "Company": "81fca4cbac1d55eb355b4b6db0e3c80f",
                "Cost": "1699.99 USD",
                "DisplayName": "P1000440 - ThinkStation S20",
                "ID": "01a9ec0d3790200044e0bfc8bcbe5dc3",
                "Name": "ThinkStation S20",
                "OperatingSystem": "Windows XP Professional",
                "State": "In use"
            },
            {
                "AssetTag": "P1000357",
                "AssignedTo": "ce826bf03710200044e0bfc8bcbe5d67",
                "Company": "a66b1fb03710200044e0bfc8bcbe5d08",
                "Cost": "1329 USD",
                "DisplayName": "P1000357 - Precision T5500 Workstation",
                "ID": "02a9604d3790200044e0bfc8bcbe5d17",
                "Name": "Precision T5500 Workstation",
                "OperatingSystem": "Windows XP Professional",
                "State": "In use"
            }
        ]
    }
}
Human Readable Output

image

16. Query groups


Queries the sys_user_group table in ServiceNow.

Base Command

servicenow-query-groups

Input
Argument Name Description Required
group_id Query by group sys_id Optional
group_name Query by group name Optional
query Query by specified query, for more information about querying in ServiceNow, see the ServiceNow documentation. Optional
limit Query results limit Optional
offset Starting record index to begin retrieving records from Optional

 

Context Output
Path Type Description
ServiceNow.Group.ID string Group sys_id
ServiceNow.Group.Description string Group description
ServiceNow.Group.Name string Group name
ServiceNow.Group.Manager string Group manager sys_id
ServiceNow.Group.Updated date Group update time

 

Command Example

!servicenow-query-groups group_name=test1

Context Example
{
    "ServiceNow": {
        "Group": {
            "Active": "true",
            "Description": "Incident Management Group",
            "ID": "12a586cd0bb23200ecfd818393673a30",
            "Name": "test1",
            "Updated": "2018-11-26 14:51:41"
        }
    }
}
Human Readable Output

image

17. Query users


Queries the sys_user table in ServiceNow.

Base Command

servicenow-query-users

Input
Argument Name Description Required
user_id Query by user sys_id Optional
user_name Query by username Optional
query Query by specified query, for more information about querying in ServiceNow, see the ServiceNow documentation. Optional
limit Query results limit Optional
offset Starting record index to begin retrieving records from Optional

 

Context Output
Path Type Description
ServiceNow.User.ID string User sys_id
ServiceNow.User.Name string User name (first, middle, last)
ServiceNow.User.UserName string User username
ServiceNow.User.Email string User email
ServiceNow.User.Created date User creation time
ServiceNow.User.Updated date User update time

 

Command Example
!servicenow-query-users user_name=sean.bonnet
Context Example
{
    "ServiceNow": {
        "User": {
            "Created": "2012-02-18 03:04:50",
            "Email": "sean.bonnet@example.com",
            "ID": "02826bf03710200044e0bfc8bcbe5d6d",
            "Name": "Sean Testman",
            "Updated": "2018-08-12 22:18:39",
            "UserName": "sean.bonnet"
        }
    }
}
Human Readable Output

image

18. Get table names


Retrieves table names by a label to use in commands.

Base Command
servicenow-get-table-name
Input
Argument Name Description Required
label The table label, e.g., Asset, Incident, IP address, etc. Required
limit The number of results to return Optional
offset Starting record index to begin retrieving records from Optional

 

Context Output
Path Type Description
ServiceNow.Table.ID string Table system ID
ServiceNow.Table.Name string Table name to use in commands, e.g., alm_asset
ServiceNow.Table.SystemName string Table system name, e.g., Asset
 
Command Example
!servicenow-get-table-name label=Asset
Context Example
{
    "ServiceNow": {
        "Table": {
            "ID": "56cc519d5d521300651727c3e282e819",
            "Name": "alm_asset",
            "SystemName": "Asset"
        }
    }
}
Human Readable Output

image

19. Get ticket notes


Returns notes for a specified ticket.

Base Command

servicenow-get-ticket-notes

Input
Argument Name Description Required
id Ticket System ID Required
limit Limit for the ticket notes Optional
offset Offset of the ticket notes Optional

 

Context Output
Path Type Description
ServiceNow.Ticket.ID string Ticket ID
ServiceNow.Ticket.Note.Value unknown Ticket note value
ServiceNow.Ticket.Note.CreatedOn date Ticket note created on date
ServiceNow.Ticket.Note.CreatedBy string Ticket note created by
ServiceNow.Ticket.Note.Type string Ticket note type

 

Command Example

!servicenow-get-ticket-notes id=9c573169c611228700193229fff72400

Context Example
{
    "ServiceNow": {
        "Ticket": {
            "ID": "9c573169c611228700193229fff72400",
            "Note": [
                {
                    "CreatedBy": "admin",
                    "CreatedOn": "2019-01-31 08:40:56",
                    "Type": "Comment",
                    "Value": "test"
                },
                {
                    "CreatedBy": "admin",
                    "CreatedOn": "2019-01-31 11:09:14",
                    "Type": "Work Note",
                    "Value": "hmm"
                }
            ]
        }
    }
Human Readable Output

image

 

Additional Information

The tables and fields in the ServiceNow UI are different than those in the API. Each table and field name in the UI have their representation in the API.

The integration allows viewing the API table name with the servicenow-get-table command and the API fields(columns) for each table using the servicenow-list-fields command, given the table has records.

In the API, fields that are linked to records in a table (e.g., user) return as an object of link and value as the link refers to the link to that record in the API and the value refers to the system ID of the record. By default, the integration retrieves the value of the object - which means its system ID.

Troubleshooting

If there is an error within ServiceNow, the message is usually indicative, e.g., User Not Authenticated, details: Required to provide Auth information Invalid table or No Record found, details: Record doesn't exist or ACL restricts the record retrieval.

However there may be connection issues, e.g., when the ServiceNow server URL is incorrect or is unreachable.

Configuration parameters

  • url — ServiceNow URL, in the format https://company.service-now.com/ (required)
  • credentials — Username
  • proxy — Use system proxy settings
  • insecure — Trust any certificate (not secure)
  • ticket_type — Default ticket type for running ticket commands and fetching incidents
  • api_version — ServiceNow API Version (e.g. 'v1')
  • isFetch — Fetch incidents
  • sysparm_query — The query to use when fetching incidents
  • fetch_limit — How many incidents to fetch each time
  • fetch_time — First fetch timestamp (<number> <time unit>, e.g., 12 hours, 7 days, 3 months, 1 year)
  • timestamp_field — Timestamp field to filter by (e.g., `opened_at`) This is how the filter is applied to the query: “ORDERBYopened_at^opened_at>[Last Run]. To prevent duplicate incidents, this field is mandatory for fetching incidents.
  • incidentType — Incident type
  • get_attachments — Get incident attachments

Commands (31)

  • servicenow-add-comment Deprecated

    Add comment to specific ticket by providing ticket id

  • servicenow-add-link Deprecated

    Add a link to specific ticket

  • servicenow-create Deprecated

    Deprecated. Use servicenow-create-ticket or servicenow-create-record instead

  • servicenow-create-record Deprecated

    Create a new record in a specified ServiceNow table

  • servicenow-create-ticket Deprecated

    Create new ServiceNow ticket

  • servicenow-delete-record Deprecated

    Delete a record in a specified ServiceNow table

  • servicenow-delete-ticket Deprecated

    Delete a ticket from ServiceNow

  • servicenow-get Deprecated

    Deprecated. Use servicenow-get-ticket or servicenow-get-record instead

  • servicenow-get-computer Deprecated

    Deprecated. Use servicenow-query-computers instead.

  • servicenow-get-groups Deprecated

    Deprecated. Use servicenow-query-groups instead.

  • servicenow-get-record Deprecated

    Retrieve record information by specific record ID

  • servicenow-get-table-name Deprecated

    Get table names by a label to use in commands

  • servicenow-get-ticket Deprecated

    Retrieve ticket information by specific ticket ID

  • servicenow-get-ticket-notes Deprecated

    Get notes from the specified ServiceNow ticket - Read permissions are required for the sys_journal_field table.

  • servicenow-incident-add-comment Deprecated

    Deprecated. Use servicenow-add-comment instead.

  • servicenow-incident-add-link Deprecated

    Deprecated. Use servicenow-add-link instead.

  • servicenow-incident-create Deprecated

    Create a new ServiceNow ticket

  • servicenow-incident-update Deprecated

    Deprecated. Use servicenow-update-ticket or servicenow-update-record instead

  • servicenow-incident-upload-file Deprecated

    Deprecated. Use servicenow-upload-file instead.

  • servicenow-incidents-query Deprecated

    Deprecated. Use servicenow-query-table or servicenow-query-tickets instead.

  • servicenow-list-table-fields Deprecated

    List API fields for a specified ServiceNow table

  • servicenow-query Deprecated

    Deprecated. Use servicenow-query-table or servicenow-query-tickets instead.

  • servicenow-query-computers Deprecated

    Query the cmdb_ci_computer table in ServiceNow

  • servicenow-query-groups Deprecated

    Query the sys_user_group table in ServiceNow

  • servicenow-query-table Deprecated

    Query a specified table in ServiceNow

  • servicenow-query-tickets Deprecated

    Retrieve ticket info with a query

  • servicenow-query-users Deprecated

    Query the sys_user table in ServiceNow

  • servicenow-update Deprecated

    Deprecated. Use servicenow-update-ticket or servicenow-update-record instead

  • servicenow-update-record Deprecated

    Update a record in a specified ServiceNow table

  • servicenow-update-ticket Deprecated

    Update specific ticket

  • servicenow-upload-file Deprecated

    Upload a file to a specific ticket

import demistomock as demisto
from CommonServerPython import *
from CommonServerUserPython import *
import re
import requests
import json
from datetime import datetime
import shutil

# disable insecure warnings
requests.packages.urllib3.disable_warnings()

if not demisto.params().get('proxy', False):
    del os.environ['HTTP_PROXY']
    del os.environ['HTTPS_PROXY']
    del os.environ['http_proxy']
    del os.environ['https_proxy']


def get_server_url():
    url = demisto.params()['url']
    url = re.sub('/[\/]+$/', '', url)
    url = re.sub('\/$', '', url)
    return url


''' GLOBAL VARIABLES '''

DEFAULTS = {
    'limit': 10,
    'offset': 0,
    'fetch_limit': 10,
    'fetch_time': '10 minutes',
    'ticket_type': 'incident'
}

USERNAME = demisto.params()['credentials']['identifier']
PASSWORD = demisto.params()['credentials']['password']
VERIFY_SSL = not demisto.params().get('insecure', False)
API = '/api/now/'
VERSION = demisto.params().get('api_version')
PARAMS_TICKET_TYPE = demisto.params().get('ticket_type', DEFAULTS['ticket_type'])
FETCH_TIME = demisto.params().get('fetch_time').strip()
SYSPARM_QUERY = demisto.params().get('sysparm_query')
SYSPARM_LIMIT = demisto.params().get('fetch_limit', DEFAULTS['fetch_limit'])
TIMESTAMP_FIELD = demisto.params().get('timestamp_field', 'opened_at')
TICKET_TYPE = demisto.params().get('ticket_type', DEFAULTS['ticket_type'])
GET_ATTACHMENTS = demisto.params().get('get_attachments', False)

if VERSION:
    API += VERSION + '/'

SERVER_URL = get_server_url() + API

TICKET_STATES = {
    'incident': {
        '1': '1 - New',
        '2': '2 - In Progress',
        '3': '3 - On Hold',
        '4': '4 - Awaiting Caller',
        '5': '5 - Awaiting Evidence',
        '6': '6 - Resolved',
        '7': '7 - Closed',
        '8': '8 - Canceled'
    },
    'problem': {
        '1': '1 - Open',
        '2': '2 - Known Error',
        '3': '3 - Pending Change',
        '4': '4 - Closed/Resolved'
    },
    'change_request': {
        '-5': '-5 - New',
        '-4': '-4 - Assess',
        '-3': '-3 - Authorize',
        '-2': '-2 - Scheduled',
        '-1': '-1 - Implement',
        '0': '0 - Review',
        '3': '3 - Closed',
        '4': '4 - Canceled'
    },
    'sc_task': {
        '-5': '-5 - Pending',
        '1': '1 - Open',
        '2': '2 - Work In Progress',
        '3': '3 - Closed Complete',
        '4': '4 - Closed Incomplete',
        '7': '7 - Closed Skipped'
    },
    'sc_request': {
        '1': '1 - Approved',
        '3': '3 - Closed',
        '4': '4 - Rejected'
    }
}

TICKET_SEVERITY = {
    '1': '1 - High',
    '2': '2 - Medium',
    '3': '3 - Low'
}

TICKET_PRIORITY = {
    '1': '1 - Critical',
    '2': '2 - High',
    '3': '3 - Moderate',
    '4': '4 - Low',
    '5': '5 - Planning'
}

COMPUTER_STATUS = {
    '1': 'In use',
    '2': 'On order',
    '3': 'On maintenance',
    '6': 'In stock/In transit',
    '7': 'Retired',
    '100': 'Missing'
}

# Map SNOW severity to Demisto severity for incident creation
SEVERITY_MAP = {
    '1': 3,
    '2': 2,
    '3': 1
}

SNOW_ARGS = ['active', 'activity_due', 'opened_at', 'short_description', 'additional_assignee_list', 'approval_history',
             'approval_set', 'assigned_to', 'assignment_group',
             'business_duration', 'business_service', 'business_stc', 'calendar_duration', 'calendar_stc', 'caller_id',
             'caused_by', 'close_code', 'close_notes',
             'closed_at', 'closed_by', 'cmdb_ci', 'comments', 'comments_and_work_notes', 'company', 'contact_type',
             'correlation_display', 'correlation_id',
             'delivery_plan', 'delivery_task', 'description', 'due_date', 'expected_start', 'follow_up', 'group_list',
             'hold_reason', 'impact', 'incident_state',
             'knowledge', 'location', 'made_sla', 'notify', 'order', 'parent', 'parent_incident', 'priority',
             'problem_id', 'resolved_at', 'resolved_by', 'rfc',
             'severity', 'sla_due', 'state', 'subcategory', 'sys_tags', 'time_worked', 'urgency', 'user_input',
             'watch_list', 'work_end', 'work_notes', 'work_notes_list',
             'work_start', 'impact', 'incident_state', 'title', 'type', 'change_type', 'category', 'state', 'caller']

# Every table in ServiceNow should have those fields
DEFAULT_RECORD_FIELDS = {
    'sys_id': 'ID',
    'sys_updated_by': 'UpdatedBy',
    'sys_updated_on': 'UpdatedAt',
    'sys_created_by': 'CreatedBy',
    'sys_created_on': 'CreatedAt'
}

DEPRECATED_COMMANDS = ['servicenow-get', 'servicenow-incident-get',
                       'servicenow-create', 'servicenow-incident-create',
                       'servicenow-update', 'servicenow-query',
                       'servicenow-incidents-query', 'servicenow-incident-update']

''' HELPER FUNCTIONS '''


def send_request(path, method='get', body=None, params=None, headers=None, file=None):
    body = body if body is not None else {}
    params = params if params is not None else {}

    url = '{}{}'.format(SERVER_URL, path)
    if not headers:
        headers = {
            'Accept': 'application/json',
            'Content-Type': 'application/json'
        }
    if file:
        # Not supported in v2
        url = url.replace('v2', 'v1')
        try:
            file_entry = file['id']
            file_name = file['name']
            shutil.copy(demisto.getFilePath(file_entry)['path'], file_name)
            with open(file_name, 'rb') as f:
                files = {'file': f}
                res = requests.request(method, url, headers=headers, params=params, data=body, files=files,
                                       auth=(USERNAME, PASSWORD), verify=VERIFY_SSL)
            shutil.rmtree(demisto.getFilePath(file_entry)['name'], ignore_errors=True)
        except Exception as e:
            raise Exception('Failed to upload file - ' + str(e))
    else:
        res = requests.request(method, url, headers=headers, data=json.dumps(body) if body else {}, params=params,
                               auth=(USERNAME, PASSWORD), verify=VERIFY_SSL)

    try:
        obj = res.json()
    except Exception as e:
        if not res.content:
            return ''
        raise Exception('Error parsing reply - {} - {}'.format(res.content, str(e)))

    if 'error' in obj:
        message = obj.get('error', {}).get('message')
        details = obj.get('error', {}).get('detail')
        if message == 'No Record found':
            return {
                # Return an empty results array
                'result': []
            }
        raise Exception('ServiceNow Error: {}, details: {}'.format(message, details))

    if res.status_code < 200 or res.status_code >= 300:
        raise Exception('Got status code {} with url {} with body {} with headers {}'
                        .format(str(res.status_code), url, str(res.content), str(res.headers)))

    return obj


def get_table_name(ticket_type=None):
    if ticket_type:
        return ticket_type
    else:
        if PARAMS_TICKET_TYPE:
            return PARAMS_TICKET_TYPE
        else:
            return 'incident'


def create_ticket_context(data, ticket_type):
    context = {
        'ID': data.get('sys_id'),
        'Summary': data.get('short_description'),
        'Number': data.get('number'),
        'CreatedOn': data.get('sys_created_on'),
        'Active': data.get('active'),
        'AdditionalComments': data.get('comments'),
        'CloseCode': data.get('close_code'),
        'OpenedAt': data.get('opened_at')
    }

    # These fields refer to records in the database, the value is their system ID.
    if 'closed_by' in data:
        context['ResolvedBy'] = data['closed_by']['value'] if 'value' in data['closed_by'] else ''
    if 'opened_by' in data:
        context['OpenedBy'] = data['opened_by']['value'] if 'value' in data['opened_by'] else ''
        context['Creator'] = data['opened_by']['value'] if 'value' in data['opened_by'] else ''
    if 'assigned_to' in data:
        context['Assignee'] = data['assigned_to']['value'] if 'value' in data['assigned_to'] else ''

    # Try to map fields
    if 'priority' in data:
        # Backward compatibility
        if demisto.command() in DEPRECATED_COMMANDS:
            context['Priority'] = data['priority']
        else:
            context['Priority'] = TICKET_PRIORITY.get(data['priority'], data['priority'])
    if 'state' in data:
        mapped_state = data['state']
        # Backward compatibility
        if demisto.command() not in DEPRECATED_COMMANDS:
            if ticket_type in TICKET_STATES:
                mapped_state = TICKET_STATES[ticket_type].get(data['state'], mapped_state)
        context['State'] = mapped_state

    return createContext(context, removeNull=True)


def get_ticket_context(data, ticket_type):
    if not isinstance(data, list):
        return create_ticket_context(data, ticket_type)

    tickets = []
    for d in data:
        tickets.append(create_ticket_context(d, ticket_type))
    return tickets


def get_ticket_human_readable(tickets, ticket_type):
    if not isinstance(tickets, list):
        tickets = [tickets]

    result = []
    for ticket in tickets:

        hr = {
            'Number': ticket.get('number'),
            'System ID': ticket['sys_id'],
            'Created On': ticket.get('sys_created_on'),
            'Created By': ticket.get('sys_created_by'),
            'Active': ticket.get('active'),
            'Close Notes': ticket.get('close_notes'),
            'Close Code': ticket.get('close_code'),
            'Description': ticket.get('description'),
            'Opened At': ticket.get('opened_at'),
            'Due Date': ticket.get('due_date'),
            # This field refers to a record in the database, the value is its system ID.
            'Resolved By': ticket.get('closed_by', {}).get('value') if isinstance(ticket.get('closed_by'), dict)
            else ticket.get('closed_by'),
            'Resolved At': ticket.get('resolved_at'),
            'SLA Due': ticket.get('sla_due'),
            'Short Description': ticket.get('short_description'),
            'Additional Comments': ticket.get('comments')
        }

        # Try to map the fields
        if 'impact' in ticket:
            hr['Impact'] = TICKET_SEVERITY.get(ticket['impact'], ticket['impact'])
        if 'urgency' in ticket:
            hr['Urgency'] = TICKET_SEVERITY.get(ticket['urgency'], ticket['urgency'])
        if 'severity' in ticket:
            hr['Severity'] = TICKET_SEVERITY.get(ticket['severity'], ticket['severity'])
        if 'priority' in ticket:
            hr['Priority'] = TICKET_PRIORITY.get(ticket['priority'], ticket['priority'])
        if 'state' in ticket:
            mapped_state = ticket['state']
            if ticket_type in TICKET_STATES:
                mapped_state = TICKET_STATES[ticket_type].get(ticket['state'], mapped_state)
            hr['State'] = mapped_state
        result.append(hr)
    return result


def get_ticket_fields(template, ticket_type):
    # Inverse the keys and values of those dictionaries to map the arguments to their corresponding values in ServiceNow
    args = unicode_to_str_recur(demisto.args())
    inv_severity = {v: k for k, v in TICKET_SEVERITY.iteritems()}
    inv_priority = {v: k for k, v in TICKET_PRIORITY.iteritems()}
    states = TICKET_STATES.get(ticket_type)
    inv_states = {v: k for k, v in states.iteritems()} if states else {}

    body = {}
    for arg in SNOW_ARGS:
        input_arg = args.get(arg)
        if input_arg:
            if arg in ['impact', 'urgency', 'severity']:
                body[arg] = inv_severity.get(input_arg, input_arg)
            elif arg == 'priority':
                body[arg] = inv_priority.get(input_arg, input_arg)
            elif arg == 'state':
                body[arg] = inv_states.get(input_arg, input_arg)
            else:
                body[arg] = input_arg
        elif template and arg in template:
            body[arg] = template[arg]

    return body


def get_body(fields, custom_fields):
    body = {}

    if fields:
        for field in fields:
            body[field] = fields[field]

    if custom_fields:
        for field in custom_fields:
            # custom fields begin with "u_"
            if field.startswith('u_'):
                body[field] = custom_fields[field]
            else:
                body['u_' + field] = custom_fields[field]

    return body


def split_fields(fields):
    dic_fields = {}

    if fields:
        # As received by the command
        arr_fields = fields.split(';')

        for f in arr_fields:
            field = f.split('=')
            if len(field) > 1:
                dic_fields[field[0]] = field[1]

    return dic_fields


# Converts unicode elements of obj (incl. dictionary and list) to string recursively
def unicode_to_str_recur(obj):
    if isinstance(obj, dict):
        obj = {unicode_to_str_recur(k): unicode_to_str_recur(v) for k, v in obj.iteritems()}
    elif isinstance(obj, list):
        obj = map(unicode_to_str_recur, obj)
    elif isinstance(obj, unicode):
        obj = obj.encode('utf-8')
    return obj


# Converts to an str
def convert_to_str(obj):
    if isinstance(obj, unicode):
        return obj.encode('utf-8')
    try:
        return str(obj)
    except ValueError:
        return obj


''' FUNCTIONS '''


def get_template(name):
    query_params = {'sysparm_limit': 1, 'sysparm_query': 'name=' + name}

    ticket_type = 'sys_template'
    path = 'table/' + ticket_type
    res = send_request('GET', path, params=query_params)

    if len(res['result']) == 0:
        raise ValueError("Incorrect template name")

    template = res['result'][0]['template'].split('^')
    dic_template = {}

    for i in range(len(template) - 1):
        template_value = template[i].split('=')
        if len(template_value) > 1:
            dic_template[template_value[0]] = template_value[1]

    return dic_template


def get_ticket_command():
    args = unicode_to_str_recur(demisto.args())
    ticket_type = get_table_name(args.get('ticket_type'))
    ticket_id = args.get('id')
    number = args.get('number')
    get_attachments = args.get('get_attachments', 'false')

    res = get(ticket_type, ticket_id, number)
    if not res or 'result' not in res:
        return 'Cannot find ticket'

    if isinstance(res['result'], list):
        if len(res['result']) == 0:
            return 'Cannot find ticket'
        ticket = res['result'][0]
    else:
        ticket = res['result']

    entries = []  # type: List[Dict]

    if get_attachments.lower() != 'false':
        entries = get_ticket_attachment_entries(ticket['sys_id'])

    hr = get_ticket_human_readable(ticket, ticket_type)
    context = get_ticket_context(ticket, ticket_type)

    headers = ['System ID', 'Number', 'Impact', 'Urgency', 'Severity', 'Priority', 'State', 'Created On', 'Created By',
               'Active', 'Close Notes', 'Close Code',
               'Description', 'Opened At', 'Due Date', 'Resolved By', 'Resolved At', 'SLA Due', 'Short Description',
               'Additional Comments']

    entry = {
        'Type': entryTypes['note'],
        'Contents': res,
        'ContentsFormat': formats['json'],
        'ReadableContentsFormat': formats['markdown'],
        'HumanReadable': tableToMarkdown('ServiceNow ticket', hr, headers=headers, removeNull=True),
        'EntryContext': {
            'Ticket(val.ID===obj.ID)': context,
            'ServiceNow.Ticket(val.ID===obj.ID)': context
        }
    }

    entries.append(entry)

    return entries


def get_record_command():
    args = unicode_to_str_recur(demisto.args())
    table_name = args['table_name']
    record_id = args['id']
    fields = args.get('fields')

    res = get(table_name, record_id)

    if not res or 'result' not in res:
        return 'Cannot find record'

    if isinstance(res['result'], list):
        if len(res['result']) == 0:
            return 'Cannot find record'
        record = res['result'][0]
    else:
        record = res['result']

    entry = {
        'Type': entryTypes['note'],
        'Contents': res,
        'ContentsFormat': formats['json']
    }

    if fields:
        fields = argToList(fields)
        if 'sys_id' not in fields:
            # ID is added by default
            fields.append('sys_id')
        # filter the record for the required fields
        record = dict(filter(lambda kv_pair: kv_pair[0] in fields, record.items()))
        for k, v in record.iteritems():
            if isinstance(v, dict):
                # For objects that refer to a record in the database, take their value(system ID).
                record[k] = v.get('value', record[k])
        record['ID'] = record.pop('sys_id')
        entry['ReadableContentsFormat'] = formats['markdown']
        entry['HumanReadable'] = tableToMarkdown('ServiceNow record', record, removeNull=True)
        entry['EntryContext'] = {
            'ServiceNow.Record(val.ID===obj.ID)': createContext(record)
        }
    else:
        mapped_record = {DEFAULT_RECORD_FIELDS[k]: record[k] for k in DEFAULT_RECORD_FIELDS if k in record}
        entry['ReadableContentsFormat'] = formats['markdown']
        entry['HumanReadable'] = tableToMarkdown('ServiceNow record' + record_id, mapped_record, removeNull=True)
        entry['EntryContext'] = {
            'ServiceNow.Record(val.ID===obj.ID)': createContext(mapped_record)
        }

    return entry


def get(table_name, record_id, number=None):
    path = None
    query_params = {}  # type: Dict
    if record_id:
        path = 'table/' + table_name + '/' + record_id
    elif number:
        path = 'table/' + table_name
        query_params = {
            'number': number
        }
    else:
        # Only in cases where the table is of type ticket
        raise ValueError('servicenow-get-ticket requires either ticket ID (sys_id) or ticket number')

    return send_request(path, 'get', params=query_params)


def get_ticket_attachments(ticket_id):
    path = 'attachment'
    query_params = {
        'sysparm_query': 'table_sys_id=' + ticket_id
    }

    return send_request(path, 'get', params=query_params)


def get_ticket_attachment_entries(ticket_id):
    entries = []
    links = []  # type: List[Tuple[str, str]]
    attachments_res = get_ticket_attachments(ticket_id)
    if 'result' in attachments_res and len(attachments_res['result']) > 0:
        attachments = attachments_res['result']
        links = [(attachment['download_link'], attachment['file_name']) for attachment in attachments]

    for link in links:
        file_res = requests.get(link[0], auth=(USERNAME, PASSWORD), verify=VERIFY_SSL)
        if file_res is not None:
            entries.append(fileResult(link[1], file_res.content))

    return entries


def update_ticket_command():
    args = unicode_to_str_recur(demisto.args())
    custom_fields = split_fields(args.get('custom_fields'))
    template = args.get('template')
    ticket_type = get_table_name(args.get('ticket_type'))
    ticket_id = args['id']

    if template:
        template = get_template(template)
    fields = get_ticket_fields(template, ticket_type)

    res = update(ticket_type, ticket_id, fields, custom_fields)

    if not res or 'result' not in res:
        return_error('Unable to retrieve response')

    hr = get_ticket_human_readable(res['result'], ticket_type)
    context = get_ticket_context(res['result'], ticket_type)

    entry = {
        'Type': entryTypes['note'],
        'Contents': res,
        'ContentsFormat': formats['json'],
        'ReadableContentsFormat': formats['markdown'],
        'HumanReadable': tableToMarkdown('ServiceNow ticket updated successfully\nTicket type: ' + ticket_type,
                                         hr, removeNull=True),
        'EntryContext': {
            'ServiceNow.Ticket(val.ID===obj.ID)': context
        }
    }

    return entry


def update_record_command():
    args = unicode_to_str_recur(demisto.args())
    table_name = args['table_name']
    record_id = args['id']
    fields = args.get('fields', {})
    custom_fields = args.get('custom_fields')

    if fields:
        fields = split_fields(fields)
    if custom_fields:
        custom_fields = split_fields(custom_fields)

    res = update(table_name, record_id, fields, custom_fields)

    if not res or 'result' not in res:
        return 'Could not retrieve record'

    result = res['result']

    mapped_record = {DEFAULT_RECORD_FIELDS[k]: result[k] for k in DEFAULT_RECORD_FIELDS if k in result}
    entry = {
        'Type': entryTypes['note'],
        'Contents': res,
        'ContentsFormat': formats['json'],
        'ReadableContentsFormat': formats['markdown'],
        'HumanReadable': tableToMarkdown('ServiceNow record updated successfully', mapped_record, removeNull=True),
        'EntryContext': {
            'ServiceNow.Record(val.ID===obj.ID)': createContext(mapped_record)
        }
    }

    return entry


def update(table_name, record_id, fields, custom_fields):
    body = get_body(fields, custom_fields)
    path = 'table/' + table_name + '/' + record_id

    return send_request(path, 'patch', body=body)


def create_ticket_command():
    args = unicode_to_str_recur(demisto.args())
    custom_fields = split_fields(args.get('custom_fields'))
    template = args.get('template')
    ticket_type = get_table_name(args.get('ticket_type'))

    if template:
        template = get_template(template)
    fields = get_ticket_fields(template, ticket_type)

    res = create(ticket_type, fields, custom_fields)

    if not res or 'result' not in res:
        return_error('Unable to retrieve response')

    hr = get_ticket_human_readable(res['result'], ticket_type)
    context = get_ticket_context(res['result'], ticket_type)

    headers = ['System ID', 'Number', 'Impact', 'Urgency', 'Severity', 'Priority', 'State', 'Created On', 'Created By',
               'Active', 'Close Notes', 'Close Code',
               'Description', 'Opened At', 'Due Date', 'Resolved By', 'Resolved At', 'SLA Due', 'Short Description',
               'Additional Comments']

    entry = {
        'Type': entryTypes['note'],
        'Contents': res,
        'ContentsFormat': formats['json'],
        'ReadableContentsFormat': formats['markdown'],
        'HumanReadable': tableToMarkdown('ServiceNow ticket created successfully', hr,
                                         headers=headers, removeNull=True),
        'EntryContext': {
            'Ticket(val.ID===obj.ID)': context,
            'ServiceNow.Ticket(val.ID===obj.ID)': context
        }
    }

    return entry


def create_record_command():
    args = unicode_to_str_recur(demisto.args())
    table_name = args['table_name']
    fields = args.get('fields')
    custom_fields = args.get('custom_fields')

    if fields:
        fields = split_fields(fields)
    if custom_fields:
        custom_fields = split_fields(custom_fields)

    res = create(table_name, fields, custom_fields)

    if not res or 'result' not in res:
        return 'Could not retrieve record'

    result = res['result']

    mapped_record = {DEFAULT_RECORD_FIELDS[k]: result[k] for k in DEFAULT_RECORD_FIELDS if k in result}
    entry = {
        'Type': entryTypes['note'],
        'Contents': res,
        'ContentsFormat': formats['json'],
        'ReadableContentsFormat': formats['markdown'],
        'HumanReadable': tableToMarkdown('ServiceNow record created successfully', mapped_record, removeNull=True),
        'EntryContext': {
            'ServiceNow.Record(val.ID===obj.ID)': createContext(mapped_record)
        }
    }

    return entry


def create(table_name, fields, custom_fields):
    body = get_body(fields, custom_fields)
    path = 'table/' + table_name

    return send_request(path, 'post', body=body)


def delete_ticket_command():
    args = unicode_to_str_recur(demisto.args())
    ticket_id = args['id']
    ticket_type = get_table_name(args.get('ticket_type'))

    res = delete(ticket_type, ticket_id)

    entry = {
        'Type': entryTypes['note'],
        'Contents': res,
        'ContentsFormat': formats['json'],
        'ReadableContentsFormat': formats['text'],
        'HumanReadable': 'Ticket with ID ' + ticket_id + ' was successfully deleted.'
    }

    return entry


def delete_record_command():
    args = unicode_to_str_recur(demisto.args())
    record_id = args['id']
    table_name = args.get('table_name')

    res = delete(table_name, record_id)

    entry = {
        'Type': entryTypes['note'],
        'Contents': res,
        'ContentsFormat': formats['json'],
        'ReadableContentsFormat': formats['text'],
        'HumanReadable': 'Record with ID ' + record_id + ' was successfully deleted.'
    }

    return entry


def delete(table_name, record_id):
    path = 'table/' + table_name + '/' + record_id

    return send_request(path, 'delete')


def add_link_command():
    args = unicode_to_str_recur(demisto.args())
    ticket_id = args['id']
    key = 'comments' if args.get('post-as-comment', 'false').lower() == 'true' else 'work_notes'
    text = args.get('text', args['link'])
    link = '[code]<a class="web" target="_blank" href="' + args['link'] + '" >' + text + '</a>[/code]'
    ticket_type = get_table_name(args.get('ticket_type'))

    res = add_link(ticket_id, ticket_type, key, link)

    if not res or 'result' not in res:
        return_error('Unable to retrieve response')

    headers = ['System ID', 'Number', 'Impact', 'Urgency', 'Severity', 'Priority', 'State', 'Created On', 'Created By',
               'Active', 'Close Notes', 'Close Code',
               'Description', 'Opened At', 'Due Date', 'Resolved By', 'Resolved At', 'SLA Due', 'Short Description',
               'Additional Comments']

    hr = get_ticket_human_readable(res['result'], ticket_type)
    entry = {
        'Type': entryTypes['note'],
        'Contents': res,
        'ContentsFormat': formats['json'],
        'ReadableContentsFormat': formats['markdown'],
        'HumanReadable': tableToMarkdown('Link successfully added to ServiceNow ticket', hr,
                                         headers=headers, removeNull=True)
    }

    return entry


def add_link(ticket_id, ticket_type, key, link):
    body = {}
    body[key] = link
    path = 'table/' + ticket_type + '/' + ticket_id

    return send_request(path, 'patch', body=body)


def add_comment_command():
    args = unicode_to_str_recur(demisto.args())
    ticket_id = args['id']
    key = 'comments' if args.get('post-as-comment', 'false').lower() == 'true' else 'work_notes'
    text = args['comment']
    ticket_type = get_table_name(args.get('ticket_type'))

    res = add_comment(ticket_id, ticket_type, key, text)

    if not res or 'result' not in res:
        return_error('Unable to retrieve response')

    headers = ['System ID', 'Number', 'Impact', 'Urgency', 'Severity', 'Priority', 'State', 'Created On', 'Created By',
               'Active', 'Close Notes', 'Close Code',
               'Description', 'Opened At', 'Due Date', 'Resolved By', 'Resolved At', 'SLA Due', 'Short Description',
               'Additional Comments']

    hr = get_ticket_human_readable(res['result'], ticket_type)
    entry = {
        'Type': entryTypes['note'],
        'Contents': res,
        'ContentsFormat': formats['json'],
        'ReadableContentsFormat': formats['markdown'],
        'HumanReadable': tableToMarkdown('Comment successfully added to ServiceNow ticket', hr,
                                         headers=headers, removeNull=True)
    }

    return entry


def add_comment(ticket_id, ticket_type, key, text):
    body = {}
    body[key] = text
    path = 'table/' + ticket_type + '/' + ticket_id

    return send_request(path, 'patch', body=body)


def get_ticket_notes_command():
    args = unicode_to_str_recur(demisto.args())
    ticket_id = args['id']
    limit = args.get('limit')
    offset = args.get('offset')

    comments_query = 'element_id=' + ticket_id + '^element=comments^ORelement=work_notes'

    res = query('sys_journal_field', limit, offset, comments_query)

    if not res or 'result' not in res:
        return 'No results found'

    headers = ['Value', 'CreatedOn', 'CreatedBy', 'Type']

    mapped_notes = [{
        'Value': n.get('value'),
        'CreatedOn': n.get('sys_created_on'),
        'CreatedBy': n.get('sys_created_by'),
        'Type': 'Work Note' if n.get('element', '') == 'work_notes' else 'Comment'
    } for n in res['result']]

    if not mapped_notes:
        return 'No results found'

    ticket = {
        'ID': ticket_id,
        'Note': mapped_notes
    }

    entry = {
        'Type': entryTypes['note'],
        'Contents': res,
        'ContentsFormat': formats['json'],
        'ReadableContentsFormat': formats['markdown'],
        'HumanReadable': tableToMarkdown('ServiceNow notes for ticket ' + ticket_id, mapped_notes, headers=headers,
                                         headerTransform=pascalToSpace, removeNull=True),
        'EntryContext': {
            'ServiceNow.Ticket(val.ID===obj.ID)': createContext(ticket, removeNull=True)
        }
    }

    return entry


def query_tickets_command():
    args = unicode_to_str_recur(demisto.args())
    sysparm_limit = args.get('limit', DEFAULTS['limit'])
    sysparm_query = args.get('query')
    sysparm_offset = args.get('offset', DEFAULTS['offset'])

    if not sysparm_query:
        # backward compatibility
        sysparm_query = args.get('sysparm_query')
    ticket_type = get_table_name(args.get('ticket_type'))

    res = query(ticket_type, sysparm_limit, sysparm_offset, sysparm_query)

    if not res or 'result' not in res or len(res['result']) == 0:
        return 'No results found'

    hr = get_ticket_human_readable(res['result'], ticket_type)
    context = get_ticket_context(res['result'], ticket_type)

    headers = ['System ID', 'Number', 'Impact', 'Urgency', 'Severity', 'Priority', 'State', 'Created On', 'Created By',
               'Active', 'Close Notes', 'Close Code',
               'Description', 'Opened At', 'Due Date', 'Resolved By', 'Resolved At', 'SLA Due', 'Short Description',
               'Additional Comments']

    entry = {
        'Type': entryTypes['note'],
        'Contents': res,
        'ContentsFormat': formats['json'],
        'ReadableContentsFormat': formats['markdown'],
        'HumanReadable': tableToMarkdown('ServiceNow tickets', hr, headers=headers, removeNull=True),
        'EntryContext': {
            'Ticket(val.ID===obj.ID)': context,
            'ServiceNow.Ticket(val.ID===obj.ID)': context
        }
    }

    return entry


def query_table_command():
    args = unicode_to_str_recur(demisto.args())
    table_name = args['table_name']
    sysparm_limit = args.get('limit', DEFAULTS['limit'])
    sysparm_query = args.get('query')
    sysparm_offset = args.get('offset', DEFAULTS['offset'])
    fields = args.get('fields')

    res = query(table_name, sysparm_limit, sysparm_offset, sysparm_query)

    if not res or 'result' not in res or len(res['result']) == 0:
        return 'No results found'

    entry = {
        'Type': entryTypes['note'],
        'Contents': res,
        'ContentsFormat': formats['json']
    }

    result = res['result']

    if fields:
        fields = argToList(fields)
        if 'sys_id' not in fields:
            # ID is added by default
            fields.append('sys_id')
        # Filter the records according to the given fields
        records = [dict(filter(lambda kv_pair: kv_pair[0] in fields, r.iteritems())) for r in res['result']]
        for r in records:
            r['ID'] = r.pop('sys_id')
            for k, v in r.iteritems():
                if isinstance(v, dict):
                    # For objects that refer to a record in the database, take their value (system ID).
                    r[k] = v.get('value', v)
        entry['ReadableContentsFormat'] = formats['markdown']
        entry['HumanReadable'] = tableToMarkdown('ServiceNow records', records, removeNull=True)
        entry['EntryContext'] = {
            'ServiceNow.Record(val.ID===obj.ID)': createContext(records)
        }
    else:
        mapped_records = [{DEFAULT_RECORD_FIELDS[k]: r[k] for k in DEFAULT_RECORD_FIELDS if k in r} for r in result]
        entry['ReadableContentsFormat'] = formats['markdown']
        entry['HumanReadable'] = tableToMarkdown('ServiceNow records', mapped_records, removeNull=True)
        entry['EntryContext'] = {
            'ServiceNow.Record(val.ID===obj.ID)': createContext(mapped_records)
        }

    return entry


def query(table_name, sysparm_limit, sysparm_offset, sysparm_query):
    query_params = {}
    query_params['sysparm_limit'] = sysparm_limit
    query_params['sysparm_offset'] = sysparm_offset
    if sysparm_query:
        query_params['sysparm_query'] = sysparm_query

    path = 'table/' + table_name

    return send_request(path, 'get', params=query_params)


def upload_file_command():
    args = unicode_to_str_recur(demisto.args())
    ticket_type = get_table_name(args.get('ticket_type'))
    ticket_id = args['id']
    file_id = args['file_id']
    file_name = args.get('file_name', demisto.dt(demisto.context(), "File(val.EntryID=='" + file_id + "').Name"))

    # in case of info file
    if not file_name:
        file_name = demisto.dt(demisto.context(), "InfoFile(val.EntryID=='" + file_id + "').Name")

    if not file_name:
        return_error('Could not find the file')

    file_name = file_name[0] if isinstance(file_name, list) else file_name

    res = upload_file(ticket_id, file_id, file_name, ticket_type)

    if not res or 'result' not in res or not res['result']:
        return_error('Unable to retrieve response')

    hr = {
        'Filename': res['result'].get('file_name'),
        'Download link': res['result'].get('download_link'),
        'System ID': res['result'].get('sys_id')
    }

    context = {
        'ID': ticket_id,
        'File': {}
    }
    context['File']['Filename'] = res['result'].get('file_name')
    context['File']['Link'] = res['result'].get('download_link')
    context['File']['SystemID'] = res['result'].get('sys_id')

    entry = {
        'Type': entryTypes['note'],
        'Contents': res,
        'ContentsFormat': formats['json'],
        'ReadableContentsFormat': formats['markdown'],
        'HumanReadable': tableToMarkdown('File uploaded successfully', hr),
        'EntryContext': {
            'ServiceNow.Ticket(val.ID===obj.ID)': context,
            'Ticket(val.ID===obj.ID)': context
        }
    }

    return entry


def upload_file(ticket_id, file_id, file_name, ticket_type):
    headers = {
        'Accept': 'application/json'
    }

    body = {
        'table_name': ticket_type,
        'table_sys_id': ticket_id,
        'file_name': file_name
    }

    path = 'attachment/upload'

    return send_request(path, 'post', headers=headers, body=body, file={'id': file_id, 'name': file_name})


# Deprecated
def get_computer_command():
    args = unicode_to_str_recur(demisto.args())
    table_name = 'cmdb_ci_computer'
    computer_name = args['computerName']

    res = query(table_name, None, 0, 'u_code=' + computer_name)

    if not res or 'result' not in res:
        return 'Cannot find computer'
    elif isinstance(res['result'], list):
        if len(res['result']) == 0:
            return 'Cannot find computer'
        computer = res['result'][0]
    else:
        computer = res['result']

    if computer['u_code'] != computer_name:
        return 'Computer not found'

    hr = {
        'ID': computer['sys_id'],
        'u_code (computer name)': computer['u_code'],
        'Support group': computer['support_group'],
        'Operating System': computer['os'],
        'Comments': computer['comments']
    }

    ec = createContext(computer, removeNull=True)
    if 'support_group' in computer:
        ec['support_group'] = computer['support_group']['value'] if 'value' in computer['support_group'] else ''

    entry = {
        'Type': entryTypes['note'],
        'Contents': computer,
        'ContentsFormat': formats['json'],
        'ReadableContentsFormat': formats['markdown'],
        'HumanReadable': tableToMarkdown('ServiceNow Computer', hr),
        'EntryContext': {
            'ServiceNowComputer(val.sys_id==obj.sys_id)': ec,
        }
    }

    return entry


def query_computers_command():
    args = unicode_to_str_recur(demisto.args())
    table_name = 'cmdb_ci_computer'
    computer_id = args.get('computer_id')
    computer_name = args.get('computer_name')
    asset_tag = args.get('asset_tag')
    computer_query = args.get('query', {})
    offset = args.get('offset', DEFAULTS['offset'])
    limit = args.get('limit', DEFAULTS['limit'])

    if computer_id:
        res = get(table_name, computer_id)
    else:
        if computer_name:
            computer_query = 'name=' + computer_name
        elif asset_tag:
            computer_query = 'asset_tag=' + asset_tag

        res = query(table_name, limit, offset, computer_query)

    if not res or 'result' not in res:
        return 'No computers found'

    computers = res['result']
    if not isinstance(computers, list):
        computers = [computers]

    if len(computers) == 0:
        return 'No computers found'

    headers = ['ID', 'AssetTag', 'Name', 'DisplayName', 'SupportGroup', 'OperatingSystem', 'Company', 'AssignedTo',
               'State', 'Cost', 'Comments']

    mapped_computers = [{
        'ID': computer.get('sys_id'),
        'AssetTag': computer.get('asset_tag'),
        'Name': computer.get('name'),
        'DisplayName': '{} - {}'.format(computer.get('asset_tag', ''), computer.get('name', '')),
        'SupportGroup': computer.get('support_group'),
        'OperatingSystem': computer.get('os'),
        'Company': computer.get('company', {}).get('value')
        if isinstance(computer.get('company'), dict) else computer.get('company'),
        'AssignedTo': computer.get('assigned_to', {}).get('value')
        if isinstance(computer.get('assigned_to'), dict) else computer.get('assigned_to'),
        'State': COMPUTER_STATUS.get(computer.get('install_status', ''), computer.get('install_status')),
        'Cost': '{} {}'.format(computer.get('cost', ''), computer.get('cost_cc', '')).rstrip(),
        'Comments': computer.get('comments')
    } for computer in computers]

    entry = {
        'Type': entryTypes['note'],
        'Contents': res,
        'ContentsFormat': formats['json'],
        'ReadableContentsFormat': formats['markdown'],
        'HumanReadable': tableToMarkdown('ServiceNow Computers', mapped_computers, headers=headers,
                                         removeNull=True, headerTransform=pascalToSpace),
        'EntryContext': {
            'ServiceNow.Computer(val.ID===obj.ID)': createContext(mapped_computers, removeNull=True),
        }
    }

    return entry


def query_groups_command():
    args = unicode_to_str_recur(demisto.args())
    table_name = 'sys_user_group'
    group_id = args.get('group_id')
    group_name = args.get('group_name')
    group_query = args.get('query', {})
    offset = args.get('offset', DEFAULTS['offset'])
    limit = args.get('limit', DEFAULTS['limit'])

    if group_id:
        res = get(table_name, group_id)
    else:
        if group_name:
            group_query = 'name=' + group_name
        res = query(table_name, limit, offset, group_query)

    if not res or 'result' not in res:
        return 'No groups found'

    groups = res['result']
    if not isinstance(groups, list):
        groups = [groups]

    if len(groups) == 0:
        return 'No groups found'

    headers = ['ID', 'Description', 'Name', 'Active', 'Manager', 'Updated']

    mapped_groups = [{
        'ID': group.get('sys_id'),
        'Description': group.get('description'),
        'Name': group.get('name'),
        'Active': group.get('active'),
        'Manager': group.get('manager', {}).get('value')
        if isinstance(group.get('manager'), dict) else group.get('manager'),
        'Updated': group.get('sys_updated_on'),
    } for group in groups]

    entry = {
        'Type': entryTypes['note'],
        'Contents': res,
        'ContentsFormat': formats['json'],
        'ReadableContentsFormat': formats['markdown'],
        'HumanReadable': tableToMarkdown('ServiceNow Groups', mapped_groups, headers=headers,
                                         removeNull=True, headerTransform=pascalToSpace),
        'EntryContext': {
            'ServiceNow.Group(val.ID===obj.ID)': createContext(mapped_groups, removeNull=True),
        }
    }

    return entry


def query_users_command():
    args = unicode_to_str_recur(demisto.args())
    table_name = 'sys_user'
    user_id = args.get('user_id')
    user_name = args.get('user_name')
    user_query = args.get('query', {})
    offset = args.get('offset', DEFAULTS['offset'])
    limit = args.get('limit', DEFAULTS['limit'])

    if user_id:
        res = get(table_name, user_id)
    else:
        if user_name:
            user_query = 'user_name=' + user_name
        res = query(table_name, limit, offset, user_query)

    if not res or 'result' not in res:
        return 'No users found'
    res = unicode_to_str_recur(res)

    users = res['result']
    if not isinstance(users, list):
        users = [users]

    if len(users) == 0:
        return 'No users found'

    headers = ['ID', 'Name', 'UserName', 'Email', 'Created', 'Updated']

    mapped_users = [{
        'ID': user.get('sys_id'),
        'Name': '{} {}'.format(user.get('first_name', ''), user.get('last_name', '')).rstrip(),
        'UserName': user.get('user_name'),
        'Email': user.get('email'),
        'Created': user.get('sys_created_on'),
        'Updated': user.get('sys_updated_on'),
    } for user in users]
    mapped_users = unicode_to_str_recur(mapped_users)
    entry = {
        'Type': entryTypes['note'],
        'Contents': res,
        'ContentsFormat': formats['json'],
        'ReadableContentsFormat': formats['markdown'],
        'HumanReadable': tableToMarkdown('ServiceNow Users', mapped_users, headers=headers, removeNull=True,
                                         headerTransform=pascalToSpace),
        'EntryContext': {
            'ServiceNow.User(val.ID===obj.ID)': createContext(mapped_users, removeNull=True),
        }
    }

    return entry


# Deprecated
def get_groups_command():
    args = unicode_to_str_recur(demisto.args())
    table_name = 'sys_user_group'
    group_name = args['name']
    res = query(table_name, None, 0, 'name=' + group_name)

    if not res or 'result' not in res:
        return 'No groups found'

    hr_groups = []
    context_groups = []

    for group in res['result']:
        if group['name'] == group_name:
            hr_groups.append({
                'ID': group['sys_id'],
                'Name': group['name'],
                'Description': group['description'],
                'Email': group['email'],
                'Active': group['active'],
                'Manager': ['manager']
            })
            context_groups.append({
                'GroupId': group['sys_id'],
                'GroupName': group['name']
            })

    entry = {
        'Type': entryTypes['note'],
        'Contents': res,
        'ContentsFormat': formats['json'],
        'ReadableContentsFormat': formats['markdown'],
        'HumanReadable': tableToMarkdown('ServiceNow Group', hr_groups),
        'EntryContext': {
            'ServiceNowGroups(val.GroupId==obj.GroupId)': context_groups,
        }
    }

    return entry


def list_table_fields_command():
    args = unicode_to_str_recur(demisto.args())
    table_name = args['table_name']

    res = get_table_fields(table_name)

    if not res or 'result' not in res:
        return 'Cannot find table'

    if len(res['result']) == 0:
        return 'Table contains no records'

    fields = [{'Name': k} for k, v in res['result'][0].iteritems()]

    entry = {
        'Type': entryTypes['note'],
        'Contents': res,
        'ContentsFormat': formats['json'],
        'ReadableContentsFormat': formats['markdown'],
        'HumanReadable': tableToMarkdown('ServiceNow Table fields - ' + table_name, fields),
        'EntryContext': {
            'ServiceNow.Field': createContext(fields),
        }
    }

    return entry


def get_table_fields(table_name):
    # Get one record
    path = 'table/' + table_name + '?sysparm_limit=1'
    res = send_request(path, 'GET')

    return res


def get_table_name_command():
    args = unicode_to_str_recur(demisto.args())
    label = args['label']
    offset = args.get('offset', DEFAULTS['offset'])
    limit = args.get('limit', DEFAULTS['limit'])

    table_query = 'label=' + label

    res = query('sys_db_object', limit, offset, table_query)

    if not res or 'result' not in res:
        return 'Cannot find table'

    tables = res['result']

    if len(tables) == 0:
        return 'Cannot find table'

    headers = ['ID', 'Name', 'SystemName']

    mapped_tables = [{
        'ID': table.get('sys_id'),
        'Name': table.get('name'),
        'SystemName': table.get('sys_name')
    } for table in tables]

    entry = {
        'Type': entryTypes['note'],
        'Contents': res,
        'ContentsFormat': formats['json'],
        'ReadableContentsFormat': formats['markdown'],
        'HumanReadable': tableToMarkdown('ServiceNow Tables for label - ' + label, mapped_tables,
                                         headers=headers, headerTransform=pascalToSpace),
        'EntryContext': {
            'ServiceNow.Table(val.ID===obj.ID)': createContext(mapped_tables),
        }
    }

    return entry


def fetch_incidents():
    query_params = {}
    incidents = []
    if FETCH_TIME:
        fetch_time = FETCH_TIME
    else:
        fetch_time = DEFAULTS['fetch_time']

    last_run = demisto.getLastRun()
    if 'time' not in last_run:
        snow_time, _ = parse_date_range(fetch_time, '%Y-%m-%d %H:%M:%S')
    else:
        snow_time = last_run['time']

    query = ''
    if SYSPARM_QUERY:
        query += SYSPARM_QUERY + '^'
    query += 'ORDERBY{0}^{0}>{1}'.format(TIMESTAMP_FIELD, snow_time)

    if query:
        query_params['sysparm_query'] = query

    query_params['sysparm_limit'] = SYSPARM_LIMIT

    path = 'table/' + TICKET_TYPE
    res = send_request(path, 'get', params=query_params)

    count = 0
    parsed_snow_time = datetime.strptime(snow_time, '%Y-%m-%d %H:%M:%S')

    for result in res.get('result', []):
        labels = []

        if TIMESTAMP_FIELD not in result:
            raise ValueError("The timestamp field [{}]"
                             " does not exist in the ticket".format(TIMESTAMP_FIELD))

        if count > SYSPARM_LIMIT:
            break

        try:
            if datetime.strptime(result[TIMESTAMP_FIELD], '%Y-%m-%d %H:%M:%S') < parsed_snow_time:
                continue
        except Exception:
            pass

        for k, v in result.iteritems():
            if isinstance(v, basestring):
                labels.append({
                    'type': k,
                    'value': v
                })
            else:
                labels.append({
                    'type': k,
                    'value': json.dumps(v)
                })

        severity = SEVERITY_MAP.get(result.get('severity', ''), 0)

        file_names = []
        if GET_ATTACHMENTS:
            file_entries = get_ticket_attachment_entries(result['sys_id'])
            for file_result in file_entries:
                if file_result['Type'] == entryTypes['error']:
                    raise Exception('Error getting attachment: ' + str(file_result['Contents']))
                file_names.append({
                    'path': file_result['FileID'],
                    'name': file_result['File']
                })

        incidents.append({
            'name': 'ServiceNow Incident ' + result.get('number'),
            'labels': labels,
            'details': json.dumps(result),
            'severity': severity,
            'attachment': file_names,
            'rawJSON': json.dumps(result)
        })

        count += 1
        snow_time = result[TIMESTAMP_FIELD]

    demisto.incidents(incidents)
    demisto.setLastRun({'time': snow_time})


def test_module():
    # Validate fetch_time parameter is valid (if not, parse_date_range will raise the error message)
    parse_date_range(FETCH_TIME, '%Y-%m-%d %H:%M:%S')

    path = 'table/' + TICKET_TYPE + '?sysparm_limit=1'
    res = send_request(path, 'GET')
    if 'result' not in res:
        return_error('ServiceNow error: ' + str(res))
    ticket = res['result']
    if ticket and demisto.params().get('isFetch'):
        if isinstance(ticket, list):
            ticket = ticket[0]
        if TIMESTAMP_FIELD not in ticket:
            raise ValueError("The timestamp field [{}]"
                             " does not exist in the ticket".format(TIMESTAMP_FIELD))


LOG('Executing command ' + demisto.command())
raise_exception = False
try:
    if demisto.command() == 'test-module':
        test_module()
        demisto.results('ok')
    elif demisto.command() == 'fetch-incidents':
        raise_exception = True
        fetch_incidents()
    elif demisto.command() == 'servicenow-get' or \
            demisto.command() == 'servicenow-incident-update' or demisto.command() == 'servicenow-get-ticket':
        demisto.results(get_ticket_command())
    elif demisto.command() == 'servicenow-update' or \
            demisto.command() == 'servicenow-incident-update' or demisto.command() == 'servicenow-update-ticket':
        demisto.results(update_ticket_command())
    elif demisto.command() == 'servicenow-create' or \
            demisto.command() == 'servicenow-incident-create' or demisto.command() == 'servicenow-create-ticket':
        demisto.results(create_ticket_command())
    elif demisto.command() == 'servicenow-delete-ticket':
        demisto.results(delete_ticket_command())
    elif demisto.command() == 'servicenow-add-link' or demisto.command() == 'servicenow-incident-add-link':
        demisto.results(add_link_command())
    elif demisto.command() == 'servicenow-add-comment' or demisto.command() == 'servicenow-incident-add-comment':
        demisto.results(add_comment_command())
    elif demisto.command() == 'servicenow-query' or \
            demisto.command() == 'servicenow-incidents-query' or demisto.command() == 'servicenow-query-tickets':
        demisto.results(query_tickets_command())
    elif demisto.command() == 'servicenow-upload-file' or demisto.command() == 'servicenow-incident-upload-file':
        demisto.results(upload_file_command())
    elif demisto.command() == 'servicenow-query-table':
        demisto.results(query_table_command())
    elif demisto.command() == 'servicenow-get-computer':
        demisto.results(get_computer_command())
    elif demisto.command() == 'servicenow-query-computers':
        demisto.results(query_computers_command())
    elif demisto.command() == 'servicenow-query-groups':
        demisto.results(query_groups_command())
    elif demisto.command() == 'servicenow-query-users':
        demisto.results(query_users_command())
    elif demisto.command() == 'servicenow-get-groups':
        demisto.results(get_groups_command())
    elif demisto.command() == 'servicenow-get-record':
        demisto.results(get_record_command())
    elif demisto.command() == 'servicenow-update-record':
        demisto.results(update_record_command())
    elif demisto.command() == 'servicenow-create-record':
        demisto.results(create_record_command())
    elif demisto.command() == 'servicenow-delete-record':
        demisto.results(delete_record_command())
    if demisto.command() == 'servicenow-list-table-fields':
        demisto.results(list_table_fields_command())
    if demisto.command() == 'servicenow-get-table-name':
        demisto.results(get_table_name_command())
    if demisto.command() == 'servicenow-get-ticket-notes':
        demisto.results(get_ticket_notes_command())
except Exception as e:
    LOG(e)
    LOG.print_log()
    if not raise_exception:
        return_error(str(e))
    else:
        raise