src/Customization/Import-XrmRibbon.ps1
|
<# .SYNOPSIS Import ribbon customization XML for a table. .DESCRIPTION Import a modified RibbonDiffXml for a specific table by creating a temporary solution containing the table, exporting the solution, replacing the RibbonDiffXml node in customizations.xml, re-zipping, and importing. This allows modifying classic ribbon customizations (commands, display rules, enable rules) programmatically. .PARAMETER XrmClient Xrm connector initialized to target instance. Use latest one by default. (Dataverse ServiceClient) .PARAMETER EntityLogicalName Logical name of the table whose ribbon to update. .PARAMETER RibbonDiffXml The RibbonDiffXml content as string or XmlElement containing CustomActions, CommandDefinitions, RuleDefinitions, etc. .PARAMETER SolutionUniqueName Existing solution unique name to use for import. If provided, uses this solution instead of creating a temporary one. .PARAMETER Publish Publish customizations after import. Default: true. .OUTPUTS System.Void. .EXAMPLE $ribbonXml = Export-XrmRibbon -EntityLogicalName "account"; # Modify $ribbonXml as needed... Import-XrmRibbon -EntityLogicalName "account" -RibbonDiffXml $ribbonXml.OuterXml; .LINK https://learn.microsoft.com/en-us/power-apps/developer/model-driven-apps/customize-commands-ribbon #> function Import-XrmRibbon { [CmdletBinding()] [OutputType([System.Void])] param ( [Parameter(Mandatory = $false, ValueFromPipeline)] [Microsoft.PowerPlatform.Dataverse.Client.ServiceClient] $XrmClient = $Global:XrmClient, [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] [string] $EntityLogicalName, [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] [string] $RibbonDiffXml, [Parameter(Mandatory = $false)] [string] $SolutionUniqueName, [Parameter(Mandatory = $false)] [bool] $Publish = $true ) begin { $StopWatch = [System.Diagnostics.Stopwatch]::StartNew(); Trace-XrmFunction -Name $MyInvocation.MyCommand.Name -Stage Start -Parameters ($MyInvocation.MyCommand.Parameters); } process { $workPath = Join-Path $env:TEMP "RibbonImport_$([Guid]::NewGuid().ToString('N').Substring(0, 8))"; $useTempSolution = (-not $PSBoundParameters.ContainsKey('SolutionUniqueName')); $tempSolutionName = "RibbonImport_$([Guid]::NewGuid().ToString('N').Substring(0, 8))"; if ($useTempSolution) { $publisher = Get-XrmPublisher | Select-Object -First 1; $publisherRef = $publisher.ToEntityReference(); $SolutionUniqueName = $tempSolutionName; New-XrmSolution -DisplayName $tempSolutionName -UniqueName $tempSolutionName -PublisherReference $publisherRef | Out-Null; $entityMetadata = Get-XrmEntityMetadata -LogicalName $EntityLogicalName; Add-XrmSolutionComponent -SolutionUniqueName $tempSolutionName -ComponentId $entityMetadata.MetadataId -ComponentType 1 -DoNotIncludeSubcomponents $true | Out-Null; } try { # Export solution $solutionFilePath = Export-XrmSolution -SolutionUniqueName $SolutionUniqueName -Managed $false -ExportPath $env:TEMP; # Extract zip Expand-Archive -Path $solutionFilePath -DestinationPath $workPath -Force; # Modify customizations.xml $customizationsPath = Join-Path $workPath "customizations.xml"; if (-not (Test-Path $customizationsPath)) { throw "customizations.xml not found in exported solution."; } [xml]$customizationsXml = Get-Content -Path $customizationsPath -Raw; $entityNode = $customizationsXml.ImportExportXml.Entities.Entity | Where-Object { $_.EntityInfo.entity.Name -eq $EntityLogicalName; }; if (-not $entityNode) { throw "Entity '$EntityLogicalName' not found in customizations.xml."; } # Replace RibbonDiffXml [xml]$ribbonFragment = "<RibbonDiffXml>$RibbonDiffXml</RibbonDiffXml>"; if ($RibbonDiffXml.TrimStart().StartsWith("<RibbonDiffXml")) { [xml]$ribbonFragment = $RibbonDiffXml; } $importedNode = $customizationsXml.ImportNode($ribbonFragment.DocumentElement, $true); $oldRibbonNode = $entityNode.SelectSingleNode("RibbonDiffXml"); if ($oldRibbonNode) { $entityNode.ReplaceChild($importedNode, $oldRibbonNode) | Out-Null; } else { $entityNode.AppendChild($importedNode) | Out-Null; } $customizationsXml.Save($customizationsPath); # Re-zip $importZipPath = Join-Path $env:TEMP "$($SolutionUniqueName)_ribbon.zip"; if (Test-Path $importZipPath) { Remove-Item -Path $importZipPath -Force; } [System.IO.Compression.ZipFile]::CreateFromDirectory($workPath, $importZipPath); # Import solution Import-XrmSolution -SolutionUniqueName $SolutionUniqueName -SolutionFilePath $importZipPath -OverwriteUnmanagedCustomizations $true; # Publish if ($Publish) { Publish-XrmCustomizations; } } finally { # Cleanup temp solution if ($useTempSolution) { $tempSolution = Get-XrmSolution -SolutionUniqueName $tempSolutionName; if ($tempSolution) { Uninstall-XrmSolution -SolutionUniqueName $tempSolutionName; } } # Cleanup work files if ((Test-Path $workPath)) { Remove-Item -Path $workPath -Recurse -Force -ErrorAction SilentlyContinue; } if ((Test-Path $importZipPath)) { Remove-Item -Path $importZipPath -Force -ErrorAction SilentlyContinue; } } } end { $StopWatch.Stop(); Trace-XrmFunction -Name $MyInvocation.MyCommand.Name -Stage Stop -StopWatch $StopWatch; } } Export-ModuleMember -Function Import-XrmRibbon -Alias *; Register-ArgumentCompleter -CommandName Import-XrmRibbon -ParameterName "EntityLogicalName" -ScriptBlock { param($CommandName, $ParameterName, $WordToComplete, $CommandAst, $FakeBoundParameters) $validLogicalNames = Get-XrmEntitiesLogicalName; return $validLogicalNames | Where-Object { $_ -like "$wordToComplete*" }; } |