Details
| ID | Salesforce |
|---|---|
| Provider | Salesforce |
| Category | Case Management |
| From Version | 5.0.0 |
| Supported Modules | Agentix XSIAM EDR Cortex Cloud Cloud Runtime Security |
README
Overview
Use the Salesforce integration to search, query, and add new objects (tasks, accounts, cases, and so on) to Salesforce directly from Cortex XSOAR.
Support Salesforce API Version 63.0.
The fetch incident option enables you to monitor the creation of new Salesforce cases and manage them in Cortex XSOAR.
Use cases
Search for data: !salesforce-search pattern=”1234”
This command will return all relevant objects containing the string 1234 as a part of their data.
Querying the data base: !salesforce-query query=“SELECT Subject,Description FROM User WHERE CreatedDate>2018-01-01T00:00:00Z”
This command will return the subject and description of all cases that were created in 2018.
Creating Cases: !salesforce-add-case status="On Hold" subject="adding case from command add-case" type="Feature Request" priority=Low
This command will create a new Salesforce case with the given data.
Submitting Chatter message: !salesforce-push-comment oid=5003600000Smg4sAAB text="adding comment to case" link="google.com"
This command will add a new comment to the object oid and result with the following comment.
Configure Salesforce to Work with Cortex XSOAR
- Add a new connected App in Salesforce. For more information, see the Salesforce documentation.
- If you already have a connected App, navigate to Setup > App Manager and select the correct App from the list, and click View.
- You can find the Consumer Key / Secret under API (Enable OAuth Settings).
For detailed instructions see the Credentials walkthrough section.
Configure the Salesforce Integration on Cortex XSOAR
- Navigate to Settings > Integrations > Servers `&` Services.
- Search for Salesforce.
- Click Add instance to create and configure a new integration instance.
- Name: a textual name for the integration instance.
- Instance URL: A URL to the instance.
- Credentials: The username for accessing the instance
- Consumer Key: The Consumer Key set for this integration with Cortex XSOAR, as explained in 'Setting Salesforce'
- Consumer Secret: The Consumer Secret set for this integration with Cortex XSOAR, as explained in 'Setting Salesforce' above.
- Trust any certificate (not secure)
- Fetch type: cases/comments (Only fetch comments when using the SalesforceAskUser automation)
- Use system proxy settings
- Fetch incidents
- Incident type
- Click Test to validate the URLs, token, and connection.
Fetched Incidents Data
The fetch command monitors the creation of new cases starting from the time the option was enabled.
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.
- Search Salesforce records: salesforce-search
- Perform a query using SOQL: salesforce-query
- Get an object: salesforce-get-object
- Update an object: salesforce-update-object
- Create an object: salesforce-create-object
- Add a comment to chatter: salesforce-push-comment
- Get case information: salesforce-get-case
- Create a case: salesforce-create-case
- Update a case: salesforce-update-case
- Get all cases: salesforce-get-cases
- Close a case: salesforce-close-case
- Add a comment to a chatter thread: salesforce-push-comment-threads
- Delete a case: salesforce-delete-case
1. Search Salesforce records
Search records that contain values with pattern.
Base Command
salesforce-search
Input
| Argument Name | Description | Required |
|---|---|---|
| pattern | String or number to search | Required |
Context Output
| Path | Type | Description |
|---|---|---|
| SalesForce.Case.ID | string | Case Object ID |
| SalesForce.Case.CaseNumber | string | Case number |
| SalesForce.Case.Subject | string | Case subject |
| SalesForce.Case.Description | string | Case description |
| SalesForce.Case.CreateDate | date | Time the case was created |
| SalesForce.Case.ClosedDate | date | Time the case was closed |
| SalesForce.Case.Owner | string | Case owner |
| SalesForce.Case.Priority | string | Case priority (Low, Medium, High) |
| SalesForce.Case.Origin | string | How case originated (Web, Phone, Email) |
| SalesForce.Case.Status | string | Case status (New, Escalated, On Hold, or Closed) |
| SalesForce.Case.Reason | string | Reason the case was created |
| SalesForce.Contact.ID | string | Contact ID |
| SalesForce.Contact.Name | string | Contact name |
| SalesForce.Contact.Account | string | Account associated with the contact information |
| SalesForce.Contact.Title | string | Contact title |
| SalesForce.Contact.Phone | string | Contact phone number |
| SalesForce.Contact.MobliePhone | string | Contact mobile number |
| SalesForce.Contact.Email | string | Contact email address |
| SalesForce.Contact.Owner | string | Contact owner |
| SalesForce.Lead.ID | string | Lead ID |
| SalesForce.Lead.Name | string | Lead name |
| SalesForce.Lead.Title | string | Lead title |
| SalesForce.Lead.Company | string | Lead company |
| SalesForce.Lead.Phone | string | Lead phone number |
| SalesForce.Lead.Mobile | string | Lead mobile number |
| SalesForce.Lead.Email | string | Lead email address |
| SalesForce.Lead.Owner | string | Lead owner |
| SalesForce.Lead.Status | string | Lead status (New, Nurturing, Working, Qualified, or Unqualified) |
| SalesForce.Task.ID | string | Task ID |
| SalesForce.Task.Subject | string | Task subject |
| SalesForce.Task.Lead | string | Task leader |
| SalesForce.Task.RelatedTo | string | Relevant account |
| SalesForce.Task.DueDate | date | Task due date |
| SalesForce.User.ID | string | User's ID |
| SalesForce.User.Name | string | User's name |
| SalesForce.User.Title | string | User's title |
| SalesForce.User.Phone | string | User's phone number |
| SalesForce.User.Email | string | User's email address |
2. Perform a query using SOQL
Query Salesforce with SOQL
Base Command
salesforce-query
Input
| Argument Name | Description | Required |
|---|---|---|
| query | Query in SOQL format: "SELECT name from Account" | Required |
Context Output
There is no context output for this command.
Command Example
3. Get an object
Get object by path
Base Command
salesforce-get-object
Input
| Argument Name | Description | Required |
|---|---|---|
| path | Object path, for example: "Case/5000Y000001EjzRQAS" for Object "Case" with ID "5000Y000001EjzRQAS" | Required |
Context Output
| Path | Type | Description |
|---|---|---|
| SalesForce.Case.ID | string | Case Object ID |
| SalesForce.Case.CaseNumber | string | Case number |
| SalesForce.Case.Subject | string | Case subject |
| SalesForce.Case.Description | string | Case description |
| SalesForce.Case.CreateDate | date | Time the case was created |
| SalesForce.Case.ClosedDate | date | Time the case was closed |
| SalesForce.Case.Owner | string | Case owner |
| SalesForce.Case.Priority | string | Case priority (Low, Medium, High) |
| SalesForce.Case.Origin | string | How case originated (Web, Phone, Email) |
| SalesForce.Case.Status | string | Case status (New, Escalated, On Hold, or Closed) |
| SalesForce.Case.Reason | string | Reason the case was created |
| SalesForce.Contact.ID | string | Contact ID |
| SalesForce.Contact.Name | string | Contact name |
| SalesForce.Contact.Account | string | Account associated with the contact information |
| SalesForce.Contact.Title | string | Contact title |
| SalesForce.Contact.Phone | string | Contact phone number |
| SalesForce.Contact.MobliePhone | string | Contact mobile number |
| SalesForce.Contact.Email | string | Contact email address |
| SalesForce.Contact.Owner | string | Contact owner |
| SalesForce.Lead.ID | string | Lead ID |
| SalesForce.Lead.Name | string | Lead name |
| SalesForce.Lead.Title | string | Lead title |
| SalesForce.Lead.Company | string | Lead company |
| SalesForce.Lead.Phone | string | Lead phone number |
| SalesForce.Lead.Mobile | string | Lead mobile number |
| SalesForce.Lead.Email | string | Lead email address |
| SalesForce.Lead.Owner | string | Lead owner |
| SalesForce.Lead.Status | string | Lead status (New, Nurturing, Working, Qualified, or Unqualified) |
| SalesForce.Task.ID | string | Task ID |
| SalesForce.Task.Subject | string | Task subject |
| SalesForce.Task.Lead | string | Task leader |
| SalesForce.Task.RelatedTo | string | Relevant account |
| SalesForce.Task.DueDate | date | Task due date |
| SalesForce.User.ID | string | User ID |
| SalesForce.User.Name | string | User's name |
| SalesForce.User.Title | string | User's title |
| SalesForce.User.Phone | string | User's phone number |
| SalesForce.User.Email | string | User's email |
Command Example
Context Example
Human Readable Output
4. Update an object
Update object fields
Base Command
salesforce-update-object
Input
| Argument Name | Description | Required |
|---|---|---|
| path | Object path, for example: "Case/5000Y000001EjzRQAS" for Object "Case" with ID "5000Y000001EjzRQAS" | Required |
| json | JSON with fields and values of the object to be updated | Required |
Context Output
| Path | Type | Description |
|---|---|---|
| SalesForce.Case.ID | string | Case Object ID |
| SalesForce.Case.CaseNumber | string | Case number |
| SalesForce.Case.Subject | string | Case subject |
| SalesForce.Case.Description | string | Case description |
| SalesForce.Case.CreateDate | date | Time case was created |
| SalesForce.Case.ClosedDate | date | Time case was closed |
| SalesForce.Case.Owner | string | Case owner |
| SalesForce.Case.Priority | string | Case priority (Low, Medium, High) |
| SalesForce.Case.Origin | string | How case originated (Web, Phone, Email) |
| SalesForce.Case.Status | string | Case status (New, Escalated, On Hold, or Closed) |
| SalesForce.Case.Reason | string | Reason the case was created |
| SalesForce.Contact.ID | string | Contact ID |
| SalesForce.Contact.Name | string | Contact name |
| SalesForce.Contact.Account | string | Account associated with the contact information |
| SalesForce.Contact.Title | string | Contact title |
| SalesForce.Contact.Phone | string | Contact phone number |
| SalesForce.Contact.MobliePhone | string | Contact mobile number |
| SalesForce.Contact.Email | string | Contact email address |
| SalesForce.Contact.Owner | string | Contact owner |
| SalesForce.Lead.ID | string | Lead ID |
| SalesForce.Lead.Name | string | Lead name |
| SalesForce.Lead.Title | string | Lead title |
| SalesForce.Lead.Company | string | Lead company |
| SalesForce.Lead.Phone | string | Lead phone number |
| SalesForce.Lead.Mobile | string | Lead mobile number |
| SalesForce.Lead.Email | string | Lead email address |
| SalesForce.Lead.Owner | string | Lead owner |
| SalesForce.Lead.Status | string | Lead status (New, Nurturing, Working, Qualified, or Unqualified) |
| SalesForce.Task.ID | string | Task ID |
| SalesForce.Task.Subject | string | Task subject |
| SalesForce.Task.Lead | string | Task leader |
| SalesForce.Task.RelatedTo | string | Relevant account |
| SalesForce.Task.DueDate | date | Task due date |
| SalesForce.User.ID | string | User's ID |
| SalesForce.User.Name | string | User's name |
| SalesForce.User.Title | string | User's title |
| SalesForce.User.Phone | string | User's phone number |
| SalesForce.User.Email | string | User's email |
5. Create an object
Create new object
Base Command
salesforce-create-object
Input
| Argument Name | Description | Required |
|---|---|---|
| path | Object path, for example: "Case" for Object "Case" | Required |
| json | JSON with fields and values of the object to be created | Required |
Context Output
| Path | Type | Description |
|---|---|---|
| SalesForce.Case.ID | string | Case Object ID |
| SalesForce.Case.CaseNumber | string | Case number |
| SalesForce.Case.Subject | string | Case subject |
| SalesForce.Case.Description | string | Case description |
| SalesForce.Case.CreateDate | date | Time case was created |
| SalesForce.Case.ClosedDate | date | Time case was closed |
| SalesForce.Case.Owner | string | Case owner |
| SalesForce.Case.Priority | string | Case priority (Low, Medium, High) |
| SalesForce.Case.Origin | string | How case originated (Web, Phone, Email) |
| SalesForce.Case.Status | string | Case status (New, Escalated, On Hold, or Closed) |
| SalesForce.Case.Reason | string | Reason the case was created |
| SalesForce.Contact.ID | string | Contact ID |
| SalesForce.Contact.Name | string | Contact name |
| SalesForce.Contact.Account | string | Account associated with the contact information |
| SalesForce.Contact.Title | string | Contact title |
| SalesForce.Contact.Phone | string | Contact phone number |
| SalesForce.Contact.MobliePhone | string | Contact mobile number |
| SalesForce.Contact.Email | string | Contact email |
| SalesForce.Contact.Owner | string | Contact owner |
| SalesForce.Lead.ID | string | Lead ID |
| SalesForce.Lead.Name | string | Lead name |
| SalesForce.Lead.Title | string | Lead title |
| SalesForce.Lead.Company | string | Lead company |
| SalesForce.Lead.Phone | string | Lead phone number |
| SalesForce.Lead.Mobile | string | Lead mobile number |
| SalesForce.Lead.Email | string | Lead email |
| SalesForce.Lead.Owner | string | Lead owner |
| SalesForce.Lead.Status | string | Lead status (New, Nurturing, Working, Qualified, or Unqualified) |
| SalesForce.Task.ID | string | Task ID |
| SalesForce.Task.Subject | string | Task subject |
| SalesForce.Task.Lead | string | Task leader |
| SalesForce.Task.RelatedTo | string | Relevant account |
| SalesForce.Task.DueDate | date | Task due date |
| SalesForce.User.ID | string | User's ID |
| SalesForce.User.Name | string | User's name |
| SalesForce.User.Title | string | User's title |
| SalesForce.User.Phone | string | User's hone number |
| SalesForce.User.Email | string | User's email address |
6. Add a comment to chatter
Add a comment to chatter
Base Command
salesforce-push-comment
Input
| Argument Name | Description | Required |
|---|---|---|
| oid | Object ID of subject | Required |
| text | Chat text | Required |
| link | Add a link to the message | Optional |
Context Output
| Path | Type | Description |
|---|---|---|
| SalesForce.Comment.Body | string | Comment body |
| SalesForce.Comment.CreatedDate | date | Comment created date |
| SalesForce.Comment.Title | string | Comment title |
| SalesForce.Comment.ParentType | string | Comment parent type |
| SalesForce.Comment.ParentName | string | Comment parent name |
| SalesForce.Comment.URL | string | Comment URL link |
| SalesForce.Comment.Visibility | string | Comment visibility |
Command Example
Context Example
7. Get case information
Get information for a specified on a case. All arguments are optional, but you must specify at least one for the command to execute successfully.
Base Command
salesforce-get-case
Input
| Argument Name | Description | Required |
|---|---|---|
| oid | Object ID of the case | Optional |
| caseNumber | Case number | Optional |
Context Output
| Path | Type | Description |
|---|---|---|
| SalesForce.Case.ID | string | Case Object ID |
| SalesForce.Case.CaseNumber | string | Case number |
| SalesForce.Case.Subject | string | Case subject |
| SalesForce.Case.Description | string | Case description |
| SalesForce.Case.CreateDate | date | Time case was created |
| SalesForce.Case.ClosedDate | date | Time case was closed |
| SalesForce.Case.Owner | string | Case owner |
| SalesForce.Case.Priority | string | Priority of the case (Low, Medium, High) |
| SalesForce.Case.Origin | string | How case originated (Web, Phone, Email) |
| SalesForce.Case.Status | string | Case status (New, Escalated, On Hold, or Closed) |
| SalesForce.Case.Reason | string | Reason the case was created |
Command Example
Context Example
8. Create a case
Create a new case
Base Command
salesforce-create-case
Input
| Argument Name | Description | Required |
|---|---|---|
| subject | Case subject | Required |
| description | Case description | Optional |
| status | Case status | Required |
| origin | Case origin | Optional |
| priority | Case priority | Optional |
| type | Case type | Optional |
Context Output
| Path | Type | Description |
|---|---|---|
| SalesForce.Case.ID | string | Case Object ID |
| SalesForce.Case.CaseNumber | string | Case number |
| SalesForce.Case.Subject | string | Case subject |
| SalesForce.Case.Description | string | Case description |
| SalesForce.Case.CreateDate | date | Time case was created |
| SalesForce.Case.ClosedDate | date | Time case was closed |
| SalesForce.Case.Owner | string | Case owner |
| SalesForce.Case.Priority | string | Priority of the case (Low, Medium, High) |
| SalesForce.Case.Origin | string | How case originated (Web, Phone, Email) |
| SalesForce.Case.Status | string | Case status (New, Escalated, On Hold, or Closed) |
| SalesForce.Case.Reason | string | Reason for case creation |
9. Update a case
Update case fields
Base Command
salesforce-update-case
Input
| Argument Name | Description | Required |
|---|---|---|
| oid | Case Object ID | Optional |
| caseNumber | Case number | Optional |
| subject | Case subject | Optional |
| description | Case description | Optional |
| status | Case status | Optional |
| origin | Case origin | Optional |
| priority | Case priority | Optional |
| type | Case type | Optional |
Context Output
| Path | Type | Description |
|---|---|---|
| SalesForce.Case.ID | string | Case Object ID |
| SalesForce.Case.CaseNumber | string | Case number |
| SalesForce.Case.Subject | string | Case subject |
| SalesForce.Case.Description | string | Case description |
| SalesForce.Case.CreateDate | date | Time case was created |
| SalesForce.Case.ClosedDate | date | Time case was closed |
| SalesForce.Case.Owner | string | Case owner |
| SalesForce.Case.Priority | string | Priority of the case (Low, Medium, High) |
| SalesForce.Case.Origin | string | How case originated (Web, Phone, Email) |
| SalesForce.Case.Status | string | Case status (New, Escalated, On Hold, or Closed) |
| SalesForce.Case.Reason | string | Reason the case was created |
10. Get all cases
Get all cases
Base Command
salesforce-get-cases
Input
There is no input for this command.
Context Output
There is no context output for this command.
11. Close a case
Close a case
Base Command
salesforce-close-case
Input
| Argument Name | Description | Required |
|---|---|---|
| oid | Case Object ID | Optional |
| caseNumber | Case Number | Optional |
Context Output
| Path | Type | Description |
|---|---|---|
| SalesForce.Case.ID | string | Case Object ID |
| SalesForce.Case.CaseNumber | string | Case Number |
| SalesForce.Case.Subject | string | Case Subject |
| SalesForce.Case.Description | string | Case Description |
| SalesForce.Case.CreateDate | date | Creation Time of Case |
| SalesForce.Case.ClosedDate | date | Closure Time of Case |
| SalesForce.Case.Owner | string | Case Owner |
| SalesForce.Case.Priority | string | Priority of the Case. one of Low, Medium, High. |
| SalesForce.Case.Origin | string | Origin of the Case. one of Web, Phone, Email. |
| SalesForce.Case.Status | string | Case Status. one of the following: New, Escalated, On Hold or Closed. |
| SalesForce.Case.Reason | string | Reason for case creation |
Command Example
12. Add a comment to a chatter thread
Add the comment to the chatter thread. Use this command only after salesforce-push-comment
Base Command
salesforce-push-comment-threads
Input
| Argument Name | Description | Required |
|---|---|---|
| id | The Chatter comment Thread ID | Required |
| text | The comment text | Required |
Context Output
| Path | Type | Description |
|---|---|---|
| SalesForce.Comment.Reply.Body | string | Reply body |
| SalesForce.Comment.Reply.CreatedDate | date | Reply created date |
| SalesForce.Comment.Reply.URL | string | Reply URL link |
13. Delete a case
Delete a specified on a case. All arguments are optional, but you must specify at least one for the command to execute successfully.
Base Command
salesforce-get-case
Input
| Argument Name | Description | Required |
|---|---|---|
| oid | Object ID of the case | Optional |
| caseNumber | Case number | Optional |
Troubleshooting
Connection error:
-
This error indicates that there is a problem with the credentials or the IP connection to the app (see step 6-7 in Credentials walkthrough below).

- This error indicates a problem in the Costumer Key:

- This error indicates a problem in the Costumer Secret:

Credentials walkthrough:
1. Open Setup.

2. Go to the App manager on the left side menu (use Quick Find for faster search).

3. Click “New Connected App” on the right side of the screen.
4. Fill in the form as follows:
- Mark “Enable OAuth Settings”.
- In “Selected OAuth Scopes”, Add the following:
- Access and manage your Chatter data (chatter_api)
- Access and manage your data (api)
5. Click “Save” at the bottom of the form.
6. In the information page on the APP that you will be forwarded to locate “API (Enable OAuth Settings)” and copy the consumer key and secret to the integration instance configuration.
6. Go to “Manage Connected Apps”.
7. Edit the new application and under “OAuth Policies” enable all IP address.
salesforce-get-casecomment
Returns a comment through the case number.
Base Command
salesforce-get-casecomment
Input
| Argument Name | Description | Required |
|---|---|---|
| oid | Returns information of a case. | Optional |
| caseNumber | The case number of the case. | Optional |
Context Output
| Path | Type | Description |
|---|---|---|
| ID | string | The ID of the case. |
| ParentId | string | The ID of the parent case of the case comment. Required. |
| IsPublished | boolean | Whether the case comment is visible to customers in the Self-Service portal (true). The label is published. This is the only CaseComment field that can be updated through the API. |
| CommentBody | string | The text of the case body. Maximum size is 4,000 bytes. The label is Body. |
| CreatedById | unknown | The created date by ID. |
| CreatedDate | string | The created date. |
| SystemModstamp | string | The SystemMod stamp. |
| LastModifiedDate | string | The last modified date. |
| LastModifiedById | string | The last modified date by ID. |
| IsDeleted | boolean | Whether the object has been moved to the Recycle Bin (true). Label is Deleted. |
salesforce-post-casecomment
The post comment through the case number.
Base Command
salesforce-post-casecomment
Input
| Argument Name | Description | Required |
|---|---|---|
| oid | The Object ID of the case. | Optional |
| caseNumber | The case number of the case. | Optional |
| text | Added Text to context. | Optional |
Context Output
There is no context output for this command.
salesforce-get-user
Returns the UserName through the case number.
Base Command
salesforce-get-user
Input
| Argument Name | Description | Required |
|---|---|---|
| oid | The Object ID of the case. | Optional |
| caseNumber | The case number of the case. | Optional |
Context Output
| Path | Type | Description |
|---|---|---|
| ID | string | The ID of the case. |
| Alias | string | The user’s alias. Required. For example, jsmith. |
| CommunityNickname | string | The name used to identify the user in the Community application, which includes the ideas and answers features. |
| CreatedById | string | Created by the ID. |
| string | The user’s email address. Required. | |
| LastLoginDate | string | The time and date when the user last successfully logged in. This value is updated if 60 seconds have elapsed since the user’s last login. |
| LastModifiedDate | string | The last modified date. |
| LastName | string | The user’s last name. Required. |
| Name | string | Concatenation of FirstName and LastName. Limited to 121 characters. |
| Username | string | Contains the name that a user enters to log in to the API or the user interface. Required. Must be in the form of an email address, all characters should be lowercase, and unique across all organizations. Each added user counts as a license. Every organization has a maximum number of licenses. If you attempt to exceed the maximum number of licenses by inserting user records, the attempt to create a user is rejected. |
| UserRoleId | string | The ID of the user’s UserRole. Label is Role ID. |
salesforce-get-org
Returns organization details from the case number.
Base Command
salesforce-get-org
Input
| Argument Name | Description | Required |
|---|---|---|
| caseNumber | The case number of the case. | Optional |
Context Output
| Path | Type | Description |
|---|---|---|
| ID | string | The unique ID of the case. |
| Name | string | Name of the account. Required. Label is Account Name. Maximum size is 255 characters. If the account has a record type of Person Account, this value is the concatenation of the FirstName, MiddleName, LastName, and Suffix of the associated person contact. You cannot modify this value. |
salesforce-file-delete
Soft-deletes a file (ContentDocument), moving it to the Recycle Bin and cascading to its versions. Recoverable for 15 days.
Base Command
salesforce-file-delete
Input
| Argument Name | Description | Required |
|---|---|---|
| type | The Salesforce sObject type. Default is ContentDocument. | Optional |
| id | The ContentDocument record ID to delete. | Required |
| ignore_not_found | Whether to treat a 404/NOT_FOUND response as success. Possible values are: true, false. Default is true. | Optional |
Context Output
| Path | Type | Description |
|---|---|---|
| SalesForce.Remediation.Action | string | The remediation action performed. |
| SalesForce.Remediation.ObjectType | string | The Salesforce sObject type acted on. |
| SalesForce.Remediation.Id | string | The record ID acted on. |
| SalesForce.Remediation.Status | string | The result status of the remediation action. |
Command example
!salesforce-file-delete id=069dn000007JlATAA0
Context Example
{
"SalesForce": {
"Remediation": {
"Action": "Delete File",
"Id": "069dn000007JlATAA0",
"ObjectType": "ContentDocument",
"Status": "Deleted"
}
}
}
Human Readable Output
Salesforce Remediation - Delete File
Action Id ObjectType Status Delete File 069dn000007JlATAA0 ContentDocument Deleted
salesforce-knowledge-article-archive
Archives a Knowledge article (non-destructive). Sets PublishStatus to Archived via the standard archiveKnowledgeArticles action. Reversible to Draft/Online.
Base Command
salesforce-knowledge-article-archive
Input
| Argument Name | Description | Required |
|---|---|---|
| type | The Salesforce sObject type. Default is KnowledgeArticleVersion. | Optional |
| id | The Knowledge article version ID (the __kav record ID, starting with ‘ka’) to archive. | Required |
| ignore_not_found | Whether to treat a 404/NOT_FOUND response as success. Possible values are: true, false. Default is true. | Optional |
Context Output
| Path | Type | Description |
|---|---|---|
| SalesForce.Remediation.Action | string | The remediation action performed. |
| SalesForce.Remediation.ObjectType | string | The Salesforce sObject type acted on. |
| SalesForce.Remediation.Id | string | The record ID acted on. |
| SalesForce.Remediation.Status | string | The result status of the remediation action. |
Command example
!salesforce-knowledge-article-archive id=ka1dn0000001ZwbAAE
Context Example
{
"SalesForce": {
"Remediation": {
"Action": "Archive Knowledge Article",
"Id": "ka1dn0000001ZwbAAE",
"ObjectType": "KnowledgeArticleVersion",
"Status": "Archived"
}
}
}
Human Readable Output
Salesforce Remediation - Archive Knowledge Article
Action Id ObjectType Status Archive Knowledge Article ka1dn0000001ZwbAAE KnowledgeArticleVersion Archived
salesforce-file-public-link-list
Lists all public links (ContentDistribution records) for a given ContentDocument ID. Use the returned ContentDistribution IDs with salesforce-file-public-link-remove to remove each link.
Base Command
salesforce-file-public-link-list
Input
| Argument Name | Description | Required |
|---|---|---|
| id | The ContentDocument record ID whose public links (ContentDistribution rows) should be listed. | Required |
Context Output
| Path | Type | Description |
|---|---|---|
| SalesForce.ContentDistribution.Id | string | The ContentDistribution record ID (use with salesforce-file-public-link-remove). |
| SalesForce.ContentDistribution.ContentDocumentId | string | The parent ContentDocument ID. |
| SalesForce.ContentDistribution.Name | string | The name of the content distribution. |
| SalesForce.ContentDistribution.PublicUrl | string | The public distribution URL. |
Command example
!salesforce-file-public-link-list id=069dn000007PpOIAA0
Context Example
{
"SalesForce": {
"ContentDistribution": {
"ContentDocumentId": "069dn000007PpOIAA0",
"Id": "05Ddn000000VfWjEAK",
"Name": "cortex-test-file-01 7/28/2026",
"PublicUrl": "https://paloaltonetworks-sahil.sandbox.my.salesforce.com/sfc/p/7X000001T5lm/a/dn000000VfWj/TExjFQ7nttSYbbnXER8CaxGLqYDmVhzw2LEe1jg9_Vo"
}
}
}
Human Readable Output
Salesforce File Public Links (ContentDistribution) for 069dn000007PpOIAA0
ContentDocumentId Id Name PublicUrl 069dn000007PpOIAA0 05Ddn000000VfWjEAK cortex-test-file-01 7/28/2026 https://paloaltonetworks-sahil.sandbox.my.salesforce.com/sfc/p/7X000001T5lm/a/dn000000VfWj/TExjFQ7nttSYbbnXER8CaxGLqYDmVhzw2LEe1jg9_Vo
salesforce-file-public-link-remove
Removes a file public link by deleting a single ContentDistribution record. The external link is severed immediately and irreversibly.
Base Command
salesforce-file-public-link-remove
Input
| Argument Name | Description | Required |
|---|---|---|
| type | The Salesforce sObject type. Default is ContentDistribution. | Optional |
| id | The ContentDistribution record ID to delete (severs the public link). Use salesforce-file-public-link-list to obtain distribution IDs for a ContentDocument. | Required |
| ignore_not_found | Whether to treat a 404/NOT_FOUND response as success. Possible values are: true, false. Default is true. | Optional |
Context Output
| Path | Type | Description |
|---|---|---|
| SalesForce.Remediation.Action | string | The remediation action performed. |
| SalesForce.Remediation.ObjectType | string | The Salesforce sObject type acted on. |
| SalesForce.Remediation.Id | string | The ContentDistribution record ID acted on. |
| SalesForce.Remediation.Status | string | The result status of the remediation action. |
Command example
!salesforce-file-public-link-remove id=05Ddn000000VfWjEAK
Context Example
{
"SalesForce": {
"Remediation": {
"Action": "Remove File Public Link",
"Id": "05Ddn000000VfWjEAK",
"ObjectType": "ContentDistribution",
"Status": "Deleted"
}
}
}
Human Readable Output
Salesforce Remediation - Remove File Public Link
Action Id ObjectType Status Remove File Public Link 05Ddn000000VfWjEAK ContentDistribution Deleted
salesforce-knowledge-article-draft-create
Moves a published Knowledge article to Draft using the standard createDraftFromOnlineKnowledgeArticle action. By default (unpublish=true) the online version is removed from public visibility and an editable draft is created.
Base Command
salesforce-knowledge-article-draft-create
Input
| Argument Name | Description | Required |
|---|---|---|
| type | The Salesforce sObject type. Default is KnowledgeArticleVersion. | Optional |
| id | The Knowledge article ID (KnowledgeArticleId, starting with ‘kA’) or article version ID (starting with ‘ka’). Used with the standard createDraftFromOnlineKnowledgeArticle action. | Required |
| unpublish | Whether to archive the current online version (removing it from public visibility) and create a new draft. If false, keeps the article online and creates an editable draft alongside it. Possible values are: true, false. Default is true. | Optional |
| ignore_not_found | Whether to treat a 404/NOT_FOUND response as success. Possible values are: true, false. Default is true. | Optional |
Context Output
| Path | Type | Description |
|---|---|---|
| SalesForce.Remediation.Action | string | The remediation action performed. |
| SalesForce.Remediation.ObjectType | string | The Salesforce sObject type acted on. |
| SalesForce.Remediation.Id | string | The record ID acted on. |
| SalesForce.Remediation.Status | string | The result status of the remediation action. |
| SalesForce.Remediation.Unpublished | boolean | Whether the online version was unpublished (removed from public visibility). |
| SalesForce.Remediation.DraftVersionId | string | The ID of the newly created draft article version. |
Command example
!salesforce-knowledge-article-draft-create id=ka1dn0000001ZyDAAU unpublish=true
Context Example
{
"SalesForce": {
"Remediation": {
"Action": "Draft Knowledge Article",
"DraftVersionId": "ka1dn0000001ZzpAAE",
"Id": "ka1dn0000001ZyDAAU",
"ObjectType": "KnowledgeArticleVersion",
"Status": "Unpublished (Draft created)",
"Unpublished": true
}
}
}
Human Readable Output
Salesforce Remediation - Draft Knowledge Article
Action DraftVersionId Id ObjectType Status Unpublished Draft Knowledge Article ka1dn0000001ZzpAAE ka1dn0000001ZyDAAU KnowledgeArticleVersion Unpublished (Draft created) true
<~PLATFORM>
License Requirements
The following configuration parameters require one of these licenses: Cortex XSIAM or Agentix:
- Fetch incidents
</~PLATFORM>
Configuration parameters
InstanceURL— Instance URL (required)credentials— CredentialsclientID— Consumer KeyclientSecret— Consumer Secretcredentials_client_secret— Consumer Keyinsecure— Trust any certificate (not secure)fetchType— Fetch type: cases/comments (Only fetch comments when using the SalesforceAskUser automation)useproxy— Use system proxy settingsisFetch— Fetch incidentsincidentFetchInterval— Incidents Fetch IntervalincidentType— Incident type
Commands (22)
-
salesforce-close-caseClose a case
-
salesforce-create-caseCreates a new case.
-
salesforce-create-objectCreates a new object.
-
salesforce-delete-caseDeletes a case.
-
salesforce-file-deleteSoft-deletes a file (ContentDocument), moving it to the Recycle Bin and cascading to its versions. Recoverable for 15 days.
-
salesforce-file-public-link-listLists all public links (ContentDistribution records) for a given ContentDocument ID. Use the returned ContentDistribution IDs with salesforce-file-public-link-remove to remove each link.
-
salesforce-file-public-link-removeRemoves a file public link by deleting a single ContentDistribution record. The external link is severed immediately and irreversibly.
-
salesforce-get-caseReturns information on a case. All arguments are optional, but you must specify at least one for the command to execute successfully.
-
salesforce-get-casecommentReturns a comment through the case number.
-
salesforce-get-casesReturns all cases.
-
salesforce-get-objectReturns an object by its path.
-
salesforce-get-orgReturns organization details from the case number.
-
salesforce-get-userReturns the UserName through the case number.
-
salesforce-knowledge-article-archiveArchives a Knowledge article (non-destructive). Sets PublishStatus to Archived via the standard archiveKnowledgeArticles action. Reversible to Draft/Online.
-
salesforce-knowledge-article-draft-createMoves a published Knowledge article to Draft using the standard createDraftFromOnlineKnowledgeArticle action. By default (unpublish=true) the online version is removed from public visibility and an editable draft is created.
-
salesforce-post-casecommentThe post comment through the case number.
-
salesforce-push-commentAdds a comment to Chatter.
-
salesforce-push-comment-threadsAdd the comment to the chatter thread. Use this command only after salesforce-push-comment
-
salesforce-queryQueries Salesforce in SOQL format.
-
salesforce-searchSearch records that contain values with pattern
-
salesforce-update-caseUpdates case fields.
-
salesforce-update-objectUpdates object fields.
var URI_PREFIX = '/services/data/v63.0/'; var SESSION_DATA = ''; function getNewToken() { var client_id = params.credentials_client_secret !== null ? params.credentials_client_secret.identifier : params.clientID; var client_secret = params.credentials_client_secret !== null ? params.credentials_client_secret.password : params.clientSecret; if (client_id === null || client_secret === null) return('Consumer Key and Consumer Secret must be provided.') var request = { grant_type: 'password', client_id: client_id, client_secret: client_secret, username: params.credentials.identifier, password: params.credentials.password }; var body = encodeToURLQuery(request).substr(1); var response = http( params.InstanceURL + '/services/oauth2/token', { Method: 'POST', Headers: {'Content-Type': ['application/x-www-form-urlencoded']}, Body: body, }, params.insecure === false, params.useproxy ); if (response.StatusCode < 200 || response.StatusCode >= 300) { throw 'Failed to get new token, request status code: ' + response.StatusCode + ' and Body: ' + response.Body + '.'; } return JSON.parse(response.Body); } function sendRequest(method, url, body, token) { var headers = {}; if (token) { headers['Authorization'] = ['Bearer ' + token]; } if (method == 'POST' || method == 'PATCH') { headers['Content-Type'] = ['application/json']; } return http( url, { Method: method, Headers: headers, Body: body, }, params.insecure === false, params.useproxy ); } function sendRequestInSession(method, uri, body) { if (!SESSION_DATA || !SESSION_DATA.access_token) { throw "Failed to get access token for Salesforce integration."; } var response = sendRequest(method, SESSION_DATA.instance_url + URI_PREFIX + uri, body, SESSION_DATA.access_token); if (response.StatusCode === 401) { // ── UCP: invalidate cache and get fresh credentials, fall back to legacy ── try { var use_ucp_auth = shouldUseUcpAuth(); } catch (e) { var use_ucp_auth = false; } if (use_ucp_auth) { logDebug('[UCP][Salesforce.js] Received 401 — invalidating cached credentials and retrying'); invalidateUcpCredentialsCache(getUcpMethodUniqueId()); var ucpCreds = getUcpCredentials(); if (!ucpCreds || !ucpCreds.access_token) { logError('[UCP][Salesforce.js] Failed to refresh access token after 401'); throw '[UCP] Failed to refresh the Salesforce access token after an authentication error. ' + 'Please verify the authentication profile is correctly configured.'; } logDebug('[UCP][Salesforce.js] Successfully refreshed access token after 401'); SESSION_DATA.access_token = ucpCreds.access_token; } else { SESSION_DATA = getNewToken(); } response = sendRequest(method, SESSION_DATA.instance_url + URI_PREFIX + uri, body, SESSION_DATA.access_token); } if (response.StatusCode < 200 || response.StatusCode >= 300) { throw 'Failed to run command uri: ' + uri + ', request status code: ' + response.StatusCode + ' and Body: ' + response.Body + '.'; } return response; } function getUserNames() { var res = queryObjects(['Id', 'Name'], 'User'); var users = {}; for (var i in res.records) { users[res.records[i].Id] = res.records[i].Name; } return users; } function commentToEntry(raw_info, title, userMapping) { // fix owner field if (userMapping) { for (var i in raw_info) { // use OwnerId if no user was found raw_info[i].OwnerId = userMapping[raw_info[i].OwnerId] || raw_info[i].OwnerId; } } return createEntry(raw_info, { contextPath: 'SalesForce.CaseComment(val.ID && val.ID == obj.ID)', title: title, data: [ {to: 'ID', from: 'Id', humanReadable: false}, {to: 'ParentId', from: 'ParentId'}, {to: 'IsPublished', from: 'IsPublished'}, {to: 'CommentBody', from: 'CommentBody'}, {to: 'CreatedById', from: 'CreatedById'}, {to: 'CreatedDate', from: 'CreatedDate'}, {to: 'SystemModstamp', from: 'SystemModstamp'}, {to: 'LastModifiedDate', from: 'LastModifiedDate'}, {to: 'LastModifiedById', from: 'LastModifiedById'}, {to: 'IsDeleted', from: 'IsDeleted'} ] }); } function userToEntry(raw_info, title, userMapping) { // fix owner field if (userMapping) { for (var i in raw_info) { // use OwnerId if no user was found raw_info[i].OwnerId = userMapping[raw_info[i].OwnerId] || raw_info[i].OwnerId; } } return createEntry(raw_info, { contextPath: 'SalesForce.User(val.ID && val.ID == obj.ID)', title: title, data: [ {to: 'ID', from: 'Id', humanReadable: false}, {to: 'Alias', from: 'Alias'}, {to: 'CommunityNickname', from: 'CommunityNickname'}, {to: 'CreatedById', from: 'CreatedById'}, {to: 'Email', from: 'Email'}, {to: 'LastLoginDate', from: 'LastLoginDate'}, {to: 'LastModifiedDate', from: 'LastModifiedDate'}, {to: 'LastName', from: 'LastName'}, {to: 'Name', from: 'Name'}, {to: 'Username', from: 'Username'}, {to: 'UserRoleId', from: 'UserRoleId'} ] }); } function orgToEntry(raw_info, title, userMapping) { // fix owner field if (userMapping) { for (var i in raw_info) { // use OwnerId if no user was found raw_info[i].OwnerId = userMapping[raw_info[i].OwnerId] || raw_info[i].OwnerId; } } return createEntry(raw_info, { contextPath: 'SalesForce.GetOrg(val.ID && val.ID == obj.ID)', title: title, data: [ {to: 'ID', from: 'Id', humanReadable: false}, {to: 'Name', from: 'Name'} ] }); } function casesToEntry(raw_info, title, userMapping) { // fix owner field if (userMapping) { for (var i in raw_info) { // use OwnerId if no user was found raw_info[i].OwnerId = userMapping[raw_info[i].OwnerId] || raw_info[i].OwnerId; } } return createEntry(raw_info, { contextPath: 'SalesForce.Case(val.ID && val.ID == obj.ID)', title: title, data: [ {to: 'ID', from: 'Id', humanReadable: false}, {to: 'CaseNumber', from: 'CaseNumber'}, {to: 'Subject', from: 'Subject'}, {to: 'Description', from: 'Description'}, {to: 'CreatedDate', from: 'CreatedDate'}, {to: 'ClosedDate', from: 'ClosedDate'}, {to: 'Owner', from: 'OwnerId'}, {to: 'Priority', from: 'Priority'}, {to: 'Origin', from: 'Origin'}, {to: 'Status', from: 'Status'}, {to: 'Reason', from: 'Reason'}, {to: 'IsEscalated', from: 'IsEscalated'}, {to: 'SuppliedPhone', from: 'SuppliedPhone'}, {to: 'SuppliedCompany', from: 'SuppliedCompany'}, {to: 'SuppliedEmail', from: 'SuppliedEmail'}, {to: 'ContactEmail', from: 'ContactEmail'}, {to: 'ContactId', from: 'ContactId'}, {to: 'AccountId', from: 'AccountId'}, {to: 'Id', from: 'Id'} ] }); } function contactsToEntry(raw_info, title, userMapping, accountMapping) { var i; // fix owner field if (userMapping) { for (i in raw_info) { // use OwnerId if no user was found raw_info[i].OwnerId = userMapping[raw_info[i].OwnerId] || raw_info[i].OwnerId; } } if (accountMapping) { for (i in raw_info) { // use AccountId if no account was found raw_info[i].AccountId = accountMapping[raw_info[i].AccountId] || raw_info[i].AccountId; } } return createEntry(raw_info, { contextPath: 'SalesForce.Contact(val.ID && val.ID == obj.ID)', title: title, data: [ {to: 'ID', from: 'Id', humanReadable: false}, {to: 'Name', from: 'Name'}, {to: 'Account', from: 'AccountId'}, {to: 'Title', from: 'Title'}, {to: 'Phone', from: 'Phone'}, {to: 'Mobile', from: 'MobilePhone'}, {to: 'Email', from: 'Email'}, {to: 'Owner', from: 'OwnerId'}, ] }); } function leadsToEntry(raw_info, title, userMapping) { // fix owner field if (userMapping) { for (var i in raw_info) { // use OwnerId if no user was found raw_info[i].OwnerId = userMapping[raw_info[i].OwnerId] || raw_info[i].OwnerId; } } return createEntry(raw_info, { contextPath: 'SalesForce.Lead(val.ID && val.ID == obj.ID)', title: title, data: [ {to: 'ID', from: 'Id', humanReadable: false}, {to: 'Name', from: 'Name'}, {to: 'Title', from: 'Title'}, {to: 'Company', from: 'Company'}, {to: 'Phone', from: 'Phone'}, {to: 'Mobile', from: 'MobilePhone'}, {to: 'Email', from: 'Email'}, {to: 'Owner', from: 'OwnerId'}, {to: 'Status', from: 'Status'} ] }); } function tasksToEntry(raw_info, title, lead_dict) { // fix owner field if (leadMapping) { for (var i in raw_info) { // use WhoId if no lead was found raw_info[i].WhoId = leadMapping[raw_info[i].WhoId] || raw_info[i].WhoId; } } return createEntry(raw_info, { contextPath: 'SalesForce.Task(val.ID && val.ID == obj.ID)', title: title, data: [ {to: 'ID', from: 'Id', humanReadable: false}, {to: 'Subject', from: 'Subject'}, {to: 'Lead', from: 'WhoId'}, {to: 'RelatedTo', from: 'RelatedTo'}, {to: 'DueDate', from: 'ActivityDate'} ] }); } function usersToEntry(raw_info, title) { return createEntry(raw_info, { contextPath: 'SalesForce.GetUsers(val.ID && val.ID == obj.ID)', title: title, data: [ {to: 'ID', from: 'Id', humanReadable: false}, {to: 'Name', from: 'Name'}, {to: 'Alias', from: 'Alias'}, {to: 'CommunityNickname', from: 'CommunityNickname'}, {to: 'Title', from: 'Title'}, {to: 'Phone', from: 'Phone'}, {to: 'Email', from: 'Email'}, {to: 'FirstName', from: 'FirstName'}, {to: 'Username', from: 'Username'} ] }); } function objectToEntry(obj_type, obj) { var userMapping = getUserNames(); switch (obj_type) { case 'CaseComment': return commentToEntry([obj], 'CaseComment:', userMapping); case 'getOrgName': return orgToEntry([obj], 'getOrgName:', userMapping); case 'userToEntry': return userToEntry([obj],'getUser', userMapping); case 'Case': return casesToEntry([obj], 'Case:', userMapping); case 'Contact': accountMapping = undefined; // TODO: implement return contactsToEntry([obj], 'Contact:', userMapping, accountMapping); case 'Lead': return leadsToEntry([obj], 'Lead:', userMapping); case 'Task': leadMapping = undefined; // TODO: implement return tasksToEntry([obj], 'Lead:', leadMapping); case 'User': return usersToEntry([obj], 'User:'); default: return obj; } } function queryToEntry(query) { return { Type : entryTypes.note, Contents : query.records, ContentsFormat: formats.json, ReadableContentsFormat: formats.markdown, HumanReadable : tableToMarkdown('Query Results', query.records) }; } function searchToEntry(searchRecords) { if (searchRecords.length === 0) { return { Type : entryTypes.Note, Contents : 'No records matched the search.', // ContentsFormat: formats.json, ReadableContentsFormat: formats.markdown }; } var case_ids = []; var contact_ids = []; var lead_ids = []; var task_ids = []; var user_ids = []; var general = []; var case_comment = []; var get_org = []; for (var i in searchRecords) { switch (searchRecords[i].attributes.type) { case 'CaseComment': case_comment.push(searchRecords[i].Id); break; case 'getOrgName': get_org.push(searchRecords[i].Id); break; case 'Case': case_ids.push(searchRecords[i].Id); break; case 'Contact': contact_ids.push(searchRecords[i].Id); break; case 'Lead': lead_ids.push(searchRecords[i].Id); break; case 'Task': task_ids.push(searchRecords[i].Id); break; case 'User': user_ids.push(searchRecords[i].Id); break; default: // in case we don't know how to parse the object general.push(searchRecords[i]); break; } } var condition, properties; var entries = []; var userMapping = getUserNames(); if (get_org.length > 0) { condition = "ID IN ('" + get_org.join("','") + "')"; properties = ['ID', 'Name']; var cases = queryObjects(properties, "Account", condition).records; entries.push(orgToEntry(cases, 'Account:', userMapping)); } if (case_ids.length > 0) { condition = "ID IN ('" + case_ids.join("','") + "')"; properties = ['ID', 'CaseNumber', 'Subject', 'Description', 'CreatedDate', 'ClosedDate', 'OwnerID', 'Priority', 'Origin', 'Status', 'Reason','IsEscalated','SuppliedPhone','SuppliedCompany','SuppliedEmail','ContactEmail','ContactId','AccountId']; var cases = queryObjects(properties, "Case", condition).records; entries.push(casesToEntry(cases, 'Cases:', userMapping)); } if (case_comment.length > 0) { condition = "ID IN ('" + case_comment.join("','") + "')"; properties = ['ID', 'CommentBody', 'CreatedDate', 'CreatedById', 'IsPublished', 'SystemModstamp', 'LastModifiedById', 'LastModifiedDate']; var cases_comment = queryObjects(properties, "CaseComment", condition).records; entries.push(commentToEntry(cases_comment, 'CaseComment:', userMapping)); } if (contact_ids.length > 0) { condition = "ID IN ('" + contact_ids.join("','") + "')"; properties = ['ID', 'Name', 'Title', 'AccountId', 'Phone', 'MobilePhone', 'Email', 'OwnerId']; var contacts = queryObjects(properties, "Contact", condition).records; entries.push(contactsToEntry(contacts, 'Contacts:', userMapping)); } if (lead_ids.length > 0) { condition = "ID IN ('" + lead_ids.join("','") + "')"; properties = ['ID', 'Name', 'Title', 'Company', 'Phone', 'MobilePhone', 'Email', 'Status', 'OwnerId']; var leads = queryObjects(properties, "Lead", condition).records; entries.push(leadsToEntry(leads, 'Leads:', userMapping)); } if (task_ids.length > 0) { condition = "ID IN ('" + task_ids.join("','") + "')"; properties = ['ID', 'Subject', 'WhoId', 'ActivityDate']; var tasks = queryObjects(properties, "Task", condition).records; entries.push(tasksToEntry(tasks, 'Tasks:')); } if (user_ids.length > 0) { condition = "ID IN ('" + user_ids.join("','") + "')"; properties = ['ID', 'Name', 'Title', 'Phone', 'Email']; var users = queryObjects(properties, "User", condition).records; entries.push(usersToEntry(users, 'Users:')); } if (general.length > 0) entries.push({'unparsed' : general}); return entries; } function queryRaw(query) { var url = 'query/' + encodeToURLQuery({q : query}); response = sendRequestInSession('GET', url, ''); return JSON.parse(response.Body); } function queryObjects(fields, table, condition) { query = 'SELECT ' + fields.join(',') + ' FROM ' + table; if (condition !== undefined) { query += ' WHERE ' + condition; } return queryRaw(query); } function getObject(path) { response = sendRequestInSession('GET', 'sobjects/' + path, ''); return objectToEntry(path.split('/')[0], JSON.parse(response.Body)); } function createObject(path, json_obj) { response = sendRequestInSession('POST','sobjects/' + path, json_obj); response = JSON.parse(response.Body); if (response.success !== true) { return { Type: entryTypes.note, Contents: response, ContentsFormat: formats.json, ReadableContentsFormat: formats.markdown, HumanReadable : tableToMarkdown('Request failed with errors', response.errors, undefined, undefined, dotToSpace) }; } return getObject(path + '/' + response.id); } function updateObject(path, json_obj) { response = sendRequestInSession('PATCH','sobjects/' + path, json_obj); if (response.StatusCode != 204) { throw 'object '+ path + ' update failed with status code: ' + response.StatusCode; } return getObject(path); } function deleteObject(path) { response = sendRequestInSession('DELETE', 'sobjects/' + path); if (response.StatusCode != 204) { throw 'object ' + path + ' delete failed with status code: ' + response.StatusCode; } return 'object ' + path + 'was successfully deleted.'; } function isNotFoundError(err) { var msg = ('' + err).toUpperCase(); return msg.indexOf('404') !== -1 || msg.indexOf('NOT_FOUND') !== -1 || msg.indexOf('ENTITY_IS_DELETED') !== -1; } function remediationEntry(action, objectType, id, status) { var outputs = { Action: action, ObjectType: objectType, Id: id, Status: status }; return { Type: entryTypes.note, Contents: outputs, ContentsFormat: formats.json, ReadableContentsFormat: formats.markdown, HumanReadable: tableToMarkdown('Salesforce Remediation - ' + action, outputs), EntryContext: { 'SalesForce.Remediation(val.Id == obj.Id)': outputs } }; } function deleteSobject(action, defaultType, args) { var objectType = args.type || defaultType; var id = args.id; if (!id) { throw "The 'id' argument is required."; } var ignoreNotFound = (args.ignore_not_found === undefined) ? true : (String(args.ignore_not_found) === 'true'); try { deleteObject(objectType + '/' + id); } catch (err) { if (ignoreNotFound && isNotFoundError(err)) { return remediationEntry(action, objectType, id, 'NotFound (ignored)'); } throw err; } return remediationEntry(action, objectType, id, 'Deleted'); } function deleteFile(args) { return deleteSobject('Delete File', 'ContentDocument', args); } function listFilePublicLinks(args) { var id = args.id; if (!id) { throw "The 'id' argument is required."; } // Escape backslashes first, then single quotes, to avoid breaking out of the SOQL string literal. var safeId = String(id).replace(/\\/g, "\\\\").replace(/'/g, "\\'"); var records = queryObjects(['Id', 'ContentDocumentId', 'Name', 'DistributionPublicUrl'], 'ContentDistribution', "ContentDocumentId='" + safeId + "'").records || []; var distributions = []; for (var i = 0; i < records.length; i++) { distributions.push({ Id: records[i].Id, ContentDocumentId: records[i].ContentDocumentId, Name: records[i].Name, PublicUrl: records[i].DistributionPublicUrl }); } return { Type: entryTypes.note, Contents: { records: distributions }, ContentsFormat: formats.json, ReadableContentsFormat: formats.markdown, HumanReadable: tableToMarkdown('Salesforce File Public Links (ContentDistribution) for ' + id, distributions), EntryContext: { 'SalesForce.ContentDistribution(val.Id == obj.Id)': distributions } }; } function removeFilePublicLinks(args) { return deleteSobject('Remove File Public Link', 'ContentDistribution', args); } function extractActionError(first) { // Standard knowledge actions report failure detail in outputValues (keyed by // article id) while errors is often null. Surface whichever is present. if (first.errors && first.errors.length) { return JSON.stringify(first.errors); } if (first.outputValues) { return JSON.stringify(first.outputValues); } return JSON.stringify(first); } function deleteExistingDraft(id) { // Salesforce allows at most one draft per article. A lingering draft blocks // archive/unpublish of the online version. Delete it so the action can proceed. var draftId = findExistingDraftVersionId(id); if (draftId) { try { deleteObject('Knowledge__kav/' + draftId); return draftId; } catch (e) { // fall through: report original failure if we can't clear the draft } } return ''; } function findExistingDraftVersionId(id) { // Resolve the KnowledgeArticleId, then look for an existing Draft version. try { var articleId = id; if (id.length > 2 && id.substring(0, 2) === 'ka') { var kav = queryObjects(['KnowledgeArticleId'], 'Knowledge__kav', "Id='" + id + "'").records || []; if (kav.length > 0 && kav[0].KnowledgeArticleId) { articleId = kav[0].KnowledgeArticleId; } } var drafts = queryObjects(['Id'], 'Knowledge__kav', "KnowledgeArticleId='" + articleId + "' AND PublishStatus='Draft'").records || []; return (drafts.length > 0) ? drafts[0].Id : ''; } catch (e) { return ''; } } function draftKnowledgeArticle(args) { var objectType = args.type || 'KnowledgeArticleVersion'; var id = args.id; if (!id) { throw "The 'id' argument is required."; } var ignoreNotFound = (args.ignore_not_found === undefined) ? true : (String(args.ignore_not_found) === 'true'); // unpublish=true archives the current online version (removes it from public) // and creates an editable draft. unpublish=false keeps it online. var unpublish = (args.unpublish === undefined) ? true : (String(args.unpublish) === 'true'); // The standard action accepts either an Article ID (kA...) or an Article Version ID (ka...). var input = { action: 'EDIT_AS_DRAFT_ARTICLE', unpublish: unpublish }; if (id.length > 2 && id.substring(0, 2) === 'kA') { input.articleId = id; } else { input.articleVersionId = id; } var body = JSON.stringify({ inputs: [input] }); var response; try { response = sendRequestInSession('POST', 'actions/standard/createDraftFromOnlineKnowledgeArticle', body); } catch (err) { if (ignoreNotFound && isNotFoundError(err)) { return remediationEntry('Draft Knowledge Article', objectType, id, 'NotFound (ignored)'); } throw err; } var parsed = JSON.parse(response.Body); var first = (parsed && parsed.length) ? parsed[0] : {}; if (first.isSuccess === false) { // A draft may already exist (Salesforce allows only one per article), // which blocks the action. When unpublish=true the goal is remediation // (remove from public), so a lingering draft that still leaves the // article Online is NOT success - clear the draft and retry. if (unpublish) { var clearedDraftId = deleteExistingDraft(id); if (clearedDraftId) { var retryResp = sendRequestInSession('POST', 'actions/standard/createDraftFromOnlineKnowledgeArticle', body); var retryParsed = JSON.parse(retryResp.Body); first = (retryParsed && retryParsed.length) ? retryParsed[0] : {}; if (first.isSuccess === false) { throw 'Failed to draft Knowledge article ' + id + ' (after clearing draft ' + clearedDraftId + '): ' + extractActionError(first); } // fall through to success handling below } else { throw 'Failed to draft Knowledge article ' + id + ': ' + extractActionError(first); } } else { // unpublish=false: an existing editable Draft satisfies the request. var existingDraftId = findExistingDraftVersionId(id); if (existingDraftId) { var idemOutputs = { Action: 'Draft Knowledge Article', ObjectType: objectType, Id: id, Status: 'Draft (already exists)', Unpublished: unpublish, DraftVersionId: existingDraftId }; return { Type: entryTypes.note, Contents: idemOutputs, ContentsFormat: formats.json, ReadableContentsFormat: formats.markdown, HumanReadable: tableToMarkdown('Salesforce Remediation - Draft Knowledge Article', idemOutputs), EntryContext: { 'SalesForce.Remediation(val.Id == obj.Id)': idemOutputs } }; } throw 'Failed to draft Knowledge article ' + id + ': ' + extractActionError(first); } } var draftVersionId = (first.outputValues && first.outputValues.draftId) ? first.outputValues.draftId : ''; var status = unpublish ? 'Unpublished (Draft created)' : 'Draft created'; var outputs = { Action: 'Draft Knowledge Article', ObjectType: objectType, Id: id, Status: status, Unpublished: unpublish, DraftVersionId: draftVersionId }; return { Type: entryTypes.note, Contents: outputs, ContentsFormat: formats.json, ReadableContentsFormat: formats.markdown, HumanReadable: tableToMarkdown('Salesforce Remediation - Draft Knowledge Article', outputs), EntryContext: { 'SalesForce.Remediation(val.Id == obj.Id)': outputs } }; } function archiveKnowledgeArticle(args) { var objectType = args.type || 'KnowledgeArticleVersion'; var id = args.id; if (!id) { throw "The 'id' argument is required."; } var ignoreNotFound = (args.ignore_not_found === undefined) ? true : (String(args.ignore_not_found) === 'true'); var body = JSON.stringify({ inputs: [{ articleVersionIdList: [id] }] }); var response; try { response = sendRequestInSession('POST', 'actions/standard/archiveKnowledgeArticles', body); } catch (err) { if (ignoreNotFound && isNotFoundError(err)) { return remediationEntry('Archive Knowledge Article', objectType, id, 'NotFound (ignored)'); } throw err; } var parsed = JSON.parse(response.Body); var first = (parsed && parsed.length) ? parsed[0] : {}; if (first.isSuccess === false) { // A lingering Draft (Salesforce allows only one per article) blocks the // archive of the Online version with a "current state" error. Clear the // draft and retry once so the article can actually be archived. var clearedDraftId = deleteExistingDraft(id); if (clearedDraftId) { var retryResponse = sendRequestInSession('POST', 'actions/standard/archiveKnowledgeArticles', body); var retryParsed = JSON.parse(retryResponse.Body); var retryFirst = (retryParsed && retryParsed.length) ? retryParsed[0] : {}; if (retryFirst.isSuccess === false) { throw 'Failed to archive Knowledge article ' + id + ' (after clearing draft ' + clearedDraftId + '): ' + extractActionError(retryFirst); } return remediationEntry('Archive Knowledge Article', objectType, id, 'Archived (cleared draft ' + clearedDraftId + ')'); } throw 'Failed to archive Knowledge article ' + id + ': ' + extractActionError(first); } return remediationEntry('Archive Knowledge Article', objectType, id, 'Archived'); } function getCase(oid, caseNumber) { if (caseNumber !== undefined) { var condition = "CaseNumber='" + caseNumber + "'"; var properties = ['ID', 'CaseNumber', 'Subject', 'Description', 'CreatedDate', 'ClosedDate', 'OwnerID', 'Priority', 'Origin', 'Status', 'Reason','IsEscalated','SuppliedPhone','SuppliedCompany','SuppliedEmail','ContactEmail','ContactId','AccountId']; cases = queryObjects(properties, 'Case', condition).records; return casesToEntry(cases, 'Case #' + caseNumber + ':', getUserNames()); } if (oid !== undefined) { return getObject('Case/' + oid); } return { Type : entryTypes.error, Contents : 'You must specify object ID or a Case Number', // ContentsFormat: formats.json, ReadableContentsFormat: formats.markdown }; } // Add the capability to get all comment in specific case function getCaseComment(oid, caseNumber) { if (caseNumber !== undefined) { var condition = "CaseNumber='" + caseNumber + "'"; cases = queryObjects(['Id', 'CaseNumber'], 'Case', condition).records; comments = JSON.parse(sendRequestInSession('GET', 'sobjects/Case/'+cases[0].Id+'/CaseComments').Body); return commentToEntry(comments.records, 'CaseComment #' + cases[0].CaseNumber + ':', getUserNames()); } if (oid !== undefined) { comments = JSON.parse(sendRequestInSession('GET', 'sobjects/Case/'+oid+'/CaseComments').Body); return commentToEntry(comments.records, 'CaseComment #' + oid + ':', getUserNames()); } return { Type : entryTypes.error, Contents : 'You must specify object ID or a Case Number', // ContentsFormat: formats.json, ReadableContentsFormat: formats.markdown }; } function getUser(oid, caseNumber) { if (caseNumber !== undefined) { var condition = "CaseNumber='" + caseNumber + "'"; cases = queryObjects(['Id', 'CaseNumber','OwnerId'], 'Case', condition).records; var conditionA = "Id='" + cases[0].OwnerId + "'"; properties = ['Id', 'Name', 'Alias', 'CommunityNickname', 'Email','FirstName','Username']; var users = queryObjects(properties, "User", conditionA).records; return usersToEntry(users, 'User #' + cases[0].OwnerId + ':', getUserNames()); } if (oid !== undefined) { var usersOid = JSON.parse(sendRequestInSession('GET', 'sobjects/'+'User').Body); return usersToEntry(usersToEntry.records, 'User #' + usersOid + ':', getUserNames()); } return { Type : entryTypes.error, Contents : 'You must specify object ID or a Case Number', // ContentsFormat: formats.json, ReadableContentsFormat: formats.markdown }; } function getOrgName(caseNumber) { if (caseNumber !== undefined) { var condition = "CaseNumber='" + caseNumber + "'"; var properties = ['ID', 'CaseNumber','AccountId']; var cases = queryObjects(properties, 'Case', condition).records; var conditionA = "Id='" + cases[0].AccountId + "'"; var propertiesA = ['Id', 'Name']; var usersA = queryObjects(propertiesA, "Account", conditionA).records; return orgToEntry(usersA, 'Account #' + cases[0].AccountId + ':', getUserNames()); } return { Type : entryTypes.error, Contents : 'You must specify a Case Number', // ContentsFormat: formats.json, ReadableContentsFormat: formats.markdown }; } // Add the capability to post comment in specific case function postCaseComment(oid,caseNumber,text ) { data = { CommentBody : text, ParentId: caseNumber }; response = sendRequestInSession('POST', 'sobjects/CaseComment', JSON.stringify(data)); message = JSON.parse(response.Body); message.body = text; return { Type : entryTypes.note, Contents : message, ContentsFormat: formats.json, HumanReadable: tableToMarkdown('comment', message) }; } function createCase(subject, description, status, origin, priority, caseType) { var data = { Subject : subject, Description : description, Status : status, Origin : origin, Priority : priority, Type : caseType }; return createObject('Case', JSON.stringify(data)); } function updateCase(oid, caseNumber, subject, description, status, origin, priority, caseType) { if ((oid === undefined) && (caseNumber === undefined)) { return { Type : entryTypes.error, Contents : 'You must specify object ID or a Case Number', // ContentsFormat: formats.json, ReadableContentsFormat: formats.markdown }; } if (oid === undefined) { var condition = "CaseNumber='" + caseNumber + "'"; cases = queryObjects(['ID'], 'Case', condition).records; oid = (cases.length === 1) ? cases[0].Id : undefined; } var data = { Subject : subject, Description : description, Status : status, Origion : origin, Priority : priority, Type : caseType }; return updateObject('Case/' + oid, JSON.stringify(data)); } function getCases() { var properties = ['ID', 'CaseNumber', 'Subject', 'Description', 'CreatedDate', 'ClosedDate', 'OwnerID', 'Priority', 'Origin', 'Status', 'Reason']; cases = queryObjects(properties, 'Case').records; return casesToEntry(cases, 'Cases:', getUserNames()); } function closeCase(oid, caseNumber) { return updateCase(oid, caseNumber, undefined, undefined, 'Closed'); } function deleteCase(oid, caseNumber) { if ((oid === undefined) && (caseNumber === undefined)) { return { Type : entryTypes.error, Contents : 'You must specify object ID or a Case Number', // ContentsFormat: formats.json, ReadableContentsFormat: formats.markdown }; } if (oid === undefined) { var condition = "CaseNumber='" + caseNumber + "'"; cases = queryObjects(['ID'], 'Case', condition).records; oid = (cases.length === 1) ? cases[0].Id : undefined; } return deleteObject('Case/' + oid); } function pushComment(oid, text, linkUrl) { data = { body : { messageSegments : [{ type : 'Text', text : text }], }, feedElementType : 'FeedItem', subjectId : oid }; if (linkUrl !== undefined) { data.body.messageSegments.push({ type : 'Link', url : linkUrl}); } response = sendRequestInSession('POST', 'chatter/feed-elements', JSON.stringify(data)); message = JSON.parse(response.Body); return createEntry(message, { title : 'New Message', contextPath: 'SalesForce.Comment(val.URL && val.URL == obj.URL)', data : [ {to : 'Body', from : 'body.text'}, {to : 'CreatedDate', from : 'createdDate'}, {to : 'Title', from : 'header.text'}, {to : 'ParentType', from : 'parent.type'}, {to : 'ParentName', from : 'parent.name'}, {to : 'URL', from : 'url'}, {to : 'Visibility', from : 'visibility'} ] }); } function pushCommentThread(id, text) { var data = { body : { messageSegments : [{ type : 'Text', text : text }] } }; var response = sendRequestInSession('POST', 'chatter/feed-elements/'+ id +'/capabilities/comments/items', JSON.stringify(data)); var message = JSON.parse(response.Body); var output = { Body: message.body.text, CreatedDate: message.createdDate, URL: message.url }; ec = { 'SalesForce.Comment(val.URL && val.URL == obj.URL)': { URL: URI_PREFIX + 'chatter/feed-elements/' + id, Reply: output } }; return { Type: entryTypes.note, Contents: message, ContentsFormat: formats.json, ReadableContentsFormat: formats.markdown, HumanReadable: tableToMarkdown('New Reply', output), EntryContext: ec }; } function casesToIncidents(raw_info, userMapping) { var cases = []; for (var i in raw_info) { cases.push({ name : raw_info[i].Id + " " + raw_info[i].Subject, details : raw_info[i].Description, rawJSON : JSON.stringify(raw_info[i]), Reason : raw_info[i].Reason }); } if (cases.length == 0) { return '[]'; } else { return JSON.stringify(cases); } } function fetchIncident() { var fetchType = params.fetchType; lastRun = getLastRun(); if (lastRun.last_case_time === undefined) { current_time = new Date(); current_time.setMonth(current_time.getMonth() - 1); // TODO - remove this line lastRun.last_case_time = dateToString(current_time, "%Y-%m-%dT%H:%M:%S.%fZ"); setLastRun(lastRun); } var condition; var properties; var incidents = '[]'; if (fetchType === 'cases') { // query cases from last time condition = "CreatedDate>" + lastRun.last_case_time + " ORDER BY CreatedDate DESC"; properties = ['ID', 'CaseNumber', 'Subject', 'Description', 'CreatedDate', 'ClosedDate', 'OwnerID', 'Priority', 'Origin', 'Status', 'Reason']; var cases = queryObjects(properties, "Case", condition).records; if (cases.length > 0) { new_time = stringToDate(cases[0].CreatedDate, "%Y-%m-%dT%H:%M:%S.%fZ"); lastRun.last_case_time = dateToString(new_time, "%Y-%m-%dT%H:%M:%S.%fZ"); setLastRun(lastRun); var userMapping = getUserNames(); incidents = casesToIncidents(cases, userMapping); } } else { //fetchType === 'comment' //query comments from last time incidents = '[]'; //Fetch comment replies properties = ['Id', 'CommentBody', 'CreatedDate']; condition = "CreatedDate>" + lastRun.last_case_time + " ORDER BY CreatedDate DESC LIMIT 10"; var replies = queryObjects(properties, "FeedComment", condition).records; if (replies.length > 0) { var newTime = stringToDate(replies[0].CreatedDate, "%Y-%m-%dT%H:%M:%S.%fZ"); lastRun.last_case_time = dateToString(newTime, "%Y-%m-%dT%H:%M:%S.%fZ"); setLastRun(lastRun); for (var i = 0; i < replies.length; i++) { //Get reply details var replyDetails = sendRequestInSession('GET', 'chatter/comments/' + replies[i].Id); var data = JSON.parse(replyDetails.Body); var feedElement = data.feedElement; var parentID = feedElement.id; //Get parent details var parentDetails = getObject('CaseFeed/' + parentID); var parentText = parentDetails.Body; if (parentText.indexOf('DemistoID') !== -1) { var messageSegments = data.body.messageSegments; for (var j = 0; j < messageSegments.length; j++) if (messageSegments[j].type === 'Text') { // Found the relavent comment (there's only one), so we return it return JSON.stringify([{ name: parentText, details: messageSegments[j].text }]); } } } } } return incidents; } // ── UCP: Use BE-managed token if available, otherwise legacy OAuth2 ── try { var use_ucp_auth = shouldUseUcpAuth(); } catch (e) { var use_ucp_auth = false; } if (use_ucp_auth) { logDebug("[UCP][Salesforce.js] Using UCP-managed token"); var ucpCredentials = getUcpCredentials(); if (!ucpCredentials || !ucpCredentials.access_token) { logError('[UCP][Salesforce.js] Failed to obtain access token from UCP credentials'); throw '[UCP] Failed to obtain a Salesforce access token. ' + 'Please verify the authentication profile is correctly configured and the credentials are valid.'; } SESSION_DATA = { access_token: ucpCredentials.access_token, instance_url: params.InstanceURL }; } else { SESSION_DATA = getNewToken(); } // The command input arg holds the command sent from the user. var response; switch (command) { case 'fetch-incidents': return fetchIncident(); case 'salesforce-search': response = sendRequestInSession('GET','search/?q=FIND+%7B' + args.pattern +'%7D',''); return searchToEntry(JSON.parse(response.Body).searchRecords); case 'salesforce-query': return queryToEntry(queryRaw(args.query)); case 'salesforce-get-object': return getObject(args.path); case 'salesforce-update-object': return updateObject(args.path, args.json); case 'salesforce-create-object': return createObject(args.path, args.json); case 'salesforce-get-case': return getCase(args.oid, args.caseNumber); case 'salesforce-get-user': return getUser(args.oid, args.caseNumber); case 'salesforce-get-casecomment': return getCaseComment(args.oid, args.caseNumber); case 'salesforce-get-org': return getOrgName(args.caseNumber); case 'salesforce-post-casecomment': return postCaseComment(args.oid, args.caseNumber, args.text); case 'salesforce-create-case': return createCase(args.subject, args.description, args.status, args.origin, args.priority, args.type); case 'salesforce-update-case': return updateCase(args.oid, args.caseNumber, args.subject, args.description, args.status, args.origin, args.priority, args.type); case 'salesforce-get-cases': return getCases(); case 'salesforce-close-case': return closeCase(args.oid, args.caseNumber); case 'salesforce-delete-case': return deleteCase(args.oid, args.caseNumber); case 'salesforce-file-delete': return deleteFile(args); case 'salesforce-knowledge-article-archive': return archiveKnowledgeArticle(args); case 'salesforce-file-public-link-list': return listFilePublicLinks(args); case 'salesforce-file-public-link-remove': return removeFilePublicLinks(args); case 'salesforce-knowledge-article-draft-create': return draftKnowledgeArticle(args); case 'salesforce-push-comment': return pushComment(args.oid, args.text, args.link); case 'salesforce-push-comment-threads': return pushCommentThread(args.id, args.text); case 'test-module': try { sendRequestInSession('GET', '', ''); } catch (err) { return 'Connection test failed with error: ' + err + '.'; } return 'ok'; default: }





