Microsoft Graph Query Development Tool
Overview
I created this application, Microsoft Graph Query Development Tool, to allow you to connect to your Microsoft Graph API to run REST requests. This is a development tool and allows you to work with Microsoft Graph data as an array that is returned when running the tool. You can also simply run the tool and view the data, but the intended purpose is to pipe the data into another application.
Microsoft Graph basics and how they apply to this tool
Microsoft Graph is the API that sits directly below your Intune Console and is the method to interact with live Intune data (or other online Microsoft services, such as Outlook, Office 365 and Azure). When you use your Intune console, the console is actually making a REST API request/response pair with Microsoft Graph where a GET method is sent with a request URI and a MIME-encoded response object is returned in the HTTP response body back to the Intune console.
For example, if you open your Microsoft Edge Browser and connect to your Azure Portal https://portal.azure.com/ you can view the Microsoft Graph request URI that Intune makes to retrieve the data displayed in the console.
For example open Intune -> Devices -> Compliant Devices. Now, open the Developer Options by hitting F12. Click the Network Column and search for requests to https://graph.microsoft.com. On the right hand side, under Headers, you will see you exact request URI:
In this example, the request URI was to retrieve my compliant devices:
https://graph.microsoft.com/beta/deviceManagement/managedDevices?$filter=(((managementAgent eq ‘mdm’) or (managementAgent eq ‘easMdm’)))&$top=25&_=1518599459508
Deconstructing the request URI, you can begin to understand the components that will be used with my development tool to make the same request:
API Version: Beta
Resource: deviceManagement/managedDevices
Query: (((managementAgent eq ‘mdm’) or (managementAgent eq ‘easMdm’)))&$top=25&_=1518599459508
Query Type: filter
Note these values and how they were derived from the request URI, as they will be used as the inputs for the tool. This is a request URI that returns compliant devices, and running the request within the tool will return the same JSON content to you, but in a form you can work on within an application by converting it to a PowerShell object.
Getting Your Environment Ready for Microsoft Graph Query Development Tool
This application was written in PowerShell and needs to be connected to the internet in order to run. It also requires the AzureAD module for ADAL (Azure Active Directory Authentication Libraries) authentication. It will attempt to download this module automatically if you do not already have it installed. However, feel free to get your environment ready by running:
Install-Module AzureAD
Installing the Microsoft Graph Query Development Tool
Download the required files below and unzip the files into a directory of your choosing.
Once downloaded, modify the main application file, Invoke-MicrosoftGraphRequestTool.ps1, and change this $user single value at the bottom of the script to match the administrative account for either Intune, AzureAD or whatever cloud application you want to make the Microsoft Graph request URI against.
$user = youradminaccount@company.onmicrosoft.com
Running the Maiolo Microsoft Graph Query Development Tool
To run the application, run the Invoke-MicrosoftGraphRequestTool.ps1 file within PowerShell and you will immediately be prompted to log into your Azure tenant. Although this login method may seem similar to some of my other tools, the underlying authentication method is different when connecting to the Intune Data Warehouse vs running a Microsoft Graph Request, so any authentication tokens you have open with my other tools will be insufficient here without logging in again.
Once logged in, you will be presented with the application’s main menu:
Run Custom Graph Request
This is the most basic version of a request URI and does not support a query. It only asks for the API version, which is typically “Beta” or “v1.0” and the Resource, such as “deviceAppManagement/mobileApps”, which will return all of the Mobile Applications in your Intune environment.
Run Custom Graph Request (GUI Assisted)
This is the most advanced portion of the application and will allow you to input a full Microsoft Graph request URI, including an optional query. This will bring up a small GUI window where you can build your request URI. Remember the request URI we found from our Intune console on the F12 development box earlier? Let’s use that URI to build our request URI within the application so we can return the same results.
A request URI is then built and invoked with the proper GET request.
This data is then returned, where we can either pass it on to the pipeline or just simply view it:
For example, we could make a small modification to Invoke-MicrosoftGraphRequestTool.ps1 and pass the data to Out-Gridview:
Invoke-MicrosoftGraphRequestTool -user $user | Out-GridView
Now, when we re-run the application, the data is piped and viewed in Out-GrideView.
Replace Out-GridView with whatever application you would want to next see the data. That’s how the tool works!
The Invocation Function: Invoke-MicrosoftGraphRequestTool
<#
.Synopsis
Connect to Microsoft Graph to GET a Microsoft Graph request URI JSON content return. This is a development tool to return Graph API content which can be fed to the pipleline to be ingested by another application.
.Author
David Maiolo
.Help
$user variable is your InTune tenant admin account such as admin@yourcompany.onmicrosoft.com
If you are super new to all of this and dont even know what this is, create one by searching "Intune trial" and go through the process
#>
function Invoke-MicrosoftGraphRequestTool{
Param
(
[Parameter(Mandatory=$true,ValueFromPipeline=$false,Position=0)]
[ValidateScript({$_ -like '*onmicrosoft.com*'})]
[string]$user
)
#Welcome Screen
clear
Write-Host "==============================================="-ForegroundColor Cyan
Write-Host "Maiolo Microsoft Graph Query Development Tool"-ForegroundColor Cyan
Write-Host "==============================================="-ForegroundColor Cyan
Write-Host "v1.0 (2018-02-13) by dmaiolo"-ForegroundColor Cyan
Write-Host "Connect to Microsoft Graph to Run Graph GET Requests. This is a "
Write-Host "development tool to return graph data which can be fed to the "
Write-Host "pipleline to be ingested by another application."
Write-Host "========================================="-ForegroundColor Cyan
#Import Local Modules
$ModulePath = "$PSScriptRoot\MicrosoftGraphRequestToolModules.psm1"
$localmodules = @("$ModulePath")
foreach ($localmodule in $localmodules){
if (Test-Path "$ModulePath") {
Write-Host "Confirmed: Module Path $ModulePath exists."
try{
Write-Host "Importing $localmodule..."
Import-Module $localmodule -Force
}
catch{
throw "Error: Could not import $localmodule."
}
}else {
throw "Error: Module Path '$ModulePath' doesn't exist."
}
}
$modules = @("AzureAD")
#Install Azure AD modules
foreach ($module in $modules){
if (Get-Module -ListAvailable -Name $module) {
Write-Host "Confirmed: $module module was already imported."
} else {
try{
Write-Host "$module module does not exist. Installing $module..." -ForegroundColor Yellow
Install-Module $module
}catch{
throw "Could not install required module $module. Tried to run `"Install-Module $module`" but it didn't work."
}
}
}
# Checking if authToken exists before running authentication
if($global:authToken){
#Get Universal Date and Time
$DateTime = (Get-Date).ToUniversalTime()
# If the authToken exists checking when it expires
$TokenExpires = ($authToken.ExpiresOn.datetime - $DateTime).Minutes
if($TokenExpires -le 0){
write-host "Warning: Authentication Token expired" $TokenExpires "minutes ago." -ForegroundColor Yellow
$global:authToken = Get-AuthToken -User $User
}else{
Write-Host "User $user has already been authenticated. (against Microsoft Graph)"
}
}else{
$global:authToken = Get-AuthToken -User $User
}
sleep 5
#Menu
$Collections = Get-MicrosoftGraphMainCategories
$results = Get-IntuneMainMenu -Collections $Collections -CollectionName "Maiolo Microsoft Graph Query Development Tool"
Write-Host "===================================="
Write-Host "Summary of Graph Query Findings"
Write-Host "===================================="
Write-Host "Number of values returned:"($results.count)
$i=0
#Write-Host "Summary of First 5 Findings..."
#$results | select DisplayName,id -First 5| FT -AutoSize
#Write-Host "Returning full results..."
#sleep 5
return $results
}
$user = "user@company.onmicrosoft.com"
Invoke-MicrosoftGraphRequestTool -user $user
The Methods: MicrosoftGraphRequestToolModules.psm1
#Functions Specific TO Microsoft Graph
function Get-AuthToken {
<#
.SYNOPSIS
This function is used to authenticate with the Graph API REST interface
.DESCRIPTION
The function authenticate with the Graph API Interface with the tenant name
.EXAMPLE
Get-AuthToken
Authenticates you with the Graph API interface
.NOTES
NAME: Get-AuthToken
#>
[cmdletbinding()]
param
(
[Parameter(Mandatory=$true)]
$User
)
$userUpn = New-Object "System.Net.Mail.MailAddress" -ArgumentList $User
$tenant = $userUpn.Host
Write-Host "Checking for AzureAD module..."
$AadModule = Get-Module -Name "AzureAD" -ListAvailable
if ($AadModule -eq $null) {
Write-Host "AzureAD PowerShell module not found, looking for AzureADPreview"
$AadModule = Get-Module -Name "AzureADPreview" -ListAvailable
}
if ($AadModule -eq $null) {
write-host
write-host "AzureAD Powershell module not installed..." -f Red
write-host "Install by running 'Install-Module AzureAD' or 'Install-Module AzureADPreview' from an elevated PowerShell prompt" -f Yellow
write-host "Script can't continue..." -f Red
write-host
exit
}
# Getting path to ActiveDirectory Assemblies
# If the module count is greater than 1 find the latest version
if($AadModule.count -gt 1){
$Latest_Version = ($AadModule | select version | Sort-Object)[-1]
$aadModule = $AadModule | ? { $_.version -eq $Latest_Version.version }
# Checking if there are multiple versions of the same module found
if($AadModule.count -gt 1){
$aadModule = $AadModule | select -Unique
}
$adal = Join-Path $AadModule.ModuleBase "Microsoft.IdentityModel.Clients.ActiveDirectory.dll"
$adalforms = Join-Path $AadModule.ModuleBase "Microsoft.IdentityModel.Clients.ActiveDirectory.Platform.dll"
}
else {
$adal = Join-Path $AadModule.ModuleBase "Microsoft.IdentityModel.Clients.ActiveDirectory.dll"
$adalforms = Join-Path $AadModule.ModuleBase "Microsoft.IdentityModel.Clients.ActiveDirectory.Platform.dll"
}
[System.Reflection.Assembly]::LoadFrom($adal) | Out-Null
[System.Reflection.Assembly]::LoadFrom($adalforms) | Out-Null
$clientId = "d1ddf0e4-d672-4dae-b554-9d5bdfd93547"
$redirectUri = "urn:ietf:wg:oauth:2.0:oob"
$resourceAppIdURI = "https://graph.microsoft.com"
$authority = "https://login.microsoftonline.com/$Tenant"
try {
$authContext = New-Object "Microsoft.IdentityModel.Clients.ActiveDirectory.AuthenticationContext" -ArgumentList $authority
# https://msdn.microsoft.com/en-us/library/azure/microsoft.identitymodel.clients.activedirectory.promptbehavior.aspx
# Change the prompt behaviour to force credentials each time: Auto, Always, Never, RefreshSession
$platformParameters = New-Object "Microsoft.IdentityModel.Clients.ActiveDirectory.PlatformParameters" -ArgumentList "Auto"
$userId = New-Object "Microsoft.IdentityModel.Clients.ActiveDirectory.UserIdentifier" -ArgumentList ($User, "OptionalDisplayableId")
$authResult = $authContext.AcquireTokenAsync($resourceAppIdURI,$clientId,$redirectUri,$platformParameters,$userId).Result
# If the accesstoken is valid then create the authentication header
if($authResult.AccessToken){
# Creating header for Authorization token
$authHeader = @{
'Content-Type'='application/json'
'Authorization'="Bearer " + $authResult.AccessToken
'ExpiresOn'=$authResult.ExpiresOn
}
return $authHeader
}
else {
Write-Host
Write-Host "Authorization Access Token is null, please re-run authentication..." -ForegroundColor Red
Write-Host
break
}
}
catch {
write-host $_.Exception.Message -f Red
write-host $_.Exception.ItemName -f Red
write-host
break
}
}
function Get-MicrosoftGraphMainCategories{
<#
.SYNOPSIS
This function is used to provide the Main applicaiton menu what to display and Data warehouse collections to call when invoked.
.Author
David Maiolo
.NOTES
NAME: Get-MicrosoftGraphMainCategories
#>
$GraphCategories = @()
$GraphCategories += [PSCustomObject] @{'name' = 'Get-MicrosoftGraphRESTRequest'; 'DisplayName' = 'Run Custom Graph Request'};
$GraphCategories += [PSCustomObject] @{'name' = 'Show-Command Get-MicrosoftGraphRESTRequest'; 'DisplayName' = 'Run Custom Graph Request (GUI Assisted)'};
$GraphCategories += [PSCustomObject] @{'name' = 'Get-MicrosoftGraphRESTRequest -graphApiVersion "Beta" -Resource "deviceAppManagement/mobileApps"'; 'DisplayName' = 'Run Sample Graph Request: Mobile Apps'};
return $GraphCategories
}
function Get-MicrosoftGraphRESTRequest{
<#
.SYNOPSIS
This function is used to call your Microsoft Graph request of choice and return the results as an array.
.Author
David Maiolo
.NOTES
NAME: Get-MicrosoftGraphRESTRequest
#>
param(
[Parameter(Mandatory=$true,ValueFromPipeline=$false,Position=0)]
[ValidateSet ("Beta","v1.0")]
[string]$graphApiVersion,
[Parameter(Mandatory=$true,ValueFromPipeline=$false,Position=1)]
[String]$Resource,
[Parameter(Mandatory=$false,ValueFromPipeline=$false,Position=2)]
[ValidateSet ("count","expand","filter","format","orderby","search","select","skip","skipToken","top")]
[String]$QueryType,
[Parameter(Mandatory=$false,ValueFromPipeline=$false,Position=3)]
[String]$Query
)
try{
if(([bool]($MyInvocation.BoundParameters.Keys -contains 'Query')) -and ([bool]($MyInvocation.BoundParameters.Keys -contains 'QueryType'))){
$uri = "https://graph.microsoft.com/$graphApiVersion/$($resource)?`$$QueryType=$Query"
}else{
$uri = "https://graph.microsoft.com/$graphApiVersion/$($resource)"
}
Write-Host "Requesting $uri..."
$GraphRequestValue = (Invoke-RestMethod -Uri $uri -Headers $authToken -Method Get).Value | ? { ($_.'@odata.type').Contains("managed") }
return $GraphRequestValue
}catch{
$ex = $_.Exception
$errorResponse = $ex.Response.GetResponseStream()
$reader = New-Object System.IO.StreamReader($errorResponse)
$reader.BaseStream.Position = 0
$reader.DiscardBufferedData()
$responseBody = $reader.ReadToEnd();
Write-Host "Response content:`n$responseBody" -ForegroundColor Red
Write-Error "Request to $Uri failed with HTTP Status $($ex.Response.StatusCode) $($ex.Response.StatusDescription)"
write-host
break
}
}
#Universal Functions
function Convert-VariableNameToFriendlyName{
<#
.SYNOPSIS
This function is used to convert a variable name, such as helloMyNameIsDavidMaiolo to a friendly variable name, such as Hello My Name Is David Maiolo.
.Author
David Maiolo
.NOTES
NAME: Convert-VariableNameToFriendlyName
#>
param(
[Parameter(Mandatory=$true,ValueFromPipeline=$false,Position=0)]
[String]$VariableName
)
$FriendlyVariableName = ($VariableName -split '_')[0] -creplace '(?<=\w)([A-Z])', ' $1'
$TextInfo = (Get-Culture).TextInfo
$FriendlyVariableName = $TextInfo.ToTitleCase($FriendlyVariableName)
return $FriendlyVariableName
}
function Get-IntuneMainMenu {
<#
.SYNOPSIS
This function is used to provide a main menu for the application. This was written generically and can be used for other applications.
.Author
David Maiolo
.NOTES
NAME: Get-IntuneMainMenu
#>
Param
(
[Parameter(Mandatory=$true,ValueFromPipeline=$false,Position=0)]
$Collections,
[Parameter(Mandatory=$true,ValueFromPipeline=$false,Position=1)]
[String]$CollectionName,
[Parameter(Mandatory=$false,ValueFromPipeline=$false,Position=2)]
[Switch]$UseSubMenu
)
#if (!($global:intuneWarehouseAuthResult) -or !($global:authToken)) {
# throw "No authentication context. Authenticate first!"
#}
try {
do {
clear
Write-Host "========================================="
Write-Host $CollectionName
Write-Host "========================================="
#$menu = @{}
$MenuArray = @()
for ($i=1;$i -le $collections.count; $i++) {
Write-Host "[$i] $($collections[$i-1].DisplayName)"
if($i -eq $Collections.count){
$iplusone = ($i+1)
Write-Host "[$iplusone] Exit"
}
}
[int]$ans = Read-Host 'Enter Selection'
if ($ans -eq ($collections.count+1)){
break
}else{
$collection = Invoke-Expression $collections[$ans-1].Name
#Write-Host Colection: $collection
if(([bool]($MyInvocation.BoundParameters.Keys -contains 'UseSubMenu'))){
Get-IntuneSubMenu -Collections $collection -CollectionName $collections[$ans-1].DisplayName
}else{
return $collection
}
}
}while($ans -ne ($collections.count+1))
}catch{
$ex = $_.Exception
$errorResponse = $ex.Response.GetResponseStream()
$reader = New-Object System.IO.StreamReader($errorResponse)
$reader.BaseStream.Position = 0
$reader.DiscardBufferedData()
$responseBody = $reader.ReadToEnd();
Write-Host "Response content:`n$responseBody" -ForegroundColor Red
write-host
sleep 10
break
}
}
function Get-IntuneSubMenu {
<#
.SYNOPSIS
This function is used to provide a sub menu for the application. This was written generically and can be used for other applications.
.Author
David Maiolo
.NOTES
NAME: Get-IntuneSubMenu
#>
Param
(
[Parameter(Mandatory=$true,ValueFromPipeline=$false,Position=0)]
$Collections,
[Parameter(Mandatory=$true,ValueFromPipeline=$false,Position=1)]
[String]$CollectionName
)
#$collections = Get-IntuneDatawarehouseCollections
if (!$global:intuneWarehouseAuthResult) {
throw "No authentication context. Authenticate first by running 'Connect-IntuneDataWarehouse'"
}
try {
do{
clear
Write-Host ">>>>>$CollectionName " -ForegroundColor Cyan
Write-Host ">>>>>=========================================" -ForegroundColor Cyan
$menu = @{}
for ($i=1;$i -le $collections.count; $i++) {
Write-Host " [$i] $($collections[$i-1].DisplayName)"
$menu.Add($i,($collections[$i-1].name))
if($i -eq $Collections.count){
$iplusone = ($i+1)
Write-Host " [$iplusone] Go Back"
$menu.Add($i+1,("GoBack"))
}
}
[int]$ans = Read-Host 'Enter Selection'
$selection = $menu.Item($ans);
if ($selection -eq "GoBack"){
#Menu
#$Collections = Get-IntuneDatawarehouseMenuCategories
#Get-IntuneMainMenu -Collections $Collections -CollectionName "InTune Management"
return
}else{
Get-IntuneDataWarehouseCollection $selection | Out-GridView -Title (Convert-VariableNameToFriendlyName -VariableName $selection)
#Read-Host -Prompt "Press Enter to continue"
}
}while($selection -ne "GoBack")
}catch{
$ex = $_.Exception
$errorResponse = $ex.Response.GetResponseStream()
$reader = New-Object System.IO.StreamReader($errorResponse)
$reader.BaseStream.Position = 0
$reader.DiscardBufferedData()
$responseBody = $reader.ReadToEnd();
Write-Host "Response content:`n$responseBody" -ForegroundColor Red
Write-Error "Request to $Uri failed with HTTP Status $($ex.Response.StatusCode) $($ex.Response.StatusDescription)"
write-host
sleep 10
break
}
}
Leave a Reply
Want to join the discussion?Feel free to contribute!