{"id":5517,"date":"2018-04-17T09:37:07","date_gmt":"2018-04-17T16:37:07","guid":{"rendered":"\/developerblog\/?p=5517"},"modified":"2020-03-14T17:00:34","modified_gmt":"2020-03-15T00:00:34","slug":"packaging-electron-app-managed-distribution-across-devices","status":"publish","type":"post","link":"https:\/\/devblogs.microsoft.com\/ise\/packaging-electron-app-managed-distribution-across-devices\/","title":{"rendered":"Packaging an Electron app for managed distribution across devices"},"content":{"rendered":"<h2>Introduction<\/h2>\n<p>A customer we were recently collaborating with came to us with an interesting problem regarding their cross platform codebase and managed distribution. We learned that they were planning to build an <a href=\"https:\/\/electronjs.org\/\">Electron<\/a>-based application and needed help from Microsoft to understand how to build, deploy and manage the application on Windows using an MDM solution (eg. <a href=\"https:\/\/www.microsoft.com\/en-us\/cloud-platform\/microsoft-intune\">Microsoft Intune<\/a>).\u00a0<a href=\"https:\/\/docs.microsoft.com\/en-us\/intune\/introduction-intune\">Microsoft Intune<\/a> is a cloud service that provides mobile device management and mobile application management capabilities. Together with the customer, Microsoft engineers built a sample electron application that could be opened in Kiosk mode and could be deployed through Microsoft Intune.<\/p>\n<p>In this code story, we&#8217;ll explore the following aspects of our solution:<\/p>\n<ul>\n<li>Packaging Electron source into Windows binaries<\/li>\n<li>Setting the app in &#8216;Kiosk&#8217; mode using PowerShell<\/li>\n<li>Building a Configurable Windows installer (<em>.MSI<\/em>) using WiX Toolset<\/li>\n<li>Cross-platform CI with AppVeyor and Travis<\/li>\n<\/ul>\n<p>In order to focus on these aspects, we created a <a href=\"https:\/\/github.com\/syedhassaanahmed\/kiosk-demo-electron\">sample Electron app<\/a> and iterated on it.<\/p>\n<p><!--more--><\/p>\n<h2>Windows Binaries<\/h2>\n<p>In perhaps the most straightforward part of our journey, we used the Electron Packager CLI tool to create OS-specific bundles from an Electron app.<\/p>\n<pre class=\"theme:shell-default lang:sh decode:true\">electron-packager .\/src --platform=win32 --arch=x64 --asar --overwrite --out .\/dist<\/pre>\n<h2>Kiosk Mode<\/h2>\n<p>Windows 10 Enterprise provides multiple ways to run an app in kiosk mode:<\/p>\n<ul>\n<li><a href=\"https:\/\/docs.microsoft.com\/en-us\/windows-hardware\/drivers\/partnerapps\/create-a-kiosk-app-for-assigned-access\">Assigned Access<\/a> method, which allows a single Universal Windows Platform (UWP) app to run in kiosk mode.<\/li>\n<li><a href=\"https:\/\/docs.microsoft.com\/en-us\/windows-hardware\/customize\/enterprise\/shell-launcher\">Shell Launcher<\/a> method, which allows a single classic Windows Application (e.g Electron app) to run in kiosk mode.<\/li>\n<\/ul>\n<p>Intune provides a built-in <a href=\"https:\/\/docs.microsoft.com\/en-us\/windows\/client-management\/mdm\/assignedaccess-csp\">Configuration Service Provider<\/a> to remote-enable Assigned Access. It is as simple as providing a JSON with domain\/local username and the UWP Application ID in\u00a0 Azure Portal.<\/p>\n<pre class=\"lang:js decode:true\">{\"Account\":\"contoso\\\\kioskuser\",\"AUMID\":\"Microsoft.Windows.Contoso_cw5n1h2txyewy!Microsoft.ContosoApp.ContosoApp\"}<\/pre>\n<p>Since Electron is a classic Windows app, we chose Shell Launcher as our way forward.<\/p>\n<h2>Shell Launcher<\/h2>\n<p>Because Intune does not provide a direct way to remote-enable Shell Launcher, we had to use an elevated PowerShell script. The script takes the local\/domain username as well as the\u00a0full path of the Windows executable (.exe) produced in the previous step as parameters.<\/p>\n<p>We turned on the Shell Launcher feature in Windows 10 (<em>Programs and Features -&gt; Turn Windows features on or off -&gt; Expand &#8216;Device Lockdown&#8217; -&gt;\u00a0Select Shell Launcher<\/em>). Below is the code to do the same in PowerShell:<\/p>\n<pre class=\"theme:powershell lang:ps decode:true\">Enable-WindowsOptionalFeature -online -FeatureName Client-EmbeddedShellLauncher -all -NoRestart<\/pre>\n<p>Then we created a Shell Launcher Object as provided by Windows Management Instrumentation (WMI).<\/p>\n<pre class=\"theme:powershell lang:ps decode:true\">$ShellLauncherClass = [wmiclass]\"\\\\localhost\\root\\standardcimv2\\embedded:WESL_UserSetting\"<\/pre>\n<p>In order to set a custom shell, we had to first find out the Security Identifier (SID) of the Windows username. We wrote a helper function for this:<\/p>\n<pre class=\"theme:powershell lang:ps decode:true\">function Get-UsernameSID($AccountName) {\r\n        $NTUserObject = New-Object System.Security.Principal.NTAccount($AccountName)\r\n        $NTUserSID = $NTUserObject.Translate([System.Security.Principal.SecurityIdentifier])\r\n        return $NTUserSID.Value\r\n    }<\/pre>\n<p>Finally, we set the custom shell for the above SID and full application path:<\/p>\n<pre class=\"theme:powershell lang:ps decode:true\"># Last parameter is Default Action\r\n# 0 - Restarts the shell application\r\n# 1 - Restarts the device\r\n# 2 - Shuts down the device\r\n$ShellLauncherClass.SetCustomShell($Cashier_SID, $ExeName, ($null), ($null), 0)<\/pre>\n<p>For debugging purposes we also print modified shell settings:<\/p>\n<pre class=\"theme:powershell lang:ps decode:true\">$shellSetting = Get-WmiObject -namespace \"root\\standardcimv2\\embedded\" `\r\n    -computer \"localhost\" -class WESL_UserSetting | Select-Object Shell, Sid\r\n\r\nWrite-Host $shellSetting<\/pre>\n<p>If ShellLauncher was successfully updated, the output of this command would be:<\/p>\n<pre class=\"theme:powershell lang:ps decode:true\">Shell                                                    Sid\r\n-----                                                    ---\r\nC:\\kiosk-demo-electron-win32-x64\\kiosk-demo-electron.exe S-1-5-21-XXXXXXXXXX-XXXXXXXXX-XXXXXXXXXX-XXXXXXX<\/pre>\n<h2>Windows Auto-Logon<\/h2>\n<p>To provide a full kiosk experience, we decided to auto-login the kiosk user. For this step, we added another parameter <em>$Password<\/em> to the PowerShell Script and modify Windows Registry.<\/p>\n<pre class=\"theme:powershell lang:ps decode:true\">$RegPath = \"HKLM:\\SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Winlogon\"\r\nSet-ItemProperty $RegPath \"AutoAdminLogon\" -Value \"1\" -type String\r\nSet-ItemProperty $RegPath \"DefaultUsername\" -Value \"$UserName\" -type String\r\nSet-ItemProperty $RegPath \"DefaultPassword\" -Value \"$Password\" -type String<\/pre>\n<h2>Windows Installer<\/h2>\n<p>For building Windows Installers for Electron apps, the most popular choice is the Squirrel-based <a href=\"https:\/\/github.com\/electron\/windows-installer\">electron-winstaller<\/a> npm module. However, electron-winstaller does not allow custom parameters for the produced installer or executing elevated PowerShell; as a result, we chose to use\u00a0\u00a0<a href=\"http:\/\/wixtoolset.org\/documentation\/\">WiX Toolset<\/a>\u00a0(specifically, we chose the\u00a0npm module <a href=\"https:\/\/github.com\/fabvla\/node-wixtoolset-compiler\"><em>wixtoolset-compiler<\/em><\/a>). When executed normally, the installer prompts for Windows elevation; however, apps distributed with Intune are installed without interruption via the\u00a0<em>SYSTEM<\/em> user, which keeps the elevation prompt silent.<\/p>\n<h2>WiX Toolset<\/h2>\n<p>Building a Windows installer with WiX is a multi-step process. The first step is to configure it with an XML file (<em>.wxs<\/em>). We have provided a base config file <em>product.wxs\u00a0<\/em>that contains app name, version, author, a unique upgrade code, installer parameters with default values, as well as custom actions that execute our PowerShell script.<\/p>\n<pre class=\"lang:xhtml decode:true\">&lt;?xml version=\"1.0\" encoding=\"UTF-8\"?&gt;\r\n&lt;Wix xmlns=\"http:\/\/schemas.microsoft.com\/wix\/2006\/wi\" xmlns:util=\"http:\/\/schemas.microsoft.com\/wix\/UtilExtension\"&gt;\r\n  &lt;Product Id=\"*\" Name=\"Kiosk Demo Electron\" Language=\"1033\" Version=\"1.0.0.0\" Manufacturer=\"Syed Hassaan Ahmed\" UpgradeCode=\"a8d48e7b-86a0-4171-8db8-6fb3618dfdc6\"&gt;\r\n    &lt;Package InstallerVersion=\"500\" Compressed=\"yes\" InstallScope=\"perMachine\" \/&gt;\r\n    &lt;MajorUpgrade Schedule=\"afterInstallInitialize\" AllowDowngrades=\"no\" DowngradeErrorMessage=\"ok\" AllowSameVersionUpgrades=\"yes\" \/&gt;\r\n    &lt;MediaTemplate EmbedCab=\"yes\" \/&gt;\r\n    &lt;!-- Params passed to PowerShell script --&gt;\r\n    &lt;Property Id=\"KIOSK_USERNAME\"&gt;kioskuser&lt;\/Property&gt;\r\n    &lt;Property Id=\"KIOSK_PASSWORD\"&gt;kioskpassword&lt;\/Property&gt;\r\n    &lt;Property Id=\"EXE_NAME\"&gt;kiosk-demo-electron.exe&lt;\/Property&gt;\r\n    &lt;Directory Id=\"TARGETDIR\" Name=\"SourceDir\"&gt;\r\n      &lt;Directory Id=\"ProgramFilesFolder\"&gt;\r\n        &lt;Directory Id=\"APPLICATIONROOTDIRECTORY\" Name=\"Kiosk Demo Electron\"\/&gt;\r\n      &lt;\/Directory&gt;\r\n    &lt;\/Directory&gt;\r\n    &lt;Feature Id=\"MainApplication\" Title=\"Main Application\" Level=\"1\"&gt;\r\n      &lt;ComponentRef Id=\"InstallShellLauncher\" \/&gt;\r\n      &lt;ComponentGroupRef Id=\"ElectronBinaries\" \/&gt;\r\n    &lt;\/Feature&gt;\r\n    &lt;!-- Install Shell Launcher --&gt;\r\n    &lt;CustomAction Id=\"InstallShellLauncher\"\r\n                  Property=\"InvokeInstall\"\r\n                  Value=\"&amp;quot;powershell&amp;quot; -NoProfile -NonInteractive -InputFormat None -ExecutionPolicy Bypass -File &amp;quot;[APPLICATIONROOTDIRECTORY]Install-ShellLauncher.ps1&amp;quot; &amp;quot;[KIOSK_USERNAME]&amp;quot; &amp;quot;[KIOSK_PASSWORD]&amp;quot; &amp;quot;[APPLICATIONROOTDIRECTORY][EXE_NAME]&amp;quot;\"\r\n                  Execute=\"immediate\"\/&gt;\r\n    &lt;CustomAction Id=\"InvokeInstall\"\r\n                  BinaryKey=\"WixCA\"\r\n                  DllEntry=\"CAQuietExec64\"\r\n                  Execute=\"deferred\"\r\n                  Return=\"check\"\r\n                  Impersonate=\"no\" \/&gt;\r\n    &lt;InstallExecuteSequence&gt;\r\n      &lt;Custom Action=\"InstallShellLauncher\" After=\"CostFinalize\"&gt;NOT Installed&lt;\/Custom&gt;\r\n      &lt;Custom Action=\"InvokeInstall\" After=\"InstallFiles\"&gt;NOT Installed&lt;\/Custom&gt;\r\n    &lt;\/InstallExecuteSequence&gt;\r\n  &lt;\/Product&gt;\r\n&lt;\/Wix&gt;<\/pre>\n<p>Then we execute WiX steps in this order:<\/p>\n<h3>1) Heat<\/h3>\n<p><a href=\"http:\/\/wixtoolset.org\/documentation\/manual\/v3\/overview\/heat.html\">Heat<\/a> is a binary harvester that scans through folders or <em>.NET<\/em> solutions (<em>.sln<\/em>) for Windows binaries and generates an XML output with a\u00a0list of files.<\/p>\n<pre class=\"theme:shell-default lang:sh decode:true\">heat.exe dir \"&lt;PATH_TO_ELECTRON_BINARIES&gt;\" -v -ag -cg ElectronBinaries -var var.SourceDir -dr APPLICATIONROOTDIRECTORY -srd -suid -sfrag -sreg -out .\/dist\/heat.wxs<\/pre>\n<p>Here is a part of the generated <em>heat.wxs<\/em> output:<\/p>\n<pre class=\"lang:xhtml decode:true\">&lt;?xml version=\"1.0\" encoding=\"utf-8\"?&gt;\r\n&lt;Wix xmlns=\"http:\/\/schemas.microsoft.com\/wix\/2006\/wi\"&gt;\r\n    &lt;Fragment&gt;\r\n        &lt;DirectoryRef Id=\"APPLICATIONROOTDIRECTORY\"&gt;\r\n            &lt;Component Id=\"kiosk_demo_electron.exe\" Guid=\"*\"&gt;\r\n                &lt;File Id=\"kiosk_demo_electron.exe\" KeyPath=\"yes\" Source=\"$(var.SourceDir)\\kiosk-demo-electron.exe\" \/&gt;\r\n            &lt;\/Component&gt;\r\n            &lt;Component Id=\"node.dll\" Guid=\"*\"&gt;\r\n                &lt;File Id=\"node.dll\" KeyPath=\"yes\" Source=\"$(var.SourceDir)\\node.dll\" \/&gt;\r\n            &lt;\/Component&gt;\r\n            &lt;Component&gt;...Other Chromium binaries...&lt;\/Component&gt;\r\n        &lt;\/DirectoryRef&gt;\r\n    &lt;\/Fragment&gt;\r\n    &lt;Fragment&gt;\r\n        &lt;ComponentGroup Id=\"ElectronBinaries\"&gt;\r\n            &lt;ComponentRef Id=\"kiosk_demo_electron.exe\" \/&gt;\r\n            &lt;ComponentRef Id=\"node.dll\" \/&gt;\r\n            &lt;ComponentRef Id=\"...Other Chromium binaries...\" \/&gt;\r\n        &lt;\/ComponentGroup&gt;\r\n    &lt;\/Fragment&gt;\r\n&lt;\/Wix&gt;<\/pre>\n<h3>2) Candle<\/h3>\n<p><a href=\"http:\/\/wixtoolset.org\/documentation\/manual\/v3\/overview\/candle.html\">Candle<\/a>\u00a0pre-processes\u00a0<em>.wxs<\/em> files and generates compiled <em>.wixobj<\/em> files. We passed the above <em>.wxs<\/em> files to Candle.<\/p>\n<pre class=\"theme:shell-default lang:sh decode:true\">candle.exe -v -ext WixUtilExtension -dSourceDir=\"&lt;PATH_TO_ELECTRON_BINARIES&gt;\" .\/tools\/product.wxs .\/dist\/heat.wxs -out .\/dist\/<\/pre>\n<h3>3) Light<\/h3>\n<p>The<a href=\"http:\/\/wixtoolset.org\/documentation\/manual\/v3\/overview\/light.html\"> Light<\/a> tool processes the previously mentioned\u00a0<em>.wixobj<\/em> files and produces the final installer (<em>.MSI<\/em>).<\/p>\n<pre class=\"theme:shell-default lang:sh decode:true\">light.exe -v -ext WixUtilExtension .\/dist\/product.wixobj .\/dist\/heat.wixobj -out .\/dist\/KioskDemoElectron.msi<\/pre>\n<p>The previous WiX steps were wrapped inside npm scripts. Here is how the <em>package.json\u00a0<\/em>looks:<\/p>\n<pre class=\"lang:js decode:true\">\"config\": {\r\n    \"binaries\": \".\/dist\/kiosk-demo-electron-win32-x64\"\r\n  },\r\n\"scripts\": {\r\n    \"clean\": \"rimraf .\/dist\",\r\n    \"pack\": \"cross-env DEBUG=electron-packager electron-packager .\/src --platform=win32 --arch=x64 --asar --overwrite --out .\/dist\",\r\n    \"heat\": \"wixtoolset-compiler heat --args=\\\"dir %npm_package_config_binaries% -v -ag -cg ElectronBinaries -var var.SourceDir -dr APPLICATIONROOTDIRECTORY -srd -suid -sfrag -sreg -out .\/dist\/heat.wxs\\\"\",\r\n    \"copyScripts\": \"cross-conf-env copyfiles -f .\/tools\/scripts\/*.* npm_package_config_binaries\",\r\n    \"candle\": \"wixtoolset-compiler candle --args=\\\"-v -ext WixUtilExtension -dSourceDir=%npm_package_config_binaries% .\/tools\/product.wxs .\/dist\/heat.wxs -out .\/dist\/\\\"\",\r\n    \"light\": \"wixtoolset-compiler light --args=\\\"-v -ext WixUtilExtension .\/dist\/product.wixobj .\/dist\/heat.wixobj -out .\/dist\/KioskDemoElectron.msi\\\"\",\r\n    \"build\": \"run-s heat copyScripts candle light\",\r\n    \"dist\": \"run-s clean pack build\"\r\n  },\r\n\"devDependencies\": {\r\n    \"copyfiles\": \"^1.2.0\",\r\n    \"cross-conf-env\": \"^1.1.2\",\r\n    \"cross-env\": \"^5.0.5\",\r\n    \"electron\": \"^1.7.8\",\r\n    \"electron-packager\": \"^9.1.0\",\r\n    \"npm-run-all\": \"^4.1.1\",\r\n    \"rimraf\": \"^2.6.2\",\r\n    \"wixtoolset-compiler\": \"^1.0.3\"\r\n  }<\/pre>\n<p>Next, generate the installer in the\u00a0<em>\/dist<\/em> folder:<\/p>\n<pre class=\"theme:shell-default lang:sh decode:true\">npm run dist<\/pre>\n<p>The installer can simply be invoked by:<\/p>\n<pre class=\"theme:shell-default lang:sh decode:true\">KioskDemoElectron.msi KIOSK_USERNAME=&lt;KIOSK_USERNAME&gt; KIOSK_PASSWORD=&lt;KIOSK_PASSWORD&gt;<\/pre>\n<p><figure id=\"attachment_5539\" aria-labelledby=\"figcaption_attachment_5539\" class=\"wp-caption aligncenter\" ><img decoding=\"async\" src=\"https:\/\/devblogs.microsoft.com\/cse\/wp-content\/uploads\/sites\/55\/2018\/04\/Featured.png\" alt=\"Image Featured\" width=\"450\" height=\"300\" class=\"aligncenter size-full wp-image-10771\" srcset=\"https:\/\/devblogs.microsoft.com\/ise\/wp-content\/uploads\/sites\/55\/2018\/04\/Featured.png 450w, https:\/\/devblogs.microsoft.com\/ise\/wp-content\/uploads\/sites\/55\/2018\/04\/Featured-300x200.png 300w\" sizes=\"(max-width: 450px) 100vw, 450px\" \/><figcaption id=\"figcaption_attachment_5539\" class=\"wp-caption-text\"><\/p>\n<p><img decoding=\"async\" src=\"https:\/\/devblogs.microsoft.com\/cse\/wp-content\/uploads\/sites\/55\/2018\/04\/intune-1024x511-1.png\" alt=\"Image intune 1024 215 511\" width=\"1024\" height=\"511\" class=\"aligncenter size-full wp-image-10772\" srcset=\"https:\/\/devblogs.microsoft.com\/ise\/wp-content\/uploads\/sites\/55\/2018\/04\/intune-1024x511-1.png 1024w, https:\/\/devblogs.microsoft.com\/ise\/wp-content\/uploads\/sites\/55\/2018\/04\/intune-1024x511-1-300x150.png 300w, https:\/\/devblogs.microsoft.com\/ise\/wp-content\/uploads\/sites\/55\/2018\/04\/intune-1024x511-1-768x383.png 768w\" sizes=\"(max-width: 1024px) 100vw, 1024px\" \/>Distributing the generated .msi with Intune in the Azure Portal<\/figcaption><\/figure><\/p>\n<h2>Continuous Integration<\/h2>\n<h2>AppVeyor<\/h2>\n<p>We wanted to make sure we had a way to verify that changes in Electron app or PowerShell script continue to produce an installer that correctly sets the app in kiosk mode, so we used <a href=\"https:\/\/www.appveyor.com\/\">AppVeyor<\/a> CI to build and execute the installer on AppVeyor&#8217;s Windows build agent as well as produce an installation log file.<\/p>\n<p>Here is the <em>appveyor.yml<\/em>:<\/p>\n<pre class=\"lang:yaml decode:true\">image: Visual Studio 2017\r\ninstall:\r\n  - ps: Install-Product node 9\r\n  - npm install npm@latest -g\r\n  - npm install\r\nbuild_script:\r\n - npm run dist\r\n - cd dist\r\n - msiexec \/i \"KioskDemoElectron.msi\" \/l*v \"install.log\" KIOSK_USERNAME=%username%\r\nartifacts:\r\n  - path: .\\dist\\*.msi\r\n  - path: .\\dist\\*.log<\/pre>\n<h2>Travis CI<\/h2>\n<p>Building Windows installers on a Linux environment is uncommon, and we ran into a few challenges.<\/p>\n<ul>\n<li>WiX Toolset is a .NET application.\u00a0 We\u00a0used\u00a0<a href=\"https:\/\/wiki.winehq.org\/Winetricks\">Winetricks<\/a> to install .NET Framework 4.0.<\/li>\n<li>If the full path of any of the electron-packager output files were longer than 128 chars, we ran into\u00a0<a href=\"https:\/\/github.com\/wixtoolset\/issues\/issues\/5314#issuecomment-329188877\">error LGHT0103 : The system cannot find the file<\/a>.<\/li>\n<li>Light tool has a <a href=\"https:\/\/appdb.winehq.org\/objectManager.php?sClass=version&amp;iId=16248&amp;iTestingId=39182\">known issue with MSI validation on Wine<\/a> so it had to be turned off with <em>-sval<\/em> flag.<\/li>\n<\/ul>\n<p>In order to isolate Wine and Winetricks setup, we used an <a href=\"https:\/\/hub.docker.com\/r\/syedhassaanahmed\/wix-node\/~\/dockerfile\/\">existing Linux Docker image<\/a>\u00a0which contained WiX Toolset, Wine and Node.js, to create our app&#8217;s Docker image.<\/p>\n<pre class=\"lang:default decode:true\">FROM syedhassaanahmed\/wix-node\r\nRUN mkdir \/home\/wix\/src\r\nWORKDIR \/home\/wix\/src\r\nCOPY . .\r\nRUN npm install\r\nRUN npm run dist:wine<\/pre>\n<p>We then added these Docker commands to npm scripts which build the image, create the container and copy the artifacts from the container to host machine.<\/p>\n<pre class=\"lang:js decode:true\">{\r\n    \"docker:build\": \"cross-conf-env docker build -t npm_package_name .\",\r\n    \"docker:create\": \"cross-conf-env docker create --name npm_package_name npm_package_name\",\r\n    \"docker:copy\": \"cross-conf-env docker cp npm_package_name:\/home\/wix\/src\/dist\/ .\/dist\",\r\n    \"docker:remove\": \"cross-conf-env docker rm -f npm_package_name\",\r\n    \"dist:docker\": \"run-s docker:build docker:create clean docker:copy docker:remove\"\r\n}<\/pre>\n<p>Here is the final Travis CI configuration:<\/p>\n<pre class=\"lang:yaml decode:true\">language: node_js\r\nnode_js:\r\n  - \"9\"\r\nservices:\r\n  - docker\r\nscript:\r\n  - npm run dist:docker\r\nnotifications:\r\n  email: false<\/pre>\n<h2>Conclusion<\/h2>\n<p>Through our collaboration with the customer, we were able to build a simple Electron-based app that we could deploy to a broad range of devices using Microsoft Intune.\u00a0 While we used a simple Electron app here, you can easily use the scripts and code that we developed here for other projects where you want to manage and customize the deployment of Electron apps.<\/p>\n<h2>Resources<\/h2>\n<ul>\n<li><a href=\"https:\/\/github.com\/syedhassaanahmed\/kiosk-demo-electron\">Sample Project repo<\/a><\/li>\n<li><a href=\"https:\/\/github.com\/syedhassaanahmed\/wix-node-container\">WiX Toolset Docker repo<\/a><\/li>\n<\/ul>\n<p>You can reach out to us with feedback and questions in the comments below.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>We developed an Electron-based app using Microsoft Intune cloud service for management and distribution across a broad range of devices.<\/p>\n","protected":false},"author":21412,"featured_media":10771,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"footnotes":""},"categories":[15,16,17],"tags":[49,156,165,214,243,264,277,295,362,387,390,391],"class_list":["post-5517","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-containers","category-devops","category-frameworks","tag-appveyor","tag-docker","tag-electron","tag-intune","tag-mdm","tag-msi","tag-npm","tag-powershell","tag-travis-ci","tag-windows-10-iot-enterprise","tag-windows-registry","tag-wix-toolset"],"acf":[],"blog_post_summary":"<p>We developed an Electron-based app using Microsoft Intune cloud service for management and distribution across a broad range of devices.<\/p>\n","_links":{"self":[{"href":"https:\/\/devblogs.microsoft.com\/ise\/wp-json\/wp\/v2\/posts\/5517","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/devblogs.microsoft.com\/ise\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/devblogs.microsoft.com\/ise\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/ise\/wp-json\/wp\/v2\/users\/21412"}],"replies":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/ise\/wp-json\/wp\/v2\/comments?post=5517"}],"version-history":[{"count":0,"href":"https:\/\/devblogs.microsoft.com\/ise\/wp-json\/wp\/v2\/posts\/5517\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/ise\/wp-json\/wp\/v2\/media\/10771"}],"wp:attachment":[{"href":"https:\/\/devblogs.microsoft.com\/ise\/wp-json\/wp\/v2\/media?parent=5517"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/ise\/wp-json\/wp\/v2\/categories?post=5517"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/ise\/wp-json\/wp\/v2\/tags?post=5517"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}