julianhayward / azapicall Goto Github PK
View Code? Open in Web Editor NEWPowerShell module Azure REST API call handler for ARM, Microsoft Graph, KeyVault, LogAnalytics
License: MIT License
PowerShell module Azure REST API call handler for ARM, Microsoft Graph, KeyVault, LogAnalytics
License: MIT License
Hey,
some of our users have access to the Azure Portal but don't have RBAC permissions on a subscription/resourcegroup/resource.
Regarding to this, they can authenticate with Connect-AzAccount
without having a context.
Nevertheless, they also want to be able to use the AzAPICall
.
Error message:
AzAPICall.ps1: You cannot call a method on a null-valued expression.
Debugger show that this is related to the following rows (218-243):
#API Call Tracking
$tstmp = (Get-Date -format "yyyyMMddHHmmssms")
$null = $script:arrayAPICallTracking.Add([PSCustomObject]@{
CurrentTask = $currentTask
TargetEndpoint = $targetEndpoint
Uri = $uri
Method = $method
TryCounter = $tryCounter
TryCounterUnexpectedError = $tryCounterUnexpectedError
RetryAuthorizationFailedCounter = $retryAuthorizationFailedCounter
RestartDueToDuplicateNextlinkCounter = $restartDueToDuplicateNextlinkCounter
TimeStamp = $tstmp
})
if ($caller -eq "CustomDataCollection") {
$null = $script:arrayAPICallTrackingCustomDataCollection.Add([PSCustomObject]@{
CurrentTask = $currentTask
TargetEndpoint = $targetEndpoint
Uri = $uri
Method = $method
TryCounter = $tryCounter
TryCounterUnexpectedError = $tryCounterUnexpectedError
RetryAuthorizationFailedCounter = $retryAuthorizationFailedCounter
RestartDueToDuplicateNextlinkCounter = $restartDueToDuplicateNextlinkCounter
TimeStamp = $tstmp
})
}
In my branch I tested and added the following lines which will init the mentioned variabels:
$arrayAPICallTracking = [System.Collections.ArrayList]@()
$arrayAPICallTrackingCustomDataCollection = [System.Collections.ArrayList]@()
or if you would like to run the powershell function in parallel:
$arrayAPICallTracking = [System.Collections.ArrayList]::Synchronized((New-Object System.Collections.ArrayList))
$arrayAPICallTrackingCustomDataCollection = [System.Collections.ArrayList]::Synchronized((New-Object System.Collections.ArrayList))
The ConsistencyLevel header required for advanced queries against directory objects is not included by default in subsequent page requests. It must be set explicitly in subsequent pages.
Regarding to this, an additional parameter for the "AzAPICall"-function call would be needed.
Within the new product "AzAdServicePrincipalInsights" the usage of "consistencyLevel" is already given:
https://github.com/JulianHayward/AzADServicePrincipalInsights/blob/main/pwsh/AzADServicePrincipalInsights.ps1
Example of listing more than 100 private DNS zones will stuck in an endless loop:
https://docs.microsoft.com/en-us/rest/api/dns/privatedns/privatezones/list
Unfortunately, a ":443" is within the NextLink response and then won't match the "$arrayAzureManagementEndPointUrls":
if ($arrayAzureManagementEndPointUrls | Where-Object { $uri -match $_ }) {
$targetEndpoint = "ManagementAPI"
$bearerToUse = $htBearerAccessToken.AccessTokenManagement
}
else {
$targetEndpoint = "MSGraphAPI"
$bearerToUse = $htBearerAccessToken.AccessTokenMSGraph
}
Regarding to this, no bearer token will be used and the variable "$bearerToUse" is empty.
The request will got a authorization error regarding the missing bearer token.
Quick solution is to check and replace the ":443":
if ($azAPIRequestConvertedFromJson.nextLink) {
$isMore = $true
if ($uri -eq $azAPIRequestConvertedFromJson.nextLink) {
if ($restartDueToDuplicateNextlinkCounter -gt 3) {
Write-Host " $currentTask restartDueToDuplicateNextlinkCounter: #$($restartDueToDuplicateNextlinkCounter) - Please report this error/exit"
Throw "Error - check the last console output for details"
}
else {
$restartDueToDuplicateNextlinkCounter++
Write-Host "nextLinkLog: uri is equal to nextLinkUri"
Write-Host "nextLinkLog: uri: $uri"
Write-Host "nextLinkLog: nextLinkUri: $($azAPIRequestConvertedFromJson.nextLink)"
Write-Host "nextLinkLog: re-starting (#$($restartDueToDuplicateNextlinkCounter)) '$currentTask'"
$apiCallResultsCollection = [System.Collections.ArrayList]@()
$uri = $initialUri
Start-Sleep -Seconds 1
createBearerToken -targetEndPoint $targetEndpoint
Start-Sleep -Seconds 1
}
}
else {
$uri = $azAPIRequestConvertedFromJson.nextLink
if($uri -match ":443"){
$uri = $uri -replace ":443"
}
}
if ($htParameters.DebugAzAPICall -eq $true) { Write-Host " DEBUG: nextLink: $Uri" -ForegroundColor $debugForeGroundColor }
}
Now, we would like to only use the Azure APIs instead of AZ cmdlets.
Regarding to this, several APIs will be tested.
One, which isn't working in the main branch, is checking the existence of an resource group:
https://docs.microsoft.com/en-us/rest/api/resources/resourcegroups/checkexistence
As explained in the article the statuscode (204) of the API call or the statuscode (404 => NotFound) of the error in the catch is needed.
Regarding to this, an add $listenOn
-Parameter like "StatusCode" is needed.
Additionally, not only the status code 200 is accepted and within the catch-part of the API call a status code check is needed as well.
Within the branch "user/kaiaschulz/mgmtapi-rg_check" a first test was successful.
Hey,
by default, the Microsoft Graph API respond with just 100 entries.
To increase the result entries we can use $top.
$top value must be between 1 and 999 inclusive.
The query parameter "$top" can use paging: https://docs.microsoft.com/en-us/graph/paging
If I use the query parameter "$top=50" should be used for testing purposes the respond will include a NextLink for paging.
Regarding to this, the "AzAPICall"-function will iterate further.
To prevent this behavior I would suggest to add another "Paging"-parameter to the "AzAPICall"-function.
If "Paging"-parameter is $true, then it should proceed further with the received "@oData.nextLink":
old:
elseIf ($azAPIRequestConvertedFromJson."@oData.nextLink") {
new:
elseIf ($azAPIRequestConvertedFromJson."@oData.nextLink" -and $Paging) {
$script:azAPICallConf = initAzAPICall
$uri = "$($azAPICallConf['azAPIEndpointUrls'].MicrosoftGraph)/beta/groups/<GroupId>/members/`$ref"
$uri
$Body = "{ ""@odata.id"": ""$($azAPICallConf['azAPIEndpointUrls'].MicrosoftGraph)/beta/directoryObjects/<EnterpriseApplicationObjectId>" }"
$Body
$AddAzureADGroupMember = AzAPICall -uri $uri -AzAPICallConfiguration $azAPICallConf -method 'POST' -body $Body
$AddAzureADGroupMember
POST https://microsoftgraph.chinacloudapi.cn/beta/groups/_<GroupId>_/members/$ref - try #1; returned: (StatusCode: '204') <.code: ''> <.error.code: ''> | <.message: ''> <.error.message: ''> - (plain : ) try again in 7 second(s)
https://docs.microsoft.com/en-us/graph/api/group-post-members?view=graph-rest-1.0&tabs=http
The status code of the request is 204. Regarding to this, the following code need to be replaced:
if ($azAPIRequest.StatusCode -ne 200) {
if ($azAPIRequest.StatusCode -notin 200..204) {
Add parameter "UnhandledErrorAction" to AzApiCall and AzAPICallErrorHandler functions and to update Documentation as such.
UnhandledErrorAction Parameter will determine how the AzAPICallErrorHandler function is exited when an Unhandled exception occurs
Parameter UnhandledErrorAction will accept values "Stop" (default) & "Continue" and aligns with PowerShell's ErrorAction values "Stop" and "Continue"
The UnhandledErrorAction Parameter Values are:
"Stop" will end processing with Throw 'Error - check the last console output for details'
"Continue" will break to return and continue processing
The Following to be added to the Param() section in both functions:
[ValidateSet('Stop','Continue')]
[string] $UnhandledErrorAction = 'Stop'
AzAPICall is causing an endless loop with an internal server error:
AzAPICall -uri $Uri -AzAPICallConfiguration $azAPICallConf -Method $method -currentTask $currentTask -body $body -listenOn 'Content'
[AzAPICall] DEBUGTASK: exportTemplate -> unexpectedError: false
[AzAPICall] DEBUGTASK: exportTemplate -> apiStatusCode: '500' (InternalServerError)
[AzAPICallErrorHandler] exportTemplate try #345; return: (StatusCode: '500' (InternalServerError)) <.code: ''> <.error.code: 'InternalServerError'> | <.message: ''> <.error.message: 'Encountered internal server error. Diagnostic information: timestamp '20221216T074356Z', subscription id 'xxxxxxxx, tracking id 'xxxxxxxx, request correlation id 'xxxxxxxx'.'> - AzAPICall: try again in 345 second(s)
[AzAPICall] DEBUGTASK: exportTemplate -> [AzAPICall] exportTemplate - retry
[AzAPICall] DEBUGTASK: exportTemplate -> attempt#346 processing: exportTemplate uri: 'https://management.azure.com/subscriptions/xxxxxxxx/resourceGroups/xxxxxxxx/exportTemplate?api-version=2019-05-01' body: '{
"resources": [
"*"
],
"options": "IncludeParameterDefaultValue"
}
'
implement telemetry functionality to get information about usage
When trying to use the property -listenOn 'Headers' the default will response with 'value' instead:
[AzAPICall] DEBUGTASK: exportTemplate - resourceGroupId: /subscriptions/xxx/resourceGroups/yyy, resourceId: * -> attempt#1 processing: exportTemplate - resourceGroupId: /subscriptions/xxx/resourceGroups/yyy, resourceId: * uri: 'https://management.azure.com/subscriptions/xxx/resourceGroups/yyy/exportTemplate?api-version=2019-10-01' body: '{
"resources": [
"*"
],
"options": "IncludeParameterDefaultValue"
}
'
[AzAPICall] DEBUGTASK: exportTemplate - resourceGroupId: /subscriptions/xxx/resourceGroups/yyy, resourceId: * -> unexpectedError: false
[AzAPICall] DEBUGTASK: exportTemplate - resourceGroupId: /subscriptions/xxx/resourceGroups/yyy, resourceId: * -> apiStatusCode: '202' (OK)
[AzAPICall] DEBUGTASK: exportTemplate - resourceGroupId: /subscriptions/xxx/resourceGroups/yyy, resourceId: * -> listenOn=Headers (13)
[AzAPICall] DEBUGTASK: exportTemplate - resourceGroupId: /subscriptions/xxx/resourceGroups/yyy, resourceId: * -> listenOn=Default(Value) value not exists; return empty array
[AzAPICall] DEBUGTASK: exportTemplate - resourceGroupId: /subscriptions/xxx/resourceGroups/yyy, resourceId: * -> NextLink/skipToken/NextMarker: none
In the specific case of exporting templates (ARM) we have several statusCode which are responding:
https://learn.microsoft.com/en-us/rest/api/resources/resource-groups/export-template?tabs=HTTP#response
In this case, if the status code is 200 the content will be filled.
If the status code is 202 instead, within the header.location a new uri will be available as feedback which need to be used to send the request again.
Actually, the if
-clause for the listenOn
isn't set correctly.
In some combinations of the writeMethod
and debugWriteMethod
the initialization of AzAPICall is failing and running into an error:
Line |
1788 | … rs'] += setHtParameters -AzAccountsVersion $AzAccountsVersion -gitHub …
| ~~~~~~~~~~~~~~~~~~
| Cannot process argument transformation on parameter 'AzAccountsVersion'. Cannot convert value to type System.String.
AzAPICall htParameters:
Name Value
---- -----
writeMethod Output
debugWriteMethod Output
Create htParameters succeeded
Get Az context
Get Az context succeeded
WARNING: Check Az context
WARNING: Az context AccountId: ''
WARNING: Az context AccountType: ''
InvalidOperation: C:\<Path>\Modules\AzAPICall\1.1.68\functions\AzAPICallFunctions.ps1:1817:61
Line |
1817 | … accountType = $($AzAPICallConfiguration['checkContext'].Account.Type)
| ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
| The property 'accountType' cannot be found on this object. Verify that the property exists and can be set.
WARNING: Check Az context failed: Az context is not set to any Subscription
WARNING: Set Az context to a subscription by running: Set-AzContext -subscription <subscriptionId> (run Get-AzSubscription to get the list of available Subscriptions). When done re-run the script
WARNING: OR
WARNING: Use parameter -SubscriptionId4AzContext - e.g. .\AzGovVizParallel.ps1 -SubscriptionId4AzContext <subscriptionId>
Exception: C:\<Path>\Modules\AzAPICall\1.1.68\functions\AzAPICallFunctions.ps1:1859:9
Line |
1859 | Throw 'Error - check the last console output for details'
| ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
| Error - check the last console output for details
Regarding to this, I wrote a small script to analyze which of the possible 64 combinations are affected to identify them and improving the code:
TestCases | Method | Debug | Error | Host | Information | Output | Progress | Verbose | Warning |
---|---|---|---|---|---|---|---|---|---|
1 | writeMethod | x | |||||||
1 | debugWriteMethod | x | |||||||
2 | writeMethod | x | |||||||
2 | debugWriteMethod | x | |||||||
3 | writeMethod | x | |||||||
3 | debugWriteMethod | x | |||||||
4 | writeMethod | x | |||||||
4 | debugWriteMethod | x | |||||||
5 | writeMethod | x | |||||||
5 | debugWriteMethod | x | |||||||
6 | writeMethod | x | |||||||
6 | debugWriteMethod | x | |||||||
7 | writeMethod | x | |||||||
7 | debugWriteMethod | x | |||||||
8 | writeMethod | x | |||||||
8 | debugWriteMethod | x | |||||||
9 | writeMethod | x | |||||||
9 | debugWriteMethod | x | |||||||
10 | writeMethod | x | |||||||
10 | debugWriteMethod | x | |||||||
11 | writeMethod | x | |||||||
11 | debugWriteMethod | x | |||||||
12 | writeMethod | x | |||||||
12 | debugWriteMethod | x | |||||||
13 | writeMethod | x | |||||||
13 | debugWriteMethod | x | |||||||
14 | writeMethod | x | |||||||
14 | debugWriteMethod | x | |||||||
15 | writeMethod | x | |||||||
15 | debugWriteMethod | x | |||||||
16 | writeMethod | x | |||||||
16 | debugWriteMethod | x | |||||||
17 | writeMethod | x | |||||||
17 | debugWriteMethod | x | |||||||
18 | writeMethod | x | |||||||
18 | debugWriteMethod | x | |||||||
19 | writeMethod | x | |||||||
19 | debugWriteMethod | x | |||||||
20 | writeMethod | x | |||||||
20 | debugWriteMethod | x | |||||||
21 | writeMethod | x | |||||||
21 | debugWriteMethod | x | |||||||
22 | writeMethod | x | |||||||
22 | debugWriteMethod | x | |||||||
23 | writeMethod | x | |||||||
23 | debugWriteMethod | x | |||||||
24 | writeMethod | x | |||||||
24 | debugWriteMethod | x | |||||||
25 | writeMethod | x | |||||||
25 | debugWriteMethod | x | |||||||
26 | writeMethod | x | |||||||
26 | debugWriteMethod | x | |||||||
27 | writeMethod | x | |||||||
27 | debugWriteMethod | x | |||||||
28 | writeMethod | x | |||||||
28 | debugWriteMethod | x | |||||||
29 | writeMethod | x | |||||||
29 | debugWriteMethod | x | |||||||
30 | writeMethod | x | |||||||
30 | debugWriteMethod | x | |||||||
31 | writeMethod | x | |||||||
31 | debugWriteMethod | x | |||||||
32 | writeMethod | x | |||||||
32 | debugWriteMethod | x | |||||||
33 | writeMethod | x | |||||||
33 | debugWriteMethod | x | |||||||
34 | writeMethod | x | |||||||
34 | debugWriteMethod | x | |||||||
35 | writeMethod | x | |||||||
35 | debugWriteMethod | x | |||||||
36 | writeMethod | x | |||||||
36 | debugWriteMethod | x | |||||||
37 | writeMethod | x | |||||||
37 | debugWriteMethod | x | |||||||
38 | writeMethod | x | |||||||
38 | debugWriteMethod | x | |||||||
39 | writeMethod | x | |||||||
39 | debugWriteMethod | x | |||||||
40 | writeMethod | x | |||||||
40 | debugWriteMethod | x | |||||||
41 | writeMethod | x | |||||||
41 | debugWriteMethod | x | |||||||
42 | writeMethod | x | |||||||
42 | debugWriteMethod | x | |||||||
43 | writeMethod | x | |||||||
43 | debugWriteMethod | x | |||||||
44 | writeMethod | x | |||||||
44 | debugWriteMethod | x | |||||||
45 | writeMethod | x | |||||||
45 | debugWriteMethod | x | |||||||
46 | writeMethod | x | |||||||
46 | debugWriteMethod | x | |||||||
47 | writeMethod | x | |||||||
47 | debugWriteMethod | x | |||||||
48 | writeMethod | x | |||||||
48 | debugWriteMethod | x | |||||||
49 | writeMethod | x | |||||||
49 | debugWriteMethod | x | |||||||
50 | writeMethod | x | |||||||
50 | debugWriteMethod | x | |||||||
51 | writeMethod | x | |||||||
51 | debugWriteMethod | x | |||||||
52 | writeMethod | x | |||||||
52 | debugWriteMethod | x | |||||||
53 | writeMethod | x | |||||||
53 | debugWriteMethod | x | |||||||
54 | writeMethod | x | |||||||
54 | debugWriteMethod | x | |||||||
55 | writeMethod | x | |||||||
55 | debugWriteMethod | x | |||||||
56 | writeMethod | x | |||||||
56 | debugWriteMethod | x | |||||||
57 | writeMethod | x | |||||||
57 | debugWriteMethod | x | |||||||
58 | writeMethod | x | |||||||
58 | debugWriteMethod | x | |||||||
59 | writeMethod | x | |||||||
59 | debugWriteMethod | x | |||||||
60 | writeMethod | x | |||||||
60 | debugWriteMethod | x | |||||||
61 | writeMethod | x | |||||||
61 | debugWriteMethod | x | |||||||
62 | writeMethod | x | |||||||
62 | debugWriteMethod | x | |||||||
63 | writeMethod | x | |||||||
63 | debugWriteMethod | x | |||||||
64 | writeMethod | x | |||||||
64 | debugWriteMethod | x |
The result of the tests shows that 21 combinations of the 64 test cases are affected:
The different methods of writing are needed for a customization of the output.
Some of the Azure services, e.g. Automation Account, won't display Write-Host
.
I cloned the Repo using VS Code to C:\Git_Ext
In my script, I did a:
Import-Module -Scope Local -Name 'C:\Git_Ext\AzAPICall\AzApiCall\AzApiCall.psm1'
and got this error:
Get-PSFConfigValue : The term 'Get-PSFConfigValue' is not recognized as the name of a cmdlet, function, script file, or operable program. Check the spelling of the name, or if a path was included, verify that the path is correct
and try again.
Do we have any instructions on how to build the Module so I can import locally vs from a gallery?
Hi.
I use AzAPICall as part of AzGovViz.
Using AzGovViz, I export data from several tenants, however, one of the tenants suddenly required the SubscriptionId4AzContext, which I have then provided. However, the addition of SubscriptionId4AzContext makes a Set-AzContext fail with "Please provide a valid tenant or a valid subscription." as if it can't find the subscription.
AzAPICall tries to Set-AzContext at line 1834 and then throws. This could of course be because I give it a wrong subscription ID but just before the Set-AzContext it tests the subscription at line 1830, using testSubscription, which correctly finds the subscription. I also verified that the ID it checked is the correct one.
I have tried logging in in Azure using the app registration that I use for the pipeline and I can switch between all necessary subscriptions.
I can't find a reason why it will not set the context.
Hello,
While running the script I am getting this error.
PS C:\Users\Admin\Downloads> .\AzGovVizParallel.ps1 -ManagementGroupId bxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
Security warning
Run only scripts that you trust. While scripts from the internet can be useful, this script can potentially harm your
computer. If you trust this script, use the Unblock-File cmdlet to allow the script to run without this warning
message. Do you want to run C:\Users\Admin\Downloads\AzGovVizParallel.ps1?
[D] Do not run [R] Run once [S] Suspend [?] Help (default is "D"): R
ParserError: C:\Users\Admin\Downloads\AzGovVizParallel.ps1:210
Line |
210 | … k Button--medium Button d-lg-none color-fg-inherit p-1"> <span cla …
| ~
| The '<' operator is reserved for future use.
PS C:\Users\Admin\Downloads>
Regards,
WVDAdminTF
adding to createbearertoken the endpoint for keyvault
missing Cred in Github Secrets
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.