
· 6 min read
PowerPlatform: Source Code and Naming Tips
As the tech world evolves, so does the need to stay up-to-date with the latest trends and best practices. Ron Jeffries once said, “Code never lies,” and with the new Source Code Integration in Power Platform, I can’t help but agree. This addition gives us the ability to peek into the actual code behind our solutions, which is a game-changer.
I previously blogged about Naming conventions and DevOpts integration, and now with this new feature, I wanted to dive deeper. This post will focus on how you can create a DevOps pipeline that automates the process of checking the naming conventions for your apps. It builds on my previous post and adds new insights.
Setting Up Source Control Integration
With the native source control integration in Power Platform, exporting or unpacking the solution is no longer necessary. If you haven’t set up the source control integration yet, check out the Microsoft guide here: Power Platform Source Code Integration.
Building a pipeline
The first thing we need is a pipeline.yml that we will trigger on each commit. This pipeline will call our script, passing in the folder where the solution is extracted. Here’s an example:
trigger:
- main
pool:
vmImage: ubuntu-latest
steps:
- checkout: self #checkout source code so it can be used
- task: PowerShell@2 #run the powershell script
displayName: 'Blis Digital: Naming Validator'
inputs:
filePath: '$(Build.SourcesDirectory)/scripts/scoring-script.ps1'
arguments: '-folderName "PP/ContosoRealEstate" -runAsPipeline $true' #make sure to use the correct folder name
The naming conventions
First lets have a look at our naming conventions, the source is a namingconvention.json and looks like the following:
[
{
"Control": "3d object",
"Naming": "3do"
},
{
"Control": "add picture",
"Naming": "pic"
},
...
]
A full list of controls can be found in the Microsoft documentation, but luckily Elianne has provided a full sample.
The improved script
Next, let’s look at the script that will be triggered. The first step is to install the necessary module for handling YAML. The script will then loop through the solution and find all canvas apps, each of which has a canvasapp.yml file.
$canvasApps = Get-ChildItem -Path "$($folderName)\." -Filter canvasapp.yml -Recurse -File
The script will continue by processing each canvasapp.yml file, finding all screens (which are separate files) and recursively checking each element.
function Read-AppElements($element, $screenName) {
# Check for child elements, if so recursive loop through
if($element.Keys -eq "Children") {
if($enableDebug) { Write-output "Has child elements, recursive processing" }
foreach($child in $element.Children) {
Read-AppElements $child $screenName
}
}
else {
# do stuff
}
}
Check for best practices
We can also add checks for best practices, such as ensuring no OnStart functions are present in the apps. Here’s how we can do that:
if($sourceFile.Name -eq "App.pa.yaml") {
# Check the OnStart
if($null -ne $jsonObject.App.Properties.OnStart) {
$tempElements.Add([PSCustomObject]@{
Issue = [IssueType]::BestPractices
AppName = $canvasAppDetails.CanvasApp.DisplayName
Message = "OnStart should not contain functions, you should use the named formulas instead."
});
continue
}
}
Given that the structure of the solution is slightly different compared to using the PAC CLI the script contains a few other changes as well,
Here’s the final script that validates naming conventions and checks for best practices:
[CmdletBinding()]
param (
[Parameter(Mandatory)]
[string]$folderName,
[bool]$runAsPipeline=$false,
[bool]$enableDebug=$false
)
# Set of 'fixed' parameters
$requiredModule = "powershell-yaml"
$namingConventionsPath = "./scripts/namingconvention.json"
# Force Yaml module install due to pipeline being stateless
if (-not (Get-Module -ListAvailable -Name $requiredModule)) {
Write-Host "$requiredModule is not installed. Installing..."
Install-Module -Name $requiredModule -Force -Scope CurrentUser
} else {
Write-Host "$requiredModule is already installed."
}
enum IssueType {
ControlNaming
ScreenNaming
BestPractices
}
$tempElements = [System.Collections.Generic.List[object]]::new()
# Load the naming conventions
$table = Get-Content -Path $namingConventionsPath -Raw | ConvertFrom-Json
# Each CanvasApp has a canvasapp.yml file
write-Host $folderName
$canvasApps = Get-ChildItem -Path "$($folderName)\." -Filter canvasapp.yml -Recurse -File
function Read-AppElements($element, $screenName) {
# Check for child elements, if so recursive loop through
if($element.Keys -eq "Children") {
if($enableDebug) { Write-output "Has child elements, recursive processing" }
foreach($child in $element.Children) {
Read-AppElements $child $screenName
}
}
else {
foreach($key in $element.Keys) {
if($enableDebug) { Write-Host "Processing element: $($key), $($element[$key].Control), $($screenName)" }
$tempElements.Add([PSCustomObject]@{
Issue = [IssueType]::ControlNaming
AppName = $canvasAppDetails.CanvasApp.DisplayName
Screen = $screenName
Name = $key
Type = $element[$key].Control
});
if($null -ne $element[$key].Children) {
if($enableDebug) { Write-output "Has child elements, recursive processing" }
Read-AppElements $element[$key] $screenName
}
}
}
}
# Loop through all CanvasApps and get details
foreach ($canvasApp in $canvasApps) {
# Load App Details
$canvasAppDetailContent = Get-Content "$($canvasApp.FullName)" -Raw
$canvasAppDetails = ConvertFrom-Yaml -Yaml $canvasAppDetailContent
Write-Host "Found app" $canvasAppDetails.CanvasApp.DisplayName
# Load all source files
$sourceFiles = Get-ChildItem -Path "$($folderName)/$($canvasAppDetails.CanvasApp.DocumentUri.split('.')[0])/Src" -Filter *.yaml -Recurse -File
foreach ($sourceFile in $sourceFiles) {
if($enableDebug) { Write-output "$($sourceFile.FullName)" }
$fileContent = Get-Content "$($sourceFile.FullName)" -Raw
$jsonObject = ConvertFrom-Yaml -Yaml $fileContent
# Check for App best practices
if($sourceFile.Name -eq "App.pa.yaml") {
# Check the OnStart
if($null -ne $jsonObject.App.Properties.OnStart) {
$tempElements.Add([PSCustomObject]@{
Issue = [IssueType]::BestPractices
AppName = $canvasAppDetails.CanvasApp.DisplayName
Message = "OnStart should not contain functions, you should use the named formulas instead."
});
continue
}
}
# Loop through the screens and start processing
foreach($key in $jsonObject.Screens.Keys) {
Write-Host "Processing screen: $($key)"
Read-AppElements $jsonObject.Screens[$key] $key
}
}
}
## Loop through all elements and check if they are in the naming conventions
## Then handle the naming conventions itself
foreach ($item in $tempElements | Where-Object { $_.Issue -eq [IssueType]::BestPractices }) {
$message = "$($item.Message)"
$status = "Warning"
Write-Host "❌ " $message -ForeGroundColor Blue
if($runAsPipeline) { Write-Host "##vso[task.logissue type=warning;]" $message }
}
foreach ($item in $tempElements | Where-Object { $_.Issue -eq [IssueType]::ControlNaming }) {
$naming = $table | Where-Object { $_.Control -contains $item.Type.split('@')[0] } | Select-Object -ExpandProperty Naming
if ($naming -and $item.Name -cmatch "^$naming") {
$message = "Control '$($item.Name)' matches type '$($item.Type)' with naming convention '$($naming)'."
$status = "Correct"
Write-Host "✅ " $message
}
elseif ($item.Type -eq "screen" -and $item.Name -cmatch 'Screen$') {
$message = "Screen $($item.Name) matches naming convention."
$status = "Correct"
Write-Host "✅ " $message
}
elseif ($item.Type -eq "screen" -and $item.Name -notmatch 'Screen$') {
$message = "Screen $($item.Name) doesn't match naming convention."
$status = "Error"
Write-Host "❌ " $message -ForeGroundColor Blue
if($runAsPipeline) { Write-Host "##vso[task.logissue type=warning;]" $message }
}
else {
$message = "Control '$($item.Name)' with type '$($item.Type)' does not match naming convention, Name should start with '$($naming)' on Screen named '$($item.Screen)' $(if ( $item.Path -ne '') { "in element '$($item.Path)'"})"
$status = "Error"
Write-Host "❌ " $message -ForegroundColor Blue
if($runAsPipeline) { Write-Host "##vso[task.logissue type=warning;]" $message }
}
}
Conclusion
Integrating naming convention checks into your DevOps pipeline for Power Platform solutions ensures consistency across the board. By automating this process, you can catch issues early and maintain a high level of governance in your development process. With the new source code integration feature in Power Platform, this becomes even more seamless.

Albert-Jan Schot
CTO, Microsoft MVP & FastTrack Recognized Solution Architect
I am Albert-Jan Schot, CTO at Blis Digital, Microsoft MVP, and FastTrack Recognized Solution Architect focused on Microsoft 365, Azure, and AI agents. I help teams turn complex Microsoft Cloud challenges into practical architecture decisions and shipped outcomes.
Zuid Holland, Netherlands


