Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
197 changes: 96 additions & 101 deletions modules/EntraTokenAid.psm1

Large diffs are not rendered by default.

10 changes: 8 additions & 2 deletions modules/Send-ApiRequest.psm1
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
<#
<#
.SYNOPSIS
Sends a single API request with retry, pagination, and robust error parsing.

.DESCRIPTION
Send-ApiRequest is a generic wrapper around Invoke-RestMethod.
It supports automatic pagination, retry logic for transient failures,
custom headers/query parameters, proxy usage, and improved response error parsing.

.LINK
https://github.com/zh54321/Send-ApiRequest
#>
Expand Down Expand Up @@ -68,6 +68,8 @@ function Get-ApiErrorDetails {
$statusCode = [int]$response.StatusCode
}
} catch {
# Status code extraction is best-effort; format varies by exception type
Write-Verbose "Could not extract HTTP status code: $($_.Exception.Message)"
}
}

Expand All @@ -93,6 +95,8 @@ function Get-ApiErrorDetails {
}
}
} catch {
# Response body extraction is best-effort; stream may be closed or unavailable
Write-Verbose "Could not read response body: $($_.Exception.Message)"
}
}

Expand All @@ -113,6 +117,8 @@ function Get-ApiErrorDetails {
if ($parsedError.message) { $errorMessage = [string]$parsedError.message }
}
} catch {
# JSON parsing is best-effort; body may not be valid JSON
Write-Verbose "Could not parse error response as JSON: $($_.Exception.Message)"
}
}

Expand Down
28 changes: 14 additions & 14 deletions modules/Send-GraphBatchRequest.psm1
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
<#
<#
.SYNOPSIS
Sends a batch request to Microsoft Graph API.

Expand Down Expand Up @@ -59,21 +59,21 @@
$Requests = @(
@{ "id" = "1"; "method" = "GET"; "url" = "/groups" }
)

Send-GraphBatchRequest -AccessToken $AccessToken -Requests $Requests -DebugMode

.EXAMPLE
$AccessToken = "YOUR_ACCESS_TOKEN"
$Requests = @(
@{
@{
"id" = "1"
"method" = "POST"
"url" = "/groups"
"body" = @{ "displayName" = "New Group"; "mailEnabled" = $false; "mailNickname" = "whatever"; "securityEnabled" = $true }
"headers" = @{"Content-Type"= "application/json"}
"headers" = @{"Content-Type"= "application/json"}
}
)

Send-GraphBatchRequest -AccessToken $AccessToken -Requests $Requests -RawJson

.EXAMPLE
Expand Down Expand Up @@ -301,9 +301,9 @@ function Send-GraphBatchRequest {
Write-Warning ("[{0}] [!] Missing first-page data for ID {1} - initializing empty list." -f (Get-Date -Format "HH:mm:ss"), $id)
$PagedResultsMap[$id] = New-Object 'System.Collections.Generic.List[object]'
}

$PagedResultsMap[$id].AddRange($BatchResult.values[$id])

if ($BatchResult.nextLinks.ContainsKey($id)) {
$GlobalNextLinks.Add("$id|$($BatchResult.nextLinks[$id])")
}
Expand All @@ -327,7 +327,10 @@ function Send-GraphBatchRequest {
}


# JsonDepthResponse and VerboseMode are accepted from callers for interface consistency
function Invoke-GraphNextLinkBatch {
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSReviewUnusedParameter', 'JsonDepthResponse')]
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSReviewUnusedParameter', 'VerboseMode')]
param (
[string[]]$NextLinks,
[string[]]$Ids,
Expand All @@ -343,9 +346,6 @@ function Invoke-GraphNextLinkBatch {
[string]$ApiVersion
)

$ResultsList = New-Object 'System.Collections.Generic.List[object]'
$MoreNextLinks = New-Object 'System.Collections.Generic.List[string]'

$Headers = @{
"Authorization" = "Bearer $AccessToken"
"User-Agent" = $UserAgent
Expand Down Expand Up @@ -416,19 +416,19 @@ function Invoke-GraphNextLinkBatch {
}
$ResultMap = @{}
$MoreLinksMap = @{}

foreach ($resp in $BatchResp.responses) {
$i = [int]($resp.id -replace 'nl_', '')
$realId = $Ids[$i]

if (-not $ResultMap.ContainsKey($realId)) {
$ResultMap[$realId] = New-Object 'System.Collections.Generic.List[object]'
}

if ($resp.body.value) {
$ResultMap[$realId].AddRange(@($resp.body.value))
}

if ($resp.body.'@odata.nextLink') {
$MoreLinksMap[$realId] = $resp.body.'@odata.nextLink'
}
Expand Down
20 changes: 10 additions & 10 deletions modules/Send-GraphRequest.psm1
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
<#
<#
.SYNOPSIS
Sends requests to the Microsoft Graph API.

Expand Down Expand Up @@ -59,10 +59,10 @@
.EXAMPLE
Send-GraphRequest -AccessToken $token -Method GET -Uri '/users' -AdditionalHeaders @{ 'ConsistencyLevel' = 'eventual' }

.EXAMPLE
.EXAMPLE
Send-GraphRequest -AccessToken $token -Method GET -Uri '/users' -QueryParameters @{ '$filter' = "startswith(displayName,'Alex')" }"

.EXAMPLE
.EXAMPLE
$Body = @{
displayName = "Test Security Group2"
mailEnabled = $false
Expand All @@ -71,7 +71,7 @@
groupTypes = @()
}
$result = Send-GraphRequest -AccessToken $token -Method POST -Uri "/groups" -Body $Body -VerboseMode


.NOTES
Author: ZH54321
Expand Down Expand Up @@ -110,13 +110,13 @@ function Send-GraphRequest {

#Add query parameters
if ($QueryParameters) {
$QueryString = ($QueryParameters.GetEnumerator() |
ForEach-Object {
"$($_.Key)=$([uri]::EscapeDataString($_.Value))"
$QueryString = ($QueryParameters.GetEnumerator() |
ForEach-Object {
"$($_.Key)=$([uri]::EscapeDataString($_.Value))"
}) -join '&'
$FullUri = "$FullUri`?$QueryString"
}


#Define basic headers
$Headers = @{
Expand Down Expand Up @@ -223,15 +223,15 @@ function Send-GraphRequest {
} else {
if (-not ($StatusCode -eq 404 -and $Suppress404)) {
$msg = "[!] Graph API request failed after $RetryCount retries. Status: $StatusCode. Message: $StatusDesc"
$exception = New-Object System.Exception($msg)
$exception = New-Object System.Exception($msg)

$errorRecord = New-Object System.Management.Automation.ErrorRecord (
$exception,
"GraphApiRequestFailed",
$errorCategory,
$FullUri
)

Write-Error $errorRecord
}

Expand Down
Loading