AzureDataExplorer

Use the Azure Data Explorer integration to collect and analyze data inside Azure Data Explorer clusters, and to manage search queries.

Analytics & SIEM · Azure Data Explorer

Details

IDAzureDataExplorer
ProviderMicrosoft
CategoryAnalytics & SIEM
From Version6.0.0
Docker Imagedemisto/auth-utils:1.0.0.10133006
Supported ModulesAgentix XSIAM

README

Use the Azure Data Explorer integration to collect and analyze data inside Azure Data Explorer clusters, and to manage search queries.
This integration was integrated and tested with version V1 of AzureDataExplorer.

Authorization

In order to connect to the Azure Data Explorer using either Cortex XSOAR Azure App or the Self-Deployed Azure App, use one of the following methods:

  1. Authorization Code Flow (Recommended).
  2. Device Code Flow.
  3. Client Credentials Flow.

Self-Deployed Azure App

To use a self-configured Azure application, you need to add a new Azure App Registration in the Azure Portal.
To add the registration, refer to the following Microsoft article steps 1-8.

Required permissions

  • Azure Data Explorer - permission user_impersonation of type Delegated.
  • Microsoft Graph - permission offline_access of type Delegated.

To add a permission:

  1. Navigate to Azure Poral > Home > App registrations.
  2. Search for your app under ‘all applications’.
  3. Click API permissions > Add permission.
  4. Search for the specific Microsoft API and select the specific permission of type Delegated.

Authentication Using the Authorization Code Flow (recommended)

  1. In the Authentication Type field, select the Authorization Code option.
  2. In the Application ID field, enter your Client/Application ID.
  3. In the Client Secret field, enter your Client Secret.
  4. In the Tenant ID field, enter your Tenant ID .
  5. In the Application redirect URI field, enter your Application redirect URI.
  6. Save the instance.
  7. Run the !azure-data-explorer-generate-login-url command in the War Room and follow the instruction.
  8. Save the instance.

Authentication Using the Device Code Flow

Use the device code flow
to link Azure Data Explorer with Cortex XSOAR.

  1. Fill in the required parameters.
  2. In the Authentication Type field, select the Device Code option.
  3. Run the !azure-data-explorer-auth-start command.
  4. Follow the instructions that appear.
  5. Run the !azure-data-explorer-auth-complete command.

At end of the process you’ll see a message that you’ve logged in successfully.

Cortex XSOAR Azure App

In order to use the Cortex XSOAR Azure application, use the default application ID (a9ce8db2-847a-46af-9bfb-725d8a8d3c53).

Authentication Using the Client Credentials Flow

  1. Assign Azure roles using the Azure portal Microsoft article

    Note: In the Select members section, assign the application you created earlier.

  2. To configure a Microsoft integration that uses this authorization flow with a self-deployed Azure application:
    a. In the Authentication Type field, select the Client Credentials option.
    b. In the Application ID field, enter your Client/Application ID.
    e. In the Tenant ID field, enter your Tenant ID .
    f. In the Client Secret field, enter your Client Secret.
    g. Click Test to validate the URLs, token, and connection
    h. Save the instance.

Configure Azure Data Explorer in Cortex

Parameter Description Required
Cluster URL (e.g. https://help.kusto.windows.net)   True
Application ID   True
Client Activity Prefix A customized prefix of the client activity identifier for the query execution. For example, for a prefix value of ‘XSOAR-DataExplorer’, the client activity ID will be in the format of: ‘XSOAR-DataExplorer;<UUID>’. True
Trust any certificate (not secure)   False
Use system proxy settings   False
Authentication Type Type of authentication - could be Authorization Code Flow (recommended), Device Code Flow or Client Credentials Flow. False
Tenant ID For Authorization Code or Client Credentials Flows. False
Client Secret For Authorization Code or Client Credentials Flows. False
Application redirect URI (for Authorization Code mode)   False
Authorization code for Authorization Code mode - received from the authorization step. see Detailed Instructions (?) section False

Commands

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

azure-data-explorer-search-query-execute


Execute a Kusto Query Language (KQL) query against the given database inside a cluster. The Kusto query is a read-only request to process data and return results. To learn more about KQL go to https://docs.microsoft.com/en-us/azure/kusto/query/.

Base Command

azure-data-explorer-search-query-execute

Input

Argument Name Description Required
query Kusto Query Language (KQL) search query to execute on given database. Required
database_name The name of the database to execute the query on. Required
timeout The timeout for the execution of the search query on the server side. The timeout is a float number in minutes that ranges from 0 to 60. Default is 5. Optional

Context Output

Path Type Description
AzureDataExplorer.SearchQueryResults.Query String The executed query on the given database.
AzureDataExplorer.SearchQueryResults.ClientActivityID String The Client Activity ID. A unique identifier of the executed query.
AzureDataExplorer.SearchQueryResults.PrimaryResults Unknown The results of the query execution.
AzureDataExplorer.SearchQueryResults.Database String The database against which the query will be executed.

Command Example

!azure-data-explorer-search-query-execute database_name=Samples query="StormEvents| limit 1"

Context Example

{
    "AzureDataExplorer": {
        "SearchQueryResults": {
            "ClientActivityID": "XSOAR-DataExplorer;759d43a9-cdc1-4882-8f8b-3e8d8a703f1e",
            "Database": "Samples",
            "PrimaryResults": [
                {
                    "BeginLat": 28.0393,
                    "BeginLocation": "MELBOURNE BEACH",
                    "BeginLon": -80.6048,
                    "DamageCrops": 0,
                    "DamageProperty": 0,
                    "DeathsDirect": 0,
                    "DeathsIndirect": 0,
                    "EndLat": 28.0393,
                    "EndLocation": "MELBOURNE BEACH",
                    "EndLon": -80.6048,
                    "EndTime": "2007-09-29T08:11:00",
                    "EpisodeId": 11091,
                    "EpisodeNarrative": "Showers and thunderstorms lingering along the coast produced waterspouts in Brevard County.",
                    "EventId": 61032,
                    "EventNarrative": "A waterspout formed in the Atlantic southeast of Melbourne Beach and briefly moved toward shore.",
                    "EventType": "Waterspout",
                    "InjuriesDirect": 0,
                    "InjuriesIndirect": 0,
                    "Source": "Trained Spotter",
                    "StartTime": "2007-09-29T08:11:00",
                    "State": "ATLANTIC SOUTH",
                    "StormSummary": {
                        "Details": {
                            "Description": "A waterspout formed in the Atlantic southeast of Melbourne Beach and briefly moved toward shore.",
                            "Location": "ATLANTIC SOUTH"
                        },
                        "EndTime": "2007-09-29T08:11:00.0000000Z",
                        "StartTime": "2007-09-29T08:11:00.0000000Z",
                        "TotalDamages": 0
                    }
                }
            ],
            "Query": "StormEvents| limit 1"
        }
    }
}

Human Readable Output

Results of executing search query with client activity ID: XSOAR-DataExplorer;759d43a9-cdc1-4882-8f8b-3e8d8a703f1e

Begin Lat Begin Location Begin Lon Damage Crops Damage Property Deaths Direct Deaths Indirect End Lat End Location End Lon End Time Episode Id Episode Narrative Event Id Event Narrative Event Type Injuries Direct Injuries Indirect Source Start Time State Storm Summary
28.0393 MELBOURNE BEACH -80.6048 0 0 0 0 28.0393 MELBOURNE BEACH -80.6048 2007-09-29T08:11:00 11091 Showers and thunderstorms lingering along the coast produced waterspouts in Brevard County. 61032 A waterspout formed in the Atlantic southeast of Melbourne Beach and briefly moved toward shore. Waterspout 0 0 Trained Spotter 2007-09-29T08:11:00 ATLANTIC SOUTH TotalDamages: 0
StartTime: 2007-09-29T08:11:00.0000000Z
EndTime: 2007-09-29T08:11:00.0000000Z
Details: {“Description”: “A waterspout formed in the Atlantic southeast of Melbourne Beach and briefly moved toward shore.”, “Location”: “ATLANTIC SOUTH”}

azure-data-explorer-search-query-list


List search queries that have reached a final state in the given database. A database admin or database monitor can see any command that was invoked on their database. Other users can only see queries that they themselves invoked.

Base Command

azure-data-explorer-search-query-list

Input

Argument Name Description Required
database_name The name of the database from which to list the completed search queries. . Required
client_activity_id The client activity ID property of the search query. Use this value to get a specific search query. Optional
limit The maximum number of completed queries to return. Default is 50. Optional
page The page number from which to start a search. Default is 1. Optional
page_size The maximum number of completed queries to return per page. If this argument is not provided, an automatic pagination will be made according to the limit argument. Optional

Context Output

Path Type Description
AzureDataExplorer.SearchQuery.ClientActivityId String The client activity ID. A unique identifier of the query execution.
AzureDataExplorer.SearchQuery.Text String The search query text.
AzureDataExplorer.SearchQuery.Database String The name of the database that the search query is run on.
AzureDataExplorer.SearchQuery.StartedOn Date The query execution start time in UTC.
AzureDataExplorer.SearchQuery.LastUpdatedOn Date The last update time of the query.
AzureDataExplorer.SearchQuery.Duration Date The search query runtime.
AzureDataExplorer.SearchQuery.State String The search query state.
AzureDataExplorer.SearchQuery.RootActivityId String The root activity ID.
AzureDataExplorer.SearchQuery.User String The user who performed the query.
AzureDataExplorer.SearchQuery.FailureReason String The reason for query failure.
AzureDataExplorer.SearchQuery.TotalCpu String The total CPU clock time (User mode + Kernel mode) consumed by this query.
AzureDataExplorer.SearchQuery.CacheStatistics.Memory.Hits Number The number of cache hits.
AzureDataExplorer.SearchQuery.CacheStatistics.Memory.Misses Number The number of cache misses.
AzureDataExplorer.SearchQuery.CacheStatistics.Disk.Hits Number The number of disk hits.
AzureDataExplorer.SearchQuery.CacheStatistics.Disk.Misses Number The number of disk misses.
AzureDataExplorer.SearchQuery.CacheStatistics.Shards.Hot.HitBytes Number The amount of data (in bytes) which was found in the hot data cache of the table’s extents, during the search query execution.
AzureDataExplorer.SearchQuery.CacheStatistics.Shards.Hot.MissBytes Number The amount of data (in bytes) which was not found in the hot data cache of the table’s extents, during the search query execution.
AzureDataExplorer.SearchQuery.CacheStatistics.Shards.Hot.RetrieveBytes Number The amount of data (in bytes) that was retrieved from hot data cache of the table’s extents, during the search query execution.
AzureDataExplorer.SearchQuery.CacheStatistics.Shards.Cold.HitBytes Number The amount of data (in bytes) which was found in the cold data cache of the table’s extents, during the search query execution.
AzureDataExplorer.SearchQuery.CacheStatistics.Shards.Cold.MissBytes Number The amount of data (in bytes) which was not found in the cold data cache of the table’s extents, during the search query execution.
AzureDataExplorer.SearchQuery.CacheStatistics.Shards.Cold.RetrieveBytes Number The amount of data (in bytes) that was retrieved from cold data cache during the search query execution.
AzureDataExplorer.SearchQuery.CacheStatistics.Shards.BypassBytes Number The amount of data (in bytes) that was bypassed (reloaded) in the cache of the table’s extents during the search query execution.
AzureDataExplorer.SearchQuery.Application String The application name that invoked the command.
AzureDataExplorer.SearchQuery.MemoryPeak Number The peak memory usage of the query execution.
AzureDataExplorer.SearchQuery.ScannedExtentsStatistics.MinDataScannedTime Date The minimum data scan time.
AzureDataExplorer.SearchQuery.ScannedExtentsStatistics.MaxDataScannedTime Date The maximum data scan time.
AzureDataExplorer.SearchQuery.ScannedExtentsStatistics.TotalExtentsCount Number The total number of extents which were used during the query execution.
AzureDataExplorer.SearchQuery.ScannedExtentsStatistics.ScannedExtentsCount Number The number of extents which were scanned during the query execution.
AzureDataExplorer.SearchQuery.ScannedExtentsStatistics.TotalRowsCount Number The total row count of extents which were used during the query execution.
AzureDataExplorer.SearchQuery.ScannedExtentsStatistics.ScannedRowsCount Number The number of scanned rows of an extent during query execution.
AzureDataExplorer.SearchQuery.Principal String The principal that invoked the query.
AzureDataExplorer.SearchQuery.ClientRequestProperties.SecurityTokenPresent Boolean Whether the security token is present in the request or not.
AzureDataExplorer.SearchQuery.ClientRequestProperties.AuthorizationScheme String The authorization scheme.
AzureDataExplorer.SearchQuery.ClientRequestProperties.RequestHostName String The hostname of the request.
AzureDataExplorer.SearchQuery.ClientRequestProperties.LocalClusterName String The cluster name.
AzureDataExplorer.SearchQuery.ClientRequestProperties.OriginClusterName String The origin cluster name.
AzureDataExplorer.SearchQuery.ClientRequestProperties.Options.api_version String The API version.
AzureDataExplorer.SearchQuery.ClientRequestProperties.Options.request_readonly Boolean Whether the request is read-only or not.
AzureDataExplorer.SearchQuery.ClientRequestProperties.Options.servertimeout Number The server timeout value.
AzureDataExplorer.SearchQuery.ClientRequestProperties.Options.servertimeoutorigin String The server timeout origin.
AzureDataExplorer.SearchQuery.ClientRequestProperties.Options.query_datascope Number The query datascope.
AzureDataExplorer.SearchQuery.ClientRequestProperties.Options.query_fanout_nodes_percent Number The percentage of the query nodes in the cluster to use per subquery distribution operation.
AzureDataExplorer.SearchQuery.ClientRequestProperties.Options.query_fanout_threads_percent Number The percentage of CPUs the cluster will assign on each node.
AzureDataExplorer.SearchQuery.ClientRequestProperties.Options.maxmemoryconsumptionperiterator Number The maximum amount of memory that a single query plan result set iterator can hold.
AzureDataExplorer.SearchQuery.ClientRequestProperties.Options.max_memory_consumption_per_query_per_node Number The maximum amount of memory that can be used on a single node for a specific query.
AzureDataExplorer.SearchQuery.ClientRequestProperties.Options.truncationmaxsize Number The maximum overall data size returned by the query, in bytes.
AzureDataExplorer.SearchQuery.ClientRequestProperties.Options.truncationmaxrecords Number The maximum number of records returned by the query.
AzureDataExplorer.SearchQuery.ResultSetStatistics.TableCount Number The number of tables that were retrieved following search query execution.
AzureDataExplorer.SearchQuery.ResultSetStatistics.TablesStatistics.RowCount Number The row count of the table retrieved following search query execution.
AzureDataExplorer.SearchQuery.ResultSetStatistics.TablesStatistics.TableSize Number The total size in bytes of the table retrieved following search query execution.
AzureDataExplorer.SearchQuery.WorkloadGroup String The workload group which the query was assigned to. The query is executed using the policies assigned to the workload group. There are two pre-defined workload groups (internal and default) and up to 10 custom workload groups which may be defined at the cluster level.

Command Example

!azure-data-explorer-search-query-list database_name=Samples limit=1

Context Example

{
    "AzureDataExplorer": {
        "SearchQuery": {
            "Application": "KusWeb",
            "CacheStatistics": {
                "Disk": {
                    "Hits": 0,
                    "Misses": 0
                },
                "Memory": {
                    "Hits": 0,
                    "Misses": 0
                },
                "Shards": {
                    "BypassBytes": 0,
                    "Cold": {
                        "HitBytes": 0,
                        "MissBytes": 0,
                        "RetrieveBytes": 0
                    },
                    "Hot": {
                        "HitBytes": 0,
                        "MissBytes": 0,
                        "RetrieveBytes": 0
                    }
                }
            },
            "ClientActivityId": "KustoWebV2;f1be2c7e-f810-437b-a1f8-f8bbbedf238d",
            "ClientRequestProperties": {
                "AuthorizationScheme": "Bearer",
                "LocalClusterName": "https://help.kusto.windows.net/",
                "Options": {
                    "api_version": "v2",
                    "max_memory_consumption_per_query_per_node": 2000000000,
                    "maxmemoryconsumptionperiterator": 5368709120,
                    "query_datascope": 1,
                    "query_fanout_nodes_percent": 100,
                    "query_fanout_threads_percent": 100,
                    "query_language": "csl",
                    "queryconsistency": "strongconsistency",
                    "request_app_name": "KusWeb",
                    "request_readonly": true,
                    "request_readonly_hardline": false,
                    "servertimeout": 600000000,
                    "truncationmaxrecords": 500000,
                    "truncationmaxsize": 67108864
                },
                "OriginClusterName": "https://help.kusto.windows.net/",
                "RequestHostName": "https://help.kusto.windows.net:443/",
                "SecurityTokenPresent": true
            },
            "Database": "Samples",
            "Duration": "0:00:00",
            "FailureReason": "[none]",
            "LastUpdatedOn": "2021-11-24T15:15:27",
            "MemoryPeak": 0,
            "Principal": "aaduser=xxx-xxxx-xxxx",
            "ResultSetStatistics": {
                "TableCount": 2,
                "TablesStatistics": [
                    {
                        "RowCount": 0,
                        "TableSize": 0
                    },
                    {
                        "RowCount": 2,
                        "TableSize": 1244
                    }
                ]
            },
            "RootActivityId": "2b9e0ec8-f6b0-407e-90b6-68eba3777564",
            "ScannedExtentsStatistics": {
                "MaxDataScannedTime": null,
                "MinDataScannedTime": null,
                "ScannedExtentsCount": 0,
                "ScannedRowsCount": 0,
                "TotalExtentsCount": 0,
                "TotalRowsCount": 0
            },
            "StartedOn": "2021-11-24T15:15:27",
            "State": "Completed",
            "Text": "set notruncation;\nCovid19",
            "TotalCpu": "0:00:00",
            "User": "dataExplorer@qmasterslabgmail.onmicrosoft.com",
            "WorkloadGroup": "default"
        }
    }
}

Human Readable Output

List of Completed Search Queries

Showing 0 to 1 records out of 134.

Client Activity Id User Text Database Started On Last Updated On State
KustoWebV2;f1be2c7e-f810-437b-a1f8-f8bbbedf238d dataExplorer@qmasterslabgmail.onmicrosoft.com set notruncation;
Covid19
Samples 2021-11-24T15:15:27 2021-11-24T15:15:27 Completed

azure-data-explorer-running-search-query-list


List currently executing search queries in the given database. A database admin or database monitor can see any search query that was invoked on their database.
Other users can only see search queries that they themselves invoked.

Base Command

azure-data-explorer-running-search-query-list

Input

Argument Name Description Required
database_name The database name. Required
client_activity_id The client activity ID property of the search query. Use this to get a specific running search query. Optional
limit The maximum number of running queries to return. Default is 50. Optional
page The page number from which to start a search. Default is 1. Optional
page_size The maximum number of running queries to return per page. If this argument is not provided, an automatic pagination will be made according to the limit argument. Optional

Context Output

Path Type Description
AzureDataExplorer.RunningSearchQuery.ClientActivityId String The client activity ID. A unique identifier of the query execution.
AzureDataExplorer.RunningSearchQuery.Text String The search query text.
AzureDataExplorer.RunningSearchQuery.Database String The name of the database that the search query is run on.
AzureDataExplorer.RunningSearchQuery.StartedOn Date The query execution start time in UTC.
AzureDataExplorer.RunningSearchQuery.LastUpdatedOn String The last update time of the query.
AzureDataExplorer.RunningSearchQuery.Duration Date The search query runtime duration.
AzureDataExplorer.RunningSearchQuery.State String The search query state.
AzureDataExplorer.RunningSearchQuery.RootActivityId String The root activity ID.
AzureDataExplorer.RunningSearchQuery.User String The user who performed the query.
AzureDataExplorer.RunningSearchQuery.FailureReason String The reason for query failure.
AzureDataExplorer.RunningSearchQuery.TotalCpu String The total CPU clock time (User mode + Kernel mode) consumed by this query.
AzureDataExplorer.RunningSearchQuery.CacheStatistics Unknown The cache statistics.
AzureDataExplorer.RunningSearchQuery.Application String The application name that invoked the command.
AzureDataExplorer.RunningSearchQuery.MemoryPeak Number The peak memory usage of the running query execution.
AzureDataExplorer.RunningSearchQuery.ScannedExtentsStatistics Unknown The scanned extent count.
AzureDataExplorer.RunningSearchQuery.Principal String The principal that invoked the query.
AzureDataExplorer.RunningSearchQuery.ClientRequestProperties.SecurityTokenPresent Boolean Whether the security token is present in the request or not.
AzureDataExplorer.RunningSearchQuery.ClientRequestProperties.AuthorizationScheme String The authorization scheme.
AzureDataExplorer.RunningSearchQuery.ClientRequestProperties.RequestHostName String The hostname of the request.
AzureDataExplorer.RunningSearchQuery.ClientRequestProperties.LocalClusterName String The cluster name.
AzureDataExplorer.RunningSearchQuery.ClientRequestProperties.OriginClusterName String The origin cluster name.
AzureDataExplorer.RunningSearchQuery.ClientRequestProperties.Options.api_version String The API version.
AzureDataExplorer.RunningSearchQuery.ClientRequestProperties.Options.request_readonly Boolean Whether the request is read-only or not.
AzureDataExplorer.RunningSearchQuery.ClientRequestProperties.Options.servertimeout Number The server timeout value.
AzureDataExplorer.RunningSearchQuery.ClientRequestProperties.Options.servertimeoutorigin String The server timeout origin.
AzureDataExplorer.RunningSearchQuery.ClientRequestProperties.Options.query_datascope Number The query datascope.
AzureDataExplorer.RunningSearchQuery.ClientRequestProperties.Options.query_fanout_nodes_percent Number The percentage of the query nodes in the cluster to use per subquery distribution operation.
AzureDataExplorer.RunningSearchQuery.ClientRequestProperties.Options.query_fanout_threads_percent Number The percentage of CPUs the cluster will assign on each node.
AzureDataExplorer.RunningSearchQuery.ClientRequestProperties.Options.maxmemoryconsumptionperiterator Number The maximum amount of memory that a single query plan result set iterator can hold.
AzureDataExplorer.RunningSearchQuery.ClientRequestProperties.Options.max_memory_consumption_per_query_per_node Number The maximum amount of memory that can be used on a single node for a specific query.
AzureDataExplorer.RunningSearchQuery.ClientRequestProperties.Options.truncationmaxsize Number The maximum overall data size returned by the query, in bytes.
AzureDataExplorer.RunningSearchQuery.ClientRequestProperties.Options.truncationmaxrecords Number The maximum number of records returned by the query.
AzureDataExplorer.RunningSearchQuery.ResultSetStatistics Unknown The result set statistics.
AzureDataExplorer.RunningSearchQuery.WorkloadGroup String The workload group.

Command Example

!azure-data-explorer-running-search-query-list database_name=Samples limit=1

Context Example

{
    "AzureDataExplorer": {
        "RunningSearchQuery": {
            "Application": "KusWeb",
            "CacheStatistics": "null",
            "ClientActivityId": "KustoWebV2;c6ff3e99-d2cb-4a3e-ab05-955ae383a7c6",
            "ClientRequestProperties": "{\"SecurityTokenPresent\":true,\"AuthorizationScheme\":\"Bearer\",\"RequestHostName\":\"https://help.kusto.windows.net:443/\",\"LocalClusterName\":\"https://help.kusto.windows.net/\",\"OriginClusterName\":\"https://help.kusto.windows.net/\",\"Options\":{\"servertimeout\":600000000,\"queryconsistency\":\"strongconsistency\",\"query_language\":\"csl\",\"request_readonly\":true,\"request_readonly_hardline\":false,\"api_version\":\"v2\",\"request_app_name\":\"KusWeb\",\"query_datascope\":1,\"query_fanout_nodes_percent\":100,\"query_fanout_threads_percent\":100,\"maxmemoryconsumptionperiterator\":5368709120,\"max_memory_consumption_per_query_per_node\":2000000000,\"truncationmaxsize\":67108864,\"truncationmaxrecords\":500000}}",
            "Database": "Samples",
            "Duration": "0:00:00",
            "FailureReason": "",
            "LastUpdatedOn": "2021-11-24T15:16:34",
            "MemoryPeak": 0,
            "Principal": "aaduser=xxx-xxxx-xxxx",
            "ResultSetStatistics": "null",
            "RootActivityId": "c8233607-30a9-4cc0-9c54-ec716e5fc246",
            "ScannedExtentsStatistics": "null",
            "StartedOn": "2021-11-24T15:16:34",
            "State": "InProgress",
            "Text": "set notruncation;\nCovid19_Bing",
            "TotalCpu": "0:00:00",
            "User": "dataExplorer@qmasterslabgmail.onmicrosoft.com",
            "WorkloadGroup": "default"
        }
    }
}

Human Readable Output

List of Currently running Search Queries

Showing 0 to 1 records out of 2.

Client Activity Id User Text Database Started On Last Updated On State
KustoWebV2;c6ff3e99-d2cb-4a3e-ab05-955ae383a7c6 dataExplorer@qmasterslabgmail.onmicrosoft.com set notruncation;
Covid19_Bing
Samples 2021-11-24T15:16:34 2021-11-24T15:16:34 InProgress

azure-data-explorer-running-search-query-cancel


Starts a best-effort attempt to cancel a specific running search query in the specified database.

Base Command

azure-data-explorer-running-search-query-cancel

Input

Argument Name Description Required
client_activity_id The client activity ID of the query to delete. Required
database_name The database name. Required
reason The reason for canceling the running query. . Optional

Context Output

Path Type Description
AzureDataExplorer.CanceledSearchQuery.RunningQueryCanceled Boolean Whether the query was successfully canceled or not.
AzureDataExplorer.CanceledSearchQuery.ClientRequestId String The client activity ID of the cancelled query.
AzureDataExplorer.CanceledSearchQuery.ReasonPhrase String The reason for canceling the running query.

Command Example

!azure-data-explorer-running-search-query-cancel database_name=Samples client_activity_id=xxxx-xxxxx-xxxxx

Context Example

{
    "AzureDataExplorer": {
        "CanceledSearchQuery": {
            "ClientRequestId": "xxxx-xxxxx-xxxxx",
            "ReasonPhrase": "None",
            "RunningQueryCanceled": false
        }
    }
}

Human Readable Output

Canceled Search Query xxxx-xxxxx-xxxxx

Client Request Id Reason Phrase Running Query Canceled
xxxx-xxxxx-xxxxx None false

azure-data-explorer-auth-start


Run this command to start the authorization process and follow the instructions in the command results.

Base Command

azure-data-explorer-auth-start

Input

There are no input arguments for this command.

Context Output

There is no context output for this command.

azure-data-explorer-auth-complete


Run this command to complete the authorization process. This should be used after running the azure-data-explorer-auth-start command.

Base Command

azure-data-explorer-auth-complete

Input

There are no input arguments for this command.

Context Output

There is no context output for this command.

azure-data-explorer-auth-reset


Run this command if for some reason you need to rerun the authentication process.

Base Command

azure-data-explorer-auth-reset

Input

There are no input arguments for this command.

Context Output

There is no context output for this command.

azure-data-explorer-auth-test


Run this command to test the connectivity to Azure Data Explorer.

Base Command

azure-data-explorer-auth-test

Input

There are no input arguments for this command.

Context Output

There is no context output for this command.

azure-data-explorer-generate-login-url


Generate the login url used for Authorization code flow.

Base Command

azure-data-explorer-generate-login-url

Input

There are no input arguments for this command.

Context Output

There is no context output for this command.

Command Example

azure-data-explorer-generate-login-url

Human Readable Output

Authorization instructions

  1. Click on the login URL to sign in and grant Cortex XSOAR permissions for your Azure Service Management.
    You will be automatically redirected to a link with the following structure:
    REDIRECT_URI?code=AUTH_CODE&session_state=SESSION_STATE
  2. Copy the AUTH_CODE (without the code= prefix, and the session_state parameter)
    and paste it in your instance configuration under the Authorization code parameter.

Configuration parameters

  • cluster_url — Cluster URL (e.g. https://help.kusto.windows.net) (required)
  • client_id — Application ID (required)
  • client_activity_prefix — Client Activity Prefix (required)
  • insecure — Trust any certificate (not secure)
  • proxy — Use system proxy settings
  • authentication_type — Authentication Type (required)
  • tenant_id — Tenant ID
  • credentials — Client Secret
  • redirect_uri — Application redirect URI
  • auth_code — Authorization code

Commands (9)

  • azure-data-explorer-auth-complete

    Run this command to complete the authorization process. This should be used after running the azure-data-explorer-auth-start command.

  • azure-data-explorer-auth-reset

    Run this command if for some reason you need to rerun the authentication process.

  • azure-data-explorer-auth-start

    Run this command to start the authorization process and follow the instructions in the command results.

  • azure-data-explorer-auth-test

    Run this command to test the connectivity to Azure Data Explorer.

  • azure-data-explorer-generate-login-url

    Generate the login url used for Authorization code flow.

  • azure-data-explorer-running-search-query-cancel

    Starts a best-effort attempt to cancel a specific running search query in the specified database.

  • azure-data-explorer-running-search-query-list

    List currently executing search queries in the given database. A database admin or database monitor can see any search query that was invoked on their database. Other users can only see search queries that they themselves invoked.

  • azure-data-explorer-search-query-execute

    Execute a Kusto Query Language (KQL) query against the given database inside a cluster. The Kusto query is a read-only request to process data and return results. To learn more about KQL go to https://docs.microsoft.com/en-us/azure/kusto/query/.

  • azure-data-explorer-search-query-list

    List search queries that have reached a final state in the given database. A database admin or database monitor can see any command that was invoked on their database. Other users can only see queries that they themselves invoked.

from CommonServerPython import *

""" IMPORTS """
import uuid
from datetime import datetime
from decimal import Decimal

import requests
from azure.kusto.data.response import KustoResponseDataSet, KustoResponseDataSetV1
from MicrosoftApiModule import *  # noqa: E402

""" CONSTANTS """
DEFAULT_PAGE_NUMBER = "1"
DEFAULT_LIMIT = "50"
DATE_TIME_FORMAT = "%Y-%m-%dT%H:%M:%S"
REQUEST_BASE_TIMEOUT = 20
GRANT_BY_CONNECTION = {
    "Device Code": DEVICE_CODE,
    "Authorization Code": AUTHORIZATION_CODE,
    "Client Credentials": CLIENT_CREDENTIALS,
}


class DataExplorerClient:
    """
    Azure Data Explorer API Client.
    """

    def __init__(
        self,
        cluster_url: str,
        client_id: str,
        client_activity_prefix: str,
        verify: bool,
        proxy: bool,
        connection_type: str,
        tenant_id: str = None,
        enc_key: str = None,
        auth_code: str = None,
        redirect_uri: str = None,
    ):
        if "@" in client_id:  # for use in test-playbook
            client_id, refresh_token = client_id.split("@")
            integration_context = get_integration_context()
            integration_context.update(current_refresh_token=refresh_token)
            set_integration_context(integration_context)

        if not cluster_url.startswith("https://"):
            raise ValueError("Cluster URL parameter must contain 'https://' as prefix (e.g. https://help.kusto.windows.net).")

        self.cluster_url = cluster_url
        self.host = cluster_url.split("https://")[1]

        self.scope = (
            f"{cluster_url}/user_impersonation offline_access user.read"
            if "Device Code" in connection_type
            else f"{cluster_url}/.default"
        )
        self.client_activity_prefix = client_activity_prefix
        client_args = assign_params(
            self_deployed=True,
            auth_id=client_id,
            token_retrieval_url="https://login.microsoftonline.com/organizations/oauth2/v2.0/token"
            if "Device Code" in connection_type
            else None,
            grant_type=GRANT_BY_CONNECTION[connection_type],
            base_url=cluster_url,
            verify=verify,
            proxy=proxy,
            scope=self.scope,
            tenant_id=tenant_id,
            enc_key=enc_key,
            auth_code=auth_code,
            redirect_uri=redirect_uri,
            command_prefix="azure-data-explorer",
        )
        self.ms_client = MicrosoftClient(**client_args)
        self.connection_type = connection_type

    def http_request(
        self,
        method,
        url_suffix: str = None,
        full_url: str = None,
        params: dict = None,
        headers=None,
        data=None,
        timeout: int = REQUEST_BASE_TIMEOUT,
    ):
        if headers is None:
            headers = {}
        if data is None:
            data = {}
        headers.update(
            {
                "Accept": "application/json",
                "Expect": "100-Continue",
                "Content-Type": "application/json; charset=utf-8",
                "Host": self.host,
                "Connection": "Keep-Alive",
            }
        )

        res = self.ms_client.http_request(
            method=method,
            url_suffix=url_suffix,
            full_url=full_url,
            headers=headers,
            json_data=data,
            params=params,
            resp_type="response",
            timeout=timeout,
            ok_codes=(200, 204, 400, 401, 403, 404, 409),
        )

        if res.status_code in (200, 204) and not res.text:
            return res

        res_json = res.json()

        if res.status_code in (400, 401, 403, 404, 409):
            code = res_json.get("error", {}).get("code", "Error")
            error_msg = res_json.get("error", {}).get("message", res_json)
            raise ValueError(f"[{code} {res.status_code}] {error_msg}")

        return res_json

    def search_query_execute_request(
        self, database_name: str, query: str, server_timeout: Decimal, client_activity_id: str
    ) -> dict[str, Any]:
        """
            Execute a KQL query against the given database inside the specified cluster.
            The query's client activity ID is a combination of the user's
            client_activity_prefix parameter and a random UUID.
        Args:
            database_name (str): The name of the database to execute the query on.
            query (str): The KQL query to execute against the database.
            server_timeout: Query execution timeout on server side.
            client_activity_id (str): A unique ID for query execution.
        Returns:
            Dict[str,Any]: API response from Azure.
        """
        data = retrieve_common_request_body(database_name, query, {"Options": {"servertimeout": f"{server_timeout}m"}})
        headers = {"x-ms-client-request-id": client_activity_id}
        response = self.http_request(
            "POST",
            url_suffix="/v1/rest/query",
            data=data,
            headers=headers,
            timeout=calculate_total_request_timeout(server_timeout),
        )
        return response

    def search_queries_list_request(self, database_name: str, client_activity_id: str) -> dict[str, Any]:
        """
            List search queries that have reached a final state on the given database.
            When the client_activity_id argument is provided, the request will retrieve information
            regarding specific search query.

        Args:
            database_name (str): The name of the database to see the completed queries.
            client_activity_id (str):  client-specified identity of the request.

        Returns:
            Dict[str, Any]: API response from Azure.
        """
        mgmt_query = (
            f".show queries | where ClientActivityId=='{client_activity_id}'"
            if client_activity_id
            else ".show queries | sort by StartedOn"
        )
        return self.management_query_request(database_name, mgmt_query)

    def running_search_queries_list_request(self, database_name: str, client_activity_id: str) -> dict[str, Any]:
        """
            List currently running search queries on the given database.
            When client_activity_id argument is set, the request will retrieve information
            regarding specific running search query.

        Args:
            database_name (str): The name of the database to see the running queries.
            client_activity_id (str): Client-specified identity of the request.

        Returns:
            Dict[str, Any]: API response from Azure.
        """
        mgmt_query = (
            f".show running queries | where ClientActivityId=='{client_activity_id}'"
            if client_activity_id
            else ".show running queries | sort by StartedOn"
        )

        return self.management_query_request(database_name, mgmt_query)

    def running_search_query_delete_request(self, database_name: str, client_activity_id: str, reason: str) -> dict[str, Any]:
        """
        Starts a best-effort attempt to cancel a specific running search query
        on the given database.

        Args:
            database_name (str): The name of the database to see the completed queries on.
            client_activity_id (str):  Client specified identity of the request.
            reason (str): The reason for the cancellation.
        Returns:
            Dict[str, Any]: API response from Azure.
        """
        cancel_running_query = f".cancel query '{client_activity_id}'"

        if reason:
            cancel_running_query += f" with ( reason = '{reason}' )"
        return self.management_query_request(database_name, cancel_running_query)

    def management_query_request(self, database_name: str, mgmt_query: str) -> dict[str, Any]:
        """
         API call method for management query endpoint.
         Each requests that uses management query endpoint uses this method.
        Args:
            database_name (str): The name of the database to see the completed queries on.
            mgmt_query (str):  Client specified identity of the request.
        Returns:
            Dict[str, Any]: API response from Azure.
        """
        data = retrieve_common_request_body(database_name, mgmt_query)
        response = self.http_request("POST", url_suffix="/v1/rest/mgmt", data=data)
        return response


def search_query_execute_command(client: DataExplorerClient, args: dict[str, Any]) -> CommandResults:
    """
    Execute search query command.
    Args:
        client (DataExplorerClient): Azure Data Explorer API client.
        args (Dict[str, Any]): Command arguments from XSOAR.
    Returns:
        CommandResults: Command results with raw response, outputs and readable outputs.

    """
    query = str(args["query"])
    database_name = str(args["database_name"])
    timeout = Decimal(args.get("timeout", "5"))
    if timeout < 0 or timeout > 60:
        raise ValueError("Timeout argument should be a float number between 0 to 60.")

    client_activity_id = f"{client.client_activity_prefix};{uuid.uuid4()}"
    response = client.search_query_execute_request(database_name, query, timeout, client_activity_id)
    response_kusto_dataset = KustoResponseDataSetV1(response)
    primary_results = convert_kusto_response_to_dict(response_kusto_dataset)
    outputs = {
        "Database": database_name,
        "Query": query,
        "ClientActivityID": client_activity_id,
        "PrimaryResults": primary_results,
    }
    readable_output = tableToMarkdown(
        f"Results of executing search query with client activity ID: {client_activity_id}",
        primary_results,
        headerTransform=pascalToSpace,
    )
    command_results = CommandResults(
        outputs_prefix="AzureDataExplorer.SearchQueryResults",
        outputs_key_field="ClientActivityID",
        outputs=outputs,
        raw_response=response,
        readable_output=readable_output,
    )

    return command_results


def search_queries_list_command(client: DataExplorerClient, args: dict[str, Any]) -> CommandResults:
    """
    List completed search queries command.
    Args:
        client (DataExplorerClient): Azure Data Explorer API client.
        args (Dict[str, Any]): Command arguments from XSOAR.
    Returns:
        CommandResults: Command results with raw response, outputs and readable outputs.

    """

    database_name = str(args["database_name"])
    page = arg_to_number(args.get("page", DEFAULT_PAGE_NUMBER))
    page_size = arg_to_number(args.get("page_size"))
    limit = arg_to_number(args.get("limit", DEFAULT_LIMIT))
    client_activity_id = str(args.get("client_activity_id", ""))
    validate_list_command_arguments(page, page_size, limit)  # type: ignore[arg-type]
    response = client.search_queries_list_request(database_name, client_activity_id)

    return retrieve_command_results_of_list_commands(
        response,
        "List of Completed Search Queries",
        page,  # type: ignore[arg-type]
        page_size,  # type: ignore[arg-type]
        limit,  # type: ignore[arg-type]
        "AzureDataExplorer.SearchQuery",
    )


def running_search_queries_list_command(client: DataExplorerClient, args: dict[str, Any]) -> CommandResults:
    """
    List currently running search queries command.
    Args:
        client (DataExplorerClient): Azure Data Explorer API client.
        args (Dict[str, Any]): Command arguments from XSOAR.
    Returns:
        CommandResults: Command results with raw response, outputs and readable outputs.

    """
    database_name = str(args["database_name"])
    page = arg_to_number(args.get("page", DEFAULT_PAGE_NUMBER))
    page_size = arg_to_number(args.get("page_size"))
    limit = arg_to_number(args.get("limit", DEFAULT_LIMIT))
    client_activity_id = str(args.get("client_activity_id", ""))

    validate_list_command_arguments(page, page_size, limit)  # type: ignore[arg-type]
    response = client.running_search_queries_list_request(database_name, client_activity_id)

    return retrieve_command_results_of_list_commands(
        response,
        "List of Currently running Search Queries",
        page,  # type: ignore[arg-type]
        page_size,  # type: ignore[arg-type]
        limit,  # type: ignore[arg-type]
        "AzureDataExplorer.RunningSearchQuery",
    )


def running_search_query_cancel_command(client: DataExplorerClient, args: dict[str, Any]) -> CommandResults:
    """
    Cancel currently running search query command.
    Args:
        client (DataExplorerClient): Azure Data Explorer API client.
        args (Dict[str, Any]): Command arguments from XSOAR.
    Returns:
        CommandResults: Command results with raw response, outputs and readable outputs.

    """
    client_activity_id = str(args["client_activity_id"])
    database_name = str(args["database_name"])
    reason = str(args.get("reason"))
    response = client.running_search_query_delete_request(database_name, client_activity_id, reason)

    response_kusto_dataset = KustoResponseDataSetV1(response)

    outputs = convert_kusto_response_to_dict(response_kusto_dataset)
    readable_output = tableToMarkdown(
        f"Canceled Search Query {client_activity_id}",
        outputs,
        headers=["ClientRequestId", "ReasonPhrase", "RunningQueryCanceled"],
        headerTransform=pascalToSpace,
    )
    command_results = CommandResults(
        outputs_prefix="AzureDataExplorer.CanceledSearchQuery",
        outputs_key_field="ClientRequestId",
        outputs=outputs,
        raw_response=response,
        readable_output=readable_output,
    )

    return command_results


def retrieve_command_results_of_list_commands(
    response: dict[str, Any], base_header: str, page: int, page_size: int, limit: int, outputs_prefix: str
) -> CommandResults:
    """
    Retrieves the command results of list commands.
    Args:
        response (Dict[str,Any]): API response from Azure.
        base_header: (str) Header prefix in the readable output.
        page (int): Page number.
        page_size (int): Page size.
        limit (int): Page size.
        outputs_prefix (str): Command context outputs prefix.
    Returns:
        CommandResults: List Command results.
    """
    response_kusto_dataset = KustoResponseDataSetV1(response)
    total_rows = response_kusto_dataset.primary_results[0].rows_count

    outputs = convert_kusto_response_to_dict(response_kusto_dataset, page, page_size, limit)
    readable_header = format_header_for_list_commands(base_header, total_rows, page, page_size, limit)
    readable_output = tableToMarkdown(
        readable_header,
        outputs,
        headers=["ClientActivityId", "User", "Text", "Database", "StartedOn", "LastUpdatedOn", "State"],
        headerTransform=pascalToSpace,
    )
    command_results = CommandResults(
        outputs_prefix=outputs_prefix,
        outputs_key_field="ClientActivityId",
        outputs=outputs,
        raw_response=response,
        readable_output=readable_output,
    )

    return command_results


""" INTEGRATION HELPER METHODS """


def convert_datetime_fields(raw_data: list[dict]) -> list[dict]:
    """
    Converting datetime fields of the response from the API call
    to str type (in order to make the response json-serializable).

    Args:
        raw_data (List[dict]): Response from API call to azure.

    Returns:
         List[dict]: JSON serializable response from API.
    """
    for row in raw_data:
        for key, value in row.items():
            if isinstance(value, datetime):
                row[key] = value.strftime(DATE_TIME_FORMAT)
            if isinstance(value, timedelta):
                row[key] = str(value)
    return raw_data


def convert_kusto_response_to_dict(
    kusto_response: KustoResponseDataSet, page: int = None, page_size: int = None, limit: int = None
) -> list[dict]:
    """
    Converting KustoResponseDataSet object to dict type.
    Support two use cases of pagination: 'Manual Pagination' and 'Automatic Pagination'.
    Args:
        kusto_response (KustoResponseDataSet): The response from API call.
        page (int): First index to retrieve from.
        page_size (int) : Number of records to return per page.
        limit (int): Limit on the number of the results to return.

    Returns:
        Dict[str, Any]: Converted response.
    """
    raw_data = kusto_response.primary_results[0].to_dict().get("data", [])
    if page and page_size:  # Manual Pagination
        from_index = min((page - 1) * page_size, len(raw_data))
        to_index = min(from_index + page_size, len(raw_data))
        relevant_raw_data = raw_data[from_index:to_index]

    elif limit:  # Automatic Pagination
        relevant_raw_data = raw_data[: min(len(raw_data), limit)]

    else:  # used only in search query execution command
        relevant_raw_data = raw_data
    serialized_data: list[dict] = convert_datetime_fields(relevant_raw_data)
    return serialized_data


def format_header_for_list_commands(base_header: str, rows_count: int, page: int, page_size: int, limit: int) -> str:
    """
    Retrieve the header of the readable output for list commands.
    Format the header according to the pagination use case:
    'Manual Pagination' or 'Automatic Pagination'.
    Args:
        base_header (str): The header prefix.
        rows_count (int): The number of rows in the output.
        page (int): Client's page number argument.
        page_size (int): number of records per page.
        limit (int): Client's limit argument.
    Returns:
        Dict[str, Any]: Header for readable output of the command.
    """
    if page_size:
        total_pages = rows_count // page_size + (rows_count % page_size != 0)
        if rows_count > 0:
            base_header += f" \nShowing page {page} out of {total_pages} total pages. Current page size: {page_size}."
    else:
        base_header += f" \nShowing 0 to {limit} records out of {rows_count}."
    return base_header


def retrieve_common_request_body(database_name: str, query: str, properties: dict[str, Any] = None) -> dict[str, Any]:
    """
    Retrieve requests body. For every request, the body contains the database name and the query to the execute.

    Args:
        database_name (str): The database name.
        query (str): The query to execute.
        properties (Dict[str, Any], optional): Other user's properties to send in the request
                                               Defaults to None.

    Returns:
        Dict[str, Any]: Body raw data for the request.
    """
    data = {"db": database_name, "csl": query}
    if properties:
        data["properties"] = properties  # type: ignore[assignment]
    return data


def calculate_total_request_timeout(server_timeout: Decimal) -> int:
    """
    Calculates the total timeout duration of a request.
    Takes into consideration the timeout duration on server side.

    Args:
        server_timeout (int): Quesry execution duration on server side.

    Returns:
        int: Total timeout duration of a request.
    """
    server_timeout_in_seconds = int(server_timeout * 60)
    return server_timeout_in_seconds + REQUEST_BASE_TIMEOUT


def validate_list_command_arguments(page: int, page_size: int, limit: int) -> None:
    """
    Validation of page number, page size and limit arguments in list commands.

    Args:
        page (int): The page number.
        page_size(int) : Limit on page size.
        limit (int): Limit on number of records.

    Raises:
        ValueError: Error message.
    """
    if not page >= 1 and limit >= 1 and page_size >= 1:
        raise ValueError("Page and limit arguments must be integers greater than 0.")


""" AUTHORIZATION METHODS """


def start_auth(client: DataExplorerClient) -> CommandResults:
    """
    Start the authorization process.

    Args:
        client (DataExplorerClient): Azure Data Explorer API client.

    Returns:
        CommandResults: authentication guidelines.
    """
    result = client.ms_client.start_auth("!azure-data-explorer-auth-complete")
    return CommandResults(readable_output=result)


def complete_auth(client: DataExplorerClient) -> str:
    """
    Start the authorization process.

    Args:
        client (DataExplorerClient): Azure Data Explorer API client.

    Returns:
          str: Message about completing the authorization process successfully.
    """
    client.ms_client.get_access_token()
    return "✅ Authorization completed successfully."


def test_connection(client: DataExplorerClient) -> str:
    """
    Test the connection with Azure Data Explorer service.

    Args:
        client (DataExplorerClient): Azure Data Explorer API client.

    Returns:
          str: Message about successfully connected to the Azure Data Explorer.
    """
    client.ms_client.get_access_token()
    return "✅ Success!"


def test_module(client: DataExplorerClient) -> str:
    """Tests API connectivity and authentication for client credentials only.
    Returning 'ok' indicates that the integration works like it is supposed to.
    Connection to the service is successful.
    Raises exceptions if something goes wrong.
    :type client: ``Client``
    :param Client: client to use
    :return: 'ok' if test passed.
    :rtype: ``str``
    """
    # This  should validate all the inputs given in the integration configuration panel,
    # either manually or by using an API that uses them.
    if "Device Code" in client.connection_type:
        raise DemistoException(
            "Please enable the integration and run `!azure-data-explorer-auth-start`"
            "and `!azure-data-explorer-auth-complete` to log in."
            "You can validate the connection by running `!azure-data-explorer-auth-test`\n"
            "For more details press the (?) button."
        )
    elif client.connection_type == "Client Credentials":
        client.ms_client.get_access_token()
        return "ok"
    else:
        raise Exception(
            "When using user auth flow configuration, "
            "Please enable the integration and run the !azure-data-explorer-auth-test command in order to test it"
        )


def main() -> None:
    """
    PARSE AND VALIDATE INTEGRATION PARAMS
    """
    params: dict[str, Any] = demisto.params()
    args: dict[str, Any] = demisto.args()
    cluster_url = params["cluster_url"]
    client_id = params["client_id"]
    client_activity_prefix = params.get("client_activity_prefix")
    verify_certificate: bool = not params.get("insecure", False)
    proxy = params.get("proxy", False)
    enc_key = (params.get("credentials", {})).get("password")
    tenant_id = params.get("tenant_id")
    connection_type = params.get("authentication_type", "Device Code")
    auth_code = (params.get("auth_code", {})).get("password")
    redirect_uri = params.get("redirect_uri")

    command = demisto.command()
    demisto.debug(f"Command being called is {command}")

    try:
        requests.packages.urllib3.disable_warnings()  # type: ignore[attr-defined]
        client: DataExplorerClient = DataExplorerClient(
            cluster_url,
            client_id,
            client_activity_prefix,  # type: ignore[arg-type]
            verify_certificate,
            proxy,
            connection_type,
            tenant_id,
            enc_key,
            auth_code,
            redirect_uri,
        )

        commands = {
            "azure-data-explorer-search-query-execute": search_query_execute_command,
            "azure-data-explorer-search-query-list": search_queries_list_command,
            "azure-data-explorer-running-search-query-list": running_search_queries_list_command,
            "azure-data-explorer-running-search-query-cancel": running_search_query_cancel_command,
        }

        if command == "test-module":
            return_results(test_module(client))
        elif command == "azure-data-explorer-generate-login-url":
            return_results(generate_login_url(client.ms_client))
        elif command == "azure-data-explorer-auth-start":
            return_results(start_auth(client))
        elif command == "azure-data-explorer-auth-complete":
            return_results(complete_auth(client))
        elif command == "azure-data-explorer-auth-reset":
            return_results(reset_auth())
        elif command == "azure-data-explorer-auth-test":
            return_results(test_connection(client))
        elif command in commands:
            return_results(commands[command](client, args))

        else:
            raise NotImplementedError(f"{command} command is not implemented.")

    except Exception as e:
        error_text = str(e)
        if "OneApiErrors" in error_text:
            error_text = "The execution of search query failed due a client cancel request."
        elif "Request execution timeout" in error_text:
            error_text = "Search query execution took longer than the assigned timeout value and has been aborted."

        return_error(f"Failed to execute {command} command.\nError:\n{error_text}")


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