Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
154 changes: 136 additions & 18 deletions Research_Notes/Microsoft-Graph-Behavior.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,15 @@

What Microsoft Graph and the Microsoft Graph PowerShell SDK do in the areas this module relies on:
permissions, what a successful send returns, chat creation and reuse, attachment and upload limits, how SDK sign-in
persists, and what disconnecting after a send changes. Each claim is tied to the part of the module it affects.
persists, what disconnecting after a send changes, and which forms of address find a user. Each claim is tied to
the part of the module it affects.

**Only sections 7 and 9 were measured against a tenant**, on **2026-09-16** and **2026-09-30**; each section
gives its environment. Documentation was read on **2026-09-15** from the pages listed under Sources, and the SDK
source on **2026-09-16**, except for section 8, whose pages and SDK source were read on **2026-09-21**, and
section 9, whose pages were read on **2026-09-30**. Microsoft revises these pages, so re-read a page before
relying on a limit or a permission.
**Only sections 7, 9, and 10, and one finding in section 5, were measured against a tenant**, on **2026-09-16**,
**2026-09-30**, **2026-10-01**, and **2026-10-02**; each section gives its environment. Documentation was read on
**2026-09-15** from the pages listed under Sources, and the SDK source on **2026-09-16**, except for section 8,
whose pages and SDK source were read on **2026-09-21**, section 9, whose pages were read on **2026-09-30**, and
section 10, whose pages were read on **2026-10-01** and **2026-10-02**. Microsoft revises these pages, so re-read a
page before relying on a limit or a permission.

**Claims are labelled with their evidence.**

Expand Down Expand Up @@ -53,6 +55,8 @@ relying on a limit or a permission.
| [Microsoft Graph PowerShell SDK authentication source, branch `main`](https://github.com/microsoftgraph/msgraph-sdk-powershell/tree/main/src/Authentication) | none (branch, read 2026-09-21) |
| [List chats](https://learn.microsoft.com/en-us/graph/api/chat-list?view=graph-rest-1.0) | 2026-04-13 |
| [chat resource type](https://learn.microsoft.com/en-us/graph/api/resources/chat?view=graph-rest-1.0) | 2026-08-05 |
| [Integrate Microsoft Entra Agent ID with third-party identity providers](https://learn.microsoft.com/en-us/microsoft-agent-365/developer/third-party-identity-providers) | 2026-08-13 |
| [How the proxyAddresses attribute is populated in Microsoft Entra ID](https://learn.microsoft.com/en-us/troubleshoot/entra/entra-id/user-prov-sync/proxyaddresses-attribute-populate) | 2026-09-17 |

## 1. From documentation: permissions for each operation the module performs

Expand Down Expand Up @@ -147,6 +151,12 @@ requests `Mail.Send` only. Chat attachments are uploaded with a simple `PUT` to
that name is taken), shared with the chat recipients as read-only with sign-in required and no invitation
email, and then referenced from the chat message.

**Measured** (2026-10-01, in the environment section 9 gives for that date): a chat attachment named
`plus+sign test.txt`, in a folder whose name also contains a `+`, was stored in OneDrive under exactly that name,
and Teams showed it under that name in the chat. The module escapes the name with `[uri]::EscapeDataString`,
which turns `+` into `%2B` in both PowerShell editions (measured locally the same day), so the `+` is kept rather
than read as a space.

### What this does not establish

- **Unverified:** the largest attachment, or total request size, that `sendMail` accepts inline. The 3 MB figure
Expand All @@ -158,6 +168,8 @@ email, and then referenced from the chat message.
comment in the module says it overwrites, and the module renames the file to avoid the question.
- **Unverified:** that `Microsoft Teams Chat Files` is the folder Teams itself uses for chat attachments. The
module assumes it; none of the pages above mention it.
- The `+` result was seen from the sender's side only; whether the recipients can open the shared file was not
checked. No other special character in a file name was tested.

## 6. From documentation: how SDK sign-in persists, and other clouds

Expand Down Expand Up @@ -330,9 +342,9 @@ accounts the tester controls: the signed-in sender and two recipients.
**Measured:**

- `Get-MgChat -All -Filter "ChatType eq 'group'"` with `-ExpandProperty 'Members', 'LastMessagePreview'`, the
call the module makes, succeeded: Graph accepts both expansions in one request. Each of the signed-in account's
6 group chats came back with its members' `email` values and a `LastMessagePreview`, and no two of them had the
same members.
call the module makes, succeeded: Graph accepts both expansions in one request. In a separate read-only run,
signed in as one of the recipient accounts instead of the sender, each of that account's 6 group chats came
back with its members' `email` values and a `LastMessagePreview`, and no two of them had the same members.
- **A group chat with the same members as an existing one is a new chat.** `New-MgChat -BodyParameter`, with
`chatType` `group` and the same three members as an existing group chat, returned a chat with a new ID and a
`CreatedDateTime` of that moment.
Expand Down Expand Up @@ -360,6 +372,31 @@ accounts the tester controls: the signed-in sender and two recipients.
`user@odata.bind` value was present but named a user that does not exist (a quoted list of addresses passed as
one address). The message says the field is missing when it is there but wrong. Both the `-Members` form and
the `-BodyParameter` form gave it.
- **Timing** (read-only, signed in as the sender, 21 group chats, five alternating runs of each call): the
`Chat.Read` path's call, expanding `Members` and `LastMessagePreview`, took 580 to 780 ms, median 668 ms. The
`Chat.ReadBasic` path's call, expanding only `Members`, took 541 to 646 ms, median 573 ms. The `Chat.Read` call
was slower in all five pairs.

**Measured on 2026-10-01** in the same environment, with `Microsoft.Graph.Files` 2.25.0 added, `Send-ScriptMessage`
from `develop` at `32282b7`, and the same three accounts:

- **A recipient leaving a group chat in Teams changes the members the sender's listing returns.** One recipient
left both group chats holding all three accounts; afterwards the sender's `Get-MgChat` listing still returned
both, with only the two remaining members, so neither matched a send to all three.
- **The module's own `New-MgChat -Members` call creates a group chat with valid members.** With no group chat
holding exactly the sender and both recipients, `Send-ScriptMessage -ChatType Group` created one, of type
`group`, with exactly those three members, and posted its message into it.
- **A second send finds the chat the first one created.** The same send again posted into that chat, and the
sender still had only that one group chat with those three members.
- **A member's `email` can differ in letter case from the address the caller gave.** One recipient, given in
lower case, came back as a member `email` with capital letters. The match still succeeded: `Compare-Object`
ignores letter case unless given `-CaseSensitive`, in both PowerShell editions (measured locally the same day).
- **A recipient given by a UPN that differs from their primary email gets a new group chat instead of the
existing one.** One recipient's UPN was changed in Microsoft Entra ID to a name other than their primary email,
which stayed as it was. The sender's listing still showed that member's `email` as the primary address. A
`Group` send naming that recipient by the new UPN, with the other two members as before, found no match,
created a second group chat with the same three members as the existing one, and posted into it. Its `Error`
was empty, so the caller got no warning. Section 10 covers which forms of address find a user.

**From documentation:** the chat resource type page defines `lastUpdatedDateTime` as "Date and time at which the
chat was renamed or the list of members was last changed", and says `lastMessagePreview` is "Null if no messages
Expand All @@ -381,20 +418,101 @@ values are exactly the sender plus the recipients, and creates a new group chat
the same holds for every setting that only chooses which delegated permission is asked for.
- A group chat with more than 25 members never matches, if the 25-member cap applies to `-All` paging too, so the
module would create a new chat on every send to it.
- A recipient given by a UPN that differs from their primary email address never matches either, with the same
result: the module names each member as `users('<address>')`, which going by Graph's
`/users/{id | userPrincipalName}` form takes a user ID or UPN, but matches on the member's `email`, the primary
address. Matching on each member's `userId` would avoid it. A true alias, neither the UPN nor the primary
address, probably fails when the chat is created instead.
- The UPN mismatch measured above happens because the module names each member as `users('<address>')`, which
going by Graph's `/users/{id | userPrincipalName}` form takes a user ID or UPN, but matches on the member's
`email`, the primary address. Each later send by UPN finds no match in the same way, so it creates yet another
chat. Matching on each member's `userId` would avoid it.

### What this does not establish

- **Unverified:** the module's own `New-MgChat -Members` call creating a group chat with valid members. A matching
chat already existed, so that path was not exercised.
- **Unverified:** whether the Teams client ever creates a second group chat with the same members, and whether
anything besides a rename or a member change moves `LastUpdatedDateTime` (only the documentation says so).
- **Unverified:** the 25-member cap, the UPN mismatch, and the alias failure in the inferences above. None was
tested.
- **Unverified:** the 25-member cap in the inferences above; it was not tested.
- The UPN mismatch was measured with one send. The account syncs from on-premises Active Directory, and its UPN
was changed in Microsoft Entra ID only.
- Only a three-member group chat was created. A `Group` send to a single recipient, which would match or create a
two-member group chat, was not tested.
- **Unverified:** whether permissions a user consented to for themselves, rather than ones an administrator granted
for the organization, are carried the same way. Only administrator grants were measured.
- How the timing gap grows with many more chats, and whether sorting on the server by
`lastMessagePreview/createdDateTime` would make the `Chat.Read` path faster.
- Nothing in this section was measured on Windows PowerShell 5.1 or with an SDK release other than 2.25.0.

## 10. Measured: which forms of address find a user

**Environment:** measured on 2026-10-01 and 2026-10-02 in the environment section 9 gives for 2026-10-01, with
the same code and the same three accounts. Chat sends used a delegated sign-in, and the mail send the
`Mail.Send` application permission. The accounts sync from on-premises Active Directory. On 2026-10-01 one
recipient's UPN was changed in Microsoft Entra ID only, to a name other than their primary email, which stayed
as it was, and it stayed changed through every test below.

**Measured:**

- **A chat member given by UPN resolves.** A `Group` send naming that recipient by the new UPN created a group
chat with them as a member (section 9). On 2026-10-02 a `OneOnOne` send to the same UPN posted into the
existing one-on-one chat between the two accounts, created in 2024, with `Error` empty.
- **A chat member given by primary email resolves while it is not the UPN.** On 2026-10-01, and again on
2026-10-02, a `OneOnOne` send to that recipient's primary email posted into the same one-on-one chat, with
`Error` empty.
- **A secondary email address that was never a UPN does not resolve.** On 2026-10-01 a `OneOnOne` send with
`From` set to one of the sender's own secondary SMTP addresses, which had never been their UPN, failed in
`New-MgChat` with 404 `Failed to find users with user principal name '<address>'`. Nothing was posted. The
result's `Error` held only the module's warning `Cannot create a chat with the recipient '<recipient>'.`,
which names the recipient although the sender's address was the one that failed; Graph's own message was
printed to the console, not added to the result.
- **A chat attachment can be shared with a UPN.** On 2026-10-02 a `OneOnOne` send to the recipient's UPN with a
small attachment uploaded the file to the sender's OneDrive and gave the recipient a `read` permission on it,
with `Error` empty. That permission's SharePoint sign-in name was the UPN; the only other permission on the
file was the sender's, as owner.
- **Mail to that UPN was delivered.** On 2026-10-02 a `Group` mail send to the recipient's UPN returned `Status`
`$true` with `Error` empty, the message reached the recipient's inbox, and no non-delivery report reached the
sender. The recipient's proxy addresses, as Microsoft Entra ID listed them the same day, included the UPN as a
secondary `smtp:` address.

**From documentation:** the page on how the proxyAddresses attribute is populated (read 2026-10-02) says "If the
user has an Exchange license assigned or the user is an Exchange Online recipient, such as a shared mailbox, the
`userPrincipalName` is always added as a proxy address", and its scenarios add the UPN as a secondary `smtp`
address whenever the primary SMTP address differs from it. The Agent ID integration page (read 2026-10-01) finds
a user from an email address by filtering on `mail`, then on `otherMails`, and addresses the user resource
directly only by UPN.

**From source:** every address reaches Graph as the caller gave it. When a chat is created,
`ConvertTo-IMicrosoftGraphConversationMember` names the sender and each recipient as `users('<address>')`. When
an attachment is shared, `ConvertTo-IMicrosoftGraphDriveInvite` passes each chat recipient as an invitation
`email`. `ConvertTo-IMicrosoftGraphRecipient` passes each mail recipient as an `emailAddress` `address`. A
`OneOnOne` send calls `New-MgChat` for each recipient without listing or comparing chats.

**Inference:**

- A one-on-one send never compares addresses, so a UPN cannot cause the duplicate chat that section 9 describes
there.
- Mail to a mailbox user's UPN is delivered: going by the documentation above, a mailbox's UPN is always one of
its email addresses.
- A secondary address given for a recipient, rather than for `From` as measured above, fails the same way when
the chat is created, because the same function names the sender and every recipient.
- With section 9: a group chat is created only when every address resolves, and found again only when every
member is given by the address its `email` holds, the primary address. Of the forms tested, only the primary
email can do both, provided it resolves when it is not the UPN (unverified, below). The UPN worked for every
other use tested: one-on-one chats, attachment sharing, and mail.

### What this does not establish

- **Unverified:** whether `users('<address>')` resolves a primary email that is not the UPN. The primary email
that resolved had also been that account's UPN until 2026-10-01, so the results do not separate "Graph
resolves the primary email" from "Graph still resolves a former UPN". Against the first: the 404 above names
the lookup as a user principal name lookup, and the Agent ID integration page addresses the user resource
directly only by UPN. Against the second: the new UPN and the old one both resolved within the same hour, at
least half an hour after the change, and the old one resolved again more than an hour after it and again the
next day. That rules out a replaced UPN that stays resolvable for less than a day, but not one Graph keeps
longer or for good. An account whose primary email has never been its UPN would settle it. So would the
former UPN, once the changed UPN is put back, if it stops resolving: a secondary address that was never a UPN
does not resolve, so the primary email would be what resolved, even if the former UPN stays among the
account's secondary addresses.
- **Unverified:** a secondary address given for a recipient; only `From` was tested.
- Each result came from one or two sends, and every UPN and primary email result involved the same recipient
account. The attachment share and the mail send used a UPN that was also a proxy address, so neither shows
what happens with a UPN that is not an email address; going by the documentation above, a mailbox user cannot
have one.
- Attachment sharing was tested only with a UPN, not with a primary email that is not the UPN or with a secondary
address.
- Nothing in this section was measured on Windows PowerShell 5.1 or with an SDK release other than 2.25.0.
Loading