diff --git a/Research_Notes/Microsoft-Graph-Behavior.md b/Research_Notes/Microsoft-Graph-Behavior.md index c7b8b2b..f4eedfa 100644 --- a/Research_Notes/Microsoft-Graph-Behavior.md +++ b/Research_Notes/Microsoft-Graph-Behavior.md @@ -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.** @@ -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 @@ -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 @@ -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 @@ -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. @@ -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 @@ -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('
')`, 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('
')`, 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 '
'`. Nothing was posted. The + result's `Error` held only the module's warning `Cannot create a chat with the 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('
')`. 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('
')` 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.