Directory_ExpiredPageToken when looping through response without delay - powershell

I am accessing Microsoft Graph using PowerShell which is working like a charm, except paging.
From time to time I get Directory_ExpiredPageToken errors. The errors are mostly gone if I set a 1 sec delay between the requests.
$ReturnObject = #()
while ($Uri.Length -gt 0) {
Add-TraceLine -Type DEBUG -Text "Calling '$($Uri)'"
$Response = Invoke-RestMethod -Method GET -Uri $Uri -Headers $Headers
$ReturnObject += $Response
if ($Response.'#odata.nextlink') {
$Uri = $Response.'#odata.nextlink'
#Delay needed, otherwise Directory_ExpiredPageToken errors occured
Start-Sleep -Milliseconds 1000
}
else {
$Uri = $null
}
}
Any idea whats going on here?
The delay is a workaround to get the workflow running which is using this module. But I want to get rid of it because of performance reason.
EDIT:
This is a JSON response simplified (value is not only one object, but an array) and changed a little bit because of privacy/security reasons:
{
"#odata.context": "https://graph.microsoft.com/v1.0/$metadata#users(department,displayName,ext2j5y6eqq_linkeduser,givenName,id,jobTitle,mail,mailNickname,surname,userPrincipalName)",
"#odata.nextLink": "https://graph.microsoft.com/v1.0/users?$filter=userType+eq+%27Guest%27\u0026$select=department%2cdisplayName%2cext2j5y6eqq_linkeduser%2cgivenName%2cid%2cjobTitle%2cmail%2cmailNickname%2csurname%2cuserPrincipalName\u0026$skiptoken=RFNwdAkAAQAAAAAAAAAAFAAAADzX_JYCsThFmyry6_Ndz8kBAAAAAAAAAAAAAAAAAAAXMS4yLjg0MC4xMTM1NTYuMS40LjIzMzECAAAAAAABJruiMN_CtUmk63sXDRMMdg",
"value": [
{
"id": "80f7fa19-2669-470d-86ac-ee4c052a5186",
"department": null,
"displayName": "XC Admin",
"givenName": "XC",
"jobTitle": null,
"mail": null,
"mailNickname": "xcadmin",
"surname": "Admin",
"userPrincipalName": "xcadmin#domain.onmicrosoft.com"
}
]
}
EDIT:
This is the error response
+ ERROR { throw $Line }
+ ~~~~~~~~~~~
+ CategoryInfo : OperationStopped: (11.04.2018-06:5...
}
}
}:String) [], RuntimeException
+ FullyQualifiedErrorId : 11.04.2018-06:50:13: <ERROR> - Get-User: {
"error": {
"code": "Directory_ExpiredPageToken",
"message": "The specified page token value has expired and can no longer be included in your request.",
"innerError": {
"request-id": "13ecca81-2d8c-4d0f-a58e-4a9cdd72e100",
"date": "2018-04-11T04:50:15"
}
}
}

Related

update/create TFS Server variable group using REST API with powershell gives object reference not set

Allright i have been fiddling with this for a few hours now and i can't seem to find any resources online or solve the issue.
Looking at the api documentation at https://learn.microsoft.com/en-us/rest/api/azure/devops/distributedtask/variablegroups/get?view=azure-devops-rest-6.0
I can get info regarding the variable group that i created as a test:
$invRestMethParams = #{
Uri = "https://xxx.xxx.nl/tfs/DefaultCollection/$($project)/_apis/distributedtask/variablegroups/242/?api-version=6.0-preview.2"
Method = 'GET'
ContentType = 'application/json'
}
Invoke-RestMethod #invRestMethParams -UseDefaultCredentials
I then convert the result to json to get the required values for the PUT or POST to update or create:
{
"variables": {
"check": {
"value": "value"
}
},
"id": 242,
"type": "Vsts",
"name": "TestUpdate",
"description": "",
"isShared": false,
"variableGroupProjectReferences": [
{
"projectReference": "#{id=0add8cd8-0fc8-4aca-8eb4-164cd8d09c9d; name=AutoUpdateVariableGroups}",
"name": "TestUpdate",
"description": ""
}
]
}
The documentation states the update url is:
PUT https://{instance}/{collection}/_apis/distributedtask/variablegroups/{groupId}?api-version=6.0-preview.2
With request body having the above json values (without the ID as this is in the URI). So my update json becomes:
$body = #"
{
"variables": {
"checkupdated": {
"value": "valueupdated"
}
},
"type": "Vsts",
"name": "TestUpdate",
"description": "",
"isShared": false,
"variableGroupProjectReferences": [
{
"projectReference": "#{id=0add8cd8-0fc8-4aca-8eb4-164cd8d09c9d; name=AutoUpdateVariableGroups}",
"name": "TestUpdate",
"description": ""
}
]
}
"#
i convert it from json to see if its valid (no errors).
and use it in my PUT request:
$invRestMethParams = #{
Uri = "https://xxx.xxx.nl/tfs/DefaultCollection/_apis/distributedtask/variablegroups/242/?&api-version=6.0-preview.2"
Method = 'PUT'
ContentType = 'application/json'
Body = $body
}
Invoke-RestMethod #invRestMethParams -UseDefaultCredentials
The error i receive is:
Invoke-RestMethod : {"$id":"1","innerException":null,"message":"Object reference not set to an instance of an object.","typeName":"System.NullReferenceException, mscorlib","typeKey":"NullReferenceException","errorCode":0,"eventId":0}
At line:32 char:1
+ Invoke-RestMethod #invRestMethParams -UseDefaultCredentials
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : InvalidOperation: (System.Net.HttpWebRequest:HttpWebRequest) [Invoke-RestMethod], WebException
+ FullyQualifiedErrorId : WebCmdletWebResponseException,Microsoft.PowerShell.Commands.InvokeRestMethodCommand
Not sure how to troubleshoot this, does anyone know how to fix this issue? seems i am forgetting a value but im kinda lost as to what is the issue here.
From your script, it seems that it has issue with your request body.
In projectReference field, you need to use , to replace the ; and you need to modify the format.
"projectReference": {"id":"projectID", "name":"projectname"},
Refer to my sample:
$JSON = #"
{
"variables": {
"checkupdate": {
"value": "vaslueupdate"
}
},
"type": "Vsts",
"name": "AA",
"description": "",
"isShared": false,
"variableGroupProjectReferences": [
{
"projectReference": {"id":"108e4d7c-0bf3-4940-80da-4793f83def92", "name":"kevin1012"},
"name": "AA",
"description": ""
}
]
}
"#

Graph send mail with attachment

I'm trying to send a e-mail with attachment with powershell, as soon as I add in the attachment, I get a 400 Bad Request error, while it works find without attachment. Hope someone has a clue...
Part added:
"attachments": [
{
"#odata.type": "#microsoft.graph.fileAttachment",
"name": "attachment.txt",
"contentType": "text/plain",
"contentBytes": "SGVsbG8gV29ybGQh"
}
]
Full request:
$Subject = "Subject2"
$Message = "Message2"
$Recipient = "testemailaddress"
$SaveToSentItems = $false
$Request=#"
{
"Message": {
"Subject": $(Escape-StringToJson $Subject),
"Body": {
"ContentType": "HTML",
"Content": $(Escape-StringToJson $Message)
},
"ToRecipients": [
{
"EmailAddress": {
"Address": "$Recipient"
}
}
],
"attachments": [
{
"#odata.type": "#microsoft.graph.fileAttachment",
"name": "attachment.txt",
"contentType": "text/plain",
"contentBytes": "SGVsbG8gV29ybGQh"
}
]
},
"SaveToSentItems": "$(if($SaveToSentItems){"true"}else{"false"})"
}
"#
# Convert to UTF-8 bytes
$Request_bytes = [system.Text.Encoding]::UTF8.getBytes($Request)
$headers = #{
"Authorization" = "Bearer $($attributes.EXO)"
"Accept" = "text/*, multipart/mixed, application/xml, application/json; odata.metadata=none"
"Content-Type" = "application/json; charset=utf-8"
"X-AnchorMailbox" = (Read-AADIntAccesstoken $attributes.EXO).upn
"Prefer" = 'exchange.behavior="ActivityAccess"'
}
$url="https://outlook.office.com/api/v2.0/me/sendmail"
Invoke-RestMethod -UseBasicParsing -Uri $Url -Method Post -Headers $headers -Body $Request_bytes
Error:
Invoke-RestMethod : The remote server returned an error: (400) Bad Request.
At line:47 char:1
Invoke-RestMethod -UseBasicParsing -Uri $Url -Method Post -Headers $h ...
+ CategoryInfo : InvalidOperation: (System.Net.HttpWebRequest:HttpWebRequest) [Invoke-RestMethod], WebException
+ FullyQualifiedErrorId : WebCmdletWebResponseException,Microsoft.PowerShell.Commands.InvokeRestMethodCommand
I use this for send attachs in mails with ms graph in powershell
$attach = "C:\file.pdf"
$fileName = (Get-Item -Path $attach).Name
$base64string = [Convert]::ToBase64String([IO.File]::ReadAllBytes($attach))
$message = '{
"message": {
"subject": "subject",
"body": {
"contentType": "HTML",
"content": "'+$body+'"
},
"importance": "high",
"toRecipients": [
{
"emailAddress": {
"address": "'+$mail+'"
}
}
],
"attachments": [
{
"#odata.type": "#microsoft.graph.fileAttachment",
"name": "'+$fileName+'",
"contentType": "text/plain",
"contentBytes": "'+$base64string+'"
}
],
},
"saveToSentItems": "true"
}'
$URL = "https://graph.microsoft.com/v1.0/users/xxxxxxxxxxxxxxxxxxxx/sendMail"
Invoke-RestMethod -Method POST -Uri $URL -Headers $headers -Body $message
Convert to Base64 string before insert in the request.
I guess you have your own "body"!
Regards.

Azure-Devops remove user from projectContributor via Rest API

Using PowerShell I am attempting to remove a user from groupType projectContributor so I can move him to the Project Team. I can accomplish the add to the Project Team however I have tried everything I can to remove this users entitlement using a PATCH without success. FYI to avoid comments, OrgUrl, projectId and userId are being passed.
$b= #"
[
{
"op": "remove",
"path": "/projectEntitlements",
"value": {
"projectRef": {
"id": "$projectID"
},
"group": {
"groupType": "projectContributor"
}
}
}
]
"#
$uri = "$orgURL/_apis/userentitlements/$userId`?api-version=5.1-preview.2"
Invoke-RestMethod -Uri $uri -ContentType "application/json-patch+json" -Body $b -Method PATCH -Headers #{ Authorization = ("Basic {0}" -f $base64AuthInfo)}
The error I am getting is this:
projectId","typeName":"System.ArgumentException, mscorlib","typeKey":"ArgumentException","errorCode":0,"eventId":0}
At line:20 char:1
+ Invoke-RestMethod -Uri $uri -ContentType "application/json-patch+json ...
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : InvalidOperation: (System.Net.HttpWebRequest:HttpWebRequest) [Invoke-RestMethod], WebException
+ FullyQualifiedErrorId : WebCmdletWebResponseException,Microsoft.PowerShell.Commands.InvokeRestMethodCommand
Any help / examples are appreciated.
I have gone through all the API documentation for Azure DevOps API.
I got the same error using User Entitlement rest api.
It worked for me with Remove Member From Group rest api.
DELETE https://vsaex.dev.azure.com/{organization}/_apis/GroupEntitlements/{groupId}/members/{memberId}?api-version=5.1-preview.1
When a user is added to a project as Project Contributors. This user will be added to group [ProjectName]\\Contributors of this Project.
You can then use Group List rest api to get the group id of [ProjectName]\\Contributors. The {memberId} of above Remove Member From Group api is user's userId. Then you can just call above api to remove the user from the project contributors group.
Please try this:
$b= #"
[
{
"op": "remove",
"path": "/projectEntitlements/$projectID",
"value": {
"projectRef": {
"id": "$projectID"
},
"group": {
"groupType": "projectContributor"
}
}
}
]
"#
And since you are removing value under this path I'm not sure if you need value, so this should provide you the same:
$b= #"
[
{
"op": "remove",
"path": "/projectEntitlements/$projectID",
"value": ""
}
]
"#

Format for json via powershell

I'm trying to use powershell to send json to DevOps API. I can't seem to figure out how to properly format this so powershell will take it. I keep getting this error. Any advice? I'm able to use the same json in Postman without any issues. Thanks
$URI= "https://vsaex.dev.azure.com/$ClientOrg/_apis/userentitlements?api-version=5.1-preview.2" $AzureDevOpsAuthenicationHeader = #{Authorization = 'Basic ' + [Convert]::ToBase64String([Text.Encoding]::ASCII.GetBytes(":$($PAT)")) }
Invoke-RestMethod -uri $URI -Method POST -Headers $AzureDevOpsAuthenicationHeader -Body $a -ContentType "application/json"
$a= ConvertFrom-JSON #'
{
"accessLevel": {
"licensingSource": "msdn",
"accountLicenseType": "enterprise",
"msdnLicenseType": "enterprise"
},
"extensions": [
{
"id": "ms.feed"
}
],
"user": {
"principalName": "email#mail.com",
"subjectKind": "user"
},
"projectEntitlements": [
{
"group": {
"groupType": "projectAdministrator"
},
"projectRef": {
"id": "0685a10e"
}
}
]
}
'#
Invoke-RestMethod : {"$id":"1","innerException":null,"message":"Value cannot be null.\r\nParameter name: userEntitlement","typeName":"System.ArgumentNullException,
mscorlib","typeKey":"ArgumentNullException","errorCode":0,"eventId":0}
At line:4 char:1
+ Invoke-RestMethod -uri $URI -Method POST -Headers $AzureDevOpsAutheni ...
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : InvalidOperation: (System.Net.HttpWebRequest:HttpWebRequest) [Invoke-RestMethod], WebException
+ FullyQualifiedErrorId : WebCmdletWebResponseException,Microsoft.PowerShell.Commands.InvokeRestMethodCommand
Your body should be the JSON string itself and not an object built from the JSON.
$a= #'
{
"accessLevel": {
"licensingSource": "msdn",
"accountLicenseType": "enterprise",
"msdnLicenseType": "enterprise"
},
"extensions": [
{
"id": "ms.feed"
}
],
"user": {
"principalName": "email#mail.com",
"subjectKind": "user"
},
"projectEntitlements": [
{
"group": {
"groupType": "projectAdministrator"
},
"projectRef": {
"id": "0685a10e"
}
}
]
}
'#
Invoke-RestMethod -uri $URI -Method POST -Headers $AzureDevOpsAuthenicationHeader -Body $a -ContentType "application/json"

ForEach Invoke-RestMethod

I'm trying to make a request to the AccessPointsDetails endpoint of the Cisco Prime API and then loop through that to get all URL objects for the AccessPoints so I can get data on each of them.
I get the following output:
VERBOSE: Making request to: #{#type=AccessPointDetails; #url=https://cpist/webacs/api/v3/data/AccessPointDetails/3373245; $=3373245}
VERBOSE: Making request to: #{#type=AccessPointDetails; #url=https://cpist/webacs/api/v3/data/AccessPointDetails/3413720; $=3413720}
VERBOSE: Making request to: #{#type=AccessPointDetails; #url=https://cpist/webacs/api/v3/data/AccessPointDetails/3432295; $=3432295}
VERBOSE: Making request to: #{#type=AccessPointDetails; #url=https://cpist/webacs/api/v3/data/AccessPointDetails/3432310; $=3432310}
VERBOSE: Making request to: #{#type=AccessPointDetails; #url=https://cpist/webacs/api/v3/data/AccessPointDetails/3462672; $=3462672}
VERBOSE: Making request to: #{#type=AccessPointDetails; #url=https://cpist/webacs/api/v3/data/AccessPointDetails/3497980; $=3497980}
VERBOSE: Making request to: #{#type=AccessPointDetails; #url=https://cpist/webacs/api/v3/data/AccessPointDetails/3497993; $=3497993}
VERBOSE: Making request to: #{#type=AccessPointDetails; #url=https://cpist/webacs/api/v3/data/AccessPointDetails/3512621; $=3512621}
VERBOSE: Making request to: #{#type=AccessPointDetails; #url=https://cpist/webacs/api/v3/data/AccessPointDetails/3526872; $=3526872}
VERBOSE: Making request to: #{#type=AccessPointDetails; #url=https://cpist/webacs/api/v3/data/AccessPointDetails/16162527426; $=16162527426}
#type #url $
----- ---- -
AccessPointDetails https://cpist/webacs/api/v3/data/AccessPointDetails/3373245 3373245
AccessPointDetails https://cpist/webacs/api/v3/data/AccessPointDetails/3413720 3413720
AccessPointDetails https://cpist/webacs/api/v3/data/AccessPointDetails/3432295 3432295
AccessPointDetails https://cpist/webacs/api/v3/data/AccessPointDetails/3432310 3432310
AccessPointDetails https://cpist/webacs/api/v3/data/AccessPointDetails/3462672 3462672
AccessPointDetails https://cpist/webacs/api/v3/data/AccessPointDetails/3497980 3497980
AccessPointDetails https://cpist/webacs/api/v3/data/AccessPointDetails/3497993 3497993
AccessPointDetails https://cpist/webacs/api/v3/data/AccessPointDetails/3512621 3512621
AccessPointDetails https://cpist/webacs/api/v3/data/AccessPointDetails/3526872 3526872
AccessPointDetails https://cpist/webacs/api/v3/data/AccessPointDetails/16162527426 16162527426
Here is the response JSON for one store as well:
{
"queryResponse": {
"#last": 9,
"#first": 0,
"#count": 10,
"#type": "AccessPointDetails",
"#requestUrl": "https://cpist/webacs/api/v3/data/AccessPointDetails?.group=0026",
"#responseType": "listEntityIds",
"#rootUrl": "https://cpist/webacs/api/v3/data",
"entityId": [
{
"#type": "AccessPointDetails",
"#url": "https://cpist/webacs/api/v3/data/AccessPointDetails/3373245",
"$": "3373245"
},
{
"#type": "AccessPointDetails",
"#url": "https://cpist/webacs/api/v3/data/AccessPointDetails/3413720",
"$": "3413720"
},
{
"#type": "AccessPointDetails",
"#url": "https://cpist/webacs/api/v3/data/AccessPointDetails/3432295",
"$": "3432295"
},
{
"#type": "AccessPointDetails",
"#url": "https://cpist/webacs/api/v3/data/AccessPointDetails/3432310",
"$": "3432310"
},
{
"#type": "AccessPointDetails",
"#url": "https://cpist/webacs/api/v3/data/AccessPointDetails/3462672",
"$": "3462672"
},
{
"#type": "AccessPointDetails",
"#url": "https://cpist/webacs/api/v3/data/AccessPointDetails/3497980",
"$": "3497980"
},
{
"#type": "AccessPointDetails",
"#url": "https://cpist/webacs/api/v3/data/AccessPointDetails/3497993",
"$": "3497993"
},
{
"#type": "AccessPointDetails",
"#url": "https://cpist/webacs/api/v3/data/AccessPointDetails/3512621",
"$": "3512621"
},
{
"#type": "AccessPointDetails",
"#url": "https://cpist/webacs/api/v3/data/AccessPointDetails/3526872",
"$": "3526872"
},
{
"#type": "AccessPointDetails",
"#url": "https://cpist/webacs/api/v3/data/AccessPointDetails/16162527426",
"$": "16162527426"
}
]
}
}
I feel I may be a bit burnt and making a silly oversight?
Solution Code (See Accepted Answer):
Function allAP {
Write-Verbose "Getting all APs for Store $Store"
$storeApReq = "https://cpist/webacs/api/v3/data/AccessPointDetails.json?.group=$Store"
Write-Verbose "Making request to $storeApReq"
$Global:apIdListReq = Invoke-RestMethod -uri $storeApReq -method Get -ContentType 'application/json' -headers #{ Authorization = $auth }
$Global:apIdList = $apIdListReq.queryResponse.entityId
$Global:apIdCount = $apIdListReq.queryResponse."#count"
Write-Verbose "Found $siteAPCount APs in Sites Database. $apIdCount out of $siteAPCount APs found."
Write-Verbose "Response Received: $apIdList"
$Global:apIdURL = $apIdListReq.queryResponse.entityId
$Global:apURL = $apIdUrl.'#url'
Write-Verbose "Starting a loop."
ForEach($apIdURL in $apIdList) {
Invoke-RestMethod -uri $apURL -method Get -ContentType 'application/json' -headers #{ Authorization = $auth }
Write-Verbose "Making request to: $apURL"
$apURL
}
}
The biggest issue you're having is accessing the members of your returned object incorrectly. The Invoke-RestMethod is returning a pscustomobject representation of the JSON the application returns to you. Your example JSON looked like this:
{
"queryResponse": {
"#last": 9,
"#first": 0,
"#count": 10,
"#type": "AccessPointDetails",
"#requestUrl": "https://cpist/webacs/api/v3/data/AccessPointDetails?.group=0026",
"#responseType": "listEntityIds",
"#rootUrl": "https://cpist/webacs/api/v3/data",
"entityId": [
{
"#type": "AccessPointDetails",
"#url": "https://cpist/webacs/api/v3/data/AccessPointDetails/3373245",
"$": "3373245"
},
...
]
}
}
This gets transformed into pscustomobject:
[pscustomobject]#{
'queryResponse' = [pscustomobject]#{
'#last' = 9
'#first' = 0
'#count' = 10
'#type' = 'AccessPointDetails'
'#requestUrl' = 'https://cpist/webacs/api/v3/data/AccessPointDetails?.group=0026'
'#responseType' = 'listEntityIds'
'#rootUrl' = 'https://cpist/webacs/api/v3/data'
'entityId' = #(
[pscustomobject]#{
'#type' = 'AccessPointDetails'
'#url' = 'https://cpist/webacs/api/v3/data/AccessPointDetails/3373245'
'$' = '3373245'
}
...
)
}
}
I'm unsure why accessing .'#url' isn't working for you. Below is a working implementation of what you're trying to do.
function Get-AccessPoint
{
param
(
[Parameter(Position = 0, Mandatory)]
[ValidateNotNullOrEmpty()]
[string]
$Store
)
# function is accessing above-scope variables $auth and $siteAPCount
Write-Verbose "Retrieving APs for store $Store"
$uri = "https://cpist/webacs/api/v3/data/AccessPointDetails.json?.group=$Store"
Write-Verbose "Request: $uri"
$global:apIdListReq = Invoke-RestMethod -Uri $uri -ContentType application/json -Headers #{Authorization = $auth}
$global:apIdList = $apIdListReq.queryResponse.entityId
$global:apIdCount = $apIdListReq.'#count'
Write-Verbose "Found $siteAPCount APs in Sites Database. $apIdCount out of $siteAPCount APs found."
foreach ($entity in $apIdList)
{
Write-Verbose "Making request to $($entity.'#url')"
Invoke-RestMethod -Uri $entity.'#url' -ContentType application/json -Headers #{Authorization = $auth}
}
}