EWS v2

Exchange Web Services and Office 365 (mail).

Email · Microsoft Exchange On-Premise

Details

IDEWS v2
ProviderMicrosoft
CategoryEmail
From Version5.0.0
Docker Imagedemisto/py-ews:5.6.0.12072219
Supported ModulesAgentix Cloud Runtime Security Cloud Posture Security XSIAM EDR Cortex Cloud

README

Exchange Web Services (EWS) provides the functionality to enable client applications to communicate with the Exchange server. EWS provides access to much of the same data that is made available through Microsoft Office Outlook.

The EWS v2 integration implants EWS leading services. The integration allows getting information on emails and activities in a target mailbox, and some active operations on the mailbox such as deleting emails and attachments or moving emails from folder to folder.

Note: Starting from pack version 2.0.0 the EWS v2 integration requires the Exchange server to support TLS v1.2 and up in order to connect.

Multi-Factor Authentication (MFA)
EWS v2 does not support Multi-Factor Authentication (MFA).

If using MFA, use EWS O365 (see https://xsoar.pan.dev/docs/reference/integrations/ewso365)

or if you have Graph Outlook use O365 Outlook Mail (Using Graph API) (see https://xsoar.pan.dev/docs/reference/integrations/microsoft-graph-mail)

or O365 Outlook Mail Single User (Using Graph API) (see https://xsoar.pan.dev/docs/reference/integrations/microsoft-graph-mail-single-user).

EWS v2 Playbooks

  • Office 365 Search and Delete
  • Search And Delete Emails - EWS
  • Get Original Email - EWS
  • Process Email - EWS

Use Cases

The EWS integration can be used for the following use cases:

  • Monitor a specific email account and create incidents from incoming emails to the defined folder.
    Follow the instructions in the Fetched Incidents Data section.
  • Search for an email message across mailboxes and folders.
    This can be achieved in the following ways:
    • Use the ews-search-mailboxes command to search for all emails in a specific scope of mailboxes. Use the filter argument to narrow the search for emails sent from a specific account and more.
    • Use the ews-search-mailbox command to search for all emails in a specific folder within the target mailbox. Use the query argument to narrow the search for emails sent from a specific account and more.

    Both of these commands retrieve the ItemID field for each email item listed in the results. TheItemID can be used in the ews-get-items command in order to get more information about the email item itself.
    For instance, use the ews-search-mailboxes command to hunt for emails that were marked as malicious in prior investigations, across organization mailboxes. Focus your hunt on emails sent from a specific mail account, emails with a specific subject and more.

  • Get email attachment information. Use the ews-get-attachment command to retrieve information on one attachment or all attachments of a message at once. It supports both file attachments and item attachments (e.g., email messages).
  • Delete email items from a mailbox. First, make sure you obtain the email item ID. The item ID can be obtained with one of the integration’s search commands. Use the ews-delete-items command to delete one or more items from the target mailbox in a single action. A less common use case is to remove emails that were marked as malicious from a user’s mailbox. You can delete the items permanently (hard delete), or delete the items (soft delete), so they can be recovered by running the ews-recover-messages command.
  • Send notifications to external users.
  • Send an email asking for a response to be returned as part of a playbook. See Receiving an email reply

Configure EWS v2 in Cortex

Parameter Required
Email address True
Password True
Email address from which to fetch incidents True
Name of the folder from which to fetch incidents (supports Exchange Folder ID and sub-folders e.g. Inbox/Phishing) True
Public Folder False
Has impersonation rights False
Use system proxy settings False
Fetch incidents False
First fetch timestamp (<number> <time unit>, e.g., 12 hours, 7 days) False
Mark fetched emails as read False
Incident type False
┉┉┉┉┉┉┉┉┉┉┉┉┉┉┉┉┉┉┉┉┉┉
‎ Manual Mode
Exchange Server Hostname or IP address
False
DOMAIN\USERNAME (e.g. DEMISTO.INT\admin) False
Exchange Server Version (On-Premise only. Supported versions: 2007, 2010, 2010_SP2, 2013, 2016, and 2019) False
Trust any certificate (not secure) False
┉┉┉┉┉┉┉┉┉┉┉┉┉┉┉┉┉┉┉┉┉┉
‎ Advanced Mode
Override Authentication Type (NTLM, Basic, or Digest).
False
Timeout (in seconds) for HTTP requests to Exchange Server False
Max incidents per fetch False
Run as a separate process (protects against memory depletion) False
Skip unparsable emails during fetch incidents False

Fetched Incidents Data

The integration imports email messages from the destination folder in the target mailbox as incidents. If the message contains any attachments, they are uploaded to the War Room as files. If the attachment is an email, Cortex XSOAR fetches information about the attached email and downloads all of its attachments (if there are any) as files.

To use Fetch incidents, configure a new instance and select the Fetches incidents option in the instance settings.

IMPORTANT: The initial fetch interval is the previous 10 minutes. If no emails were fetched before from the destination folder, all emails from 10 minutes prior to the instance configuration and up to the current time will be fetched. Additionally, moving messages manually to the destination folder will not trigger a fetch incident. Define rules on phishing/target mailbox instead of moving messages manually.

You can configure the First fetch timestamp field to determine how much time back you want to fetch incidents.

Notice that it might require you to set the Timeout field to a higher value.

Pay special attention to the following fields in the instance settings:

  • Email address from which to fetch incidents – mailbox to fetch incidents from.

  • Name of the folder from which to fetch incidents – use this field to configure the destination folder from where emails should be fetched. The default is Inbox folder. Please note, if Exchange is configured with an international flavor, Inbox will be named according to the configured language.

  • Has impersonation rights – mark this option if you set the target mailbox to an account different than your personal account. Otherwise Delegation access will be used instead of Impersonation.
    Find more information on impersonation or delegation rights in the Additional Information section.

Commands

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

ews-get-attachment


Retrieves the actual attachments from an item (email message). To get all attachments for a message, only specify the item-id argument.

Base Command

ews-get-attachment

Input

Argument Name Description Required
item-id The ID of the email message for which to get the attachments. Required
target-mailbox The mailbox in which this attachment was found. If empty, the default mailbox is used. Otherwise the user might require impersonation rights to this mailbox. Optional
attachment-ids The attachments ids to get. If none - all attachments will be retrieve from the message. Support multiple attachments with comma-separated value or array. Optional

Context Output

Path Type Description
EWS.Items.FileAttachments.attachmentId string The attachment ID. Used for file attachments only.
EWS.Items.FileAttachments.attachmentName string The attachment name. Used for file attachments only.
EWS.Items.FileAttachments.attachmentSHA256 string The SHA256 hash of the attached file.
EWS.Items.FileAttachments.attachmentLastModifiedTime date The attachment last modified time. Used for file attachments only.
EWS.Items.ItemAttachments.datetimeCreated date The created time of the attached email.
EWS.Items.ItemAttachments.datetimeReceived date The received time of the attached email.
EWS.Items.ItemAttachments.datetimeSent date The sent time of the attached email.
EWS.Items.ItemAttachments.receivedBy string The received by address of the attached email.
EWS.Items.ItemAttachments.subject string The subject of the attached email.
EWS.Items.ItemAttachments.textBody string The body of the attached email (as text).
EWS.Items.ItemAttachments.headers Unknown The headers of the attached email.
EWS.Items.ItemAttachments.hasAttachments boolean Whether the attached email has attachments.
EWS.Items.ItemAttachments.itemId string The attached email item ID.
EWS.Items.ItemAttachments.toRecipients Unknown A list of recipient email addresses for the attached email.
EWS.Items.ItemAttachments.body string The body of the attached email (as HTML).
EWS.Items.ItemAttachments.attachmentSHA256 string The SHA256 hash of the attached email (as EML file).
EWS.Items.ItemAttachments.FileAttachments.attachmentSHA256 string SHA256 hash of the attached files inside of the attached email.
EWS.Items.ItemAttachments.ItemAttachments.attachmentSHA256 string SHA256 hash of the attached emails inside of the attached email.
EWS.Items.ItemAttachments.isRead String The read status of the attachment.

Command Example

!ews-get-attachment item-id=BBFDShfdafFSDF3FADR3434DFASDFADAFDADFADFCJebinpkUAAAfxuiVAAA= target-mailbox=test@demistodev.onmicrosoft.com

Context Example

{
  "EWS": {
    "Items": {
      "ItemAttachments": {
        "originalItemId": "BBFDShfdafFSDF3FADR3434DFASDFADAFDADFADFCJebinpkUAAAfxuiVAAA=",
        "attachmentSize": 2956,
        "receivedBy": "test@demistodev.onmicrosoft.com",
        "size": 28852,
        "author": "test2@demistodev.onmicrosoft.com",
        "attachmentLastModifiedTime": "2019-08-11T15:01:30+00:00",
        "subject": "Moving Email between mailboxes",
        "body": "Some text inside",
        "datetimeCreated": "2019-08-11T15:01:47Z",
        "importance": "Normal",
        "attachmentType": "ItemAttachment",
        "toRecipients": [
          "test@demistodev.onmicrosoft.com"
        ],
        "mailbox": "test@demistodev.onmicrosoft.com",
        "isRead": false,
        "attachmentIsInline": false,
        "datetimeSent": "2019-08-07T12:50:19Z",
        "lastModifiedTime": "2019-08-11T15:01:30Z",
        "sender": "test2@demistodev.onmicrosoft.com",
        "attachmentName": "Moving Email between mailboxes",
        "datetimeReceived": "2019-08-07T12:50:20Z",
        "attachmentSHA256": "119e27b28dc81bdfd4f498d44bd7a6d553a74ee03bdc83e6255a53",
        "hasAttachments": false,
        "headers": [
          {
            "name": "Subject",
            "value": "Moving Email between mailboxes"
          }
        ],
        "attachmentId": "BBFDShfdafFSDF3FADR3434DFASDFADAFDADFADFCJebinpkUAAAfxuiVAAABEgAQAOpEfpzDB4dFkZ+/K4XSj44=",
        "messageId": "&lt;message_id&gt;"
      }
    }
  }
}

ews-delete-attachment


Deletes the attachments of an item (email message).

Base Command

ews-delete-attachment

Input

Argument Name Description Required
item-id The ID of the email message for which to delete attachments. Required
target-mailbox The mailbox in which this attachment was found. If empty, the default mailbox is used. Otherwise the user might require impersonation rights to this mailbox. Optional
attachment-ids A CSV list (or array) of attachment IDs to delete. If empty, all attachments will be deleted from the message. Optional

Context Output

Path Type Description
EWS.Items.FileAttachments.attachmentId string The ID of the deleted attachment, in case of file attachment.
EWS.Items.ItemAttachments.attachmentId string The ID of the deleted attachment, in case of other attachment (for example, “email”).
EWS.Items.FileAttachments.action string The deletion action in case of file attachment. This is a constant value: ‘deleted’.
EWS.Items.ItemAttachments.action string The deletion action in case of other attachment (for example, “email”). This is a constant value: ‘deleted’.

Command Example

!ews-delete-attachment item-id=AAMkADQ0NmwBGAAAAAAA4kxh+ed3JTJPMPXU3wX3aBwCyyVyFtlsUQZfBJjfaljfAFDVSDinpkUAAAfxxd9AAA= target-mailbox=test@demistodev.onmicrosoft.com

Human Readable Output

action attachmentId
deleted AAMkADQ0NmwBGAAAAAAA4kxh+ed3JTJPMPXU3wX3aBwCyyVyFtlsUQZfBJjfaljfAFDVSDinpkUAAAfxxd9AAABEgAQAIUht2vrOdErec33=

Context Example

{
    "EWS": {
        "Items": {
            "FileAttachments": {
                "action": "deleted",
                "attachmentId": "AAMkADQ0NmwBGAAAAAAA4kxh+ed3JTJPMPXU3wX3aBwCyyVyFtlsUQZfBJjfaljfAFDVSDinpkUAAAfxxd9AAABEgAQAIUht2vrOdErec33="
            }
        }
    }
}

ews-get-searchable-mailboxes


Returns a list of searchable mailboxes. This command requires eDiscovery permissions to the Exchange Server. For more information, see the EWSv2 integration documentation.

Base Command

ews-get-searchable-mailboxes

Input

There are no input arguments for this command.

Context Output

Path Type Description
EWS.Mailboxes.mailbox string Addresses of the searchable mailboxes.
EWS.Mailboxes.mailboxId string IDs of the searchable mailboxes.
EWS.Mailboxes.displayName string The email display name.
EWS.Mailboxes.isExternal boolean Whether the mailbox is external.
EWS.Mailboxes.externalEmailAddress string The external email address.

Command Example


#### Human Readable Output

>|displayName|isExternal|mailbox|mailboxId|
>|---|---|---|--- |
>| test | false |<test@demistodev.onmicrosoft.com> | /o=Exchange***/ou=Exchange Administrative Group ()/cn=**/cn=**-**|

#### Context Example

```json
{
    "EWS": {
        "Mailboxes": [
            {
                "mailbox": "test@demistodev.onmicrosoft.com", 
                "displayName": "test", 
                "mailboxId": "/o=Exchange***/ou=Exchange Administrative Group ()/cn=**/cn=**-**", 
                "isExternal": "false"
            }
        ]
    }
}

ews-search-mailboxes


Searches over multiple mailboxes or all Exchange mailboxes. Use either the mailbox-search-scope command or the email-addresses command to search specific mailboxes. This command requires eDiscovery permissions to the Exchange Server. For more information, see the EWS v2 integration documentation.

The number of mailboxes to search in may be limited by Microsoft Exchange. See here for more information.

Base Command

ews-search-mailboxes

Input

Argument Name Description Required
filter The filter query to search. Required
mailbox-search-scope The mailbox IDs to search. If empty, all mailboxes are searched. Optional
limit Maximum number of results to return. Default is 250. Optional
email_addresses CSV list or array of email addresses. Optional

Context Output

Path Type Description
EWS.Items.itemId string The item ID.
EWS.Items.mailbox string The mailbox address where the item was found.
EWS.Items.subject string The subject of the email.
EWS.Items.toRecipients Unknown List of recipient email addresses.
EWS.Items.sender string Sender email address.
EWS.Items.hasAttachments boolean Whether the email has attachments?
EWS.Items.datetimeSent date Sent time of the email.
EWS.Items.datetimeReceived date Received time of the email.

Command Example

!ews-search-mailboxes filter="subject:Test" limit=1

Human Readable Output

datetimeReceived datetimeSent hasAttachments itemId mailbox sender subject toRecipients  
2019-08-11T11:00:28Z 2019-08-11T11:00:28Z false AAMkAGY3OTQyMzMzLWYxNjktNDE0My05NmZhLWQ5MGY1YjIyNzBkNABGACASFAACYCKjWAnXDFrfsdhdnfkanpAAA= test2@demistodev.onmicrosoft.com John Smith test report dem@demistodev.onmicrosoft.com  

Context Example

{
    "EWS": {
        "Items": {
            "itemId": "AAMkAGY3OTQyMzMzLWYxNjktNDE0My05NmZhLWQ5MGY1YjIyNzBkNABGACASFAACYCKjWAnXDFrfsdhdnfkanpAAA=", 
            "sender": "John Smith", 
            "datetimeReceived": "2019-08-11T11:00:28Z", 
            "hasAttachments": "false", 
            "toRecipients": [
                "dem@demistodev.onmicrosoft.com"
            ], 
            "mailbox": "test2@demistodev.onmicrosoft.com", 
            "datetimeSent": "2019-08-11T11:00:28Z", 
            "subject": "test report "
        }
    }
}

ews-move-item


Move an item to different folder in the mailbox.

Base Command

ews-move-item

Input

Argument Name Description Required
item-id The ID of the item to move. Required
target-folder-path The path to the folder to which to move the item. Complex paths are supported, for example, “Inbox\Phishing”. Required
target-mailbox The mailbox on which to run the command. Optional
is-public Whether the target folder is a public folder. Possible values are: True, False. Optional

Context Output

Path Type Description
EWS.Items.newItemID string The item ID after move.
EWS.Items.messageID string The item message ID.
EWS.Items.itemId string The original item ID.
EWS.Items.action string The action taken. The value will be “moved”.

ews-delete-items


Delete items from mailbox. This command requires eDiscovery permissions to the Exchange Server. For more information, see the EWSv2 integration documentation.

Base Command

ews-delete-items

Input

Argument Name Description Required
item-ids The item IDs to delete. Required
delete-type Deletion type. Can be “trash”, “soft”, or “hard”. Default is soft. Required
target-mailbox The mailbox on which to run the command. Optional

Context Output

Path Type Description
EWS.Items.itemId string The deleted item ID.
EWS.Items.messageId string The deleted message ID.
EWS.Items.action string The deletion action. Can be ‘trash-deleted’, ‘soft-deleted’, or ‘hard-deleted’.

Command Example

!ews-delete-items item-ids=VWAFA3hmZjdmNTZjNTMxNwBGAAAAAAA4kxh+ed3JTJPMPXU3wX3aBwCyyVyFtlsUQZfBJebinpkUAAABjKMGAACyw+kAAA= delete-type=soft target-mailbox=test@demistodev.onmicrosoft.com

Human Readable Output

action itemId messageId
soft-deleted VWAFA3hmZjdmNTZjNTMxNwBGAAAAAAA4kxh+ed3JTJPMPXU3wX3aBwCyyVyFtlsUQZfBJebinpkUAAABjKMGAACyw+kAAA=

Context Example

{
    "EWS": {
        "Items": {
            "action": "soft-deleted", 
            "itemId": "VWAFA3hmZjdmNTZjNTMxNwBGAAAAAAA4kxh+ed3JTJPMPXU3wX3aBwCyyVyFtlsUQZfBJebinpkUAAABjKMGAACyw+kAAA=", 
            "messageId": "&lt;messaage_id&gt;"
        }
    }
}

ews-search-mailbox


Searches for items in the specified mailbox. Specific permissions are needed for this operation to search in a target mailbox other than the default.

Base Command

ews-search-mailbox

Input

Argument Name Description Required
query The search query string. For more information about the query syntax, see the Microsoft documentation: https://msdn.microsoft.com/en-us/library/ee693615.aspx. Optional
folder-path The folder path in which to search. If empty, searches all the folders in the mailbox. Optional
limit Maximum number of results to return. Default is 100. Optional
target-mailbox The mailbox on which to apply the search. Optional
is-public Whether the folder is a Public Folder?. Possible values are: True, False. Optional
message-id The message ID of the email. This will be ignored if a query argument is provided. Optional
selected-fields A CSV list of fields to retrieve. Possible values are: . Default is all. Optional
surround_id_with_angle_brackets Whether to surround the message ID with angle brackets (<>) if it does not exist. Default is ‘True’. Optional

Context Output

Path Type Description
EWS.Items.itemId string The email item ID.
EWS.Items.hasAttachments boolean Whether the email has attachments.
EWS.Items.datetimeReceived date Received time of the email.
EWS.Items.datetimeSent date Sent time of the email.
EWS.Items.headers Unknown Email headers (list).
EWS.Items.sender string Sender email address of the email.
EWS.Items.subject string Subject of the email.
EWS.Items.textBody string Body of the email (as text).
EWS.Items.size number Email size.
EWS.Items.toRecipients Unknown List of email recipients addresses.
EWS.Items.receivedBy Unknown Email received by address.
EWS.Items.messageId string Email message ID.
EWS.Items.body string Body of the email (as HTML).
EWS.Items.FileAttachments.attachmentId unknown Attachment ID of the file attachment.
EWS.Items.ItemAttachments.attachmentId unknown Attachment ID of the item attachment.
EWS.Items.FileAttachments.attachmentName unknown Attachment name of the file attachment.
EWS.Items.ItemAttachments.attachmentName unknown Attachment name of the item attachment.
EWS.Items.isRead String The read status of the email.

Command Example

!ews-search-mailbox query="subject:"Get Attachment Email" target-mailbox=test@demistodev.onmicrosoft.com limit=1

Human Readable Output

sender subject hasAttachments datetimeReceived receivedBy author toRecipients
test2@demistodev.onmicrosoft.com Get Attachment Email true 2019-08-11T10:57:37Z test@demistodev.onmicrosoft.com test2@demistodev.onmicrosoft.com test@demistodev.onmicrosoft.com

Context Example

{
    "EWS": {
        "Items": {
            "body": "&lt;html&gt;\r\n&lt;head&gt;\r\n&lt;meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\"&gt;\r\n&lt;style type=\"text/css\" style=\"display:none;\"&gt;&lt;!-- P {margin-top:0;margin-bottom:0;} --&gt;&lt;/style&gt;\r\n&lt;/head&gt;\r\n&lt;body dir=\"ltr\"&gt;\r\n&lt;div id=\"divtagrapper\" style=\"font-size:12pt;color:#000000;font-family:Calibri,Helvetica,sans-serif;\" dir=\"ltr\"&gt;\r\n&lt;p style=\"margin-top:0;margin-bottom:0\"&gt;Some text inside email&lt;/p&gt;\r\n&lt;/div&gt;\r\n&lt;/body&gt;\r\n&lt;/html&gt;\r\n", 
            "itemId": "AAMkADQ0NmFFijer3FFmNTZjNTMxNwBGAAAAAAFSAAfxw+jAAA=", 
            "toRecipients": [
                "test@demistodev.onmicrosoft.com"
            ], 
            "datetimeCreated": "2019-08-11T10:57:37Z", 
            "datetimeReceived": "2019-08-11T10:57:37Z", 
            "author": "test2@demistodev.onmicrosoft.com", 
            "hasAttachments": true, 
            "size": 30455, 
            "subject": "Get Attachment Email", 
            "FileAttachments": [
                {
                    "attachmentName": "atta1.rtf", 
                    "attachmentSHA256": "csfd81097bc049fbcff6e637ade0407a00308bfdfa339e31a44a1c4e98f28ce36e4f", 
                    "attachmentType": "FileAttachment", 
                    "attachmentSize": 555, 
                    "attachmentId": "AAMkADQ0NmFkODFkLWQ4MDEtNDE4Mi1hN2NkLThmZjdmNTZjNTMxNwBGAAAAAAA4kxh+ed3JTJPMPXU3wX3aBwCyyVyFtlsUQZfBJebinpkUAAABjKMGAACyyVyFtlsUQZfBJebinpkUAAAfxw+jAAABEgAQAEyq1TB2nKBLpKUiFUJ5Geg=", 
                    "attachmentIsInline": false, 
                    "attachmentLastModifiedTime": "2019-08-11T11:06:02+00:00", 
                    "attachmentContentLocation": null, 
                    "attachmentContentType": "text/rtf", 
                    "originalItemId": "AAMkADQ0NmFFijer3FFmNTZjNTMxNwBGAAAAAAFSAAfxw+jAAA=", 
                    "attachmentContentId": null
                }
            ], 
            "headers": [
                {
                    "name": "Subject", 
                    "value": "Get Attachment Email"
                }
            ], 
            "isRead": true, 
            "messageId": "&lt;mesage_id&gt;", 
            "receivedBy": "test@demistodev.onmicrosoft.com", 
            "datetimeSent": "2019-08-11T10:57:36Z", 
            "lastModifiedTime": "2019-08-11T11:13:59Z", 
            "mailbox": "test@demistodev.onmicrosoft.com", 
            "importance": "Normal", 
            "textBody": "Some text inside email\r\n", 
            "sender": "test2@demistodev.onmicrosoft.com"
        }
    }
}

ews-get-contacts


Retrieves contacts for a specified mailbox.

Base Command

ews-get-contacts

Input

Argument Name Description Required
target-mailbox The mailbox for which to retrieve the contacts. Optional
limit Maximum number of results to return. Default is 100. Optional

Context Output

Path Type Description
Account.Email.EwsContacts.displayName Unknown The contact name.
Account.Email.EwsContacts.lastModifiedTime Unknown The time that the contact was last modified.
Account.Email.EwsContacts.emailAddresses Unknown Phone numbers of the contact.
Account.Email.EwsContacts.physicalAddresses Unknown Physical addresses of the contact.
Account.Email.EwsContacts.phoneNumbers.phoneNumber Unknown Email addresses of the contact.

Command Example

!ews-get-contacts limit="1"

Human Readable Output

changekey culture datetimeCreated datetimeReceived datetimeSent displayName emailAddresses fileAs fileAsMapping givenName id importance itemClass lastModifiedName lastModifiedTime postalAddressIndex sensitivity subject uniqueBody webClientReadFormQueryString
EABYACAADcsxRwRjq/zTrN6vWSzKAK1Dl3N en-US 2019-08-05T12:35:36Z 2019-08-05T12:35:36Z 2019-08-05T12:35:36Z Contact Name some@dev.microsoft.com Contact Name LastCommaFirst Contact Name AHSNNK3NQNcasnc3SAS/zTrN6vWSzK4OWAAAAAAEOAADrxRwRjq/zTrNFSsfsfVWAAK1KsF3AAA= Normal IPM.Contact John Smith 2019-08-05T12:35:36Z None Normal Contact Name   https://outlook.office365.com/owa/?ItemID=***

Context Example

{
    "Account.Email": [
        {
            "itemClass": "IPM.Contact", 
            "lastModifiedName": "John Smith", 
            "displayName": "Contact Name", 
            "datetimeCreated": "2019-08-05T12:35:36Z", 
            "datetimeReceived": "2019-08-05T12:35:36Z", 
            "fileAsMapping": "LastCommaFirst", 
            "importance": "Normal", 
            "sensitivity": "Normal", 
            "postalAddressIndex": "None", 
            "webClientReadFormQueryString": "https://outlook.office365.com/owa/?ItemID=***", 
            "uniqueBody": "&lt;html&gt;&lt;body&gt;&lt;/body&gt;&lt;/html&gt;", 
            "fileAs": "Contact Name", 
            "culture": "en-US", 
            "changekey": "EABYACAADcsxRwRjq/zTrN6vWSzKAK1Dl3N", 
            "lastModifiedTime": "2019-08-05T12:35:36Z", 
            "datetimeSent": "2019-08-05T12:35:36Z", 
            "emailAddresses": [
                "some@dev.microsoft.com"
            ], 
            "givenName": "Contact Name", 
            "id": "AHSNNK3NQNcasnc3SAS/zTrN6vWSzK4OWAAAAAAEOAADrxRwRjq/zTrNFSsfsfVWAAK1KsF3AAA=", 
            "subject": "Contact Name"
        }
    ]
}

ews-resolve-name


This operation verifies aliases and matches display names to the correct mailbox user. It handles one ambiguous name at a time. If there are multiple potential matches, all will be returned, but limited to a maximum of 100 candidates.

Base Command

ews-resolve-name

Input

Argument Name Description Required
identifier The text value of this argument is used to resolve names against the following fields: First name, Last name, Display name, Full name, Office, Alias, SMTP address. Eg. John Doe or sip:johndoe@example.com. Required
full-contact-data Describes whether the full contact details for public contacts for a resolved name are returned. Possible values are: True, False. Optional

Context Output

Path Type Description
EWS.ResolvedNames.FullContactInfo.contactSource String Whether the contact is located in the Exchange store or Active Directory Domain Services (AD DS).
EWS.ResolvedNames.FullContactInfo.culture String Represents the culture for a given item in a mailbox.
EWS.ResolvedNames.FullContactInfo.displayName String The display name of a contact.
EWS.ResolvedNames.FullContactInfo.ItemId String Contains the unique identifier and change key of an item in the Exchange store.
EWS.ResolvedNames.FullContactInfo.emailAddresses String Represents a collection of email addresses for a contact.
EWS.ResolvedNames.FullContactInfo.givenName String Contains a contact’s given name.
EWS.ResolvedNames.FullContactInfo.importance String Describes the importance of an item.
EWS.ResolvedNames.FullContactInfo.initials String Represents the initials of a contact.
EWS.ResolvedNames.FullContactInfo.phoneNumbers.label String The following are the possible values for this attribute: AssistantPhone, BusinessFax, BusinessPhone, BusinessPhone2, Callback, CarPhone, CompanyMainPhone, HomeFax, HomePhone, HomePhone2, Isdn, MobilePhone, OtherFax, OtherTelephone, Pager, PrimaryPhone, RadioPhone, Telex, TtyTddPhone
EWS.ResolvedNames.FullContactInfo.phoneNumbers.phoneNumber String The phone number of the contact
EWS.ResolvedNames.FullContactInfo.physicalAddresses.city String The physical addresses city associated with the contact.
EWS.ResolvedNames.FullContactInfo.physicalAddresses.country String The physical addresses country associated with the contact.
EWS.ResolvedNames.FullContactInfo.physicalAddresses.label String The physical addresses label associated with the contact.
EWS.ResolvedNames.FullContactInfo.physicalAddresses.state String The physical addresses state associated with the contact.
EWS.ResolvedNames.FullContactInfo.physicalAddresses.street String The physical addresses street associated with the contact.
EWS.ResolvedNames.FullContactInfo.physicalAddresses.zipcode String The physical addresses zipcode associated with the contact.
EWS.ResolvedNames.FullContactInfo.postalAddressIndex String Represents the display types for physical addresses.
EWS.ResolvedNames.FullContactInfo.sensitivity String Indicates the sensitivity level of an item.
EWS.ResolvedNames.email_address String The primary SMTP address of a mailbox user.
EWS.ResolvedNames.mailbox_type String The type of mailbox that is represented by the email address.
EWS.ResolvedNames.name String The name of a mailbox user.
EWS.ResolvedNames.routing_type String The address type for the mailbox

Command example

!ews-resolve-name identifier=`example@example.com` full-contact-data=True

Context Example

{
    "EWS": {
        "ResolvedNames": {
            "FullContactInfo": {
                "contactSource": "ActiveDirectory",
                "culture": "en-US",
                "displayName": "ews-2016-test EW2016.",
                "emailAddresses": [
                    "example-sec@example.com",
                    "example@example.com"
                ],
                "givenName": "ews-2016-test",
                "importance": "Normal",
                "initials": "EW2016",
                "phoneNumbers": [
                    {
                        "label": "AssistantPhone",
                        "phoneNumber": null
                    },
                    {
                        "label": "BusinessFax",
                        "phoneNumber": null
                    },
                    {
                        "label": "BusinessPhone",
                        "phoneNumber": null
                    },
                    {
                        "label": "HomePhone",
                        "phoneNumber": null
                    },
                    {
                        "label": "MobilePhone",
                        "phoneNumber": null
                    },
                    {
                        "label": "Pager",
                        "phoneNumber": null
                    }
                ],
                "physicalAddresses": [
                    {
                        "city": null,
                        "country": null,
                        "label": "Business",
                        "state": null,
                        "street": null,
                        "zipcode": null
                    }
                ],
                "postalAddressIndex": "None",
                "sensitivity": "Normal"
            },
            "email_address": "ews-2016-test@lab-demisto.com",
            "mailbox_type": "Mailbox",
            "name": "ews-2016-test EW2016.",
            "routing_type": "SMTP"
        }
    }
}

Human Readable Output

Resolved Names

primary_email_address name mailbox_type routing_type
ews-2016-test@lab-demisto.com ews-2016-test EW2016. Mailbox SMTP

ews-get-out-of-office


Retrieves the out-of-office status for a specified mailbox.

Base Command

ews-get-out-of-office

Input

Argument Name Description Required
target-mailbox The mailbox for which to get the out-of-office status. Required

Context Output

Path Type Description
Account.Email.OutOfOffice.state Unknown Out-of-office state. Result can be: Enabled, Scheduled, Disabled.
Account.Email.OutOfOffice.externalAudience Unknown Out-of-office external audience. Can be “None”, “Known”, or “All”.
Account.Email.OutOfOffice.start Unknown Out-of-office start date.
Account.Email.OutOfOffice.end Unknown Out-of-office end date.
Account.Email.OutOfOffice.internalReply Unknown Out-of-office internal reply.
Account.Email.OutOfOffice.externalReply Unknown Out-of-office external reply.
Account.Email.OutOfOffice.mailbox Unknown Out-of-office mailbox.

Command Example

!ews-get-out-of-office target-mailbox=test@demistodev.onmicrosoft.com

Human Readable Output

end externalAudienc mailbox start state
2019-08-12T13:00:00Z all test@demistodev.onmicrosoft.com 2019-08-11T13:00:00Z Disabled

Context Example

{
    "Account": {
        "Email": {
            "OutOfOffice": {
                "start": "2019-08-11T13:00:00Z", 
                "state": "Disabled", 
                "mailbox": "test@demistodev.onmicrosoft.com", 
                "end": "2019-08-12T13:00:00Z", 
                "externalAudience": "All"
            }
        }
    }
}

ews-recover-messages


Recovers messages that were soft-deleted.

Base Command

ews-recover-messages

Input

Argument Name Description Required
message-ids A CSV list of message IDs. Run the py-ews-delete-items command to retrieve the message IDs. Required
target-folder-path The folder path to recover the messages to. Default is Inbox. Required
target-mailbox The mailbox in which the messages found. If empty, will use the default mailbox. If you specify a different mailbox, you might need impersonation rights to the mailbox. Optional
is-public Whether the target folder is a Public Folder. Possible values are: True, False. Optional

Context Output

Path Type Description
EWS.Items.itemId Unknown The item ID of the recovered item.
EWS.Items.messageId Unknown The message ID of the recovered item.
EWS.Items.action Unknown The action taken on the item. The value will be ‘recovered’.

Command Example

!ews-recover-messages message-ids=&lt;DFVDFmvsCSCS.com&gt; target-folder-path=Moving target-mailbox=test@demistodev.onmicrosoft.com

Human Readable Output

action itemId messageId
recovered AAVCSVS1hN2NkLThmZjdmNTZjNTMxNwBGAAAAAAA4kxh+ed33wX3aBwCyyVyFtlsUQZfBJebinpkUAAAa2bUBAACyyVyFtlscfxxd/AAA= DFVDFmvsCSCS.com

Context Example

{
    "EWS": {
        "Items": {
            "action": "recovered", 
            "itemId": "AAVCSVS1hN2NkLThmZjdmNTZjNTMxNwBGAAAAAAA4kxh+ed33wX3aBwCyyVyFtlsUQZfBJebinpkUAAAa2bUBAACyyVyFtlscfxxd/AAA=", 
            "messageId": "&lt;DFVDFmvsCSCS.com&gt;"
        }
    }
}

ews-create-folder


Creates a new folder in a specified mailbox.

Base Command

ews-create-folder

Input

Argument Name Description Required
new-folder-name The name of the new folder. Required
folder-path Path to locate the new folder. Exchange folder ID is also supported. Default is Inbox. Required
target-mailbox The mailbox in which to create the folder. Optional

Context Output

There is no context output for this command.

Command Example

!ews-create-folder folder-path=Inbox new-folder-name="Created Folder" target-mailbox=test@demistodev.onmicrosoft.com

Human Readable Output

Folder Inbox\Created Folder created successfully

ews-mark-item-as-junk


Marks an item as junk. This is commonly used to block an email address. For more information, see the Microsoft documentation: https://msdn.microsoft.com/en-us/library/office/dn481311(v=exchg.150).aspx

Base Command

ews-mark-item-as-junk

Input

Argument Name Description Required
item-id The item ID to mark as junk. Required
move-items Whether to move the item from the original folder to the junk folder. Possible values are: yes, no. Default is yes. Optional
target-mailbox If empty, will use the default mailbox. If you specify a different mailbox, you might need impersonation rights to the mailbox. Optional

Context Output

There is no context output for this command.

Command Example

!ews-mark-item-as-junk item-id=AAMkcSQ0NmFkOhmZjdmNTZjNTMxNwBGAAAAAAA4kxh+ed3JTJPMPXU3wX3aBwCyyVyFtlsUcsBJebinpkUAAAAAAEMASFDkUAAAfxuiSAAA= move-items=yes target-mailbox=test@demistodev.onmicrosoft.com

Human Readable Output

action itemId
marked-as-junk AAMkcSQ0NmFkOhmZjdmNTZjNTMxNwBGAAAAAAA4kxh+ed3JTJPMPXU3wX3aBwCyyVyFtlsUcsBJebinpkUAAAAAAEMASFDkUAAAfxuiSAAA=

Context Example

{
    "EWS": {
        "Items": {
            "action": "marked-as-junk", 
            "itemId": "AAMkcSQ0NmFkOhmZjdmNTZjNTMxNwBGAAAAAAA4kxh+ed3JTJPMPXU3wX3aBwCyyVyFtlsUcsBJebinpkUAAAAAAEMASFDkUAAAfxuiSAAA="
        }
    }
}

ews-find-folders


Retrieves information for folders for a specified mailbox. Only folders with read permissions will be returned. Your visual folders on the mailbox, such as “Inbox”, are under the folder “Top of Information Store”.

Base Command

ews-find-folders

Input

Argument Name Description Required
target-mailbox The mailbox on which to apply the command. Optional
is-public Whether to find Public Folders. Possible values are: True, False. Optional

Context Output

Path Type Description
EWS.Folders.name string Folder name.
EWS.Folders.id string Folder ID.
EWS.Folders.totalCount Unknown Number of items in folder.
EWS.Folders.unreadCount number Number of unread items in folder
EWS.Folders.changeKey number Folder change key.
EWS.Folders.childrenFolderCount number Number of sub-folders.

Command Example

!ews-find-folders target-mailbox=test@demistodev.onmicrosoft.com

Human Readable Output

root
├── AllContacts
├── AllItems
├── Common Views
├── Deferred Action
├── ExchangeSyncData
├── Favorites
├── Freebusy Data
├── Location
├── MailboxAssociations
├── My Contacts
├── MyContactsExtended
├── People I Know
├── PeopleConnect
├── Recoverable Items
│ ├── Calendar Logging
│ ├── Deletions
│ ── Purges
│ └── Versions
├── Reminders
├── Schedule
├── Sharing
├── Shortcuts
├── Spooler Queue
├── System
├── To-Do Search
├── Top of Information Store
│ ├── Calendar
│ ├── Contacts
│ │ ├── GAL Contacts
│ │ ├── Recipient Cache
│ ├── Conversation Action Settings
│ ├── Deleted Items
│ │ └── Create1
│ ├── Drafts
│ ├── Inbox
...

Context Example

{
    "EWS": {
        "Folders": [    
            {
                "unreadCount": 1, 
                "name": "Inbox", 
                "childrenFolderCount": 1, 
                "totalCount": 44, 
                "changeKey": "**********fefsduQi0", 
                "id": "*******VyFtlFDSAFDSFDAAA="
            }
        ]
    }
}

ews-get-items-from-folder


Retrieves items from a specified folder in a mailbox. The items are order by the item created time, most recent is first.

Base Command

ews-get-items-from-folder

Input

Argument Name Description Required
folder-path The folder path from which to get the items. Required
limit Maximum number of items to return. Default is 100. Optional
target-mailbox The mailbox to on which to apply the command. Optional
is-public Whether the folder is a Public Folder. Default is ‘False’. Possible values are: True, False. Optional
get-internal-item If the email item contains another email as an attachment (EML or MSG file), whether to retrieve the EML/MSG file attachment. Can be “yes” or “no”. Default is “no”. Possible values are: yes, no. Default is no. Optional

Context Output

Path Type Description
EWS.Items.itemId string The item ID of the email.
EWS.Items.hasAttachments boolean Whether the email has attachments.
EWS.Items.datetimeReceived date Received time of the email.
EWS.Items.datetimeSent date Sent time of the email.
EWS.Items.headers Unknown Email headers (list).
EWS.Items.sender string Sender mail address of the email.
EWS.Items.subject string Subject of the email.
EWS.Items.textBody string Body of the email (as text).
EWS.Items.size number Email size.
EWS.Items.toRecipients Unknown Email recipients addresses (list).
EWS.Items.receivedBy Unknown Received by address of the email.
EWS.Items.messageId string Email message ID.
EWS.Items.body string Body of the email (as HTML).
EWS.Items.FileAttachments.attachmentId unknown Attachment ID of file attachment.
EWS.Items.ItemAttachments.attachmentId unknown Attachment ID of the item attachment.
EWS.Items.FileAttachments.attachmentName unknown Attachment name of the file attachment.
EWS.Items.ItemAttachments.attachmentName unknown Attachment name of the item attachment.
EWS.Items.isRead String The read status of the email.
EWS.Items.categories String Categories of the email.

Command Example

!ews-get-items-from-folder folder-path=Test target-mailbox=test@demistodev.onmicrosoft.com limit=1

Human Readable Output

sender subject hasAttachments datetimeReceived receivedBy author toRecipients itemId
test2@demistodev.onmicrosoft.com Get Attachment Email true 2019-08-11T10:57:37Z test@demistodev.onmicrosoft.com test2@demistodev.onmicrosoft.com test@demistodev.onmicrosoft.com AAFSFSFFtlsUQZfBJebinpkUAAABjKMGAACyyVyFtlsUQZfBJebinpkUAAAsfw+jAAA=

Context Example

{
    "EWS": {
        "Items": {
            "body": "&lt;html&gt;\r\n&lt;head&gt;\r\n&lt;meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\"&gt;\r\n&lt;style type=\"text/css\" style=\"display:none;\"&gt;&lt;!-- P {margin-top:0;margin-bottom:0;} --&gt;&lt;/style&gt;\r\n&lt;/head&gt;\r\n&lt;body dir=\"ltr\"&gt;\r\n&lt;div id=\"divtagdefaultwrapper\" style=\"font-size:12pt;color:#000000;font-family:Calibri,Helvetica,sans-serif;\" dir=\"ltr\"&gt;\r\n&lt;p style=\"margin-top:0;margin-bottom:0\"&gt;Some text inside email&lt;/p&gt;\r\n&lt;/div&gt;\r\n&lt;/body&gt;\r\n&lt;/html&gt;\r\n", 
            "itemId": "AAFSFSFFtlsUQZfBJebinpkUAAABjKMGAACyyVyFtlsUQZfBJebinpkUAAAsfw+jAAA=", 
            "toRecipients": [
                "test@demistodev.onmicrosoft.com"
            ], 
            "datetimeCreated": "2019-08-11T10:57:37Z", 
            "datetimeReceived": "2019-08-11T10:57:37Z", 
            "author": "test2@demistodev.onmicrosoft.com", 
            "hasAttachments": true, 
            "size": 21435, 
            "subject": "Get Attachment Email", 
            "FileAttachments": [
                {
                    "attachmentName": "atta1.rtf", 
                    "attachmentSHA256": "cd81097bcvdiojf3407a00308b48039e31a44a1c4fdnfkdknce36e4f", 
                    "attachmentType": "FileAttachment", 
                    "attachmentSize": 535, 
                    "attachmentId": "AAFSFSFFtlsUQZfBJebinpkUAAABjKMGAACyyVyFtlsUQZfBJebinpkUAAAsfw+jAAABEgAQAEyq1TB2nKBLpKUiFUJ5Geg=", 
                    "attachmentIsInline": false, 
                    "attachmentLastModifiedTime": "2019-08-11T11:06:02+00:00", 
                    "attachmentContentLocation": null, 
                    "attachmentContentType": "text/rtf", 
                    "originalItemId": "AAFSFSFFtlsUQZfBJebinpkUAAABjKMGAACyyVyFtlsUQZfBJebinpkUAAAsfw+jAAA=", 
                    "attachmentContentId": null
                }
            ], 
            "headers": [
                {
                    "name": "Subject", 
                    "value": "Get Attachment Email"
                }
                            ], 
            "isRead": true, 
            "messageId": "&lt;message_id&gt;", 
            "receivedBy": "test@demistodev.onmicrosoft.com", 
            "datetimeSent": "2019-08-11T10:57:36Z", 
            "lastModifiedTime": "2019-08-11T11:13:59Z", 
            "mailbox": "test@demistodev.onmicrosoft.com", 
            "importance": "Normal", 
            "textBody": "Some text inside email\r\n", 
            "sender": "test2@demistodev.onmicrosoft.com"
        }
    }
}

ews-get-items


Retrieves items by item ID.

Base Command

ews-get-items

Input

Argument Name Description Required
item-ids A CSV list if item IDs. Required
target-mailbox The mailbox on which to run the command on. Optional

Context Output

Path Type Description
EWS.Items.itemId string The email item ID.
EWS.Items.hasAttachments boolean Whether the email has attachments.
EWS.Items.datetimeReceived date Received time of the email.
EWS.Items.datetimeSent date Sent time of the email.
EWS.Items.headers Unknown Email headers (list).
EWS.Items.sender string Sender mail address of the email.
EWS.Items.subject string Subject of the email.
EWS.Items.textBody string Body of the email (as text).
EWS.Items.size number Email size.
EWS.Items.toRecipients Unknown Email recipients addresses (list).
EWS.Items.receivedBy Unknown Received by address of the email.
EWS.Items.messageId string Email message ID.
EWS.Items.body string Body of the email (as HTML).
EWS.Items.FileAttachments.attachmentId unknown Attachment ID of the file attachment.
EWS.Items.ItemAttachments.attachmentId unknown Attachment ID of the item attachment.
EWS.Items.FileAttachments.attachmentName unknown Attachment name of the file attachment.
EWS.Items.ItemAttachments.attachmentName unknown Attachment name of the item attachment.
EWS.Items.isRead String The read status of the email.
EWS.Items.categories String Categories of the email.
Email.CC String Email addresses CC’ed to the email.
Email.BCC String Email addresses BCC’ed to the email.
Email.To String The recipient of the email.
Email.From String The sender of the email.
Email.Subject String The subject of the email.
Email.Text String The plain-text version of the email.
Email.HTML String The HTML version of the email.
Email.HeadersMap String The headers of the email.

Command Example

!ews-get-items item-ids=AAMkADQ0NmFkODFkLWQ4MDEtNDFDFZjNTMxNwBGAAAAAAA4kxhFFAfxw+jAAA= target-mailbox=test@demistodev.onmicrosoft.com

Human Readable Output

Identical outputs to ews-get-items-from-folder command.

ews-move-item-between-mailboxes


Moves an item from one mailbox to different mailbox.

Base Command

ews-move-item-between-mailboxes

Input

Argument Name Description Required
item-id The item ID to move. Required
destination-folder-path The folder in the destination mailbox to which to move the item. You can specify a complex path, for example, “Inbox\Phishing”. Required
destination-mailbox The mailbox to which to move the item. Required
source-mailbox The mailbox from which to move the item (conventionally called the “target-mailbox”, the target mailbox on which to run the command). Optional
is-public Whether the destination folder is a Public Folder. Default is “False”. Possible values are: True, False. Optional

Context Output

Path Type Description
EWS.Items.movedToMailbox string The mailbox wo which the item was moved.
EWS.Items.movedToFolder string The folder to which the item was moved.
EWS.Items.action string The action taken on the item. The value will be “moved”.

Command Example

!ews-move-item-between-mailboxes item-id=AAMkAGY3OTQyMzMzLWYxNjktNDE0My05NFSFSyNzBkNABGAAAAAACYCKjWAjq/zTrN6vWSzK4OWAAK2ISFSA= destination-folder-path=Moving destination-mailbox=test@demistodev.onmicrosoft.com source-mailbox=test2@demistodev.onmicrosoft.com

Human Readable Output

Item was moved successfully.

Context Example

{
    "EWS": {
        "Items": {
            "movedToMailbox": "test@demistodev.onmicrosoft.com", 
            "movedToFolder": "Moving"
        }
    }
}

ews-get-folder


Retrieves a single folder.

Base Command

ews-get-folder

Input

Argument Name Description Required
target-mailbox The mailbox on which to apply the search. Optional
folder-path The path of the folder to retrieve. If empty, will retrieve the folder “AllItems”. Default is AllItems. Optional
is-public Whether the folder is a Public Folder. Default is “False”. Possible values are: True, False. Optional

Context Output

Path Type Description
EWS.Folders.id string Folder ID.
EWS.Folders.name string Folder name.
EWS.Folders.changeKey string Folder change key.
EWS.Folders.totalCount number Total number of emails in the folder.
EWS.Folders.childrenFolderCount number Number of sub-folders.
EWS.Folders.unreadCount number Number of unread emails in the folder.

Command Example

!ews-get-folder folder-path=demistoEmail target-mailbox=test@demistodev.onmicrosoft.com

Human Readable Output

changeKey childrenFolderCount id name totalCount unreadCount
***yFtCdJSH 0 AAMkADQ0NmFkODFkLWQ4MDEtNDE4Mi1hN2NlsjflsjfSF= demistoEmail 1 0

Context Example

{
    "EWS": {
        "Folders": {
            "unreadCount": 0, 
            "name": "demistoEmail", 
            "childrenFolderCount": 0, 
            "totalCount": 1, 
            "changeKey": "***yFtCdJSH", 
            "id": "AAMkADQ0NmFkODFkLWQ4MDEtNDE4Mi1hN2NlsjflsjfSF="
        }
    }
}

ews-get-autodiscovery-config


Returns the auto-discovery information. Can be used to manually configure the Exchange Server.

Base Command

ews-get-autodiscovery-config

Input

There are no input arguments for this command.

Context Output

There is no context output for this command.

Command Example


#### Human Readable Output

>|api_version|auth_type|build|service_endpoint|
>|---|---|---|---|
>| Exchange2016 |###|--|<https://outlook.office365.com/EWS/Exchange.asmx>|

### ews-expand-group

***
Expands a distribution list to display all members. By default, expands only first layer of the distribution list. If recursive-expansion is "True", the command expands nested distribution lists and returns all members.

#### Base Command

`ews-expand-group`

#### Input

| **Argument Name** | **Description** | **Required** |
| --- | --- | --- |
| email-address | Email address of the group to expand. | Required |
| recursive-expansion | Whether to enable recursive expansion. Default is "False". Possible values are: True, False. Default is False. | Optional |

#### Context Output

There is no context output for this command.

#### Command Example

```!ews-expand-group email-address="TestPublic" recursive-expansion="False"```

#### Human Readable Output

>|displayNam|mailbox|mailboxtype|
>|---|---|---|
>| John Wick|<john@wick.com>|MailBox|

#### Context Example

```json
{
    "EWS.ExpandGroup": {
        "name": "TestPublic", 
        "members": [
            {
                "mailboxType": "Mailbox", 
                "displayName": "John Wick", 
                "mailbox": "john@wick.com"
            }
        ]
    }
}

ews-mark-items-as-read


Marks items as read or unread.

Base Command

ews-mark-items-as-read

Input

Argument Name Description Required
item-ids A CSV list of item IDs. Required
operation How to mark the item. Can be “read” or “unread”. Default is “read”. Possible values are: read, unread. Default is read. Optional
target-mailbox The mailbox on which to run the command. If empty, the command will be applied on the default mailbox. Optional

Context Output

Path Type Description
EWS.Items.action String The action that was performed on item.
EWS.Items.itemId String The ID of the item.
EWS.Items.messageId String The message ID of the item.

Command Example

!ews-mark-items-as-read item-ids=AAMkADQ0NFSffU3wX3aBwCyyVyFtlsUQZfBJebinpkUAAABjKMnpkUAAAfxw+jAAA= operation=read target-mailbox=test@demistodev.onmicrosoft.com

Human Readable Output

action itemId messageId
mark-as-read AAMkADQ0NFSffU3wX3aBwCyyVyFtlsUQZfBJebinpkUAAABjKMnpkUAAAfxw+jAAA= id

Context Example

{
    "EWS": {
        "Items": {
            "action": "marked-as-read", 
            "itemId": "AAMkADQ0NFSffU3wX3aBwCyyVyFtlsUQZfBJebinpkUAAABjKMnpkUAAAfxw+jAAA= ", 
            "messageId": "&lt;message_id&gt;"
        }
    }
}

ews-get-items-as-eml


Retrieves items by item ID and uploads it’s content as eml file.

Base Command

ews-get-items-as-eml

Input

Argument Name Description Required
item-id The item ID of item to upload as and EML file. Required
target-mailbox The mailbox in which this email was found. If empty, the default mailbox is used. Otherwise the user might require impersonation rights to this mailbox. Optional

Context Output

Path Type Description
File.Size String The size of the file.
File.SHA1 String The SHA1 hash of the file.
File.SHA256 String The SHA256 hash of the file.
File.SHA512 String The SHA512 hash of the file.
File.Name String The name of the file.
File.SSDeep String The SSDeep hash of the file.
File.EntryID String EntryID of the file
File.Info String Information about the file.
File.Type String The file type.
File.MD5 String The MD5 hash of the file.
File.Extension String The extension of the file.

send-mail


Sends an email using EWS.

Base Command

send-mail

Input

Argument Name Description Required
to A CSV list of email addresses for the ‘to’ field. Required
cc A CSV list of email addresses for the ‘cc’ field. Optional
bcc A CSV list of email addresses for the ‘bcc’ field. Optional
subject Subject for the email to be sent. Required
replyTo The email address specified in the ‘reply to’ field. Optional
body The contents (body) of the email to send. This argument overrides the “htmlBody” argument if the “bodyType” argument is Text. Optional
htmlBody HTML formatted content (body) of the email to be sent. This argument overrides the “body” argument if the “bodyType” argument is HTML. Optional
bodyType The message response body type. Possible values are: Text, HTML. Default is Text. Optional
attachIDs A CSV list of War Room entry IDs that contain files, and are used to attach files to the outgoing email. For example: attachIDs=15@8,19@8. Optional
attachNames A CSV list of names of attachments to send. Should be the same number of elements as attachIDs. Optional
attachCIDs A CSV list of CIDs to embed attachments within the email itself. Optional
raw_message Raw email message from MimeContent type. Optional
from The email address from which to send mail. Optional
handle_inline_image Whether to handle inline images in the HTML body. When set to ‘True’, inline images will be extracted from the HTML and attached to the email as an inline attachment object. Note that in some cases, attaching the image as an object may cause the image to disappear when replying to the email. Additionally, sending the image in the html body as base64 data (inline image) may cause the image to disappear if the image is too large or recognized as malicious and subsequently deleted. Possible values are: True, False. Default is True. Optional

Context Output

There is no context output for this command.

reply-mail


Replies to an email using EWS.

Command Example

!send-mail body="hello this is a test" subject=Hi to=avishai@demistodev.onmicrosoft.com

Human Readable Output

Sent email

attachments from subject to
  avishai@demistodev.onmicrosoft.com Hi avishai@demistodev.onmicrosoft.com

Base Command

reply-mail

Input

Argument Name Description Required
inReplyTo ID of the item to reply to. Required
to A CSV list of email addresses for the ‘to’ field. Required
cc A CSV list of email addresses for the ‘cc’ field. Optional
bcc A CSV list of email addresses for the ‘bcc’ field. Optional
subject Subject for the email to be sent. Optional
body The contents (body) of the email to be sent. Optional
htmlBody HTML formatted content (body) of the email to be sent. This argument overrides the “body” argument. Optional
attachIDs A CSV list of War Room entry IDs that contain files, and are used to attach files to the outgoing email. For example: attachIDs=15@8,19@8. Optional
attachNames A CSV list of names of attachments to send. Should be the same number of elements as attachIDs. Optional
attachCIDs A CSV list of CIDs to embed attachments within the email itself. Optional
handle_inline_image Whether to handle inline images in the HTML body. When set to ‘True’, inline images are extracted from the HTML and attached to the email as inline attachment objects. Possible values are: True, False. Default is True. NOTE: Sometimes inline images sent in emails may not appear for recipients, either because their email system blocks the image (for example, due to image size or the email is flagged as malicious) or for other reasons. Optional

Context Output

There is no context output for this command.

Command Example

!reply-mail item_id=AAMkAGY3OTQyMzMzLWYxNjktNDE0My05NmZhLWQ5MGY1YjIyNzBkNABGAAAAAACYCKjWAnXBTrnhgWJCcLX7BwDrxRwRjq/zTrN6vWSzK4OWAAAAAAEMAADrxRwRjq/zTrN6vWSzK4OWAAPYQGFeAAA= body=hello subject=hi to="avishai@demistodev.onmicrosoft.com"

Human Readable Output

Sent email

attachments from subject to
  avishai@demistodev.onmicrosoft.com hi avishai@demistodev.onmicrosoft.com

Additional Information

EWS Permissions

To perform actions on mailboxes of other users, and to execute searches on the Exchange server, you need specific permissions. For a comparison between Delegate and Impersonation permissions, see the Microsoft documentation

Permission Use Case How to Configure
Delegated One-to-one relationship between users. Read more here.
Impersonation A single account needs to access multiple mailboxes. Read more here.
eDiscovery A single account needs to access multiple mailboxes. Read more here.
Compliance Search Perform searches across mailboxes and get an estimate of the results. Read more here.

New-Compliance Search

The EWS v2 integration uses remote ps-session to run commands of compliance search as part of Office 365. To check if your account can connect to Office 365 Security & Compliance Center via powershell, check the following steps. New-Compliance search is a long-running task which has no limitation of searched mailboxes and therefore the suggestion is to useOffice 365 Search and Deleteplaybook. New-Compliance search returns statistics of matched content search query and doesn’t return preview of found emails in contrast toews-search-mailboxescommand.

Troubleshooting

For troubleshooting information, see the EWS V2 Troubleshooting.

Configuration parameters

  • credentials — Email address (required)
  • defaultTargetMailbox — Email address from which to fetch incidents (required)
  • folder — Name of the folder from which to fetch incidents (supports Exchange Folder ID and sub-folders e.g. Inbox/Phishing)
  • isPublicFolder — Public Folder
  • impersonation — Has impersonation rights
  • proxy — Use system proxy settings
  • isFetch — Fetch incidents
  • fetch_time — First fetch timestamp (<number> <time unit>, e.g., 12 hours, 7 days)
  • markAsRead — Mark fetched emails as read
  • incidentType — Incident type
  • incidentFetchInterval — Incidents Fetch Interval
  • ewsServer — ┉┉┉┉┉┉┉┉┉┉┉┉┉┉┉┉┉┉┉┉┉┉ ‎ Manual Mode Exchange Server Hostname or IP address
  • domainAndUserman — DOMAIN\USERNAME (e.g. DEMISTO.INT\admin)
  • defaultServerVersion — Exchange Server Version (On-Premise only. Supported versions: 2007, 2010, 2010_SP2, 2013, 2013_SP1, 2016, and 2019)
  • insecure — Trust any certificate (not secure)
  • authType — ┉┉┉┉┉┉┉┉┉┉┉┉┉┉┉┉┉┉┉┉┉┉ ‎ Advanced Mode Override Authentication Type (NTLM, Basic, or Digest).
  • requestTimeout — Timeout (in seconds) for HTTP requests to Exchange Server
  • maxFetch — Max incidents per fetch
  • separate_process — Run as a separate process (protects against memory depletion)
  • legacy_name — Use legacy attachment name
  • skip_unparsable_emails — Skip unparsable emails during fetch incidents

Commands (24)

  • ews-create-folder

    Creates a new folder in a specified mailbox.

  • ews-delete-attachment

    Deletes the attachments of an item (email message).

  • ews-delete-items

    Delete items from mailbox. This command requires eDiscovery permissions to the Exchange Server. For more information, see the EWSv2 integration documentation.

  • ews-expand-group

    Expands a distribution list to display all members. By default, expands only first layer of the distribution list. If recursive-expansion is "True", the command expands nested distribution lists and returns all members.

  • ews-find-folders

    Retrieves information for folders for a specified mailbox. Only folders with read permissions will be returned. Your visual folders on the mailbox, such as "Inbox", are under the folder "Top of Information Store".

  • ews-get-attachment

    Retrieves the actual attachments from an item (email message). To get all attachments for a message, only specify the item-id argument.

  • ews-get-autodiscovery-config

    Returns the auto-discovery information. Can be used to manually configure the Exchange Server.

  • ews-get-contacts

    Retrieves contacts for a specified mailbox.

  • ews-get-folder

    Retrieves a single folder.

  • ews-get-items

    Retrieves items by item ID.

  • ews-get-items-as-eml

    Retrieves items by item ID and uploads it's content as eml file.

  • ews-get-items-from-folder

    Retrieves items from a specified folder in a mailbox. The items are order by the item created time, most recent is first.

  • ews-get-out-of-office

    Retrieves the out-of-office status for a specified mailbox.

  • ews-get-searchable-mailboxes

    Returns a list of searchable mailboxes. This command requires eDiscovery permissions to the Exchange Server. For more information, see the EWSv2 integration documentation.

  • ews-mark-item-as-junk

    Marks an item as junk. This is commonly used to block an email address. For more information, see the Microsoft documentation: https://msdn.microsoft.com/en-us/library/office/dn481311(v=exchg.150).aspx.

  • ews-mark-items-as-read

    Marks items as read or unread.

  • ews-move-item

    Move an item to different folder in the mailbox.

  • ews-move-item-between-mailboxes

    Moves an item from one mailbox to different mailbox.

  • ews-recover-messages

    Recovers messages that were soft-deleted.

  • ews-resolve-name

    This operation verifies aliases and matches display names to the correct mailbox user. It handles one ambiguous name at a time. If there are multiple potential matches, all will be returned, but limited to a maximum of 100 candidates.

  • ews-search-mailbox

    Searches for items in the specified mailbox. Specific permissions are needed for this operation to search in a target mailbox other than the default.

  • ews-search-mailboxes

    Searches over multiple mailboxes or all Exchange mailboxes. Use either the mailbox-search-scope command or the email-addresses command to search specific mailboxes. This command requires eDiscovery permissions to the Exchange Server. For more information, see the EWS v2 integration documentation.

  • reply-mail

    Replies to an email using EWS.

  • send-mail

    Sends an email using EWS.

import email
import hashlib
from email.policy import SMTP, SMTPUTF8
from io import StringIO
from multiprocessing import Process

import dateparser  # type: ignore
import exchangelib
from CommonServerPython import *
from EWSApiModule import *
from exchangelib import (
    BASIC,
    DELEGATE,
    DIGEST,
    IMPERSONATION,
    NTLM,
    Body,
    EWSDateTime,
    EWSTimeZone,
    FileAttachment,
    FolderCollection,
    HTMLBody,
    ItemAttachment,
    Version,
)
from exchangelib.errors import (
    ErrorCannotOpenFileAttachment,
    ErrorFolderNotFound,
    ErrorInvalidPropertyRequest,
    ErrorIrresolvableConflict,
    ErrorMailboxMoveInProgress,
    ErrorMailboxStoreUnavailable,
    ErrorMimeContentConversionFailed,
    ErrorNameResolutionNoResults,
    RateLimitError,
    TransportError,
)
from exchangelib.items import Contact, Item, Message
from exchangelib.services import EWSService
from exchangelib.util import add_xml_child, create_element
from exchangelib.version import (
    EXCHANGE_2007,
    EXCHANGE_2010,
    EXCHANGE_2010_SP2,
    EXCHANGE_2013,
    EXCHANGE_2013_SP1,
    EXCHANGE_2016,
    EXCHANGE_2019,
)
from exchangelib.version import VERSIONS as EXC_VERSIONS
from future import utils as future_utils
from requests.exceptions import ConnectionError


# Exchange2 2019 patch - server dosen't connect with 2019 but with other versions creating an error mismatch (see CIAC-3086),
# overriding this function to remove minor version test and remove error throw.
# opened bug for exchanglib here https://github.com/ecederstrand/exchangelib/issues/1210
def our_fullname(self):  # pragma: no cover
    for build, api_version, full_name in EXC_VERSIONS:
        # removed 'or self.build.minor_version != build.minor_version'
        if self.build and self.build.major_version != build.major_version:
            continue
        if self.api_version == api_version:
            return full_name
    return None


Version.fullname = our_fullname

# Ignore warnings print to stdout
warnings.filterwarnings("ignore")

MNS, TNS = exchangelib.util.MNS, exchangelib.util.TNS

# consts
VERSIONS = {
    "2007": EXCHANGE_2007,
    "2010": EXCHANGE_2010,
    "2010_SP2": EXCHANGE_2010_SP2,
    "2013": EXCHANGE_2013,
    "2013_SP1": EXCHANGE_2013_SP1,
    "2016": EXCHANGE_2016,
    "2019": EXCHANGE_2019,
}

APP_NAME = "EWSv2"
ATTACHMENT_ORIGINAL_ITEM_ID = "originalItemId"
NEW_ITEM_ID = "newItemId"
MESSAGE_ID = "messageId"
ITEM_ID = "itemId"
MAILBOX = "mailbox"
MAILBOX_ID = "mailboxId"
FOLDER_ID = "id"

MOVED_TO_MAILBOX = "movedToMailbox"
MOVED_TO_FOLDER = "movedToFolder"

FILE_ATTACHMENT_TYPE = "FileAttachment"
ITEM_ATTACHMENT_TYPE = "ItemAttachment"
ATTACHMENT_TYPE = "attachmentType"

TOIS_PATH = "/root/Top of Information Store/"

ENTRY_CONTEXT = "EntryContext"
CONTEXT_UPDATE_EWS_ITEM = (
    f"EWS.Items(val.{ITEM_ID} == obj.{ITEM_ID} || "
    f"(val.{MESSAGE_ID} && obj.{MESSAGE_ID} && val.{MESSAGE_ID} == obj.{MESSAGE_ID}))"
)
CONTEXT_UPDATE_EWS_ITEM_FOR_ATTACHMENT = f"EWS.Items(val.{ITEM_ID} == obj.{ATTACHMENT_ORIGINAL_ITEM_ID})"
CONTEXT_UPDATE_FOLDER = f"EWS.Folders(val.{FOLDER_ID} == obj.{FOLDER_ID})"

LAST_RUN_TIME = "lastRunTime"
LAST_RUN_IDS = "ids"
LAST_RUN_FOLDER = "folderName"
ERROR_COUNTER = "errorCounter"

ITEMS_RESULTS_HEADERS = [
    "sender",
    "subject",
    "hasAttachments",
    "datetimeReceived",
    "receivedBy",
    "author",
    "toRecipients",
    "textBody",
]


LAST_RUN_IDS_QUEUE_SIZE = 500

# NOTE: Same method used in EWSMailSender
# If you are modifying this probably also need to modify in the other file


def exchangelib_cleanup():  # pragma: no cover
    try:
        exchangelib.close_connections()
    except Exception as ex:
        demisto.error(f"Error was found in exchangelib cleanup, ignoring: {ex}")


# Prep Functions
def parse_auth_type(auth_type: str):  # pragma: no cover
    auth_type = auth_type.lower()
    if auth_type == "ntlm":
        return NTLM
    elif auth_type == "basic":
        return BASIC
    elif auth_type == "digest":
        return DIGEST
    raise Exception("{} auth method is not supported. Choose one of {}".format(auth_type, "ntlm\\basic\\digest"))


# LOGGING
log_stream = None
log_handler = None


# Library loggers whose verbose HTTP transaction dumps must never reach stdout.
# Their output (Request/Response headers, Response XML, etc.) corrupts the entry
# JSON that the docker runner reads from stdout. We route it only to the
# in-memory buffer used for the error-path "Full debug log" feature.
EXCHANGELIB_LOGGERS = ("exchangelib", "exchangelib.util")


def start_logging():
    global log_stream
    global log_handler
    logging.raiseExceptions = False
    # Only emit DEBUG-level output when the instance Log Level is set to debug/verbose.
    # Otherwise keep a non-verbose level so exchangelib's HTTP transaction dumps are not produced.
    log_level = logging.DEBUG if is_debug_mode() else logging.INFO
    if log_stream is None:
        log_stream = StringIO()
        log_handler = logging.StreamHandler(stream=log_stream)
        log_handler.setFormatter(logging.Formatter(logging.BASIC_FORMAT))
        logger = logging.getLogger()
        logger.addHandler(log_handler)
        logger.setLevel(log_level)
        # Ensure exchangelib's logging-based output can NEVER reach stdout: attach only the
        # in-memory buffer handler and stop propagation to the root/stdout handlers.
        for logger_name in EXCHANGELIB_LOGGERS:
            lib_logger = logging.getLogger(logger_name)
            lib_logger.addHandler(log_handler)
            lib_logger.setLevel(log_level)
            lib_logger.propagate = False
    else:
        logging.getLogger().setLevel(log_level)
        for logger_name in EXCHANGELIB_LOGGERS:
            logging.getLogger(logger_name).setLevel(log_level)


# Exchange 2010 Fixes
def fix_2010(client: EWSClient):  # pragma: no cover
    version = client.server_build if client.server_build else get_on_prem_build(client.version)
    if version <= EXCHANGE_2010_SP2:
        for m in (
            Item,
            Message,
            exchangelib.items.CalendarItem,
            exchangelib.items.Contact,
            exchangelib.items.DistributionList,
            exchangelib.items.PostItem,
            exchangelib.items.Task,
            exchangelib.items.MeetingRequest,
            exchangelib.items.MeetingResponse,
            exchangelib.items.MeetingCancellation,
        ):
            for i, f in enumerate(m.FIELDS):
                if f.name == "text_body":
                    m.FIELDS.pop(i)
                    break
        for m in (exchangelib.Folder, exchangelib.folders.Inbox):
            for i, f in enumerate(m.FIELDS):
                if f.name == "unread_count":
                    m.FIELDS.pop(i)
                    break

        def repr1(self):
            return self.__class__.__name__ + repr(
                (self.root, self.name, self.total_count, self.child_folder_count, self.folder_class, self.id, self.changekey)
            )

        def repr2(self):
            return self.__class__.__name__ + repr(
                (self.root, self.name, self.total_count, self.child_folder_count, self.folder_class, self.changekey)
            )

        def repr3(self):
            return self.__class__.__name__ + repr(
                (self.account, "[self]", self.name, self.total_count, self.child_folder_count, self.folder_class, self.changekey)
            )

        exchangelib.Folder.__repr__ = repr1
        exchangelib.folders.Inbox.__repr__ = exchangelib.folders.JunkEmail.__repr__ = repr2
        exchangelib.folders.Root.__repr__ = repr3

    start_logging()


def str_to_unicode(obj):  # pragma: no cover
    if isinstance(obj, dict):
        obj = {k: str_to_unicode(v) for k, v in list(obj.items())}
    elif isinstance(obj, list):
        obj = [str_to_unicode(k) for k in obj]
    elif isinstance(obj, str):
        obj = obj.encode("utf-8")
    return obj


def get_time_zone() -> EWSTimeZone | None:
    """get the XSOAR user time zone
    :return:
        returns an ``EWSTimeZone`` if TZ available or ``None`` if not
    :rtype: ``Optional[EWSTimeZone]``
    """
    time_zone = demisto.callingContext.get("context", {}).get("User", {}).get("timeZone", None)
    if time_zone:
        time_zone = EWSTimeZone(time_zone)
    return time_zone


def get_attachment_name(attachment_name, content_id="", is_inline=False, attachment_subject=""):  # pragma: no cover
    demisto.debug(
        f"get_attachment_name called with attachment_name='{attachment_name}', content_id='{content_id}', "
        f"is_inline={is_inline}, attachment_subject='{attachment_subject}'"
    )

    legacy_name = argToBoolean(demisto.params().get("legacy_name", False))

    if is_inline and content_id and content_id != "None" and not legacy_name:
        if attachment_name is None or attachment_name == "":
            return f"{content_id}-attachmentName-demisto_untitled_attachment"
        return f"{content_id}-attachmentName-{attachment_name}"
    if not attachment_name and attachment_subject:
        return attachment_subject
    if not attachment_name and not attachment_subject:
        return "demisto_untitled_attachment"
    return attachment_name


def send_email_to_mailbox(  # pragma: no cover
    account,
    to,
    subject,
    body,
    body_type,
    bcc,
    cc,
    reply_to,
    handle_inline_image: bool = True,
    html_body=None,
    attachments=None,
    raw_message=None,
    from_address=None,
):
    """
    Send an email to a mailbox.

    Args:
        body_type: type of the body. Can be 'html' or 'text' or None.
        account (Account): account from which to send an email.
        to (list[str]): a list of emails to send an email.
        subject (str): subject of the mail.
        body (str): body of the email.
        reply_to (list[str]): list of emails of which to reply to from the sent email.
        bcc (list[str]): list of email addresses for the 'bcc' field.
        cc (list[str]): list of email addresses for the 'cc' field.
        html_body (str): HTML formatted content (body) of the email to be sent. This argument
            overrides the "body" argument.
        attachments (list[str]): list of names of attachments to send.
        raw_message (str): Raw email message from MimeContent type.
        from_address (str): the email address from which to reply.
    """
    if not attachments:
        attachments = []
    message_body, inline_attachments = get_message_for_body_type(body, body_type, html_body, handle_inline_image)
    attachments += inline_attachments
    m = Message(
        account=account,
        mime_content=raw_message.encode("UTF-8") if raw_message else None,
        folder=account.sent,
        cc_recipients=cc,
        bcc_recipients=bcc,
        subject=subject,
        body=message_body,
        to_recipients=to,
        reply_to=reply_to,
        author=from_address,
    )
    if account.protocol.version.build <= EXCHANGE_2010_SP2:
        m.save()
        for attachment in attachments:
            m.attach(attachment)
        m.send()
    else:
        for attachment in attachments:
            m.attach(attachment)
        m.send_and_save()
    return m


def get_message_for_body_type(body, body_type, html_body, handle_inline_image: bool):
    """
    Compatibility with Data Collection - where body_type is not provided, we will use the html_body if it exists.
    Compatibility with 'send-mail' command - where body_type should be provided, we will use the body_type to decide.
    Args:
        body_type: type of the body. Can be 'html' or 'text' or None.
        body: plain text body.
        html_body: HTML formatted content (body) of the email to be sent.

    Returns:
        Body: the body of the message.
    """
    demisto.debug(f"get_message_for_body_type: Received {body_type=}, {handle_inline_image=}")
    attachments: list = []

    if html_body and handle_inline_image:
        html_body, attachments = handle_html(html_body)
        demisto.debug(f"get_message_for_body_type: Processed HTML body with {len(attachments)} attachments")

    if body_type is None:  # When called from a data collection task.
        return (HTMLBody(html_body) if html_body else Body(body)), attachments

    if body_type.lower() == "html" and html_body:  # When called from 'send-mail' command.
        return HTMLBody(html_body), attachments

    return Body(body) if (body or not html_body) else HTMLBody(html_body), attachments


class SearchMailboxes(EWSService):
    def __init__(self, protocol, limit):
        self.limit = limit
        super().__init__(protocol)

    SERVICE_NAME = "SearchMailboxes"
    element_container_name = f"{{{MNS}}}SearchMailboxesResult/{{{TNS}}}Items"

    @staticmethod
    def parse_element(element):  # pragma: no cover
        to_recipients = element.find(f"{{{TNS}}}ToRecipients")
        if to_recipients:
            to_recipients = [x.text if x is not None else None for x in to_recipients]

        result = {
            ITEM_ID: element.find(f"{{{TNS}}}Id").attrib["Id"] if element.find(f"{{{TNS}}}Id") is not None else None,
            MAILBOX: element.find(f"{{{TNS}}}Mailbox/{{{TNS}}}PrimarySmtpAddress").text
            if element.find(f"{{{TNS}}}Mailbox/{{{TNS}}}PrimarySmtpAddress") is not None
            else None,
            "subject": element.find(f"{{{TNS}}}Subject").text if element.find(f"{{{TNS}}}Subject") is not None else None,
            "toRecipients": to_recipients,
            "sender": element.find(f"{{{TNS}}}Sender").text if element.find(f"{{{TNS}}}Sender") is not None else None,
            "hasAttachments": element.find(f"{{{TNS}}}HasAttachment").text
            if element.find(f"{{{TNS}}}HasAttachment") is not None
            else None,
            "datetimeSent": element.find(f"{{{TNS}}}SentTime").text if element.find(f"{{{TNS}}}SentTime") is not None else None,
            "datetimeReceived": element.find(f"{{{TNS}}}ReceivedTime").text
            if element.find(f"{{{TNS}}}ReceivedTime") is not None
            else None,
        }

        return result

    def call(self, query, mailboxes):  # pragma: no cover
        if self.protocol.version.build < EXCHANGE_2013:
            raise NotImplementedError(f"{self.SERVICE_NAME} is only supported for Exchange 2013 servers and later")
        elements = list(self._get_elements(payload=self.get_payload(query, mailboxes)))
        return [self.parse_element(x) for x in elements]

    def get_payload(self, query, mailboxes):  # pragma: no cover
        def get_mailbox_search_scope(mailbox_id):
            mailbox_search_scope = create_element("t:MailboxSearchScope")
            add_xml_child(mailbox_search_scope, "t:Mailbox", mailbox_id)
            add_xml_child(mailbox_search_scope, "t:SearchScope", "All")
            return mailbox_search_scope

        mailbox_query_element = create_element("t:MailboxQuery")
        add_xml_child(mailbox_query_element, "t:Query", query)
        mailboxes_scopes = []
        for mailbox in mailboxes:
            mailboxes_scopes.append(get_mailbox_search_scope(mailbox))
        add_xml_child(mailbox_query_element, "t:MailboxSearchScopes", mailboxes_scopes)

        element = create_element(f"m:{self.SERVICE_NAME}")
        add_xml_child(element, "m:SearchQueries", mailbox_query_element)
        add_xml_child(element, "m:ResultType", "PreviewOnly")
        add_xml_child(element, "m:PageSize", str(self.limit))

        return element


def search_mailboxes(client: EWSClient, args: dict):  # pragma: no cover
    """
    Search mailboxes for items matching the given filter.

    Args:
        client (EWSClient): The EWS client object.
        args (dict): The command arguments.

    Returns:
        dict: A dictionary containing the search results.

    Raises:
        Exception: If both mailbox_search_scope and email_addresses are provided, or if no searchable mailboxes are found.
    """
    filter = args.get("filter", "")
    limit = args.get("limit", 100)
    mailbox_search_scope = args.get("mailbox_search_scope")
    email_addresses = args.get("email_addresses")

    mailbox_ids = []
    limit_argument = arg_to_number(limit)
    if not limit_argument:
        raise DemistoException(f"Invalid limit value: {limit}. Please provide a valid integer.")

    if mailbox_search_scope is not None and email_addresses is not None:
        raise Exception("Use one of the arguments - mailbox-search-scope or email-addresses, not both")
    if email_addresses:
        email_addresses = email_addresses.split(",")
        all_mailboxes = GetSearchableMailboxes(protocol=client.get_protocol()).call()
        for email_address in email_addresses:
            for mailbox in all_mailboxes:
                addr = mailbox.get(MAILBOX, None)
                if addr and email_address.lower() == addr.lower():
                    mailbox_ids.append(mailbox[MAILBOX_ID])
        if len(mailbox_ids) == 0:
            raise Exception("No searchable mailboxes were found for the provided email addresses.")
    elif mailbox_search_scope:
        mailbox_ids = mailbox_search_scope if type(mailbox_search_scope) is list else [mailbox_search_scope]
    else:
        all_mailboxes = GetSearchableMailboxes(protocol=client.get_protocol()).call()
        mailbox_ids = [x[MAILBOX_ID] for x in all_mailboxes if x.get(MAILBOX_ID, None)]
    try:
        search_results = SearchMailboxes(protocol=client.get_protocol(), limit=limit_argument).call(filter, mailbox_ids)
    except TransportError as e:
        if "ItemCount>0<" in str(e):
            return "No results for search query: " + filter
        else:
            raise e

    return get_entry_for_object("Search mailboxes results", CONTEXT_UPDATE_EWS_ITEM, search_results)


def get_last_run(client: EWSClient):
    last_run = demisto.getLastRun()
    if not last_run or last_run.get(LAST_RUN_FOLDER) != client.folder_name:
        last_run = {LAST_RUN_TIME: None, LAST_RUN_FOLDER: client.folder_name, LAST_RUN_IDS: []}
    if LAST_RUN_TIME in last_run and last_run[LAST_RUN_TIME] is not None:
        last_run[LAST_RUN_TIME] = EWSDateTime.from_string(last_run[LAST_RUN_TIME])

    # In case we have existing last_run data
    if last_run.get(LAST_RUN_IDS) is None:
        last_run[LAST_RUN_IDS] = []

    return last_run


def fetch_last_emails(
    client: EWSClient,
    folder_name="Inbox",
    since_datetime=None,
    exclude_ids=None,
    fetch_all_history=False,
    fetch_time="10 minutes",
):
    account = client.get_account(client.account_email)
    qs = client.get_folder_by_path(folder_name, account, is_public=client.is_public_folder)
    demisto.debug(f"since_datetime: {since_datetime}")
    if since_datetime:
        qs = qs.filter(datetime_received__gte=since_datetime)
    else:
        if not fetch_all_history:
            tz = EWSTimeZone("UTC")
            first_fetch_datetime = dateparser.parse(fetch_time)
            if not first_fetch_datetime:
                raise DemistoException("Failed to parse first last run time")
            first_fetch_ews_datetime = first_fetch_datetime.astimezone(tz)
            qs = qs.filter(datetime_received__gte=first_fetch_ews_datetime)
    qs = qs.filter().only(*[x.name for x in Message.FIELDS])
    qs = qs.filter().order_by("datetime_received")
    result = []
    exclude_ids = exclude_ids if exclude_ids else set()
    demisto.debug(f"Exclude ID list: {exclude_ids}")

    for item in qs:
        try:
            demisto.debug(
                f"Looking on subject={item.subject}, "
                f"message_id={item.message_id}, "
                f"created={item.datetime_created}, "
                f"received={item.datetime_received}"
            )
            if isinstance(item, Message) and item.message_id not in exclude_ids:
                result.append(item)
                demisto.debug(f"Appending {item.subject}, {item.message_id}.")
                if len(result) >= client.max_fetch:
                    break
        except ValueError as exc:
            future_utils.raise_from(
                ValueError("Got an error when pulling incidents. You might be using the wrong exchange version."), exc
            )
            raise exc
        except ErrorMimeContentConversionFailed as exc:
            demisto.debug(f"Encountered an ErrorMimeContentConversionFailed error object while iterating: {exc}.\
                Continuing to next item.")
            continue
        except AttributeError as exc:
            demisto.debug(f"Encountered an Attribute error object while iterating: {exc}.\
                 Continuing to next item.")

    demisto.debug(f"EWS V2 - Got total of {len(result)} from ews query. ")
    return result


def keys_to_camel_case(value):
    def str_to_camel_case(snake_str):
        # Add condition as Email object arrived in list and raised error
        if not isinstance(snake_str, str):
            return snake_str
        components = snake_str.split("_")
        return components[0] + "".join(x.title() for x in components[1:])

    if value is None:
        return None
    if isinstance(value, list | set):
        return [keys_to_camel_case(v) for v in value]
    if isinstance(value, dict):
        return {
            keys_to_camel_case(k): keys_to_camel_case(v) if isinstance(v, list | dict) else v for (k, v) in list(value.items())
        }

    return str_to_camel_case(value)


def email_ec(item):  # pragma: no cover
    return {
        "CC": None if not item.cc_recipients else [mailbox.email_address for mailbox in item.cc_recipients],
        "BCC": None if not item.bcc_recipients else [mailbox.email_address for mailbox in item.bcc_recipients],
        "To": None if not item.to_recipients else [mailbox.email_address for mailbox in item.to_recipients],
        "From": item.author.email_address,
        "Subject": item.subject,
        "Text": item.text_body,
        "HTML": item.body,
        "HeadersMap": {} if not item.headers else {header.name: header.value for header in item.headers},
    }


def parse_object_as_dict_with_serialized_items(object):
    raw_dict = {}
    if object is not None:
        for field in object.FIELDS:
            try:
                v = getattr(object, field.name, None)
                if v is not None:
                    json.dumps(v)
                    raw_dict[field.name] = v
            except (TypeError, OverflowError):
                demisto.debug(f"Data in field {field.name} is not serilizable, skipped field value is \n{v}\n")
                continue
    return raw_dict


def parse_item_as_dict(item, email_address=None, camel_case=False, compact_fields=False):  # pragma: no cover
    def parse_object_as_dict(object):
        raw_dict = {}
        if object is not None:
            for field in object.FIELDS:
                field_val = getattr(object, field.name, None)
                try:
                    json.dumps(field_val)
                except TypeError:
                    field_val = parse_object_as_dict(field_val)
                raw_dict[field.name] = field_val
        return raw_dict

    def parse_folder_as_json(folder):  # pragma: no cover
        raw_dict = parse_object_as_dict(folder)
        if "parent_folder_id" in raw_dict:
            raw_dict["parent_folder_id"] = parse_folder_as_json(raw_dict["parent_folder_id"])
        if "effective_rights" in raw_dict:
            raw_dict["effective_rights"] = parse_object_as_dict(raw_dict["effective_rights"])
        return raw_dict

    raw_dict = parse_object_as_dict_with_serialized_items(item)

    if getattr(item, "attachments", None):
        raw_dict["attachments"] = [parse_attachment_as_dict(item.id, x) for x in item.attachments]

    for time_field in ["datetime_sent", "datetime_created", "datetime_received", "last_modified_time", "reminder_due_by"]:
        value = getattr(item, time_field, None)
        if value:
            raw_dict[time_field] = value.ewsformat()

    for dict_field in [
        "effective_rights",
        "parent_folder_id",
        "conversation_id",
        "author",
        "extern_id",
        "received_by",
        "received_representing",
        "reply_to",
        "sender",
        "folder",
    ]:
        value = getattr(item, dict_field, None)
        if value:
            if type(value) is list:
                raw_dict[dict_field] = [parse_object_as_dict(x) for x in value]
            else:
                raw_dict[dict_field] = parse_object_as_dict(value)

    for list_dict_field in ["headers", "cc_recipients", "to_recipients"]:
        value = getattr(item, list_dict_field, None)
        if value:
            raw_dict[list_dict_field] = [parse_object_as_dict(x) for x in value]

    for list_str_field in ["categories"]:
        value = getattr(item, list_str_field, None)
        if value:
            raw_dict[list_str_field] = value

    if getattr(item, "folder", None):
        raw_dict["folder"] = parse_folder_as_json(item.folder)
        folder_path = (
            item.folder.absolute[len(TOIS_PATH) :] if item.folder.absolute.startswith(TOIS_PATH) else item.folder.absolute
        )
        raw_dict["folder_path"] = folder_path

    raw_dict["item_id"] = getattr(item, "id", None)
    raw_dict["id"] = getattr(item, "id", None)

    if compact_fields:
        new_dict = {}
        fields_list = [
            "datetime_created",
            "datetime_received",
            "datetime_sent",
            "sender",
            "has_attachments",
            "importance",
            "message_id",
            "last_modified_time",
            "size",
            "subject",
            "text_body",
            "headers",
            "body",
            "folder_path",
            "is_read",
            "categories",
        ]

        fields_list.append("item_id")

        for field in fields_list:
            if field in raw_dict:
                new_dict[field] = raw_dict.get(field)
        for field in ["received_by", "author", "sender"]:
            if field in raw_dict:
                new_dict[field] = raw_dict.get(field, {}).get("email_address")
        for field in ["to_recipients"]:
            if field in raw_dict:
                new_dict[field] = [x.get("email_address") for x in raw_dict[field]]
        attachments = raw_dict.get("attachments")
        if attachments and len(attachments) > 0:
            file_attachments = [x for x in attachments if x[ATTACHMENT_TYPE] == FILE_ATTACHMENT_TYPE]
            if len(file_attachments) > 0:
                new_dict["FileAttachments"] = file_attachments
            item_attachments = [x for x in attachments if x[ATTACHMENT_TYPE] == ITEM_ATTACHMENT_TYPE]
            if len(item_attachments) > 0:
                new_dict["ItemAttachments"] = item_attachments
        raw_dict = new_dict

    if camel_case:
        raw_dict = keys_to_camel_case(raw_dict)

    if email_address:
        raw_dict[MAILBOX] = email_address
    return raw_dict


def cast_mime_item_to_message(item):
    mime_content = item.mime_content
    email_policy = SMTP if mime_content.isascii() else SMTPUTF8
    if isinstance(mime_content, bytes):
        demisto.debug("Returning message as bytes")
        return email.message_from_bytes(mime_content, policy=email_policy)  # type: ignore[arg-type]
    else:
        demisto.debug("Returning message as string")
        return email.message_from_string(mime_content, policy=email_policy)  # type: ignore[arg-type]


def parse_incident_from_item(item, is_fetch, mark_as_read):  # pragma: no cover
    incident = {}
    labels = []

    try:
        try:
            incident["details"] = item.text_body or item.body
        except AttributeError:
            incident["details"] = item.body

        incident["name"] = item.subject
        labels.append({"type": "Email/subject", "value": item.subject})
        incident["occurred"] = item.datetime_created.ewsformat()

        # handle recipients
        if item.to_recipients:
            for recipient in item.to_recipients:
                labels.append({"type": "Email", "value": recipient.email_address})

        # handle cc
        if item.cc_recipients:
            for recipient in item.cc_recipients:
                labels.append({"type": "Email/cc", "value": recipient.email_address})
        # handle email from
        if item.sender:
            labels.append({"type": "Email/from", "value": item.sender.email_address})

        # email format
        email_format = ""
        try:
            if item.text_body:
                labels.append({"type": "Email/text", "value": item.text_body})
                email_format = "text"
        except AttributeError:
            pass
        if item.body:
            labels.append({"type": "Email/html", "value": item.body})
            email_format = "HTML"
        labels.append({"type": "Email/format", "value": email_format})

        # handle attachments
        if item.attachments:
            incident["attachment"] = []
            for attachment in item.attachments:
                if attachment is not None:
                    attachment.parent_item = item
                    file_result = None
                    label_attachment_type = None
                    label_attachment_id_type = None
                    if isinstance(attachment, FileAttachment):
                        try:
                            if attachment.content:
                                # file attachment
                                label_attachment_type = "attachments"
                                label_attachment_id_type = "attachmentId"

                                # save the attachment
                                file_name = get_attachment_name(
                                    attachment_name=attachment.name,
                                    content_id=attachment.content_id,
                                    is_inline=attachment.is_inline,
                                )
                                file_result = fileResult(file_name, attachment.content)

                                # check for error
                                if file_result["Type"] == entryTypes["error"]:
                                    demisto.error(file_result["Contents"])
                                    raise Exception(file_result["Contents"])

                                # save attachment to incident
                                incident["attachment"].append(
                                    {
                                        "path": file_result["FileID"],
                                        "name": get_attachment_name(
                                            attachment_name=attachment.name,
                                            content_id=attachment.content_id,
                                            is_inline=attachment.is_inline,
                                        ),
                                        "description": FileAttachmentType.ATTACHED if not attachment.is_inline else "",
                                    }
                                )
                        except TypeError as e:
                            if str(e) != "must be string or buffer, not None":
                                raise
                            continue
                        except ErrorCannotOpenFileAttachment as e:
                            if str(e) != "The attachment could not be opened.":
                                raise
                            demisto.error(f"Skipped attachment: {attachment.name} - {e}")
                            continue
                    else:
                        # other item attachment
                        label_attachment_type = "attachmentItems"
                        label_attachment_id_type = "attachmentItemsId"
                        formatted_message: str | bytes
                        # save the attachment
                        if hasattr(attachment, "item") and attachment.item.mime_content:
                            # Some items arrive with bytes attachemnt
                            attached_email = cast_mime_item_to_message(attachment.item)
                            if attachment.item.headers:
                                attached_email_headers = []
                                for h, v in list(attached_email.items()):
                                    if not isinstance(v, str):
                                        try:
                                            v = str(v)
                                        except:  # noqa: E722
                                            demisto.debug(f'cannot parse the header "{h}"')
                                            continue

                                    v = " ".join(map(str.strip, v.split("\r\n")))
                                    attached_email_headers.append((h.lower(), v))

                                for header in attachment.item.headers:
                                    if (
                                        header.name.lower(),
                                        header.value,
                                    ) not in attached_email_headers and header.name.lower() != "content-type":
                                        try:
                                            attached_email.add_header(header.name, header.value)
                                        except ValueError as err:
                                            if "There may be at most" not in str(err):
                                                raise err
                            formatted_message = get_formatted_message(attached_email)
                            file_result = fileResult(
                                get_attachment_name(
                                    attachment_name=attachment.name,
                                    content_id=attachment.content_id,
                                    is_inline=attachment.is_inline,
                                    attachment_subject=attachment.item.subject,
                                )
                                + ".eml",
                                formatted_message,
                            )

                        if file_result:
                            # check for error
                            if file_result["Type"] == entryTypes["error"]:
                                demisto.error(file_result["Contents"])
                                raise Exception(file_result["Contents"])

                            # save attachment to incident
                            incident["attachment"].append(
                                {
                                    "path": file_result["FileID"],
                                    "name": get_attachment_name(
                                        attachment_name=attachment.name,
                                        content_id=attachment.content_id,
                                        is_inline=attachment.is_inline,
                                        attachment_subject=attachment.item.subject,
                                    )
                                    + ".eml",
                                }
                            )

                        else:
                            incident["attachment"].append(
                                {
                                    "name": get_attachment_name(
                                        attachment_name=attachment.name,
                                        content_id=attachment.content_id,
                                        is_inline=attachment.is_inline,
                                        attachment_subject=attachment.item.subject,
                                    )
                                    + ".eml",
                                }
                            )

                    labels.append(
                        {
                            "type": label_attachment_type,
                            "value": get_attachment_name(
                                attachment_name=attachment.name,
                                content_id=attachment.content_id,
                                is_inline=attachment.is_inline,
                                attachment_subject="" if isinstance(attachment, FileAttachment) else attachment.item.subject,
                            ),
                        }
                    )

                    labels.append({"type": label_attachment_id_type, "value": attachment.attachment_id.id})

        # handle headers
        if item.headers:
            headers = []
            for header in item.headers:
                labels.append({"type": f"Email/Header/{header.name}", "value": str(header.value)})
                headers.append(f"{header.name}: {header.value}")
            labels.append({"type": "Email/headers", "value": "\r\n".join(headers)})

        # handle item id
        if item.message_id:
            labels.append({"type": "Email/MessageId", "value": str(item.message_id)})
            # fetch history
            incident["dbotMirrorId"] = str(item.message_id)

        if item.id:
            labels.append({"type": "Email/ID", "value": item.id})
            labels.append({"type": "Email/itemId", "value": item.id})

        # handle conversion id
        if item.conversation_id:
            labels.append({"type": "Email/ConversionID", "value": item.conversation_id.id})

        if mark_as_read and is_fetch:
            item.is_read = True
            try:
                item.save()
            except ErrorIrresolvableConflict:
                time.sleep(0.5)
                item.save()
            except ValueError as e:
                if item.subject and len(item.subject) > 255:
                    demisto.debug(
                        "Length of message subject is greater than 255, item.save could not handle it, cutting the subject."
                    )
                    sub_subject = f"Length of subject greater than 255 characters. Partial subject: {item.subject[:180]}"
                    item.subject = sub_subject
                    item.save()
                else:
                    raise e

        incident["labels"] = labels
        incident["rawJSON"] = json.dumps(parse_item_as_dict(item, None), ensure_ascii=False)

    except Exception as e:
        if "Message is not decoded yet" in str(e):
            demisto.debug("EWS v2 - Skipped a protected message")
            return None
        else:
            raise e

    return incident


def get_formatted_message(attached_email) -> str | bytes:
    try:
        demisto.debug("Formatting attached mail as string")
        return attached_email.as_string()
    except Exception as e:
        demisto.info(f"Could not parse attached mail as string, trying as bytes.\n{e}")
        try:
            return attached_email.as_bytes()
        except Exception as e:
            # Python 3.12 hardened the email library: as_string()/as_bytes() raise
            # email.errors.HeaderWriteError for headers containing embedded newlines
            # (policy.verify_generated_headers defaults to True). Retry with a policy
            # that does not verify generated headers to preserve the previous lenient
            # behavior of returning the message as bytes even when a header is malformed.
            demisto.info(f"Could not parse attached mail as bytes, retrying with a non-verifying policy.\n{e}")
            try:
                current_policy = getattr(attached_email, "policy", SMTP)
                lenient_policy = current_policy.clone(verify_generated_headers=False)
                return attached_email.as_bytes(policy=lenient_policy)
            except Exception as e:
                demisto.error(f"Could not parse attached mail as bytes, {e}")
                raise Exception(f"Could not format message, {e}")


def fetch_emails_as_incidents(
    client: EWSClient, skip_unparsable_emails: bool = False, fetch_all_history=False, fetch_time="10 minutes"
) -> list[dict]:
    last_run = get_last_run(client)
    excluded_ids = set(last_run.get(LAST_RUN_IDS, []))

    try:
        last_emails = fetch_last_emails(
            client, client.folder_name, last_run.get(LAST_RUN_TIME), excluded_ids, fetch_all_history, fetch_time
        )

        incidents = []
        incident = {}  # type: Dict[Any, Any]
        current_fetch_ids = set()
        last_incident_run_time = None

        for item in last_emails:
            try:
                if item.message_id:
                    current_fetch_ids.add(item.message_id)
                    incident = parse_incident_from_item(item, True, client.mark_as_read)
                    demisto.debug(f"Parsed incident: {item.message_id}")
                    if incident:
                        incidents.append(incident)
                        last_incident_run_time = item.datetime_received
                        demisto.debug(f"Appended incident: {item.message_id}")

                    if len(incidents) >= client.max_fetch:
                        break
            except Exception as e:
                if not skip_unparsable_emails:  # default is to raise and exception and fail the command
                    raise

                # when the skip param is `True`, we log the exceptions and move on instead of failing the whole fetch
                error_msg = (
                    "Encountered email parsing issue while fetching. "
                    f"Skipping item with message id: {item.message_id or '<error parsing message_id>'}"
                )
                demisto.debug(f"{error_msg}, Error: {e!s} {traceback.format_exc()}")
                demisto.updateModuleHealth(error_msg, is_error=False)

        demisto.debug(f"EWS V2 - ending fetch - got {len(incidents)} incidents.")
        last_fetch_time = last_run.get(LAST_RUN_TIME)
        last_incident_run_time = last_incident_run_time if last_incident_run_time else last_fetch_time

        # making sure both last fetch time and the time of last incident are the same type for comparing.
        if isinstance(last_incident_run_time, EWSDateTime):
            last_incident_run_time = last_incident_run_time.ewsformat()

        if isinstance(last_fetch_time, EWSDateTime):
            last_fetch_time = last_fetch_time.ewsformat()

        debug_msg = "#### last_incident_time: {}({}). last_fetch_time: {}({}) ####"
        demisto.debug(
            debug_msg.format(last_incident_run_time, type(last_incident_run_time), last_fetch_time, type(last_fetch_time))
        )

        # If the fetch query is not fully fetched (we didn't have any time progress) - then we keep the
        # id's from current fetch until progress is made. This is for when max_fetch < incidents_from_query.
        if not last_incident_run_time or not last_fetch_time or last_incident_run_time > last_fetch_time:
            ids = current_fetch_ids
        else:
            ids = current_fetch_ids | excluded_ids
        new_last_run = {
            LAST_RUN_TIME: last_incident_run_time,
            LAST_RUN_FOLDER: client.folder_name,
            LAST_RUN_IDS: list(ids),
            ERROR_COUNTER: 0,
        }

        demisto.setLastRun(new_last_run)
        return incidents

    except RateLimitError:
        if LAST_RUN_TIME in last_run:
            last_run[LAST_RUN_TIME] = last_run[LAST_RUN_TIME].ewsformat()
        if ERROR_COUNTER not in last_run:
            last_run[ERROR_COUNTER] = 0
        last_run[ERROR_COUNTER] += 1
        demisto.setLastRun(last_run)
        if last_run[ERROR_COUNTER] > 2:
            raise
        return []


def get_entry_for_file_attachment(item_id, attachment):  # pragma: no cover
    entry = fileResult(
        get_attachment_name(attachment_name=attachment.name, content_id=attachment.content_id, is_inline=attachment.is_inline),
        attachment.content,
    )
    ec = {CONTEXT_UPDATE_EWS_ITEM_FOR_ATTACHMENT + CONTEXT_UPDATE_FILE_ATTACHMENT: parse_attachment_as_dict(item_id, attachment)}
    entry[ENTRY_CONTEXT] = filter_dict_null(ec)
    return entry


def parse_attachment_as_dict(item_id, attachment):  # pragma: no cover
    try:
        # if this is a file attachment or a non-empty email attachment
        if isinstance(attachment, FileAttachment) or hasattr(attachment, "item"):
            attachment_content = attachment.content if isinstance(attachment, FileAttachment) else attachment.item.mime_content

            return {
                ATTACHMENT_ORIGINAL_ITEM_ID: item_id,
                ATTACHMENT_ID: attachment.attachment_id.id,
                "attachmentName": get_attachment_name(
                    attachment_name=attachment.name,
                    content_id=attachment.content_id,
                    is_inline=attachment.is_inline,
                    attachment_subject="" if isinstance(attachment, FileAttachment) else attachment.item.subject,
                ),
                "attachmentSHA256": hashlib.sha256(attachment_content).hexdigest() if attachment_content else None,
                "attachmentContentType": attachment.content_type,
                "attachmentContentId": attachment.content_id,
                "attachmentContentLocation": attachment.content_location,
                "attachmentSize": attachment.size,
                "attachmentLastModifiedTime": attachment.last_modified_time.ewsformat(),
                "attachmentIsInline": attachment.is_inline,
                ATTACHMENT_TYPE: FILE_ATTACHMENT_TYPE if isinstance(attachment, FileAttachment) else ITEM_ATTACHMENT_TYPE,
            }

        # If this is an empty email attachment
        else:
            return {
                ATTACHMENT_ORIGINAL_ITEM_ID: item_id,
                ATTACHMENT_ID: attachment.attachment_id.id,
                "attachmentName": get_attachment_name(
                    attachment_name=attachment.name,
                    content_id=attachment.content_id,
                    is_inline=attachment.is_inline,
                    attachment_subject="" if isinstance(attachment, FileAttachment) else attachment.item.subject,
                ),
                "attachmentSize": attachment.size,
                "attachmentLastModifiedTime": attachment.last_modified_time.ewsformat(),
                "attachmentIsInline": attachment.is_inline,
                ATTACHMENT_TYPE: FILE_ATTACHMENT_TYPE if isinstance(attachment, FileAttachment) else ITEM_ATTACHMENT_TYPE,
            }

    except (TypeError, ErrorCannotOpenFileAttachment) as e:
        if str(e) not in ("must be string or buffer, not None", "The attachment could not be opened."):
            raise
        demisto.debug(f"Add attachment info to context, without the content. Error: {e!s}")
        return {
            ATTACHMENT_ORIGINAL_ITEM_ID: item_id,
            ATTACHMENT_ID: attachment.attachment_id.id,
            "attachmentName": get_attachment_name(
                attachment_name=attachment.name,
                content_id=attachment.content_id,
                is_inline=attachment.is_inline,
                attachment_subject="" if isinstance(attachment, FileAttachment) else attachment.item.subject,
            ),
            "attachmentSHA256": None,
            "attachmentContentType": attachment.content_type,
            "attachmentContentId": attachment.content_id,
            "attachmentContentLocation": attachment.content_location,
            "attachmentSize": attachment.size,
            "attachmentLastModifiedTime": attachment.last_modified_time.ewsformat(),
            "attachmentIsInline": attachment.is_inline,
            ATTACHMENT_TYPE: FILE_ATTACHMENT_TYPE if isinstance(attachment, FileAttachment) else ITEM_ATTACHMENT_TYPE,
        }


def get_entry_for_item_attachment(item_id, attachment, target_email):  # pragma: no cover
    item = attachment.item
    dict_result = parse_attachment_as_dict(item_id, attachment)
    dict_result.update(parse_item_as_dict(item, target_email, camel_case=True, compact_fields=True))
    title = f'EWS get attachment got item for "{target_email}", "{get_attachment_name(attachment_name=attachment.name, content_id=attachment.content_id, is_inline=attachment.is_inline, attachment_subject=attachment.item.subject)}"'  # noqa: E501

    return get_entry_for_object(title, CONTEXT_UPDATE_EWS_ITEM_FOR_ATTACHMENT + CONTEXT_UPDATE_ITEM_ATTACHMENT, dict_result)


def fetch_attachments_for_message(client: EWSClient, args: dict) -> list:  # pragma: no cover
    item_id = args.get("item_id", "")
    target_mailbox = args.get("target_mailbox")
    attachment_ids = args.get("attachment_ids", [])
    identifiers_filter = args.get("identifiers_filter", "")

    identifiers_filter = argToList(identifiers_filter)
    account = client.get_account(target_mailbox or client.account_email)
    attachment_ids = argToList(attachment_ids)
    attachments = client.get_attachments_for_item(item_id, account, attachment_ids)
    entries = []
    for attachment in attachments:
        if isinstance(attachment, FileAttachment):
            try:
                if attachment.content:
                    entries.append(get_entry_for_file_attachment(item_id, attachment))
            except (TypeError, ErrorCannotOpenFileAttachment) as e:
                if str(e) not in ("must be string or buffer, not None", "The attachment could not be opened."):
                    raise
                demisto.debug(f"Skipping attachment '{attachment.name}', Error: {e}")
        else:
            entries.append(get_entry_for_item_attachment(item_id, attachment, account.primary_smtp_address))
            if attachment.item.mime_content:
                attached_email = cast_mime_item_to_message(attachment.item)
                entries.append(
                    fileResult(
                        get_attachment_name(
                            attachment.name,
                            content_id=attachment.content_id,
                            is_inline=attachment.is_inline,
                            attachment_subject=attachment.item.subject,
                        )
                        + ".eml",
                        get_formatted_message(attached_email),
                    )
                )

    return entries


def prepare_args(d):  # pragma: no cover
    d = {k.replace("-", "_"): v for k, v in list(d.items())}
    if "is_public" in d:
        d["is_public"] = d["is_public"] == "True"
    return d


def get_limited_number_of_messages_from_qs(qs, limit):  # pragma: no cover
    count = 0
    results = []
    for item in qs:
        if count == limit:
            break
        if isinstance(item, Message):
            count += 1
            results.append(item)
    return results


def search_items_in_mailbox(client: EWSClient, args: dict):  # pragma: no cover
    query = args.get("query")
    message_id = args.get("message_id")
    folder_path = args.get("folder_path", "")
    limit = args.get("limit", 100)
    target_mailbox = args.get("target_mailbox")
    is_public = args.get("is_public")
    selected_fields = args.get("selected_fields", "all")
    surround_id_with_angle_brackets = argToBoolean(args.get("surround_id_with_angle_brackets", True))

    if not query and not message_id:
        return_error("Missing required argument. Provide query or message-id")
    if argToBoolean(surround_id_with_angle_brackets) and message_id and message_id[0] != "<" and message_id[-1] != ">":
        message_id = f"<{message_id}>"
    account = client.get_account(target_mailbox or client.account_email)
    limit = int(limit)
    if folder_path.lower() == "inbox":
        folders = [account.inbox]
    elif folder_path:
        is_public = client.is_default_folder(folder_path, is_public)
        folders = [client.get_folder_by_path(folder_path, account, is_public)]
    else:
        folders = FolderCollection(account=account, folders=[account.root.tois]).find_folders()  # pylint: disable=E1101
    items = []  # type: ignore
    selected_all_fields = selected_fields == "all"
    if selected_all_fields:
        restricted_fields = {x.name for x in Message.FIELDS}  # type: ignore
    else:
        restricted_fields = set(argToList(selected_fields))  # type: ignore
        restricted_fields.update(["id", "message_id"])  # type: ignore

    for folder in folders:
        if Message not in folder.supported_item_models:
            continue
        if query:
            items_qs = folder.filter(query).only(*restricted_fields)
        else:
            items_qs = folder.filter(message_id=message_id).only(*restricted_fields)
        items += get_limited_number_of_messages_from_qs(items_qs, limit)
        if len(items) >= limit:
            break
    items = items[:limit]
    searched_items_result = [
        parse_item_as_dict(item, account.primary_smtp_address, camel_case=True, compact_fields=selected_all_fields)
        for item in items
    ]
    if not selected_all_fields:
        # we show id as 'itemId' for BC
        restricted_fields.remove("id")
        restricted_fields.add("itemId")
        searched_items_result = [
            {k: v for (k, v) in i.items() if k in keys_to_camel_case(restricted_fields)} for i in searched_items_result
        ]
    return get_entry_for_object(
        "Searched items",
        CONTEXT_UPDATE_EWS_ITEM,
        searched_items_result,
        headers=ITEMS_RESULTS_HEADERS if selected_all_fields else None,
    )


def parse_physical_address(address):
    result = {}
    for attr in ["city", "country", "label", "state", "street", "zipcode"]:
        result[attr] = getattr(address, attr, None)
    return result


def parse_phone_number(phone_number):
    result = {attr: getattr(phone_number, attr, None) for attr in ["label", "phone_number"]}
    return result if result.get("phone_number") else {}


def is_jsonable(x):
    try:
        json.dumps(x)
        return True
    except Exception:
        return False


def parse_contact(contact):
    contact_dict = parse_object_as_dict_with_serialized_items(contact)
    for k in contact_dict:
        v = contact_dict[k]
        if isinstance(v, EWSDateTime):
            contact_dict[k] = v.ewsformat()  # pylint: disable=E4702

    contact_dict["id"] = contact.id
    if isinstance(contact, Contact) and contact.physical_addresses:
        contact_dict["physical_addresses"] = list(map(parse_physical_address, contact.physical_addresses))
    if isinstance(contact, Contact) and contact.phone_numbers:
        contact_dict["phone_numbers"] = [elt for elt in map(parse_phone_number, contact.phone_numbers) if elt]
    if isinstance(contact, Contact) and contact.email_addresses and len(contact.email_addresses) > 0:
        contact_dict["emailAddresses"] = [x.email for x in contact.email_addresses]
    contact_dict = keys_to_camel_case(contact_dict)
    contact_dict = {k: v for k, v in contact_dict.items() if (v and is_jsonable(v))}
    return contact_dict


def get_contacts(client: EWSClient, args: dict):  # pragma: no cover
    limit = args.get("limit", 100)
    target_mailbox = args.get("target_mailbox")

    account = client.get_account(target_mailbox or client.account_email)
    contacts = []

    for contact in account.contacts.all()[: int(limit)]:  # pylint: disable=E1101
        contact = parse_contact(contact)
        contact["originMailbox"] = target_mailbox
        contacts.append(contact)
    return get_entry_for_object(
        f"Email contacts for {target_mailbox or client.account_email}",
        "Account.Email(val.Address == obj.originMailbox).EwsContacts",
        contacts,
    )


def find_folders(client: EWSClient, args: dict):  # pragma: no cover
    target_mailbox = args.get("target_mailbox")
    is_public = args.get("is_public")

    account = client.get_account(target_mailbox or client.account_email)
    root = account.public_folders_root if is_public else account.root.tois  # pylint: disable=E1101
    root_collection = FolderCollection(account=account, folders=[root])
    folders = []
    for f in root_collection.find_folders():  # pylint: disable=E1101
        folder = folder_to_context_entry(f)
        folders.append(folder)

    readable_output = root.tree()  # pylint: disable=E1101

    return {
        "Type": entryTypes["note"],
        "Contents": folders,
        "ContentsFormat": formats["json"],
        "ReadableContentsFormat": formats["markdown"],
        "HumanReadable": readable_output,
        ENTRY_CONTEXT: {"EWS.Folders(val.id == obj.id)": folders},
    }


def get_items_from_folder(client: EWSClient, args: dict):  # pragma: no cover
    folder_path = args.get("folder_path", "")
    limit = args.get("limit", 100)
    target_mailbox = args.get("target_mailbox")
    is_public = args.get("is_public")
    get_internal_item = args.get("get_internal_item", "no")

    account = client.get_account(target_mailbox or client.account_email)
    limit = int(limit)
    get_internal_item = get_internal_item == "yes"
    is_public = client.is_default_folder(folder_path, is_public)
    folder = client.get_folder_by_path(folder_path, account, is_public)
    qs = folder.filter().order_by("-datetime_created")[:limit]
    items = get_limited_number_of_messages_from_qs(qs, limit)
    items_result = []

    for item in items:
        item_attachment = parse_item_as_dict(item, account.primary_smtp_address, camel_case=True, compact_fields=True)

        for attachment in item.attachments:
            if attachment is not None:
                attachment.parent_item = item
                if get_internal_item and isinstance(attachment, ItemAttachment) and isinstance(attachment.item, Message):
                    # if found item attachment - switch item to the attchment
                    item_attachment = parse_item_as_dict(
                        attachment.item, account.primary_smtp_address, camel_case=True, compact_fields=True
                    )
                    break

        items_result.append(item_attachment)
    hm_headers = ["sender", "subject", "hasAttachments", "datetimeReceived", "receivedBy", "author", "toRecipients", "itemId"]
    return get_entry_for_object("Items in folder " + folder_path, CONTEXT_UPDATE_EWS_ITEM, items_result, headers=hm_headers)


def get_items(client: EWSClient, args: dict):  # pragma: no cover
    item_ids = args.get("item_ids", "")
    target_mailbox = args.get("target_mailbox")

    account = client.get_account(target_mailbox or client.account_email)
    item_ids = argToList(item_ids)

    items = client.get_items_from_mailbox(account, item_ids)
    items = [x for x in items if isinstance(x, Message)]
    items_as_incidents = [parse_incident_from_item(x, False, client.mark_as_read) for x in items]
    items_to_context = [parse_item_as_dict(x, account.primary_smtp_address, True, True) for x in items]

    return {
        "Type": entryTypes["note"],
        "Contents": items_as_incidents,
        "ContentsFormat": formats["json"],
        "ReadableContentsFormat": formats["markdown"],
        "HumanReadable": tableToMarkdown("Get items", items_to_context, ITEMS_RESULTS_HEADERS),
        ENTRY_CONTEXT: {
            CONTEXT_UPDATE_EWS_ITEM: items_to_context,
            "Email": [email_ec(item) for item in items],
        },
    }


def get_autodiscovery_config():  # pragma: no cover
    config_dict = demisto.getIntegrationContext()
    return {
        "Type": entryTypes["note"],
        "Contents": config_dict,
        "ContentsFormat": formats["json"],
        "ReadableContentsFormat": formats["markdown"],
        "HumanReadable": tableToMarkdown("Auto-Discovery Exchange Configuration", config_dict),
    }


def format_identifier(identifier):
    """
    Exchangelib has a default smtp routing type. If there's no given routingtype, add explicitly so that
    exchangelib can be searched by secondary email addresses without making cusomter add it manually
    """
    return f"smtp:{identifier}" if "@" in identifier and ":" not in identifier else identifier


def resolve_name_command(client: EWSClient, args):  # pragma: no cover
    protocol = client.get_protocol()
    unresolved_entry = format_identifier(args["identifier"])
    full_contact_data = argToBoolean(args.get("full_contact_data", True))
    resolved_names = protocol.resolve_names([unresolved_entry], return_full_contact_data=full_contact_data, search_scope="")
    demisto.debug(f"{len(resolved_names)=}")

    output = []
    for rn in resolved_names:
        if isinstance(rn, ErrorNameResolutionNoResults):
            demisto.info(f"got ErrorNameResolutionNoResults error, {rn!s}")
            return "No results were found."
        elif isinstance(rn, tuple):
            mail, contact = rn
        else:
            mail, contact = rn, None
        mail_context = parse_item_as_dict(mail)
        if contact:
            mail_context["FullContactInfo"] = parse_contact(contact)
        output.append(mail_context)
    return get_entry_for_object(
        "Resolved Names",
        "EWS.ResolvedNames(val.email_address && val.email_address == obj.email_address)",
        remove_empty_elements(output),  # noqa: F405
        headers=["primary_email_address", "name", "mailbox_type", "routing_type"],
        hr_header_changes={"email_address": "primary_email_address"},
    )


def get_item_as_eml(client: EWSClient, args: dict):  # pragma: no cover
    item_id = args.get("item_id", "")
    target_mailbox = args.get("target_mailbox")

    account = client.get_account(target_mailbox or client.account_email)
    item = client.get_item_from_mailbox(account, item_id)

    if item.mime_content:
        # came across an item with bytes attachemnt which failed in the source code, added this to keep functionality
        email_content = cast_mime_item_to_message(item)
        if item.headers:
            attached_email_headers = []
            for h, v in list(email_content.items()):
                if not isinstance(v, str):
                    try:
                        v = str(v)
                    except:  # noqa: E722
                        demisto.debug(f'cannot parse the header "{h}"')

                v = " ".join(map(str.strip, v.split("\r\n")))
                attached_email_headers.append((h.lower(), v))
            for header in item.headers:
                if (header.name.lower(), header.value) not in attached_email_headers and header.name.lower() != "content-type":
                    try:
                        email_content.add_header(header.name, header.value)
                    except ValueError as err:
                        if "There may be at most" not in str(err):
                            raise err
        eml_name = item.subject if item.subject else "demisto_untitled_eml"
        file_result = fileResult(eml_name + ".eml", get_formatted_message(email_content))
        file_result = file_result if file_result else "Failed uploading eml file to war room"

        return file_result
    return None


def collect_manual_attachments(manual_attach_obj):  # pragma: no cover
    attachments = []
    for attachment in manual_attach_obj:
        res = demisto.getFilePath(os.path.basename(attachment["RealFileName"]))

        file_path = res["path"]
        with open(file_path, "rb") as f:
            attachments.append(FileAttachment(content=f.read(), name=attachment["FileName"]))

    return attachments


def process_attachments(attach_cids="", attach_ids="", attach_names="", manual_attach_obj=None):  # pragma: no cover
    if manual_attach_obj is None:
        manual_attach_obj = []
    file_entries_for_attachments = []  # type: list
    attachments_names = []  # type: list

    if attach_ids:
        file_entries_for_attachments = attach_ids if isinstance(attach_ids, list) else attach_ids.split(",")
        if attach_names:
            attachments_names = attach_names if isinstance(attach_names, list) else attach_names.split(",")
        else:
            for att_id in file_entries_for_attachments:
                att_name = demisto.getFilePath(att_id)["name"]
                if isinstance(att_name, list):
                    att_name = att_name[0]
                attachments_names.append(att_name)
        if len(file_entries_for_attachments) != len(attachments_names):
            raise Exception("attachIDs and attachNames lists should be the same length")

    attachments = collect_manual_attachments(manual_attach_obj)

    if attach_cids:
        file_entries_for_attachments_inline = attach_cids if isinstance(attach_cids, list) else attach_cids.split(",")
        for att_id_inline in file_entries_for_attachments_inline:
            try:
                file_info = demisto.getFilePath(att_id_inline)
            except Exception as ex:
                demisto.info(f"EWS error from getFilePath: {ex}")
                raise Exception(f"entry {att_id_inline} does not contain a file")
            att_name_inline = file_info["name"]
            with open(file_info["path"], "rb") as f:
                attachments.append(
                    FileAttachment(content=f.read(), name=att_name_inline, is_inline=True, content_id=att_name_inline)
                )

    for i in range(len(file_entries_for_attachments)):
        entry_id = file_entries_for_attachments[i]
        attachment_name = attachments_names[i]
        try:
            res = demisto.getFilePath(entry_id)
        except Exception as ex:
            raise Exception(f"entry {entry_id} does not contain a file: {ex!s}")
        file_path = res["path"]
        with open(file_path, "rb") as f:
            attachments.append(FileAttachment(content=f.read(), name=attachment_name))
    return attachments, attachments_names


def get_none_empty_addresses(addresses_ls):
    return [adress for adress in addresses_ls if adress]


def send_email(client: EWSClient, args):
    time_zone = get_time_zone()
    account = client.get_account(target_mailbox=client.account_email, time_zone=time_zone)
    bcc = get_none_empty_addresses(argToList(args.get("bcc")))
    cc = get_none_empty_addresses(argToList(args.get("cc")))
    to = get_none_empty_addresses(argToList(args.get("to")))
    replyTo = get_none_empty_addresses(argToList(args.get("replyTo")))
    render_body = argToBoolean(args.get("renderBody") or False)
    handle_inline_image: bool = argToBoolean(args.get("handle_inline_image", True))
    subject = args.get("subject")
    subject = subject[:252] + "..." if len(subject) > 255 else subject

    attachments, attachments_names = process_attachments(
        args.get("attachCIDs", ""), args.get("attachIDs", ""), args.get("attachNames", ""), args.get("manualAttachObj") or []
    )

    body_type = args.get("bodyType", args.get("body_type"))
    send_email_to_mailbox(
        account=account,
        to=to,
        subject=subject,
        body=args.get("body"),
        body_type=body_type,
        bcc=bcc,
        cc=cc,
        reply_to=replyTo,
        handle_inline_image=handle_inline_image,
        html_body=args.get("htmlBody"),
        attachments=attachments,
        raw_message=args.get("raw_message"),
        from_address=args.get("from"),
    )
    result_object = {
        "from": args.get("from") or account.primary_smtp_address,
        "to": to,
        "subject": subject,
        "attachments": attachments_names,
    }

    results = [
        {
            "Type": entryTypes["note"],
            "Contents": result_object,
            "ContentsFormat": formats["json"],
            "ReadableContentsFormat": formats["markdown"],
            "HumanReadable": tableToMarkdown("Sent email", result_object),
        }
    ]
    if render_body:
        results.append({"Type": entryTypes["note"], "ContentsFormat": formats["html"], "Contents": args.get("htmlBody")})
    return results


def reply_email(client: EWSClient, args: dict):  # pragma: no cover
    time_zone = get_time_zone()
    account = client.get_account(target_mailbox=client.account_email, time_zone=time_zone)
    bcc = argToList(args.get("bcc"))
    cc = argToList(args.get("cc"))
    to = argToList(args.get("to"))
    handle_inline_image: bool = argToBoolean(args.get("handle_inline_image", True))
    from_mailbox = args.get("from")
    subject = args.get("subject", "")
    subject = subject[:252] + "..." if subject and len(subject) > 255 else subject

    attachments, attachments_names = process_attachments(
        args.get("attachCIDs", ""), args.get("attachIDs", ""), args.get("attachNames", ""), args.get("manualAttachObj") or []
    )

    client.reply_email(
        in_reply_to=args.get("inReplyTo", ""),
        to=to,
        body=args.get("body", ""),
        subject=subject,
        bcc=bcc,
        cc=cc,
        html_body=args.get("htmlBody"),
        attachments=attachments,
        from_mailbox=from_mailbox,
        account=account,
        handle_inline_image=handle_inline_image,
    )
    result_object = {
        "from": from_mailbox or account.primary_smtp_address,
        "to": to,
        "subject": subject,
        "attachments": attachments_names,
    }

    return {
        "Type": entryTypes["note"],
        "Contents": result_object,
        "ContentsFormat": formats["json"],
        "ReadableContentsFormat": formats["markdown"],
        "HumanReadable": tableToMarkdown("Sent email", result_object),
    }


def test_module(client: EWSClient):  # pragma: no cover
    try:
        account = client.get_account(client.account_email)
        folder = client.get_folder_by_path(client.folder_name, account, client.is_public_folder)
        if not folder.effective_rights.read:  # pylint: disable=E1101
            raise Exception(
                "Success to authenticate, but user has no permissions to read from the mailbox. "
                "Need to delegate the user permissions to the mailbox - "
                "please read integration documentation and follow the instructions"
            )
        folder.test_access()
    except ErrorFolderNotFound as e:
        if "Top of Information Store" in str(e):
            raise Exception(
                "Success to authenticate, but user probably has no permissions to read from the specific folder."
                "Check user permissions. You can try !ews-find-folders command to "
                "get all the folders structure that the user has permissions to"
            )
    except Exception as e:
        if "403" in str(e):
            error_message_simple = (
                "Got invalid response with status code: 403."
                " Please make sure you have the right permissions to your application.\n"
            )
            raise DemistoException(error_message_simple)
        raise DemistoException(str(e))
    demisto.results("ok")


def get_client_from_params(params: dict):
    """
    Constructs an EWSClient instance from the provided integration parameters.

    :param params: The dictionary received from demisto.params()

    :return: An EWSClient instance for interacting with the Exchange server.
    """
    username = params.get("credentials", {}).get("identifier")
    password = params.get("credentials", {}).get("password")
    access_type = IMPERSONATION if params.get("impersonation", False) else DELEGATE
    account_email = params.get("defaultTargetMailbox", "")
    max_fetch = min(50, int(params.get("maxFetch", 50)))
    ews_server = params.get("ewsServer", "")
    auth_type = params.get("authType", "")
    if auth_type:
        auth_type = parse_auth_type(auth_type)
    version = params.get("defaultServerVersion", "")
    folder = params.get("folder", "Inbox")
    is_public_folder = params.get("isPublicFolder", False)
    request_timeout = int(params.get("requestTimeout", 120))
    mark_as_read = params.get("markAsRead", False)
    manual_username = params.get("domainAndUserman", "")
    insecure = params.get("insecure", True)

    if ews_server and manual_username:
        username = manual_username

    return EWSClient(
        client_id=username,
        client_secret=password,
        access_type=access_type,
        default_target_mailbox=account_email,
        max_fetch=max_fetch,
        ews_server=ews_server,
        auth_type=auth_type,
        version=version,
        folder=folder,
        is_public_folder=is_public_folder,
        request_timeout=request_timeout,
        mark_as_read=mark_as_read,
        incident_filter=IncidentFilter.RECEIVED_FILTER,
        app_name=APP_NAME,
        insecure=insecure,
    )


def sub_main():  # pragma: no cover
    params = demisto.params()

    args = prepare_args(demisto.args())
    client = get_client_from_params(params)
    skip_unparsable_emails: bool = argToBoolean(params.get("skip_unparsable_emails", False))
    fetch_all_history = argToBoolean(demisto.params().get("fetchAllHistory", False))
    fetch_time = demisto.params().get("fetch_time") or "10 minutes"
    is_test_module = False

    fix_2010(client)

    try:
        if demisto.command() == "test-module":
            is_test_module = True
            test_module(client)
        elif demisto.command() == "fetch-incidents":
            incidents = fetch_emails_as_incidents(client, skip_unparsable_emails, fetch_all_history, fetch_time)
            demisto.incidents(incidents)
        elif demisto.command() == "ews-get-attachment":
            return_results(fetch_attachments_for_message(client, args))
        elif demisto.command() == "ews-delete-attachment":
            return_results(delete_attachments_for_message(client, args))
        elif demisto.command() == "ews-get-searchable-mailboxes":
            return_results(get_searchable_mailboxes(client, args))
        elif demisto.command() == "ews-search-mailboxes":
            return_results(search_mailboxes(client, args))
        elif demisto.command() == "ews-move-item-between-mailboxes":
            return_results(move_item_between_mailboxes(client, args))
        elif demisto.command() == "ews-move-item":
            return_results(move_item(client, args))
        elif demisto.command() == "ews-delete-items":
            return_results(delete_items(client, args))
        elif demisto.command() == "ews-search-mailbox":
            return_results(search_items_in_mailbox(client, args))
        elif demisto.command() == "ews-get-contacts":
            return_results(get_contacts(client, args))
        elif demisto.command() == "ews-get-out-of-office":
            return_results(get_out_of_office_state(client, args))
        elif demisto.command() == "ews-recover-messages":
            return_results(recover_soft_delete_item(client, args))
        elif demisto.command() == "ews-create-folder":
            return_results(create_folder(client, args))
        elif demisto.command() == "ews-mark-item-as-junk":
            return_results(mark_item_as_junk(client, args))
        elif demisto.command() == "ews-find-folders":
            return_results(find_folders(client, args))
        elif demisto.command() == "ews-get-items-from-folder":
            return_results(get_items_from_folder(client, args))
        elif demisto.command() == "ews-get-items":
            return_results(get_items(client, args))
        elif demisto.command() == "ews-get-folder":
            return_results(get_folder(client, args))
        elif demisto.command() == "ews-get-autodiscovery-config":
            return_results(get_autodiscovery_config())
        elif demisto.command() == "ews-expand-group":
            return_results(get_expanded_group(client, args))
        elif demisto.command() == "ews-mark-items-as-read":
            return_results(mark_item_as_read(client, args))
        elif demisto.command() == "ews-resolve-name":
            return_results(resolve_name_command(client, args))
        elif demisto.command() == "ews-get-items-as-eml":
            return_results(get_item_as_eml(client, args))
        elif demisto.command() == "send-mail":
            return_results(send_email(client, args))
        elif demisto.command() == "reply-mail":
            return_results(reply_email(client, args))
        else:
            return_error(f'Command: "{demisto.command()}" was not recognized by this integration')

    except Exception as e:
        import time

        time.sleep(2)
        start_logging()
        debug_log = log_stream.getvalue()  # type: ignore
        error_message_simple = ""
        error_message = ""

        # Office365 regular maintenance case
        if (
            isinstance(e, ErrorMailboxMoveInProgress | ErrorMailboxStoreUnavailable)
            and urlparse(client.ews_server.lower()).hostname == "outlook.office365.com"
        ):
            log_message = (
                "Office365 is undergoing load balancing operations. As a result, the service is temporarily unavailable."
            )
            if demisto.command() == "fetch-incidents":
                demisto.info(log_message)
                demisto.incidents([])
                sys.exit(0)
            if is_test_module:
                demisto.results(log_message + " Please retry the instance configuration test.")
                sys.exit(0)
            error_message_simple = log_message + " Please retry your request."

        if isinstance(e, ConnectionError):
            error_message_simple = (
                "Could not connect to the server.\n"
                "Verify that the Hostname or IP address is correct.\n\n"
                f"Additional information: {e!s}"
            )
        if isinstance(e, ErrorInvalidPropertyRequest):
            error_message_simple = "Verify that the Exchange version is correct."
        else:
            from exchangelib.errors import MalformedResponseError

            if is_test_module and isinstance(e, MalformedResponseError):
                error_message_simple = (
                    "Got invalid response from the server.\nVerify that the Hostname or IP address is is correct."
                )

        # Legacy error handling
        if "Status code: 401" in debug_log:
            error_message_simple = (
                "Got unauthorized from the server. Check credentials are correct and authentication method are supported. "
            )

            error_message_simple += (
                "You can try using 'domain\\username' as username for authentication. "
                if client.auth_type.lower() == "ntlm"
                else ""
            )

        if "SSL: CERTIFICATE_VERIFY_FAILED" in debug_log:
            # same status code (503) but different error.
            error_message_simple = (
                "Certificate verification failed - This error may happen if the server "
                "certificate cannot be validated or as a result of a proxy that is doing SSL/TLS "
                "termination. It is possible to bypass certificate validation by checking "
                "'Trust any certificate' in the instance settings."
            )

        elif "Status code: 503" in debug_log:
            error_message_simple = (
                "Got timeout from the server. "
                "Probably the server is not reachable with the current settings. "
                "Check proxy parameter. If you are using server URL - change to server IP address. "
            )

        if not error_message_simple:
            error_message = error_message_simple = str(e)
        else:
            error_message = error_message_simple + "\n" + str(e)

        stacktrace = traceback.format_exc()
        if stacktrace:
            error_message += "\nFull stacktrace:\n" + stacktrace

        if debug_log:
            error_message += "\nFull debug log:\n" + debug_log

        if demisto.command() == "fetch-incidents":
            raise Exception(str(e) + traceback.format_exc())
        if demisto.command() == "ews-search-mailbox" and isinstance(e, ValueError):
            return_error(message="Selected invalid field, please specify valid field name.", error=e)
        if is_test_module:
            demisto.results(error_message_simple)
        else:
            demisto.results({"Type": entryTypes["error"], "ContentsFormat": formats["text"], "Contents": error_message_simple})
        demisto.error(f"{e.__class__.__name__}: {error_message}")
    finally:
        exchangelib_cleanup()
        if log_stream:
            try:
                logging.getLogger().removeHandler(log_handler)  # type: ignore
                log_stream.close()
            except Exception as ex:
                demisto.error(f"EWS: unexpected exception when trying to remove log handler: {ex}")


def process_main():  # pragma: no cover
    """setup stdin to fd=0 so we can read from the server"""
    sys.stdin = os.fdopen(0, "r")
    sub_main()


def main():  # pragma: no cover
    try:
        handle_proxy()
        # When running big queries, like 'ews-search-mailbox' the memory might not freed by the garbage
        # collector. `separate_process` flag will run the integration on a separate process that will prevent
        # memory leakage.
        separate_process = demisto.params().get("separate_process", False)
        demisto.debug(f"Running as separate_process: {separate_process}")
        if separate_process:
            try:
                p = Process(target=process_main)
                p.start()
                p.join()
            except Exception as ex:
                demisto.error(f"Failed starting Process: {ex}")
        else:
            sub_main()
    except Exception as exc:
        return_error(f"Found error in EWSv2: {exc}", error=f"Error: {exc}\nTraceback: {traceback.format_exc()}")


# python2 uses __builtin__ python3 uses builtins
if __name__ in ("__builtin__", "builtins", "__main__"):  # pragma: no cover
    main()