Skip to content

Latest commit

 

History

History
1492 lines (1047 loc) · 96.9 KB

WALKTHROUGH.md

File metadata and controls

1492 lines (1047 loc) · 96.9 KB

AutoGraphPS Walkthrough

This walkthrough will expose you to the key capabilities of AutoGraphPS and the Microsoft Graph. It is certainly not exhaustive, but should be enough for you understand how to use AutoGraphPS in your work, where it falls short, and how you might remedy any omissions with your own contributions to the project.

The walkthrough assumes you are familiar with PowerShell and basic usage of the object pipeline, and that you are familiar with REST concepts.

How to explore with AutoGraphPS

After you've installed the module, invoke AutoGraphPS cmdlets from any PowerShell session.

Get started -- simple commands

AutoGraphPS cmdlets allow you to explore the graph. Before using the cmdlets, you must establish a connection to the graph by signing in. If you have not already done this using Connect-GraphApi, that's ok -- any commands that need a connection will request one before communicating with the graph. After that, any subsequent commands will re-use that same connection, so you won't have to sign in again.

Here's a really simple command you can execute -- if it asks you to sign-in, please do so:

Get-GraphResource me

After you respond to authentication prompts, Get-GraphResource returns a PowerShell object representing MS Graph's view of the me entity, i.e. the entity that represents your user object:

id                : 82f53da9-b996-4227-b268-c20564ceedf7
officeLocation    : 7/3191
@odata.context    : https://graph.microsoft.com/v1.0/$metadata#users/$entity
surname           : Okorafor
mail              : starchild@mothership.io
jobTitle          : Professor
givenName         : Starchild
userPrincipalName : starchild@mothership.io
businessPhones    : +1 (313) 360 3141
displayName       : Starchild Okorafor

Since the first execution of Get-GraphResource establishes a logical "session," you can continue to execute cmdlets without being asked to re-authenticate, e.g. a subsequent invocation like

PS> Get-GraphResource organization

will not prompt for credentials but immediately return details about your organization:

id              : fb6df3ba-c5f5-43dd-b108-a921f1a7e759
businessPhones  : {206 881 8080}
city            : Seattle
displayName     : Akana
postalCode      : 98144
state           : Washington
street          : 101 23rd Avenue Suite X

Expanding your (permission) scope

The commands above are trivial demonstrations of Graph. In particular, they only require the Graph permission known as User.Read. More interesting explorations of the Graph require that you request additional permissions when you connect to the Graph.

Here's how you can request the Files.Read, Mail.Read, and Contacts.Read permissions in addition to User.Read -- the additional permissions enable you to access those parts of the Graph for reading information about user files from OneDrive, your mail, and your list of personal contacts:

Connect-GraphApi -Permissions User.Read, Files.Read, Mail.Read, Contacts.Read

This will prompt you to authenticate again and consent to allow the application to acquire these permissions. Note that it is generally not obvious what permissions are required to access different functionality in the Graph; future updates to AutoGraphPS will attempt to address this. For now, consult the Graph permissions documentation whenever you're accessing a new part of the Graph.

Note that there are more than 500 permissions (and counting). If you're not sure which permission you need to request with Connect-GraphApi, (i.e. Files.Read, Mail.Read, etc.), you can use the Find-GraphPermissions to find permissions and their descriptions by specifying a search keyword.

Now you can issue commands to read contacts, mail, and files. In addition to the Get-GraphResource cmdlet which returns data as a series of flat lists, you can use Get-GraphResourceWithMetadata or its alias gls to retrieve your personal contacts:

PS> Get-GraphResourceWithMetadata me/contacts

              Email DisplayName    Phone                  Address
              ----- ----------     -----                  -------
cosmo@soulsonic.org Cosmo Jones    Mobile: (313) 555-7334 Home: 1075 Chalfonte
        aks@bee.org Akeelah Smith  Home: 8760 Lafayette
  jhenry@legend.net John Henry     Work: (206) 882-9000   Work: One Turing Way
 deandre@skynet.org Deandre George Mobile: (404) 200-1024 Home: 200 OK Drive

The Get-GraphResourceWithMetatdata combines the response from Graph with type metadata that allows it to recognize the response as a contact, and since it so happens that the command "knows" how to provide user-friendly output for the contact type, it applies specific formatting to improve readability.

Get-GraphResourceWithMetadata only knows about certain fairly popular types, so if the response does not confirm to a type with formatting logic, the command applies default formatting. The default format view is called GraphItem, and you can override any formatting by using Format-Table with the View parameter:

PS> Get-GraphResourceWithMetadata me/contacts | Format-Table -View GraphItem
Info Type    Preview        Name
---- ----    -------        ----
t +> contact Cosmo Jones    XMKDFX1
t +> contact Akeelah Smith  XMKDFX2
t +> contact John Henry     XMKDFX4
t +> contact Deandre George XMKDFX8

The Preview column shows data returned from Graph that Get-GraphResourceWithMetadata deems to be most human-readable -- in this case, the displayName of the contact entity.

Items with a + in the Info field returned by Get-GraphResourceWithMetadata contain content. The actual content, i.e. data that would be returned by Get-GraphResource is not displayed by default, but you can use the Select alias of PowerShell to retrieve the Content property of each row. In this way Get-GraphResourceWithMetadata returns a superset of the data returned from Get-GraphResource. The use of Get-GraphResource below

Get-GraphResource me/contacts/XMKDFX1

returns the contact with id XMKDFX1. The same object may also be retrieved through the sequence below through Get-GraphResourceWithMetadata (alias gls) using the First parameter :

PS> gls me -First 1

              Email DisplayName    Phone                  Address
              ----- ----------     -----                  -------
cosmo@soulsonic.org Cosmo Jones    Mobile: (313) 555-7334 Home: 1075 Chalfonte

You can also use the Format-List to make the output look the same as Get-GraphResource (ggr):

gls me/contacts | Format-List
ggr me/contacts

The $LASTGRAPHITEMS variable

As with any PowerShell cmdlet that returns a value, the Get-GraphResource, Get-GraphResourceWithMetadata, Get-GraphChildItem cmdlets can be assigned to a variable for use with other commands or simply to allow you to dump properties of objects and their child objects:

$mycontacts = gls me/contacts

However, even if you neglect to make such an assignment, the results of the last invoked Get-*Item cmdlet is available in the $LASTGRAPHITEMS variable:

PS> gls me/drive

Info Type      Preview  Name
---- ----      -------  ----
t +> drive     OneDrive drive
n* > driveItem          items
n  > list               list
n  > driveItem          root
n* > driveItem          special

PS> $LASTGRAPHITEMS[0].content.lastModifiedDateTime
2017-06-12T03:12:33Z

This makes ad-hoc exploration of the Graph less expensive -- you don't have to query the Graph again with another cmdlet to examine the objects you just recently retrieved. They are available in $LASTGRAPHITEMS.

IMPORTANT NOTE: The example above illustrates an import aspect behavior of Get-GraphResourceWithMetadata:

  1. When the Uri argument for the cmdlet references a collection, the result is simply the items in the collection. This is actual data from Graph.
  2. But when the Uri references a single entity AND the URI is not empty AND is not the value ., only that entity is returned.
  3. If the URI references a single entity and is either not specified or is the value ., the returned elements are the allowed segments (if any) that can immediately follow the entity in a valid Uri for Graph. This is metadata about Graph that reveals its structure.
  4. This contrasts with Get-GraphResource which only exhibits the behavior in (1).

Note that Get-GraphChildItem is similar to Get-GraphResourceWithMetadata, except it does not exhibit the behavior in (2) above, it always exhibits both (1) and (3) at the same time.

This makes the Get-GraphResourceWithMetadata / gls and Get-GraphChildItem commands effective ways to recursively discover the Uris for both Graph data and structure (metadata).

Explore new locations

You may have noticed that after the first time you invoked gls, your PowerShell prompt displayed some additional information:

[starchild@mothership.io] /v1.0:/
PS>

By default, AutoGraphPS automatically adds this to your path on your first use of the exploration-oriented cmdlets Get-GraphResourceWithMetadata, Get-GraphChildItem and Set-GraphLocation (alias gcd). The text in square brackets denotes the user identity with which you've logged in. The next part before the : tells you what Graph API version you're using, in this case the default of v1.0. The part following this is your location within that API version. Any Uris specified to Get-GraphResourceWithMetadata, Get-GraphChildItem, Get-GraphResource, or Set-GraphLocation are interpreted as relative to the current location, in very much the same way that file-system oriented shells like bash and PowerShell interpret paths specified to commands as relative to the current working directory. In this case, your current location in the Graph is /, the root of the graph.

With AutoGraphPS, you can traverse the Graph using gls and gcd just the way you'd traverse your file system using ls to "see" what's in and under the current location and "move" to a new location. Here's an example of exploring the /drive entity, i.e. the entity that represents your OneDrive files:

gcd /me/drive/root/children
[starchild@mothership.io] /v1.0:/me/drive/root/children
PS> gls

   Graph Location: /v1.0:/me/drive/root/children

CreatedBy           LastModifiedDateTime     Size Name
---------           --------------------     ---- ----
cosmo@soulsonic.org 2017-10-24 21:36       612440 Recipes
cosmo@soulsonic.org 2018-06-18 12:38         7581 Pyramid.js
cosmo@soulsonic.org 2021-05-20 12:50       221260 Panther.md
cosmo@soulsonic.org 2017-01-04 14:06     34400953 Spacetime

If you'd like to know what's "inside" of Recipes, you can gcd and gls again:

[starchild@mothership.io] /v1.0:/me/drive/root/children
PS> gcd me/drive/root/children/Recipes
[starchild@mothership.io] /v1.0:/me/drive/root/children/Recipes
PS> gls

   Graph Location: /me/drive/root/children/Recipes

Info Type             Preview    Name
---- ----             -------    ----
t +> driveItem        Recipes candidates
n* > driveItem                   children
n  > listItem                    listItem
n* > permission                  permissions
n* > thumbnailSet                thumbnails
n* > driveItemVersion            versions
n  > workbook                    workbook

PS> gcd children

[starchild@mothership.io] /v1.0:/me/drive/root/children/Recipes/children
PS> gls

   Graph Location: /v1.0:/me/drive/root/children/Recipes/children

CreatedBy           LastModifiedDateTime     Size Name
---------           --------------------     ---- ----
cosmo@soulsonic.org 2001-10-25 12:03        17831 SweetPotatoPie.md
cosmo@soulsonic.org 2008-11-04 21:14        84003 Gumbo.md

Note that as in the file system case, you can save yourself a lot of typing of long Uri's by using gcd and taking advantage of the ability to use shorter Uri's relative to the current location.

Of course, you can always use absolute URI's that are independent of your current location, just start the Uri with /, e.g.

gls /me/contacts

which returns the same result regardless of your current location, for which you can also query using get-graphlocation, aka gwd (like pwd in the file system):

gwd

Path
----
/v1.0:/me/drive/root/children/candidates/children

Query capabilities

The Microsoft Graph supports a rich set of query capabilities through its OData support. You can learn the the specifics of MS Graph and OData query specification as part of MS Graph REST Uri's via Graph's query documentation, the OData query tutorial, or simply by using the Graph Explorer.

AutoGraphPS's query capabilities are exposed in the Get-GraphResource Get-GetGraphItemWithMetadata (ggr and gls aliases respectively), and Get-GraphChildItem commands. To use them, you don't need to construct query Uri's as you might if you were making direct use of OData. And in most cases you will not need to know very much about OData.

Filtering data with -Filter and -SimpleMatch

The one area of AutoGraphPS usage in which it is helpful to understand OData is the filtering language. The -Filter option on Get-GraphResource and Get-GraphChildItem allows you to specify an OData query to limit the result set from Graph to items that satisfy certain conditions much like a SQL where clause. The query is performed by the Graph service, so your network and AutoGraphPS don't have to waste time processing results that don't match the criteria you specified:

gls me/people -Filter "department eq 'Ministry of Funk'"

In the example above we've retrieved all people related to me whose department property is equal to Ministry of Funk. Note that single quotes are used to delimit strings in the OData query syntax, and eq is an equality operator. OData supports many other operators, including mathematical and logical operators as well as additional operators related to strings, such as startsWith:

gls /users -Filter "startsWith(mail, 'pfunk')"

This example returns all the users whose mail property (i.e. their e-mail address) starts with pfunk.

Complex expressions combining multiple operators using logical operators like and, or, and not allow for far more complicated queries involving multiple predicates. For more details on OData filter queries, consult Microsoft Graph documentation for $filter.

A simpler solution: -SimpleMatch

While the filter syntax is powerful and flexible at the expense of learning the query language syntax, SimpleMatch requires no syntax. Just supply the term you want to search for -- SimpleMatch will try to "fuzzy" matches against commonly used fields. The command

gls /users -SimpleMatch pfunk

will return the same results as the previous query since the startsWith is one of the filters it applies. In many casual interactive cases, SimpleMatch will get the job done without the need to recall OData filter syntax.

Limiting enumerations with -First and -Skip

Another way to avoid excessive traffic between AutoGraphPS and Graph is to use the -First and -Skip options, which are PowerShell standard parameters for paging. These options are implemented as analogs to the $top and $skip options in OData syntax. For example:

gls me/contacts -first 3

returns the first 3 items in your contacts list.

Note that by default Graph will limit the number of results returned even when you don't specify -first to avoid performance issues with the service. If you have 200 contacts and would like to retrieve them all with one command, you'll need to specify -all to get them as Graph currently defaults to returning only 10 at a time:

# If I have hunreds of contacts contacts, this ensures I get them all :)
gls me/contacts -All

You can also skip ahead -- perhaps you want the last elements of a sorted collection or you've already read some number of previous items:

gls me/contacts -Skip 3 -First 3

Projecting fields with -Property

By default, Graph does not return all properties of an object -- it returns those deemed most likely to be useful without retrieving every field to avoid excessive network traffic. For example, the query below for a given user is missing the department property in the response:

ggr /users/starchild@mothership.io

id                : 82f53da9-b996-4227-b268-c20564ceedf7
officeLocation    : 7/3191
@odata.context    : https://graph.microsoft.com/v1.0/$metadata#users/$entity
surname           : Okorafor
mail              : starchild@mothership.io
jobTitle          : Professor
givenName         : Starchild
userPrincipalName : starchild@mothership.io
businessPhones    : +1 (313) 360 3141
displayName       : Starchild Okorafor

To fix this, use -Property to project the exact set of fields you're interested in. This has the benefit of allowing you to reduce network consumption as well, which is most useful when handling large result sets:

ggr me -property displayName, department, mail, officeLocation

@odata.context : https://graph.microsoft.com/v1.0/$metadata#users(displayName,department,mail,officeLocation)
                 /$entity
department     : Ministry of Funk
displayName    : Starchild Okorafor
officeLocation : 7/3191
mail           : starchild@mothership.io

For a better understanding of how and when OData services like Graph can project properties, see the Microsoft Graph documentation for $select.

Sorting with -Sort and -Descending

Particularly when retrieving large result sets, it is important to be able to specify the order in which items are returned. For an e-mail inbox with 1000 messages for instance, you may only want to retreive the first 50 or so after sorting by descending date (the most recent messages). In other cases you may want to sort on multiple fields.

The following example uses the -Sort option to retrieve the 10 oldest messages -- note that we use PowerShell's client-side select cmdlet to limit the displayed fields:

ggr /me/messages -first 10 -Sort receivedDateTime | select ReceivedDateTime, Importance, Subject

However, to retrieve the 10 newest high-importance items, you should specify the -Descending option, which means that all fields specified through OrderBy will have a descending order by default:

ggr /me/messages -first 10 -Sort receivedDateTime, importance -Descending | select ReceivedDateTime, Importance, Subject

What if you want to sort using ascending order for one field, and descending for another? You can specify a PowerShell HashTable to identify sort directions for specific fields. This lets you find the oldest high-importance items:

ggr /me/messages -first 10 -Sort @{receivedDateTime=$false;importance=$true} | select ReceivedDateTime, Importance, Subject

The $false assignment in the hash table means that the field will use ascending sort order, and $true means descending order.

Beyond read-only -- writing the Graph

AutoGraphPS isn't just an excellent browsing experience, it features commands for modifying the Graph as well:

  • New-GraphItem
  • Set-GraphItem
  • New-GraphObject
  • New-GraphItemRelationship
  • Remove-GraphItem

The following examples demonstrate common usage of these commands in creating and editing Graph resources. Unlike the read-only cases we've explored so far, you're likely to need some light research before jumping in with these command, i.e. you might want to read the actual Graph API documentation first. In particular, you'll need to have some basic answers in mind for the following questions before using write-operation commands:

  • When creating a resource
    • What is the name of the resource you want to create / modify? E.g. user, group, message, etc.
    • Alternatively do you know the resource's parent URI, e.g. users for user, me/contacts for contact, etc.
    • What properties of the resource are required and must be set at creation time? You can find this information in the searchable / browseable Graph API reference; you can use the Show-GraphHelp command to quickly access the documentation for a resource if you know its name (e.g. Show-GraphHelp contact).
    • What are the other (non-required) properties you'd like to configure? The commands will automatically complete the property name parameters if you've already specified the resource name or URI to help you find them. You can also use Get-GraphType <resourcename> -Members to get a list of the property names and their data types.
  • When modifying an existing resource
    • As in create, what is the name of the resource you want to modify?
    • And alternatively, what is the resource's URI, e.g. users/user@domain.com for a user, me/contacts/<contactid> for a contact
    • What are the names of the properties you want to modify and what are their types? Just like the create use case, parameter completion, Get-GraphType -Members and Show-GraphHElp to find out.

With this information, you can use a single command to create or update the resource -- just supply it with

  • For create: resource name and required property values OR parent URI and required property values
  • For modify: resource name and ID and the property values to update OR resource URI and the property values

Lastly, while in many cases the property values are simple "primitive" types like strings and integers that can be easily expressed as command parameters, they may also themselves be nested "complex" data structures that contain their own properties. If you've obtained the type name of that property from the documentation (remember Show-GraphHelp) or Get-GraphType, you can specify that name to the New-GraphObject command with the TypeClass Complex parameter, i.e.:

# This command also lets you specify properties -- if you don't, it creates
# an object with empty values for all top-level properties
$ipRange = New-GraphObject ipv6Range

You can then set the properties of the $ipRange variable as desired, and specify that variable as one of the property values to New-GraphItem or Set-GraphItem.

Now this preamble may seem rather lengthy compared to our near zero-knowledge approach of read-only use cases, but all of this really boils down to the need to know what you're going to change before you make an update, which isn't unreasonable for these more "dangerous" write use-cases. Fortunately, the answers to all of those questions are a few tab-completions or Get-GraphType / Show-GraphHelp invocations away.

Now let's see concrete examples of these commands in action.

The prerequisite: get write access

Our earlier examples were read-only operations. For write-operations to succeed, you'll need to request specific write permissions, so execute this command before you try the examples:

Connect-GraphApi User.ReadWrite.All, Group.ReadWrite.All, Contacts.ReadWrite

A few notes are in order:

  • As long as you sign in with the same account used to execute the command above, you'll only need to run it once -- even if you start a new PowerShell session and sign in without executing that command, AAD will continue to grant those permissions to AutoGraphPS until you take an explicit action to revoke your consent to those permissions.
  • These write permissions are typically not available to ordinary users in an AAD tenant; for the AAD-only scenarios, you'll need to be highly-privileged. If this isn't the case for you in your standard usage, you should use an alternate tenant, potentially one acquired through a developer program or trial Azure subscription.

Create a simple resource: group (AAD accounts only)

This example uses the New-GraphItem command to creates a new AAD security group:

PS> $newGroup = New-GraphItem group -Property mailNickName, displayName, mailEnabled, securityEnabled -Value Group7Access, 'Group 7 Access', $false, $true

The example specifies the following parameters for New-GraphItem:

  • The first (unnamed) parameter group specifies that the type of the resource to create is group
  • The Property parameter specifies the properties that must be set for the new resource
  • The Value parameter specifies the values to which those properties must be set

Note that the choice of properties to specify was not arbitrary; the Graph documentation for the user resource states that for our use case the mailNickName, displayName, mailEnabled, and securityEnabled properties are mandatory. Additional properties may also be specified so that the resulting resource object has those properties configured to desired values, but the properties given in this example are the minimum set required for the user resource.

When the command is issued, a request is made to Graph to create the security group, and Graph returns the serialized representation of the Group, the same representation that would be returned if that group were retrieved from Graph through Get-GraphResource.

Since the value was assigned to the variable $newGroup, it can be used in subsequent script operations, or as in the remainder of this example, inspected simply by evaluating it:

PS> $newGroup | select createdDateTime, displayName, mailNickname

createdDateTime      displayName    mailNickname
---------------      -----------    ------------
2020-04-22T01:27:41Z Group 7 Access Group7Access

The example specifies the following parameters for New-GraphItem:

  • The first (unnamed) parameter group specifies that the type of the resource to create is group
  • The Property parameter specifies the properties that must be set for the new resource
  • The Value parameter specifies the values to which those properties must be set

Additionally, following the invocation of New-GraphItem that creates the new security group, the example assigns the result of the creation to the variable $newGroup, and then outputs 3 columns of the variable to the console for inspection.

Create a resource with nested data: user

Creating a group was easy. Let's create a user. Before doing so, a quick consultation of the user resource's documentation via Show-GraphHelp user indicates that we need to specify the following properties at creation time: mailNickName, userPrincipalName, displayName, accountEnabled, and passwordProfile. The first four are simple types (string, string, string, and bool respectively) that we know how to specify via command line arguments, but according to the documentation (and also Get-GraphType user), the last is of type passswordProfile, how do we specify that?

We can use the New-GraphObject command to create the data structures as PowerShell objects (or optionally as JSON text). New-GraphObject does not issue requests or otherwise interact with Graph, but the local objects it creates can be can be specified to commands like New-GraphItem, Set-GraphItem, etc., that must submit such objects in requests. The parameters of New-GraphObject have similar naming and semantics to thsoe of New-GraphItem when it comes to types and properties, so after consulting the documentation for passwordProfile we see that we can create the passwordProfile object with this command:

$passwordProfile = New-GraphObject passwordprofile -Property forceChangePasswordNextSignIn, password -value $true, (Get-Credential user).GetNetworkCredential().Password

Note that we could have directly specified the password property value via the Value parameter, but we chose to use Get-Credential which provides an interactive UX for you to type the password. This approach prevents anyone nearby from seeing the password and also keeps it out of your PowerShell command history (however, it is now unencrypted in memory because it must be passed this way to Graph, hence care must be taken when configuring symmetric keys like this via Graph -- certificates are a much safer form of credential for instance).

Now we're ready to actually make the request to Graph that creates the user:

# This assumes you set `$passwordProfile` using the earlier `New-GraphObject` example
$newUser = New-GraphItem user -Property mailNickname, userPrincipalName, displayname, accountEnabled, passwordProfile -Value treejack, treejack@newnoir.org, 'Treemonisha Jackson', $true, $passwordProfile

We can see that the user has been successfully created by issuing a request to the Graph to get the new user using a command like the following:

PS> $newUser | Get-GraphItem

   Graph Location: /users

Id                                   DisplayName         Job Title UserPrincipalName
--                                   -----------         --------- -----------------
8618a75d-a209-44f3-b2f8-2423cb211eed Treemonisha Jackson Director  treejack@newnoir.org

Create a resource with nested data: contact

In this example, a new contact (i.e. e-mail or phone contact; requires either a free Microsoft account or non-trial AAD subscription) for the signed-in user is created. Here's a first attempt:

PS> $newContact = New-GraphItem contact

foreach : Exception calling "InvokeMethod" with "2" argument(s): "Unable to find URI for type 'contact' -- explicitly specify the target URI and retry."

Unfortunately, that didn't work. The exception and resulting error message indicating we should specify the target URI and retry means that New-GraphItem could not translate our request to create a contact to a REST URI for Graph. Unlike the user case, there is no well-known request URI at which to create a contact (for user it is a URI like https://graph.microsoft.com/v1.0/users) because contact is the type of object that exists only in relation to another object, not as a standalone instance.

Actually reviewing the REST documentation for contact shows that the use case for creating a contact is accomplished via POST to the resource URI https://graph.microsoft.com/v1.0/me/contacts. Conveniently, New-GraphItem supports a Uri parameter that allows you to specify the URI (me since AutoGraphPS commands abstract the earlier parts of the URI), so after searching for additional documentation about contact and experimenting to make up for the fact that as of this writing the documentation lacks details about which properties are required at creation time, we can execute the following commands to create a contact:

# Create the emailAddress object that is required at creation time as an array
$emailAddress = New-GraphObject emailAddress -Property name, address -Value Work, cleo@soulsonic.org
$newContact = New-GraphItem -uri /me/contacts -Property givenName, emailAddresses -Value 'Cleopatra Jones', @($emailAddress)

# Dump the returned item to the console
PS> $newContact | select createdDateTime, displayname, emailAddresses

createdDateTime      displayName     emailAddresses
---------------      -----------     --------------
2020-04-22T04:20:11Z Cleopatra Jones @{name=Work; address=cleo@soulsonic.org}

You can confirm that it really exists in the Graph by invoking a command such as

Get-GraphResource -Uri /me/contacts | where displayName -eq $newContact.displayName

which will issue a request to Graph to retrieve all contacts, and then filter the results with where to find the new contact by its display name and output it.

Update an existing resource: contact, group, and user

To modify an existing Graph resource, use the Set-GraphItem command. You can pipe in the result of a previous Get-GraphItem, Get-GraphResource, New-GraphItem, etc., invocation as the object to modify:

$newGroup | Set-GraphItem -Property displayName, description -Value 'Group 7 Access Level', 'All users with Group 7 access'

This changes the group's display name to Group 7 Access Level and updates the description as well. This example takes the object to modify from the pipeline. Since this command has analogs of parameters from New-GraphItem and New-GraphObject, you can also specify commands using the following syntax:

Set-GraphItem group $newGroup.Id -Property displayName, description -Value 'Group 7 Access Level', 'All users with Group 7 access'

And there are still more equivalent syntaxes, using the PropertyTable or TemplateObject parameters. PropertyTable is just a more concise way to specify the Property and Value parameters via a HashTable:

$newGroup | Set-GraphItem -PropertyTable @{displayName='Group 7 Access Level'; description='All users with Group 7 access'}

The TeamplateObject parameter allows the these properties and values to be specified in the form of an object, such as one returned by New-GraphObject or even from the Graph itself via Get-GraphResource or gls:

$modifiedGroup = New-GraphObject group description 'Just the description'
$newGroup | Set-GraphItem -TemplateObject $modifiedGroup

$newGroup | gls | select displayname, description

displayName          description
-----------          -----------
Group 7 Access Level Just the description

Both the TemplateObject and PropertyTable parameters can be specified simultaneously -- this could be useful for copying parts of one object as a "template" while adding additional properties:

$existingGroup = Get-GraphItem group 4e5701ac-92b2-42d5-91cf-45f4865d0e70

$existingGroup | gls | select description, displayName

mailNickname displayName description
------------ ----------- -----------
             Unused      Unassigned group

$templateGroup = Get-GraphItem group 0b828d58-2f7d-4ec5-92fb-20f0f88aa1a2 -Property displayName, description

$existingGroup | Set-GraphItem -TemplateObject $templateGroup -PropertyTable @{mailNickName='dorateam'}

$existingGroup | gls | select description, displayName

mailNickname displayName description
------------ ----------- -----------
dorateam     Team group  Standard team collaboration group

Finally, an object returned from the Graph may be "edited" locally and then resubmitted so that the local changes are reflected in the Graph. In this case, the GraphItem parameter supplied to the pipeline is both the target item to update and the source of data to modify:

$existingGroup = Get-GraphItem group 4e5701ac-92b2-42d5-91cf-45f4865d0e70
$existingGroup.displayName += ' - ' + [DateTime]::now

$existingGroup | Set-GraphItem

$existingGroup | gls | select description, displayName

description                       displayName
-----------                       -----------
Standard team collaboration group Team group - 05/16/2019 15:14:41

Note that Set-GraphItem includes an ExcludeObjectProperty parameter that allows you to ignore properties specified through TemplateObject and GraphItem which is useful when the object contains read-only properties that may have been returned as part of an object from a previously executed command.

Link resources: add a user to a group (AAD accounts only)

The Graph is not just about individual resources, its power comes from the relationships between those resources. With groups and users for example, the fact that users are members of a group is modeled by a relationship property called members. This means that by modifying the members relationship property, we can modify which users are members of a group.

To modify a relationship, the New-GraphItemRelationship command may be used:

New-GraphItemRelationship -FromItem $newGroup -ToItem $newUser -Relationship members


Relationship TargetId                             FromUri                                      TargetUri
------------ --------                             -------                                      ---------
members      71edbed4-a7e7-4b46-8e2f-a28e9135ca54 /groups/f8418e85-e865-45ba-bec6-d16b6dc44045 /directoryObjects/e510d45c-aec3-4026-ba8e-317480ae7bc5

This adds a directional relationship between the group and the user "group is related to user" through the members relationship. In accordance with the API documentation for group, the interpretation of this relationship is that the user is now a member of the group.

To see the new relationship, but not the related items themselves, use the Get-GraphItemRelationship command:

$newGroup | Get-GraphRelatedItem -WithRelationship members

To see the actual items from the relationships (e.g. the members of a group and not just their id's), use the Get-GraphRelatedItem command:

$newGroup | Get-GraphRelatedItem -WithRelationship members

   Graph Location: /v1.0:/groups/053850da-691d-4605-9bda-6b3d74c7addb/members

Info Type Preview             Id
---- ---- -----------         --
t +> user Treemonisha Jackson 8618a75d-a209-44f3-b2f8-2423cb211eed

Another useful syntax for New-GraphItemRelationship is to supply the items on the "to" side of the relatinoship via the pipeline -- this exploits PowerShell pipeline idioms to simplify operating on sets of objects:

# Create passwords for some new users
$passwordProfile1 = New-GraphObject passwordprofile -Property forceChangePasswordNextSignIn, password -Value $true, (Get-Credential user).GetNetworkCredential().Password
$passwordProfile2 = New-GraphObject passwordprofile -Property forceChangePasswordNextSignIn, password -Value $true, (Get-Credential user).GetNetworkCredential().Password

# Create the actual users
$newUser1 = New-GraphItem user -Property mailNickname, userPrincipalName, displayname, accountEnabled, passwordProfile -Value vashford, vashford@newnoir.org, 'Val Ashford', $true, $passwordProfile1
$newUser2 = New-GraphItem user -Property mailNickname, userPrincipalName, displayname, accountEnabled, passwordProfile -Value nsimpson, nsimpson@newnoir.org, 'Nick Simpson', $true, $passwordProfile2

# Create a new group for the users
$teamGroup = New-GraphItem group mailNickName, displayName, mailEnabled, securityEnabled Group7AccessT1, 'Group 7 Access 2', $false, $true

# Add the users to the group
$newUser1, $newUser2 | New-GraphItemRelationship $teamGroup members | out-null

# Display the group's updated membership with the new users

$teamGroup | Get-GraphRelatedItem -WithRelationship members

   Graph Location: /v1.0:/groups/c436312c-4f6e-4963-ac05-bf68b98d7475/members

Info Type Preview      Id
---- ---- -------      --
t +> user Val Ashford  d126df35-b441-472e-a3d1-2de370cbbbb7
t +> user Nick Simpson aafbc281-cce2-450b-9409-7113033d2f62

Removing a relationship

The inverse of the New-GraphItemRelationship command is Remove-GraphItemRelationship. In this example the user with id 36d3e3d4-55f2-405f-a601-fd522b7998f4 is removed from the group with id 51a617a1-9174-4836-9a8c-d1cee804bc61:

Remove-GraphItemRelationship -FromType group -FromId 51a617a1-9174-4836-9a8c-d1cee804bc61 -Relationship members -Id 36d3e3d4-55f2-405f-a601-fd522b7998f4

A syntax that supports an object rather than identifier for the subject or object of the relationship or both is also available:

Remove-GraphItemRelationship -FromItem $existingGroup -Relationship members -ToId 36d3e3d4-55f2-405f-a601-fd522b7998f4

And the pipeline is also supported -- these example removse all members from the group $teamGroup:

$teamGroup | Get-GraphItemRelationship -WithRelationship members | Remove-GraphItemRelationship

Delete resources

Lastly, the resources we've created can all be deleted using the Remove-GraphItem command. This example deletes the group with the id c436312c-4f6e-4963-ac05-bf68b98d7475:

Remove-GraphItem group c436312c-4f6e-4963-ac05-bf68b98d7475

The command also takes an object returned by Get-GraphItem or Get-GraphResource, etc., which is useful for deleting resources accessible only through a relationship with another resource, such as contact:

# If you have a variable containing the contact retrieved by an AutoGraphPS
# command such as Get-GraphResource, New-GraphItem, gls, etc., you can easily
# delete it by passing it to Remove-GraphItem
$oldContact | Remove-GraphItem

Write operation tips and tricks

  • Use Show-GraphHelp to get the documentation for the Graph resource in which you're interested.
  • Use Get-GraphType -Members to view the properties of the type or structure you're updating
  • Use parameter completion with commands like New-GraphObject, New-GraphItem, Set-GraphItem, etc. to ease your workflow and avoid the need to refer to documentation
  • Pay attention to error messages from Graph -- these messages will often give useful information about missing or invalid properties so that you can try again, or at least help you navigate a particular help topic.
  • Look for opportunities to use the PowerShell pipeline with AutoGraphPS command for concise, efficient, and scalable automation of Graph resource management.

Invoking Graph methods for complex behaviors and operations

We've covered the ways in which data can be created, updated, queried, and deleted. Graph also supports capabilities beyond those circumscribed by such CRUD operations through methods. Graph methods, not to be confused with methods like PUT, GET, et. al. of the HTTP protocol upon which the Graph protocol is layered, can perform arbitrary operations. These Graph methods are very much like methods in the object-oriented sense, i.e. they are named units of computation that operate on state within the scope of some object. Methods in Graph have the following characteristics:

  • Methods are scoped to an entity type, e.g. user, group, drive, etc.
  • Methods have a name that can be used to invoke them -- examples are sendMail on the user object, assignLicense on the group object, or search on the drive object.
  • To invoke a method, you need to be able directly or indirectly space the id of the entity and the name of the method

Within the framework of the Graph's REST protocol, invoking a method involves constructing the appropriate URI just as in the case of all the CRUD operations. The URI in this case will refer to both a particular instance of an entity, typically using an id but potentially using a singleton such as me.

Using Invoke-GraphMethod to access Graph methods

AutoGraphPS provides the Invoke-GraphMethod command to make it easy to invoke methods. Here's an example that performs a search of the user's drive, using the search method of the calling user's drive and passing the query string in the method's q parameter:

Invoke-GraphMethod /me/drive/search q 'name:docx powershell'

Info Type      Preview                                               Id
---- ----      -------                                               --
t +> driveItem Exploring the Microsoft Graph.docx                    AX893842
t +> driveItem PowerShell user experience for Graph.docx             ZZ8972ZR
t +> driveItem DevOps and PowerShell use cases.docx                  KSJFLAJ3
t +> driveItem Analysis of PowerShell and REST API usability         K8JRAJZE

The method returned a set of driveItem objects that satisfied the query name:docx powershell, the intent of which was to return any content on the drive that contained the keyword powershell in items with a name containing docx.

Since the URI given as the first parameter is relative to the current location, the following invocation would produce the same result:

gcd /me/drive
Invoke-GraphMethod search q 'name:docx powershell'

In the next example, Invoke-GraphMethod is used to send an email message. In this case, we construct the email message to send using New-Graphobject and New-GraphMethodParameterObject before passing it as a parameter to Invoke-GraphMethod:

$me = gls me
$recipientEmail = new-graphobject emailAddress address katwe@newnoir.org
$recipient = new-graphobject recipient emailAddress $recipientEmail

$sendMailParameters = New-GraphMethodParameterObject user sendMail

$sendMailParameters.SaveToSentItems = $true
$sendMailParameters.Message = $sendMailParameters.Message | select subject, body, toRecipients

$sendMailParameters.Message.toRecipients = @($recipient)
$sendMailParameters.Message.Subject = 'What time is it?'
$sendMailParameters.Message.Body.ContentType = 'text'
$sendMailParameters.Message.Body.Content = "The time is $([DateTime]::now), so it's time to Wake Up! --Love, $($me.Content.givenName)"

$me | Invoke-GraphMethod -methodname sendmail -ParameterObject $sendMailParameters

In this case, we invoke the method sendMail on the previously retrieved $me object by passing $me in the pipeline. The constructed parameters are specified by the ParameterObject parameter of Invoke-GraphMethod.

Discovering resources, methods, URIs, and documentation

All of the rich Microsoft Graph operations described here, even simple read operations, require you to have some minimum amount of knowledge, e.g. the URI or the "type" of the resource you'd like to access. You will find all of the required information in the Microsoft Graph documentation. Fortunately, you don't have to spend your time poring over manuals: AutoGraph provides the tools that often eliminate the need to leave PowerShell to read documentation, or at least help you find the right docs for your scenario.

The commands described below are very useful when you know you want to use Microsoft Graph to do something, but you're not sure where to start.

What is the name of the resource I need?

In the Microsoft Graph documentation, the term "resource" is used to refer to any aspect of the Graph that is addressable by an identifier (whether a URI or non-URI) and that is capable of relating to or being related to by other resources. The OData specification upon which Microsoft Graph is currently defined at the protocol level refers to this same resource concept as an "entity type," and this latter terminology is mostly what you'll find in AutoGraphPS. Either way, resources / entity types are the key concepts exposed by Graph that form a cohesive model within and across the problem domains (e.g. security, communication, document management, etc.) that you understand. Once you understand the resource for your use case, you've unlocked your comprehension of a meaningful chunk of the Graph.

Let's say you want to find a security group that has a specific name. Since you know you want to do something with a "group," you can look for resources related to "group" using the Find-GraphType command:

Find-GraphGype group

TypeClass TypeId                               Criteria MatchedTerms
--------- ------                               -------- ------------
Entity    microsoft.graph.group                {Name}   {microsoft.graph.calendargroup microsoft.graph.group microso...
Entity    microsoft.graph.grouplifecyclepolicy {Name}   {microsoft.graph.calendargroup microsoft.graph.group microso...
Entity    microsoft.graph.schedulinggroup      {Name}   {microsoft.graph.calendargroup microsoft.graph.group microso...

The output of Find-GraphType in this case is a list of types deemed "relevant" to the supplied search term group. The results are sorted by relevance, so the first item in the list can be treated as most likely to be the one you're looking for. The command contains several parameters that allow you to customize the criteria used to identify matches as well as the "fuzziness" of those matches.

Now I know the resource, where are the docs so I can understand what it does?

The Show-GraphHelp command launches a web browser with the web page displaying help for a resource specified by URI, an object returned as a response from the Graph API, and also the name of a type, i.e. Graph resource. Since the command takes input from the pipeline, the type can be supplied by the output of the Find-GraphType command we demonstrated above:

Find-GraphGype group | Select-Object -First 1 | Show-GraphHelp

This command shows the help page for the first result in the output of the search for group, the result for microsoft.graph.group. Now you can read about the group resource to confirm that it is indeed the resource you need, and also find out more details about the operations that can be performed on the resource.

What permissions do I need?

The documentation for the resource should contain information describing the permissions you need to perform specific operations on the resource. If you don't already have the web-based documentation for the relevant operation in view, you can look for this information within your PowerShell session via the Find-GraphPermission command:

Find-GraphPermission group

Type      ConsentType Name                                    Description
----      ----------- ----                                    -----------
Delegated Admin       Group.Read.All                          Allows the app to list groups, and to read their prope...
Delegated Admin       Group.ReadWrite.All                     Allows the app to create groups and read all group pro...
AppOnly   Admin       Group.Read.All                          Allows the app to read group properties and membership...
AppOnly   Admin       Group.ReadWrite.All                     Allows the app to create groups, read all group proper...

Using this output which includes a description of the permission, you can determine which permission applies to your scenario. Do you need write access, or just read? Are you accessing the resource using interactive delegated sign-in, or as part of a non-interactive background application? Once you've used those answers to narrow the list down to the least-privileged permission required for your scenario, you can request that permission using the Connect-GraphApi command so that your application is able to access the resource.

Can I use PowerShell to look at the resource's documentation instead of a web browser?

Currently, the resource's rich documentation, including examples, is available only the web browser and can be accessed via the Show-GraphHelp command. However, in many cases your focus is on understanding the structural properties, methods, or relationships between resources. This information about the types is available within PowerShell via several commands: Get-GraphType, Get-GraphMember, and Get-GraphMethod. For example, here is the output of Get-GraphType for the group resource identified earlier:

Get-GraphType group

   Graph TypeId: microsoft.graph.group


TypeClass     : Entity
BaseType      : microsoft.graph.directoryObject
DefaultUri    : /groups
Relationships : {appRoleAssignments, createdOnBehalfOf, memberOf, members...}
Properties    : {assignedLabels, assignedLicenses, classification, createdDateTime...}
Methods       : {subscribeByMail, validateProperties, unsubscribeByMail, resetUnseenCount...}
GraphName     : v1.0

The resulting object contains information such as the "default" URI, this case /groups, under which instances of the group resource may be found. You can use Select-Object to project the Relationships field that shows the connections with other resources, the Properties field which gives the structure of the object, and the Methods field which returns all of the methods available to the object.

Is there an easier way to see the properties?

Yes -- the Get-GraphMember command which is similar to the widely utilized core PowerShell command Get-Member emits output that provides a streamlined representation of all of the members, including Property, Relationship, and Method members. To view just the structural properties, the MemberType parameter can be specified with the value Property:

Get-GraphMember group -MemberType Property

   MemberType: Property (microsoft.graph.group)

Name                          MemberType TypeId                                      IsCollection DefiningTypeId
----                          ---------- ------                                      ------------ --------------
allowExternalSenders          Property   Edm.Boolean                                 False        microsoft.graph.group
assignedLabels                Property   microsoft.graph.assignedLabel               True         microsoft.graph.group
assignedLicenses              Property   microsoft.graph.assignedLicense             True         microsoft.graph.group
...
theme                         Property   Edm.String                                  False        microsoft.graph.group
unseenCount                   Property   Edm.Int32                                   False        microsoft.graph.group
visibility                    Property   Edm.String                                  False        microsoft.graph.group

For each property name the data type is given as well as whether it's a collection, and can save you a trip to the web browser.

How do I understand the structure of a properties that aren't just strings or numbers?

In the example above, the properties that are named without the Edm prefix are actually structured to definitions present in the API schema. They are typically nested structures, and you can simply use Get-GraphMember again along with the associated resource name (TypeId in acccordance to the output of Get-GraphMember) to find it:

Get-GraphMember assignedLicense -MemberType Property

   MemberType: Property (microsoft.graph.assignedLicense)

Name          MemberType TypeId   IsCollection DefiningTypeId
----          ---------- ------   ------------ --------------
disabledPlans Property   Edm.Guid True         microsoft.graph.assignedLicense
skuId         Property   Edm.Guid False        microsoft.graph.assignedLicense

This is useful both in understanding what to expect in terms of responses and also what you may need to supply in submitting requests that include these types. Note that simply using New-GraphObject to create an "empty" object can allow you to inspect an object representing the resource directly:

$sample = New-GraphObject group -Recurse -SetDefaultValues
$sample.licenseProcessingState

state
-----

It should be noted that when constructing resources to submit requests that create new resource instances or update them, the commands that do this actually simplify the specification of the structure. It's often unnecessary to know about every single property of a resource. See the earlier examples for New-GraphItem, Set-GraphItem, and New-GraphObject for more details on how you can efficiently specify just the properties required to submit a resource as part of a request to Graph.

I want the resource's URI so I can gcd to it and gls it

If you are using PowerShell to browse resources exposed by the Graph, the documentation will explain the URIs under which you might find them. The Get-GraphType command exposes a DefaultUri field that provides a possible location where you might find the type, though not all types have a "default" URI:

Get-GraphType group | Select-Object DefaultUri

DefaultUri
----------
/groups

gcd /groups
gls

What other resources is it related to?

The Get-GraphMember command's MemberType can be specified as Relationship to enumerate all of the relationships from this resource to other resources:

Get-GraphMember group -MemberType Relationship

   MemberType: Relationship (microsoft.graph.group)

Name                     MemberType   TypeId                                          IsCollection DefiningTypeId
----                     ----------   ------                                          ------------ --------------
acceptedSenders          Relationship microsoft.graph.directoryObject                 True         microsoft.graph.group
appRoleAssignments       Relationship microsoft.graph.appRoleAssignment               True         microsoft.graph.group
calendar                 Relationship microsoft.graph.calendar                        False        microsoft.graph.group
...
threads                  Relationship microsoft.graph.conversationThread              True         microsoft.graph.group
transitiveMemberOf       Relationship microsoft.graph.directoryObject                 True         microsoft.graph.group
transitiveMembers        Relationship microsoft.graph.directoryObject                 True         microsoft.graph.group

Does the resource have interesting methods I can call?

You can use Get-GraphMember again to view just Method members, but the specialized Get-GraphMethod command provides more focused output:

Get-GraphMethod group

   TypeId: group

Name                          MethodType ReturnType                                      IsCollection DefiningTypeId
----                          ---------- ----------                                      ------------ --------------
addFavorite                   Action                                                     False        microsoft.graph.group
assignLicense                 Action     microsoft.graph.group                           False        microsoft.graph.group
checkMemberGroups             Action     Edm.String                                      True         microsoft.graph.directoryObject
restore                       Action     microsoft.graph.directoryObject                 False        microsoft.graph.directoryObject
...
subscribeByMail               Action                                                     False        microsoft.graph.group
unsubscribeByMail             Action                                                     False        microsoft.graph.group
validateProperties            Action                                                     False        microsoft.graph.group

What are the parameters for a given method?

When you've identified a useful method, the next question is what parameters do you need to feed it? Get-GraphMethod has a Parameter option that lists just the parameters for a method:

Get-GraphMethod group assignLicense -Parameters


   Method: assignLicense

ParameterName  TypeId                          IsCollection
-------------  ------                          ------------
addLicenses    microsoft.graph.assignedLicense True
removeLicenses Edm.Guid                        True

In summary: now put your knowledge to work!

By making use of these "type" introspection commands, you can easily find your way to detailed web-based documentation when you need it, and otherwise get focused, relevant reference information within the comfortable and efficient environment of PowerShell.

Managing and customizing application identities

AutoGraphPS provides commands to manage Azure Active Directory application identities, including the ability to manage permission consent for the applications. These commands are useful for provisioning any application or service for your organization, and may also be used to create new identities for which to execute AutoGraphPS-based scripts. And because application management commands are built-in to the module, AutoGraphPS is a self-contained Graph automation solution; there is no need jump outside to other command-line or graphical tools / portals if you need to provision an application identity dedicated to automation since AutoGraphPS itself can perform the provisioning.

Provision a new application for interactive AutoGraphPS usage

By default, AutoGraphPS uses a particular multi-tenant application identity to allow you to sign in to your organization and execute all of the AutoGraphPS commands that access the Graph API. This application was provisioned by the maintainer of AutoGraphPS. Alternatively, you may use a different application with AutoGraphPS, one that you have provisioned and over which you have complete and sole control.

The New-GraphApplication command provisions a new public client application for interactive sign-in -- the application is granted the specific delegated permissions User.Read and Group.Read.All in this invocation:

$newApp = New-GraphApplication 'Custom Graph PowerShell App' -DelegatedUserPermissions User.Read, Group.Read.All -ConsentForAllUsers

Connect-GraphApi -AppId $newApp.AppId

AppId                                ConnectionName Organization                         AuthType
-----                                -------------- ------------                         --------
32a7262b-492a-4ad2-bb96-2ff8ff6cf680 (Unnamed)      24775524-ce1d-42ec-8aba-0b5763695729 Delegated

$groups = Get-GraphResource /groups

The New-GraphApplication command issued a request through the Graph API to create a new public client AAD application. Anyone in the organization can sign in to the application. Since the optional ConsentForAllUsers parameter was also specified, consent has been granted to the application for all users in the organization; they will not get prompted for consent when using it. By default, any delegated permissions specified to the command will be consented only for the user issuing the New-GraphApplication command, not the entire organization.

The subsequent use of the Connect-GraphApi command with the AppId parameter shown above allows users to sign in with this (or any other) application by specifying the application's identifier. Once signed in, AutoGraphPS commands may be issue just as they are when using the default AutoGraphPS application; the commands will be authorized by the Graph API based on the permissions granted to this application at sign-in.

In general, consent can be managed outside of application creation by using commands such as Set-GraphApplicationConsent, Get-GraphApplicationConsent, and Remove-GraphapplicationConsent

Provision a new application for non-interactive unattended AutoGraphPS usage

The built-in application identity for AutoGraphPS requires a user to sign-in before commands can be issued against the Graph. The requirement for human interaction won't work for fully automated scenarios such as running AutoGraphPS PowerShell scripts as background jobs or in other unattended contexts. To address this, you can use New-GraphApplication to provision a confidential client application that authenticates without human intervention:

# In addition to creating the app identity with Graph, this commmand
# creates a certificate in the local certificate store for unattended
# authentication
$newApp = New-GraphApplication 'Daily user information stats background app' -Confidential -ApplicationPermissions User.Read.All -NewCredential

# This command requires no user interaction because a certificate in the local
# certificate store is being used
Connect-GraphApi -AppId $newapp.AppId -NoninteractiveAppOnlyAuth -TenantId natossa@ether.org

AppId                                ConnectionName Organization AuthType
-----                                -------------- ------------ --------
3ea110e5-80e1-48a6-b67f-a3c5dc108b96 (Unnamed)                   AppOnly

$users = Get-GraphResource users

In this example, New-GraphApplication creates a new confidential client application identity and provisions it with User.Read.All. However, if the AutoGraphPS script is executed from the Windows OS, then by default New-GraphApplication also creates a certificate in the local certificate store specific to this application and configures AAD to enable that certificate as a credential for the identity.

The example continues by using the newly created application to sign in to Graph using Connect-GraphApi. In addition to specifying the new application's identifier using the AppId parameter, the Confidential parameter must be supplied to direct the command to require credentials for the application itself and not just a user's credentials, AND the NonInteractiveAppOnlyAuth parameter must be supplied so that only the application credential is required (no user interaction is desired in this case). Additionally, the TenantId parameter must be specified using either the domain name of the tenant as in this case or the the tenant identifer. This allows the command to direct the sign-in to the correct tenant.

Connect-GraphApi then searches for a certificate for the application in the local certificate store, and will find the one created by New-GraphApplication and use it to authenticate.

After authentication succeeds, the example retrieves all of the users in the tenant by invoking Get-GraphResource against the users URI.

The following considerations related to non-interactive authentication should be noted:

  • The automatic certificate provisioning in the local certificate store by New-GraphApplication is optional, and indeed only occurs on the Windows platform
  • The certificates created by New-GraphApplication and related commands can be enumerated using the Find-GraphLocalCertificate command and filtered by the application identifier.
  • New-GraphApplication allows for certificates from other sources such as files to be specified instead of allowing New-GraphApplication to create the certificate. The latter approach is fully supported for non-Windows platforms.
  • Set-GraphApplicationCertificate can be used to update the application credentials outside of the context application creation. This is useful for periodic certificate rotations prior to the expiration of the certificate for instance. In general it can be used set pre-existing certificates as application credentials (fully supported for non-Windows PowerShell environments)
  • Remove-GraphApplicatinCertificate removes the association of a certificate from an application -- this is also useful as part of a certificate rotation process. It does not impact the actual certificate itself, even if it is stored on the local system.
  • New-GraphApplicationCertificate can be used to create a new certificate as an object, in the file system, or in the local certificate store (Windows-only) and sets that certificate as a credential for the application.
  • As is the case with the applications using delegated permissions, the Set-GraphApplicationConsent, Get-GraphApplicationConsent, and Remove-GraphApplication consent commands may be used to manage application permissions outside of the application creation flow
  • Certificates used as credentials for applications are secrets and must be treated as such; they must be secured from discovery and made available only on trusted systems for authorized uses.

The parameters of all of these commands allow for additional scenarios beyond those described here, so be sure to explore the commands further in evaluating identity provisioning and sign-in capabilities against your needs.

Troubleshooting and debugging

Diagnosing errors

Whether it's due to coding defects in scripts or typos during your exploration of the Graph, you'll inevitably encounter errors. The cmdlet Get-GraphError will show you the last error returned by the Microsoft Graph API during your last cmdlet invocation:

Get-GraphResourceWithMetadata /users -Filter "startwith(userPrincipalName, 'pfunk')"

This results in an error:

foreach : Exception calling "InvokeMethod" with "2" argument(s): "Exception calling "InvokeMethod" with "2" argument(s): "The remote server returned an
error: (400) Bad Request.""
At C:\Users\adamed\src\autographps\.devmodule\scriptclass\0.20.2\src\scriptobject\dsl\MethodDsl.ps1:37 char:16
+     $objects | foreach {
+                ~~~~~~~~~
    + CategoryInfo          : NotSpecified: (:) [ForEach-Object], MethodInvocationException
    + FullyQualifiedErrorId : ScriptMethodRuntimeException,Microsoft.PowerShell.Commands.ForEachObjectCommand

The 400 is a fairly generic error -- to debug it, we'd like to see the response body from the server. Get-GraphError exposes it along with other useful information. For the error above, the message field of the ErrorResponse JSON property gives a hint at what was wrong with the request: Invalid filter clause:

Get-GraphError

RequestTimestamp        : 10/12/2021 22:03:15 PM -07:00
Status                  : 400
ErrorResponse           : {"error":{"code":"BadRequest","message":"Invalid filter clause","innerError":{"date":"2021-10-14T03:38:07","request-id":"25343cc1
                          -84b7-47ce-aec6-cc64cad83ecd","client-request-id":"956d2a74-268f-45fd-8359-febe38edcc66"}}}
Method                  : GET
Uri                     : https://graph.microsoft.com/v1.0/users?$filter=startwith(userPrincipalName, 'pfunk')
RequestBodySize         : 0
ClientElapsedTime       : 00:00:00.0962222
RequestHeaders          : {Authorization, Content-Type, client-request-id, ConsistencyLevel}
ClientRequestId         : 956d2a74-268f-45fd-8359-febe38edcc66
AppId                   : a8d7de53-99f6-4542-834b-b4185eb366fc
TenantId                :
UserUpn                 :
UserObjectId            :
AuthType                : AppOnly
ResourceUri             : users
Query                   : ?$filter=startwith(userPrincipalName,%20'pfunk')
ResponseTimestamp       : 10/12/2021 22:03:15 PM -07:00
ResponseClientRequestId : 956d2a74-268f-45fd-8359-febe38edcc66
ResponseHeaders         : {x-ms-ags-diagnostic, Transfer-Encoding, request-id, Content-Type...}
ResponseContentSize     :
ResponseRawContentSize  :

A close look at the filter clause shows that startwith is missing an s after start -- the corrected command below will succeed with a 200:

Get-GraphResourceWithMetadata /users -Filter "startswith(userPrincipalName, 'pfunk')"

   Graph Location: /v1.0_10:/users

Id                                   DisplayName Job Title  UserPrincipalName
--                                   ----------- ---------  -----------------
83dd3dbb-d7f3-44d3-a4a1-b92971ba7379 Sir Nose               devoidof@funk.org
30285b8b-70ba-42e0-9bd9-fbcee5d1ce64 PFunk 4Life Verbalizer pfunk@funk.org

You can inspect the various properties and object returned by Get-GraphError to find additional details that help you debug a failure.

Logs for exploration and troubleshooting

AutoGraphPS records a log of every request and response to Graph issued by AutoGraphPS commands. To view the contents of the log, invoke the Get-GraphLog command. In the example below, Get-GraphLog is invoked immediately following an attempt to create a new security group using New-GraphItem

$newGroup = New-GraphItem group -Property mailNickName, displayName, mailEnabled  -Value ReportViewers, 'Report Viewers', $false

Get-GraphLog

RequestTimestamp             StatusCode Method Uri
----------------             ---------- ------ ---
2/26/2021 10:08:38 PM -08:00        400 POST   https://graph.microsoft.com/v1.0/groups
2/26/2021 10:01:07 PM -08:00        204 DELETE https://graph.microsoft.com/v1.0/groups/7330583a-d3e5-494b-800c-8e4b67be8460
2/26/2021 10:01:07 PM -08:00        200 GET    https://graph.microsoft.com/v1.0/groups/7330583a-d3e5-494b-800c-8e4b67be8460
2/26/2021 10:00:57 PM -08:00        201 POST   https://graph.microsoft.com/v1.0/groups
2/26/2021 9:58:56 PM -08:00         200 GET    https://graph.microsoft.com/v1.0/organization
2/26/2021 9:55:28 PM -08:00         200 GET    https://graph.microsoft.com/v1.0/users?$search=johnson
2/26/2021 9:47:23 PM -08:00         200 GET    https://graph.microsoft.com/v1.0/groups?$search=ashford

TIP: You can pipe the output of Get-GraphLog to the Format-GraphLog command and use its View parameter to provide output optimized for scenarios such as investigating timing or authentication issues.

The log shows information about the underlying REST protocol request issued by the commands, sorted with the most recent requests first. The StatusCode column shows that the most recent request was a failed request because its value of 400 is outside the 2xx success range. There are many more diagnostic fields such as HTTP request and response headers available outside of the default tabular view for the command -- use the Get-Member command on the output of Get-GraphLog to learn more.

You can use commands like Clear-Log and Set-GraphLogOption to manage the log's data and the module's logging behaviors including retention and level of detailed logging:

  • By default, AutoGraphPS logs information such as the HTTP request URI, request and response headers, and status code, but omits the request and reponse body to protect the privacy of users and organizations using AutoGraphPS.
  • Headers known to contain secrets such as the Authorization header used to communicate the access token are always redacted -- the actual value of the header is never logged
  • To completely disable logging which is enabled by default, use the command Set-GraphLogOption -LogLevel None
  • To erase previously logged data, issue the Clear-Log command
  • Currently the log persists only in-memory, so when the PowerShell session ends or the AutoGraphPS-SDK module is unloaded, the log contents are completely erased.

The log allows for the full range of protocol debugging and can help diagnose connectivity issues or problems with intermediate gateways between AutoGraphPS and the Graph API service. When used in conjunction with standard network diagnostic tools you'll be able to narrow down just about any issue.

In addition, the logs are a great way to learn about Microsoft Graph at the REST protocol level; with each command, you can see the resulting REST requests and responses to gain an understanding of the Graph API protocol.

Get a closer look at errors

In the example above, the most recent command was

$newGroup = New-GraphItem group -Property mailNickName, displayName, mailEnabled  -Value ReportViewers, 'Report Viewers', $false

which failed with a 400 Bad Request error. We can get more information on the failure by looking in more detail at the most recent log entry, which has the same output as Get-GraphError:

Get-GraphLog -Newest 1 | Format-List *

RequestTimestamp        : 2/27/2021 1:15:23 AM -08:00
Status                  : 400
ErrorResponse           : {"error":{"code":"Request_BadRequest","message":"A value is required for property 'securityEnabled' of resource 'Group'.","innerE
                          rror":{"date":"2021-02-27T09:14:50","request-id":"2f2f9077-1c5b-46f1-a7a1-c706924b2ba3","client-request-id":"f8e1b815-cd69-4431-b
                          362-554d901c4019"}}}
Method                  : POST
Uri                     : https://graph.microsoft.com/v1.0/groups
RequestBodySize         : 110
ClientElapsedTime       : 00:00:00.2353523
RequestHeaders          : {Authorization, Content-Type, client-request-id, ConsistencyLevel}
ClientRequestId         : 87572233-f11d-41db-bfa7-dd172fd2a03f
AppId                   : ac70e3e2-a821-4d19-839c-b8af4515254b
TenantId                :
UserUpn                 :
UserObjectId            :
AuthType                : AppOnly
ResourceUri             : groups
Query                   :
ResponseTimestamp       : 2/27/2021 1:15:23 AM -08:00
ResponseClientRequestId : 87572233-f11d-41db-bfa7-dd172fd2a03f
ResponseHeaders         : {x-ms-ags-diagnostic, Transfer-Encoding, request-id, Content-Type...}
ResponseContentSize     :
ResponseRawContentSize  :

Note that the ErrorResponse field provides the detail needed to resolve the issue: A value is required for property 'securityEnabled' of resource 'Group'. The request did not include an explicit value for the securityEnabled property of the group; Graph requires this property be specified at creation time. The fix then is simple -- the original command can be modified to include the required property and re-invoked as followed:

$newGroup = New-GraphItem group -Property mailNickName, displayName, mailEnabled, securityEnabled  -Value ReportViewers, 'Report Viewers', $false $true

This modified command now succeeds.

Enabling full logging

To ensure the privacy of Graph data accessed by AutoGraphPS, by default the module does not log the body of a request or response. These elements of the protocol can contain sensitive information such as the actual contents of a document or e-mail, as well as the title or subject of such communication. This safe default behavior can be overridden when the need arises using the Set-GraphLogOption command:

$newGroup = New-GraphItem group -Property mailNickName, mailEnabled, securityEnabled -Value Group7Access,  $false, $true

# This command generates no output because the request body
# is not emitted to the log by default
Get-Graphlog -Newest 1 | Select-Object -ExpandProperty RequestBody

# Issue this command to enable full logging of request and response body
Set-GraphLogOption -LogLevel Full

# Now retry the scenario
$newGroup = New-GraphItem group -Property mailNickName, mailEnabled, securityEnabled -Value Group7Access,  $false, $true

# Now the JSON request body from the retried command is emitted:
Get-Graphlog -Newest 1 | Select-Object -ExpandProperty RequestBody

{
    "mailEnabled":  false,
    "securityEnabled":  true,
    "displayName":  "Group 7 Access",
    "mailNickname":  "Group7Access"
}

The ability to view the full request and response enhances debugging scenarios, aids users learning about the Graph API protocol, and provides an easy way to generate REST protocol sequences for use in developing non-PowerShell applications that make direct use of REST to access the Graph API.

Full logging enabled by Set-GraphLogOption will remain in effect for the duration of the PowerShell session until the Basic logging level is restored by another invocation of Set-GraphLogOption. Note that changing the log level does not affect the content of previous logs, so to remove any full request or response data in the log, use Clear-Log to erase it once it is no longer needed, or simply close the PowerShell session.

Diagnostic output via -verbose

All AutoGraphPS cmdlets support the PowerShell standard option -verbose and the associated $VerbosePreference preference variable. When using cmdlets such as Get-GraphResource and Get-GraphChildItem, specifying -verbose will output not only the http verb and uri used to access the Graph, but also the request headers and for responses the response body and headers.

By default, the response body is truncated after a certain length, though the behavior can be overridden by setting GraphVerboseOutputPreference to High.

Authorization errors

Microsoft Graph requires callers to obtain specific authorization for applications like AutoGraphPS to access particular capabilities of the Graph. Because the mapping of required permissions to functionality is currently only available through human-readable documentation, it's easy for developers and other human users of applications including AutoGraphPS to encounter errors due to insufficient permissions.

Often, users and developers remedy the error by reading the documentation, and updating the application to request the missing permissions. AutoGraphPS tries to hasten such fixes by surfacing authorization failures with a warning encouring the user to request additional permissions as in the example below where the caller tries to access me/people to get information about the people with whom she has been interacting:

PS> gls me/people
WARNING: Graph endpoint returned 'Unauthorized' accessing 'me/people'. Retry after re-authenticating via the
'Connect-GraphApi' cmdlet and requesting appropriate permissions. See this location for documentation on
permissions that may apply to this part of the Graph:
'https://developer.microsoft.com/en-us/graph/docs/concepts/permissions_reference'.
WARNING: {
  "error": {
    "code": "ErrorAccessDenied",
    "message": "Access is denied. Check credentials and try again.",
    "innerError": {
      "request-id": "bfcd8509-1a0e-4e8b-8b15-435bb413003b",
      "date": "2018-07-07T30:43:14"
    }
  }
}

The warning message in the AutoGraphPS output includes a link to the permissions documentation and suggestion to use Connect-GraphApi to grant AutoGraphPS additional permissions. Consultation of the documentation may lead the user to conclude that AutoGraphPS is missing the 'People.Read' scope, and a retry of the original attempt after using Connect-GraphApi to request People.Read will succeed:

# This will prompt the user to re-authenticate and grant People.Read
# to AutoGraphPS
Connect-GraphApi People.Read

gls me/people

Info Type   Preview       Name
---- ----   -------       ----
t +> person Cosmo Jones   X8FF834
t +> person Minerva Smith X8FF835
t +> person Rufus Chang   X8FF332

Advanced commands and concepts

The concepts and commands below are described briefly as their usage is (currently) less common. In cases where they prove to cover important scenarios, simpler cmdlets will eventually be added for those uses.

Advanced queries with -Query

The -Query parameter for several commands lets you directly specify the Uri query parameters for the Graph call made by AutoGraphPS. It must conform to OData specifications. The option is provided to allow you to overcome limitations in AutoGraphPS's simpler query options. For example the two commands below are equivalent:

gls /users -Filter "startsWith(mail, 'p')" -top 20
gls /users -Query  "`$filter=startsWith(mail, 'p')&`top=20"

Note that -Query requires you to understand how to combine multiple query options via '&' and also how to make correct use of PowerShell escape characters so that OData tokens like $top which are preceded by the $ character which is reserved as a variable prefix in PowerShell are taken literally instead of as the value of a PowerShell variable.

While -Query may be more complicated to use, when AutoGraphPS's other query options do not support a particular Graph query feature, you still have a way to use Graph's full capabilities.

For more details on how to construct this parameter, see the MS Graph REST API documentation for queries.

Invoke-GraphApiRequest -- the universal Graph cmdlet

The Invoke-GraphApiRequest cmdlet supports all of the functionality of Get-GraphResource, and exceeds it in a key aspect: where Get-GraphResource only enables APIs that support the GET http method, Invoke-GraphApiRequest supports all http methods, including PUT, POST, PATCH, and DELETE.

This means you can use Invoke-GraphApiRequest for not just read operations as with the Get-Graph*Item cmdlets, but write operations as well. In fact, any behavior supported by Microsoft Graph is accessible through this command. However such invocations are typically far more cumbersome to issue than those of the other dedicated commands which abstract many concepts and automatically generate or interpret complex JSON structure that must be specified explicitly when using Invoke-GraphApiRequest.

Still, the generic capability of Invoke-GraphApiRequest means that even if AutoGraphPS does not expose a specific command for some feature of Microsoft Graph, you can still use that feature by doing some research into the REST protocol for the capability and then invoking Invoke-GraphApiRequest according to the protocol.

Write access through Invoke-GraphApiRequest

Note: The requirements for write operations vary among the different entities exposed by the Graph -- consult the Microsoft Graph documentation for information on creating, updating, and deleting objects in the Graph.

In this example, we create a new contact. This will require we specify data in a structure that can be serialized into JSON format, and fortunately this is fairly simple as PowerShell has easy-to-use support for JSON:

$contactData = @{givenName='Cleopatra Jones';emailAddresses=@(@{name='Work';Address='cleo@soulsonic.org'})}
Invoke-GraphApiRequest me/contacts -Method POST -Body $contactData

This will return the newly created contact object (you can inspect it further by accessing $LASTGRAPHITEMS[0]).

The contactData variable was assigned to a structure that would result in the JSON required for the Graph type contact whose JSON representation can be found in the Graph documentation. The rules for creating such an object are roughly as follows:

  • For key-value pairs of a Javascript object, simply use a PowerShell HashTable that contains all the keys and values. You can use the @{} syntax for the PowerShell HashTable.
  • If there are values that are also Javascript objects (i.e. they are not strings, numbers, etc.), those themselves can be PowerShell HashTable instances with their own keys and values. Thus, you'll likely end up with one or more levels of nested HashTable instances
  • If a value is an array, express the array using PowerShell's Array type via the @() syntax. The elements of the array can be any type as well, including HashTable instances that represent Javascript objects, other arrays expressed via @() syntax, and of course simple types like numbers and strings.
  • If it isn't clear, the HashTable instances can contain arrays.

The ability of PowerShell to express mutual nesting of arrays and HashTable instances via compact @() and @{} syntax makes it fairly intuitive and readable to express any object that can be expressed as JSON.

Making it easier with New-GraphObject

Simple manual construction of JSON serializable objects is feasible with PowerShell syntax, however the process still requires human understanding of the detailed correspondence between JSON format and PowerShell data types with mistake-free rendering of the object.

Fortunately, AutoGraphPS provides the New-GraphObject command which correctly renders the object according to the API schema and removes the source of much of the human error. For example, the, following sequence of commands can be used to create a contact just as in the previous contact example:

$emailAddress = New-GraphObject -TypeClass Complex emailAddress -Property name, address -Value Work, cleo@soulsonic.org
$contactData = New-GraphObject contact -Property givenName, emailAddresses -Value 'Cleopatra Jones', @($emailAddress)
Invoke-GraphApiRequest me/contacts -Method POST -Body $contactData

While this command takes 3 lines instead of 2, its lack of @() and @{} syntax makes its intent more plain. The Property parameter allows you to specify which properties of the object you'd like to include. The optional Value property lets you specify the value of the property -- each element of the Value parameter corresponds to the desired value of the property named by an element at the same position in the list specified to Property. The command will keep you honest by throwing an error if you specify a property that does not exist on the object; this error checking is not available with the shorter 2 line version -- rather than finding out your error sooner with an explicit error message, the failure occurs when making the request to Graph, and the error message may not always be explicit about which property is set incorrectly.

Note that the Value parameter is not mandatory, and in fact does not require the same cardinality as Property -- any properties without a corresponding value are simply set to a default value.

One difficulty is that Graph defines to kinds of composite types, the Entity and Complex types of OData. To avoid the potentially incorrect asssumption that type names are unique across Entity and Complex types, you must specify the TypeClass parameter with the value Complext to override the default type class of Entity that New-GraphObject uses to build the object.

The PropertyTable alternative

The PropertyTable argument combines the approaches above, allowig you to use the HashTable @{} syntax to specify each property and value as keys and vlaues in a HashTable object using the {} syntax. Because this expresses properties and values in one pair rather than as part of two separate lists which must be carefully arranged to align the right value to the desired property, it is less error-prone. Since the HashTable may be specified with a multi-line syntax, this can be a very readable way to express the object:

$emailAddress = New-GraphObject -TypeClass Complex emailAddress -PropertyTable @{
    name = 'Work'
    address = 'cleo@soulsonic.org'
}

$contactData = New-GraphObject contact -PropertyTable @{
    givenName = 'Cleopatra Jones'
    emailAddresses = @($emailAddress)
}

Invoke-GraphApiRequest me/contacts -Method POST -Body $contactData

This approach, while certainly using more lines than the others, is even more readable and easier to express correctly than the parallel lists.

Get-GraphUriInfo -- understanding the Graph's structure

You can use Get-GraphUriInfo to get information about whether a given Uri is valid, what entity type it represents, and what Uri segments may follow it. Its functionality is based on data retrieved from the Graph endpoint's $metadata response.

Here are some examples:

# Get basic type information about the uri '/me/drive/root'
Get-GraphUriInfo /me/drive/root

Info Type      Preview Name
---- ----      ------- ----
n  > driveItem         root

# Use format-list to see all fields
Get-GraphUriInfo /me/drive/root

ParentPath   : /me/drive
Info         : n  >
Relation     : Direct
Collection   : False
Class        : NavigationProperty
Type         : driveItem
Name         : root
Namespace    : microsoft.graph
Uri          : https://graph.microsoft.com/v1.0/me/drive/root
GraphUri     : /me/drive/root
Path         : /v1.0:/me/drive/root
FullTypeName : microsoft.graph.driveItem
Version      : v1.0
Endpoint     : https://graph.microsoft.com/
IsDynamic    : False
Parent       :
Details      : @{ScriptClass=; isDynamic=False; graphUri=/me/drive/root; type=NavigationProperty; parent=;
               isVirtual=False; isInVirtualPath=False; name=root; leadsToVertex=False; graphElement=;
               decoration=; PSTypeName=GraphSegment}
Content      :
Preview      :
PSTypeName   : GraphSegmentDisplayType

# View information about the full uri
Get-GraphUriInfo /me/drive/root | select -expandproperty uri

In the above example, Get-GraphUriInfo parsed the Uri in order to generate the returned information. If you were to give a Uri that is not valid for the current graph, you'd receive an error like the one below:

Get-GraphUriInfo /me/idontexist
Uri '/me/idontexist' not found: no matching child segment 'idontexist' under segment 'me'
At C:\users\myuser\Documents\WindowsPowerShell\modules\autographps\0.14.0\src\metadata\segmentparser.ps1:140 char:21
+ ...             throw "Uri '$($Uri.tostring())' not found: no matching ch ...
+                 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : OperationStopped: (Uri '/me/idonte...er segment 'me':String) [], RuntimeExcept
   ion
    + FullyQualifiedErrorId : Uri '/me/idontexist' not found: no matching child segment 'idontexist' under s
   egment 'me'

Finding the parents and children

The Get-GraphUriInfo cmdlet also allows you to determine the set of parent (predecessor) segments of the Uri, as well as all segments immediately following the Uri, that is the children (successors):

# These are all the segments that precede /me/drive/root
Get-GraphUriInfo /me/drive/root -Parents

Info Type      Preview Name
---- ----      ------- ----
/  > root              /
s  > user              me
n  > drive             drive
n  > driveItem         root

# And these are all the children
Get-GraphUriInfo /me/drive/root -Children

Info Type             Preview Name
---- ----             ------- ----
n* > driveItem                children
a    scalar                   copy
a    scalar                   createLink
a    scalar                   createUploadSession
f    driveItem                delta
a    scalar                   invite
n  > listItem                 listItem
n* > permission               permissions
f    driveItem                search
n* > thumbnailSet             thumbnails
n* > driveItemVersion         versions
n  > workbook                 workbook

The presence of workbook in the list of children suggests that the following Uri is valid, which indeed it is:

/me/drive/root/workbook

As for what it does, the "Type" column indicates that a GET for that Uri should return an entity of type workbook. The "Info" column consists of four symbols with the following meaning

  • Column 0: This is the segment class from OData's Entity Data Model (EDM). It can be one of the following:
    • 'a' - Action: An action method invoked through a POST method that potentially changes the state of the entity
    • 'e' - EntitySet: A collection of every instance of a particular kind of entity type
    • 'f' - Function: A function method that does not alter the entity but does return a value related to it
    • 'n' - NavigationProperty: A link from one entity to one or more entities of a particular type
    • 's' - Singleton: A single instance of a particular entity type with a unique name across the entire EDM
    • 't' - EntityType instance: An instance of an entity type, i.e. actual data returned from Graph that describes users, computers, etc.
  • Column 1: The collection column -- it contains * if the item is a collection and is empty if not
  • Column 2: The data column -- it contains a + if the item is one or more entities returned from Graph, is empty otherwise
  • Column 3: The locatable column: this column has a > character if it can be followed by an entity and is empty otherwise

Tip: Use the command Get-GraphUriInfo / to see all of the segments that may legally start a Uri.

Don't forget about "virtual" segments

Get-GraphUriInfo doesn't just parse static Uris, but any that are syntactically valid, i.e.

Get-GraphResource /me/drive/root/children/myfile.txt | fl *

returns the following whether or not that Uri (and the OneDrive file this particular path represents) exists in the Graph:

ParentPath   : /me/drive/root/children
Info         : t  >
Relation     : Data
Collection   : False
Class        : EntityType
Type         : driveItem
Name         : myfile.txt
Namespace    : microsoft.graph
Uri          : https://graph.microsoft.com/v1.0/me/drive/root/children/myfile.txt
GraphUri     : /me/drive/root/children/myfile.txt
Path         : /v1.0:/me/drive/root/children/myfile.txt
FullTypeName : microsoft.graph.driveItem
Version      : v1.0
Endpoint     : https://graph.microsoft.com/
IsDynamic    : True
Parent       :
Details      : @{ScriptClass=; isDynamic=True; graphUri=/me/drive/root/children/myfile.txt; type=EntityType;
               parent=; isVirtual=False; isInVirtualPath=False; name=myfile.txt; leadsToVertex=False;
               graphElement=; decoration=; PSTypeName=GraphSegment}
Content      :
Preview      :
PSTypeName   : GraphSegmentDisplayType

The purpose of this cmdlet is to let you know what's syntactically valid, not what is actually valid.

Note that it will even "make up" hypothetical Uri's for you when -Children with the -IncludeVirtualChildren option:

Get-GraphResourceWithMetadata /me/drive/root/children -children -IncludeVirtualChildren | select uri

Uri
---
https://graph.microsoft.com/v1.0/me/drive/root/children/{driveItem}

The {driveItem} segment tells you format of a hypothetical segment that could follow children, as well as the type (driveItem), allowing tools provide developers hints about possible queries and the data they can return.

Using Get-GraphToken with other Graph tools

When using tools like Postman or Fiddler to troubleshoot or test the Graph, you'll need to acquire a token. Token acquisition continues to be one of the biggest barriers to using Graph, so use AutoGraphPS's Get-GraphToken cmdlet to automate it:

Get-GraphToken

This cmdlet will retrieve a token for the current Graph. Running the command above will write the token to the display so that it can be cut and pasted into tools like Fiddler and Postman. To avoid displaying it, assign it to a variable or better yet just pipe it to the clipboard using the clip.exe command built into Windows:

Get-GraphToken | clip

You can then simply paste it into your tool of choice without having to highlight text or click buttons.