Describe the bug
Invoke-MgGraphRequest -Body serializes IDictionary bodies with Newtonsoft.Json. If a hashtable body contains a value that came from a PowerShell pipeline (for example, bare $_ from ForEach-Object) Newtonsoft sees a System.Management.Automation.PSObject wrapper rather than a plain CLR string and reflects into PowerShell adapted members on the string.
For a string value, this exposes the Chars indexed property as a PSParameterizedProperty. Serialization then fails with a circular/self-reference error before the HTTP request can be sent.
Observed exception:
Newtonsoft.Json.JsonSerializationException: Self referencing loop detected for property 'Value' with type 'System.Management.Automation.PSParameterizedProperty'. Path 'message.toRecipients[0].emailAddress.address.Chars'.
This is surprising because from PowerShell the value still reports as System.String:
$payload.message.toRecipients[0].emailAddress.address.GetType().FullName
# System.String
But from .NET, the dictionary value is actually a System.Management.Automation.PSObject whose BaseObject is a System.String.
Expected behavior
Invoke-MgGraphRequest -Body should serialize idiomatic PowerShell hashtable bodies containing pipeline-produced primitive values the same way it serializes directly assigned primitive values.
At minimum, string-like PSObject wrappers should be unwrapped before JSON serialization so callers do not need to know about this PowerShell-to-.NET interop boundary.
How to reproduce
The public Invoke-MgGraphRequest path requires an authenticated Graph context before it reaches body serialization. The following repro invokes the same internal SetRequestContent(HttpRequestMessage, IDictionary) path via reflection so it can be run without a tenant or token.
Import-Module Microsoft.Graph.Authentication
$type = [Microsoft.Graph.PowerShell.Authentication.Cmdlets.InvokeMgGraphRequest]
$method = $type.GetMethod(
'SetRequestContent',
[System.Reflection.BindingFlags]'NonPublic,Instance',
$null,
@([System.Net.Http.HttpRequestMessage], [System.Collections.IDictionary]),
$null)
$cmdlet = [Activator]::CreateInstance($type)
$toAddresses = @('recipient@example.com' -split '[;,]' |
ForEach-Object { $_.Trim() } |
Where-Object { $_ })
# This is the problematic, idiomatic PowerShell shape:
$body = @{
message = @{
toRecipients = @(
$toAddresses | ForEach-Object {
@{ emailAddress = @{ address = $_ } }
}
)
}
saveToSentItems = $true
}
$request = [System.Net.Http.HttpRequestMessage]::new(
[System.Net.Http.HttpMethod]::Post,
'https://graph.microsoft.com/v1.0/users/sender@example.com/sendMail')
$method.Invoke($cmdlet, @($request, $body))
Actual result:
Exception calling "Invoke" with "2" argument(s): "Self referencing loop detected for property 'Value' with type 'System.Management.Automation.PSParameterizedProperty'. Path 'message.toRecipients[0].emailAddress.address.Chars'."
If the address assignment is changed to explicitly cast the pipeline value, serialization succeeds:
@{ emailAddress = @{ address = [string]$_ } }
With that cast, the serialized body is:
{"saveToSentItems":true,"message":{"toRecipients":[{"emailAddress":{"address":"recipient@example.com"}}]}}
A small probe showing the type mismatch at the .NET boundary:
Add-Type -ReferencedAssemblies ([System.Management.Automation.PSObject].Assembly.Location) -TypeDefinition @'
using System;
using System.Collections;
using System.Management.Automation;
public static class DictProbe {
public static string DescribeAddress(IDictionary body) {
var message = (IDictionary)body["message"];
var recipients = (IEnumerable)message["toRecipients"];
object firstRecipient = null;
foreach (var item in recipients) { firstRecipient = item; break; }
var recipient = (IDictionary)firstRecipient;
var email = (IDictionary)recipient["emailAddress"];
var address = email["address"];
var s = "dotnetType=" + address.GetType().FullName;
if (address is PSObject p) {
s += "; baseType=" + p.BaseObject.GetType().FullName;
s += "; charsMemberType=" + p.Members["Chars"].GetType().FullName;
s += "; charsMemberKind=" + p.Members["Chars"].MemberType;
}
return s;
}
}
'@
[DictProbe]::DescribeAddress($body)
Output for the failing body:
dotnetType=System.Management.Automation.PSObject; baseType=System.String; charsMemberType=System.Management.Automation.PSParameterizedProperty; charsMemberKind=ParameterizedProperty
Output after using [string]$_:
SDK Version
Reproduced with:
Microsoft.Graph.Authentication 2.25.0
Microsoft.Graph.Authentication 2.35.1
The current source also appears to still call Newtonsoft directly for dictionary bodies:
// InvokeMgGraphRequest.SetRequestContent(HttpRequestMessage request, IDictionary content)
var body = JsonConvert.SerializeObject(content);
Latest version known to work for scenario above?
None known.
Known Workarounds
Explicitly cast pipeline-derived primitive values before placing them into the hashtable body, e.g.:
$toAddresses | ForEach-Object {
@{ emailAddress = @{ address = [string]$_ } }
}
or otherwise ensure values crossing into Invoke-MgGraphRequest -Body are plain CLR primitives rather than PSObject-wrapped values.
Debug output
No Graph debug output is produced in the repro above because the failure happens during request content serialization, before the HTTP request is sent.
Configuration
Local repro environment:
PowerShell 7.6.3
Debian GNU/Linux 13 (trixie)
x64
Other information
This looks like a PowerShell/.NET interop robustness issue in Invoke-MgGraphRequest's body serializer rather than a Graph API issue. PowerShell callers can construct a normal-looking hashtable where .GetType() reports System.String, but a generic .NET serializer sees a PSObject wrapper and walks PowerShell adapted members such as Chars.
Related prior art for this general failure class exists outside the Graph SDK, e.g. serialization of raw PowerShell objects hitting circular reference errors involving PSParameterizedProperty.
Describe the bug
Invoke-MgGraphRequest -BodyserializesIDictionarybodies with Newtonsoft.Json. If a hashtable body contains a value that came from a PowerShell pipeline (for example, bare$_fromForEach-Object) Newtonsoft sees aSystem.Management.Automation.PSObjectwrapper rather than a plain CLR string and reflects into PowerShell adapted members on the string.For a string value, this exposes the
Charsindexed property as aPSParameterizedProperty. Serialization then fails with a circular/self-reference error before the HTTP request can be sent.Observed exception:
This is surprising because from PowerShell the value still reports as
System.String:But from .NET, the dictionary value is actually a
System.Management.Automation.PSObjectwhoseBaseObjectis aSystem.String.Expected behavior
Invoke-MgGraphRequest -Bodyshould serialize idiomatic PowerShell hashtable bodies containing pipeline-produced primitive values the same way it serializes directly assigned primitive values.At minimum, string-like
PSObjectwrappers should be unwrapped before JSON serialization so callers do not need to know about this PowerShell-to-.NET interop boundary.How to reproduce
The public
Invoke-MgGraphRequestpath requires an authenticated Graph context before it reaches body serialization. The following repro invokes the same internalSetRequestContent(HttpRequestMessage, IDictionary)path via reflection so it can be run without a tenant or token.Actual result:
If the address assignment is changed to explicitly cast the pipeline value, serialization succeeds:
With that cast, the serialized body is:
{"saveToSentItems":true,"message":{"toRecipients":[{"emailAddress":{"address":"recipient@example.com"}}]}}A small probe showing the type mismatch at the .NET boundary:
Output for the failing body:
Output after using
[string]$_:SDK Version
Reproduced with:
The current source also appears to still call Newtonsoft directly for dictionary bodies:
Latest version known to work for scenario above?
None known.
Known Workarounds
Explicitly cast pipeline-derived primitive values before placing them into the hashtable body, e.g.:
or otherwise ensure values crossing into
Invoke-MgGraphRequest -Bodyare plain CLR primitives rather thanPSObject-wrapped values.Debug output
No Graph debug output is produced in the repro above because the failure happens during request content serialization, before the HTTP request is sent.
Configuration
Local repro environment:
Other information
This looks like a PowerShell/.NET interop robustness issue in
Invoke-MgGraphRequest's body serializer rather than a Graph API issue. PowerShell callers can construct a normal-looking hashtable where.GetType()reportsSystem.String, but a generic .NET serializer sees aPSObjectwrapper and walks PowerShell adapted members such asChars.Related prior art for this general failure class exists outside the Graph SDK, e.g. serialization of raw PowerShell objects hitting circular reference errors involving
PSParameterizedProperty.