May 22, 2025

Step-by-Step Guide: Create a PCF Control to Record Video in Power Apps

Introduction

In Power Apps, the default controls are often enough for typical business needs, but what if you need a more customized user interface or logic that goes beyond the built-in features? That’s where the PowerApps Component Framework (PCF) comes into play.

PCF enables developers to create custom components using modern web technologies like HTML, CSS, and TypeScript. These components can be reused just like standard Power Apps controls but offer much greater flexibility and power.
 
With PCF, you can build: 

  • Rich visual components like sliders, charts, or custom input fields
  • Controls that connect to external data sources
  • Reusable UI elements across different apps and environments

Whether you're enhancing user experience or integrating third-party libraries, PCF gives you the tools to take your apps beyond low-code.

In this blog, we’ll walk you through creating a custom PCF control that records video directly within a Power Apps, perfect for scenarios like field service reporting.
 
Prerequisites
Before you begin, ensure you have the following tools and knowledge:
  • Node.js (LTS version)
  • Power Platform CLI (pac)
  • Visual Studio Code
  • A Microsoft Power Apps Developer Environment

Use Case: Record Video in Power Apps
Imagine a field engineer needing to quickly document a machine fault. Instead of recording a video externally and uploading it later, a custom PCF control enables the engineer to record and save the video directly within the mobile app, streamlining the entire submission process.


Step-by-Step Guide to Creating the PCF Control

1. Create the PCF Control

  • Open Visual Studio Code and run:

pac pcf init --namespace VideoRecoder --name VideoRecoder --template field 

  • Then install the dependencies:

 npm install

 2.  Add Video Recording Logic (in index.ts)

  • Use the MediaRecorder API to access the camera and record video.

private async startRecording(): Promise<void> {
        this._errorElement.innerText = "";
        try {
            // Request back camera with facingMode: "environment"
            const stream = await navigator.mediaDevices.getUserMedia({
                video: { facingMode: "environment" },
                audio: true
            });
            this._videoElement.srcObject = stream;
            this._videoElement.play();

            this._mediaRecorder = new MediaRecorder(stream);
            this._recordedChunks = [];

            this._mediaRecorder.ondataavailable = (event) => {
                if (event.data.size > 0) {
                    this._recordedChunks.push(event.data);
                }
            };

            this._mediaRecorder.start();
            this._startButton.disabled = true;
            this._stopButton.disabled = false;
            this._uploadButton.disabled = true;
        } catch (error) {
            this._errorElement.innerText = "Failed to access camera/microphone. Please
ensure permissions are granted.";
            console.error("Recording error:", error);
        }
    }

    private stopRecording(): void {
        if (this._mediaRecorder && this._mediaRecorder.state !== "inactive") {
            this._mediaRecorder.stop();
            this._videoElement.srcObject = null;
            this._startButton.disabled = false;
            this._stopButton.disabled = true;
            this._uploadButton.disabled = false; // Enable upload button after stopping
        }
        console.log("from stopRecording() test");
    }


You can view the complete source code here.
  • Run the development server to test locally:
npm start

3. Build the PCF Control into a Solution
  • Create a new solution folder and initialize it:
mkdir VideoRecoderSolution
cd VideoRecoderSolution
pac solution init --publisher-name YourPublisher --publisher-prefix yourprefix
Then add the reference and build:
pac solution add-reference --path ..\..\
dotnet build
  • A .zip file will be created inside VideoRecoderSolution > bin > Debug

4. Import the Solution into Power Apps

  • Open your Power Apps portal
  • Navigate to Solutions

  • Click Import Solution and select the generated .zip file


  • Click Next > Import
Once imported
  • Go to your app
  • Click Insert > Get More Components

  • Click on code and select your custom component
  • Click Import, and it will be available under Code Components


Conclusion

With PCF, you unlock a whole new level of customization in Power Apps. In this tutorial, we created a practical video recording control - ideal for industries like field services, insurance, or any scenario where direct media input is valuable.


If you have any questions you can reach out our SharePoint Consulting team here.

Automate Azure Logic App Deployment with Azure DevOps CI/CD and ARM Templates – Step-by-Step Guide

Managing Azure Logic Apps in various environments can be a tedious and error-prone assignment when relying on manual deployments. Thankfully, utilizing Azure DevOps CI/CD pipelines with ARM templates allows for complete automation of Logic App deployments, removing the need for manual involvement and guaranteeing uniform infrastructure-as-code releases.

This blog walks you through setting up a complete automated deployment pipeline for Logic Apps, from exporting templates to running parameterized deployments using YAML pipelines.

Prerequisites:
Before we begin, make sure you have the following in place:

  • An existing Logic App in your Azure subscription.

  • An Azure DevOps project with permissions to create service connections and pipelines.

  • Basic familiarity with ARM templates and YAML


Step 1: Export the ARM Template of the Logic App
  • Go to the Azure Portal and navigate to your Logic App.

  • In the left pane, select Automation > Export template.

  • Click Download to save the template.json and parameters.json files.

These files define the infrastructure and configuration of your Logic App and are essential for reproducible deployments.


Step 2: Create a Service Connection in Azure DevOps
To allow Azure DevOps to deploy resources to Azure, you need a service connection:

  • Go to your Azure DevOps project > Project settings > Service connections.

  • Click New service connection > choose Azure Resource Manager.

  • Select Default, Identity Type > App registration (automatic).

  • Select Default, Credential > Workload Identity Federation.

  • Choose Scope Level > Subscription.

  • Choose your Azure subscription and resource group.

  • Name the service connection (e.g., AzureServiceConnection) and save.

 

Step 3: Create a Repository and Add Template Files
  • Create a new Git repository in Azure DevOps or use an existing one.

  • Commit the downloaded files template.json and parameters.json into the repository.

  • Arrange files within a specific folder structure when handling various Logic Apps or environments.

 

Step 4: Create a Variable Group (Optional but Recommended)
To support environment-specific deployments, create a variable group:

  • Go to Pipelines > Library in Azure DevOps.

  • Create a new variable group.

  • Add variables to replace parameters, connections, or static values, such as:
       
  1. LogicAppName

  2. Location

  3. SubscriptionId

  4. ResourceGroup

  • After creating the pipeline, grant it permission to access the variable group. Alternatively, you can configure the variable group to be accessible by all pipelines within the project by enabling the “Open Access” option.
     
  • Alternatively, you can define variables directly in your YAML pipeline using variables: if you want to keep everything within the pipeline file.

 

Step 5: Create a YAML Pipeline
Now, create a YAML pipeline to:

  • Replace parameters dynamically.

  • Deploy the Logic App using ARM templates.

  • Use variables for flexibility across environments.

Here’s a sample pipeline (azure-pipelines.yml).

trigger:
    branches:
        include:
        - main

stages:
  - stage: Build
    displayName: "Build And Deploy"
    jobs:
      - job: BuildLogicApp
        displayName: "Deploy Logic App to Azure"
        pool:
          vmImage: "windows-latest"

        variables:
          # Store the variables in the group for dynamic use
          - group: TestLogicApp-Variables

        steps:
          - checkout: self

          # Preprocess template.json to replace hardcoded values
          - task: PowerShell@2
            displayName: "Preprocess ARM Template"
            inputs:
              targetType: "inline"
              script: |

                $templatePath = "$(Build.SourcesDirectory)/template.json"

                $jsonRaw = Get-Content $templatePath -Raw | ConvertFrom-Json

                # Update LogicApp name parameter
                if ($jsonRaw.parameters.workflows_TestLogicApp_01_name) {
                    Write-Host "Updating Logic App name to: $(LogicAppName)"

                    $jsonRaw.parameters.workflows_TestLogicApp_01_name.defaultValue = "$(LogicAppName)"
                } else {
                    Write-Host "Error: workflows_TestLogicApp_01_name parameter not found!" -ForegroundColor Red
                    exit 1
                }

                # Update location parameter
                if ($jsonRaw.resources[0].location) {
                    Write-Host "Replacing WorkFlow location defaultValue with $(Location)"
                    $jsonRaw.resources[0].location = "$(Location)"
                } else {
                    Write-Host "Warning: Location parameter not found in resoures!" -ForegroundColor Yellow
                }

                # Update the connections properties
                if ($jsonRaw.resources[0].properties.parameters.'$connections') {
                    $connections = $jsonRaw.resources[0].properties.parameters.'$connections'.value
                    if ($connections.sharepointonline) {
                        # Replace subscriptionId in the id field
                        $connections.sharepointonline.id = $connections.sharepointonline.id -replace '/subscriptions//', "/subscriptions/$(SubscriptionId)/"
                        Write-Host "Replaced sharepointonline id subscriptionId: $(SubscriptionId)"
                        # Replace location in the id field
                        $connections.sharepointonline.id = $connections.sharepointonline.id -replace '/locations//', "/locations/$(Location)/"
                        Write-Host "Replaced sharepointonline id location: $(Location)"
                        # Update connectionName
                        $connections.sharepointonline.connectionName = "$(SharepointConnectionName)"
                        Write-Host "Replaced sharepointonline connectionName: $(SharepointConnectionName)"
                    }
                }
                              
                # Save the updated template
                $jsonString = $jsonRaw | ConvertTo-Json -Depth 100 -Compress
                $jsonString | Set-Content $templatePath -Encoding UTF8
                Write-Host "Updated template.json with all dynamic values saved to $templatePath"

          - task: PowerShell@2
            displayName: "Prepare Exported Templates Directory"
            inputs:
              targetType: "inline"
              script: |
                $exportPath = "$(Build.ArtifactStagingDirectory)/exportedTemplates

                # Copy template.json
                $templatePath = "$(Build.SourcesDirectory)/template.json"
                if (Test-Path -Path $templatePath) {
                    Copy-Item -Path $templatePath -Destination "$exportPath/template.json" -Force
                } else {
                    Write-Host "Warning: template.json not found in source directory"
                }

                # Copy parameters.json
                $parametersPath = "$(Build.SourcesDirectory)/parameters.json"
                if (Test-Path -Path $parametersPath) {
                    Copy-Item -Path $parametersPath -Destination "$exportPath/parameters.json" -Force
                } else {
                    Write-Host "Warning: parameters.json not found in source directory"
                }

                Write-Host "Exported templates prepared successfully."

          - task: PowerShell@2
            displayName: "Debug Exported Templates"
            inputs:
              targetType: "inline"
              script: |
                Get-Content "$(Build.ArtifactStagingDirectory)/exportedTemplates/template.json" | Write-Host
                Get-Content "$(Build.ArtifactStagingDirectory)/exportedTemplates/parameters.json" | Write-Host

          - task: PublishBuildArtifacts@1
            displayName: "Publish Exported Templates as Artifacts"
            inputs:
              PathtoPublish: "$(Build.ArtifactStagingDirectory)/exportedTemplates"
              ArtifactName: "LogicAppExportedTemplates"

          - task: DownloadBuildArtifacts@0
            displayName: "Download Published Artifacts"
            inputs:
              buildType: "current"
              downloadType: "single"
              artifactName: "LogicAppExportedTemplates"
              downloadPath: "$(Pipeline.Workspace)/ExportedTemplates"

          - task: PowerShell@2
            displayName: "Debug Downloaded Artifacts"
            inputs:
              targetType: "inline"
              script: |
                Get-ChildItem -Recurse "$(Pipeline.Workspace)/ExportedTemplates"
                Get-Content "$(Pipeline.Workspace)/ExportedTemplates/LogicAppExportedTemplates/template.json" | Write-Host
                Get-Content "$(Pipeline.Workspace)/ExportedTemplates/LogicAppExportedTemplates/parameters.json" | Write-Host

          - task: AzureResourceManagerTemplateDeployment@3
            displayName: "Deploy Logic App ARM Template"
            inputs:
              deploymentScope: "Resource Group"
              azureResourceManagerConnection: ""
              subscriptionId: ""
              resourceGroupName: ""
              location: ""
              templateLocation: "Linked artifact"
              csmFile: "$(Pipeline.Workspace)/ExportedTemplates/LogicAppExportedTemplates/template.json"
              csmParametersFile: "$(Pipeline.Workspace)/ExportedTemplates/LogicAppExportedTemplates/parameters.json"
              deploymentMode: "Incremental"
The provided YAML pipeline automates the deployment of a Logic App to Azure using a dynamic, reusable approach. Key features include,

  • Branch Trigger: The pipeline runs when changes are pushed to the main branch.

  • Variable Group Usage: A variable group (TestLogicApp-Variables) is linked to the pipeline to store dynamic values like LogicAppName, Location, SubscriptionId, etc. These are accessed using the $(varName) syntax for flexibility and easy updates without modifying the template.

  • ARM Template Preprocessing: The template.json is dynamlically updated using PowerShell to inject values from the variable group, replacing placeholders such as Logic App name, location, and connection settings.

  • Template Export and Debugging: The processed templates are saved, exported as build artifacts, and verified via debug steps to ensure correctness.

  • Deployment: The AzureResourceManagerTemplateDeployment@3 task deploys the Logic App ARM template to the specified Azure resource group using values from the variable group.

This modular and dynamic approach ensures reusability across environments and simplifies Logic App deployment directly from your DevOps workflow.

Step 6: Create and Run the Pipelines
  • Go to Pipelines > New Pipeline.

  • Select Azure Repos Git > choose your repo.

  • Select YAML and point to your YAML file.

  • Save and run the pipeline.

Your Logic App will now be deployed automatically with environment-specific values.


Final Thoughts
Utilizing ARM templates alongside Azure DevOps pipelines for Logic App deployments provides a secure, scalable, and repeatable method that aligns with contemporary DevOps methodologies. By adhering to the previously described steps, you can eradicate manual deployment mistakes, standardize your deployment process, and fully version control your Logic App infrastructure.

Furthermore, you can adjust various parameters and connection settings dynamically by utilizing PowerShell scripts in the pipeline, as shown in this blog. This method offers enhanced flexibility for deployments in diverse environments or for making real-time updates to configuration values.

If you have any questions you can reach out our SharePoint Consulting team here.

Understanding Low, Mid, and High-Fidelity Layouts in UI/UX Design

Introduction:

In the world of UI/UX design, every great product begins with a blueprint - a visual guide that brings ideas to life before a single line of code is written. These blueprints, often referred to as design layouts or wireframes, come in varying levels of detail: low-fidelity, mid-fidelity, and high-fidelity. Each plays a distinct role in the design process, from capturing rough ideas on paper to creating polished, interactive prototypes ready for development. In this blog, we’ll break down the characteristics, purposes, and tools of each layout type to help you understand when and why to use them in your design workflow.


1. Low-Fidelity Layout

Meaning:

  • Very simple, rough sketches.
  • Focus on structure and flow, not visuals.
  • Often black-and-white, with boxes, lines, and placeholder text like "Image Here" or "Button".

Purpose:

  • Quick exploration of ideas.
  • Getting early feedback.
  • No focus on branding, typography, or detailed design.

Tools used:

  • Paper and pen.
  • Basic digital tools like Balsamiq, Figma (wireframe mode), or Whimsical.

Looks like:

  • Hand-drawn or very basic shapes.
  • Labels instead of real images.
  • No colors, shadows, or textures.


2. Mid-Fidelity Layout

Meaning:

  • More detailed than low-fidelity.
  • Basic interactions and flow logic are shown.
  • Better typography and some design elements (like buttons, menus) but still no polished visuals.

Purpose:

  • Validate usability.
  • Start testing navigation and layout.
  • Communicate more clearly with developers or stakeholders.

Tools used:

  • Figma, Adobe XD, Sketch, or wireframe software with mid-level polish.

Looks like:

  • Clear sections.
  • Greyscale or minimal color.
  • Basic icons.
  • Some real text instead of placeholders.


3. High-Fidelity Layout

Meaning:

  • Pixel-perfect design.
  • Full visuals, real images, colors, typography, and UI components.
  • Shows final interactions, animations, and responsive behavior.

Purpose:

  • Final approval before development.
  • Handoff to developers.
  • Client presentations.

Tools used:

  • Figma, Adobe XD, Sketch, InVision, Framer.

Looks like:

  • Real branding.
  • Real images, polished fonts, shadows, gradients.
  • Complete interactivity (if prototyped).

Conclusion:

Designing a product isn’t a one-step process - it’s a journey from rough sketches to pixel-perfect interfaces. Low-fidelity layouts help you brainstorm and gather early feedback quickly, mid-fidelity layouts refine structure and usability, and high-fidelity layouts showcase the final vision ready for development. Understanding the differences between these fidelity levels enables clearer communication with stakeholders and a smoother design-to-development handoff. Whether you're just sketching out concepts or presenting to clients, choosing the right level of fidelity at the right stage is key to effective and efficient design.

If you have any questions you can reach out our SharePoint Consulting team here.