functions/Get-PpeCustomApi.ps1
|
<# .SYNOPSIS Get custom APIs from a Power Platform / Dataverse environment. .DESCRIPTION Retrieves custom API definitions from the Dataverse /api/data/v9.2/customapis endpoint, returning discovery information for each API. Results show the unique name, whether the API is a Function (HTTP GET) or an Action (HTTP POST), the binding type (Global, Entity, EntityCollection) and whether the API is private. A private API (IsPrivate) is hidden from the Web API $metadata document and code generation tools, but it can still be invoked through the Web API and the SOAP Organization Service (Execute) when the unique name is known. No custom API is SOAP-only. .PARAMETER EnvironmentId The ID of the environment to retrieve custom APIs from. Can be either the environment name or the environment GUID (PPAC). .PARAMETER Name The value to filter the results by. Filters against the custom API UniqueName, Name and DisplayName fields — any match on either will include the record. Supports wildcard characters for flexible matching. Default value is "*", which returns all custom APIs. .PARAMETER AsExcelOutput Instructs the cmdlet to export the retrieved custom APIs to an Excel file. .EXAMPLE PS C:\> Get-PpeCustomApi -EnvironmentId "ContosoEnv" This command retrieves all custom APIs in the environment "ContosoEnv". .EXAMPLE PS C:\> Get-PpeCustomApi -EnvironmentId "ContosoEnv" -Name "msprov_*" This command retrieves all custom APIs whose unique name starts with "msprov_" from the environment "ContosoEnv". .EXAMPLE PS C:\> Get-PpeCustomApi -EnvironmentId "ContosoEnv" -Name "*fino*" This command retrieves all custom APIs whose unique name, name or display name contains "fino" from the environment "ContosoEnv". .EXAMPLE PS C:\> Get-PpeCustomApi -EnvironmentId "ContosoEnv" -AsExcelOutput This command retrieves all custom APIs in the environment "ContosoEnv" and exports the results to an Excel file. .NOTES Author: Mötz Jensen (@Splaxi) #> function Get-PpeCustomApi { [CmdletBinding()] [OutputType('System.Object[]')] param ( [Parameter (Mandatory = $true)] [string] $EnvironmentId, [string] $Name = "*", [switch] $AsExcelOutput ) begin { # Make sure all *BapEnvironment* cmdlets will validate that the environment exists prior running anything. $envObj = Get-BapEnvironment ` -EnvironmentId $EnvironmentId | ` Select-Object -First 1 if ($null -eq $envObj) { $messageString = "The supplied EnvironmentId: <c='em'>$EnvironmentId</c> didn't return any matching environment details. Please verify that the EnvironmentId is correct - try running the <c='em'>Get-BapEnvironment</c> cmdlet." Write-PSFMessage -Level Important -Message $messageString Stop-PSFFunction -Message "Stopping because environment was NOT found based on the id." -Exception $([System.Exception]::new($($messageString -replace '<[^>]+>', ''))) } if (Test-PSFFunctionInterrupt) { return } $baseUri = $envObj.PpacEnvUri.TrimEnd('/') $secureToken = (Get-AzAccessToken -ResourceUrl $baseUri -AsSecureString).Token $tokenWebApiValue = ConvertFrom-SecureString -AsPlainText -SecureString $secureToken $headersWebApi = @{ "Authorization" = "Bearer $($tokenWebApiValue)" "Accept" = "application/json;odata.metadata=minimal" "OData-MaxVersion" = "4.0" "OData-Version" = "4.0" "Prefer" = "odata.include-annotations=*" } $bindingLabels = @{ 0 = "Global" 1 = "Entity" 2 = "EntityCollection" } $processingStepLabels = @{ 0 = "None" 1 = "Async Only" 2 = "Sync and Async" } $parameterTypeLabels = @{ 0 = "Boolean" 1 = "DateTime" 2 = "Decimal" 3 = "Entity" 4 = "EntityCollection" 5 = "EntityReference" 6 = "Float" 7 = "Integer" 8 = "Money" 9 = "Picklist" 10 = "String" 11 = "StringArray" 12 = "Guid" } } process { if (Test-PSFFunctionInterrupt) { return } $localUri = $baseUri + '/api/data/v9.2/customapis?$select=uniquename,name,displayname,description,isfunction,isprivate,bindingtype,boundentitylogicalname,allowedcustomprocessingsteptype,workflowsdkstepenabled,executeprivilegename&$expand=CustomAPIRequestParameters($select=uniquename,type,isoptional),CustomAPIResponseProperties($select=uniquename,type)' $colApisRaw = @() $nextUri = $localUri do { $page = Invoke-RestMethod -Method Get ` -Uri $nextUri ` -Headers $headersWebApi 4> $null $colApisRaw += @($page.value) $nextUri = $page.'@odata.nextLink' } while (-not [string]::IsNullOrEmpty($nextUri)) $colApis = $colApisRaw | Where-Object { $localDisplayName = $_.displayname if ($localDisplayName -isnot [string] -and $null -ne $localDisplayName.UserLocalizedLabel.Label) { $localDisplayName = $localDisplayName.UserLocalizedLabel.Label } ($_.uniquename -like $Name -or $_.uniquename -eq $Name) ` -or ($_.name -like $Name -or $_.name -eq $Name) ` -or ($localDisplayName -like $Name -or $localDisplayName -eq $Name) } $entitySetByLogicalName = @{} $boundLogicalNames = @($colApis | Where-Object { [int]$_.bindingtype -ne 0 -and -not [string]::IsNullOrEmpty($_.boundentitylogicalname) } | Select-Object -ExpandProperty boundentitylogicalname -Unique) $batchSize = 50 for ($i = 0; $i -lt $boundLogicalNames.Count; $i += $batchSize) { $batch = @($boundLogicalNames[$i..([Math]::Min($i + $batchSize - 1, $boundLogicalNames.Count - 1))]) try { $entityFilter = ($batch | ForEach-Object { "LogicalName eq '$_'" }) -join ' or ' $entityUri = $baseUri + "/api/data/v9.2/EntityDefinitions?`$select=LogicalName,EntitySetName&`$filter=$entityFilter" $colEntities = Invoke-RestMethod -Method Get ` -Uri $entityUri ` -Headers $headersWebApi 4> $null | ` Select-Object -ExpandProperty value foreach ($entityObj in $colEntities) { $entitySetByLogicalName["$($entityObj.LogicalName)"] = "$($entityObj.EntitySetName)" } } catch { Write-PSFMessage -Level Verbose -Message "Failed to resolve entity set names for bound custom APIs. WebApiPath will fall back to the bound entity logical name. Error: $($_.Exception.Message)" } } $resCol = @(foreach ($apiObj in $colApis) { $isFunction = [bool]$apiObj.isfunction $isPrivate = [bool]$apiObj.isprivate if ($isFunction) { $operationType = "Function" $httpMethod = "GET" } else { $operationType = "Action" $httpMethod = "POST" } $bindingValue = [int]$apiObj.bindingtype $binding = $bindingLabels[$bindingValue] if ([string]::IsNullOrEmpty($binding)) { $binding = "$bindingValue" } $boundEntity = "$($apiObj.boundentitylogicalname)" $entitySet = $entitySetByLogicalName[$boundEntity] if ([string]::IsNullOrEmpty($entitySet)) { $entitySet = $boundEntity } if ($binding -eq "Global") { $webApiPath = "/api/data/v9.2/$($apiObj.uniquename)" } elseif ($binding -eq "Entity") { $webApiPath = "/api/data/v9.2/$entitySet(<id>)/Microsoft.Dynamics.CRM.$($apiObj.uniquename)" } else { $webApiPath = "/api/data/v9.2/$entitySet/Microsoft.Dynamics.CRM.$($apiObj.uniquename)" } if ($isFunction) { $webApiPath += "(...)" } $displayName = $apiObj.displayname if ($displayName -isnot [string] -and $null -ne $displayName.UserLocalizedLabel.Label) { $displayName = $displayName.UserLocalizedLabel.Label } $requestParams = @($apiObj.CustomAPIRequestParameters | Where-Object { $null -ne $_ }) $responseProps = @($apiObj.CustomAPIResponseProperties | Where-Object { $null -ne $_ }) $requestSummaries = @($requestParams | ForEach-Object { $typeLabel = $parameterTypeLabels[[int]$_.type] if ([string]::IsNullOrEmpty($typeLabel)) { $typeLabel = "$($_.type)" } if ([bool]$_.isoptional) { $optionality = "Optional" } else { $optionality = "Required" } "$($_.uniquename) ($typeLabel, $optionality)" } | Sort-Object) $responseSummaries = @($responseProps | ForEach-Object { $typeLabel = $parameterTypeLabels[[int]$_.type] if ([string]::IsNullOrEmpty($typeLabel)) { $typeLabel = "$($_.type)" } "$($_.uniquename) ($typeLabel)" } | Sort-Object) $processingStepValue = [int]$apiObj.allowedcustomprocessingsteptype $processingStep = $processingStepLabels[$processingStepValue] if ([string]::IsNullOrEmpty($processingStep)) { $processingStep = "$processingStepValue" } [PsCustomObject][ordered]@{ Name = "$($apiObj.uniquename)" UniqueName = "$($apiObj.uniquename)" DisplayName = "$displayName" OperationType = $operationType HttpMethod = $httpMethod Binding = $binding BoundEntity = $boundEntity EntitySet = $entitySet WebApiPath = $webApiPath SoapRequest = "$($apiObj.uniquename)" IsPrivate = $isPrivate InMetadata = (-not $isPrivate) WorkflowEnabled = [bool]$apiObj.workflowsdkstepenabled AllowedProcessingStep = $processingStep ExecutePrivilege = "$($apiObj.executeprivilegename)" RequestParameterCount = $requestParams.Count RequestParameters = ($requestSummaries -join ", ") ResponsePropertyCount = $responseProps.Count ResponseProperties = ($responseSummaries -join ", ") Description = "$($apiObj.description)" } }) $resCol = @( $resCol | Sort-Object -Property Name | ` Select-PSFObject -TypeName "D365Bap.Tools.PpeCustomApi" -Property * ) if ($AsExcelOutput) { $resCol | Export-Excel -WorksheetName "Get-PpeCustomApi" return } $resCol } end { } } |