Skip to content

Added using-brands argument to generic command in get-endpoint-data #40797

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 46 commits into from
Aug 11, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
46 commits
Select commit Hold shift + click to select a range
80e3c62
added rn
Aug 3, 2025
5757777
run pre
Aug 3, 2025
7d90a42
start handling the duplications
Aug 4, 2025
16945d6
added description and checked changes
Aug 5, 2025
2017bff
removed microsoft atp and created unittests
Aug 5, 2025
df08f0f
fixed issues and updated rn
Aug 5, 2025
e7abd7d
Added description to yml
Aug 5, 2025
2f1c085
fixed a unittest
Aug 5, 2025
d44e31b
fixed a unittest
Aug 5, 2025
ee9972c
Added an exception
Aug 5, 2025
fe7131b
updated rn
Aug 5, 2025
77e48e8
updated rn
Aug 5, 2025
1251b82
Merge branch 'master' into get-endpoint-data-add-microsoft-defender-a…
noydavidi Aug 5, 2025
962df85
fixed a unittest
Aug 5, 2025
721f7da
run pre commit
Aug 5, 2025
a906f8b
Merge branch 'get-endpoint-data-add-microsoft-defender-atp-command' o…
Aug 5, 2025
4c1ee37
changed rn
Aug 5, 2025
af6a809
improved function create_using_brand_argument_to_generic_command and …
Aug 5, 2025
b712e10
update docker image
Aug 5, 2025
23ec11c
fixed names
Aug 6, 2025
118f3ae
added the generic command to run by default
Aug 6, 2025
21d3483
updated rn
Aug 6, 2025
c42be95
Merge branch 'master' into get-endpoint-data-add-microsoft-defender-a…
noydavidi Aug 6, 2025
805ca66
Apply suggestions from code review
noydavidi Aug 7, 2025
0525cc8
changed after cr
Aug 7, 2025
2eb1f13
changes checked
Aug 7, 2025
0d43d69
Merge branch 'get-endpoint-data-add-microsoft-defender-atp-command' o…
Aug 7, 2025
8da8d06
run pre commit
Aug 7, 2025
76a5424
run pre commit
Aug 7, 2025
65ac94b
fixed readme
Aug 7, 2025
225ae2c
Update README.md
noydavidi Aug 7, 2025
789bc94
Update README.md
noydavidi Aug 7, 2025
e87bb21
run pre commit
Aug 7, 2025
80da9d0
fixed comments from cr and fixed tests
Aug 10, 2025
26d9f5e
Removed import
Aug 10, 2025
d8b3b4a
run pre commit
Aug 10, 2025
4c68033
fixed cr
Aug 10, 2025
e0e4352
added another case
Aug 10, 2025
6b1a657
fixed
Aug 10, 2025
d4a9403
Merge branch 'master' into get-endpoint-data-add-microsoft-defender-a…
Aug 10, 2025
edc186f
some changes after code review
Aug 10, 2025
f175e06
after cr with julie
Aug 10, 2025
1d9c90b
fixes after cr
Aug 11, 2025
1ae5774
fixed conflicts
Aug 11, 2025
3b3ecb4
Merge branch 'master' into get-endpoint-data-add-microsoft-defender-a…
noydavidi Aug 11, 2025
1c70a25
Merge branch 'master' into get-endpoint-data-add-microsoft-defender-a…
noydavidi Aug 11, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions Packs/AggregatedScripts/ReleaseNotes/1_0_13.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@

#### Scripts

##### get-endpoint-data
- Added the *using-brand* argument in the ***endpoint*** command. The script now runs from the default list of brands in the documentation and brands not from the list with ***endpoint*** command.
- Fixed an issue where the script returned duplicate results for brands that contain the ***endpoint*** command.
68 changes: 65 additions & 3 deletions Packs/AggregatedScripts/Scripts/GetEndpointData/GetEndpointData.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
from itertools import zip_longest
from typing import Any
from enum import StrEnum
from copy import deepcopy


from CommonServerPython import *

Expand Down Expand Up @@ -41,6 +43,7 @@ def __init__(
output_mapping: dict,
get_endpoint_output: bool = False,
not_found_checker: str = "No entries.",
additional_args: dict = None,
prepare_args_mapping: Callable[[dict[str, str]], dict[str, str]] | None = None,
post_processing: Callable[[Any, list[dict[str, Any]], dict[str, str]], list[dict[str, Any]]] | None = None,
):
Expand All @@ -55,6 +58,7 @@ def __init__(
output_mapping (dict): A mapping of command output keys to endpoint keys.
get_endpoint_output (bool, optional): Flag to indicate if the command retrieves endpoint output. Defaults to False.
not_found_checker (str, optional): A string to check if no entries are found. Defaults to "No entries.".
additional_args (dict, optional): Additional arguments to add for the command, arguments with hard-coded values.
prepare_args_mapping (Callable[[dict[str, str]], dict[str, str]], optional):
A function to prepare arguments mapping. Defaults to None.
post_processing (Callable, optional): A function for post-processing command results. Defaults to None.
Expand All @@ -66,12 +70,16 @@ def __init__(
self.output_mapping = output_mapping
self.get_endpoint_output = get_endpoint_output
self.not_found_checker = not_found_checker
self.additional_args = additional_args
self.prepare_args_mapping = prepare_args_mapping
self.post_processing = post_processing

def __repr__(self):
return f"{{ name: {self.name}, brand: {self.brand} }}"

def create_additional_args(self, args):
self.additional_args = args


class ModuleManager:
def __init__(self, modules: dict[str, Any], brands_to_run: list[str]) -> None:
Expand Down Expand Up @@ -106,6 +114,9 @@ def is_brand_in_brands_to_run(self, command: Command) -> bool:
Returns:
bool: True if the brand is in the list of brands to run, or if the list is empty; False otherwise.
"""
if command.brand == Brands.GENERIC_COMMAND and command.additional_args:
# in case no brands were given or no available brands were found
return bool(command.additional_args.get("using-brand"))
return command.brand in self._brands_to_run if self._brands_to_run else True

def is_brand_available(self, command: Command) -> bool:
Expand All @@ -124,6 +135,9 @@ def is_brand_available(self, command: Command) -> bool:
"""
return False if not self.is_brand_in_brands_to_run(command) else command.brand in self._enabled_brands

def get_enabled_brands(self):
return deepcopy(self._enabled_brands)


def filter_empty_values(input_dict: dict) -> dict:
"""
Expand Down Expand Up @@ -468,7 +482,7 @@ def initialize_commands(
"IsIsolated": "IsIsolated",
"Vendor": "Brand",
},
post_processing=generic_endpint_post,
post_processing=generic_endpoint_post,
),
Command(
brand=Brands.ACTIVE_DIRECTORY_QUERY_V2,
Expand Down Expand Up @@ -712,6 +726,9 @@ def prepare_args(command: Command, endpoint_args: dict[str, Any]) -> dict[str, A
if command_arg_value := endpoint_args.get(endpoint_arg_key):
command_args[command_arg_key] = command_arg_value

if command.additional_args: # adding additional arguments
command_args.update(command.additional_args)

return command_args


Expand Down Expand Up @@ -975,20 +992,61 @@ def active_directory_post(
return fixed_endpoints


def generic_endpint_post(
def generic_endpoint_post(
self: EndpointCommandRunner, endpoints: list[dict[str, Any]], args: dict[str, Any]
) -> list[dict[str, Any]]:
endpoints_to_return = []
for endpoint in endpoints:
brand = endpoint["Brand"]
if brand in Brands.get_all_values() and self.module_manager.is_brand_available(Command(brand, "", [], {}, {})):
# If the brand is in the brands, we dno't need if from the generic command
# If the brand is in the brands, we don't need if from the generic command
demisto.debug(f"Skipping generic endpoint with brand: '{brand}'")
else:
endpoints_to_return.append(endpoint)
return endpoints_to_return


def get_generic_command(single_args_commands: list[Command]) -> Command:
"""
Retrieves the generic command object from a list of command objects.

Args:
single_args_commands (list of Command): A list of Command objects to search through.

Returns:
Command : The Command object with brand 'Generic Command', or None if not found.
"""
for command in single_args_commands:
if command.brand == Brands.GENERIC_COMMAND:
return command
raise ValueError("Generic Command not found in the Commands list.")


def create_using_brand_argument_to_generic_command(brands_to_run: list, generic_command: Command, module_manager: ModuleManager):
"""
Creates the 'using-brand' argument for a generic command by filtering out specific predefined brands.

Args:
brands_to_run (list of str): List of brand names provided as input. If empty, defaults to removing all predefined brands.
generic_command (Command): The generic command object where additional arguments will be added.
module_manager (ModuleManager) : The module manager object.

Returns:
None: The function updates the generic_command object by adding the 'using-brand' argument with filtered brands.
"""
predefined_brands = set(Brands.get_all_values())
available_brands = module_manager.get_enabled_brands()

if brands_to_run:
brands_to_run_for_generic_command = list(set(brands_to_run) - predefined_brands)
else:
# we want to run the !endpoint on all brands available
brands_to_run_for_generic_command = list(available_brands - set(predefined_brands))

joined_brands = ",".join(brands_to_run_for_generic_command)
generic_command.create_additional_args({"using-brand": joined_brands})


""" MAIN FUNCTION """


Expand All @@ -1012,6 +1070,10 @@ def main(): # pragma: no cover
command_results_list: list[CommandResults] = []

command_runner, single_args_commands, list_args_commands = initialize_commands(module_manager, add_additional_fields)

generic_command = get_generic_command(single_args_commands)
create_using_brand_argument_to_generic_command(brands_to_run, generic_command, module_manager)

zipped_args: list[tuple] = list(zip_longest(endpoint_ids, endpoint_ips, endpoint_hostnames, fillvalue=""))

endpoint_outputs_single_commands, command_results_single_commands = run_single_args_commands(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,17 @@ args:
name: endpoint_hostname
isArray: true
- description: |-
A comma-seperated list of brands you wish to run the command for. If not provided, the command will run for all available integrations. Example: 'Active Directory Query v2,FireEyeHX v2'.
Supported brands are:
A comma-seperated list of brands you want to run get-endpoint-data for. If not provided, the get-endpoint-data script will run for the list of supported default brands. Example: 'Active Directory Query v2,FireEyeHX v2'.
Default supported brands are:
- 'Active Directory Query v2'
- 'McAfee ePO v2'
- 'CrowdstrikeFalcon'
- 'Cortex XDR - IR'
- 'Cortex Core - IR'
- 'FireEyeHX v2'
- 'Generic Command'.
If you provide a list of brands, only those brands will be executed and not the default list.
If you include a brand that is not on the default list, the script will run the !endpoint command on it.
If you do not provide any brand, the predefined list of default brands and the !endpoint command will automatically run on all brands.
name: brands
isArray: true
- description: Set to true to display human-readable output for each step of the command. Set to false (default) to only display the final result.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1136,3 +1136,62 @@ def test_get_endpoints_not_found_list_partial_match_by_ip():
result = get_endpoints_not_found_list(endpoints, zipped_args)

assert result == []


def test_get_generic_command_returns_correct_command():
"""
Given:
- A list of commands including one with brand 'BrandA' and one with brand 'Generic Command'.
When:
The get_generic_command function is called with this list.
Then:
It should return the command with brand 'Generic Command'.
"""
commands = [
Command(brand="BrandA", name="commandA", output_keys=[], args_mapping={}, output_mapping={}),
Command(brand=Brands.GENERIC_COMMAND, name="commandB", output_keys=[], args_mapping={}, output_mapping={}),
]
result = get_generic_command(commands)
assert result.brand == "Generic Command"


@pytest.mark.parametrize(
"brands_to_run, available_brands, predefined_brands, expected",
[
(
[],
{"BrandA", "BrandD", "BrandE"},
["BrandA", "BrandB", "BrandC"],
{"BrandD", "BrandE"},
),
(
["BrandD"],
{"BrandA", "BrandD", "BrandE"},
["BrandA", "BrandB", "BrandC"],
{"BrandD"},
),
],
)
def test_create_using_brand_argument_to_generic_command_all_default(
mocker, brands_to_run, available_brands, predefined_brands, expected
):
"""
Given:
- Enabled brands: BrandA, BrandD, BrandE (BrandB inactive).
- Predefined brands: BrandA, BrandB, BrandC.
- Empty 'using-brand' argument list provided.
When:
create_using_brand_argument_to_generic_command is called.
Then:
'using-brand' should contain only active brands not in the predefined list (BrandD, BrandE).
"""
mocker.patch("GetEndpointData.Brands.get_all_values", return_value=predefined_brands)
mock_module_manager = mocker.Mock()
mock_module_manager.get_enabled_brands.return_value = available_brands

command = Command(brand="Generic Command", name="gc", output_keys=[], args_mapping={}, output_mapping={})

create_using_brand_argument_to_generic_command(brands_to_run, command, mock_module_manager)

actual_set = set(command.additional_args["using-brand"].split(","))
assert actual_set == expected
24 changes: 24 additions & 0 deletions Packs/AggregatedScripts/Scripts/GetEndpointData/README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,29 @@
This script gathers endpoint data from multiple integrations and returns an endpoint entity with consolidated information to the context.

The following brands run by default:

- 'Active Directory Query v2'
- 'McAfee ePO v2'
- 'CrowdstrikeFalcon'
- 'Cortex XDR - IR'
- 'Cortex Core - IR'
- 'FireEyeHX v2'

**Note**:

If the *brands* argument is not provided to the script, all brands will be executed and the ***!endpoint*** command will run across all available brands.

If you provide specific brands, only those brands will be executed.
If you include additional brands not on the defaultlist, the predefined list of default brands and the ***!endpoint*** command will run only for those brands.

### Examples

**brands="Active Directory Query v2,FireEyeHX v2"** → the script will run the Active Directory Query v2 and the FireEyeHX v2 commands.

**brands="Microsoft Defender Advanced Threat Protection"** → the script will run !endpoint only with this brand.

**brands="Active Directory Query v2,FireEyeHX v2,Microsoft Defender Advanced Threat Protection"** → the script will run the Active Directory Query v2 command, the FireEyeHX v2 command and the !endpoint command with the Microsoft Defender Advanced Threat Protection brand.

## Script Data

---
Expand Down
2 changes: 1 addition & 1 deletion Packs/AggregatedScripts/pack_metadata.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"name": "Aggregated Scripts",
"description": "A pack containing all aggregated scripts.",
"support": "xsoar",
"currentVersion": "1.0.12",
"currentVersion": "1.0.13",
"author": "Cortex XSOAR",
"url": "https://www.paloaltonetworks.com/cortex",
"email": "",
Expand Down
Loading