Looking at how to tackle this problem, it appeared that there were two ways. One was writing a Flow (but I thought you needed the "Dataverse (Legacy)" connector to do that.
@Amardeep Raj thanks for showing how to do that. The other was to use PowerShell. I've been looking at this for a few days to see if I could do it. With a lot of help from Copilot, I came up with the following script in PowerShell. I ran it using VS Code and installed the PowerShell extension. You need to set the $envUrl. You can also specify a specific Solution name if you want the objects from that. This will generate some CSV files that you can open in Excel and filter on. The CSV file will have columns for: Solution Id, Solution Name, Component Id, ComponentFriendlyName, Object Id, Object Name, and Holding Solution (which I think is what you are looking for).
This community is supported by individuals freely devoting their time to answer questions and provide support. They do it to let you know you are not alone. This is a community.
If someone has been able to answer your questions or solve your problem, please click Does this answer your question. This will help others who have the same question find a solution quickly via the forum search.
If someone was able to provide you with more information that moved you closer to a solution, throw them a Like. It might make their day. 😊
Thanks
-Mark
# ============================================
# Config
# ============================================
$envUrl = "https://orgxxxxxxxx.api.crm.dynamics.com"
$targetSolutionName = "Your Solution Name here"
$exportFolder = "C:\Temp"
if (-not (Test-Path $exportFolder)) {
New-Item -Path $exportFolder -ItemType Directory | Out-Null
}
# ============================================
# Ensure MSAL.PS is available
# ============================================
if (-not (Get-Module -ListAvailable -Name MSAL.PS)) {
Install-Module MSAL.PS -Force
}
# ============================================
# Authenticate
# ============================================
$token = Get-MsalToken `
-ClientId "04f0c124-f2bc-4f59-8241-bf6df9866bbd" `
-TenantId "common" `
-Scopes "$envUrl/.default"
$accessToken = $token.AccessToken
$headers = @{
"Authorization" = "Bearer $accessToken"
"OData-Version" = "4.0"
"OData-MaxVersion" = "4.0"
"Accept" = "application/json"
}
# ============================================
# Component type friendly names
# ============================================
$ComponentTypeNames = @{
1 = "Entity"
2 = "Attribute"
3 = "Relationship"
4 = "Attribute Picklist Value"
5 = "Attribute Lookup Value"
6 = "View Attribute"
7 = "Localized Label"
8 = "Relationship Extra Condition"
9 = "Option Set"
10 = "Entity Relationship"
11 = "Entity Relationship Role"
12 = "Entity Relationship Relationships"
13 = "Managed Property"
14 = "Entity Key"
16 = "Privilege"
17 = "PrivilegeObjectTypeCode"
18 = "Index"
20 = "Role"
21 = "Role Privilege"
22 = "Display String"
23 = "Display String Map"
24 = "Form"
25 = "Organization"
26 = "Saved Query"
29 = "Workflow"
31 = "Report"
32 = "Report Entity"
33 = "Report Category"
34 = "Report Visibility"
35 = "Attachment"
36 = "Email Template"
37 = "Contract Template"
38 = "KB Article Template"
39 = "Mail Merge Template"
44 = "Duplicate Rule"
45 = "Duplicate Rule Condition"
46 = "Entity Map"
47 = "Attribute Map"
48 = "Ribbon Command"
49 = "Ribbon Context Group"
50 = "Ribbon Customization"
52 = "Ribbon Rule"
53 = "Ribbon Tab To Command Map"
55 = "Ribbon Diff"
59 = "Saved Query Visualization"
60 = "System Form"
61 = "Web Resource"
62 = "Site Map"
63 = "Connection Role"
64 = "Complex Control"
65 = "Hierarchy Rule"
66 = "Custom Control"
68 = "Custom Control Default Config"
70 = "Field Security Profile"
71 = "Field Permission"
90 = "Plugin Type"
91 = "Plugin Assembly"
92 = "SDK Message Processing Step"
93 = "SDK Message Processing Step Image"
95 = "Service Endpoint"
150 = "Routing Rule"
151 = "Routing Rule Item"
152 = "SLA"
153 = "SLA Item"
154 = "Convert Rule"
155 = "Convert Rule Item"
161 = "Mobile Offline Profile"
162 = "Mobile Offline Profile Item"
165 = "Similarity Rule"
166 = "Data Source Mapping"
201 = "SDKMessage"
202 = "SDKMessageFilter"
203 = "SdkMessagePair"
204 = "SdkMessageRequest"
205 = "SdkMessageRequestField"
206 = "SdkMessageResponse"
207 = "SdkMessageResponseField"
208 = "Import Map"
210 = "WebWizard"
300 = "Canvas App"
371 = "Connector"
372 = "Connector"
380 = "Environment Variable Definition"
381 = "Environment Variable Value"
400 = "AI Project Type"
401 = "AI Project"
402 = "AI Configuration"
430 = "Entity Analytics Configuration"
431 = "Attribute Image Configuration"
432 = "Entity Image Configuration"
10020 = "Environment Variable Value"
10021 = "Connection Role"
10022 = "Connection Role Object Type Code"
10049 = "Power Pages Website"
10788 = "AI Model"
10790 = "AI Model Component"
10042 = "Power Pages Site Component"
10012 = "Power Pages Content Snippet"
10351 = "Power Pages Table Permission"
10037 = "Power Pages Web Role"
10041 = "Power Pages Web Page"
10017 = "Power Pages Web Template"
10000 = "Power Pages Website Language"
10002 = "Power Pages Content Snippet"
181 = "Routing Rule Set"
10066 = "Power Pages Site Setting"
10067 = "Power Pages Site Setting Value"
10068 = "Power Pages Web File"
10070 = "Power Pages Web Link"
10940 = "Power Pages Site Component"
270 = "SLA KPI"
11037 = "Power Pages Scan Report"
10427 = "Power Pages Redirect"
80 = "Connection Reference"
101 = "Plugin Assembly"
10863 = "Unknown / Undocumented Component"
10865 = "Unknown / Undocumented Component"
183 = "Unknown / Undocumented Component"
10072 = "Unknown / Undocumented Component"
10003 = "Unknown / Undocumented Component"
10043 = "Unknown / Undocumented Component"
10092 = "Unknown / Undocumented Component"
10034 = "Unknown / Undocumented Component"
}
# ============================================
# Preload table list for smart unknown resolver
# ============================================
Write-Host "Loading Dataverse table metadata..." -ForegroundColor Cyan
$entityList = Invoke-RestMethod `
-Method Get `
-Uri "$envUrl/api/data/v9.2/EntityDefinitions?`$select=LogicalName" `
-Headers $headers
$allTables = $entityList.value.logicalname
# ============================================
# Retrieve ALL solution components (paged)
# ============================================
$allComponents = @()
$nextLink = "$envUrl/api/data/v9.2/solutioncomponents?" +
"`$select=solutioncomponentid,objectid,componenttype,_solutionid_value,rootsolutioncomponentid&" +
"`$expand=solutionid(`$select=friendlyname,uniquename)"
while ($nextLink) {
Write-Host "Fetching: $nextLink" -ForegroundColor Cyan
$response = Invoke-RestMethod -Method Get -Uri $nextLink -Headers $headers
$allComponents += $response.value
$nextLink = $response.'@odata.nextLink'
}
Write-Host "Total components retrieved: $($allComponents.Count)" -ForegroundColor Green
# Use all components for resolution
$subset = $allComponents
# ============================================
# Parallel resolver for Canvas Apps (300), Workflows/Flows (29), and unknowns
# ============================================
function Resolve-ObjectNamesParallel {
param(
[array]$Components,
[string]$EnvUrl,
[hashtable]$Headers,
[string[]]$AllTables
)
$typesToResolve = @(
29, # Workflows / Flows
300, # Canvas Apps
24, 26, 59, 60, 61, 62, # Forms, Views, Web Resources, Site Map
#80, # Connection Reference
#90, 91, 92, 93, # Plugin types
380, 381 # Environment Variables
)
$targets = $Components | Where-Object { $typesToResolve -contains [int]$_.componenttype }
if ($targets.Count -eq 0) { return $Components }
$iss = [System.Management.Automation.Runspaces.InitialSessionState]::CreateDefault()
$pool = [runspacefactory]::CreateRunspacePool(5, 20, $iss, $Host)
$pool.Open()
$jobs = @()
foreach ($c in $targets) {
$ps = [powershell]::Create()
$ps.RunspacePool = $pool
$null = $ps.AddScript({
param($componentType, $objectId, $envUrl, $headers, $allTables)
function Safe-Get {
param([string]$url, [string]$field)
try {
$resp = Invoke-RestMethod -Method Get -Uri $url -Headers $headers
return $resp.$field
}
catch { return $null }
}
function Resolve-TableName {
param($objectId, $envUrl, $headers, $allTables)
foreach ($table in $allTables) {
$url = "$envUrl/api/data/v9.2/$table($objectId)?`$select=$tableid"
try {
$null = Invoke-RestMethod -Method Get -Uri $url -Headers $headers -TimeoutSec 3
return $table
}
catch { }
}
return $null
}
function Smart-Component-Resolver {
param($tableName)
switch -Wildcard ($tableName) {
"mspp_webpage" { return "Power Pages Web Page" }
"mspp_webfile" { return "Power Pages Web File" }
"mspp_webrole" { return "Power Pages Web Role" }
"mspp_webtemplate" { return "Power Pages Web Template" }
"mspp_contentsnippet" { return "Power Pages Content Snippet" }
"mspp_sitesetting" { return "Power Pages Site Setting" }
"mspp_sitesettingvalue" { return "Power Pages Site Setting Value" }
"mspp_redirect" { return "Power Pages Redirect" }
"powerpagesscanreport" { return "Power Pages Scan Report" }
default { return "Unknown / Undocumented Component" }
}
}
function Resolve-UnknownType {
param($componentType, $objectId, $envUrl, $headers, $allTables)
$table = Resolve-TableName -objectId $objectId -envUrl $envUrl -headers $headers -allTables $allTables
if ($table) {
return Smart-Component-Resolver -tableName $table
}
return "Unknown / Undocumented Component"
}
switch ([int]$componentType) {
29 {
# First try workflows
$wfUrl = "$envUrl/api/data/v9.2/workflows($objectId)?`$select=name"
$name = Safe-Get $wfUrl "name"
if (-not [string]::IsNullOrWhiteSpace($name)) {
return $name
}
# Then try flows (for Cloud Flows that might be in flow table)
$flowUrl = "$envUrl/api/data/v9.2/flows($objectId)?`$select=displayname"
$flowName = Safe-Get $flowUrl "displayname"
if (-not [string]::IsNullOrWhiteSpace($flowName)) {
return $flowName
}
return "[Workflow]"
}
300 {
$name = Safe-Get "$envUrl/api/data/v9.2/canvasapps($objectId)?`$select=displayname" "displayname"
if ([string]::IsNullOrWhiteSpace($name)) { return "[Canvas App]" }
return $name
}
24 {
return Safe-Get "$envUrl/api/data/v9.2/systemforms($objectId)?`$select=name" "name"
}
60 {
return Safe-Get "$envUrl/api/data/v9.2/systemforms($objectId)?`$select=name" "name"
}
61 {
return Safe-Get "$envUrl/api/data/v9.2/webresourceset($objectId)?`$select=name" "name"
}
80 {
return Safe-Get "$envUrl/api/data/v9.2/connectionreferences($objectId)?`$select=connectionreferencelogicalname" "connectionreferencelogicalname"
}
90 {
return Safe-Get "$envUrl/api/data/v9.2/plugintypes($objectId)?`$select=name" "name"
}
91 {
return Safe-Get "$envUrl/api/data/v9.2/pluginassemblies($objectId)?`$select=name" "name"
}
92 {
return Safe-Get "$envUrl/api/data/v9.2/sdkmessageprocessingsteps($objectId)?`$select=name" "name"
}
93 {
return Safe-Get "$envUrl/api/data/v9.2/sdkmessageprocessingstepimages($objectId)?`$select=name" "name"
}
380 {
return Safe-Get "$envUrl/api/data/v9.2/environmentvariabledefinitions($objectId)?`$select=schemaname" "schemaname"
}
381 {
return Safe-Get "$envUrl/api/data/v9.2/environmentvariablevalues($objectId)?`$select=value" "value"
}
default { return $null }
#default {
# return Resolve-UnknownType -componentType $componentType -objectId $objectId -envUrl $envUrl -headers $headers -allTables $allTables
#}
}
}).AddArgument($c.componenttype).AddArgument($c.objectid).AddArgument($EnvUrl).AddArgument($Headers).AddArgument($AllTables)
$jobs += [PSCustomObject]@{
Component = $c
Handle = $ps.BeginInvoke()
PowerShell = $ps
}
}
# Progress bar for resolving object names
$resolveIndex = 0
$resolveTotal = $jobs.Count
foreach ($job in $jobs) {
$resolveIndex++
Write-Progress `
-Activity "Resolving Object Names" `
-Status "Processing $resolveIndex of $resolveTotal" `
-PercentComplete (($resolveIndex / $resolveTotal) * 100)
# Get the raw runspace output
$raw = $job.PowerShell.EndInvoke($job.Handle)
# Extract the actual string value (first item)
$value = $raw | Select-Object -First 1
# Store the clean value
$job.Component | Add-Member -NotePropertyName ObjectName -NotePropertyValue $value -Force
$job.PowerShell.Dispose()
}
Write-Progress -Activity "Resolving Object Names" -Completed
return $Components
}
# Run the resolver
$subset = Resolve-ObjectNamesParallel -Components $subset -EnvUrl $envUrl -Headers $headers -AllTables $allTables
# ============================================
# Build fast lookup for root solution components
# ============================================
$componentLookup = @{}
foreach ($item in $subset) {
$componentLookup[$item.solutioncomponentid] = $item
}
# ============================================
# Build rows (with progress)
# ============================================
$rowIndex = 0
$rowTotal = $subset.Count
$rowsAll = foreach ($c in $subset) {
$rowIndex++
Write-Progress `
-Activity "Building Output Rows" `
-Status "Row $rowIndex of $rowTotal (Type $($c.componenttype))" `
-PercentComplete (($rowIndex / $rowTotal) * 100)
$solutionName = $c.solutionid.friendlyname
$holdingSolutionName = $null
if ($c.rootsolutioncomponentid) {
$root = $componentLookup[$c.rootsolutioncomponentid]
if ($root) { $holdingSolutionName = $root.solutionid.friendlyname }
}
[PSCustomObject]@{
SolutionId = $c._solutionid_value
Solution = $solutionName
ComponentType = $c.componenttype
ComponentFriendlyName = $ComponentTypeNames[[int]$c.componenttype]
ObjectId = $c.objectid
ObjectName = $c.ObjectName
HoldingSolution = $holdingSolutionName
}
}
Write-Progress -Activity "Building Output Rows" -Completed
$rowsTarget = $rowsAll | Where-Object { $_.Solution -eq $targetSolutionName }
# ============================================
# Unknown component types CSV
# ============================================
$unknownRows = $rowsAll | Where-Object {
-not $ComponentTypeNames.ContainsKey([int]$_.ComponentType)
}
$timestamp = (Get-Date).ToString("yyyyMMdd_HHmmss")
if ($unknownRows.Count -gt 0) {
$unknownPath = Join-Path $exportFolder "UnknownComponentTypes_$timestamp.csv"
$unknownRows | Export-Csv -Path $unknownPath -NoTypeInformation -Encoding UTF8
Write-Host "Unknown component types CSV: $unknownPath"
}
# ============================================
# Export CSVs
# ============================================
$allPath = Join-Path $exportFolder "SolutionComponents_$timestamp.csv"
$targetPath = Join-Path $exportFolder "SolutionComponents_$($targetSolutionName.Replace(' ','_'))_$timestamp.csv"
$rowsAll | Export-Csv -Path $allPath -NoTypeInformation -Encoding UTF8
$rowsTarget | Export-Csv -Path $targetPath -NoTypeInformation -Encoding UTF8
Write-Host "All Objects CSV: $allPath"
Write-Host "Target solution CSV: $targetPath"