Transferring Reusable PowerShell Objects Between Microsoft 365 Tenants
The Graph SDK’s ToJsonString Method Proves Its Worth
One of the frustrations about using the internet is when you find some code that seems useful, copy the code to try it out in your tenant, and discover that some formatting issue prevents the code from running. Many reasons cause this to happen. Sometimes it’s as simple as an error when copying code into a web editor, and sometimes errors creep in after copying the code, perhaps when formatting it for display. I guess fixing the problems is an opportunity to learn what the code really does.
Answers created by generative AI solutions like ChatGPT, Copilot for Microsoft 365, and GitHub Copilot compound the problem by faithfully reproducing errors in its responses. This is no fault of the technology, which works by creating answers from what’s gone before. If published code includes a formatting error, generative AI is unlikely to find and fix the problem.
Dealing with JSON Payloads
All of which brings me to a variation on the problem. The documentation for Graph APIs used to create or update objects usually include an example of a JSON-formatted payload containing the parameter values for the request. The Graph API interpret the JSON content in the payload to extract the parameters to run a request. By comparison, Microsoft Graph PowerShell SDK cmdlets use hash tables and arrays to pass parameters. The hash tables and arrays mimic the elements of the JSON structure used by the underlying Graph APIs.
Composing a JSON payload is no challenge If you can write perfect JSON. Like any other rules for programming or formatting, it takes time to become fluent with JSON, and who can afford that time when other work exists to be done? Here’s a way to make things easier.
Every object generated by a Graph SDK cmdlet has a ToJsonString method to create a JSON-formatted version of the object. For example:
$User = Get-MgUser -UserId Kim.Akers@office365itpros.com
$UserJson = $User.ToJsonString()
$UserJson
{
“@odata.context”: “https://graph.microsoft.com/v1.0/$metadata#users/$entity”,
“id”: “d36b323a-32c3-4ca5-a4a5-2f7b4fbef31c”,
“businessPhones”: [ “+1 713 633-5141” ],
“displayName”: “Kim Akers (She/Her)”,
“givenName”: “Kim”,
“jobTitle”: “VP Marketing”,
“mail”: “Kim.Akers@office365itpros.com”,
“mobilePhone”: “+1 761 504-0011”,
“officeLocation”: “NYC”,
“preferredLanguage”: “en-US”,
“surname”: “Akers”,
“userPrincipalName”: Kim.Akers@office365itpros.com
}
The advantages of using the ToJsonString method instead of PowerShell’s ConvertTo-JSON cmdlet is that the method doesn’t output properties with empty values. This makes the resulting output easier to review and manage. For instance, the JSON content shown above is a lot easier to use as a template for adding new user accounts than the equivalent generated by ConvertTo-JSON.
Transferring a Conditional Access Policy Using ToJsonString
The output generated by ToJsonString becomes very interesting when you want to move objects between tenants. For example, let’s assume that you use a test tenant to create and fine tune a conditional access policy. The next piece of work is to transfer the conditional access policy from the test tenant to the production environment. Here’s how I make the transfer:
Run the Get-MgIdentityConditionalAccessPolicy cmdlet to find the target policy and export its settings to JSON. Then save the JSON content in a text file.
$Policy = Get-MgIdentityConditionalAccessPolicy -ConditionalAccessPolicyId ‘1d4063cb-5ebf-4676-bfca-3775d7160b65’
$PolicyJson = $Policy.toJsonString()
$PolicyJson > PolicyExport.txt
Edit the text file to replace any tenant-specific items with equivalent values for the target tenant. For instance, conditional access policies usually include an exclusion for break glass accounts, which are listed in the policy using the account identifiers. In this case, you need to replace the account identifiers for the source tenant in the exported text file with the account identifiers for the break glass account for the target tenant.
Disconnect from the source tenant.
Connect to the target tenant with the Policy.ReadWrite.ConditionalAccess scope.
Create a variable ($Body in this example) containing the conditional policy settings.
Run the Invoke-MgGraph-Request cmdlet to import the policy definition into the target tenant.
$Uri = “https://graph.microsoft.com/v1.0/identity/conditionalAccess/policies”
Invoke-MgGraphRequest -uri $uri -method Post -Body $Body
The Other Way
Another way to create a conditional access policy with PowerShell is to run the New-MgIdentityConditionalAccessPolicy cmdlet, which takes a hash table as its payload. It’s easy to translate the JSON into the format used for parameter values stored in the hash table, but it’s even easier to run Invoke-MgGraphRequest and pass the edited version of the JSON exported from the source tenant. Why make things hard for yourself?
This tip is just one of the hundreds included the Automating Microsoft 365 with PowerShell eBook (available separately, as part of the Office 365 for IT Pros (2025 edition) bundle, or as a paperback from Amazon.com).