Unlike postman, getting slow powershell script performance when used to interact with Microsoft Graph API - powershell

We are trying to use a PS script to start using Graph API:
$clientId = "XXXXXXXXXXXXXXXXXXXXXXXXXXX"
$clientSecret = "XXXXXXXXXXXXXXXXXXXXXXXXXXXX"
$TenantName = "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"
$resource = "https://graph.microsoft.com/"
$URL = "https://graph.microsoft.com/v1.0/groups"
$tokenBody = #{
Grant_Type = "client_credentials"
Scope = "https://graph.microsoft.com/.default"
Client_Id = $clientId
Client_Secret = $clientSecret
}
$tokenResponse = Invoke-RestMethod -Uri "https://login.microsoftonline.com/$TenantName/oauth2/v2.0/token" -Method POST -Body $tokenBody
$result = Invoke-RestMethod -Headers #{Authorization = "Bearer $($tokenResponse.access_token)"} -Uri $URL -Method Get -Verbose
($result | select-object Value).Value | Select-Object id
I am not sure if it is the script or something else but the success rate for this script is 20%. Most of the time the return is either delayed or timed out.
We are getting consistent results when we use postman to interact with the graph API. How come powershell is giving us a hard time? Is there something that we need to change on our script?
Thanks

Related

How to retrieve a specific SharePoint online site using graph api and PowerShell

Am currently creating a PowerShell script to retrieve a specific SharePoint online site using Microsoft graph api. The goal is, once i retrieve the site, then i can grab the siteid. My script fails on the api call. Have tried different search and filter combination, but it's not working. All of my api calls just retrieves all the sites. I got all required permissions assigned to the app registration.
Below is the entire script.
$siteUrl = "https://bernardcomms.sharepoint.com/sites/Project1"
$tenantId = ""
$clientId = ""
$clientSecret = ""
# Get an access token for the Microsoft Graph API
$tokenAuthUri = "https://login.microsoftonline.com/$tenantId/oauth2/v2.0/token"
$tokenRequestBody = #{
grant_type = "client_credentials"
client_id = $clientId
client_secret = $clientSecret
scope = "https://graph.microsoft.com/.default"
}
$tokenResponse = Invoke-RestMethod -Method Post -Uri $tokenAuthUri -Body $tokenRequestBody
$accessToken = $tokenResponse.access_token
# Get the ID of the SharePoint site
$requesturi = "https://graph.microsoft.com/v1.0/sites?$search=$siteUrl"
$siteIdResponse = Invoke-RestMethod -Method Get -Uri $requesturi -Headers #{Authorization=("bearer {0}" -f $accessToken)}
$siteId = $siteIdResponse.value.id
$siteId
Have tried to use below calls but it just returns all the sites.
$requesturi = "https://graph.microsoft.com/v1.0/sites?$search=weburl eq '$siteUrl'"
$requesturi = "https://graph.microsoft.com/v1.0/sites?$filter=weburl eq '$siteUrl'"
How can I retrieve a site that has a matching specific siteurl.
permissions assigned.
I agree with #Cpt.Whale, you need to change your graph query to https://graph.microsoft.com/v1.0/sites/bernardcomms.sharepoint.com:/sites/Project1
I tried to reproduce the same in my environment and got below results:
I registered one Azure AD application and granted same API permissions like below:
I have one SharePoint site named sritestsite like below:
When I ran below PowerShell script by making few changes, I got siteID successfully like below:
$hostName = "mytenant.sharepoint.com"
$siteName = "sitename"
$tenantId = <tenantID>
$clientId = <appID>
$clientSecret = <secret>
# Get an access token for the Microsoft Graph API
$tokenAuthUri = "https://login.microsoftonline.com/$tenantId/oauth2/v2.0/token"
$tokenRequestBody = #{
grant_type = "client_credentials"
client_id = $clientId
client_secret = $clientSecret
scope = "https://graph.microsoft.com/.default"
}
$tokenResponse = Invoke-RestMethod -Method Post -Uri $tokenAuthUri -Body $tokenRequestBody
$accessToken = $tokenResponse.access_token
# Get the ID of the SharePoint site
$requesturi = "https://graph.microsoft.com/v1.0/sites/$hostName`:/sites/$siteName"
$siteIdResponse = Invoke-RestMethod -Method Get -Uri $requesturi -Headers #{Authorization=("bearer {0}" -f $accessToken)}
$siteId = $siteIdResponse.id
$siteId
Response:
To get full response, you can run $siteIdResponse like below:

Permissions to access to MS Graph API via PowerShell

Question
I am trying to write a PowerShell script to get report data via the MS Graph API /reports/credentialUserRegistrationDetails.
When I use Graph Explorer it works just fine, as long as I enable Reports.Read.All on the Modify permissions (Preview) tab.
But, when I try to do it with my script, I just get the error "Calling principal does not have required MSGraph permissions Reports.Read.All"
In all my searches, I can only find how to assign permissions to apps.
Is there some way to make it so I can do it from my script?
My Script
$azContext = Get-AzContext
$token = [Microsoft.Azure.Commands.Common.Authentication.AzureSession]::Instance.AuthenticationFactory.Authenticate(
$azContext.Account,
$azContext.Environment,
$azContext.Tenant.Id,
$null,
[Microsoft.Azure.Commands.Common.Authentication.ShowDialog]::Never,
$null,
"https://graph.microsoft.com"
)
$params = #{
Method = "GET"
Uri = "https://graph.microsoft.com/beta/reports/credentialUserRegistrationDetails"
Headers = #{
Authorization = "Bearer $($token.AccessToken)"
"Content-Type" = "application/json"
}
}
Invoke-RestMethod #params
Response
{
"error": {
"code":"Authentication_MSGraphPermissionMissing",
"message":"Calling principal does not have required MSGraph permissions Reports.Read.All",
"innerError": {
"date":"2021-10-19T01:18:36",
"request-id":"xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
"client-request-id":"6b8cc3a3-b93b-44bb-b1d4-190c618aa52a"
}
}
}
When I use Graph Explorer it works just fine, as long as I enable
Reports.Read.All on the Modify permissions (Preview) tab.
Its because Microsoft Graph Explorer is a Enterprise Application of Microsoft which is present on every Azure AD tenant just you need to sign in and use it by providing the required permissions.
But when you are writing running your Powershell script it uses Microsoft Azure Powershell . You can verify it by checking the access_token received in JWT Token.So, you need to provide the Reports.Read.All API Permission to the same app in your tenant with appid : 1950a258-227b-4e31-a9cf-717495945fc2 in Enterprise Application >> Permissions and grant the admin consent .After providing the required permissions only it will work.
Another Way will be to create a App registration ,Create a client secret for it and then provide the Reports.Read.All API permission and use the below script:
$TenantName = "tenantname.onmicrosoft.com"
$clientID = "d344e3xxx-xxx-xxxx-xxxx-9c861d363244" # app registration clientId
$clientSecret = "fNc7Q~UNHBgv_xxxxxxxxxxxxxxxxxxxxxx-PD"
$Scope = "https://graph.microsoft.com/.default"
$Body = #{
Grant_Type = "client_credentials"
Scope = $Scope
client_Id = $clientID
Client_Secret = $clientSecret
}
$authUri = "https://login.microsoftonline.com/$TenantName/oauth2/v2.0/token"
$TokenResponse = Invoke-RestMethod -Uri $authUri -Method POST -Body $Body
$Headers = #{
"Authorization" = "Bearer $($TokenResponse.access_token)"
"Content-type" = "application/json"
}
$apiUri = "https://graph.microsoft.com/beta/reports/credentialUserRegistrationDetails"
$response = Invoke-RestMethod -Headers $Headers -Uri $apiUri -Method GET
$response.value
Output:
Note: In Some Tenants Microsoft Azure PowerShell might not be visible from portal , so in that case please use the above solution it will be easier.
For Authorization code flow, try something like this -
#region Auth1
#With User Interaction for Delegated Permission
Add-Type -AssemblyName System.Web
Function Get-AuthCode {
Add-Type -AssemblyName System.Windows.Forms
$form = New-Object -TypeName System.Windows.Forms.Form -Property #{Width = 440; Height = 640 }
$web = New-Object -TypeName System.Windows.Forms.WebBrowser -Property #{Width = 420; Height = 600; Url = ($url -f ($Scope -join "%20")) }
$DocComp = {
$Global:uri = $web.Url.AbsoluteUri
if ($Global:uri -match "error=[^&]*|code=[^&]*") { $form.Close() }
}
$web.ScriptErrorsSuppressed = $true
$web.Add_DocumentCompleted($DocComp)
$form.Controls.Add($web)
$form.Add_Shown( { $form.Activate() })
$form.ShowDialog() | Out-Null
$queryOutput = [System.Web.HttpUtility]::ParseQueryString($web.Url.Query)
$output = #{}
foreach ($key in $queryOutput.Keys) {
$output["$key"] = $queryOutput[$key]
}
#$output
}
Get-AuthCode
#Extract Access token from the returned URI
$regex = '(?<=code=)(.*)(?=&)'
$authCode = ($uri | Select-string -pattern $regex).Matches[0].Value
Write-output "Received an authCode, $authCode"
$tokenBody = #{
Grant_Type = "authorization_code"
Scope = "https://graph.microsoft.com/.default"
Client_Id = $clientId
Client_Secret = $clientSecret
redirect_uri = $redirectUri
code = $authCode
ressource = $resource
}
$tokenResponse = Invoke-RestMethod https://login.microsoftonline.com/common/oauth2/token -Method Post -ContentType "application/x-www-form-urlencoded" -Body $tokenBody -ErrorAction STOP
#endregion Auth1
For delegated permissions use something like below -
$tokenBody = #{
Grant_Type = "password"
Scope = "user.read%20openid%20profile%20offline_access"
Client_Id = $clientId
username = $User
password = $pw
resource = $resource
}
$tokenResponse = Invoke-RestMethod https://login.microsoftonline.com/common/oauth2/token -Method Post -ContentType "application/x-www-form-urlencoded" -Body $tokenBody -ErrorAction STOP
#endregion Auth2
For Application permissions (using client credential flow) use something like this
$tokenBody = #{
Grant_Type = "client_credentials"
Scope = "https://graph.microsoft.com/.default"
Client_Id = $clientId
Client_Secret = $clientSecret
}
$tokenResponse = Invoke-RestMethod -Uri "https://login.microsoftonline.com/$Tenantid/oauth2/v2.0/token" -Method POST -Body $tokenBody
#endregion Auth3
Despite what Method you have chosen, the tokenRepsonse Variable is holding our Key to Query against the Microsoft GRAPH API.
We want a list of all Teams in our Tenant, so this require propriate Application Permission. So for example- our Powershell to get a Full List of all Teams look like this -
$headers = #{
"Authorization" = "Bearer $($tokenResponse.access_token)"
"Content-type" = "application/json"
}
$URL = "https://graph.microsoft.com/beta/groups?`$filter=resourceProvisioningOptions/Any(x:x eq 'Team')"
$AllTeams = (Invoke-RestMethod -Headers $headers -Uri $URL -Method GET).value
Thanks.
I finally gave up on using the REST APIs and started using Microsoft.Graph PowerShell Modules. I find the documentation pretty sparse, but at least it works for what I need. :)
Import-Module "Microsoft.Graph.Identity.Signins"
Import-Module "Microsoft.Graph.Users"
Import-Module "Microsoft.Graph.Groups"
Connect-MgGraph -TenantId $TenantId -Scopes "Directory.Read.All", "UserAuthenticationMethod.Read.All" -ForceRefresh
Select-MgProfile -Name "beta"
$report = Get-MgReportCredentialUserRegistrationDetail

Powershell Invoke-RestMethod Paging

I am trying to get a list of all users from our Azure B2C tenant.
With some help from the internet I was able to create the powershell script below. But the result is incomplete it only shows 100 users. After searching around I found I should probably do something with Paging but I can't get it to work.
Can someone help me to modify the script below to return all users?
# Application (client) ID, tenant Name and secret
$clientId = "**********"
$tenantName = "*********"
$clientSecret = "************"
$resource = "https://graph.microsoft.com/"
$ReqTokenBody = #{
Grant_Type = "client_credentials"
Scope = "https://graph.microsoft.com/.default"
client_Id = $clientID
Client_Secret = $clientSecret
}
$TokenResponse = Invoke-RestMethod -Uri "https://login.microsoftonline.com/$TenantName/oauth2/v2.0/token" -Method POST -Body $ReqTokenBody
$Url = "https://graph.microsoft.com/beta/users?$select=displayName"
$Data = Invoke-RestMethod -Headers #{Authorization = "Bearer $($Tokenresponse.access_token)"} -Uri $Url -Method Get
$Users = ($Data |select-object Value).Value
$Users | Format-Table DisplayName -AutoSize
Ok i got it to work in Powershell Core (Version 7.1.3).
This is the code I ended up using.
# Application (client) ID, tenant Name and secret
$clientId = "**************"
$tenantName = "***************"
$clientSecret = "******************"
$resource = "https://graph.microsoft.com/"
$ReqTokenBody = #{
Grant_Type = "client_credentials"
Scope = "https://graph.microsoft.com/.default"
client_Id = $clientID
Client_Secret = $clientSecret
}
$TokenResponse = Invoke-RestMethod -Uri "https://login.microsoftonline.com/$TenantName/oauth2/v2.0/token" -Method POST -Body $ReqTokenBody
$Url = "https://graph.microsoft.com/beta/users?$select=displayName"
$UserResponse = Invoke-RestMethod -Headers #{Authorization = "Bearer $($Tokenresponse.access_token)"} -Uri $Url -Method Get -Verbose
$CloudUser = $UserResponse.Value
$UserNextLink = $UserResponse."#odata.nextLink"
while ($UserNextLink -ne $null) {
$UserResponse = (Invoke-RestMethod -Headers #{Authorization = "Bearer $($Tokenresponse.access_token)"} -Uri $UserNextLink -Method Get -Verbose)
$UserNextLink = $UserResponse."#odata.nextLink"
$CloudUser += $UserResponse.value
}
$CloudUser | Format-Table DisplayName -AutoSize
Most of the last 8 lines (or so) are repeated. You can factor it out like this:
$Url = "https://graph.microsoft.com/beta/users?$select=displayName"
$headers = #{Authorization = "Bearer $($Tokenresponse.access_token)"}
$CloudUser = #()
do {
$UserResponse = Invoke-RestMethod -Headers $headers -Uri $Url -Method Get -Verbose
$CloudUser += $UserResponse.Value
$Url = $UserResponse."#odata.nextLink"
} while ($Url)
Also, why not use the Graph PowerShell module for this? https://learn.microsoft.com/en-us/powershell/microsoftgraph

OneDrive FB create Folder with Microsoft Graph API Powershell

I just want to create a folder one my OneDrive for Buiness Account through the Graph API. After many hours I got stuck with an error which I really can't comprehend. It says that the property 'Attributes' does not exist on type 'oneDrive.folder' and I should not use this property. The problem is that I don't use this property. After alot of research I think it has something to do with the pinned metadata or something like that. But in gerneral I really dont know what to do futher.
I used the Graph Explorer and another website to create this script.
The Error:
-1, Microsoft.SharePoint.Client.InvalidClientQueryException
The property 'Attributes' does not exist on type 'oneDrive.folder'.
Make sure to only use property names that are defined by the type.
And this is my code:
$clientId = "XXXXXXXXXXXXXX"
$tenantId = "XXXXXX.onmicrosoft.com"
$clientSecret = 'XXXXXXXXXXXX'
$uri = "https://login.microsoftonline.com/$tenantId/oauth2/v2.0/token"
$body = #{
client_id = $clientId
scope = "https://graph.microsoft.com/.default"
client_secret = $clientSecret
grant_type = "client_credentials"
}
$tokenRequest = Invoke-WebRequest -Method Post -Uri $uri -ContentType "application/x-www-form-urlencoded" -Body $body -UseBasicParsing
$token = ($tokenRequest.Content | ConvertFrom-Json).access_token
Write-Host $token
$uri = "https://graph.microsoft.com/v1.0/<ID XXXXXXX>/drive/root/children"
$method = 'POST'
$head= #{Authorization = "Bearer $token"}
$postPara= #{
name= "NewFolder"
folder= {}
} | ConvertTo-Json
$antwort = Invoke-RestMethod -Headers $head -Uri $uri -Method $method -Body $postPara -ContentType "application/json"
Write-Host $antwort
It really should work and I'm sitting on this sample task for over 10 Hours ._.
The issue with you code is
$postPara= #{
name= "NewFolder"
folder = {}
} | ConvertTo-Json
If you just output $postPara you will see the issue is because your missing the # in front of value in folder you will actually get the details from the underlying script populated in there. so try
$postPara= #{
name= "NewFolder"
folder = #{}
} | ConvertTo-Json
$postPara
Which should fix it.A good diag tool is also to use fiddler to look at the request being sent to server.

The remote server returned an error : (401) Unauthorized - PowerShell - Microsoft Graph API

I am trying to get the list of all groups where the resourceProvisioningOptions = Team, here is the url which gets all the data through API call:
$clientID = xxxx
$tenantName = xxxx
$ClientSecret = xxxx
$resource = "https://graph.microsoft.com/"
$ReqTokenBody = #{
Grant_Type = "client_credentials"
Scope = "https://graph.microsoft.com/.default"
client_Id = $clientID
Client_Secret = $clientSecret
}
$authheader = #{
'Authorization' = "Bearer $($Tokenresponse.access_token)"
'Content-Type'='application\json'
}
$TokenResponse = Invoke-RestMethod -Uri "https://login.microsoftonline.com/$TenantName/oauth2/v2.0/token" -Method POST -Body $ReqTokenBody
$test = "'Team'"
$apiUrl = 'https://graph.microsoft.com/beta/groups?$filter=resourceProvisioningOptions/Any(x:x eq {0})' -f $test
$Data = Invoke-RestMethod -Uri $apiUrl -Headers $authheader -Body $ReqTokenBody -Method Get
$Groups = ($Data | select-object Value).Value | Select-Object displayName, id, description, mail | Out-File .\texxtfile.txt
However, I am getting a 401 error when I try to run the script even though I have all the permissions required to make the API call.
You may have picked application permissions in your AAD application. There is an additioanl step. You will need to admin consent your application in the app registration portal to use Group.Read.All to run this. If you have not done this in the UI this will fail.