Table of Contents
Which to Choose for PowerShell Development?
I’m sometimes asked why people should bother using the Microsoft Graph PowerShell SDK to develop PowerShell scripts. The arguments against the SDK are that it’s buggy, doesn’t have great documentation, and adds an extra layer on top of Graph API requests. I can’t deny that the SDK has had recent quality problems that shook developer confidence.
I cannot advance a case that Microsoft’s documentation for the Graph PowerShell SDK cmdlets is good because it’s not. Some improvements have been made over the last year, but the examples given (copied mostly from the Graph documentation) are too simple, if they exist at all. There’s also the small fact that the Graph PowerShell SDK cmdlets share some foibles that make them less useful than they should be.
Given the problems, why continue to persist with the Graph PowerShell SDK? I guess the reason is that the SDK cmdlets are easier to work with for anyone who’s used to PowerShell development. For instance, the Graph SDK automatically performs housekeeping operations like retrieving an access token, renewing the token (only needed for long-running scripts), and pagination. None of these operations are complex. Once mastered, the same code can be copied into scripts to take care of these points.
Call me mad, I therefore persist in writing scripts using Graph PowerShell SDK cmdlets. However, times exist when it’s necessary to use a Graph API request, including when:
- Microsoft’s AutoRest process has not processed a new API to create a cmdlet.
- The data returned by a cmdlet is not as complete as the underlying Graph API request. This shouldn’t happen, but it does.
- It’s necessary to retrieve properties that a cmdlet doesn’t support.
Let’s look at examples of the last two points.
Fetching Attendee Data with Microsoft Graph PowerShell SDK Cmdlets and API Requests
I’ve used the List CalendarView API in situations like reporting usage statistics for room mailboxes. Here’s an example of retrieving calendar events between two dates.
$Uri = ("https://graph.microsoft.com/V1.0/users/{0}/calendar/calendarView?startDateTime={1}&endDateTime={2}" -f $Organizer.Id, $StartDateSearch, $EndDateSearch)
The resulting URI fed to the Invoke-MgGraphRequest cmdlet looks like this:
$Uri https://graph.microsoft.com/V1.0/users/4adf6057-95da-430a-8757-6a58c85e13d4/calendar/calendarView?startDateTime=2024-03-28T12:56:37&endDateTime=2024-05-29T12:56:37 $Items = Invoke-MgGraphRequest -Method Get -Uri $Uri | Select-Object -ExpandProperty Value
You might ask why I use Invoke-MgGraphRequest (a cmdlet from the Microsoft Graph PowerShell SDK) rather than the general-purpose Invoke-RestMethod cmdlet. It’s because I start scripts off with the Graph PowerShell SDK and only go to standard Graph API requests when necessary.
In any case, the attendees of a meeting are returned like this:
attendees {System.Collections.Hashtable, System.Collections.Hashtable, System.Collections.Hashtable, System.Collections.Hashtable}
The attendee data are available in individual hash tables and are easy to access:
$Items[0].attendees Name Value ---- ----- emailAddress {[address, Sean.Landy@office365itpros.com], [name, Sean Landy]} status {[response, none], [time, 01/01/0001 00:00:00]} type required emailAddress {[address, Lotte.Vetler@office365itpros.com], [name, Lotte Vetler (Paris)]} status {[response, none], [time, 01/01/0001 00:00:00]}
Get-MgUserCalendarView is the equivalent cmdlet in the Microsoft Graph PowerShell SDK. This command does the same job as the List CalendarView API request above.
[array]$CalendarItems = Get-MgUserCalendarView -UserId $Organizer.id -Startdatetime $StartDateSearch -Enddatetime $EndDateSearch -All Attendees : {Microsoft.Graph.PowerShell.Models.MicrosoftGraphAttendee, Microsoft.Graph.PowerShell.Models.MicrosoftGraphAttendee} $calendarItems[0].Attendees Type ---- required required
The attendee data is incomplete. No information is available about the attendees’ email addresses and display names. That’s why my scripts use the API rather than the cmdlet.
How the Microsoft Graph PowerShell SDK Cmdlets Return Data
When you run a Graph PowerShell SDK cmdlet, the returned data ends up in an array, which is convenient for further PowerShell processing. You’ll note that I use the -All parameter to fetch all available objects.
$AllUsers = Get-MgUser -All $AllUsers.gettype() IsPublic IsSerial Name BaseType -------- -------- ---- -------- True True Object[] System.Array
Things are a little more complicated with Graph API requests. We get an array back, but the array contains a hashtable. The actual data that we might want to process is in the record with the Value key. We also see an @odata.nextlink to use to fetch the next page of available data:
$Uri = "https://graph.microsoft.com/v1.0/users" $Data = Invoke-MgGraphRequest -Method Get -Uri $Uri $Data.GetType() IsPublic IsSerial Name BaseType -------- -------- ---- -------- True True Object[] System.Array $Data Name Value ---- ----- @odata.context https://graph.microsoft.com/v1.0/$metadata#users value {44c0ca9c-d18c-4466-9492-c60c3eb78423, bcfa6ecb-cc5b-4357-909c-1e0e450864e3, da1288d5-e63c-4118-af62-3280823e04e1, de67dc4a-4a51-4d86-… @odata.nextLink https://graph.microsoft.com/v1.0/users?$skiptoken=RFNwdAIAAQAAABc6Z3NjYWxlc0BkYXRhcnVtYmxlLmNvbSlVc2VyX2UwNjIzZjE0LTUzM2QtNDhmYS1hODRl…
In most cases, I simply create an array of the data and then go ahead and process the information as normal for an array:
[array]$Data = $Data.Value
The Invoke-MgGraphRequest cmdlet supports output to a PowerShell object.
$Data = Invoke-MgGraphRequest -Method Get -Uri $Uri -OutputType PsObject
The output data is the same but it’s in the form of an array rather than a hash table:
$Data | Format-List @odata.context : https://graph.microsoft.com/v1.0/$metadata#users @odata.nextLink : https://graph.microsoft.com/v1.0/users?$skiptoken=RFNwdAIAAQAAABc6Z3NjYWxlc0BkYXRhcnVtYmxlLmNvbSlVc2VyX2UwNjIzZjE0LTUzM2QtNDhmYS1hODRlLTljOTg0MDhkNDgxYbkAAAAAAAAAAAAA value : {@{businessPhones=System.Object[]; displayName=12 Knocksinna (Gmail); givenName=12; jobTitle=; mail=12knocksinna@gmail.com; mobilePhone=; officeLocation=; preferredLanguage=; surname=Knocksinna; userPrincipalName=12knocksinna_gmail.com#EXT#@RedmondAssociates.onmicrosoft.com; id=44c0ca9c-d18c-4466-9492-c60c3eb78423}, @{businessPhones=System.Object[]; displayName=Email Channel for Exchange GOMs; givenName=Teams; jobTitle=; mail=5e2eb5ab.office365itpros.com@emea.teams.ms; mobilePhone=; officeLocation=; preferredLanguage=; surname=Email Channel for GOM List;
Once again, the data to process is in the Value record.
I usually don’t bother outputting to a PowerShell object, perhaps because I’m used to dealing with the hash table.
Mix and Match
The important thing to remember is that a PowerShell script can mix and match Graph API requests and Graph PowerShell cmdlets. My usual approach is to start with cmdlets and only use Graph requests when absolutely necessary. I know others will disagree with this approach, but it’s one that works for me.
Make sure that you’re not surprised about changes that appear inside Microsoft 365 applications by subscribing to the Office 365 for IT Pros eBook. Our monthly updates make sure that our subscribers stay informed.
Of some relevance as it relates to Graph but have you tested the bulk provisioning of ODFB recently? The MS Learn article looks wrong (it still references an MSOL attribute which doesnt exist for the user object via MS Graph that retrieves the user in the variable). I have updated the script myself for my purposes and it appears to run successfully as it did previously but the ODFB sites do not provision back end. Runs but does nothing.
Wondered if this was a known issue or something you had come across?
Nope. I haven’t looked into it. What is the URL for the article?
https://learn.microsoft.com/en-us/sharepoint/pre-provision-accounts#pre-provision-onedrive-for-all-licensed-users-in-your-organization
I’ve never seen this script before in my life. As you note, the use of the Get-MgUser cmdlet is incorrect. It looks as if the writer swapped out Get-MsolUser for Get-MgUser. I will try and look at this later today to see if a better way is possible.
Cheers Tony. I have a working script (Correct syntax anyway!) and runs as expected but doesnt do what is expected on the MS backend. Happy to share that with you if that helps.
Just thinking aloud. Years ago, OneDrive provisioning was slow. Now it seems to be pretty fast after a new account is created. Do we need a script to force provisioning? What’s your experience?
And a pointer to a script in GitHub is always appreciated.
TBH I havent had to force provisioning and usually the ODFB site is available soon after. For example, if i manually log into a user account and access ODFB, it spins up instantly for me to work with it.
Link to my code here for you – http://bit.ly/3VrAuGV
Try https://github.com/12Knocksinna/Office365itpros/blob/master/Provision-OneDriveAccounts.PS1
Basically, the script finds existing OneDrive sites and builds a hash table from then. It then finds licensed users (for SharePoint Online, there isn’t a service plan for OneDrive anymore) and checks each user to detect if a OneDrive site exists. If no site is found, it provisions the site.
Thanks Tony. Tested and get the same outcome unfortunately – script completes and the output states it will provision the ODFB sites for the users but doesnt do anything back end.
Did this work as expected when testing your side? Beginning to think this is an issue with this MS tenant I am working with.
Well, if you go near OneDrive for a licensed account, provisioning happens immediately. I’m not sure I trust the output of Get-SPOSite to validate that provisioning has occurred… My understanding is that asking for provisioning notifies a background job that picks up tasks on a timer basis and runs them then. Maybe the timer is broken…