Microsoft PowerApps is getting more popular, with more and more companies and consultants building custom business applications for cross-platform usage. Sometimes the out of the box components might be too limiting, and you can use the PowerApps Component Framework to build custom components with JavaScript/React.

Although PowerApps is cross-platform, the PowerApps Component Framework can only be used on Windows. Generating new projects, generation new solutions, associating a project to a solution, developing the project code, packaging the solution to a zip: all limited to Windows. This is strange, inexplicable and disappointing when you look at how Microsoft is embracing Mac OS and Linux for their other initiatives...

I've been looking at ways to bring PCF to users on those other platforms, and I'd like to share my current workarounds!

Scaffolding the project files

All necessary project files are scaffolded through a CLI, similar to how SharePoint Framework uses Yeoman to generate the project structures.
Luckily there is the Office 365 CLI that started as a SharePoint Online PowerShell alternative but now also includes commands for Microsoft Graph, Microsoft Teams, SharePoint Framework (SPFx) etc.

Over the last 2 months I've submitted PR's  to the Office 365 CLI for the three most important commands from the PowerApps PCF CLI. These will allow users on Mac OS and Linux to simulate the Windows CLI.
The three main commands are

  • pa pcf init: creates a component project
  • pa solution init: creates a solution project, that bundles 1 or more component projects
  • pa solution reference add: adds a component project reference to a solution project

These commands are currently available in the beta version of Office 365 CLI (or still even just as a PR). The Windows/official version has some more commands that simplifies development but those aren't as easily ported cross-platform. If you are up to the task, the Office 365 CLI team is very open to new contributors 😉.

Developing the custom component

After you've scaffolded your component project (pa pcf init) you'll notice it is completely based on JavaScript, NodeJs and NPM packages. These are standards in modern web development so it should work on all platforms, right?
Not so much, as the packages developed by the team at Microsoft contains some pieces of code that are specific to Windows. These are so easy to make platform agnostic that you start wondering why they aren't doing this in the first place...

Remark: with every execution of npm i you'll have to redo these changes.

pcf-start: telemetry.js

Open up node_modules/pcf-start/generated/telemetry.js, find:

var localfileusersettings_1 = require("./localfileusersettings");

and add the following before it:

var os = require("os");

Look for:

var applicationPath = path.join(process.env.LOCALAPPDATA, 'Microsoft', 'PowerAppsCli');

and replace with:

var applicationPath = '';
if (os.type() === 'Darwin') {
    applicationPath = path.join(os.homedir(), 'Library', 'Preferences', 'Microsoft', 'PowerAppsCli');
}
else if (os.type() === 'Linux') {
    applicationPath = path.join(env.XDG_CONFIG_HOME || path.join(homedir, '.config'), 'Microsoft', 'PowerAppsCli')
}
else {
    applicationPath = path.join(process.env.LOCALAPPDATA, 'Microsoft', 'PowerAppsCli');
}

pcf-scripts: telemetry.js

Open up node_modules/pcf-scripts/generated/telemetry.js, find:

const localfileusersettings_1 = require("./localfileusersettings");

and add the following before it:

const os = require("os");

Look for:

const applicationPath = path.join(process.env.LOCALAPPDATA, 'Microsoft', 'PowerAppsCli');

and replace with:

let applicationPath = '';
if (os.type() === 'Darwin') {
    applicationPath = path.join(os.homedir(), 'Library', 'Preferences', 'Microsoft', 'PowerAppsCli');
}
else if (os.type() === 'Linux') {
    applicationPath = path.join(env.XDG_CONFIG_HOME || path.join(homedir, '.config'), 'Microsoft', 'PowerAppsCli')
}
else {
    applicationPath = path.join(process.env.LOCALAPPDATA, 'Microsoft', 'PowerAppsCli');
}

pcf-scripts: startTask.js

Open up node_modules/pcf-scripts/tasks/startTask.js, find:

const fs = require("fs");

and add the following before it:

const os = require("os");

Look for:

const pcfStartCmd = child_process_1.spawn('cmd', ['/c', `pcf-start ${this._watchOn ? '--watch' : ''} --codePath ${outputDir}`]);

and replace with:

const args = `pcf-start ${this._watchOn ? '--watch' : ''} --codePath ${outputDir}`;
let pcfStartCmd;
if (os.type() === 'Windows_NT') {
    pcfStartCmd = child_process_1.spawn('cmd', ['/c', args]);
}
else {
    pcfStartCmd = child_process_1.spawn('/bin/sh', ['-c', args]);
}

Packaging the solution

After your components are developed (pa pcf init), and you've added them all to a solution (pa solution init &  pa solution reference add), it is time to package them up into a zip file. This zip-file is then to be uploaded into your PowerPlatform environment to make the components available in your model-driven or canvas (preview) apps.

Unfortunately, the packaging is done through MSBuild or dotnet build and seems to contain something that only wants to execute on Windows. I'm not giving up yet to find a way to get this to work on Mac OS and Linux, but for now there is no other way than packaging it on Windows.
Now, if you don't do local packaging but use Azure DevOps or something, this last part shouldn't hinder your development team too much 😉.


PowerApps PCF Component Framework Mac OS Linux Windows