Dyota's blog

PowerShell: Extract Power Apps code into text files

Recently, I wrote a script that takes a .zip export of a Power App, cracks it open, and pulls out all of the code in individual text files.

For me, this script is like a dream partially come true (the full dream is when I can edit Power Apps live in VS Code, but that's another story). For the longest time, I have been writing Power Apps code in VS Code and copying it onto the formula bar, or vice versa.

Why? Well, the Power Apps formula bar used to be pretty crappy. It would literally forget what I typed - nevermind the crappy formatting options. I needed to write my code on Notepad or similar to be sure that what I wrote will stay. These days, the formula bar isn't so bad but the editing experience in VS Code is much much nicer.

Having all of my code also means that I can do a global search through the whole project for certain words. This comes in handy when, for example, I need to rename a global variable, everywhere. If I just used Power Apps, I can't even be sure that I've gone through every instance, let alone make the changes efficiently.

It turns out, the script is not that hard to do.

Everything is below.

Note the recursive function Get-Children. It scans through an object tree and for every node in the tree, if it has children, creates a folder for each child.

#region Introduction
Write-Host This tool will extract all of the Power Apps code into individual text files. 
Write-Host Start by having an .zip export package of the Power App. Make sure it is the only .zip folder in this location. 
#endregion

#region navigate through folder structure
$root = Read-Host Where is your Power Apps package located? `(Give the parent folder directory`)

$zip = (Get-ChildItem $root *.zip)[0]
$packageName = ($zip.Name -split "\.")[0]

Write-Host This is your zip folder: $packageName

if (!(Test-Path $root\$packageName)) {
    
    [void](New-Item -Path $root\$packageName -ItemType Directory)
    
    Write-Host Unzipping the package...
    
    Expand-Archive -Path $zip.name -DestinationPath $root\$packageName
}
else {
    Write-Host This folder already exists, assuming it has already been unzipped...
}

Write-Host Going into PowerApp
$apps = "$root\$packageName\Microsoft.PowerApps\apps"

Write-Host Going into the first folder

$firstFolder = (Get-ChildItem $apps)[0]

$msapp = (Get-ChildItem $firstFolder.FullName *.msapp)[0]

$msappName = ($msapp.Name -split "\.")[0]

Write-Host The code lives in this .msapp folder: $msapp

$destination = (Read-Host Where do you want the code files to go?) + "\Code"

Write-Host Getting .msapp package and extracting contents

Rename-Item -Path $msapp.FullName -NewName $([System.IO.Path]::ChangeExtension($msapp.Name, ".zip"))

$codeZip = (Get-ChildItem $firstFolder.FullName *.zip)[0]

Expand-Archive -Path $codeZip.FullName -DestinationPath "$($firstFolder.FullName)\$msappName"

$controls = "$($firstFolder.FullName)\$($msappName)\Controls"

$ext = Read-Host What file extension do you want the files to be? `(txt, js, c, etc`)

$justCommented = ($host.UI.PromptForChoice(
    'Just commented properties or everything?' , 
    'Pulling out everything can result in a large number of very small files. Pulling out just the code with comment marks in them will trim this down significantly.' , 
    [ChoiceDescription[]] @("Just &commented properties", "&All properties"), 
    0
) -eq 0)

#endregion

#region recursive function
function Get-Children ($node, $parent, $dir) {
    Write-Host This object is $node.name
    
    Write-Host $dir\$($node.name)
    
    if (!(Test-Path "$dir\$($node.name)")) {
        Write-Host Creating folder $dir\$($node.name)...
        [void](New-Item -Path $dir\$($node.name) -ItemType Directory)
    }
    
    $properties = $node.Rules

    $properties | 
        ForEach-Object {
            $content = $_.InvariantScript
            
            $codeCommented = $content.Contains("//") -or $content.Contains("/*")
            
            if (!(!$codeCommented -and $justCommented)) {
                Write-Host Creating file "$dir\$($node.name)\$($_.Property).$ext"
    
                [void](New-Item -Path "$dir\$($node.name)\$($_.Property).$ext" -Value $_.InvariantScript)
            }
        }

    $children = $node.children
    $count = $children.Count
    Write-Host It has $count children

    if ($count -gt 0) {
        $children | 
            ForEach-Object {
                $nextDir = "$dir\$($node.name)"
                Get-Children $_ $node.name $nextDir
                Write-Host
            }
    }
}
#endregion

#region go through all controls and call Get-Children
(Get-ChildItem $controls *.json) | 
    ForEach-Object {
        $tree = (Get-Content $_.FullName | ConvertFrom-Json).TopParent
            
        [void](Get-Children $tree $null $destination)        
    }
#endregion

#powerapps #powershell