July 5, 2017 2:00 pm

Command-Line Activation of Universal Windows Apps

By / Senior Program Manager, Windows

As we continue to close the gap between Win32 and Universal Windows Apps, one of the features we’ve recently introduced is the ability to activate a UWA from a command line and pass the app arbitrary command-line arguments. This is available to Insiders from build 16226.

This feature builds on the App Execution Alias extension already available for Desktop Bridge apps. To use this feature in a UWA, there are two key additions to your app:

  • Add an appExecutionAlias extension to your app manifest.
  • Override OnActivated and handle the incoming arguments.

For the manifest entry, you first need to declare the XML namespace for the AppExecutionAlias element:


 <Package
  xmlns="http://schemas.microsoft.com/appx/manifest/foundation/windows10"
  xmlns:mp="http://schemas.microsoft.com/appx/2014/phone/manifest"
  xmlns:uap="http://schemas.microsoft.com/appx/manifest/uap/windows10"
  xmlns:uap5="http://schemas.microsoft.com/appx/manifest/uap/windows10/5" 
  IgnorableNamespaces="uap mp uap5">

The AppExecutionAlias is declared as an Extension within your Application. This is quite simple and almost the same as for a Desktop Bridge app:


<Application ...>
      
  <Extensions>
      <uap5:Extension 
        Category="windows.appExecutionAlias" 
        Executable="MyCompany.Something.Another.exe" 
        EntryPoint=" MyCompany.Something.Another.App">
        <uap5:AppExecutionAlias>
          <uap5:ExecutionAlias Alias="MyApp.exe" />
        </uap5:AppExecutionAlias>
      </uap5:Extension>
  </Extensions>

</Application>

The Executable is the name of your UWA app EXE, and the EntryPoint is the fully qualified name of your App class. The ExecutionAlias is the name that users will type in at the command-line: This can be any arbitrary name, and it must end with “.exe.” You should choose a meaningful alias that you can reasonably expect the user to associate with your app. Note that if you choose an alias that conflicts with an app that is already installed, your alias won’t be used. Similarly, if your app is installed first, and then the user installs another app later that declares the same alias – then your app will take precedence. The rule here is that the first one wins.

The manifest entry is obviously the same for VB and C++ projects, but for a JavaScript web app, it’s slightly different. Instead of Executable, you specify a StartPage, and you don’t specify EntryPoint at all:


<Extensions>
    <uap5:Extension 
      Category="windows.appExecutionAlias" 
      StartPage="index.html">
      <uap5:AppExecutionAlias>
        <uap5:ExecutionAlias Alias="MyApp.exe" />
      </uap5:AppExecutionAlias>
    </uap5:Extension>
</Extensions>

For the OnActivated override, the first thing to do is to check the ActivationKind – this is standard practice if your app supports multiple activation kinds (file associations, custom protocols and so on). In this scenario, if the ActivationKind is CommandLineLaunch, the incoming IActivatedEventArgs will be an object of type CommandLineActivatedEventArgs. From this, you can get the CommandLineActivationOperation, and from this in turn, you can get the Arguments string. You also get the CurrentDirectoryPath, which is the directory current when the command-line activation request was made. This is typically not the install location of the app itself, but could be any arbitrary path.


async protected override void OnActivated(IActivatedEventArgs args)
{
    switch (args.Kind)
    {
        case ActivationKind.CommandLineLaunch:
            CommandLineActivatedEventArgs cmdLineArgs = 
                args as CommandLineActivatedEventArgs;
            CommandLineActivationOperation operation = cmdLineArgs.Operation;
            string cmdLineString = operation.Arguments;
            string activationPath = operation.CurrentDirectoryPath;

It’s important to remember that the command-line arguments are supplied by the caller, which means that you have no control over them. You should treat these arguments as untrustworthy and parse them very carefully. They might not have any malicious intent, but they could easily be badly formed, so you need to allow for this.

After the initial checks, you can create a window as normal, and optionally pass in the (parsed and validated) command-line arguments – or some data extracted from the arguments – to that window.


           Frame rootFrame = Window.Current.Content as Frame;
            if (rootFrame == null)
            {
                rootFrame = new Frame();
                Window.Current.Content = rootFrame;
            }
            rootFrame.Navigate(typeof(MainPage), 
                string.Format("CurrentDirectory={0}, Arguments={1}",
                activationPath, cmdLineString));
            Window.Current.Activate();

Finally, in your page’s OnNavigatedTo, you can retrieve the payload from the event args, and use the information in any way you like:


protected override void OnNavigatedTo(NavigationEventArgs e)
{
    string cmdLineString = e.Parameter as string;
}

When you build and run the app on your dev machine – or when the end user installs your app – the  alias is registered. From that point, the user can go to a command line and activate your app.

Note that by “command line,” we mean any common command line mechanism such as cmd.exe,  powershell.exe, Windows-R and so on. Here’s a slightly more sophisticated example in which the app implements a custom parser to construct command-payload tuples from the command-line arguments:


protected override void OnActivated(IActivatedEventArgs args)
{
    switch (args.Kind)
    {
        case ActivationKind.CommandLineLaunch:
            CommandLineActivatedEventArgs cmdLineArgs = 
                args as CommandLineActivatedEventArgs;
            CommandLineActivationOperation operation = cmdLineArgs.Operation;
            string cmdLineString = operation.Arguments;
            string activationPath = operation.CurrentDirectoryPath;

            Frame rootFrame = Window.Current.Content as Frame;
            if (rootFrame == null)
            {
                rootFrame = new Frame();
                Window.Current.Content = rootFrame;
            }

            ParsedCommands parsedCommands = 
                CommandLineParser.ParseUntrustedArgs(cmdLineString);
            if (parsedCommands != null && parsedCommands.Count > 0)
            {
                foreach (ParsedCommand command in parsedCommands)
                {
                    switch (command.Type)
                    {
                        case ParsedCommandType.SelectItem:
                            rootFrame.Navigate(typeof(SelectItemPage), command.Payload);
                            break;
                        case ParsedCommandType.LoadConfig:
                            rootFrame.Navigate(typeof(LoadConfigPage), command.Payload);
                            break;
                        case ParsedCommandType.Unknown:
                            rootFrame.Navigate(typeof(HelpPage), cmdLineString);
                            break;
                    }
                }
            }
            else
            {
                rootFrame.Navigate(typeof(MainPage));
            }

            Window.Current.Activate();
            break;
    }
}

The app’s logic uses these more structured commands to navigate to different pages and handle the payload in different ways. The point is that you can define whatever argument options and payload rules you like in your app.

A common scenario is testing: You can activate your app with a defined set of values for each test run – for example, to start on a particular page with boundary values set for key items – or to start a game at a particular level, with values set for player attributes, enemy count, damage levels, fuel and weaponry and so on.

If the data you provide is too long or too complex for command-line arguments, you can supply a filename on the command-line which the app can then load. One option is to include such files as content in your package (and most likely strip them out before building your release build). You can also create the files at any time later, so long as you put them in a location to which the app has access. If you want to avoid showing any filepicker UX in your app, the simplest option is to put the files in the install location for the app, which would be somewhere like %userprofile%\AppData\Local\Packages\<Package ID>\LocalState.

You should also allow for incoming arguments that are badly formed or otherwise unrecognized:

And a reasonable UX here would be to navigate to a page where you show the user the correct usage options:

Also, bear in mind that the UWP platform has a single-instance app model. This means that your app can be running, and you can then continue to execute command-line activation requests at any point thereafter. Each activation will result in a call into OnActivated. This is unlikely to be useful in end-user scenarios of course – but it can be a useful debugging/profiling strategy during development.

Command-line activation of UWAs is just one example of how we’re gradually closing the gaps between traditional Win32 development and UWA development. This feature brings you yet another missing piece from the old world that you can now leverage in your modern apps.

Sample app code.

Updated July 12, 2017 8:40 am

Join the conversation

  1. Hi,
    I’ve tried the implementation – manifesting and modifying the OnActivated – with Win10 IP 16226 + VS2017 15.3 Preview3 + SDK16225. It works. Great!!
    One finding: If I’ve used the token “$targetnametoken$” on the uap5:Extension – Executable or Entrypoint attribute, the build itself done without error. But, if I ran it from command line, the app window was showing once but closed immediately.
    I’ve checked the event viewer and following error was logged.
    Microsoft-Windows-DistributedCOM – Failed to start the DCOM Server. “C:\Users\myuserid\Source\Repos\UWPTrial\UWPTrial\bin\x86\Debug\AppX\$targetnametoken$.exe” -ServerName:App.AppXa5r8fa9hjpm4zqq5x24agfbkfdhqbr7p.mca

    • Hi Mamoru

      Thanks for trying this out! The build by default will expand $targetnametoken$ only for certain elements (such as the Application Executable), not in others. As you can see from the error output, you’ve defined an alias with the un-expanded name “$targetnametoken$.exe”, which won’t work. You should specify explicit values for all the Extension attributes (Executable, EntryPoint and Alias), without expansion.

  2. Glad to see progress being made here! Hoping a few additional questions can be answered:

    1. Is there any special storage permissions handling for command line activation? From the example above, it sounds like there is not. That is, if I have an app that can operate on arbitrary files, the user can do file activation from an Explorer window, which gives my app permissions to access that file, but they cannot do file activation from the command line. Is that correct?

    2. By extension of #1, presumably neighboring file query is not supported by command line activation?

    3. Is it possible for an app to provide feedback to the command line? For example, if the user types “MyApp.exe /?” can an app provide help text to be printed on the command line, and not launch a window? Or is it expected that help text is only displayed via a window?

    • Hi Ryan, to your questions:
      1. Standard UWP filesystem/storage constraints apply: the app can only access folders/files where it has permission, so you need to put the file in such a location. You could use a filepicker to grant permission to a custom location, but the user would have to navigate in the picker to that custom location, so that would be pretty poor UX. For file activation, as you say, this won’t work from the command-line because command-line activation relies on the use of the app’s alias explicitly, and not a file name.

      2. No, as of this release, the feature doesn’t allow the app to access the console window. You can opt not to show a window, but the only return value you can provide is an integer exit code.

  3. It would be great if you can upload a sample to get a better understanding with this new feature

  4. Andrew,
    Thank you very much for the detailed post and great example. I got it to work.
    Q: You wrote: “A common scenario is testing: You can activate your app with a defined set of values for each test run … ” – that’s great but how to test command line arguments in the debugger? In Project-> Properties-> Debug -> I put command line arguments there but when I run F5 the app does not fire OnActivated and I cannot Debug/Trace the code. (VS2017)
    Thanks.

    • I think I found it:
      Go to Project->Properties->Debug and select “[x] Do not launch, but debug my code when it starts”; then F5 the app, open a command line and run the app from the command line (using the Alias). The debugger will attach to the started instance of the app.