Electron and UAC on Windows
Lately we've been working on a new client program at Authentise. The program is calledAuthentise Echo and it's designed to sit on your network and talk to your networked 3D printers and gather data about what they're doing. It's neat and everything, but it need to run on windows and it constantly reminds us of how terrible it is to package software on windows.
What's so bad about installers on windows? Honestly, it's gotten a lot better over the past 10 years. Now we can use WiX. It's quite a bit better than NSIS, and they're both cheaper than InstallShield. So what does our WiX installer look like?
First, our installer is automated via a windows batch scripting language which is nearly the worst language ever. Second, we're using Electron. Electron is awesome, we've been quite happy with it, but it adds some significant complexity when creating installers.
Some sample code:
... heat dir %SOURCE_DIR%\ -t packaging\windows\Filter.xslt -var env.SOURCE_DIR -dr INSTALLDIR -cg Dependencies -ag -scom -sreg -sfrag -srd -o packaging\windows\Echo.Dependencies.wxs candle -arch x86 -out Echo-x86.wixobj -wx packaging\windows\Echo.wxs candle -arch x86 -out Echo-x86.Dependencies.wixobj -wx packaging\windows\Echo.Dependencies.wxs light -ext WixUIExtension -ext WixUtilExtension -cultures:en-us -wx -out Echo-x86-%CLIENT_VERSION%.msi Echo-x86.wixobj Echo-x86.Dependencies.wixobj signtool sign /t http://timestamp.comodoca.com/authenticode /f packaging\windows\authentise.pfx /p password Echo-x86-%CLIENT_VERSION%.msi candle packaging\windows\EchoBootstrapper.wxs -ext WixBalExtension -ext WixUtilExtension insignia -ib Echo-%CLIENT_VERSION%%APPEND%-unsigned.exe -o engine.exe ...
This is only a few lines of our 128 line monster. This blog post isn't about the details of what we're doing in that installer. It's about a single problem we hit and how to get around it.
A little background. Authentise Echo is essentially two parts. The first part is a service which is constantly running and talking to printers and sending telemetry data from the printers into the cloud. This is how we can build reports about printer utilization, material waste, scheduling slack, etc. Useful stuff. This is written in C#. The other part of Authentise Echo is the configuration application. We wrote that using Electron. Writing it in Electron allowed us to use just 100 lines of our own code to create an app that allows the user to log in to the website using their standard credentials and create an API token that gets stored with the service to authenticate the data from the service and configure the printers on the local network. It's a fantastic way to bridge existing web technology and a desktop application.
The problem we hit was this: where do we write the configuration file that the config program writes and the service reads?
If we put it in Application Data the service and the configuration program will disagree on the location because services and applications get different Application Data If we put it in the service's Application Data the configuration program won't be able to write it If we put it in the configuration program's Application Data the service won't be able to read it
Solution: go Admin. Go Admin hard.
Our solution was to just hard code the configuration file to a common location - the installation directory in Program Files. Then elevate permissions when writing using UAC. That's sudo's broken, confused, annoying 2nd cousin.
Usually doing UAC is easy. You just add a file to your project called <projectname>.exe.manifest and embed it in the EXE. You include a little snippet in the manifest indicating that you need Administrator permissions and off you go, the OS does the rest.
call node_modules\.bin\electron-packager .^ --platform=win32^ --arch=ia32^ --ignore="node_modules/(electron-packager|electron-prebuild)"^ --prune^ --icon=..\packaging\windows\Echo.ico^ --app-version=%CLIENT_VERSION%^ --asar^ --version-string.CompanyName="Authentise, Inc."^ --app-copyright="2016 Authentise, Inc."
Which is incredible. However, there's no option there to specify a manifest file. Or that you want admin privileges. Or...anything remotely like that. I can't say I blame them, Windows isn't the only platform out there and who wants to deal with its ideosyncracies?
Well, the output of that command is EchoConfiguration.exe. Let's take a look at what it's default manifest file is, shall we?
mt.exe -inputresource:EchoConfiguration.exe;#1 -out:manifest.xml
This uses the manifest tool, mt, to extract the manifest file. The first parameter is the binary we want to extract from - in this case EchoConfiguration.exe which was produced by electron-packager. The output file is a manifest XML file. It looks like this:
... <trustInfo xmlns="urn:schemas-microsoft-com:asm.v3"> <security> <requestedPrivileges> <requestedExecutionLevel level="user" uiAccess="false"/> </requestedPrivileges> </security> </trustInfo> ...
I've snipped all the boring stuff. This manifest file indicates that the program should run using whatever permission level the user currently has. Great if you're already Administrator. Less helpful for us if you're not. So let's change it
... <trustInfo xmlns="urn:schemas-microsoft-com:asm.v3"> <security> <requestedPrivileges> <requestedExecutionLevel level="requireAdministrator" uiAccess="false"/> </requestedPrivileges> </security> </trustInfo> ...
Much better, now we require Administrator access before the program can run. This will cause Windows to pop open the (all too familiar) UAC dialog prompting the user for Admin rights. Cool. How do we add this manifest to the executable produced by electron-packager? We use mt.exe again
mt -manifest EchoConfiguration.exe.manifest -outputresource:EchoConfiguration.exe;1
This tells mt.exe that we want to take a manifest, in this case EchoConfiguration.exe.manifest is the modified version of my manifest file, and we want to write it intoEchoConfiguration.exe. This will overwrite the manifest that is already embedded in the application. This will only work if you have the right version of mt.exe. One of our developers did the exact same steps using an older version, 6.1.7716.0 specifically, and it didn't work. Specifically, the error was:
mt -manifest EchoConfiguration.exe.manifest -outputresource:EchoConfiguration.exe;1 Microsoft (R) Manifest Tool version 6.1.7716.0 Copyright (c) Microsoft Corporation 2009. All rights reserved. mt : general error c101008d: Failed to write the updated manifest to the resource of file "EchoConfiguration.exe". The parameter is incorrect.
The mt that did work looked like this was
mt -manifest EchoConfiguration.exe.manifest -outputresource:EchoConfiguration.exe;1 Microsoft (R) Manifest Tool version 6.3.9600.17336 Copyright (c) Microsoft Corporation 2012. All rights reserved.
You don't get output indicating success. You'll know it worked when the icon for your application suddenly gains a cute little 'Admin rights' shield. Or when you double click on it and it requests Admin privileges.
But don't do that when running as Administrator. You'll think it's broken when it's working fine. Believe me, personal experience.
That's basically it. We spent hours figuring that out here.
If you're interested in talking to us more about Authentise Echo or the tech behind it, drop us a line, engineering at authentise dot com.