SecurityAndComplianceV2 Deprecated

Deprecated. Use the Microsoft Graph Security integration instead. This integration allows you to manage and interact with Microsoft security and compliance content search.

Email · Microsoft Exchange Online

Details

IDSecurityAndComplianceV2
ProviderMicrosoft
CategoryEmail
From Version5.5.0
Docker Imagedemisto/pwsh-exchangev3:1.0.0.4904712
Supported ModulesAgentix Cloud Runtime Security Cloud Posture Security XSIAM EDR Cortex Cloud

README

Microsoft Security & Compliance Center - Content Search Integration

⚠️ Deprecation Notice

This integration is deprecated. Use the Microsoft Graph Security integration for equivalent functionality.

Note: The legacy content search solution is retired. Content searches are now performed within the context of an eDiscovery case.

Commands Replacement

All functionality from O365 - Security And Compliance - Content Search v2 has been migrated to the Microsoft Graph Security integration. Use Microsoft Graph Security for the supported commands listed below.

Compliance cases

Hold policies

Hold rules

Legacy Content Search (case-based in Graph eDiscovery)

Note: The legacy content search solution is retired. Content searches are now performed within the context of an eDiscovery case.

Search execution and actions


This integration allows you to manage and interact with Microsoft Security & Compliance Center’s content search capabilities. You can perform comprehensive searches across your organization’s emails, SharePoint sites, OneDrive accounts, and more. Actions like previewing and deleting emails are supported, making it easier to manage potential security threats.

This integration has been developed and tested with the Security & Compliance Center.

The Security and Compliance PowerShell module relies on legacy eDiscovery. We recommend transitioning to the new eDiscovery implementation, which is built on the Microsoft Graph API Microsoft Graph Security integration.

Key Features

  • Content Search Management: Create, modify, retrieve, list, remove, and trigger content searches within the Security & Compliance Center.
  • Search Actions: Perform actions such as previewing and purging (deleting) emails identified in content searches.

Playbooks

  • O365 - Security and Compliance - Search and Delete: Initiates a compliance search and, if configured, deletes or previews identified emails.
  • O365 - Security and Compliance - Search: Initiates a compliance search to locate emails with attributes matching those of a malicious email.
  • O365 - Security and Compliance - Search Action - Delete: Deletes emails found by the search.
  • O365 - Security and Compliance - Search Action - Preview: Provides a preview of emails identified by the search.

Permissions and Authentication Setup

Overview

This section describes how to configure permissions for the integration, supporting both App-only Authentication and Delegated User Authentication.


App Authentication (App-Only)

To set up the integration and register the application in Azure, you will need to perform the following three primary steps:

  1. App Registration and Permission Configuration: Register a new application in Entra ID and configure necessary permissions.
  2. Authentication Configuration: Enable public client flows and create an app secret.
  3. Role Setup: Assign the required roles for the App Service Principal to function correctly.
    Detailed instructions for each step are provided below.

1. App Registration and Permission Configuration

  1. Navigate to Azure Portal: Go to the Azure Portal and sign in with your administrator account.
  2. Access App Registrations: In the left-hand navigation pane, select Entra ID > App registrations.
  3. Register a New App: Click New registration, provide a name, and register the app.
  4. Add API Permissions:
    • Under Manage, select API permissions > Add a permission.
      • Select APIs my organization uses.
      • Search for “Office 365 Exchange Online”.
      • Select Delegated permissions and search for Exchange.Manage.
      • Check the box and click Add permissions.
    • Again select Add a permission.
      • Select APIs my organization uses.
      • Search for “Microsoft Graph”.
      • Select Delegated permissions and search for eDiscovery.ReadWrite.All.
      • Check the box and click Add permissions.
    • Ensure the permissions are gra

2. Authentication Configuration

  1. Navigate to Authentication Settings: In your app registration, click on Authentication -> Settings.
  2. Enable Public Client Flows:
    • Set Allow public client flows to Enabled.
    • Click Save to apply the changes.

3. Role Setup

  1. In the Azure portal, go to Microsoft Entra roles and administrators.
  2. Locate the Required Role
    • Search for the role Compliance Administrator.
    • Select the role to open its details page.
  3. Add Role Assignment
    • Click Add assignments.
    • Select Members.
    • Search for your new application name.
    • Select the application and confirm the assignment.

Delegated User Authentication

This section describes how to configure delegated authentication using a user account. You may either create a dedicated service account for the integration or use an existing user account (including an administrator account). If an existing user is used, you can skip the user creation step and proceed directly to the MFA and role assignment steps.

1. Create a Dedicated User (Microsoft Entra ID)

Create a new user account to serve as a dedicated service account for the integration.

  • Go to the Azure Portal and sign in with an organization administrator account.
  • Navigate to Entra IDUsersNew user.
  • Create a new user and define:
    • User Principal Name (UPN) – this will be used in the integration configuration
    • Display name
    • Password – store this securely

2. Disable Multi-Factor Authentication (MFA)

For delegated authentication to work, MFA must be disabled for this service account.

  • Go to the Microsoft 365 Admin Center and sign in with an organization administrator account.
  • Navigate to UsersActive users.
  • At the top menu, select Multi-factor authentication.
  • Locate the user and verify that the MFA status is set to Disabled.

3. Purview Role Assignment (Microsoft Purview Portal)

Grant the service account the required permissions to run Security & Compliance PowerShell cmdlets.

  • Sign in to the Microsoft Purview Portal using an organization administrator account.
  • Under Setting, navigate to Roles & scopesRole groups.
  • Locate the built-in eDiscovery Manager role group.
  • Select Copy to create a new custom role group.
  • Provide a name and description.
  • In the Roles section, add the Search and Purge role to the custom role group.
  • In the Users section, add your user.
  • Review the configuration and complete the setup.

Note: Role assignment propagation may take up to one hour.

Known Endpoints

Environment ConnectionUri AzureADAuthorizationEndpointUri
Microsoft 365 or Microsoft 365 GCC https://ps.compliance.protection.outlook.com/powershell-liveid/ https://login.microsoftonline.com
Microsoft 365 GCC High https://ps.compliance.protection.office365.us/powershell-liveid/ https://login.microsoftonline.us
Microsoft 365 DoD https://l5.ps.compliance.protection.office365.us/powershell-liveid/ https://login.microsoftonline.us
Office 365 operated by 21Vianet https://ps.compliance.protection.partner.outlook.cn/powershell-liveid https://login.chinacloudapi.cn

More information available here.

Configure SecurityAndComplianceV2 in Cortex

  1. Navigate to one of the following:
    • Cortex XSOAR 6: Settings > Integrations
    • Cortex XSOAR 8: Settings & Info > Settings > Integrations > Instances
    • Cortex XSIAM: Settings > Configurations > Automation & Feed Integrations
  2. Search for O365 - Security And Compliance - Content Search v2.
  3. Authentication / Authorization methods:
    • App-only (OAuth2.0) using device code Authentication -
      1. Fill in the UPN, App ID, and Tenant ID parameters in the integration configuration.
      2. Run the o365-sc-auth-start command and follow the instructions.
      3. For testing completion of authorization process run the o365-sc-auth-test command.
    • Delegated User Authentication -
      1. Fill in the UPN parameter in the integration configuration.
      2. Fill in the ‘UPN Password’ parameter - the user’s Microsoft 365 password (the regular sign-in password for that UPN).
      3. For testing completion of authorization process run the o365-sc-auth-test command.
      4. The following commands are only available when using the Delegated User Authentication method, as per the Microsoft Update:
        • o365-sc-new-search-action
        • o365-sc-case-hold-policy-create
        • o365-sc-case-hold-policy-set
        • o365-sc-case-hold-policy-delete
        • o365-sc-case-hold-rule-create
        • o365-sc-case-hold-rule-delete
    • Note - If a UPN Password is provided:
      • Even if the password is incorrect, the integration will attempt to authenticate using it.
      • In this case, all connections to Microsoft Security and Compliance PowerShell will use interactive delegated authentication.
  4. Click Add instance to create and configure a new integration instance.

    Parameter Description Required
    UPN/Email The email address (UPN) of the user account for the integration. True
    UPN Password Password for the specified UPN. Required when using interactive delegated authentication. False
    App ID The application (client) ID from Azure False
    Tenant ID The directory (tenant) ID from Azure. False
    Organization The organization name for the Security & Compliance Center. False
    Insecure Trust any certificate (not secure). False
  • Expected Output:

    Security and Compliance - Authorize Instructions App-only (OAuth2.0) using device code Authentication

    1. To sign in, open https://microsoft.com/devicelogin in a web browser and enter the code XXXXXXX to authenticate.
    2. Run the !o365-sc-auth-complete command in the War Room.
    3. Test OAuth2.0 authorization by running the !o365-sc-auth-test command.

    Security and Compliance - Authorize Instructions Delegated User Authentication

    1. Test ok!

Troubleshooting and Testing

Common Issues and Solutions

Response status code does not indicate success: 404 Not Found

Scenario: When running the !o365-sc-auth-start command, you may encounter the error message: “Response status code does not indicate success: 404 Not Found.”

Solution:
Verify Required Parameters: Ensure that all required parameters for App-only (using device code) Authentication in the integration instance configuration are correctly filled out. This includes:

  • UPN/Email: The email address (UPN) of the user account for the integration.
  • App ID: The application (client) ID from Azure.
  • Tenant ID: The directory (tenant) ID from Azure.
  • Organization: The organization name for the Security & Compliance Center.

Missing or incorrect values in these fields can cause a 404 error, as the integration might be attempting to connect to an incorrect or non-existent endpoint.

Additional Steps:

  • Check the ConnectionUri: Verify that the ConnectionUri in your configuration matches the environment you are operating in (e.g., Microsoft 365, GCC High, DoD, etc.).
  • Review Endpoint Configuration: Ensure that the AzureADAuthorizationEndpointUri is correctly set according to your environment.

Response status code does not indicate success: 400 Bad Request

Scenario: When running the !o365-sc-auth-start command, you may encounter the error message: “Response status code does not indicate success: 400 Bad Request.”

Solution:
Check Parameters for Accuracy: A 400 Bad Request error often indicates that there is a problem with the request sent to the server. Double-check the following:

  • App ID and Tenant ID: Ensure these are correctly copied from your Azure app registration.
  • UPN/Email: Ensure that the email address is correctly formatted and belongs to a user with the necessary permissions.

Additional Steps:
Test with a Different Account: If possible, try using a different user account to verify if the issue is related to specific user permissions.

Failed OAuth2.0 Authorization

Scenario: After running the !o365-sc-auth-start command, the authorization process fails, and the integration cannot authenticate with the Security & Compliance Center.

Solution:

  • Double-Check App Permissions: Make sure that the app registration in Azure has the necessary permissions, specifically Exchange.ManageAsApp.
  • Grant Admin Consent: Ensure that admin consent has been granted for the required permissions. Without this, the application cannot function correctly.
  • Review Authentication Setup: Ensure that “Allow public client flows” is enabled in the Azure app registration settings under Authentication.

Additional Steps:

  • Use the !o365-sc-auth-test Command: Run this command to verify if the integration can successfully authenticate. If this test fails, revisit the app registration settings and verify all configurations.

Failed Delegated User Authentication

Scenario: You Filled out the ‘UPN’ and ‘UPN Password’ integration parameters and after running the !o365-sc-auth-test command, the authorization process fails, and you get the error ‘you must use multi-factor authentication to access …’.

Solution:

  • Double-Check you disabled MFA:
    1. Go to the Microsoft 365 Admin Center and sign in with your administrator account.
    2. Under Users, select Active users.
    3. At the top, click Multi-factor authentication.
    4. In the list that appears, find your user and check the Multi-Factor Auth Status column is disabled.

Additional Steps:

  • Use the !o365-sc-auth-test Command: Run this command to verify if the integration can successfully authenticate. If this test fails, revisit the app registration settings and verify all configurations.

Testing the Integration

  1. Test Basic Connectivity:
    Use the !o365-sc-auth-test command to confirm that the integration can successfully authenticate with the Security & Compliance Center.

  2. Run a Simple Search:
    Execute the !o365-sc-search command with basic parameters to ensure the integration can perform a search operation. This helps verify that the configuration is correct and the integration can communicate with the Security & Compliance Center.

  3. Check Logs and Outputs:
    After running test commands, review the logs and command outputs in the War Room for any errors or warnings. This can provide additional insights into what might be going wrong.

  4. Review Azure Logs:
    If issues persist, review the logs in Azure to identify any access issues or authentication errors. This can help diagnose problems related to Azure AD configuration.

Additional Resources

Commands

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

o365-sc-auth-start


OAuth2.0 - Start authorization.

Base Command

o365-sc-auth-start

Input

There are no input arguments for this command.

Context Output

There is no context output for this command.

Command Example


#### Human Readable Output

>## Security And Compliance - Authorize instructions
>
>1. To sign in, use a web browser to open the page [https://microsoft.com/devicelogin](https://microsoft.com/devicelogin) and enter the code **XXXXXXX** to authenticate.
>2. Run the ***!o365-sc-auth-complete*** command in the War Room.

### o365-sc-auth-complete

***
OAuth2.0 - Complete authorization.

#### Base Command

`o365-sc-auth-complete`

#### Input

There are no input arguments for this command.

#### Context Output

There is no context output for this command.

#### Command Example

```!o365-sc-auth-complete```

#### Human Readable Output

>Your account **successfully** authorized!

### o365-sc-auth-test

***
OAuth2.0 - Test authorization.

#### Base Command

`o365-sc-auth-test`

#### Input

There are no input arguments for this command.

#### Context Output

There is no context output for this command.

#### Command Example

```!o365-sc-auth-test```

#### Human Readable Output

>**Test ok!**

### o365-sc-new-search

***
Create compliance search in the Security & Compliance Center.

#### Base Command

`o365-sc-new-search`

#### Input

| **Argument Name** | **Description** | **Required** |
| --- | --- | --- |
| search_name | The name of the compliance search. If not specified, will have the prefix "XSOAR-" followed by the GUID e.g., XSOAR-d6228fd0-756b-4e4b-8721-76776df91526. | Required |
| case | The name of a Core eDiscovery case to associate with the new compliance search. | Optional |
| kql | Text search string or a query that is formatted using the Keyword Query Language (KQL). [Tips for finding messages to remove using KQL](#tips-for-finding-messages-to-remove)
| Optional |
| description | Description of the compliance search. | Optional |
| allow_not_found_exchange_locations | Whether to include mailboxes other than regular user mailboxes in the compliance search. Default is "false". | Optional |
| exchange_location | Comma-separated list of mailboxes/distribution groups to include, or you can use the value "All" to include all. | Optional |
| exchange_location_exclusion | Comma-separated list of mailboxes/distribution groups to exclude when you use the value "All" for the exchange_location parameter. Deprecated since Microsoft is supporting it only on-premise. | Optional |
| public_folder_location | Comma-separated list of public folders to include, or you can use the value "All" to include all. | Optional |
| share_point_location | Comma-separated list of SharePoint online sites to include. You can identify the sites by their URL value, or you can use the value "All" to include all sites. | Optional |
| share_point_location_exclusion | Comma-separated list of SharePoint online sites to exclude when you use the value "All" for the share_point_location argument. You can identify the sites by their URL value. | Optional |

#### Context Output

| **Path** | **Type** | **Description** |
| --- | --- | --- |
| O365.SecurityAndCompliance.ContentSearch.Search.AllowNotFoundExchangeLocationsEnabled | Boolean | Whether to include mailboxes other than regular user mailboxes in the compliance search. |
| O365.SecurityAndCompliance.ContentSearch.Search.AzureBatchFrameworkEnabled | Boolean | Whether the Azure Batch Framework is enabled for job processing. |
| O365.SecurityAndCompliance.ContentSearch.Search.CaseId | String | Identity of a Core eDiscovery case which is associated with the compliance search. |
| O365.SecurityAndCompliance.ContentSearch.Search.CaseName | String | Name of a Core eDiscovery case which is associated with the compliance search. |
| O365.SecurityAndCompliance.ContentSearch.Search.ContentMatchQuery | String | Compliance text search string or a query that is formatted using the Keyword Query Language \(KQL\). |
| O365.SecurityAndCompliance.ContentSearch.Search.CreatedBy | String | Security and compliance search creator. |
| O365.SecurityAndCompliance.ContentSearch.Search.CreatedTime | Date | Security and compliance search creation time. |
| O365.SecurityAndCompliance.ContentSearch.Search.Description | String | Security and compliance search description. |
| O365.SecurityAndCompliance.ContentSearch.Search.Errors | String | Security and compliance search errors. |
| O365.SecurityAndCompliance.ContentSearch.Search.ExchangeLocation | String | Security and compliance search exchange locations to include. |
| O365.SecurityAndCompliance.ContentSearch.Search.Identity | String | Security and compliance search identity. |
| O365.SecurityAndCompliance.ContentSearch.Search.IsValid | Boolean | Whether the security and compliance search is valid. |
| O365.SecurityAndCompliance.ContentSearch.Search.Items | Number | The number of security and compliance search scanned items. |
| O365.SecurityAndCompliance.ContentSearch.Search.JobEndTime | Date | Security and compliance search job end time. |
| O365.SecurityAndCompliance.ContentSearch.Search.JobId | String | Security and compliance search job ID. |
| O365.SecurityAndCompliance.ContentSearch.Search.JobRunId | String | Security and compliance search job run ID. |
| O365.SecurityAndCompliance.ContentSearch.Search.JobStartTime | Date | Security and compliance search job run start time. |
| O365.SecurityAndCompliance.ContentSearch.Search.LastModifiedTime | Date | Security and compliance search last modification time. |
| O365.SecurityAndCompliance.ContentSearch.Search.LogLevel | String | Security and compliance search Azure log level. |
| O365.SecurityAndCompliance.ContentSearch.Search.Name | String | Security and compliance search name. |
| O365.SecurityAndCompliance.ContentSearch.Search.OneDriveLocation | String | Security and compliance search OneDrive locations to include. |
| O365.SecurityAndCompliance.ContentSearch.Search.OneDriveLocationExclusion | String | Security and compliance search OneDrive locations to exclude. |
| O365.SecurityAndCompliance.ContentSearch.Search.PublicFolderLocation | String | Security and compliance search public folder locations to include. |
| O365.SecurityAndCompliance.ContentSearch.Search.PublicFolderLocationExclusion | String | Security and compliance search public folder locations to exclude. |
| O365.SecurityAndCompliance.ContentSearch.Search.RunBy | String | Security and compliance search last run by UPN \(Email representation\). |
| O365.SecurityAndCompliance.ContentSearch.Search.RunspaceId | String | Security and compliance search run space ID. |
| O365.SecurityAndCompliance.ContentSearch.Search.SharePointLocation | String | Security and compliance search SharePoint locations to include. |
| O365.SecurityAndCompliance.ContentSearch.Search.Size | Number | Security and compliance search bytes results size. |
| O365.SecurityAndCompliance.ContentSearch.Search.Status | String | Security and compliance search status. |
| O365.SecurityAndCompliance.ContentSearch.Search.TenantId | String | Security and compliance search Tenant ID. |

#### Command Example

```!o365-sc-new-search search_name="example" exchange_location="user1@demistodev.onmicrosoft.com,user2@demistodev.onmicrosoft.com" allow_not_found_exchange_locations=true kql="Rodrigo"```

#### Context Example

```json
{
    "O365": {
        "SecurityAndCompliance": {
            "ContentSearch": {
                "Search": {
                    "AllowNotFoundExchangeLocationsEnabled": true,
                    "AzureBatchFrameworkEnabled": false,
                    "CaseId": null,
                    "CaseName": "",
                    "ContentMatchQuery": "Rodrigo",
                    "CreatedBy": "XSOAR-user",
                    "CreatedTime": "2020-11-29T07:12:46.5943533Z",
                    "Description": "Short description",
                    "Errors": null,
                    "ExchangeLocation": [
                        "test1@onmicrosoft.com",
                        "test2@onmicrosoft.com"
                    ],
                    "ExchangeLocationExclusion": [],
                    "Identity": "xxxxx",
                    "IsValid": true,
                    "Items": 0,
                    "JobEndTime": null,
                    "JobId": "xxxxx",
                    "JobRunId": null,
                    "JobStartTime": null,
                    "LastModifiedTime": "2020-11-29T07:12:46.5943533Z",
                    "LogLevel": "Suppressed",
                    "Name": "example",
                    "OneDriveLocation": null,
                    "OneDriveLocationExclusion": null,
                    "PublicFolderLocation": null,
                    "PublicFolderLocationExclusion": null,
                    "RunBy": "",
                    "RunspaceId": null,
                    "SharePointLocation": null,
                    "SharePointLocationExclusion": null,
                    "Size": 0,
                    "Status": "NotStarted",
                    "SuccessResults": null,
                    "TenantId": "xxxxx"
                }
            }
        }
    }
}

Human Readable Output

Security And Compliance - New search ‘example’ created

ContentMatchQuery CreatedBy Description LastModifiedTime Name
Rodrigo XSOAR-user Short description 11/29/2020 7:12:46 AM example

o365-sc-set-search


Modifies non-running compliance searches in the Security & Compliance Center.

Base Command

o365-sc-set-search

Input

Argument Name Description Required
search_name The name of the compliance search. Required
kql Modify the text search string or a query that is formatted using the Keyword Query Language (KQL). Optional
description Modify the description for the compliance search. Optional
allow_not_found_exchange_locations Whether to include mailboxes other than regular user mailboxes in the compliance search. Optional
add_exchange_location Comma-separated list of added mailboxes/distribution groups to include, or you can use the value “All” to include all mailboxes. Optional
add_exchange_location_exclusion Comma-separated list of added mailboxes/distribution groups to exclude when you use the value “All” for the exchange_location (used in create new compliance search) or the add_exchange_location argument. Optional
add_public_folder_location Comma-separated list of added public folders to include, or you can use the value “All” to include all. Optional
add_share_point_location Comma-separated list of added SharePoint online sites to include. You identify the sites by their URL value, or you can use the value “All” to include all sites. Optional
add_share_point_location_exclusion Comma-separated list of added SharePoint online sites to exclude when you use the value “All” for the exchange_location (used in create new compliance search) argument or the share_point_location argument. You can identify the sites by their URL value. Optional
remove_exchange_location Comma-separated list of removed mailboxes/distribution group to include. Optional
remove_exchange_location_exclusion Comma-separated list of removed mailboxes/distribution group to exclude when you use the value “All” for the exchange_location (Used in create new compliance search) or the add_exchange_location argument. Optional
remove_public_folder_location Comma-separated list of removed public folders to include. Optional
remove_share_point_location Comma-separated list of removed SharePoint online sites to include. You can identify the sites by their URL value. Optional
remove_share_point_location_exclusion Comma-separated list of removed SharePoint online sites to exclude when you use the value “All” for the exchange_location (Used in create new compliance search) argument or the share_point_location argument. You can identify the sites by their URL value. Optional

Context Output

There is no context output for this command.

Command Example

!o365-sc-set-search search_name="example" remove_exchange_location="test2@demistodev.onmicrosoft.com"

Human Readable Output

Security And Compliance - Search example modified!

o365-sc-remove-search


Remove compliance search by name from the Security & Compliance Center.

Base Command

o365-sc-remove-search

Input

Argument Name Description Required
search_name The name of the compliance search. Required

Context Output

There is no context output for this command.

Command Example

!o365-sc-remove-search search_name="example"

Human Readable Output

Security And Compliance - Search example removed!

o365-sc-list-search


List compliance searches in the Security & Compliance Center.

Base Command

o365-sc-list-search

Input

There are no input arguments for this command.

Context Output

Path Type Description
O365.SecurityAndCompliance.ContentSearch.Search.AllowNotFoundExchangeLocationsEnabled Boolean Whether to include mailboxes other than regular user mailboxes in the compliance search.
O365.SecurityAndCompliance.ContentSearch.Search.AzureBatchFrameworkEnabled Boolean Whether the Azure Batch Framework is enabled for job processing.
O365.SecurityAndCompliance.ContentSearch.Search.CaseId String Identity of a Core eDiscovery case which is associated with the compliance search.
O365.SecurityAndCompliance.ContentSearch.Search.CaseName String Name of a Core eDiscovery case which is associated with the compliance search.
O365.SecurityAndCompliance.ContentSearch.Search.ContentMatchQuery String Compliance text search string or a query that is formatted using the Keyword Query Language (KQL).
O365.SecurityAndCompliance.ContentSearch.Search.CreatedBy String Security and compliance search creator.
O365.SecurityAndCompliance.ContentSearch.Search.CreatedTime Date Security and compliance search creation time.
O365.SecurityAndCompliance.ContentSearch.Search.Description String Security and compliance search description.
O365.SecurityAndCompliance.ContentSearch.Search.Errors String Security and compliance search errors.
O365.SecurityAndCompliance.ContentSearch.Search.ExchangeLocation String Security and compliance search exchange locations to include.
O365.SecurityAndCompliance.ContentSearch.Search.Identity String Security and compliance search identity.
O365.SecurityAndCompliance.ContentSearch.Search.IsValid Boolean Whether the security and compliance search is valid.
O365.SecurityAndCompliance.ContentSearch.Search.Items Number The number of security and compliance search scanned items.
O365.SecurityAndCompliance.ContentSearch.Search.JobEndTime Date Security and compliance search job end time.
O365.SecurityAndCompliance.ContentSearch.Search.JobId String Security and compliance search job ID.
O365.SecurityAndCompliance.ContentSearch.Search.JobRunId String Security and compliance search job run ID.
O365.SecurityAndCompliance.ContentSearch.Search.JobStartTime Date Security and compliance search job run start time.
O365.SecurityAndCompliance.ContentSearch.Search.LastModifiedTime Date Security and compliance search last modification time.
O365.SecurityAndCompliance.ContentSearch.Search.LogLevel String Security and compliance search Azure log level.
O365.SecurityAndCompliance.ContentSearch.Search.Name String Security and compliance search name.
O365.SecurityAndCompliance.ContentSearch.Search.OneDriveLocation String Security and compliance search OneDrive locations to include.
O365.SecurityAndCompliance.ContentSearch.Search.OneDriveLocationExclusion String Security and compliance search OneDrive locations to exclude.
O365.SecurityAndCompliance.ContentSearch.Search.PublicFolderLocation String Security and compliance search public folder locations to include.
O365.SecurityAndCompliance.ContentSearch.Search.PublicFolderLocationExclusion String Security and compliance search public folder locations to exclude.
O365.SecurityAndCompliance.ContentSearch.Search.RunBy String Security and compliance search last run by UPN (Email representation).
O365.SecurityAndCompliance.ContentSearch.Search.RunspaceId String Security and compliance search run space ID.
O365.SecurityAndCompliance.ContentSearch.Search.SharePointLocation String Security and compliance search SharePoint locations to include.
O365.SecurityAndCompliance.ContentSearch.Search.Size Number Security and compliance search bytes results size.
O365.SecurityAndCompliance.ContentSearch.Search.Status String Security and compliance search status.
O365.SecurityAndCompliance.ContentSearch.Search.TenantId String Security and compliance search Tenant ID.

Command Example


#### Context Example

```json
{
    "O365": {
        "SecurityAndCompliance": {
            "ContentSearch": {
                "Search": [
                    {
                        "AllowNotFoundExchangeLocationsEnabled": false,
                        "AzureBatchFrameworkEnabled": false,
                        "CaseId": null,
                        "CaseName": "",
                        "ContentMatchQuery": "subject:test",
                        "CreatedBy": "XSOAR-user1",
                        "CreatedTime": "2019-08-22T06:43:48.747",
                        "Description": "Short description",
                        "Errors": null,
                        "ExchangeLocation": null,
                        "ExchangeLocationExclusion": null,
                        "Identity": "xxxxx",
                        "IsValid": true,
                        "Items": 0,
                        "JobEndTime": "2019-09-05T13:21:11.563",
                        "JobId": "xxxx",
                        "JobRunId": null,
                        "JobStartTime": "2019-09-05T13:20:34.633",
                        "LastModifiedTime": "2019-08-22T06:43:48.747",
                        "LogLevel": "Suppressed",
                        "Name": "example1",
                        "OneDriveLocation": null,
                        "OneDriveLocationExclusion": null,
                        "PublicFolderLocation": null,
                        "PublicFolderLocationExclusion": null,
                        "RunBy": "XSOAR-user1",
                        "RunspaceId": null,
                        "SharePointLocation": null,
                        "SharePointLocationExclusion": null,
                        "Size": 0,
                        "Status": "Completed",
                        "SuccessResults": null,
                        "TenantId": "xxxx"
                    },
                    {
                        "AllowNotFoundExchangeLocationsEnabled": false,
                        "AzureBatchFrameworkEnabled": false,
                        "CaseId": null,
                        "CaseName": "",
                        "ContentMatchQuery": "subject:Incident Summary Report",
                        "CreatedBy": "XSOAR-user2",
                        "CreatedTime": "2020-01-08T00:44:30.94",
                        "Description": "Short description",
                        "Errors": null,
                        "ExchangeLocation": null,
                        "ExchangeLocationExclusion": null,
                        "Identity": "xxxxx",
                        "IsValid": true,
                        "Items": 0,
                        "JobEndTime": "2020-01-08T00:45:13.433",
                        "JobId": "xxxxx",
                        "JobRunId": null,
                        "JobStartTime": "2020-01-08T00:44:33.717",
                        "LastModifiedTime": "2020-01-08T00:44:30.94",
                        "LogLevel": "Suppressed",
                        "Name": "example2",
                        "OneDriveLocation": null,
                        "OneDriveLocationExclusion": null,
                        "PublicFolderLocation": null,
                        "PublicFolderLocationExclusion": null,
                        "RunBy": "XSOAR-user2",
                        "RunspaceId": null,
                        "SharePointLocation": null,
                        "SharePointLocationExclusion": null,
                        "Size": 0,
                        "Status": "Completed",
                        "SuccessResults": null,
                        "TenantId": "xxxxx"
                    }
                ]
            }
        }
    }
}

Human Readable Output

Security And Compliance - Search configurations

CreatedBy Description LastModifiedTime Name RunBy
XSOAR-user1 Short description 8/22/2019 6:43:48 AM example1 XSOAR-user1
XSOAR-user2 Short description 1/8/2020 12:44:30 AM example2 XSOAR-user2

o365-sc-get-search


Gets compliance search by name from the Security & Compliance Center.

Base Command

o365-sc-get-search

Input

Argument Name Description Required
search_name The name of the compliance search. Required
limit The maximum number of results to return. If you want to return all requests that match the query, use “-1” for the value of this argument. Optional
all_results Whether to include mailboxes which have no results in results entry context. Optional
export Whether to export search results as json file to war-room. Optional
statistics Show search statistics. Default is “false”. Optional

Context Output

Path Type Description
O365.SecurityAndCompliance.ContentSearch.Search.AllowNotFoundExchangeLocationsEnabled Boolean Whether to include mailboxes other than regular user mailboxes in the compliance search.
O365.SecurityAndCompliance.ContentSearch.Search.AzureBatchFrameworkEnabled Boolean Whether the Azure Batch Framework is enabled for job processing.
O365.SecurityAndCompliance.ContentSearch.Search.CaseId String Identity of a Core eDiscovery case which is associated with the compliance search.
O365.SecurityAndCompliance.ContentSearch.Search.CaseName String Name of a Core eDiscovery case which is associated with the compliance search.
O365.SecurityAndCompliance.ContentSearch.Search.ContentMatchQuery String Compliance text search string or a query that is formatted using the Keyword Query Language (KQL).
O365.SecurityAndCompliance.ContentSearch.Search.CreatedBy String Security and compliance search creator.
O365.SecurityAndCompliance.ContentSearch.Search.CreatedTime Date Security and compliance search creation time.
O365.SecurityAndCompliance.ContentSearch.Search.Description String Security and compliance search description.
O365.SecurityAndCompliance.ContentSearch.Search.Errors String Security and compliance search errors.
O365.SecurityAndCompliance.ContentSearch.Search.ExchangeLocation String Security and compliance search exchange locations to include.
O365.SecurityAndCompliance.ContentSearch.Search.Identity String Security and compliance search identity.
O365.SecurityAndCompliance.ContentSearch.Search.IsValid Boolean Whether the security and compliance search is valid.
O365.SecurityAndCompliance.ContentSearch.Search.Items Number Number of security and compliance search scanned items.
O365.SecurityAndCompliance.ContentSearch.Search.JobEndTime Date Security and compliance search job end time.
O365.SecurityAndCompliance.ContentSearch.Search.JobId String Security and compliance search job ID.
O365.SecurityAndCompliance.ContentSearch.Search.JobRunId String Security and compliance search job run ID.
O365.SecurityAndCompliance.ContentSearch.Search.JobStartTime Date Security and compliance search job run start time.
O365.SecurityAndCompliance.ContentSearch.Search.LastModifiedTime Date Security and compliance search last modification time.
O365.SecurityAndCompliance.ContentSearch.Search.LogLevel String Security and compliance search the Azure log level.
O365.SecurityAndCompliance.ContentSearch.Search.Name String Security and compliance search name.
O365.SecurityAndCompliance.ContentSearch.Search.OneDriveLocation String Security and compliance search OneDrive locations to include.
O365.SecurityAndCompliance.ContentSearch.Search.OneDriveLocationExclusion String Security and compliance search OneDrive locations to exclude.
O365.SecurityAndCompliance.ContentSearch.Search.PublicFolderLocation String Security and compliance search public folder locations to include.
O365.SecurityAndCompliance.ContentSearch.Search.PublicFolderLocationExclusion String Security and compliance search public folder locations to exclude.
O365.SecurityAndCompliance.ContentSearch.Search.RunBy String Security and compliance search last run by UPN (Email representation).
O365.SecurityAndCompliance.ContentSearch.Search.RunspaceId String Security and compliance search run space ID.
O365.SecurityAndCompliance.ContentSearch.Search.SharePointLocation String Security and compliance search SharePoint locations to include.
O365.SecurityAndCompliance.ContentSearch.Search.Size Number Security and compliance search bytes results size.
O365.SecurityAndCompliance.ContentSearch.Search.Status String Security and compliance search status.
O365.SecurityAndCompliance.ContentSearch.Search.TenantId String Security and compliance search Tenant ID.
O365.SecurityAndCompliance.ContentSearch.Search.SuccessResults.Location String Security and compliance search result location.
O365.SecurityAndCompliance.ContentSearch.Search.SuccessResults.ItemsCount Number The number of security and compliance search results in location.
O365.SecurityAndCompliance.ContentSearch.Search.SuccessResults.Size Number The byte size of the security and compliance search results in location.

Command Example

!o365-sc-get-search search_name="example"

Context Example

{
    "O365": {
        "SecurityAndCompliance": {
            "ContentSearch": {
                "Search": {
                    "Errors": "", 
                    "AzureBatchFrameworkEnabled": false,
                    "TenantId": "xxxxx", 
                    "SharePointLocationExclusion": null, 
                    "JobStartTime": "2020-11-29T07:20:59.37", 
                    "CreatedTime": "2020-11-29T07:18:04.283", 
                    "OneDriveLocation": null, 
                    "PublicFolderLocation": null, 
                    "Status": "Completed", 
                    "CaseName": "", 
                    "AllowNotFoundExchangeLocationsEnabled": false, 
                    "LogLevel": "Suppressed", 
                    "JobRunId": null, 
                    "CaseId": null, 
                    "JobId": "xxxxx", 
                    "SuccessResults": {
                        "ItemsCount": "122303", 
                        "Location": "user@onmicrosoft.com", 
                        "Size": "12339007379"
                    }, 
                    "LastModifiedTime": "2020-11-29T07:20:43.283", 
                    "Identity": "1d130723-fe0a-4726-6d2a-08d89437520e", 
                    "Name": "example", 
                    "Items": 122303, 
                    "ExchangeLocation": [
                        "user@onmicrosoft.com"
                    ], 
                    "RunBy": "XSOAR-user", 
                    "Description": "Short description", 
                    "ExchangeLocationExclusion": [], 
                    "IsValid": true, 
                    "PublicFolderLocationExclusion": null, 
                    "SharePointLocation": null, 
                    "CreatedBy": "XSOAR-user", 
                    "JobEndTime": "2020-11-29T07:22:01.99", 
                    "RunspaceId": null, 
                    "Size": 12339007379, 
                    "OneDriveLocationExclusion": null, 
                    "ContentMatchQuery": "Rodrigo"
                }
            }
        }
    }
}

Human Readable Output

Security And Compliance - ‘example’ search

CreatedBy Description LastModifiedTime Name RunBy Status
XSOAR-user Short description 2020-11-29T07:20:43.283 example XSOAR-user NotStarted

o365-sc-start-search


Starts stopped, completed, or not started compliance search in the Security & Compliance Center.

Base Command

o365-sc-start-search

Input

Argument Name Description Required
search_name The name of the compliance search. Required

Context Output

There is no context output for this command.

Command Example

!o365-sc-start-search search_name="example"

Human Readable Output

Security And Compliance - search example started !

o365-sc-stop-search


Stop running compliance search in the Security & Compliance Center.

Base Command

o365-sc-stop-search

Input

Argument Name Description Required
search_name The name of the compliance search. Required

Context Output

There is no context output for this command.

Command Example

!o365-sc-stop-search search_name="example"

Human Readable Output

Security And Compliance - search example stopped !

o365-sc-new-search-action


After you create a content search using the o365-sc-new-search command and run it using the o365-sc-start-search command, you assign a search action to the search using the o365-sc-new-search-action command.

Please note that when performing the Delete action, items which are deleted will still follow your EWS365 data retention policies. Some data retention policies will move emails to the “Purges” or “Recoverable Items” folder.

Base Command

o365-sc-new-search-action

Input

Argument Name Description Required
search_name The name of the compliance search. Required
action Search action to perform. Possible values are: Preview, Purge. Default is Preview. Optional
purge_type Purge type. Possible values are: SoftDelete, HardDelete. Default is SoftDelete. Optional

Context Output

Path Type Description
O365.SecurityAndCompliance.ContentSearch.SearchAction.Action String Security and compliance search action type. Either “Purge” or “Preview”.
O365.SecurityAndCompliance.ContentSearch.SearchAction.AllowNotFoundExchangeLocationsEnabled Boolean Whether to include mailboxes other than regular user mailboxes in the compliance search.
O365.SecurityAndCompliance.ContentSearch.SearchAction.AzureBatchFrameworkEnabled Boolean Whether the Azure Batch Framework is enabled for job processing.
O365.SecurityAndCompliance.ContentSearch.SearchAction.CaseId String Identity of a Core eDiscovery case which is associated with the compliance search.
O365.SecurityAndCompliance.ContentSearch.SearchAction.CaseName String Name of a Core eDiscovery case which is associated with the compliance search.
O365.SecurityAndCompliance.ContentSearch.SearchAction.CreatedBy String Security and compliance search action creator.
O365.SecurityAndCompliance.ContentSearch.SearchAction.CreatedTime Date Security and compliance search action creation time.
O365.SecurityAndCompliance.ContentSearch.SearchAction.Description String Security and compliance search action description.
O365.SecurityAndCompliance.ContentSearch.SearchAction.Errors String Security and compliance search action errors.
O365.SecurityAndCompliance.ContentSearch.SearchAction.EstimateSearchJobId String Security and compliance search action job ID estimation.
O365.SecurityAndCompliance.ContentSearch.SearchAction.EstimateSearchRunId String Security and compliance search action run ID estimation.
O365.SecurityAndCompliance.ContentSearch.SearchAction.ExchangeLocation String Security and compliance search action exchange locations to include.
O365.SecurityAndCompliance.ContentSearch.SearchAction.ExchangeLocationExclusion String Security and compliance search action exchange locations to exclude.
O365.SecurityAndCompliance.ContentSearch.SearchAction.Identity String Security and compliance search action identity.
O365.SecurityAndCompliance.ContentSearch.SearchAction.IsValid Boolean Whether the security and compliance search action is valid.
O365.SecurityAndCompliance.ContentSearch.SearchAction.JobEndTime Date Security and compliance search action job end time.
O365.SecurityAndCompliance.ContentSearch.SearchAction.JobId String Security and compliance search action job ID.
O365.SecurityAndCompliance.ContentSearch.SearchAction.JobRunId String Security and compliance search action job run ID.
O365.SecurityAndCompliance.ContentSearch.SearchAction.JobStartTime Date Security and compliance search action job start time.
O365.SecurityAndCompliance.ContentSearch.SearchAction.LastModifiedTime Date Security and compliance search action last modified time.
O365.SecurityAndCompliance.ContentSearch.SearchAction.Name String Security and compliance search action name.
O365.SecurityAndCompliance.ContentSearch.SearchAction.PublicFolderLocation String Security and compliance search action public folder locations to include.
O365.SecurityAndCompliance.ContentSearch.SearchAction.PublicFolderLocationExclusion String Security and compliance search action public folder locations to exclude.
O365.SecurityAndCompliance.ContentSearch.SearchAction.Retry Boolean Whether to retry if the search action failed.
O365.SecurityAndCompliance.ContentSearch.SearchAction.RunBy String Security and compliance search action run by UPN (email address).
O365.SecurityAndCompliance.ContentSearch.SearchAction.RunspaceId String Security and compliance search action run space ID.
O365.SecurityAndCompliance.ContentSearch.SearchAction.SearchName String Security and compliance search action search name.
O365.SecurityAndCompliance.ContentSearch.SearchAction.SharePointLocation String Security and compliance search action SharePoint locations to include.
O365.SecurityAndCompliance.ContentSearch.SearchAction.SharePointLocationExclusion String Security and compliance search action SharePoint locations to exclude.
O365.SecurityAndCompliance.ContentSearch.SearchAction.Status String Security and compliance search action status. Either “Started” or “Completed”.
O365.SecurityAndCompliance.ContentSearch.SearchAction.TenantId String Security and compliance search action Tenant ID.

Command Example

!o365-sc-new-search-action search_name="example" action="Preview"

Context Example

{
    "O365": {
        "SecurityAndCompliance": {
            "ContentSearch": {
                "SearchAction": {
                    "Action": "Preview",
                    "AllowNotFoundExchangeLocationsEnabled": false,
                    "AzureBatchFrameworkEnabled": false,
                    "CaseId": null,
                    "CaseName": "",
                    "CreatedBy": "XSOAR-user",
                    "CreatedTime": "2020-11-29T07:23:50.05",
                    "Description": "",
                    "Errors": "",
                    "EstimateSearchJobId": "xxxxx",
                    "EstimateSearchRunId": "xxxxx",
                    "ExchangeLocation": [
                        "user@onmicrosoft.com"
                    ],
                    "ExchangeLocationExclusion": null,
                    "Identity": "xxxxx",
                    "IsValid": true,
                    "JobEndTime": "2020-11-29T07:24:05.76",
                    "JobId": "xxxxx",
                    "JobRunId": "xxxxx",
                    "JobStartTime": "2020-11-29T07:23:50.297",
                    "LastModifiedTime": "2020-11-29T07:23:50.05",
                    "Name": "example_Preview",
                    "PublicFolderLocation": null,
                    "PublicFolderLocationExclusion": null,
                    "Results": null,
                    "Retry": false,
                    "RunBy": "XSOAR-user",
                    "RunspaceId": "xxxxx",
                    "SearchName": "example",
                    "SharePointLocation": null,
                    "SharePointLocationExclusion": null,
                    "Status": "Completed",
                    "TenantId": "xxxxx"
                }
            }
        }
    }
}

Human Readable Output

Security And Compliance - search action ‘example_Preview’ created

Action LastModifiedTime Name RunBy SearchName Status
Preview 11/29/2020 7:23:50 AM example_Preview XSOAR-user example Completed

o365-sc-remove-search-action


Removes compliance search action by search the action name from the Security & Compliance Center.

Base Command

o365-sc-remove-search-action

Input

Argument Name Description Required
search_action_name The name of the compliance search action. Required

Context Output

There is no context output for this command.

Command Example

!o365-sc-remove-search-action search_action_name="example_Preview"

Human Readable Output

Security And Compliance - search action example_Preview removed!

o365-sc-list-search-action


Lists compliance search actions from the Security & Compliance Center.

Base Command

o365-sc-list-search-action

Input

There are no input arguments for this command.

Context Output

Path Type Description
O365.SecurityAndCompliance.ContentSearch.SearchAction.Action String Security and compliance search action type. Either “Purge or “Preview”.
O365.SecurityAndCompliance.ContentSearch.SearchAction.AllowNotFoundExchangeLocationsEnabled Boolean Whether to include mailboxes other than regular user mailboxes in the compliance search.
O365.SecurityAndCompliance.ContentSearch.SearchAction.AzureBatchFrameworkEnabled Boolean Whether the Azure Batch Framework is enabled for job processing.
O365.SecurityAndCompliance.ContentSearch.SearchAction.CaseId String Identity of a Core eDiscovery case which is associated with the compliance search.
O365.SecurityAndCompliance.ContentSearch.SearchAction.CaseName String Name of a Core eDiscovery case which is associated with the compliance search.
O365.SecurityAndCompliance.ContentSearch.SearchAction.CreatedBy String Security and compliance search action creator.
O365.SecurityAndCompliance.ContentSearch.SearchAction.CreatedTime Date Security and compliance search action creation time.
O365.SecurityAndCompliance.ContentSearch.SearchAction.Description String Security and compliance search action description.
O365.SecurityAndCompliance.ContentSearch.SearchAction.Errors String Security and compliance search action errors.
O365.SecurityAndCompliance.ContentSearch.SearchAction.EstimateSearchJobId String Security and compliance search action job ID estimation.
O365.SecurityAndCompliance.ContentSearch.SearchAction.EstimateSearchRunId String Security and compliance search action run ID estimation.
O365.SecurityAndCompliance.ContentSearch.SearchAction.ExchangeLocation String Security and compliance search action exchange locations to include.
O365.SecurityAndCompliance.ContentSearch.SearchAction.ExchangeLocationExclusion String Security and compliance search action exchange locations to exclude.
O365.SecurityAndCompliance.ContentSearch.SearchAction.Identity String Security and compliance search action identity.
O365.SecurityAndCompliance.ContentSearch.SearchAction.IsValid Boolean Whether the security and compliance search action is valid.
O365.SecurityAndCompliance.ContentSearch.SearchAction.JobEndTime Date Security and compliance search action job end time.
O365.SecurityAndCompliance.ContentSearch.SearchAction.JobId String Security and compliance search action job ID.
O365.SecurityAndCompliance.ContentSearch.SearchAction.JobRunId String Security and compliance search action job run ID.
O365.SecurityAndCompliance.ContentSearch.SearchAction.JobStartTime Date Security and compliance search action job start time.
O365.SecurityAndCompliance.ContentSearch.SearchAction.LastModifiedTime Date Security and compliance search action last modified time.
O365.SecurityAndCompliance.ContentSearch.SearchAction.Name String Security and compliance search action name.
O365.SecurityAndCompliance.ContentSearch.SearchAction.PublicFolderLocation String Security and compliance search action public folder locations to include.
O365.SecurityAndCompliance.ContentSearch.SearchAction.PublicFolderLocationExclusion String Security and compliance search action public folder locations to exclude.
O365.SecurityAndCompliance.ContentSearch.SearchAction.Retry Boolean Whether to retry if the search action failed.
O365.SecurityAndCompliance.ContentSearch.SearchAction.RunBy String Security and compliance search action run by UPN (email address).
O365.SecurityAndCompliance.ContentSearch.SearchAction.RunspaceId String Security and compliance search action run space ID.
O365.SecurityAndCompliance.ContentSearch.SearchAction.SearchName String Security and compliance search action search name.
O365.SecurityAndCompliance.ContentSearch.SearchAction.SharePointLocation String Security and compliance search action SharePoint locations to include.
O365.SecurityAndCompliance.ContentSearch.SearchAction.SharePointLocationExclusion String Security and compliance search action SharePoint locations to exclude.
O365.SecurityAndCompliance.ContentSearch.SearchAction.Status String Security and compliance search action status (Started/Completed).
O365.SecurityAndCompliance.ContentSearch.SearchAction.TenantId String Security and compliance search action Tenant ID.

Command Example


#### Context Example

```json
{
    "O365": {
        "SecurityAndCompliance": {
            "ContentSearch": {
                "SearchAction": [
                    {
                        "Action": "Preview",
                        "AllowNotFoundExchangeLocationsEnabled": false,
                        "AzureBatchFrameworkEnabled": false,
                        "CaseId": null,
                        "CaseName": "",
                        "CreatedBy": "XSOAR-user",
                        "CreatedTime": "2020-10-14T13:45:44.14",
                        "Description": "",
                        "Errors": "",
                        "EstimateSearchJobId": "xxxxx",
                        "EstimateSearchRunId": "xxxxx",
                        "ExchangeLocation": null,
                        "ExchangeLocationExclusion": null,
                        "Identity": "xxxxx",
                        "IsValid": true,
                        "JobEndTime": "2020-10-14T13:47:00.103",
                        "JobId": "xxxxx",
                        "JobRunId": "xxxxx",
                        "JobStartTime": "2020-10-14T13:45:58.443",
                        "LastModifiedTime": "2020-10-14T13:45:44.14",
                        "Name": "example_Preview",
                        "PublicFolderLocation": null,
                        "PublicFolderLocationExclusion": null,
                        "Results": null,
                        "Retry": false,
                        "RunBy": "XSOAR-user",
                        "RunspaceId": "xxxxx",
                        "SearchName": "example",
                        "SharePointLocation": null,
                        "SharePointLocationExclusion": null,
                        "Status": "Completed",
                        "TenantId": "xxxxx"
                    },
                   {
                        "Action": "Purge",
                        "AllowNotFoundExchangeLocationsEnabled": false,
                        "AzureBatchFrameworkEnabled": false,
                        "CaseId": null,
                        "CaseName": "",
                        "CreatedBy": "XSOAR-user1",
                        "CreatedTime": "2020-10-14T13:45:44.14",
                        "Description": "",
                        "Errors": "",
                        "EstimateSearchJobId": "xxxxx",
                        "EstimateSearchRunId": "xxxxx",
                        "ExchangeLocation": null,
                        "ExchangeLocationExclusion": null,
                        "Identity": "xxxxx",
                        "IsValid": true,
                        "JobEndTime": "2020-10-14T13:47:00.103",
                        "JobId": "xxxxx",
                        "JobRunId": "xxxxx",
                        "JobStartTime": "2020-10-14T13:45:58.443",
                        "LastModifiedTime": "2020-10-14T13:45:44.14",
                        "Name": "example_Purge",
                        "PublicFolderLocation": null,
                        "PublicFolderLocationExclusion": null,
                        "Results": null,
                        "Retry": false,
                        "RunBy": "XSOAR-user1",
                        "RunspaceId": "xxxxx",
                        "SearchName": "Rodrigo-diffrent",
                        "SharePointLocation": null,
                        "SharePointLocationExclusion": null,
                        "Status": "Completed",
                        "TenantId": "xxxxx"
                    }
                ]
            }
        }
    }
}

Human Readable Output

Security And Compliance - search actions

Action JobEndTime LastModifiedTime Name RunBy SearchName Status
Preview 10/14/2020 1:47:00 PM 10/14/2020 1:45:44 PM example_Preview XSOAR-user example Completed
Purge 11/25/2020 10:51:04 AM 11/25/2020 10:50:37 AM example_Purge XSOAR-user example Completed

o365-sc-get-search-action


Gets compliance search action from the Security & Compliance Center.

Base Command

o365-sc-get-search-action

Input

Argument Name Description Required
search_action_name The name of the compliance search action. Required
limit The maximum number of results to return. If you want to return all requests that match the query, use “-1” for the value of this argument. Optional
export Whether to export search results as json file to war-room. Optional
results Whether to print the results in the War Room. Default is “false”. Optional

Context Output

Path Type Description
O365.SecurityAndCompliance.ContentSearch.SearchAction.Action String Security and compliance search action type. Either “Purge” or “Preview”.
O365.SecurityAndCompliance.ContentSearch.SearchAction.AllowNotFoundExchangeLocationsEnabled Boolean Whether to include mailboxes other than regular user mailboxes in the compliance search.
O365.SecurityAndCompliance.ContentSearch.SearchAction.AzureBatchFrameworkEnabled Boolean Whether the Azure Batch Framework is enabled for job processing.
O365.SecurityAndCompliance.ContentSearch.SearchAction.CaseId String Identity of a Core eDiscovery case which is associated with the compliance search.
O365.SecurityAndCompliance.ContentSearch.SearchAction.CaseName String Name of a Core eDiscovery case which is associated with the compliance search.
O365.SecurityAndCompliance.ContentSearch.SearchAction.CreatedBy String Security and compliance search action creator.
O365.SecurityAndCompliance.ContentSearch.SearchAction.CreatedTime Date Security and compliance search action creation time.
O365.SecurityAndCompliance.ContentSearch.SearchAction.Description String Security and compliance search action description.
O365.SecurityAndCompliance.ContentSearch.SearchAction.Errors String Security and compliance search action errors.
O365.SecurityAndCompliance.ContentSearch.SearchAction.EstimateSearchJobId String Security and compliance search action job ID estimation.
O365.SecurityAndCompliance.ContentSearch.SearchAction.EstimateSearchRunId String Security and compliance search action run ID estimation.
O365.SecurityAndCompliance.ContentSearch.SearchAction.ExchangeLocation String Security and compliance search action exchange locations to include.
O365.SecurityAndCompliance.ContentSearch.SearchAction.ExchangeLocationExclusion String Security and compliance search action exchange locations to exclude.
O365.SecurityAndCompliance.ContentSearch.SearchAction.Identity String Security and compliance search action identity.
O365.SecurityAndCompliance.ContentSearch.SearchAction.IsValid Boolean Whether the security and compliance search action is valid.
O365.SecurityAndCompliance.ContentSearch.SearchAction.JobEndTime Date Security and compliance search action job end time.
O365.SecurityAndCompliance.ContentSearch.SearchAction.JobId String Security and compliance search action job ID.
O365.SecurityAndCompliance.ContentSearch.SearchAction.JobRunId String Security and compliance search action job run ID.
O365.SecurityAndCompliance.ContentSearch.SearchAction.JobStartTime Date Security and compliance search action job start time.
O365.SecurityAndCompliance.ContentSearch.SearchAction.LastModifiedTime Date Security and compliance search action last modified time.
O365.SecurityAndCompliance.ContentSearch.SearchAction.Name String Security and compliance search action name.
O365.SecurityAndCompliance.ContentSearch.SearchAction.PublicFolderLocation String Security and compliance search action public folder locations to include.
O365.SecurityAndCompliance.ContentSearch.SearchAction.PublicFolderLocationExclusion String Security and compliance search action public folder locations to exclude.
O365.SecurityAndCompliance.ContentSearch.SearchAction.Results.Location String Security and compliance search action result location.
O365.SecurityAndCompliance.ContentSearch.SearchAction.Results.ItemCount String Security and compliance search action result item count.
O365.SecurityAndCompliance.ContentSearch.SearchAction.Results.TotalSize String Security and compliance search action result total size.
O365.SecurityAndCompliance.ContentSearch.SearchAction.Results.FailedCount String Security and compliance search action result failed count.
O365.SecurityAndCompliance.ContentSearch.SearchAction.Results.Sender String Security and compliance search action result mail sender.
O365.SecurityAndCompliance.ContentSearch.SearchAction.Results.Subject String Security and compliance search action result subject.
O365.SecurityAndCompliance.ContentSearch.SearchAction.Results.Type String Security and compliance search action result type.
O365.SecurityAndCompliance.ContentSearch.SearchAction.Results.Size String Security and compliance search action result size.
O365.SecurityAndCompliance.ContentSearch.SearchAction.Results.ReceivedTime Date Security and compliance search action result received time.
O365.SecurityAndCompliance.ContentSearch.SearchAction.Results.DataLink String Security and compliance search action data link.
O365.SecurityAndCompliance.ContentSearch.SearchAction.Retry Boolean Whether to retry if the search action failed.
O365.SecurityAndCompliance.ContentSearch.SearchAction.RunBy String Security and compliance search action run by UPN (email address).
O365.SecurityAndCompliance.ContentSearch.SearchAction.RunspaceId String Security and compliance search action run space ID.
O365.SecurityAndCompliance.ContentSearch.SearchAction.SearchName String Security and compliance search action search name.
O365.SecurityAndCompliance.ContentSearch.SearchAction.SharePointLocation String Security and compliance search action SharePoint locations to include.
O365.SecurityAndCompliance.ContentSearch.SearchAction.SharePointLocationExclusion String Security and compliance search action SharePoint locations to exclude.
O365.SecurityAndCompliance.ContentSearch.SearchAction.Status String Security and compliance search action status. Either “Started” or “Completed”.
O365.SecurityAndCompliance.ContentSearch.SearchAction.TenantId String Security and compliance search action Tenant ID.

Command Example

!o365-sc-get-search-action search_action_name="example_Preview"

Context Example

{
    "O365": {
        "SecurityAndCompliance": {
            "ContentSearch": {
                "SearchAction": {
                    "Action": "Preview",
                    "AllowNotFoundExchangeLocationsEnabled": false,
                    "AzureBatchFrameworkEnabled": false,
                    "CaseId": null,
                    "CaseName": "",
                    "CreatedBy": "XSOAR-user",
                    "CreatedTime": "2020-11-29T07:23:50.05",
                    "Description": "",
                    "Errors": "",
                    "EstimateSearchJobId": "2a967c40-07c3-4903-ed7f-08d89436e9b1",
                    "EstimateSearchRunId": "1d130723-fe0a-4726-6d2a-08d89437520e",
                    "ExchangeLocation": [
                        "user@onmicrosoft.com"
                    ],
                    "ExchangeLocationExclusion": null,
                    "Identity": "xxxxx",
                    "IsValid": true,
                    "JobEndTime": "2020-11-29T07:24:05.76",
                    "JobId": "xxxxx",
                    "JobRunId": "xxxxx",
                    "JobStartTime": "2020-11-29T07:23:50.297",
                    "LastModifiedTime": "2020-11-29T07:23:50.05",
                    "Name": "example_Preview",
                    "PublicFolderLocation": null,
                    "PublicFolderLocationExclusion": null,
                    "Results": [
                        {
                            "DataLink": "data/All/xxxx.eml",
                            "Location": "user@onmicrosoft.com",
                            "ReceivedTime": "11/26/2020 2:59:01 PM",
                            "Sender": "Some user",
                            "Size": "19683",
                            "Subject": "Test1",
                            "Type": "Email"
                        },
                       {
                            "DataLink": "data/All/xxxx.eml",
                            "Location": "user@onmicrosoft.com",
                            "ReceivedTime": "12/26/2020 2:59:01 PM",
                            "Sender": "Some user",
                            "Size": "20225",
                            "Subject": "Test2",
                            "Type": "Email"
                        },
                    ],
                    "Retry": false,
                    "RunBy": "XSOAR-user",
                    "RunspaceId": "xxxxx",
                    "SearchName": "example",
                    "SharePointLocation": null,
                    "SharePointLocationExclusion": null,
                    "Status": "Completed",
                    "TenantId": "xxxxx"
                }
            }
        }
    }
}

Human Readable Output

Security And Compliance - search action ‘example_Preview’

Action JobEndTime LastModifiedTime Name RunBy SearchName Status
Preview 11/29/2020 7:24:05 AM 11/29/2020 7:23:50 AM example_Preview XSOAR-user example Completed

Tips for finding messages to remove

  • Keyword Query Language (KQL)
    • If you know the exact text or phrase used in the subject line of the message, use the Subject property in the search query, e.g., (subject:give me all ur money).
    • If you know that exact date (or date range) of the message, include the Received property in the search query, e.g., (received:6/13/2021..6/16/2021).
    • If you know who sent the message, include the From property in the search query, e.g., (from:user1@demistodev.onmicrosoft.com).
    • For all the available search properties see: Keyword queries and search conditions for eDiscovery.
  • Preview the search results to verify that the search returned only the message (or messages) that you want to delete.
  • Use the search estimate statistics (displayed by using the o365-sc-get-search command) to get a count of the total number of emails.

o365-sc-compliance-case-create


Create eDiscovery cases in the Microsoft Purview compliance portal.

Base Command

o365-sc-compliance-case-create

Input

Argument Name Description Required
case_name Case name create. Required
case_type “AdvancedEdiscovery: Used to manage legal or other types of investigations.
ComplianceClassifier: This type of case corresponds to a trainable classifier.
DataInvestigation: Data investigation cases are used to investigate data spillage incidents.
DSR: Data Subject Request (DSR) cases are used to manage General Data Protection Regulation (GDPR) DSR investigations.
eDiscovery: eDiscovery (also called eDiscovery Standard) cases are used to manage legal or other types of investigations.
This is the default value.
InsiderRisk: Insider risk cases are used to manage insider risk management cases.
Typically, insider risk management cases are manually created in the Microsoft Purview
compliance portal to further investigate activity based on a risk alert.
SupervisionPolicy: This type of case corresponds to communication compliance policy.”
. Possible values are: AdvancedEdiscovery, ComplianceClassifier, DataInvestigation, DSR, eDiscovery, InsiderRisk, SupervisionPolicy. Default is eDiscovery.
Optional
description Case description. Optional
external_id Case external ID. Optional

Context Output

Path Type Description
O365.SecurityAndCompliance.ComplianceCase.Name String Case name.
O365.SecurityAndCompliance.ComplianceCase.Status String Case status.
O365.SecurityAndCompliance.ComplianceCase.CreatedDateTime String Case created date time.

o365-sc-compliance-case-list


List different types of compliance cases in the Microsoft Purview compliance portal.

Base Command

o365-sc-compliance-case-list

Input

Argument Name Description Required
identity List cases by identity. Optional
case_type List cases by type. Possible values are: AdvancedEdiscovery, ComplianceClassifier, DataInvestigation, DSR, eDiscovery, InsiderRisk, SupervisionPolicy. Optional
limit Limit returned cases list size. Default is 50. Optional

Context Output

Path Type Description
O365.SecurityAndCompliance.ComplianceCase.Name String Case name.
O365.SecurityAndCompliance.ComplianceCase.Status String Case status.
O365.SecurityAndCompliance.ComplianceCase.GUID UUID Case GUID.
O365.SecurityAndCompliance.ComplianceCase.CreatedDateTime String Case created date time.

o365-sc-compliance-case-delete


Removes compliance cases from the Microsoft Purview compliance portal.

Base Command

o365-sc-compliance-case-delete

Input

Argument Name Description Required
identity Delete case by identity. Required

Context Output

There is no context output for this command.

o365-sc-case-hold-policy-create


Creates new case hold policies in the Microsoft Purview compliance portal.

Base Command

o365-sc-case-hold-policy-create

Input

Argument Name Description Required
policy_name Name of the policy to create. Required
case eDiscovery case, Case Name, Case Identity (GUID value). Required
comment Attach a comment to the case. Optional
exchange_location Mailbox or distribution group. Optional
public_folder_location Comma-separated list of public folders to include, or you can use the value “All” to include all. Optional
share_point_location SharePoint Online and OneDrive for Business sites to include. Optional
enabled Set hold policy as enabled or not. Possible values are: true, false. Default is true. Optional

Context Output

Path Type Description
O365.SecurityAndCompliance.CaseHoldPolicy.Name String Case hold policy name.
O365.SecurityAndCompliance.CaseHoldPolicy.Workload String Case hold policy workload.
O365.SecurityAndCompliance.CaseHoldPolicy.Enabled String Is case hold policy enabled.
O365.SecurityAndCompliance.CaseHoldPolicy.Mode String Case hold policy mode.

o365-sc-case-hold-policy-get


View existing case hold policies in the Microsoft Purview compliance portal.

Base Command

o365-sc-case-hold-policy-get

Input

Argument Name Description Required
identity Identify of the case hold policy to get. Optional
case Case of policy to get. Case name or case GUID. Optional
distribution_detail Whether to include distribution details or not. Possible values are: true, false. Default is true. Optional
include_bindings Whether to include bindings or not. Possible values are: true, false. Default is true. Optional

Context Output

Path Type Description
O365.SecurityAndCompliance.CaseHoldPolicy.Name String Case hold policy name.
O365.SecurityAndCompliance.CaseHoldPolicy.GUID String Case hold policy GUID.
O365.SecurityAndCompliance.CaseHoldPolicy.Workload String Case hold policy workload.
O365.SecurityAndCompliance.CaseHoldPolicy.Status String Case hold policy status.
O365.SecurityAndCompliance.CaseHoldPolicy.Mode String Case hold policy mode.

o365-sc-case-hold-policy-delete


Removes case hold policies from the Microsoft Purview compliance portal.

Base Command

o365-sc-case-hold-policy-delete

Input

Argument Name Description Required
identity Identify of the case hold policy to delete. Required
force_delete Whether to use force delete or not. Possible values are: true, false. Default is false. Optional

Context Output

There is no context output for this command.

o365-sc-case-hold-rule-create


Creates new case hold rules in the Microsoft Purview compliance portal.

Base Command

o365-sc-case-hold-rule-create

Input

Argument Name Description Required
rule_name Create rule with the specified name. Required
policy_name Create rule for the specified policy. Required
query Query using Keyword Query Language (KQL). Optional
comment Attach a comment to the created rule. Optional
is_disabled Whether the rule is disabled or not. Possible values are: true, false. Default is false. Optional

Context Output

Path Type Description
O365.SecurityAndCompliance.CaseHoldRule.Name String Case hold policy name.
O365.SecurityAndCompliance.CaseHoldRule.Status String Case hold policy status.
O365.SecurityAndCompliance.CaseHoldRule.Mode String Case hold policy mode.

o365-sc-case-hold-rule-list


View case hold rules in the Microsoft Purview compliance portal.

Base Command

o365-sc-case-hold-rule-list

Input

Argument Name Description Required
identify Get hold rule list by identity. Optional
policy Get hold rule list by policy. Optional
limit Limit the returned items list size. Default is 50. Optional

Context Output

Path Type Description
O365.SecurityAndCompliance.CaseHoldRule.Name String Case hold policy name.
O365.SecurityAndCompliance.CaseHoldRule.GUID UUID Case hold policy GUID.
O365.SecurityAndCompliance.CaseHoldRule.Enabled String Whether case hold policy is enabled.
O365.SecurityAndCompliance.CaseHoldRule.Mode String Case hold policy mode.

o365-sc-case-hold-rule-delete


Removes case hold rules from the Microsoft Purview compliance portal.

Base Command

o365-sc-case-hold-rule-delete

Input

Argument Name Description Required
identity Delete rule by identity. Optional
force_delete Whether to use force delete or not. Possible values are: true, false. Default is false. Optional

Context Output

There is no context output for this command.

Known Limitations

  • Security and compliance integrations do not support Security and compliance on-premise.
  • Each security and compliance command creates an IPS-Session (PowerShell session). The security and compliance PowerShell limits the number of concurrent sessions to 3. Since this affects the behavior of multiple playbooks running concurrently it we recommend that you retry failed tasks when using the integration commands in playbooks.
  • Proxies are not supported due to a Microsoft limitation.
  • Due to a Microsoft limitation, you can perform a search and purge operation on a maximum of 50,000 mailboxes. To work around this limitation, configure multiple instances of the integration each with different permission filtering so that the number of mailboxes in each instance does not exceed 50,000.
  • A maximum of 10 items per mailbox can be removed at one time, due to a Microsoft limitiation.
  • For more Microsoft known limitations see Limits for eDiscovery search.

o365-sc-case-hold-policy-set


Update inputs for case hold policies.

Base Command

o365-sc-case-hold-policy-set

Input

Argument Name Description Required
identity Identity of the policy to update. Required
add_exchange_locations Exchange locations to add to the policy. Optional
add_sharepoint_locations Sharepoint locations to add to the policy. Optional
add_public_locations Public locations to add to the policy. Optional
remove_exchange_locations Exchange locations to remove from the policy. Optional
remove_sharepoint_locations Sharepoint locations to remove from the policy. Optional
remove_public_locations Public locations to remove from the policy. Optional
comment Add a comment to existing policy. Optional
enabled Enable or disable the policy. Possible values are: true, false. Default is true. Optional

Context Output

There is no context output for this command.

o365-sc-email-security-search-and-delete-email-office-365-quick-action


Deletes an email for all recipients.
On first run, creates and starts a search (if it already exists – returns its status).
Then runs polling until the deletion is complete.
The force parameter allows creating a new search even if one already exists (default: false).

Base Command

o365-sc-email-security-search-and-delete-email-office-365-quick-action

Input

Argument Name Description Required
internet_message_id The unique identifier of the email message, e.g., <abc123@mail.example.com>. Required
exchange_location Comma-separated list of mailboxes or groups. Use “All” to target everyone (may be slow). Default is All. Optional
force If true, creates a new search even if one already exists. Possible values are: false, true. Default is false. Optional

Context Output

Path Type Description
O365.SecurityAndCompliance.CaseHoldRule.Name String Case hold policy name.

Configuration parameters

  • certificate_password — Certificate Password
  • certificate — Certificate
  • delegated_auth — UPN/Email (required)
  • credentials_app_secret — UPN Password
  • organization — The organization used in app-only authentication.
  • app_id — The application ID from the Azure portal
  • credentials_app_id — The application ID from the Azure portal
  • insecure — Trust any certificate (not secure)
  • app_secret — App Secret
  • tenant_id — Tenant ID
  • credentials_tenant_id — Tenant ID
  • connection_uri — Connection URI
  • azure_ad_authorized_endpoint_uri_base — AzureADAuthorizedEndpointURI Base

Commands (25)

  • o365-sc-auth-complete

    OAuth2.0 - Complete authorization.

  • o365-sc-auth-start

    OAuth2.0 - Start authorization.

  • o365-sc-auth-test

    OAuth2.0 - Test authorization.

  • o365-sc-case-hold-policy-create

    Creates new case hold policies in the Microsoft Purview compliance portal.

  • o365-sc-case-hold-policy-delete

    Removes case hold policies from the Microsoft Purview compliance portal.

  • o365-sc-case-hold-policy-get

    View existing case hold policies in the Microsoft Purview compliance portal.

  • o365-sc-case-hold-policy-set

    Update inputs for case hold policies.

  • o365-sc-case-hold-rule-create

    Creates new case hold rules in the Microsoft Purview compliance portal.

  • o365-sc-case-hold-rule-delete

    Removes case hold rules from the Microsoft Purview compliance portal.

  • o365-sc-case-hold-rule-list

    View case hold rules in the Microsoft Purview compliance portal.

  • o365-sc-compliance-case-create

    Create eDiscovery cases in the Microsoft Purview compliance portal.

  • o365-sc-compliance-case-delete

    Removes compliance cases from the Microsoft Purview compliance portal or the Microsoft Purview compliance portal.

  • o365-sc-compliance-case-list

    List different types of compliance cases in the Microsoft Purview compliance portal.

  • o365-sc-email-security-search-and-delete-email-office-365-quick-action

    Deletes an email for all recipients.

  • o365-sc-get-search

    Gets compliance search by name from the Security & Compliance Center.

  • o365-sc-get-search-action

    Gets compliance search action from the Security & Compliance Center.

  • o365-sc-list-search

    List compliance searches in the Security & Compliance Center.

  • o365-sc-list-search-action

    Lists compliance search actions from the Security & Compliance Center.

  • o365-sc-new-search

    Create compliance search in the Security & Compliance Center.

  • o365-sc-new-search-action

    After you create a content search using the o365-sc-new-search command and run it using the o365-sc-start-search command, you assign a search action to the search using the o365-sc-new-search-action command.

  • o365-sc-remove-search

    Remove compliance search by name from the Security & Compliance Center.

  • o365-sc-remove-search-action

    Removes a compliance search action by searching for the action name in the Security & Compliance Center.

  • o365-sc-set-search

    Modifies non-running compliance searches in the Security & Compliance Center.

  • o365-sc-start-search

    Starts stopped, completed, or not started compliance search in the Security & Compliance Center.

  • o365-sc-stop-search

    Stop running compliance search in the Security & Compliance Center.

. $PSScriptRoot\CommonServerPowerShell.ps1


$script:INTEGRATION_NAME = "Security And Compliance"
$script:COMMAND_PREFIX = "o365-sc"
$script:INTEGRATION_ENTRY_CONTEX = "O365.SecurityAndCompliance.ContentSearch"
$script:INTEGRATION_ENTRY_COMPLIANCE_CASE = "O365.SecurityAndCompliance.ComplianceCase"
$script:INTEGRATION_ENTRY_CASE_HOLD_POLICY = "O365.SecurityAndCompliance.CaseHoldPolicy"
$script:INTEGRATION_ENTRY_CASE_HOLD_RULE = "O365.SecurityAndCompliance.CaseHoldRule"
$script:SEARCH_ENTRY_CONTEXT = "$script:INTEGRATION_ENTRY_CONTEX.Search(val.Name && val.Name == obj.Name)"
$script:SEARCH_ACTION_ENTRY_CONTEXT = "$script:INTEGRATION_ENTRY_CONTEX.SearchAction(val.Name && val.Name == obj.Name)"

<# IMPORTANT NOTICE
# When conencting to ExchangeOnline - only needed command between CreateSession
# and DisconnectSession and let also the `finally` term to disconnect (it will do nothing if everything is fine).
# This will reduce the time sessions are opened between Exchange and the server and will create
# less problems.
# DO NOT USE ONE FINALLY STATEMENT: we don't know if and when it'll be executed and anyway it the DisconnectSession
# should be called before returning results to the server.
#>
Import-Module ExchangeOnlineManagement

#### HELPER FUNCTIONS ####

function UpdateIntegrationContext([OAuth2DeviceCodeClient]$client){
    $integration_context = @{
        "DeviceCode" = $client.device_code
        "DeviceCodeExpiresIn" = $client.device_code_expires_in
        "DeviceCodeCreationTime" = $client.device_code_creation_time
        "AccessToken" = $client.access_token
        "RefreshToken" = $client.refresh_token
        "AccessTokenExpiresIn" = $client.access_token_expires_in
        "AccessTokenCreationTime" = $client.access_token_creation_time
    }

    SetIntegrationContext $integration_context
    <#
        .DESCRIPTION
        Update integration context from OAuth2DeviceCodeClient client

        .EXAMPLE
        UpdateIntegrationContext $client

        .PARAMETER search_name
        OAuth2DeviceCodeClient client.
    #>
}

function ParseSuccessResults([string]$success_results, [int]$limit, [bool]$all_results) {
    $parsed_success_results = New-Object System.Collections.Generic.List[System.Object]
    if ($success_results) {
        $lines = $success_results.Split([Environment]::NewLine)

        if ($limit -ne -1) {
            $limit = ($limit, $lines.Count | Measure-Object -Minimum).Minimum
        } else {
            $limit = $lines.Count
        }

        # Results limit
        $results_count = 0
        # Lines iterator
        $lines_scanned = 0
        while ($results_count -lt $limit -and $lines_scanned -lt $lines.Count) {
            if ($lines[$lines_scanned] -match 'Location: (\S+), Item count: (\d+), Total size: (\d+)')
            {
                if ($matches[2] -ne 0 -or $all_results){
                    $parsed_success_results.Add(@{
                        "Location" = $matches[1]
                        "ItemsCount" = $matches[2]
                        "Size" = $matches[3]
                    })
                    $results_count += 1
                }
            }
            $lines_scanned += 1
        }
    }

    return $parsed_success_results
    <#
        .DESCRIPTION
        Parse string return in Search PSObject property "SuccessResults"

        .PARAMETER success_results
        SuccessResults raw string.

        .EXAMPLE
        ParseSuccessResults 'Location: Private mail box, Item count: 8, Total size: 63'

        .OUTPUTS
        List of psobject SuccessResults object.
    #>
}



function ParseResults([string]$results, [int]$limit = -1, [string]$type = "Preview") {
   if ($type -eq "Preview"){
        $results_matches_preview = (Select-String -AllMatches "\{?Location: (.*); Sender: (.*); Subject: (.*); Type: (.*); Size: (.*); Received Time: (.*); Data Link: (.*)[},]"  -InputObject $results).Matches
        $parsed_results = New-Object System.Collections.Generic.List[System.Object]
        foreach ($match in $results_matches_preview)
        {
            if ($parsed_results.Count -ge $limit -and $limit -ne -1){
                break
            }

            $parsed_results.Add(@{
                "Location" = $match.Groups[1].Value
                "Sender" = $match.Groups[2].Value
                "Subject" = $match.Groups[3].Value
                "Type" = $match.Groups[4].Value
                "Size" = $match.Groups[5].Value
                "ReceivedTime" = $match.Groups[6].Value
                "DataLink" = $match.Groups[7].Value
            })
        }
   }
    if ($type -eq "Purge"){
        $results_matches_purge = (Select-String -AllMatches "\{?Location: (.*); Item count: (.*); Total size: (.*); Failed count: (.*); [},]"  -InputObject $results).Matches
        $parsed_results = New-Object System.Collections.Generic.List[System.Object]
        foreach ($match in $results_matches_purge)
        {
            if ($parsed_results.Count -ge $limit -and $limit -ne -1){
                break
            }
            $parsed_results.Add(@{
                "Location" = $match.Groups[1].Value
                "ItemCount" = $match.Groups[2].Value
                "TotalSize" = $match.Groups[3].Value
                "FailedCount" = $match.Groups[4].Value
            })
        }
    }


    return $parsed_results
    <#
        .DESCRIPTION
        Parse string return in SearchAction PSObject property "Results"

        .PARAMETER success_results
        SuccessResults raw string.

        .EXAMPLE
        ParseResults 'Location: Private mail box; Sender: user@microsoft.com; Type: mail; Size: 100; Received Time: 16 August 2010; Data Link: xxxxx,'

        .OUTPUTS
        List of psobject Results object.
    #>
}

function ParseSearchToEntryContext([psobject]$search, [int]$limit = -1, [bool]$all_results = $false) {
    return @{
        "AllowNotFoundExchangeLocationsEnabled" = $search.AllowNotFoundExchangeLocationsEnabled
        "AzureBatchFrameworkEnabled" = $search.AzureBatchFrameworkEnabled
        "CaseId" = $search.CaseId
        "CaseName" = $search.CaseName
        "ContentMatchQuery" = $search.ContentMatchQuery
        "CreatedBy" = $search.CreatedBy
        "CreatedTime" = $search.CreatedTime
        "Description" = $search.Description
        "Errors" = $search.Errors
        "ExchangeLocation" = $search.ExchangeLocation
        "ExchangeLocationExclusion" = $search.ExchangeLocationExclusion
        "Identity" = $search.Identity
        "IsValid" = $search.IsValid
        "Items" = $search.Items
        "JobEndTime" = $search.JobEndTime
        "JobId" = $search.JobId
        "JobRunId" = $searchJobRunId
        "JobStartTime" = $search.JobStartTime
        "LastModifiedTime" = $search.LastModifiedTime
        "LogLevel" = $search.LogLevel
        "Name" = $search.Name
        "OneDriveLocation" = $search.OneDriveLocation
        "OneDriveLocationExclusion" = $search.OneDriveLocationExclusion
        "PublicFolderLocation" = $search.PublicFolderLocation
        "PublicFolderLocationExclusion" = $search.PublicFolderLocationExclusion
        "RunBy" = $search.RunBy
        "RunspaceId" = $search_action.RunspaceId
        "SearchStatus" = "Success"
        "SharePointLocation" = $search.SharePointLocation
        "SharePointLocationExclusion" = $search.SharePointLocationExclusion
        "Size" = $search.Size
        "Status" = $search.Status
        "SuccessResults" = ParseSuccessResults -success_results $search.SuccessResults -limit $limit -all_results $all_results
        "TenantId" = $search.TenantId
    }
    <#
        .DESCRIPTION
        Parse Search raw response PSObject to Entry Context.

        .PARAMETER search
        search raw psobject.

        .PARAMETER all_results
        Whether to include also not found locations.

        .PARAMETER limit
        Limit found items.

        .EXAMPLE
        ParseSearchToEntryContext $search

        .OUTPUTS
        Search entry context.

        .Notes
        1. Microsoft internal properties: OneDriveLocationExclusion, OneDriveLocation.
        2. SuccessResults property return as string which should be parsed.
    #>
}

function ParseSearchActionToEntryContext([psobject]$search_action, [int]$limit = -1) {
    return @{
        "Action" = $search_action.Action
        "AllowNotFoundExchangeLocationsEnabled" = $search_action.AllowNotFoundExchangeLocationsEnabled
        "AzureBatchFrameworkEnabled" = $search_action.AzureBatchFrameworkEnabled
        "CaseId" = $search_action.CaseId
        "CaseName" = $search_action.CaseName
        "CreatedBy" = $search_action.CreatedBy
        "CreatedTime" = $search_action.CreatedTime
        "Description" = $search_action.Description
        "Errors" = $search_action.Errors
        "EstimateSearchJobId"  = $search_action.EstimateSearchJobId
        "EstimateSearchRunId" = $search_action.EstimateSearchRunId
        "ExchangeLocation" = $search_action.ExchangeLocation
        "ExchangeLocationExclusion" = $search_action.ExchangeLocationExclusion
        "Identity" = $search_action.Identity
        "IsValid" = $search_action.IsValid
        "JobEndTime" = $search_action.JobEndTime
        "JobId" = $search_action.JobId
        "JobRunId" = $search_action.JobRunId
        "JobStartTime" = $search_action.JobStartTime
        "LastModifiedTime" = $search_action.LastModifiedTime
        "PublicFolderLocation" = $search_action.PublicFolderLocation
        "PublicFolderLocationExclusion" = $search_action.PublicFolderLocationExclusion
        "Retry" = $search_action.Retry
        "RunspaceId" = $search_action.RunspaceId
        "SearchStatus" = "Success"
        "SharePointLocation" = $search_action.SharePointLocation
        "SharePointLocationExclusion" = $search_action.SharePointLocationExclusion
        "Name" = $search_action.Name
        "RunBy" = $search_action.RunBy
        "SearchName" = $search_action.SearchName
        "Status" = $search_action.Status
        "TenantId" = $search_action.TenantId
        "Results" = ParseResults -results $search_action.Results -limit $limit -type $search_action.Action
    }
    <#
        .DESCRIPTION
        Parse SearchAction raw response PSObject to Entry Context.

        .PARAMETER search
        SearchAction raw response.

        .EXAMPLE
        ParseSearchActionToEntryContext $search_action

        .OUTPUTS
        SearchAction entry context.

        .Notes
        1. Microsoft internal properties: OneDriveLocationExclusion, OneDriveLocation.
        2. Results property return as string which should be parsed.
    #>
}

#### OAuth Client - Access Token Management ####
class OAuth2DeviceCodeClient {
    [string]$application_id
    [string]$application_scope = "offline_access%20https%3A//outlook.office365.com/.default"
    [string]$device_code
    [int]$device_code_expires_in
    [int]$device_code_creation_time
    [string]$access_token
    [string]$refresh_token
    [int]$access_token_expires_in
    [int]$access_token_creation_time
    [bool]$insecure
    [bool]$proxy
    [string]$tenant_id

    OAuth2DeviceCodeClient([string]$device_code, [string]$device_code_expires_in, [string]$device_code_creation_time, [string]$access_token,
                            [string]$refresh_token,[string]$access_token_expires_in, [string]$access_token_creation_time,
                           [bool]$insecure, [bool]$proxy, [string]$application_id, [string]$tenant_id) {
        $this.device_code = $device_code
        $this.device_code_expires_in = $device_code_expires_in
        $this.device_code_creation_time = $device_code_creation_time
        $this.access_token = $access_token
        $this.refresh_token = $refresh_token
        $this.access_token_expires_in = $access_token_expires_in
        $this.access_token_creation_time = $access_token_creation_time
        $this.insecure = $insecure
        $this.proxy = $proxy
        $this.application_id = $application_id
        $this.tenant_id = $tenant_id
        <#
            .DESCRIPTION
            OAuth2DeviceCodeClient manage state of OAuth2.0 device-code flow described in https://docs.microsoft.com/en-us/azure/active-directory/develop/v2-oauth2-device-code.

            .DESCRIPTION
            Its not recomended to create an object using the constructor, Use static method CreateClientFromIntegrationContext() instead.

            OAuth2DeviceCodeClient states are:
                1. Getting device-code (Will be used in stage 2) and user-code (Will be used by the user to authorize permissions) from Microsoft application.
                2. Getting access-token and refresh-token - after use authorize (Using stage 1 - device code)
                3. Refresh access-token if access-token is expired.

            .PARAMETER device_code
            A long string used to verify the session between the client and the authorization server.
            The client uses this parameter to request the access token from the authorization server.

            .PARAMETER device_code_expires_in
            The number of seconds before the device_code and user_code expire. (15 minutes)

            .PARAMETER access_token
            Opaque string, Issued for the scopes that were requested.

            .PARAMETER refresh_token
            Opaque string, Issued if the original scope parameter included offline_access. (Valid for 90 days)

            .PARAMETER access_token_expires_in
            Number of seconds before the included access token is valid for. (Usally - 60 minutes)

            .PARAMETER access_token_creation_time
            Unix time of access token creation (Used for knowing when to refresh the token).

            .PARAMETER access_token_expires_in
            Number of seconds before the included access token is valid for. (Usally - 60 minutes)

            .PARAMETER insecure
            Wheter to trust any TLS/SSL Certificate) or not.

            .PARAMETER proxy
            Wheter to user system proxy configuration or not.

            .NOTES
            1. Application id - a0c73c16-a7e3-4564-9a95-2bdf47383716 , This is well-known application publicly managed by Microsoft and will not work in on-premise enviorment.

            .LINK
            https://docs.microsoft.com/en-us/azure/active-directory/develop/v2-oauth2-device-code
        #>
    }

    static [OAuth2DeviceCodeClient]CreateClientFromIntegrationContext([bool]$insecure, [bool]$proxy, [string]$application_id, [string]$tenant_id) {
        $ic = GetIntegrationContext
        $client = [OAuth2DeviceCodeClient]::new($ic.DeviceCode, $ic.DeviceCodeExpiresIn, $ic.DeviceCodeCreationTime, $ic.AccessToken, $ic.RefreshToken,
                                                $ic.AccessTokenExpiresIn, $ic.AccessTokenCreationTime, $insecure, $proxy, $application_id, $tenant_id)

        return $client
        <#
            .DESCRIPTION
            Static method which create object (factory method) from populated values in integration context.

            .EXAMPLE
            [OAuth2DeviceCodeClient]::CreateClientFromIntegrationContext()

            .OUTPUTS
            OAuth2DeviceCodeClient initialized object.
        #>
    }

    [PSObject]AuthorizationRequest() {
        # Reset object-properties
        $this.device_code = $null
        $this.device_code_expires_in = $null
        $this.device_code_creation_time = $null
        # Get device-code and user-code
        $params = @{
            "URI" = "https://login.microsoftonline.com/$($this.tenant_id)/oauth2/v2.0/devicecode"
            "Method" = "Post"
            "Headers" = @{
                "Content-Type" = "application/x-www-form-urlencoded"
            }
            "Body" = "client_id=$($this.application_id)&scope=$($this.application_scope)"
            "NoProxy" = !$this.proxy
            "SkipCertificateCheck" = $this.insecure
        }
        $response = Invoke-WebRequest @params
        $response_body = ConvertFrom-Json $response.Content
        # Update object properties
        $this.device_code = $response_body.device_code
        $this.device_code_creation_time = [int][double]::Parse((Get-Date -UFormat %s))
        $this.device_code_expires_in = [int]::Parse($response_body.expires_in)

    return $response_body

        <#
            .DESCRIPTION
            Reset values populated in instance context and getting new device-code and user-code.

            .EXAMPLE
            $client.AuthorizationRequest()

            .OUTPUTS
            psobject - Raw body response.

            .LINK
            https://docs.microsoft.com/en-us/azure/active-directory/develop/v2-oauth2-device-code#device-authorization-request
        #>
    }

    [psobject]AccessTokenRequest() {
        # Get new token using device-code
        try {
            $params = @{
                "URI" = "https://login.microsoftonline.com/$($this.tenant_id)/oauth2/v2.0/token"
                "Method" = "Post"
                "Headers" = (New-Object "System.Collections.Generic.Dictionary[[String],[String]]").Add("Content-Type", "application/x-www-form-urlencoded")
                "Body" = "grant_type=urn%3Aietf%3Aparams%3Aoauth%3Agrant-type%3Adevice_code&code=$($this.device_code)&client_id=$($this.application_id)"
                "NoProxy" = !$this.proxy
                "SkipCertificateCheck" = $this.insecure
            }
            $response = Invoke-WebRequest @params
            $response_body = ConvertFrom-Json $response.Content
        }
        catch {
            $response_body = ConvertFrom-Json $_.ErrorDetails.Message
            if ($response_body.error -eq "authorization_pending" -or $response_body.error -eq "invalid_grant") {
                $error_details = "Please run command !$script:COMMAND_PREFIX-auth-start , before running this command."
            }
            elseif ($response_body.error -eq "expired_token") {
                $error_details = "At least $($this.access_token_expires_in) seconds have passed from executing !$script:COMMAND_PREFIX-auth-start, Please run the ***$script:COMMAND_PREFIX-auth-start*** command again."
            } else {
                $error_details = $response_body
            }

            throw "Unable to get access token for your account, $error_details"
        }
        # Update object properties
        $this.access_token = $response_body.access_token
        $this.refresh_token = $response_body.refresh_token
        $this.access_token_expires_in = [int]::Parse($response_body.expires_in)
        $this.access_token_creation_time = [int][double]::Parse((Get-Date -UFormat %s))

        return $response_body

        <#
            .DESCRIPTION
            Getting access-token and refresh-token from Microsoft application based on the device-code we go from AuthorizationRequest() method.

            .EXAMPLE
            $client.AccessTokenRequest()

            .OUTPUTS
            psobject - Raw body response.

            .LINK
            https://docs.microsoft.com/en-us/azure/active-directory/develop/v2-oauth2-device-code#authenticating-the-user
        #>
    }

    [psobject]RefreshTokenRequest() {
        # Get new token using refresh token
        try {
            $params = @{
                "URI" = "https://login.microsoftonline.com/$($this.tenant_id)/oauth2/v2.0/token"
                "Method" = "Post"
                "Headers" = (New-Object "System.Collections.Generic.Dictionary[[String],[String]]").Add("Content-Type", "application/x-www-form-urlencoded")
                "Body" = "grant_type=refresh_token&client_id=$($this.application_id)&refresh_token=$($this.refresh_token)&scope=$($this.application_scope)"
                "NoProxy" = !$this.proxy
                "SkipCertificateCheck" = $this.insecure
            }
            $response = Invoke-WebRequest @params
            $response_body = ConvertFrom-Json $response.Content
        }
        catch {
            $response_body = ConvertFrom-Json $_.ErrorDetails.Message
            $error_details = "Unable to refresh access token for your account"

            # AADSTS50173 points to password change https://login.microsoftonline.com/error?code=50173.
            # In that case, the integration context should be overwritten and the user should execute the auth process from the begining.
            if ($response_body.error_description -like "*AADSTS50173*") {
                $this.ClearContext()
                $error_details = "The account password has been changed or reset. Please run !$script:COMMAND_PREFIX-auth-start to re-authenticate"
            }
            elseif ($response_body.error -eq "invalid_grant") {
                $error_details = "Please login to grant account permissions (After 90 days grant is expired) !$script:COMMAND_PREFIX-auth-start"
            }
            throw "$error_details. Full error message: $response_body"
        }

        # Update object properties
        $this.access_token = $response_body.access_token
        $this.refresh_token = $response_body.refresh_token
        $this.access_token_expires_in = [int]::Parse($response_body.expires_in)
        $this.access_token_creation_time = [int][double]::Parse((Get-Date -UFormat %s))

        return $response_body

        <#
            .DESCRIPTION
            Getting new access-token and refresh-token from Microsoft application based on the refresh-token we got from AccessTokenRequest() method.

            .EXAMPLE
            $client.RefreshTokenRequest()

            .OUTPUTS
            PSObject - Raw body response.

            .LINK
            https://docs.microsoft.com/en-us/azure/active-directory/develop/v2-oauth2-implicit-grant-flow#refreshing-tokens
        #>
    }

    [bool]IsDeviceCodeExpired(){
        if (!$this.device_code){
            return $true
        }
        $current_time = [int][double]::Parse((Get-Date -UFormat %s)) - 30
        $valid_until = $this.device_code_creation_time + $this.access_token_expires_in

        return $current_time -gt $valid_until

        <#
            .DESCRIPTION
            Check if device-code expired.

            .EXAMPLE
            $client.IsDeviceCodeExpired()

            .OUTPUTS
            bool - True If device-code expired else False.

            .LINK
            https://docs.microsoft.com/en-us/azure/active-directory/develop/active-directory-configurable-token-lifetimes#configurable-token-lifetime-properties-after-the-retirement
        #>
    }

    [bool]IsAccessTokenExpired(){
        if (!$this.access_token){
            return $true
        }
        $current_time = [int][double]::Parse((Get-Date -UFormat %s)) - 30
        $valid_until = $this.access_token_creation_time + $this.access_token_expires_in

        return $current_time -gt $valid_until
        <#
            .DESCRIPTION
            Check if access-token expired.

            .EXAMPLE
            $client.IsAccessTokenExpired()

            .OUTPUTS
            bool - True If access-token expired else False.

            .LINK
            https://docs.microsoft.com/en-us/azure/active-directory/develop/active-directory-configurable-token-lifetimes#configurable-token-lifetime-properties-after-the-retirement
        #>
    }

    RefreshTokenIfExpired(){
        if ($this.access_token -and $this.IsAccessTokenExpired()) {
            $this.RefreshTokenRequest()
        }
        <#
            .DESCRIPTION
            Refresh access token if expired, with offset of 30 seconds.

            .EXAMPLE
            $client.RefreshTokenIfExpired()
        #>
    }

    ClearContext(){
        $this.access_token = $null
        $this.refresh_token = $null
        $this.access_token_expires_in = $null
        $this.access_token_creation_time = $null
        UpdateIntegrationContext $this
        <#
            .DESCRIPTION
            Clear the token fields from the integration context on password change case.

            .EXAMPLE
            $client.ClearContext()
        #>

    }
}

#### Security And Compliance client ####
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidUsingConvertToSecureStringWithPlainText', '', Scope='Class')]
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidUsingPlainTextForPassword', '', Scope='Class')]
class SecurityAndComplianceClient {
    [string]$access_token
    [string]$upn
    [string]$tenant_id
    [string]$upn_password
    [string]$connection_uri
    [string]$azure_ad_authorization_endpoint_uri_base
    [string]$azure_ad_authorization_endpoint_uri
    [bool]$using_delegated


    SecurityAndComplianceClient([string]$access_token, [string]$upn, [string]$tenant_id, [string]$upn_password, [bool]$using_delegated ,[string]$connection_uri, [string]$azure_ad_authorization_endpoint_uri_base) {

        $this.access_token = $access_token

        $this.upn = $upn

        if ($tenant_id) {
            $this.tenant_id = $tenant_id
        } else {
            $this.tenant_id = $null
        }

        if ($upn_password) {
            $this.upn_password = $upn_password
        } else {
            $this.upn_password = $null
        }

        $this.using_delegated = $using_delegated

        if ($connection_uri) {
            $this.connection_uri = $connection_uri
        } else {
            $this.connection_uri = $null
        }

        if ($azure_ad_authorization_endpoint_uri_base) {
            if ($tenant_id){
                $this.azure_ad_authorization_endpoint_uri = "$azure_ad_authorization_endpoint_uri_base/$tenant_id"
            } else {
                $this.azure_ad_authorization_endpoint_uri = "$azure_ad_authorization_endpoint_uri_base/common"
            }
        } else {
            $this.azure_ad_authorization_endpoint_uri = $null
        }
    }

    VerifyRequiredDelegatedSession(){
        if ($this.using_delegated -eq $false) {
            throw "Using this command requires interactive delegated authentication. Please make sure the UPN password is set in the integration parameters."
        }
    }

    CreateDelegatedSession([string]$CommandName)
    {
        if ($this.using_delegated -eq $true)
        {
            # Use UPN and password (interactive delegated auth)
            $securePassword = ConvertTo-SecureString $this.upn_password -AsPlainText -Force
            $UserCredential = New-Object System.Management.Automation.PSCredential ($this.upn, $securePassword)

            Connect-IPPSSession -Credential $UserCredential -CommandName $CommandName -EnableSearchOnlySession -WarningAction:SilentlyContinue -ShowBanner:$false | Out-Null
        }
        else {
            # Use access token (app-only / token-based auth)
            $cmd_params = @{
                "UserPrincipalName" = $this.upn
                "Organization" = $this.organization
                "AccessToken" = $this.access_token
                "ConnectionUri" = $this.connection_uri
                "AzureADAuthorizationEndpointUri" = $this.azure_ad_authorization_endpoint_uri
            }
            Connect-IPPSSession @cmd_params -CommandName $CommandName -EnableSearchOnlySession -WarningAction:SilentlyContinue -ShowBanner:$false | Out-Null
        }
    }

    DisconnectSession(){
        Disconnect-ExchangeOnline -Confirm:$false -WarningAction:SilentlyContinue 6>$null | Out-Null
    }

    [psobject]NewSearch([string]$search_name,  [string]$case, [string]$kql, [string]$description, [bool]$allow_not_found_exchange_locations, [string[]]$exchange_location,
                        [string[]]$public_folder_location, [string[]]$share_point_location, [string[]]$share_point_location_exclusion, [string]$error_action = $null) {
                        
        # Establish session to remote
        $this.CreateDelegatedSession("New-ComplianceSearch")
        # Import and Execute command
        $cmd_params = @{
            "Name" = $search_name
            "Case" = $case
            "ContentMatchQuery" = $kql
            "Description" = $description
            "AllowNotFoundExchangeLocationsEnabled" = $allow_not_found_exchange_locations
            "ExchangeLocation" = $exchange_location
            "PublicFolderLocation" = $public_folder_location
            "SharePointLocation" = $share_point_location
            "SharePointLocationExclusion" = $share_point_location_exclusion
        }
        if ($error_action) {
            $cmd_params.ErrorAction = $error_action
        }
        $response = New-ComplianceSearch @cmd_params
        # Close session to remote
        $this.DisconnectSession()

        return $response
        <#
            .DESCRIPTION
            Create compliance search in the Security & Compliance Center.

            .PARAMETER search_name
            The name of the compliance search.

            .PARAMETER case
            Name of a Core eDiscovery case to associate the new compliance search with.

            .PARAMETER kql
            Text search string or a query that's formatted by using the Keyword Query Language (KQL).

            .PARAMETER description
            Optional description for the compliance search.

            .PARAMETER allow_not_found_exchange_locations
            Whether to include mailboxes other than regular user mailboxes in the compliance search.

            .PARAMETER exchange_location
            Mailboxes to include.

            .PARAMETER public_folder_location
            Whether to include all public folders in the search.

            .PARAMETER share_point_location
            SharePoint Online sites to include. You identify the site by its URL value, or you can use the value All to include all sites.

            .PARAMETER share_point_location_exclusion
            SharePoint Online sites to exclude when you use the value All for the SharePointLocation parameter. You identify the site by its URL value.

            .EXAMPLE
            $client.NewSearch("new-search")
            $client.NewSearch("new-search", "new-search-description")

            .OUTPUTS
            psobject - Raw response.

            .LINK
            https://docs.microsoft.com/en-us/powershell/module/exchange/new-compliancesearch?view=exchange-ps
        #>
    }

    SetSearch([string]$search_name, [string]$kql, [string]$description, [bool]$allow_not_found_exchange_locations, [string[]]$add_exchange_location,
              [string[]]$add_exchange_location_exclusion, [string[]]$add_public_folder_location, [string[]]$add_share_point_location, [string[]]$add_share_point_location_exclusion,
              [string[]]$remove_exchange_location, [string[]]$remove_exchange_location_exclusion, [string[]]$remove_public_folder_location, [string[]]$remove_share_point_location,
              [string[]]$remove_share_point_location_exclusion) {

        # Establish session to remote
        $this.CreateDelegatedSession("Set-ComplianceSearch")
        # Execute command
        $cmd_params = @{
            "Identity" = $search_name
            "ContentMatchQuery" = $kql
            "Description" = $description
            "AllowNotFoundExchangeLocationsEnabled" = $allow_not_found_exchange_locations
            "AddExchangeLocation" = $add_exchange_location
            "AddExchangeLocationExclusion" = $add_exchange_location_exclusion
            "PublicFolderLocation" = $add_public_folder_location
            "AddSharePointLocation" = $add_share_point_location
            "AddSharePointLocationExclusion" = $add_share_point_location_exclusion
            "RemoveExchangeLocation" = $remove_exchange_location
            "RemoveExchangeLocationExclusion" = $remove_exchange_location_exclusion
            "RemovePublicFolderLocation" = $remove_public_folder_location
            "RemoveSharePointLocation" = $remove_share_point_location
            "RemoveSharePointLocationExclusion" = $remove_share_point_location_exclusion
        }
        Set-ComplianceSearch @cmd_params
        # Close session to remote
        $this.DisconnectSession()
        <#
            .DESCRIPTION
            Set compliance search in the Security & Compliance Center.

            .PARAMETER search_name
            The name of the compliance search.

            .PARAMETER kql
            Text search string or a query that's formatted by using the Keyword Query Language (KQL).

            .PARAMETER description
            Optional description for the compliance search.

            .PARAMETER allow_not_found_exchange_locations
            Whether to include mailboxes other than regular user mailboxes in the compliance search.

            .PARAMETER add_exchange_location
            Add mailboxes to include.

            .PARAMETER add_exchange_location_exclusion
            Add mailboxes to exclude when you use the value "All" for the exchange_location parameter.

            .PARAMETER add_public_folder_location
            Add public folders to include.

            .PARAMETER add_share_point_location
            Add sharePoint online sites to include. You identify the site by its URL value.

            .PARAMETER add_share_point_location_exclusion
            Add sharePoint online sites to exclude when you use the value "All" for the SharePointLocation parameter. You identify the site by its URL value.

            .PARAMETER remove_exchange_location
            Remove mailboxes to include.

            .PARAMETER remove_exchange_location_exclusion
            Remove mailboxes to exclude when you use the value "All" for the exchange_location parameter.

            .PARAMETER remove_public_folder_location
            Remove public folders to include.

            .PARAMETER remove_share_point_location
            Remove sharePoint online sites to include. You identify the site by its URL value.

            .PARAMETER remove_share_point_location_exclusion
            Remove sharePoint online sites to exclude when you use the value "All" for the exchange_location (Used in create new compliance search) argument or share_point_location argument. You identify the site by its URL value.

            .EXAMPLE
            $client.SetSearch("new-search", "new-search-description")

            .LINK
            https://docs.microsoft.com/en-us/powershell/module/exchange/set-compliancesearch?view=exchange-ps
        #>
    }

    RemoveSearch([string]$search_name) {
        # Establish session to remote
        $this.CreateDelegatedSession("Remove-ComplianceSearch")
        # Import and Execute command
        Remove-ComplianceSearch -Identity $search_name -Confirm:$false

        # Close session to remote
        $this.DisconnectSession()

       <#
            .DESCRIPTION
            Remove compliance search by name from the Security & Compliance Center.

            .PARAMETER search_name
            The name of the compliance search.

            .EXAMPLE
            $client.RemoveSearch("new-search")

            .LINK
            https://docs.microsoft.com/en-us/powershell/module/exchange/remove-compliancesearch?view=exchange-ps
        #>
    }

    [array]ListSearch() {
        # Establish session to remote
        $this.CreateDelegatedSession("Get-ComplianceSearch")
        # Execute command
        $response = Get-ComplianceSearch

        # Close session to remote
        $this.DisconnectSession()

        return $response

       <#
            .DESCRIPTION
            List compliance searches in the Security & Compliance Center.

            .EXAMPLE
            $client.ListSearch()

            .OUTPUTS
            array - Raw response.

            .LINK
            https://docs.microsoft.com/en-us/powershell/module/exchange/get-compliancesearch?view=exchange-ps
        #>
    }

    [psobject]GetSearch([string]$search_name) {
        # Establish session to remote
        $this.CreateDelegatedSession("Get-ComplianceSearch")
        # Import and Execute command
        $response = Get-ComplianceSearch -Identity $search_name

        # Close session to remote
        $this.DisconnectSession()

        return $response
        <#
            .DESCRIPTION
            Get compliance search by name from the Security & Compliance Center.

            .PARAMETER search_name
            The name of the compliance search.

            .EXAMPLE
            $client.GetSearch("new-search")

            .OUTPUTS
            psobject - Raw response.

            .LINK
            https://docs.microsoft.com/en-us/powershell/module/exchange/get-compliancesearch?view=exchange-ps
        #>
    }

    StartSearch([string]$search_name) {
        # Establish session to remote
        $this.CreateDelegatedSession("Start-ComplianceSearch")
        # Execute command
        Start-ComplianceSearch -Identity $search_name -Confirm:$false -Force:$true

        # Close session to remote
        $this.DisconnectSession()
        <#
            .DESCRIPTION
            Start stopped, completed or not started compliance search in the Security & Compliance Center.

            .PARAMETER search_name
            The name of the compliance search.

            .EXAMPLE
            $client.StartSearch("new-search")

            .LINK
            https://docs.microsoft.com/en-us/powershell/module/exchange/start-compliancesearch?view=exchange-ps
        #>
    }

    StopSearch([string]$search_name) {

        # Establish session to remote
        $this.CreateDelegatedSession("Stop-ComplianceSearch")
        # Execute command
        Stop-ComplianceSearch -Identity $search_name -Confirm:$false

        # Close session to remote
        $this.DisconnectSession()

        <#
            .DESCRIPTION
            Stop compliance search by name in the Security & Compliance Center.

            .PARAMETER search_name
            The name of the compliance search.

            .EXAMPLE
            $client.StopSearch("new-search")

            .LINK
            https://docs.microsoft.com/en-us/powershell/module/exchange/stop-compliancesearch?view=exchange-ps
        #>
    }

    [psobject]NewSearchAction([string]$search_name, [string]$action, [string]$purge_type,
                              [string]$share_point_archive_format, [string]$format,
                              [bool]$include_sharepoint_document_versions, [string]$notify_email,
                              [string]$notify_email_cc, [string]$scenario, [string]$scope) {
        $this.VerifyRequiredDelegatedSession()
        # Establish session to remote
        $this.CreateDelegatedSession("New-ComplianceSearchAction")
        # Execute command
        $cmd_params = @{
            "SearchName" = $search_name
        }
        if ($action -eq "Preview") {
            $cmd_params.Preview = $true
            $cmd_params.Confirm = $false
        } elseif ($action -eq "Purge") {
            $cmd_params.Purge = $true
            $cmd_params.PurgeType = $purge_type
            $cmd_params.Confirm = $false
            $cmd_params.Force = $true
        } else {
            throw "New action must include valid action - Preview/Purge"
        }
        $response = New-ComplianceSearchAction @cmd_params

        # Close session to remote
        $this.DisconnectSession()

        return $response
        <#
            .DESCRIPTION
            Create compliance search action in the Security & Compliance Center.

            .PARAMETER search_name
            The name of the compliance search.

            .PARAMETER action
            Search action type - Preview (Showing results) / Purge (Delete found emails)

            .PARAMETER purge_type
            Used if action type is purge, Search action purge type - SoftDelete (allow recover) / HardDelete (not recoverable).

            .EXAMPLE
            $client.NewSearchAction("search-name", "Preview")
            $client.NewSearchAction("search-name", "Purge", "HardDelete")
         #>

            .OUTPUTS
            psobject - Raw response.

            .LINK
            https://docs.microsoft.com/en-us/powershell/module/exchange/new-compliancesearchaction?view=exchange-ps
        #>
    }

    RemoveSearchAction([string]$search_action_name) {
        # Establish session to remote
        $this.CreateDelegatedSession("Remove-ComplianceSearchAction")
        # Execute command
        Remove-ComplianceSearchAction -Identity $search_action_name -Confirm:$false
        # Close session to remote
        $this.DisconnectSession()

        <#
            .DESCRIPTION
            Remove compliance search action from the Security & Compliance Center.

            .PARAMETER search_action_name
            The name of the compliance search action.

            .EXAMPLE
            $client.RemoveSearchAction("search-name")

            .LINK
            https://docs.microsoft.com/en-us/powershell/module/exchange/remove-compliancesearchaction?view=exchange-ps
        #>
    }

    [array]ListSearchActions() {
        # Establish session to remote
        $this.CreateDelegatedSession("Get-ComplianceSearchAction")
        # Execute command
        $response = Get-ComplianceSearchAction

        # Close session to remote
        $this.DisconnectSession()

        return $response
        <#
            .DESCRIPTION
            List all compliance search action in the Security & Compliance Center.

            .EXAMPLE
            $client.ListearchAction()

            .OUTPUTS
            array - Raw response.

            .LINK
            https://docs.microsoft.com/en-us/powershell/module/exchange/get-compliancesearchaction?view=exchange-ps
        #>
    }

    [psobject]GetSearchAction(
        [string]$search_action_name,
        [string]$error_action = $null
    ) {
        # Establish session to remote
        $this.CreateDelegatedSession("Get-ComplianceSearchAction")
    
        # Prepare command parameters
        $cmd_params = @{
            Identity = $search_action_name
        }
    
        if ($error_action) {
            $cmd_params["ErrorAction"] = $error_action
        }
    
        # Execute command
        $response = Get-ComplianceSearchAction @cmd_params
    
        # Close session to remote
        $this.DisconnectSession()
        return $response
        <#
            .DESCRIPTION
            Get compliance search action in the Security & Compliance Center.
    
            .PARAMETER search_action_name
            The name of the compliance search action.
            .PARAMETER error_action
            Optional. PowerShell error action preference (e.g., "Stop").
    
            .EXAMPLE
            $client.GetSearchAction("search-name", "Stop")
    
            .OUTPUTS
            psobject - Raw response.
    
            .LINK
            https://docs.microsoft.com/en-us/powershell/module/exchange/get-compliancesearchaction?view=exchange-ps
        #>
    }

    [psobject]ComplianceCaseCreate([string]$case_name, [string]$case_type, [string]$description, [string]$external_id) {
        # Establish session to remote
        $this.CreateDelegatedSession("New-ComplianceCase")

        $cmd_params = @{
            "Name" = $case_name
            "CaseType" = $case_type
        }
        if ($description) {
            $cmd_params.Description = $description
        }
        if ($external_id) {
            $cmd_params.ExternalId = $external_id
        }

        # Execute command
        $response = New-ComplianceCase @cmd_params
        # Close session to remote
        $this.DisconnectSession()
        return $response
           <#
            .DESCRIPTION
            Create eDiscovery cases in the Microsoft Purview compliance portal.

            .PARAMETER case_name
            The name of the case to create.
            .PARAMETER case_type
            Type of case from a closed list.
            .PARAMETER description
            Attach a description to case.
            .PARAMETER external_id
            Optional ID or external case number that you can associate with the new compliance case.

            .EXAMPLE
            $client.ComplianceCaseCreate("case_name", "case_type", "description", "external_id")

            .OUTPUTS
            psobject - Raw response.

            .LINK
            https://learn.microsoft.com/en-us/powershell/module/exchange/new-compliancecase?view=exchange-ps
        #>
    }

    [psobject]ComplianceCaseList([string]$identity, [string]$case_type, [int]$limit) {
        # Establish session to remote
        $this.CreateDelegatedSession("Get-ComplianceCase")

        $cmd_params = @{}
        if ($identity) {
            $cmd_params.Identity = $identity
        }
        if ($case_type) {
            $case_type_list = ArgToList($case_type)
            $response = @()
            $case_type_list | ForEach-Object{
                $cmd_params.CaseType = $_
                $response += Get-ComplianceCase @cmd_params
            }
        } else {
            $response = Get-ComplianceCase @cmd_params
        }
        $response = $response | Select-Object -First $limit
        if (-not $response){
            # Close session to remote
            $this.DisconnectSession()
            throw "The list action didn't return any results. The Compliance cases do not exist or have been deleted."
        }
        # Close session to remote
        $this.DisconnectSession()
        return $response
           <#
            .DESCRIPTION
            List different types of compliance cases in the Microsoft Purview compliance portal

            .PARAMETER identity
            List case with the identity.
            .PARAMETER case_type
            List cases of the sepecified case_type.
            .PARAMETER limit
            Limit the amount of results default is 50.

            .EXAMPLE
            $client.ComplianceCaseList()

            .OUTPUTS
            psobject - Raw response.

            .LINK
            https://learn.microsoft.com/en-us/powershell/module/exchange/get-compliancecase?view=exchange-ps
        #>
    }

    [psobject]ComplianceCaseDelete([string]$identity) {
        # Establish session to remote
        $this.CreateDelegatedSession("Remove-ComplianceCase")

        # Execute command
        $response = Remove-ComplianceCase -Identity $identity -Confirm:$false
        # Close session to remote
        $this.DisconnectSession()
        return $response
           <#
            .DESCRIPTION
            Removes compliance cases from the Microsoft Purview compliance portal or the Microsoft Purview compliance portal.

            .PARAMETER identity
            Identity of the case to remove.

            .EXAMPLE
            $client.ComplianceCaseDelete("case_identity")

            .OUTPUTS
            psobject - Raw response.

            .LINK
            https://learn.microsoft.com/en-us/powershell/module/exchange/remove-compliancecase?view=exchange-ps
        #>
    }

    [psobject]CaseHoldPolicyCreate([string]$policy_name, [string]$case, [string]$comment, [string]$exchange_location,
                                   [string]$public_folder_location, [string]$share_point_location, [bool]$enabled) {

        $this.VerifyRequiredDelegatedSession()

        # Establish session to remote
        $this.CreateDelegatedSession("New-CaseHoldPolicy")
        $cmd_params = @{
            "Name" = $policy_name
            "Case" = $case
            "Enabled" = $enabled
        }
        if ($comment) {
            $cmd_params.Comment = $comment
        }
        if ($exchange_location) {
            $cmd_params.ExchangeLocation = $exchange_location
        }
        if ($public_folder_location) {
            $cmd_params.PublicFolderLocation = $public_folder_location
        }
        if ($share_point_location) {
            $cmd_params.SharePointLocation = $share_point_location
        }
        # Execute command
        $response = New-CaseHoldPolicy @cmd_params
        # Close session to remote
        $this.DisconnectSession()
        return $response
           <#
            .DESCRIPTION
            Creates new case hold policies in the Microsoft Purview compliance portal

            .PARAMETER policy_name
             Name of a new policy name to create.

            .PARAMETER case
            Case to connect the policy to.

            .PARAMETER comment
            Attach a comment to the policy.

            .PARAMETER exchange_location
            The ExchangeLocation parameter specifies the mailboxes to include in the policy.

            .PARAMETER public_folder_location
            Specifies that you want to include all public folders in the case hold policy.

            .PARAMETER share_point_location
            Specifies the SharePoint Online and OneDrive for Business sites to include.

            .PARAMETER enabled
            Whether the policy is enabled or disabled.

            .EXAMPLE
            New-CaseHoldPolicy -Name "Regulation 123 Compliance" -Case "123 Compliance Case" -ExchangeLocation "Kitty Petersen", "Scott Nakamura" -SharePointLocation "https://contoso.sharepoint.com/sites/teams/finance"

            .OUTPUTS
            psobject - Raw response.

            .LINK
            https://learn.microsoft.com/en-us/powershell/module/exchange/new-caseholdpolicy?view=exchange-ps
        #>
    }

    [psobject]CaseHoldPolicyGet([string]$identity, [string]$case, [bool]$distribution_detail, [bool]$include_bindings){
        # Establish session to remote
        $this.CreateDelegatedSession("Get-CaseHoldPolicy")
        $cmd_params = @{
            "DistributionDetail" = $distribution_detail
            "IncludeBindings" = $include_bindings
        }
        if ($identity) {
            $cmd_params.Identity = $identity
        }
        if ($case) {
            $cmd_params.Case = $case
        }
        # Execute command
        $response = Get-CaseHoldPolicy @cmd_params
        if (-not $response) {
            # Close session to remote
            $this.DisconnectSession()
            throw "The Get action didn't return any results. The Policy does not exist or has been deleted."
        }
        # Close session to remote
        $this.DisconnectSession()
        return $response
        <#
        .DESCRIPTION
        View existing case hold policies in the Microsoft Purview compliance portal

        .PARAMETER identity
        Specifies the case hold policy that you want to view.

        .PARAMETER case
        The Case parameter specifies the case hold policy that you want to view by using the eDiscovery case that's associated with the policy.

        .PARAMETER distribution_detail
        Returns detailed policy distribution information on the case hold policy.

        .PARAMETER include_bindindgs
        The ExchangeLocation parameter specifies the mailboxes to include in the policy.

        .EXAMPLE
        Get-CaseHoldPolicy -Case "Contoso Legal"
        Get-CaseHoldPolicy -Identity "Regulation 123 Compliance"

        .OUTPUTS
        psobject - Raw response.

        .LINK
        https://learn.microsoft.com/en-us/powershell/module/exchange/get-caseholdpolicy?view=exchange-ps
        #>
    }

    [psobject]CaseHoldPolicyDelete([string]$identity, [bool]$force_delete){
        $this.VerifyRequiredDelegatedSession()

        # Establish session to remote
        $this.CreateDelegatedSession("Remove-CaseHoldPolicy")
        # Execute command
        if($force_delete) {
            $response = Remove-CaseHoldPolicy -Identity $identity -ForceDeletion -Confirm:$false
        } else {
            $response = Remove-CaseHoldPolicy -Identity $identity -Confirm:$false
        }
        # Close session to remote
        $this.DisconnectSession()
        return $response
        <#
        .DESCRIPTION
        Remove case hold policies from the Microsoft Purview compliance portal.

        .PARAMETER identity
        Specify the case hold policy to remove.

        .PARAMETER distribution_detail
        Returns detailed policy distribution information on the case hold policy.

        .EXAMPLE
        Remove-CaseHoldPolicy -Identity "Regulation 123 Compliance"

        .OUTPUTS
        psobject - Raw response.

        .LINK
        https://learn.microsoft.com/en-us/powershell/module/exchange/remove-caseholdpolicy?view=exchange-ps
        #>
    }


    CaseHoldPolicySet([string]$identity, [bool]$enabled, [string[]]$add_exchange_locations, [string[]] $add_sharepoint_locations, [string[]]$add_public_locations,
        [string[]]$remove_exchange_locations, [string[]]$remove_sharepoint_locations, [string[]]$remove_public_locations, [string]$comment){
        $this.VerifyRequiredDelegatedSession()

        $this.CreateDelegatedSession("Set-CaseHoldPolicy")
      $cmd_params = @{}
  
      if ($identity) { $cmd_params.Identity = $identity }
      if ($enabled) { $cmd_params.Enabled = $enabled }
      if ($add_exchange_locations) { $cmd_params.AddExchangeLocation = $add_exchange_locations }
      if ($add_sharepoint_locations) { $cmd_params.AddSharePointLocation = $add_sharepoint_locations }
      if ($add_public_locations) { $cmd_params.AddPublicFolderLocation = $add_public_locations }
      if ($remove_exchange_locations) { $cmd_params.RemoveExchangeLocation = $remove_exchange_locations }
      if ($remove_sharepoint_locations) { $cmd_params.RemoveSharePointLocation = $remove_sharepoint_locations }
      if ($remove_public_locations) { $cmd_params.RemovePublicFolderLocation = $remove_public_locations }
      if ($comment) { $cmd_params.Comment = $comment }

        Set-CaseHoldPolicy @cmd_params
        $this.DisconnectSession()
    }


    [psobject]CaseHoldRuleCreate([string]$rule_name, [string]$policy_name, [string]$query, [string]$comment, [bool]$is_disabled){
        $this.VerifyRequiredDelegatedSession()

        # Establish session to remote
        $this.CreateDelegatedSession("New-CaseHoldRule")
        $cmd_params = @{
            "Name" = $rule_name
            "Policy" = $policy_name
            "Disabled" = $is_disabled
        }
        if ($comment) {
            $cmd_params.Comment = $comment
        }
        if ($query) {
            $cmd_params.ContentMatchQuery = $query
        }
        # Execute command
        $response = New-CaseHoldRule @cmd_params
        # Close session to remote
        $this.DisconnectSession()
        return $response
        <#
        .DESCRIPTION
        Creates new case hold rules in the Microsoft Purview compliance portal.

        .PARAMETER rule_name
        The rule name to create.
        .PARAMETER policy_name
        Policy to attach the newly created rule to.
        .PARAMETER query
        Query using Keyword Query Language (KQL).
        .PARAMETER comment
        Attach a comment to the rule.
        .PARAMETER is_disabled
        Whether the rule is disabled or not. Default is false.
        .EXAMPLE
        New-CaseHoldRule -Name "2016 Budget Spreadsheets" -Policy "CaseHoldPolicy 16" -ContentMatchQuery "filename:2016 budget filetype:xlsx"

        .OUTPUTS
        psobject - Raw response.

        .LINK
        https://learn.microsoft.com/en-us/powershell/module/exchange/new-caseholdrule
        #>
    }

    [psobject]CaseHoldRuleList([string]$identity, [string]$policy, [int]$limit){
        # Establish session to remote
        $this.CreateDelegatedSession("Get-CaseHoldRule")
        if ($identity) {
            $response = Get-CaseHoldRule -Identity $identity
        } elseif ($policy){
            $response = Get-CaseHoldRule -Policy $policy
        } else {
            $response = Get-CaseHoldRule
        }
        if (-not $response){
            # Close session to remote
            $this.DisconnectSession()
            throw "The list action didn't return any results. The rules do not exist or have been deleted."
        }
        $response = $response | Select-Object -First $limit
        # Close session to remote
        $this.DisconnectSession()
        return $response
        <#
        .DESCRIPTION
        View case hold rules in the Microsoft Purview compliance portal.

        .PARAMETER identity
        List rules by policy identity.
        .PARAMETER policy
        List rules by policy.
        .PARAMETER limit
        Limit number of rules returned. Default is 50.

        .EXAMPLE
        Get-CaseHoldRule  -Identity "Test Rule 66"

        .OUTPUTS
        psobject - Raw response.

        .LINK
        https://learn.microsoft.com/en-us/powershell/module/exchange/get-caseholdrule
        #>
    }

    [psobject]CaseHoldRuleDelete([string]$identity, [bool]$force_delete){
        $this.VerifyRequiredDelegatedSession()

        # Establish session to remote
        $this.CreateDelegatedSession("Remove-CaseHoldRule")
        # Execute command
        if ($force_delete) {
            $response = Remove-CaseHoldRule -Identity $identity -ForceDeletion -Confirm:$false
        } else {
            $response = Remove-CaseHoldRule -Identity $identity -Confirm:$false
        }
        # Close session to remote
        $this.DisconnectSession()
        return $response
        <#
        .DESCRIPTION
        Removes case hold rules from the Microsoft Purview compliance portal.

        .PARAMETER identity
        Identity of rule to delete.
        .PARAMETER force_delete
        Wethere to use force_delete or not.

        .EXAMPLE
        Remove-CaseHoldRule -Identity "Test Rule 3" -Confirm:$false

        .OUTPUTS
        psobject - Raw response.

        .LINK
        https://learn.microsoft.com/en-us/powershell/module/exchange/remove-caseholdrule
        #>
    }
}


#### COMMAND FUNCTIONS ####

function TestModuleCommand ([SecurityAndComplianceClient]$cs_client, [bool]$using_delegated) {

    $entry_context = $null
    if ($using_delegated -eq $false){
        $human_readable = "To test your connection, if you are using app-only authentication use the command !$script:COMMAND_PREFIX-auth-start and follow the instructions. If you are using UPN and password authentication, run the command !$script:COMMAND_PREFIX-auth-test to verify the connection."
        $raw_response = $null
    } else {
        $human_readable = "Test Ok!"
        $raw_response = $true
        try {
            $cs_client.CreateDelegatedSession("Start-ComplianceSearch")
        }
        finally {
            $cs_client.DisconnectSession()
        }
    }

    return $human_readable, $entry_context, $raw_response
}

function StartAuthCommand ([OAuth2DeviceCodeClient]$client) {
    $raw_response = $client.AuthorizationRequest()
    $human_readable = "## $script:INTEGRATION_NAME - Authorize instructions
1. To sign in, use a web browser to open the page [https://microsoft.com/devicelogin](https://microsoft.com/devicelogin) and enter the code **$($raw_response.user_code)** to authenticate.
2. Run the **!$script:COMMAND_PREFIX-auth-complete** command in the War Room.
3. Run the **!$script:COMMAND_PREFIX-auth-test** command in the War Room to test the completion of the authorization process and the configured parameters."
    $entry_context = @{}

    return $human_readable, $entry_context, $raw_response
}

function CompleteAuthCommand ([OAuth2DeviceCodeClient]$client) {
    # Verify that user run start before complete
    if (!$client.device_code) {
        throw "Please run !o365-sc-auth-start and follow the command instructions"
    }
    $raw_response = $client.AccessTokenRequest()
    $human_readable = "Your account **successfully** authorized!"
    $entry_context = @{}

    return $human_readable, $entry_context, $raw_response
}

function TestAuthCommand ([OAuth2DeviceCodeClient]$oclient, [SecurityAndComplianceClient]$cs_client, [bool]$using_delegated) {

    if ($using_delegated -eq $false){
        $raw_response = $oclient.RefreshTokenRequest()
    } else {
        $raw_response = $null
    }

    $human_readable = "**Test ok!**"
    $entry_context = @{}
    try {
        $cs_client.CreateDelegatedSession("Start-ComplianceSearch")
    }
    finally {
        $cs_client.DisconnectSession()
    }

    return $human_readable, $entry_context, $raw_response
}

function NewSearchCommand([SecurityAndComplianceClient]$client, [hashtable]$kwargs) {
    # Command arguemnts parsing
    $allow_not_found_exchange_locations = ConvertTo-Boolean $kwargs.allow_not_found_exchange_locations
    $exchange_location = ArgToList $kwargs.exchange_location
    $public_folder_location = ArgToList $kwargs.public_folder_location
    $share_point_location = ArgToList $kwargs.share_point_location
    $share_point_location_exclusion = ArgToList $kwargs.share_point_location_exclusion
    if (!$kwargs.search_name -or $kwargs.search_name -eq "") {
        $kwargs.search_name = "XSOAR-$(New-Guid)"
    }
    # Raw response 
        $raw_response = $client.NewSearch($kwargs.search_name, $kwargs.case, $kwargs.kql, $kwargs.description, $allow_not_found_exchange_locations,
                                      $exchange_location, $public_folder_location, $share_point_location, $share_point_location_exclusion, $null)
    # Human readable
    $md_columns = $raw_response | Select-Object -Property Name, Description, CreatedBy, LastModifiedTime, ContentMatchQuery
    $human_readable = TableToMarkdown $md_columns  "$script:INTEGRATION_NAME - New search '$($kwargs.search_name)' created"
    # Entry context
    $entry_context = @{
        $script:SEARCH_ENTRY_CONTEXT = ParseSearchToEntryContext $raw_response
    }

    return $human_readable, $entry_context, $raw_response
}

function SetSearchCommand([SecurityAndComplianceClient]$client, [hashtable]$kwargs) {
    # Command arguemnts parsing
    if ($kwargs.allow_not_found_exchange_locations) {
        $allow_not_found_exchange_locations = ConvertTo-Boolean $kwargs.allow_not_found_exchange_locations
    }
    $add_exchange_location = ArgToList $kwargs.add_exchange_location
    $add_exchange_location_exclusion = ArgToList $kwargs.add_exchange_location_exclusion
    $add_public_folder_location = ArgToList $kwargs.add_public_folder_location
    $add_share_point_location = ArgToList $kwargs.add_share_point_location
    $add_share_point_location_exclusion = ArgToList $kwargs.add_share_point_location_exclusion
    $remove_exchange_location = ArgToList $kwargs.remove_exchange_location
    $remove_exchange_location_exclusion = ArgToList $kwargs.remove_exchange_location_exclusion
    $remove_public_folder_location = ArgToList $kwargs.remove_public_folder_location
    $remove_share_point_location = ArgToList $kwargs.remove_share_point_location
    $remove_share_point_location_exclusion = ArgToList $kwargs.remove_share_point_location_exclusion
    # Set operation doesn't return any output
    $client.SetSearch($kwargs.search_name, $kwargs.kql, $kwargs.description, $allow_not_found_exchange_locations,
                      $add_exchange_location, $add_exchange_location_exclusion, $add_public_folder_location, $add_share_point_location, $add_share_point_location_exclusion,
                      $remove_exchange_location, $remove_exchange_location_exclusion, $remove_public_folder_location, $remove_share_point_location, $remove_share_point_location_exclusion)
    # Raw response
    $raw_response = @{}
    # Human readable
    $human_readable = "$script:INTEGRATION_NAME - Search **$($kwargs.search_name)** modified!"
    # Entry context
    $entry_context = @{}

    return $human_readable, $entry_context, $raw_response
}

function RemoveSearchCommand([SecurityAndComplianceClient]$client, [hashtable]$kwargs) {
    # Remove operation doesn't return any output
    $client.RemoveSearch($kwargs.search_name)
    # Raw response
    $raw_response = @{}
    # Human readable
    $human_readable = "$script:INTEGRATION_NAME - Search **$($kwargs.search_name)** removed!"
    # Entry context
    $entry_context = @{}

    return $human_readable, $entry_context, $raw_response
}

function ListSearchCommand([SecurityAndComplianceClient]$client, [hashtable]$kwargs) {
    # Raw response
    $raw_response = $client.ListSearch()

    if ($raw_response.count -eq 0){
        return "#### No compliance searches were retrieved from the Compliance Center.", @{}, $raw_response
    }

    # Human readable
    $md_columns = $raw_response | Select-Object -Property Name, Description, CreatedBy, LastModifiedTime, RunBy
    $human_readable = TableToMarkdown $md_columns "$script:INTEGRATION_NAME - Search configurations"
    # Entry context
    $entry_context = @{
        $script:SEARCH_ENTRY_CONTEXT =  $raw_response | ForEach-Object {
            ParseSearchToEntryContext $_
        }
    }

    return $human_readable, $entry_context, $raw_response
}

function GetSearchCommand([SecurityAndComplianceClient]$client, [hashtable]$kwargs) {
    # Command arguemnts parsing
    $statistics = ConvertTo-Boolean $kwargs.statistics
    $all_results = ConvertTo-Boolean $kwargs.all_results
    $export = ConvertTo-Boolean $kwargs.export
    # Raw response
    $raw_response = $client.GetSearch($kwargs.search_name)
    # Check if raw_response is null
    if ($null -eq $raw_response) {
        # Handle the scenerio if a search is not found:
        $human_readable = "Failed to retrieve search for the name: $($kwargs.search_name)"
        $entry_context = @{
            $script:SEARCH_ENTRY_CONTEXT = @{
                "SearchStatus" = "NotFound"
                "Name" = $kwargs.search_name
            }
        }
        $raw_response = "Failed to retrieve search for the name: $($kwargs.search_name)"
        return $human_readable, $entry_context, $raw_response
    }
    # Entry context
    $entry_context = @{
        $script:SEARCH_ENTRY_CONTEXT = ParseSearchToEntryContext -search $raw_response -limit $kwargs.limit -all_results $all_results
    }
    # Human readable - Basic info
    $md_columns = $raw_response | Select-Object -Property Name, Description, CreatedBy, LastModifiedTime, RunBy, Status
    $human_readable = TableToMarkdown $md_columns  "$script:INTEGRATION_NAME - '$($kwargs.search_name)' search"
    # Human readable - Statistics
    $parsed_results = $entry_context[$script:SEARCH_ENTRY_CONTEXT].SuccessResults
    if ($parsed_results -and $statistics) {
        $human_readable += TableToMarkdown $parsed_results "Search statistics"
    }
    # Results file export
    if ($export) {
        $parsed_results_all = ParseSuccessResults -success_results $raw_response.SuccessResults -limit $kwargs.limit -all_results $all_results
        if ($parsed_results_all.Count -ne 0){
            $file_entry = FileResult "$($kwargs.search_name)_search.json" $($parsed_results_all | ConvertTo-Json) $true
        }
    }

    return $human_readable, $entry_context, $raw_response, $file_entry
}

function StartSearchCommand([SecurityAndComplianceClient]$client, [hashtable]$kwargs) {
    # Start operation doesn't return any output
    $client.StartSearch($kwargs.search_name)
    # Raw response
    $raw_response = @{}
    # Human readable
    $human_readable = "$script:INTEGRATION_NAME - search **$($kwargs.search_name)** started !"
    # Entry context
    $entry_context = @{}

    return $human_readable, $entry_context, $raw_response
}

function StopSearchCommand([SecurityAndComplianceClient]$client, [hashtable]$kwargs) {
    # Stop operation doesn't return any output
    $client.StopSearch($kwargs.search_name)
    # Raw response
    $raw_response = @{}
    # Human readable
    $human_readable = "$script:INTEGRATION_NAME - search **$($kwargs.search_name)** stopped !"
    # Entry context
    $entry_context = @{}

    return $human_readable, $entry_context, $raw_response
}

function NewSearchActionCommand([SecurityAndComplianceClient]$client, [hashtable]$kwargs) {
    # Raw response
    $raw_response = $client.NewSearchAction($kwargs.search_name, $kwargs.action, $kwargs.purge_type,
                                            $kwargs.share_point_archive_format, $kwargs.format,
                                            $kwargs.include_sharepoint_document_versions, $kwargs.notify_email,
                                            $kwargs.notify_email_cc, $kwargs.scenario, $kwargs.scope)

    if ($null -eq $raw_response) {
        # Handle the scenario if a search is not found:
        $human_readable = "Failed to retrieve search for the name: $($kwargs.search_name)"
        $entry_context = @{
            $script:SEARCH_ACTION_ENTRY_CONTEXT = @{
                "SearchStatus" = "NotFound"
                "Name" = $kwargs.search_name
            }
        }
        $raw_response = "Failed to retrieve search for the name: $($kwargs.search_name)"
        return $human_readable, $entry_context, $raw_response
    }
    
    # Human readable
    $md_columns = $raw_response | Select-Object -Property Name, SearchName, Action, LastModifiedTime, RunBy, Status
    $human_readable = TableToMarkdown $md_columns "$script:INTEGRATION_NAME - search action '$($raw_response.Name)' created"
    # Entry context
    $entry_context = @{
        $script:SEARCH_ACTION_ENTRY_CONTEXT = ParseSearchActionToEntryContext $raw_response
    }

    return $human_readable, $entry_context, $raw_response
}

function RemoveSearchActionCommand([SecurityAndComplianceClient]$client, [hashtable]$kwargs) {
    # Remove operation doesn't return any output
    $client.RemoveSearchAction($kwargs.search_action_name)
    # Raw response
    $raw_response = @{}
    # Human readable
    $human_readable = "$script:INTEGRATION_NAME - search action **$($kwargs.search_action_name)** removed!"
    # Entry context
    $entry_context = @{}

    return $human_readable, $entry_context, $raw_response
}

function GetSearchActionCommand([SecurityAndComplianceClient]$client, [hashtable]$kwargs) {
    # Command arguemnts parsing
    $results = ConvertTo-Boolean $kwargs.results
    $export = ConvertTo-Boolean $kwargs.export
    # Raw response
    $raw_response = $client.GetSearchAction($kwargs.search_action_name, $null)
    # Entry context
    $entry_context = @{
        $script:SEARCH_ACTION_ENTRY_CONTEXT = ParseSearchActionToEntryContext $raw_response $kwargs.limit
    }
    # Human readable
    $md_columns = $raw_response | Select-Object -Property Name, SearchName, Action, LastModifiedTime, RunBy, JobEndTime, Status
    $human_readable = TableToMarkdown $md_columns "$script:INTEGRATION_NAME - search action '$($kwargs.search_action_name)'"
    # Human readable - Mail results
    $parsed_results = $entry_context[$script:SEARCH_ACTION_ENTRY_CONTEXT].Results
    if ($parsed_results -and $results) {
        $human_readable += TableToMarkdown $parsed_results "Search action results"
    }
    # Results file export
    if ($export) {
        $parsed_results_all = ParseResults -results $raw_response.Results -limit $kwargs.limit
        if ($parsed_results_all.Count -ne 0){
            $file_entry = FileResult "$($kwargs.search_action_name)_search_action.json" $($parsed_results_all | ConvertTo-Json) $true
        }
    }
    return $human_readable, $entry_context, $raw_response, $file_entry
}

function ListSearchActionsCommand([SecurityAndComplianceClient]$client, [hashtable]$kwargs) {
    # Raw response
    $raw_response = $client.ListSearchActions()

    # Human readable
    $md_columns = $raw_response | Select-Object -Property Name, SearchName, Action, LastModifiedTime, RunBy, JobEndTime, Status
    $human_readable = TableToMarkdown $md_columns "$script:INTEGRATION_NAME - search actions"
    # Entry context
    $entry_context = @{
        $script:SEARCH_ACTION_ENTRY_CONTEXT = $raw_response | ForEach-Object {
            ParseSearchActionToEntryContext $_
        }
    }

    return $human_readable, $entry_context, $raw_response
}

function ComplianceCaseCreateCommand([SecurityAndComplianceClient]$client, [hashtable]$kwargs) {
    # Raw response
    $raw_response = $client.ComplianceCaseCreate($kwargs.case_name, $kwargs.case_type, $kwargs.description, $kwargs.external_id)
    $md_columns = $raw_response | Select-Object -Property Identity, Name, Status, CreatedDateTime, CaseType
    $human_readable = TableToMarkdown $md_columns "Results of $command"
    $entry_context = @{"$script:INTEGRATION_ENTRY_COMPLIANCE_CASE" = $raw_response }
    return $human_readable, $entry_context, $raw_response
}

function ComplianceCaseListCommand([SecurityAndComplianceClient]$client, [hashtable]$kwargs) {
    # Raw response
    $raw_response = $client.ComplianceCaseList($kwargs.identity, $kwargs.case_type, $kwargs.limit)
    $md_columns = $raw_response | Select-Object -Property Identity, Name, Status, CreatedDateTime, CaseType
    $human_readable = TableToMarkdown $md_columns "Results of $command"
    $entry_context = @{"$script:INTEGRATION_ENTRY_COMPLIANCE_CASE(obj.Identity === val.Identity)" = $raw_response}
    return $human_readable, $entry_context, $raw_response
}

function ComplianceCaseDeleteCommand([SecurityAndComplianceClient]$client, [hashtable]$kwargs) {
    # Raw response
    $raw_response = $client.ComplianceCaseDelete($kwargs.identity)
    # Human readable
    $human_readable = "$script:INTEGRATION_ENTRY_COMPLIANCE_CASE - Case **$($kwargs.identity)** removed!"
    # Entry context
    $entry_context = @{}
    return $human_readable, $entry_context, $raw_response
}

function CaseHoldPolicyCreateCommand([SecurityAndComplianceClient]$client, [hashtable]$kwargs) {
    $enabled = ConvertTo-Boolean $kwargs.enabled
    $exchange_location = @()
    if ($kwargs.exchange_location) {
        $exchange_location = ArgToList($kwargs.exchange_location)
    }
    $public_folder_location = @()
    if ($kwargs.public_folder_location) {
        $public_folder_location = ArgToList($kwargs.public_folder_location)
    }
    $share_point_location = @()
    if ($kwargs.share_point_location) {
        $share_point_location = ArgToList($kwargs.share_point_location)
    }
    # Raw response
    $raw_response = $client.CaseHoldPolicyCreate($kwargs.policy_name, $kwargs.case, $kwargs.comment, $exchange_location,
    $public_folder_location, $share_point_location, $enabled)
    $entry_context = @{"$script:INTEGRATION_ENTRY_CASE_HOLD_POLICY(obj.Guid === val.Guid)" = $raw_response}
    $md_columns = $raw_response | Select-Object -Property Name, Workload, Enabled, Mode, @{Name = "Guid";Expression = {$_.Guid.ToString()}}
    $human_readable = TableToMarkdown $md_columns "Results of $command"
    return $human_readable, $entry_context, $raw_response
}

function CaseHoldPolicyGetCommand([SecurityAndComplianceClient]$client, [hashtable]$kwargs) {
    if ($kwargs.Identity -And $kwargs.case) {
        return "Invlid. Include Indentity or Case. Not both."
    }
    $distribution_detail = ConvertTo-Boolean $kwargs.distribution_detail
    $include_bindings = ConvertTo-Boolean $kwargs.include_bindings
    $raw_response = $client.CaseHoldPolicyGet($kwargs.identity, $kwargs.case, $distribution_detail, $include_bindings)
    $entry_context = @{"$script:INTEGRATION_ENTRY_CASE_HOLD_POLICY(obj.Guid === val.Guid)" = $raw_response}
    $md_columns = $raw_response | Select-Object -Property Name, Workload, Status, Mode, @{Name = "Guid";Expression = {$_.Guid.ToString()}}
    $human_readable = TableToMarkdown $md_columns "Results of $command"
    return $human_readable, $entry_context, $raw_response
}

function CaseHoldPolicyDeleteCommand([SecurityAndComplianceClient]$client, [hashtable]$kwargs) {
    $force_delete = ConvertTo-Boolean $kwargs.force_delete
    $raw_response = $client.CaseHoldPolicyDelete($kwargs.identity, $force_delete)
    $human_readable = "$script:INTEGRATION_ENTRY_COMPLIANCE_CASE - Case Hold policy **$($kwargs.identity)** was removed successfully"
    $entry_context = @{}
    return $human_readable, $entry_context, $raw_response
}

function CaseHoldRuleCreateCommand([SecurityAndComplianceClient]$client, [hashtable]$kwargs) {
    $rule_name = $kwargs.rule_name
    $policy_name = $kwargs.policy_name
    $query = $kwargs.query
    $comment = $kwargs.comment
    $is_disabled = ConvertTo-Boolean $kwargs.is_disabled
    $raw_response = $client.CaseHoldRuleCreate($rule_name, $policy_name, $query, $comment, $is_disabled)
    $md_columns = $raw_response | Select-Object -Property Name, Status, Mode, @{Name = "Guid";Expression = {$_.Guid.ToString()}}
    $human_readable = TableToMarkdown $md_columns "Results of $command"
    $entry_context = @{"$script:INTEGRATION_ENTRY_CASE_HOLD_RULE(obj.Guid === val.Guid)" = $raw_response}
    return $human_readable, $entry_context, $raw_response
}

function CaseHoldRuleListCommand([SecurityAndComplianceClient]$client, [hashtable]$kwargs) {
    $identity = $kwargs.identity
    $policy = $kwargs.policy
    $limit = $kwargs.limit
    $raw_response = $client.CaseHoldRuleList($identity, $policy, $limit)
    $md_columns = $raw_response | Select-Object -Property Name, Status, Mode, @{Name = "Guid";Expression = {$_.Guid.ToString()}}
    $human_readable = TableToMarkdown $md_columns "Results of $command"
    $entry_context = @{"$script:INTEGRATION_ENTRY_CASE_HOLD_RULE(obj.Guid === val.Guid)" = $raw_response}
    return $human_readable, $entry_context, $raw_response
}

function CaseHoldRuleDeleteCommand([SecurityAndComplianceClient]$client, [hashtable]$kwargs) {
    $identity = $kwargs.identity
    $force_delete = ConvertTo-Boolean $kwargs.force_delete
    $raw_response = $client.CaseHoldRuleDelete($identity, $force_delete)
    $human_readable = "$script:INTEGRATION_ENTRY_COMPLIANCE_CASE - Case Hold rule **$($kwargs.identity)** was removed successfully"
    $entry_context = @{}
    return $human_readable, $entry_context, $raw_response
}

function CaseHoldPolicySetCommand([SecurityAndComplianceClient]$client, [hashtable]$kwargs){
    $enabled = ConvertTo-Boolean $kwargs.enabled
    $add_exchange_locations = ArgToList $kwargs.add_exchange_locations
    $add_sharepoint_locations = ArgToList $kwargs.add_sharepoint_locations
    $add_public_locations = ArgToList $kwargs.add_public_locations
    $remove_exchange_locations = ArgToList $kwargs.remove_exchange_locations
    $remove_sharepoint_locations = ArgToList $kwargs.remove_sharepoint_locations
    $remove_public_locations = ArgToList $kwargs.remove_public_locations

    $client.CaseHoldPolicySet($kwargs.identity, $enabled, $add_exchange_locations,
                                $add_sharepoint_locations, $add_public_locations, $remove_exchange_locations,
                                $remove_sharepoint_locations, $remove_public_locations, $kwargs.comment)

    $raw_response = @{}
    # Human readable
    $human_readable = "$script:INTEGRATION_NAME - case hold policy **$($kwargs.identity)** modified!"
    # Entry context
    $entry_context = @{}

    return $human_readable, $entry_context, $raw_response
}

<#
.SYNOPSIS
    Generates a short SHA-256 hash of the input string.

.DESCRIPTION
    Computes a SHA-256 hash for the input string and returns the first N characters.

.PARAMETER inputString
    The input string to hash.

.PARAMETER length
    The number of characters to return from the hash. Default is 12.

.EXAMPLE
    GetShortHash "example" 8
#>
function GetShortHash($inputString, $length = 12) {
    $bytes = [System.Text.Encoding]::UTF8.GetBytes($inputString)
    $sha256 = [System.Security.Cryptography.SHA256]::Create()
    $hashBytes = $sha256.ComputeHash($bytes)
    $hashString = -join ($hashBytes | ForEach-Object { $_.ToString("x2") })
    return $hashString.Substring(0, $length)
}

<#
.SYNOPSIS
Generate a search name based on internetMessageId and exchange locations, with optional override.

.DESCRIPTION
Creates a unique search name by cleaning the internetMessageId and appending a short hash of exchange locations unless the overrideName is provided or locations include "All".

.PARAMETER internetMessageId
The Internet Message ID string to base the search name on.

.PARAMETER exchangeLocation
An array of exchange locations used to create a hash suffix.

.PARAMETER overrideName
Optional. If provided, this name will be returned directly without modification.

.EXAMPLE
MakeSearchName "<1234@example.com>", @("user@example.com", "archive@example.com")

#>
function MakeSearchName([string]$internetMessageId, [string[]]$exchangeLocation, [string]$overrideName = $null) {
    if ($overrideName) { return $overrideName }
    $baseName = $internetMessageId -replace '[<>]', ''
    if ($exchangeLocation -notcontains "All") {
        $hash = GetShortHash ($exchangeLocation -join ",")
        return "${baseName}:${hash}"
    }
    return $baseName
}

<#
.SYNOPSIS
Handles the search status and performs actions based on its state.

.DESCRIPTION
Checks the search status ($search.Status) and takes appropriate actions:
- Starts the search if not started.
- Returns status if the search is starting.
- If completed, checks for results and manages purge actions.
- Handles different statuses of the purge action.

.PARAMETER client
The client object performing search and purge operations.

.PARAMETER search_name
The name of the search.

.PARAMETER search
The current search object.

.PARAMETER entry_context
The context data to update.

.PARAMETER polling_args
Arguments for polling operations.

.PARAMETER polling_first_run
Flag indicating if this is the first polling run.

.EXAMPLE
HandleSearchStatus $client $search_name $search $entry_context $polling_args $true
#>
function HandleSearchStatus([object]$client, [string]$search_name, [object]$search, [hashtable]$entry_context, [hashtable]$polling_args, [bool]$polling_first_run) {
    switch ($search.Status) {
        "NotStarted" {
            $client.StartSearch($search_name)
            return "$script:INTEGRATION_NAME - Search started.", $entry_context, $search, $polling_args
        }
        "Starting" {
            if ($polling_first_run) {
                return "$script:INTEGRATION_NAME - Search is already starting from a previous run.", $entry_context, $search, $null
            }
            return "", $entry_context, $search, $polling_args
        }
        "Completed" {
            $demisto.debug("Search completed, items: $($search.Items)")
            if ($search.Items -eq 0) {
                return $polling_first_run ? "$script:INTEGRATION_NAME - Search already completed with no results. Run again with force=true to retry." : "$script:INTEGRATION_NAME - No emails found.", $entry_context, $search
            }

            $action = $client.GetSearchAction("${search_name}_Purge", "SilentlyContinue")
            if (-not $action) {
                $demisto.debug("Purge action missing, creating new")
                $action = $client.NewSearchAction($search_name, "Purge", "SoftDelete", $null, $null, $null, $null, $null, $null, $null)
                return "$script:INTEGRATION_NAME - Search completed. Purge action created.", $entry_context, $action, $polling_args
            }

            $demisto.debug("Purge action status: $($action.Status)")
            switch ($action.Status) {
                "InProgress" {
                    if ($polling_first_run) {
                        return "$script:INTEGRATION_NAME - Deletion in progress from previous run. Run again with force=true to retry.", $entry_context, $action, $null
                    }
                    return "", $entry_context, $action, $polling_args
                }
                "Starting" {
                    if ($polling_first_run) {
                        return "$script:INTEGRATION_NAME - Deletion starting from previous run. Run again with force=true to retry.", $entry_context, $action, $null
                    }
                    return "", $entry_context, $action, $polling_args
                }
                "Completed"  { return $polling_first_run ? "$script:INTEGRATION_NAME - Deletion already completed. Run again with force=true to retry." : "$script:INTEGRATION_NAME - Deletion completed.", $entry_context, $action }
                default      { throw "Unhandled purge action status: $($action.Status)" }
            }
        }
        default { throw "Unhandled search status: $($search.Status)" }
    }
}

<#
.SYNOPSIS
Performs an email search and delete operation by internetMessageId.

.DESCRIPTION
Creates a new search or uses an existing one based on internetMessageId and exchange location, starts the search, and manages the search status.
If `force` is true, creates a new unique search even if one exists.
Uses `HandleSearchStatus` to handle the search state.

.PARAMETER client
Client object to perform search operations.

.PARAMETER kwargs
Command arguments including internetMessageId, exchange_location, force, and more.

.EXAMPLE
SearchAndDeleteEmailCommand $client @{ internet_message_id = "<1234@example.com>"; exchange_location = @("Research Department"); force = $true }
#>
function SearchAndDeleteEmailCommand($client, [hashtable]$kwargs) {
    $demisto.debug("Received kwargs: " + (ConvertTo-Json $kwargs -Depth 3))
    $description = "Search And Delete Email"
    $entry_context = @{}
    $exchange_location = ArgToList $kwargs.exchange_location
    $polling_first_run = ConvertTo-Boolean $kwargs.polling_first_run
    $force = ConvertTo-Boolean $kwargs.force
    $internet_message_id = $kwargs.internet_message_id
    $kql = "internetMessageId:`"$internet_message_id`""
    $polling_args = $kwargs
    $search_name = MakeSearchName $internet_message_id $exchange_location $kwargs.search_name
    $polling_args.search_name = $search_name
    $demisto.debug("search_name: $search_name")

    if ($polling_first_run) {
        $polling_args.polling_first_run = $false
        $demisto.debug("First run: create search. KQL: $kql")
        $search = $client.NewSearch($search_name, '', $kql, $description, $false, $exchange_location, @(), @(), @(), "SilentlyContinue")
        if ($search) { # $search is set only when the search didn’t exist and was just created.
            $client.StartSearch($search_name)
            return "$script:INTEGRATION_NAME - Search created & started.", $entry_context, $search, $polling_args
        }

        $demisto.debug("Search already exists")
        if ($force) {
            $random_suffix = [System.Guid]::NewGuid().ToString("N").Substring(0, 6)
            $search_name = "$search_name-$random_suffix"
            $polling_args.search_name = $search_name
            $demisto.debug("Force is true - creating new search with name: $search_name")
            $search = $client.NewSearch($search_name, '', $kql, $description, $false, $exchange_location, @(), @(), @(), $null)
            if ($search) {
                $client.StartSearch($search_name)
                return "$script:INTEGRATION_NAME - Forced search created & started.", $entry_context, $search, $polling_args
            } else {
                throw "Failed to create forced search with name: $search_name"
                }
        }
    }
    $search = $client.GetSearch($search_name)
    $status = $search.Status
    $demisto.debug("GetSearch status: $status")
    return HandleSearchStatus $client $search_name $search $entry_context $polling_args $polling_first_run
}

<#
.SYNOPSIS
Handles OAuth2 authentication commands for the Security and Compliance integration.

.DESCRIPTION
Executes the appropriate OAuth2 authentication flow based on the provided command.§

.PARAMETER command
The authentication command to execute. Supported values:
- "$script:COMMAND_PREFIX-auth-start"
- "$script:COMMAND_PREFIX-auth-complete"

.PARAMETER using_delegated
Boolean flag indicating whether interactive delegated authentication is enabled
(i.e., using UPN and password).

.PARAMETER oauth2_client
The OAuth2 client object used to perform authentication operations.

.EXAMPLE
Handle-OAuth2AuthCommand "$script:COMMAND_PREFIX-auth-start" $false $oauth2_client
#>
function Handle-OAuth2AuthCommand($command, [bool]$using_delegated, [OAuth2DeviceCodeClient]$oauth2_client) {
    if ($using_delegated) {
        throw "When using UPN and password for authentication, you don't need to run this command. Please run $script:COMMAND_PREFIX-auth-test to verify that the authentication is successful."
    }

    switch ($command) {
        "$script:COMMAND_PREFIX-auth-start"   { return StartAuthCommand $oauth2_client }
        "$script:COMMAND_PREFIX-auth-complete" { return CompleteAuthCommand $oauth2_client }
    }
}

#### INTEGRATION COMMANDS MANAGER ####

function Main {
    $command = $Demisto.GetCommand()
    $command_arguments = $Demisto.Args()
    $integration_params = $Demisto.Params()
    $upn_password = if ($integration_params.credentials_app_secret.password) {$integration_params.credentials_app_secret.password} else {$integration_params.app_secret}
    $tenant_id = if ($integration_params.credentials_tenant_id.identifier) {$integration_params.credentials_tenant_id.identifier} else {$integration_params.tenant_id}
    $app_id = if ($integration_params.credentials_app_id.identifier) {$integration_params.credentials_app_id.identifier} else {$integration_params.app_id}
    if ($integration_params.insecure -eq $true) {
        # Bypass SSL verification if insecure is true
        [System.Net.ServicePointManager]::ServerCertificateValidationCallback = {$true}
    }

    try {
        $Demisto.Debug("Command being called is $Command")

        $using_delegated = -not [string]::IsNullOrWhiteSpace($upn_password)

        $Demisto.Debug("Security and Compliance being using_delegated is $using_delegated")

        # Creating Compliance and search client
        $oauth2_client = [OAuth2DeviceCodeClient]::CreateClientFromIntegrationContext($insecure, $false,
            $app_id, $tenant_id)


        # Executing oauth2 commands
        switch ($command) {
            "$script:COMMAND_PREFIX-auth-start" {
                ($human_readable, $entry_context, $raw_response) = Handle-OAuth2AuthCommand $command $using_delegated $oauth2_client
            }
            "$script:COMMAND_PREFIX-auth-complete" {
                ($human_readable, $entry_context, $raw_response) = Handle-OAuth2AuthCommand $command $using_delegated $oauth2_client
            }
        }

        # Refreshing tokens if expired
        if (($command -ne "$script:COMMAND_PREFIX-auth-start") -and (-not $using_delegated))
        {
            $oauth2_client.RefreshTokenIfExpired()
        }


        $cs_client = [SecurityAndComplianceClient]::new(
            $oauth2_client.access_token,
            $integration_params.delegated_auth.identifier,
            $tenant_id,
            $upn_password,
            $using_delegated,
            $integration_params.connection_uri,
            $integration_params.azure_ad_authorized_endpoint_uri_base
        )

        # Executing command
        switch ($command) {
            "test-module" {
                ($human_readable, $entry_context, $raw_response) = TestModuleCommand $cs_client $using_delegated
            }
            "$script:COMMAND_PREFIX-auth-test" {
                ($human_readable, $entry_context, $raw_response) = TestAuthCommand $oauth2_client $cs_client $using_delegated
            }
            "$script:COMMAND_PREFIX-new-search" {
                ($human_readable, $entry_context, $raw_response) = NewSearchCommand $cs_client $command_arguments
            }
            "$script:COMMAND_PREFIX-set-search" {
                ($human_readable, $entry_context, $raw_response) = SetSearchCommand $cs_client $command_arguments
            }
            "$script:COMMAND_PREFIX-remove-search" {
                ($human_readable, $entry_context, $raw_response) = RemoveSearchCommand $cs_client $command_arguments
            }
            "$script:COMMAND_PREFIX-list-search" {
                ($human_readable, $entry_context, $raw_response) = ListSearchCommand $cs_client $command_arguments
            }
            "$script:COMMAND_PREFIX-get-search" {
                ($human_readable, $entry_context, $raw_response, $file_entry) = GetSearchCommand $cs_client $command_arguments
            }
            "$script:COMMAND_PREFIX-start-search" {
                ($human_readable, $entry_context, $raw_response) = StartSearchCommand $cs_client $command_arguments
            }
            "$script:COMMAND_PREFIX-stop-search" {
                ($human_readable, $entry_context, $raw_response) = StopSearchCommand $cs_client $command_arguments
            }
            "$script:COMMAND_PREFIX-new-search-action" {
                ($human_readable, $entry_context, $raw_response) = NewSearchActionCommand $cs_client $command_arguments
            }
            "$script:COMMAND_PREFIX-remove-search-action" {
                ($human_readable, $entry_context, $raw_response) = RemoveSearchActionCommand $cs_client $command_arguments
            }
            "$script:COMMAND_PREFIX-list-search-action" {
                ($human_readable, $entry_context, $raw_response) = ListSearchActionsCommand $cs_client $command_arguments
            }
            "$script:COMMAND_PREFIX-get-search-action" {
                ($human_readable, $entry_context, $raw_response, $file_entry) = GetSearchActionCommand $cs_client $command_arguments
            }
            "$script:COMMAND_PREFIX-compliance-case-create" {
                ($human_readable, $entry_context, $raw_response, $file_entry) = ComplianceCaseCreateCommand $cs_client $command_arguments
            }
            "$script:COMMAND_PREFIX-compliance-case-list" {
                ($human_readable, $entry_context, $raw_response, $file_entry) = ComplianceCaseListCommand $cs_client $command_arguments
            }
            "$script:COMMAND_PREFIX-compliance-case-delete" {
                ($human_readable, $entry_context, $raw_response, $file_entry) = ComplianceCaseDeleteCommand $cs_client $command_arguments
            }
            "$script:COMMAND_PREFIX-case-hold-policy-create" {
                ($human_readable, $entry_context, $raw_response, $file_entry) = CaseHoldPolicyCreateCommand $cs_client $command_arguments
            }
            "$script:COMMAND_PREFIX-case-hold-policy-get" {
                ($human_readable, $entry_context, $raw_response, $file_entry) = CaseHoldPolicyGetCommand $cs_client $command_arguments
            }
            "$script:COMMAND_PREFIX-case-hold-policy-delete" {
                ($human_readable, $entry_context, $raw_response, $file_entry) = CaseHoldPolicyDeleteCommand $cs_client $command_arguments
            }
            "$script:COMMAND_PREFIX-case-hold-rule-create" {
                ($human_readable, $entry_context, $raw_response, $file_entry) = CaseHoldRuleCreateCommand $cs_client $command_arguments
            }
            "$script:COMMAND_PREFIX-case-hold-rule-list" {
                ($human_readable, $entry_context, $raw_response, $file_entry) = CaseHoldRuleListCommand $cs_client $command_arguments
            }
            "$script:COMMAND_PREFIX-case-hold-rule-delete" {
                ($human_readable, $entry_context, $raw_response, $file_entry) = CaseHoldRuleDeleteCommand $cs_client $command_arguments
            }
            "$script:COMMAND_PREFIX-case-hold-policy-set" {
                ($human_readable, $entry_context, $raw_response) = CaseHoldPolicySetCommand $cs_client $command_arguments
            }
            "$script:COMMAND_PREFIX-email-security-search-and-delete-email-office-365-quick-action" {
                ($human_readable, $entry_context, $raw_response, $polling_args) = SearchAndDeleteEmailCommand $cs_client $command_arguments
            }
        }

        # Updating integration context if access token changed
        UpdateIntegrationContext $oauth2_client

        # Return results to Demisto Server
        # $Demisto.results("polling_args: " + (ConvertTo-Json $polling_args -Depth 3))
        if ($polling_args) {
            ReturnPollingOutputs `
                -ReadableOutput $human_readable `
                -Outputs $entry_context `
                -RawResponse $raw_response `
                -CommandName $command `
                -PollingArgs $polling_args `
                -RemoveSelfRefs $true `
                -NextRun "30" `
                -Timeout "3600" | Out-Null
        }
        else {
            ReturnOutputs $human_readable $entry_context $raw_response | Out-Null
        }
        if ($file_entry) {
            $Demisto.results($file_entry)
        }
    } catch {
        $Demisto.debug("Integration: $script:INTEGRATION_NAME
Command: $command
Arguments: $($command_arguments | ConvertTo-Json)
Error: $($_.Exception.Message)")
        if ($_.Exception.Message -like "*Unable to open a web page using xdg-open*" ) {
           Write-Host "It looks like the access token has expired. Please run the command !$script:COMMAND_PREFIX-auth-start, before running this command."
        } elseif ($command -ne "test-module") {
            ReturnError "Error:
            Integration: $script:INTEGRATION_NAME
            Command: $command
            Arguments: $($command_arguments | ConvertTo-Json)
            Error: $($_.Exception)" | Out-Null
        } else {
            ReturnError $_.Exception.Message
        }
    }
}

# Execute Main when not in Tests
if ($MyInvocation.ScriptName -notlike "*.tests.ps1" -AND -NOT $Test) {
    Main
}