Passing installation parameters to a Windows application with MSIX and App Installer
Published Sep 28 2020 05:14 AM 10.2K Views
Microsoft

As a Windows developer, a very common requirement you might be asked to implement is to track information about the installation process. For example, let's say that you have started a special campaign to advertise your application and you want to understand how many people are installing it because they have clicked one of the promotional banners. Or, in an enterprise environment, you may want to know which is the department of the employee who is installing the application, so that you can apply a different configuration. A very common solution for this requirement is to leverage a web installation and to use query string parameters, appended to the installation URI, which must be collected by the Windows application the first time it's launched. For example, your installation URL can be something like http://www.foo.com/setup?source=campaign. Then your application, when it's launched for the first time, must able to retrieve the value of the query string parameter called source and use it as it's needed (for example, by sending this information to an analytic platform like App Center).

 

As you might know if you follow this blog, MSIX is the most modern technology to deploy Windows application and, through a feature called App Installer, you can support web installations with automatic updates. As such, is there a way to pass activation parameters to the application from the installer URL with MSIX and App Installer? The answer is yes!

 

Let's see how we can do that.

Adding protocol support

The way MSIX supports this feature is by leveraging protocol support. Your app must register a custom protocol, which will be used to launch the application after it has been installed from your website using App Installer. Then, your application will retrieve all the information about the activation through the startup arguments, like in a regular protocol activation scenario. For example, let's say you register a protocol called contoso-expenses:. This means that, when someone invokes a URL like contoso-expenses:?source=campaign, your application will receive as activation arguments the value source=campaign. This is exactly what App Installer is going to do the first time it launches your MSIX packaged app after the installation has been completed.

 

Adding protocol support in a MSIX packaged application is quite easy, thanks to the application manifest. In my scenario, I have a WPF application built with .NET Core, which is packaged as MSIX using the Windows Application Packaging Project. As such, all I have to do is to double click on the Package.appxmanifest file in the Windows Application Packaging Project and move to the Declarations section. In the Available declarations dropdown menu choose Protocol and fill the Name field with the name of the custom protocol you want to register (in my scenario, it's contoso-expenses:(

 

protocol.png

 

Listening for activation arguments

The next step is to make our application aware of activation arguments. The way you implement this support changes based on the development framework you have chosen. In this sample we're going to see how to do it in a WPF application. Activation arguments can be retrieved in the App.xaml.cs file, by overriding the OnStartup() method, which is invoked every time the application starts:

 

public partial class App : Application
{
    protected override void OnStartup(StartupEventArgs e)
    {
        string path = $"{Environment.GetFolderPath(Environment.SpecialFolder.Desktop)}//AppInstaller.txt";

        if (e.Args.Length > 0)
        {
            System.IO.File.WriteAllText(path, e.Args[0]);
        }
        else
        {
            System.IO.File.WriteAllText(path, "No arguments available");
        }
    }
}

The OnStartup() method gets, as input parameter, an object of type StartupEventArgs, which includes any activation parameter that has been passed to the application. They are stored inside an array called Args. In case of protocol activation, there will be only one item in the array with the full URL that has been used to activate the application. The previous sample code simply writes this information, for logging purposes, in a text file stored on the desktop of the current user.

Test the custom protocol implementation

Before putting all together for App Installer, we have a way to quickly test if the implementation we have done works as expected. Since the App Installer implementation of this feature is based on the standard approach for managing custom protocols, we can test this scenario right away, without needing to package everything together and upload it on our website. We just need to invoke our custom protocol from any Windows shell.

 

As first step, right click on the Windows Application Packaging Project and choose Deploy, in order to install the application on the system and register the custom protocol. Now open the Run panel in Windows 10 (or just press Start+R on your keyboard) and type a URI which uses your custom protocol. For example, in my scenario I can use something like:

 

contoso-expenses:?source=campaign

If you have implemented everything correctly, your application will start. The OnStartup() event will have been triggered as well, so if I check my desktop I will find a file called AppInstaller.txt with the following content:

 

contoso-expenses:?source=campaign

As you can see, everything is working as expected. The application has launched and, in the activation arguments, I've been able to get the full URL that it has been used to invoke the custom protocol.

If you encounter issues during the OnStartup() event, the Windows Application Packaging Project gives you an option to easily test this scenario. Right click on it, move to the Debug section and enable the option Do not launch, but debug my code when it starts.

 

Debug.png

Now you can press F5 to launch the debugger, but the application won't be actively launched. The debugger will be on hold, waiting for the application to be activated. Thanks to this feature, you can easily test different activation paths. In our scenario, you can just add a breakpoint inside the OnStartup() method and then, from the Run panel, invoke the custom URL. The application will start and the debugger will wake up, triggering the breakpoint and giving you the option to debug your code.

 

Now that we have a working implementation in our application, let's see how we can configure App Installer to leverage it.

Setting up App Installer

When it comes to the App Installer configuration, you don't have to do anything special to support this scenario. The App Installer file you're using today to deploy your MSIX application works fine. However, you will have to customize the URL that is used to trigger the installation. If you're generating the App Installer file as part of the publishing process in Visual Studio, you will end up with a web page similar to the following one:

 

ContosoExpenses.png

The Get the app button will trigger the installation of the MSIX package by leveraging the special ms-appinstaller protocol and it will look like this:

 

ms-appinstaller:?source=https://contosoexpensescd.z19.web.core.windows.net/ContosoExpenses.Package.appinstaller

The trick here is to add a special parameter to this URI, called activationUri. As such, open the web page with your favorite text editor and change the URL to look like this (or copy it directly in your web browser, if you just want to do a test):

 

ms-appinstaller:?source=https://contosoexpensescd.z19.web.core.windows.net/ContosoExpenses.Package.appinstaller&activationUri=contoso-expenses:?source=campaign

As you can see, I have added at the end an activationUri parameter with, as value, the same exact custom URL I have tested before, based on the contoso-expenses protocol registered in the application. When you do this, Windows will automatically launch this URL at the end of the MSIX deployment process, instead of simply launching your application.

The installation process won't look different. When you click on Get the app button with the new custom URL, the user will continue to see the traditional MSIX deployment experience:

 

InstallApp.png

The only difference is that, as you can notice, the option Launch when ready is hidden. The application will be forcefully launched at the end of the process with the custom URL we have specified, otherwise we won't be able to get the activation parameters. Once the deployment is complete, Windows will silently invoke the custom URL we have passed to the activationUri parameter, which in our case is contoso-expenses:?source=campaign. As a consequence, the experience will be like the one we have tested before when we have locally invoked the custom protocol: the application will be launched and, on the desktop, we'll find a file called AppInstaller.txt with the full URL that has been used for triggering the execution.

 

Unfortunately, Visual Studio doesn't have a way to customize the web page that is generated to add this custom App Installer URI. However, in a real scenario, you won't probably use that page, but you will have the App Installer link embedded in a button or banner of your existing website.

 

Working with the activation parameters

An easier way to work with the activation parameters is to leverage the HttpUtility class which is included in the System.Web namespace. Thanks to this class, you can manipulate the query string parameters coming from the Uri in an easier way:

 

protected override void OnStartup(StartupEventArgs e)
{
    string path = $"{Environment.GetFolderPath(Environment.SpecialFolder.Desktop)}//AppInstaller.txt";

    if (e.Args.Length > 0)
    {
        UriBuilder builder = new UriBuilder(e.Args[0]);
        var result = HttpUtility.ParseQueryString(builder.Query);
        var value = result["source"];

        System.IO.File.WriteAllText(path, $"Source: {value}");
    }
    else
    {
        System.IO.File.WriteAllText(path, "No arguments available");
    }
}

As first step, we use the UriBuilder class to create a Uri object out of the activation parameter we have received inside the first position of the e.Args array. This way, we can easily access only to the query string parameters (without the whole contoso-expenses protocol) by using the Query property. By using the ParseQueryString() method and passing the query string we get in return a dictionary, which makes easy to access to all the various parameters. The previous sample shows how easily we can retrieve the value of the source parameter from the query string. Thanks to this approach, we can avoid to use string manipulation to retrieve the same information, which is more error prone.

 

What about a Universal Windows Platform application?

What if, instead of a .NET application like in this example, I have a UWP app? From a custom protocol registration perspective, the approach is exactly the same. The deployment technology for UWP is MSIX, so also in this case we have a manifest file where we can register our protocol. From a code perspective, instead, UWP offers a dedicated method for custom activation paths, called OnActivated(), which must be declared in the App.xaml.cs file. This is an example implementation:

 

protected override void OnActivated(IActivatedEventArgs args)
{
    if (args is ProtocolActivatedEventArgs eventArgs)
    {
        UriBuilder builder = new UriBuilder(eventArgs.Uri);
        var result = HttpUtility.ParseQueryString(builder.Query);
        var value = result["source"];
    }
}

 

As you can see, the code is very similar to the WPF implementation. The only difference is that the OnActivated event can be triggered by multiple patterns and, as such, first we need to make sure that this is indeed a protocol activation, by checking the real type of the IActivataedEventArgs parameter. In case of protocol activation, it will be ProtocolActivatedEventArgs, which gives us access to the to the Uri property we need to retrieve the activation URL.

 

Wrapping up

This implementation is based on the standard custom protocol support offered by Windows, so it's very easy to implement. Every development platform support a way to get activation parameters when the application starts, so you just need to write some code to manage the desired scenario.

 

You can find the full sample used in this blog post on GitHub.

 

Happy coding!

 

References

13 Comments
Bronze Contributor

Hi,

 

Thank you for sharing, but it required technical knowledge and is not so easy.

I request work on simplify the process and make it more user friendly.

Microsoft

Hello @Reza_Ameri-Archived thanks for the comment! Can you share a more detailed feedback on how you think this feature should be simplified?

 

I think the current implementation isn't very complex since it's based on already existing implementations (like protocol association), but maybe I'm seeing it with the wrong angle.

 

Thanks! 

Bronze Contributor

Hi @Matteo Pagani 

Indeed the process is simple and your description is very clear.

But it could be much simpler, for example there could be some graphical interface in Project properties and from there user select these options and configure them, instead of add some codes and so on. Then user just save it and when run it do as you said.

For more complex scenario, we will relay on coding like what you mentioned, but for general usage, I believe having one special UI for this would be interesting.

Microsoft

Thanks for the details @Reza_Ameri-Archived ! I think having a settings in the Visual Studio UI to customize the App Installer URL to add the activation parameter is a very good idea, I'll pass the feedback on.

However, unfortunately, a bit of coding will always be required to implement this scenario, because the way your app behaves when it's activated through the installation is totally custom and depends by your goals and requirements. As such, a "turn on" settings wouldn't really work here, since Visual Studio can't know which parameters you want to pass and how you want to handle them.

 

Thanks for the feedback!

Bronze Contributor

Welcome @Matteo Pagani 

I am sharing this because I had experience like we want to add new feature or implement new functionality and if it is as easy as make some changes in UI, we will go ahead with that, but in case it required coding and compile, normally we leave it for future and it keep delays until we find some free time to implement it.

Copper Contributor

Is this really the end result? 

 

This only works for first launch of the application. That is why you attach a so convoluted real world example of someone wanting to register the department where it is installed. In a real world you'd want to apply arguments at later application startups.

 

Further, as far as I can tell your implementation only takes a single pair of key/value parameters. Trying 

ms-appinstaller:?source=https://...appinstaller&activationUri=contoso-expenses:?source=campaign&target=foo

raise an invalid URI exception. Surely you can do better.

 

Cheers,

 

/Martin 

 

 

 

Microsoft

Hello @Apskalle ,

thanks for your feedback. One of the key goals of this blog is to create a community for Windows developers where we can discuss technical topics and help each others.  It's totally fine if you don't like my post or if you think it can be improved, but I kindly ask you to be more respectful of the work of others next time.

Having said that, this article is specific of passing activation parameters from the installer, which is a common requirement when, for example, you're running a marketing campaign and you want to track how many people installed the application as a consequence. However, the same protocol implementation works just fine on every launch. You just have to invoke the application using the protocol (for example, by pressing Win-R and typing the full URL, or by invoking the URL from another app or website) and the query string parameters will be passed to the app. Or is your expectation that the parameters passed at the installation should always be passed down to the application? Unfortunately, this would break how protocol activation works (what if you want to activate the app with different parameters?). If you need to handle this scenario, a better approach would be to intercept the query string parameters at the first launch after the installation and then store them somewhere (a database, a file, etc.)

 

Regarding the problem with multiple query strings, thanks for the feedback. I'll test the scenario and, if it's indeed a limitation, I'll forward it to the App Installer team.

 
Copper Contributor

Oh, sorry, came out harsher then intended, my apologies.

 

Just thinking out loud, wouldnt the appinstaller file be a place for parameters

 

 

<?xml version="1.0" encoding="utf-8"?>
<AppInstaller
	<MainBundle/>
	<UpdateSettings>
		<OnLaunch
			HoursBetweenUpdateChecks="0"
			ShowPrompt="true"
			UpdateBlocksActivation="true">
			<Parameters>
				<Parameter Key=foo Value=fooval/>
				<Parameter Key=bar Value=barval/>
			<Parameters>
		</OnLaunch>
		<ForceUpdateFromAnyVersion>true</ForceUpdateFromAnyVersion>
	</UpdateSettings>
</AppInstaller>

 

As for the point of launches subsequent to install, yes it works if you continue to launch it via the appinstaller. But (the normal?) way would be via the installed app (eg Start menu) and there the arguments are no longer forwarded as far as I can see.

 

/Martin

 

 

Microsoft

No harm done @Apskalle, no worries :smiling_face_with_smiling_eyes: 

The idea of adding the parameters in the App Installer file is interesting and a good one indeed. I'll pass the feedback to the App Installer team.

 

Regarding keep passing the parameters via the Start menu, can you share a bit more about the scenario you would try to accomplish? Which is the reason why it would important to you to have the activation parameters coming from App Installer always passed to the app when it's launched via Start menu?

 

Thanks!

 

 
Copper Contributor

Hi Matteo,


My need is pretty standard I think. The app is a LOB application (net5/WPF/client/server) installed at different customer sites.
Via command line params you can among atther things control authentication method, server- and database names and alternate path for storing user configuration file.

I dont plan to use the (appinstaller) html file but intend to just place the .appinstaller file on a share letting users do initial install from there, msix appbundle files residing on another remote server.
Subsequent starts are made from users desktop.

 

Cheers,

 

/Martin

Copper Contributor

Thanks for this!  We have an appx package (not msix due to older versions of Win10) that contains 2 exe's in the manifest.  We manually call for updates when one is detected by the app (also to support older win10) and need to know which exe to restart after the update is complete.  Since we already have aliases registered, adding the query string above was trivial.

 

One thing to note, in our case we do not actually need to pass any arguments other then the alias but you still need to include the ending semicolon.  For example, this did NOT work:

ms-appinstaller:?source=\\some\file\path\someapp.appinstaller&activationUri=myapp2alias

 

but this DID work (note the trailing ":"):

ms-appinstaller:?source=\\some\file\path\someapp.appinstaller&activationUri=myapp2alias:

 

 

Ernie

Copper Contributor

I have a question about the above. It says: "The only difference is that, as you can notice, the option Launch when ready is hidden".

 

However, I've followed the steps above, and everything else works well, we get protocol-based activation etc. HOWEVER, it does not hide the "Launch when ready" button. The result is if the end-user unclicks this option during install we never get to know about the the activationUri.

 

Is there maybe a step missing from the post above that describes how to hide that button?

 

[EDIT] Argh. This has to do with launching the .appinstaller from a batch file. (Have to ^ the &). Once I fixed that it works.

 

However, I second the request that it would be great having a way to specify something like <parameters> directly in an .appinstaller - that way we don't need to ship a batch file just to launch the .appinstaller with an activationUri. Though in fairness we need both mechanisms for different scenarios.

Copper Contributor

Is there a way to get this kind of functionality on Xbox?
When the app is installed I get the arguments in `OnActivated` as described, but if the app is not installed, that data doesn't come in after installation 

Version history
Last update:
‎Sep 28 2020 05:26 AM
Updated by: