Accessing Microsoft Graph With PowerShell - powershell

I've been having a hell of a time trying to access the Microsoft Graph using PowerShell.
First I tried using the authorization flow and Invoke-WebRequest and Invoke-RestMethod neither of which I could get to work.
Then I found this blog that showed how to do it using PowerShell and a couple of the Azure modules. Below is the code I'm using (ripped right from that blog) but every time I get to the Invoke-RestMethod (in the do-while loop) instead of getting the result of the query I get a 403 Forbidden error:
Invoke-RestMethod : The remote server returned an error: (403) Forbidden.
Function GetAuthToken {
Param (
[Parameter()]
$TenantName
)
Import-Module Azure
$clientId = "1950a258-227b-4e31-a9cf-717495945fc2"
$resourceAppIdURI = "https://graph.microsoft.com"
$authority = "https://login.microsoftonline.com/$TenantName"
$authContext = New-Object "Microsoft.IdentityModel.Clients.ActiveDirectory.AuthenticationContext" -ArgumentList $authority
$Credential = Get-Credential
$AADCredentialUser = New-Object "Microsoft.IdentityModel.Clients.ActiveDirectory.UserCredential" -ArgumentList $credential.UserName, $credential.Password
$authResult = $authContext.AcquireToken($resourceAppIdURI, $clientId, $AADCredentialUser)
Write-Output $authResult
}
Function GetAllObjectOfType {
param
(
[Parameter(Mandatory = $true)]
$Tenant,
[Parameter(Mandatory = $true)]
$Type,
[Parameter(Mandatory = $false)]
$BatchSize = 100,
[Parameter(Mandatory = $false)]
$Version = 'Beta'
)
#------Get the authorization token------#
$token = GetAuthToken -TenantName $tenant
#------Building Rest Api header with authorization token------#
$authHeader = #{
'Content-Type' = 'application/json'
'Authorization' = $token.CreateAuthorizationHeader()
}
#------Initial URI Construction------#
#$uritest = "https://graph.microsoft.com/v1.0/users/user#contoso.com/mailFolders/Inbox/childFolders"
$uritest = "https://graph.microsoft.com/v1.0/me/mailFolders/Inbox"
#Join-Path -Path ''
$ObjCapture = #()
do {
$users = Invoke-RestMethod -Uri $uritest -Headers $authHeader -Method Get
$FoundUsers = ($Users.value).count
write-host "URI" $uri " | Found:" $FoundUsers
#------Batched URI Construction------#
$uri = $users.'#odata.nextlink'
$ObjCapture = $ObjCapture + $users.value
}until ($uri -eq $null)
$ObjCapture
}
I can run this same query (/v1.0/me/mailFolders/Inbox) from the Graph Explorer and it runs perfectly fine with no errors.
The GetAuthToken seems to be working as I do get a token back with an expiry, refresh token, etc and the $token.CreateAuthorizationHeader() also returns the correct Authorization = Bearer token
I've never done anything with the Microsoft Graph before so I'm sure there is something I'm doing wrong but I cannot for the life of me figure out what.

You cannot reuse the clientid from that blog post. You need to obtain your own clientid by registering your application. See Register your app with the Azure AD v2.0 endpoint for details on how to register your app.

This seems like it was answered in a follow-up blog post from the same source.
When an App is created, by default it has rights to access only the data of
the user that had signed in with the account though the “Sign in and read user
profile” delegated permissions. If you try to execute a script that uses this
AppID/ClientID to query Azure AD to get a list of all users in the directory,
you would receive an error stating (403) Forbidden because it didn’t have
adequate permissions to do that activity.

Putting this here in case any other wandering devs come across the post like I did. Here's the powershell function I'm using to get the access token from our tenant. ClientId and Secret are created in the Azure App Registrations, like others have mentioned.
$clientId = $args[0]
$secret = $args[1]
$redeemURI = "https://login.microsoftonline.com/{tenantGuid}/oauth2/v2.0/token"
$body = "client_id=$clientId&scope=https%3A%2F%2Fgraph.microsoft.com%2F.default&client_secret=$secret&grant_type=client_credentials"
$response = Invoke-RestMethod -Method Post -Uri $redeemURI -Body $body -ContentType "application/x-www-form-urlencoded"
return $response.access_token
Usage:
$token = ./GetAccessToken.ps1 "{clientId}" "{secret}"

I think it needs AD Azure Premium 2 licences
enter link description here

Related

Using powershell to process emails in an o365 mailbox when connecting with application id and secret

I am writing a PowerShell script which reads through emails in an o365 email box.
It has to connect to the inbox, read through emails and make a decision based on the subject line, and then open those emails with a specific subject line and download any attachments the email might contain to a folder.
It [then] has to move the processed mail message to a mailbox sub-folder.
I have done this in the past using Exchange web services, and basic authentication to connect, but basic authentication is no longer available, and so I am having to re-do the script using modern authentication techniques.
I have been given an application id and secret from my Azure AD admin's with permissions for the relevant mailbox.
I've been googling away, and have managed to get some way using Microsoft Graph - using this section of Powershell:
$ClientID = "my-client-id"
$DirectoryID = "my-directory-id"
$ClientSecret = "my-client-secret"
$Credential = ConvertTo-GraphCredential -ClientID $ClientID -ClientSecret $ClientSecret -DirectoryID $DirectoryID
$mailbox = Get-EXOMailbox -UserPrincipalName myemailaccount#mycompany.com
This successfully gets me a mailbox object, but from here I am at a loss as to how to go about retrieving emails from it for processing.
The Microsoft doco and Google isn't helping me much at present on how extract emails from the mailbox having obtained the object.
Any thoughts/suggestions or pointers to relevant tutorials?
I managed to figure out how to access emails just usiing application id and secret without
using any non-standard libraries. You'll need to install the Graph and powershell utilities,
but they're free from Microsoft, so I call them standard. Then import them into my powershell
script thus:
Import-Module Microsoft.Graph.Mail
Import-Module Microsoft.PowerShell.Utility
After that, use these methods to access your o365 email system using the microsoft graph
REST API.
# This AuthenticateWithSecret is a slightly modified version of the method
# described by https://adamtheautomator.com/powershell-graph-api/
function Get-AccessToken
{
param(
[string] $AppId,
[string] $TenantName,
[string] $AppSecret)
$Scope = "https://graph.microsoft.com/.default"
$Url = "https://login.microsoftonline.com/$TenantName/oauth2/v2.0/token"
# Add System.Web for urlencode
Add-Type -AssemblyName System.Web
# Create body
$Body = #{
client_id = $AppId
client_secret = $AppSecret
scope = $Scope
grant_type = 'client_credentials'
}
# Splat the parameters for Invoke-Restmethod for cleaner code
$PostSplat = #{
ContentType = 'application/x-www-form-urlencoded'
Method = 'POST'
# Create string by joining bodylist with '&'
Body = $Body
Uri = $Url
}
# Request the token!
$Request = Invoke-RestMethod #PostSplat
return $Request
}
# This method builds a header object that can be passed
# in with each request to the Graph REST API, for authentication
# purposes
function Get-Header
{
param($theRequest)
$tokenType = $theRequest.token_type
$accessToken = $theRequest.access_token
# Create header
$theHeader = #{
Authorization = "$($tokenType) $($accessToken)"
}
return $theHeader
}
# This method gets an object containing the email folders from
# a mailbox specified by its UserPrincipalName - which is typically
# the email address of the user concerned. By default it will return
# the first 200 folders it finds, but you can specify however many
# that you wish to return using the $numberOfFoldersToGet parameter.
function Get-Folders
{
param(
$Credential,
[string] $UserPrincipalName,
[int]$numberOfFoldersToGet=200)
$Header = Get-Header -theRequest $Credential
$restUrl = "https://graph.microsoft.com/v1.0/users/$UserPrincipalName/mailFolders/?`$top=$numberOfFoldersToGet"
$folderResult = Invoke-RestMethod -Uri $restUrl -Headers $Header -Method Get -ContentType "application/json"
return $folderResult
}
# This is a little helper function to get the specific folder object
# from an array of folder objects. You specify the folder that you
# want in the array based o its displayName using the $folderNameToFind
# parameter.
function Get-FolderFromListOfFolders
{
param($listOfFolders,
$folderNameToFind)
$specificFolder = ""
# Yeah, yeah, I know - we're doing this the brute-force way - just
# looping through all the folders until we find the one we want.
# Unless you have an insane number of folders, this shouldn't take
# *that* long, but this loop could be re-written to use a nicer search
# if it's really that big a problem.
foreach($fdr in $allFolders)
{
$thisFolderName = $fdr.displayName
if($thisFolderName -eq $folderNameToFind)
{
$specificFolder = $fdr
break
}
}
return $specificFolder
}
# This function allows you to retrieve an object describing a specific
# mail folder in an o365 Outlook, which you can specify by name. It allows
# you to access any folder by name - not just the common ones.
function Get-SpecificFolder
{
param(
$Credential,
[string] $UserPrincipalName,
[string] $folderName)
$allTheFolders = Get-Folders -Credential $Credential -UserPrincipalName $UserPrincipalName
$allFolders = $allTheFolders.value
$specificFolder = Get-FolderFromListOfFolders -listOfFolders $allFolders -folderNameToFind $folderName
$folderId = $specificFolder.id
$Header = Get-Header -theRequest $Credential
$theRestQuery = "https://graph.microsoft.com/v1.0/users/$UserPrincipalName/mailFolders/$folderId"
$folderResult = Invoke-RestMethod -Uri $theRestQuery -Headers $Header -Method Get -ContentType "application/json"
return $folderResult
}
# This function returns an object containing all the emails in a given
# Mail folder in Outlook in o365
function GetEmails
{
param(
$Credential,
[string] $UserPrincipalName,
[string] $folderId)
$Header = Get-Header -theRequest $Credential
$restUrl = "https://graph.microsoft.com/v1.0/users/$UserPrincipalName/mailFolders/$folderId/messages"
$emailResult = Invoke-RestMethod -Uri $restUrl -Headers $Header -Method Get -ContentType "application/json"
return $emailResult
}
You can use these methods in this way. First, you need to specify these variables
$ClientID = "My-azure-ad-client-id"
$DirectoryID = "My-directory-id"
$ClientSecret = "My-client-secret"
$MailboxName = "the-email-address-of-the-o365-mailbox-i-want-to-access"
$MailboxFolderName = "the-folder-name-of-the-mailfolder-containing-emails"
Then, you can get a credential object thus:
$Credential = Get-AccessToken -AppId $ClientID -TenantName $DirectoryID -AppSecret $ClientSecret
Then get your email folder object thus
$myfolder = Get-SpecificFolder -Credential $Credential -UserPrincipalName $MailboxName -folderName $MailboxFolderName
Then, get the id of your folder - this allows you to access any folder - even non-standard ones by name.
$folderId = $myfolder.id
Now, get the email objects from the folder
$emails = GetEmails -Credential $Credential -UserPrincipalName $MailboxName -folderId $folderId
Then get the actual array of emails
$theEmails = $emails.value
Now loop through your array of emails and do stuff with it.
foreach($email in $theEmails)
{
Write-Output $email.subject
}

Error when trying to use Power BI REST API - Invoke-RestMethod : A positional parameter cannot be found that accepts argument

Lately I've been trying to use Power BI REST API to make the refresh of a certain dataset automatically, by calling a .ps1 program. By following this tutorial, I was able to get this code, which is addapted as you can see below:
$groupID = "me" # the ID of the group that hosts the dataset. Use "me" if this is your My Workspace
$datasetID = "MYDATASETID" # the ID of the dataset that hosts the dataset
$clientId = "MYCLIENTID"
# Calls the Active Directory Authentication Library (ADAL) to authenticate against AAD
function GetAuthToken
{
if(-not (Get-Module AzureRm.Profile)) {
Import-Module AzureRm.Profile
}
$redirectUri = "urn:ietf:wg:oauth:2.0:oob"
$resourceAppIdURI = "https://analysis.windows.net/powerbi/api"
$authority = "https://login.microsoftonline.com/common/oauth2/authorize";
$authContext = New-Object "Microsoft.IdentityModel.Clients.ActiveDirectory.AuthenticationContext" -ArgumentList $authority
$authResult = $authContext.AcquireToken($resourceAppIdURI, $clientId, $redirectUri, "Auto")
return $authResult
}
$token = GetAuthToken
$authHeader = #{
'Content-Type'='application/json'
'Authorization'=$token.CreateAuthorizationHeader()
}
$groupsPath = ""
if ($groupID -eq "me") {
$groupsPath = "myorg"
} else {
$groupsPath = "myorg/groups/$groupID"
}
$uri = "https://api.powerbi.com/v1.0/$groupsPath/datasets/$datasetID/refreshes"
Invoke-RestMethod -Uri $uri –Headers $authHeader –Method POST –Verbose
$uri = "https://api.powerbi.com/v1.0/$groupsPath/datasets/$datasetID/refreshes"
Invoke-RestMethod -Uri $uri –Headers $authHeader –Method GET –Verbose
I made sure to collect the parameters (groupID, clientID and datasetID) exactly as specified in the links above. However, when I try to execute this code, I get back the error:
Invoke-RestMethod : A positional parameter cannot be found that accepts argument 'â€Headers System.Collections.Hashtable â€Method'.
At C:\Users\me\Desktop:41 char:1
I can't quite tell what's going on, and I even found some similar cases, but none of the solutions worked for me. So, some help would be deeply appreciated.
It looks like this solution is copy/pasted from somewhere and the dashes are screwed up:
Delete the last 3 dashes in Invoke-RestMethod, which looks like dashes, but are other looks like dash unicode symbols, and replace them with normal "typed by the keyboard" ones.
Hope this helps!

MS Graph API access client tenant with partner credentials

I use the following PowerShell code to access MS Graph API.
Import-Module MSOnline
$User = "UserName"
$Password = "Password"
$TenantName = "tenantname.onmicrosoft.com"
$clientId = "clientId"
$authority = "https://login.microsoftonline.com/$TenantName"
$authContext = New-Object "Microsoft.IdentityModel.Clients.ActiveDirectory.AuthenticationContext" -ArgumentList $authority
$AADCredential = New-Object "Microsoft.IdentityModel.Clients.ActiveDirectory.UserCredential" -ArgumentList $AdminUser, $Password
$resourceAppIdURI = "https://graph.microsoft.com"
$authResult = $authContext.AcquireToken($resourceAppIdURI, $clientId,$AADCredential)
$authHeader = #{
'Content-Type'='application\json'
'Authorization'=$authResult.CreateAuthorizationHeader()
}
$uri = "https://graph.microsoft.com/beta/$TenantName/reports/getEmailActivityUserDetail(period='D7')"
Invoke-RestMethod -Uri $uri -Method Get -Headers $authHeader
It works fine when the user and tenant agree. What I would like to do is access our client tenants using an admin with delegated permissions. I've set the app to have Pre-consent permissions, which according to https://developer.microsoft.com/en-us/graph/docs/concepts/auth_cloudsolutionprovider should also allow the behaviour I am after. However, when I run the code with a partner admin credentials and client tenantname, I receive a 400 bad request error. What step am I missing here?
Per the documentation (see the important note at the beginning of the topic), for CSP pre-consent, only Directory and Intune resources in Microsoft Graph supports the partner model. Over time, more resources will support the partner/CSP model. Please create a request for this on UserVoice if this is important to you.
Hope this helps,

Powershell - Do "Grant Permissions" action on Azure AD Application with Powershell

I'm creating an Azure AD application using AzureAD module to call Microsoft Graph API. I can successfully generate the access token. But, when I try to call the API I have an error "message": "Invalid scope claims/roles.".
When I click on "Grant Permissions" button in my created application in Azure Portal and retry the call to API, the call is working.
I don't find anywhere how to do this "Grant Permissions" actions with Powershell. Is there a way to do that ?
Thanks
Damien
There is an easy way to do this (as admin), it requires you have the AzureAD and AzureRM modules installed for Powershell and is not supported by Microsoft.
Original post / reference to my blog is here: http://www.lieben.nu/liebensraum/2018/04/how-to-grant-oauth2-permissions-to-an-azure-ad-application-using-powershell-unattended-silently/
The specific code sample that should help you accomplish this:
Function Grant-OAuth2PermissionsToApp{
Param(
[Parameter(Mandatory=$true)]$Username, #global administrator username
[Parameter(Mandatory=$true)]$Password, #global administrator password
[Parameter(Mandatory=$true)]$azureAppId #application ID of the azure application you wish to admin-consent to
)
$secpasswd = ConvertTo-SecureString $Password -AsPlainText -Force
$mycreds = New-Object System.Management.Automation.PSCredential ($Username, $secpasswd)
$res = login-azurermaccount -Credential $mycreds
$context = Get-AzureRmContext
$tenantId = $context.Tenant.Id
$refreshToken = #($context.TokenCache.ReadItems() | where {$_.tenantId -eq $tenantId -and $_.ExpiresOn -gt (Get-Date)})[0].RefreshToken
$body = "grant_type=refresh_token&refresh_token=$($refreshToken)&resource=74658136-14ec-4630-ad9b-26e160ff0fc6"
$apiToken = Invoke-RestMethod "https://login.windows.net/$tenantId/oauth2/token" -Method POST -Body $body -ContentType 'application/x-www-form-urlencoded'
$header = #{
'Authorization' = 'Bearer ' + $apiToken.access_token
'X-Requested-With'= 'XMLHttpRequest'
'x-ms-client-request-id'= [guid]::NewGuid()
'x-ms-correlation-id' = [guid]::NewGuid()}
$url = "https://main.iam.ad.ext.azure.com/api/RegisteredApplications/$azureAppId/Consent?onBehalfOfAll=true"
Invoke-RestMethod -Uri $url -Headers $header -Method POST -ErrorAction Stop
}
I came across the same error 'Refresh token is malformed'. When reading out the refreshtoken the token was twice in the string. Resolved it by adding the line
$refreshtoken = $refreshtoken.Split("`n")[0]
If I am not wrong, then it is using "Admin Consent". In that case, you should be using &prompt=admin_consent in the auth request directly.
If your application requests an app-only permission and a user tries to sign in to the application, an error message is displayed saying the user isn’t able to consent.
Whether or not a permission requires admin consent is determined by the developer that published the resource, and can be found in the documentation for the resource.
Link: Multi-tenant App pattern
List of Available permissions for the Azure AD Graph API and Microsoft Graph API are
Graph API Permission Scopes
Consent Framework
Hope it helps.
This answer builds on top of Jos's answer.
Active Directory Authentication library doesn't make the refresh tokens publicly available anymore. More information can be found in github/azure-powershell/7525.
The modified snippet below worked for me
Connect-AzAccount
$context = Get-AzContext
$tenantId = $context.Tenant.TenantId
Connect-AzureAD -TenantId $tenantId -AccountId $context.Account.Id
$appId = 'Your Application ID'
$token = [Microsoft.Azure.Commands.Common.Authentication.AzureSession]::Instance.AuthenticationFactory.Authenticate($context.Account, $context.Environment, $tenantId, $null, "Never", $null, "74658136-14ec-4630-ad9b-26e160ff0fc6")
$headers = #{
'Authorization' = 'Bearer ' + $token.AccessToken
'X-Requested-With'= 'XMLHttpRequest'
'x-ms-client-request-id'= [guid]::NewGuid()
'x-ms-correlation-id' = [guid]::NewGuid()}
$url = "https://main.iam.ad.ext.azure.com/api/RegisteredApplications/$appId/Consent?onBehalfOfAll=true"
Invoke-RestMethod -Uri $url -Headers $headers -Method POST -ErrorAction Stop

Azure CDN - Custom Domain SSL via Resource Management API

Using the latest Azure Powershell SDK, but still can't seem to create Custom SSL Domains for CDNs in Azure via API Management. We have 100s of subdomains to create and need to be able to script the creation of this task for future extensibility.
Does anyone know how to toggle this flag via the REST API since the SDK has no support? We are using the New-AzureRmCdnCustomDomain commandlet.
Update: The AzureRM 6.13.0-module and the new Az-modules (including Az.Cdn) now supports this using a cmdlet. See Enable-AzureCdnCustomDomain (AzureRM.Cdn) or Enable-AzCdnCustomDomain (Az.Cdn)
The REST API for enabling Custom Domain HTTPS is documented at learn.microsoft.com
Enable Custom Https
Enable https delivery of the custom domain.
POST /subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/Microsoft.Cdn/profiles/{profileName}/endpoints/{endpointName}/customDomains/{customDomainName}/enableCustomHttps?api-version=2017-10-12
Before you can use the Azure REST API you need to get an access token:
Generating access token using PowerShell:
$Token = Invoke-RestMethod -Uri https://login.microsoftonline.com/<TenantID>/oauth2/token?api-version=1.0 -Method Post -Body #{
"grant_type" = "client_credentials"
"resource" = "https://management.core.windows.net/"
"client_id" = "<application id>"
"client_secret" = "<password you selected for authentication>"
}
The response contains an access token, information about how long that
token is valid, and information about what resource you can use that
token for. The access token you received in the previous HTTP call
must be passed in for all request to the Resource Manager API. You
pass it as a header value named "Authorization" with the value "Bearer
YOUR_ACCESS_TOKEN". Notice the space between "Bearer" and your access
token.
Client ID is retrived by creating an app registration in Azure AD and the clientkey is generated in the Keys-section of the created app registration. This can be combined into a solution like this:
$subscriptionId = "..."
$resourceGroupName = "..."
$profileName = "..."
$endpointName = "..."
$customDomainName = ".."
$Token = Invoke-RestMethod -Uri https://login.microsoftonline.com/<TenantID>/oauth2/token?api-version=1.0 -Method Post -Body #{
"grant_type" = "client_credentials"
"resource" = "https://management.core.windows.net/"
"client_id" = "<application id>"
"client_secret" = "<password you selected for authentication>"
}
$header = #{
"Authorization"= "Bearer $($Token.access_token)"
}
Invoke-RestMethod -Method Post -Headers $header -Uri "https://management.azure.com/subscriptions/$subscriptionId/resourceGroups/$resourceGroupName/providers/Microsoft.Cdn/profiles/$profileName/endpoints/$endpointName/customDomains/$customDomainName/enableCustomHttps?api-version=2016-10-02"
If you don't need to automate the script, you can login manually using GUI (no need for app-registration) using this modified sample (based on Source). It requires AzureRM-module, which can be installed using Install-Module AzureRM:
Function Login-AzureRESTApi {
Import-Module AzureRM.Profile
# Load ADAL Azure AD Authentication Library Assemblies
$modulepath = Split-Path (Get-Module -Name AzureRM.Profile).Path
$adal = "$modulepath\Microsoft.IdentityModel.Clients.ActiveDirectory.dll"
$adalforms = "$modulepath\Microsoft.IdentityModel.Clients.ActiveDirectory.WindowsForms.dll"
$null = [System.Reflection.Assembly]::LoadFrom($adal)
$null = [System.Reflection.Assembly]::LoadFrom($adalforms)
# Login to Azure
$Env = Login-AzureRmAccount
# Select Subscription
$Subscription = (Get-AzureRmSubscription | Out-GridView -Title "Choose a subscription ..." -PassThru)
$adTenant = $Subscription.TenantId
$global:SubscriptionID = $Subscription.SubscriptionId
# Client ID for Azure PowerShell
$clientId = "1950a258-227b-4e31-a9cf-717495945fc2"
# Set redirect URI for Azure PowerShell
$redirectUri = "urn:ietf:wg:oauth:2.0:oob"
# Set Resource URI to Azure Service Management API | #marckean
$resourceAppIdURIASM = "https://management.core.windows.net/"
$resourceAppIdURIARM = "https://management.azure.com/"
# Set Authority to Azure AD Tenant
$authority = "https://login.windows.net/$adTenant"
# Create Authentication Context tied to Azure AD Tenant
$authContext = New-Object "Microsoft.IdentityModel.Clients.ActiveDirectory.AuthenticationContext" -ArgumentList $authority
# Acquire token
$global:authResultASM = $authContext.AcquireToken($resourceAppIdURIASM, $clientId, $redirectUri, "Auto")
$global:authResultARM = $authContext.AcquireToken($resourceAppIdURIARM, $clientId, $redirectUri, "Auto")
}
$resourceGroupName = "..."
$profileName = "..."
$endpointName = "..."
$customDomainName = ".."
Login-AzureRESTApi
#Reuse selected subscription from login
$Subscription = $global:subscriptionId
$header = #{
"Authorization"= $global:authResultARM.CreateAuthorizationHeader()
}
Invoke-RestMethod -Method Post -Headers $header -Uri "https://management.azure.com/subscriptions/$subscriptionId/resourceGroups/$resourceGroupName/providers/Microsoft.Cdn/profiles/$profileName/endpoints/$endpointName/customDomains/$customDomainName/enableCustomHttps?api-version=2017-10-12"