Public/Plugins/Remove-DataverseOrphanedSteps.ps1
|
function Remove-DataverseOrphanedSteps { <# .SYNOPSIS Removes orphaned plugin steps that exist in Dataverse but not in configuration. .DESCRIPTION Compares the registrations.json configuration with Dataverse and removes any steps that exist in Dataverse but are not defined in the configuration. This is useful for cleaning up after plugin refactoring or removal. .PARAMETER RegistrationFile Path to the registrations.json file. .PARAMETER Connection CrmServiceClient connection object from Connect-DataverseEnvironment. .PARAMETER AssemblyName Specific assembly name to clean up. If not provided, cleans all assemblies. .PARAMETER WhatIf Show what would be removed without making changes. .EXAMPLE $conn = Connect-DataverseEnvironment -Interactive Remove-DataverseOrphanedSteps -RegistrationFile "./registrations.json" -Connection $conn .EXAMPLE Remove-DataverseOrphanedSteps -RegistrationFile "./registrations.json" -Connection $conn -WhatIf Shows orphaned steps without removing them. .OUTPUTS Summary object with count of removed steps and images. #> [CmdletBinding(SupportsShouldProcess = $true, ConfirmImpact = 'High')] param( [Parameter(Mandatory = $true)] [string]$RegistrationFile, [Parameter(Mandatory = $true)] [Microsoft.Xrm.Tooling.Connector.CrmServiceClient]$Connection, [Parameter()] [string]$AssemblyName ) $isWhatIf = $WhatIfPreference -or $PSCmdlet.MyInvocation.BoundParameters["WhatIf"] if (-not (Test-Path $RegistrationFile)) { throw "Registration file not found: $RegistrationFile" } $registrations = Read-RegistrationJson -Path $RegistrationFile if (-not $registrations -or -not $registrations.assemblies) { throw "Invalid or empty registrations.json" } Write-Log "Orphaned Step Removal Tool" if ($isWhatIf) { Write-LogWarning "Running in WhatIf mode - no changes will be made" } $apiUrl = Get-WebApiBaseUrl -Connection $Connection $authHeaders = Get-AuthHeaders -Connection $Connection Write-LogSuccess "Connected to: $($Connection.ConnectedOrgFriendlyName)" $totalStepsDeleted = 0 $totalImagesDeleted = 0 $totalPluginTypesDeleted = 0 foreach ($asmReg in $registrations.assemblies) { if ($AssemblyName -and $asmReg.name -ne $AssemblyName) { continue } Write-Log "" Write-Log ("=" * 60) Write-Log "Cleaning: $($asmReg.name)" Write-Log ("=" * 60) # Get assembly from Dataverse $assembly = Get-PluginAssembly -ApiUrl $apiUrl -AuthHeaders $authHeaders -Name $asmReg.name if (-not $assembly) { Write-LogWarning "Assembly not found in Dataverse: $($asmReg.name)" continue } # Build list of configured step names $configuredStepNames = @() foreach ($plugin in $asmReg.plugins) { foreach ($step in $plugin.steps) { $configuredStepNames += $step.name } } # Get all steps for this assembly from Dataverse $existingSteps = Get-ProcessingStepsForAssembly -ApiUrl $apiUrl -AuthHeaders $authHeaders ` -AssemblyId $assembly.pluginassemblyid Write-Log "Found $($existingSteps.Count) step(s) in Dataverse" Write-Log "Configuration has $($configuredStepNames.Count) step(s)" # Find and remove orphaned steps foreach ($existingStep in $existingSteps) { if ($configuredStepNames -notcontains $existingStep.name) { if ($isWhatIf) { Write-LogWarning "[WhatIf] Would delete orphaned step: $($existingStep.name)" $totalStepsDeleted++ } elseif ($PSCmdlet.ShouldProcess($existingStep.name, "Delete orphaned step")) { Write-LogWarning "Deleting orphaned step: $($existingStep.name)" # Delete images first try { $stepImages = Get-StepImages -ApiUrl $apiUrl -AuthHeaders $authHeaders ` -StepId $existingStep.sdkmessageprocessingstepid foreach ($image in $stepImages) { Write-Log " Deleting image: $($image.name)" Remove-StepImage -ApiUrl $apiUrl -AuthHeaders $authHeaders ` -ImageId $image.sdkmessageprocessingstepimageid $totalImagesDeleted++ } } catch { Write-LogDebug " Could not query/delete images: $($_.Exception.Message)" } # Delete the step try { Remove-ProcessingStep -ApiUrl $apiUrl -AuthHeaders $authHeaders ` -StepId $existingStep.sdkmessageprocessingstepid $totalStepsDeleted++ Write-LogSuccess " Step deleted" } catch { Write-LogError " Failed to delete step: $($_.Exception.Message)" } } } } # Check for orphaned plugin types (if allTypeNames is available) if ($asmReg.allTypeNames -and $asmReg.allTypeNames.Count -gt 0) { Write-Log "" Write-Log "Checking for orphaned plugin types..." $existingPluginTypes = Get-PluginTypesForAssembly -ApiUrl $apiUrl -AuthHeaders $authHeaders ` -AssemblyId $assembly.pluginassemblyid foreach ($existingType in $existingPluginTypes) { if ($existingType.typename -notin $asmReg.allTypeNames) { $stepCount = Get-PluginTypeStepCount -ApiUrl $apiUrl -AuthHeaders $authHeaders ` -PluginTypeId $existingType.plugintypeid if ($stepCount -gt 0) { Write-LogWarning "Orphaned plugin type '$($existingType.typename)' has $stepCount step(s)" Write-Log " Delete steps first before removing plugin type" } else { if ($isWhatIf) { Write-LogWarning "[WhatIf] Would delete orphaned plugin type: $($existingType.typename)" $totalPluginTypesDeleted++ } elseif ($PSCmdlet.ShouldProcess($existingType.typename, "Delete orphaned plugin type")) { Write-LogWarning "Deleting orphaned plugin type: $($existingType.typename)" try { Remove-PluginType -ApiUrl $apiUrl -AuthHeaders $authHeaders ` -PluginTypeId $existingType.plugintypeid $totalPluginTypesDeleted++ Write-LogSuccess " Plugin type deleted" } catch { Write-LogError " Failed to delete plugin type: $($_.Exception.Message)" } } } } } } } # Summary Write-Log "" Write-Log ("=" * 60) Write-Log "Cleanup Summary" Write-Log ("=" * 60) if ($totalStepsDeleted -eq 0 -and $totalImagesDeleted -eq 0 -and $totalPluginTypesDeleted -eq 0) { Write-LogSuccess "No orphaned components found" } else { if ($isWhatIf) { Write-LogWarning "Would delete:" } else { Write-LogSuccess "Deleted:" } Write-Log " Steps: $totalStepsDeleted" Write-Log " Images: $totalImagesDeleted" Write-Log " Plugin types: $totalPluginTypesDeleted" } if ($isWhatIf) { Write-Log "" Write-LogWarning "WhatIf mode: No actual changes were made" } return [PSCustomObject]@{ StepsDeleted = $totalStepsDeleted ImagesDeleted = $totalImagesDeleted PluginTypesDeleted = $totalPluginTypesDeleted } } |