src/Customization/Add-XrmFormControl.ps1

<#
    .SYNOPSIS
    Add a PCF custom control to a form field.

    .DESCRIPTION
    Add a Power Apps Component Framework (PCF) custom control binding onto a field in a model-driven app form
    by modifying the FormXML of the systemform record.

    .PARAMETER XrmClient
    Xrm connector initialized to target instance. Use latest one by default. (Dataverse ServiceClient)

    .PARAMETER FormReference
    EntityReference of the systemform record to modify.

    .PARAMETER FieldName
    Logical name of the field on the form to bind the control to.

    .PARAMETER ControlName
    Full unique name of the PCF control (e.g. "MscrmControls.FieldControls.LinearSliderControl").

    .PARAMETER Parameters
    Hashtable of control parameters with their static values. Optional.
    Example: @{ "min" = "0"; "max" = "1000"; "step" = "1" }

    .PARAMETER Publish
    Publish customizations after update. Default: true.

    .OUTPUTS
    System.Void.

    .EXAMPLE
    $formRef = New-XrmEntityReference -LogicalName "systemform" -Id $formId;
    Add-XrmFormControl -FormReference $formRef -FieldName "revenue" -ControlName "MscrmControls.FieldControls.LinearSliderControl" -Parameters @{ "min" = "0"; "max" = "1000000"; "step" = "100" };

    .LINK
    https://learn.microsoft.com/en-us/power-apps/developer/component-framework/add-custom-controls-to-a-field-or-entity
#>

function Add-XrmFormControl {
    [CmdletBinding()]
    [OutputType([System.Void])]
    param
    (
        [Parameter(Mandatory = $false, ValueFromPipeline)]
        [Microsoft.PowerPlatform.Dataverse.Client.ServiceClient]
        $XrmClient = $Global:XrmClient,

        [Parameter(Mandatory = $true)]
        [ValidateNotNullOrEmpty()]
        [Microsoft.Xrm.Sdk.EntityReference]
        $FormReference,

        [Parameter(Mandatory = $true)]
        [ValidateNotNullOrEmpty()]
        [string]
        $FieldName,

        [Parameter(Mandatory = $true)]
        [ValidateNotNullOrEmpty()]
        [string]
        $ControlName,

        [Parameter(Mandatory = $false)]
        [Hashtable]
        $Parameters,

        [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 {
        # Retrieve current form
        $form = $XrmClient | Get-XrmRecord -LogicalName "systemform" -Id $FormReference.Id -Columns "formxml";
        $formXmlString = $form["formxml"];

        [xml]$formXml = $formXmlString;

        # Find the control node for the field
        $controlNode = $formXml.SelectSingleNode("//control[@datafieldname='$FieldName']");
        if (-not $controlNode) {
            throw "Field '$FieldName' not found in the form XML.";
        }

        $controlId = $controlNode.GetAttribute("id");

        # Build customControl element
        $customControlNode = $formXml.CreateElement("customControl");
        $customControlNode.SetAttribute("name", $ControlName);
        $customControlNode.SetAttribute("formFactor", "0");

        # Add parameters
        if ($Parameters) {
            $parametersNode = $formXml.CreateElement("parameters");
            foreach ($key in $Parameters.Keys) {
                $paramNode = $formXml.CreateElement($key);
                $paramNode.SetAttribute("type", "Whole.None");
                $paramNode.InnerText = "val";

                $staticNode = $formXml.CreateElement("bind");
                $staticNode.SetAttribute("static", "true");
                $staticNode.SetAttribute("value", $Parameters[$key]);

                $paramNode = $formXml.CreateElement($key);
                $paramNode.InnerText = $Parameters[$key];
                $parametersNode.AppendChild($paramNode) | Out-Null;
            }
            $customControlNode.AppendChild($parametersNode) | Out-Null;
        }

        # Find or create controlDescriptions node
        $controlDescriptions = $controlNode.SelectSingleNode("controlDescriptions");
        if (-not $controlDescriptions) {
            $controlDescriptions = $formXml.CreateElement("controlDescriptions");
            $controlNode.AppendChild($controlDescriptions) | Out-Null;
        }

        # Add controlDescription entry
        $controlDescription = $formXml.CreateElement("controlDescription");
        $controlDescription.SetAttribute("forControl", $controlId);
        $controlDescription.AppendChild($customControlNode) | Out-Null;
        $controlDescriptions.AppendChild($controlDescription) | Out-Null;

        # Update form
        $updatedFormXml = $formXml.OuterXml;
        $updateRecord = New-XrmEntity -LogicalName "systemform";
        $updateRecord.Id = $FormReference.Id;
        $updateRecord["formxml"] = $updatedFormXml;

        $XrmClient | Update-XrmRecord -Record $updateRecord;

        if ($Publish) {
            Publish-XrmCustomizations;
        }
    }
    end {
        $StopWatch.Stop();
        Trace-XrmFunction -Name $MyInvocation.MyCommand.Name -Stage Stop -StopWatch $StopWatch;
    }
}

Export-ModuleMember -Function Add-XrmFormControl -Alias *;