Supporting “launch at startup” in a desktop app converted with the Desktop Bridge
One of the common requirements that many traditional desktop apps have is to support the “launch at startup” option: when the user logs in into his Windows PC, the application is automatically started, without requiring him to manually open it. It’s a way to speed up the workflow of the user and it’s leveraged especially by communications apps (like Skype, Slack, Telegram, Microsoft Teams, etc.), so that the user can be immediately connected with his friends and colleagues right after booting up his computer.
Is it possible to support this requirement also in a converted desktop app with the Desktop Bridge? Sure, thanks to one of the many special extensions that are available specifically for converted apps.
Enabling the feature
For this post, we’re going to take as a starting point the usual sample app I’ve used in every other blog post (HelloCentennial) and expand it to add this feature. You can grab, as usual, the original version of the project from my GitHub repository: https://github.com/qmatteoq/DesktopBridge/tree/master/1.%20Desktop%20App%20Converter/HelloCentennial
In this case, to achieve our goal, we’re going to leverage the classic manual approach based on the Desktop Bridge Debugging Project for Visual Studio 2017. As such, our starting point will be the same you can find in this folder of my repository: https://github.com/qmatteoq/DesktopBridge/tree/master/3.%20Convert
- A Windows Forms app, with the traditional desktop app
- A Desktop Bridge Debugging Project, with a PackageFiles folder that contains the manifest, the assets and the Win32 executables of the HelloCentennial desktop app (which are kept in sync thanks to the configuration described in the AppXPackageFileList.xml file).
Here is how the solution looks like:
As for every other extension, the starting point is always the manifest file. To add support to the “launch at startup” option, we need to leverage a feature called Startup Task, which must be added to the AppxManifest.xml file included in the PackageFiles folder of the Desktop Bridge Debugging Project. Here is how the extension definition looks like:
<Extensions>
<desktop:Extension Category="windows.startupTask" Executable="HelloCentennial.exe" EntryPoint="Windows.FullTrustApplication">
<desktop:StartupTask TaskId="HelloCentennialTask" Enabled="true" DisplayName="Hello Centennial Startup Task" />
</desktop:Extension>
</Extensions>
The extension is part of a specific set reserved for desktop apps so, in the Package entry of the manifest, you need to add the desktop namespace, as in the following sample:
<?xml version="1.0" encoding="utf-8"?>
<Package xmlns="https://schemas.microsoft.com/appx/manifest/foundation/windows10"
xmlns:uap="https://schemas.microsoft.com/appx/manifest/uap/windows10"
xmlns:desktop="https://schemas.microsoft.com/appx/manifest/desktop/windows10"
xmlns:mp="https://schemas.microsoft.com/appx/2014/phone/manifest"
xmlns:rescap="https://schemas.microsoft.com/appx/manifest/foundation/windows10/restrictedcapabilities"
IgnorableNamespaces="uap rescap mp desktop">
<!-- content of the manifest -->
</Package>
The <desktop:Extension> element has two fixed attributes:
- Category, which must be windows.startupTask.
- EntryPoint, which must be Windows.FullTrustApplication
The custom attribute is Excecutable, which is a reference to the process that you want to launch at startup. In this case, it’s the same process of the main app (HelloCentennial.exe), but it isn’t a fixed requirement: it can be a different executable stored inside the same app package or you can even have multiple entries, in case you have multiple processes you need to run at startup.
Inside the <desktop:Extension> entry you need to define the real task, thanks to the <desktop:StartupTask> element. Here you can configure a set of important attributes:
- TaskId is the unique identifier of the task. It’s very important and we will see later why: we’ll need it in case we want to handle the task from the code of our desktop app.
- Enabled is a boolean value: we can set it to true if we want the startup feature to be automatically enabled when the app is launched for the first time or to false if, instead, we want to use an opt-in approach by the user.
- DisplayName is a simple string with the name of the task, that can act as a description of its purpose.
This is how the full AppxManifest.xml file of our converted app looks like:
<?xml version="1.0" encoding="utf-8"?>
<Package xmlns="https://schemas.microsoft.com/appx/manifest/foundation/windows10"
xmlns:uap="https://schemas.microsoft.com/appx/manifest/uap/windows10"
xmlns:desktop="https://schemas.microsoft.com/appx/manifest/desktop/windows10"
xmlns:mp="https://schemas.microsoft.com/appx/2014/phone/manifest"
xmlns:rescap="https://schemas.microsoft.com/appx/manifest/foundation/windows10/restrictedcapabilities"
IgnorableNamespaces="uap rescap mp desktop">
<Identity Name="HelloCentennial" Version="1.0.0.0" Publisher="CN=mpagani" ProcessorArchitecture="x86" />
<Properties>
<DisplayName>Hello Centennial</DisplayName>
<PublisherDisplayName>Matteo Pagani</PublisherDisplayName>
<Logo>Assets\storelogo.png</Logo>
</Properties>
<Dependencies>
<TargetDeviceFamily Name="Windows.Desktop" MinVersion="10.0.14393.0" MaxVersionTested="10.0.14393.0" />
</Dependencies>
<Resources>
<Resource Language="en-us" />
</Resources>
<Applications>
<Application Id="HelloCentennial" Executable="HelloCentennial.exe" EntryPoint="Windows.FullTrustApplication">
<uap:VisualElements DisplayName="Hello Centennial" Description="Hello Centennial" BackgroundColor="transparent" Square150x150Logo="Assets\Square150x150Logo.png" Square44x44Logo="Assets\Square44x44Logo.png">
<uap:DefaultTile Square310x310Logo="Assets\LargeTile.png" Wide310x150Logo="Assets\WideTile.png" Square71x71Logo="Assets\SmallTile.png">
</uap:DefaultTile>
</uap:VisualElements>
<Extensions>
<desktop:Extension Category="windows.startupTask" Executable="HelloCentennial.exe" EntryPoint="Windows.FullTrustApplication">
<desktop:StartupTask TaskId="HelloCentennialTask" Enabled="true" DisplayName="Hello Centennial Startup Task" />
</desktop:Extension>
</Extensions>
</Application>
</Applications>
<Capabilities>
<Capability Name="internetClient" />
<rescap:Capability Name="runFullTrust" />
</Capabilities>
</Package>
This is enough to enable the feature: launch the converted version of the app by setting, as startup project in is Visual Studio, the Desktop Bridge Debugging one and press F5. If you did everything correctly, your app will be now automatically enabled for startup: you can easily check it by right clicking on the taskbar in Windows, choosing Task Manager and move to the Startup tab. You should find your converted app in the list of apps that are allowed to run at startup:
There’s one important information to highlight: in order for the startup task to be properly registered, the app needs to be launched at least one time. It isn’t enough to side load the AppX or install the app from the Store to schedule the startup task.
Controlling the startup task from code
Having only the opportunity to define a startup task without giving the chance to the user or to the developer to control it wouldn’t be really useful: typically, all the traditional desktop apps that support this feature offer also a setting so that the user can control it and enable / disable the auto startup. We can achieve this goal thanks to a specific set of APIs which are part of the Universal Windows Platform. As such, if we want to include this feature in our converted app, we need to move to the Enhance phase: you can refer to one of the previous posts on this blog to learn how you can integrate UWP APIs inside a traditional desktop application.
Once you have added a reference to the required libraries (either by manually adding a reference to the Windows.md and System.Runtime.WindowsRuntime files or by using the UWPDesktop NuGet package), you can access to the StartupTask class, which belongs to the Windows.ApplicationModel namespace. Let’s assume that we have modified the user interface of the basic HelloCentennial app to add a CheckBox control that reflects the status of the startup task: when it’s checked, the task is enabled and the application is launched at startup; when it’s unchecked, the task is disabled. This is the look and feel of the updated user interface:
As first step, I’ve subscribed to the Load event of the form, so that I can check the status of the task when the app starts and keep the user interface consistent:
private async void Form1_Load(object sender, EventArgs e)
{
var startupTask = await Windows.ApplicationModel.StartupTask.GetAsync("HelloCentennialTask");
switch (startupTask.State)
{
case Windows.ApplicationModel.StartupTaskState.Disabled:
chkBoxStartup.Checked = false;
break;
case Windows.ApplicationModel.StartupTaskState.DisabledByUser:
chkBoxStartup.Checked = false;
break;
case Windows.ApplicationModel.StartupTaskState.Enabled:
chkBoxStartup.Checked = true;
break;
}
}
Looking at the code, you’ll now understand the importance of the TaskId attribute of the <desktop:StartupTask> element in the manifest file: to get a reference in code to the startup task, we need to call the GetAsync() method of the StartupTask class passing, as parameter, the identifier we have declared in the manifest. Then we can use the State enumerator to understand which is the current status:
- Disabled and DisabledByUser are similar in terms of the final result: the task is disabled and the app isn’t launched at startup. The difference is that, in case it has been disabled by the user thanks to the option which is available in the Task Manager in the Startup section, only the user can reactivate it. The application can’t programmatically activate it again. However, from a UI point of view, in both cases we unselect the checkbox.
- Enabled means, instead, that the app is automatically launched at startup and, in this case, we mark the checkbox.
By using the same APIs, we can now allow the user to control the startup behavior: once we have a reference to the startup task object, in fact, we have access to some APIs to enable or disable it. The following sample shows the code that handles the Click event of the CheckBox control, which is invoked every time the user clicks on it:
private async void chkBoxStartup_Click(object sender, EventArgs e)
{
var startupTask = await Windows.ApplicationModel.StartupTask.GetAsync("HelloCentennialTask");
if (startupTask.State == Windows.ApplicationModel.StartupTaskState.Enabled)
{
startupTask.Disable();
chkBoxStartup.Checked = false;
MessageBox.Show("The task has been disabled");
}
else
{
var state = await startupTask.RequestEnableAsync();
switch (state)
{
case Windows.ApplicationModel.StartupTaskState.DisabledByUser:
MessageBox.Show("The task has been disabled by the user");
chkBoxStartup.Checked = false;
break;
case Windows.ApplicationModel.StartupTaskState.Enabled:
MessageBox.Show("The task has been enabled");
chkBoxStartup.Checked = true;
break;
}
}
}
We get a reference to the startup task we have declared in the manifest identified by the HelloCentennialTask name. Then, by using again the State property, we check if the task is already enabled or not:
- If it’s enabled, it means that the user has clicked on the checkbox to disable it: we call the Disable() method on the object and we uncheck the CheckBox control.
- If it’s disabled, it means that the user has clicked on the checkbox to enable it: we call the RequestEnableAsync() method this time. This method returns a value of the StartupTaskState enumerator with the new status of the task, which can be useful to understand if the operation has completed with success or not. Remember, in fact, that if the task has been disabled by the user using the Task manager, the operation will fail and the returned state will be DisabledByUser. Based on the response, we also check or uncheck the Checkbox control.
That’s all. If you now try to launch again the application, the checkbox will be selected: we have queried the status of the task, we have found out that it’s enabled and we have checked it. If you click on it to uncheck it, you’ll invoke the chkBoxStartup_Click event handler, which will call the Disable() method. Again, you can see the outcome by right clicking on the Windows taskbar and opening the Task Manager: this time, in the Startup section, you should see that the status of the task connected to the Hello Centennial app will be Disabled.
Let’s do another experiment. Enable the task again by clicking on the checkbox, then close the application. Now, in the Startup section of the Task Manager, right click on the Hello Centennial task and choose Disable. Now reopen the app: this time, the checkbox will be unchecked, since during the form load the StartupTask API will detect that the task status is DisabledByUser. Now click on the checkbox: this time you’ll get a message saying that the task has been disabled by the user and the checkbox will stay unchecked. This is expected: the task has been disabled by the user directly in Windows, so the app can’t enable it again on its own. It must be the user to return to the Task Manager, go to the Startup section, right click on the Hello Centennial startup task and choose Enable. Now the application will be able to control it again and you’ll be able to use the checkbox to enable or disable it.
Wrapping up
In this post we’ve learned how to enable a “launch at startup” experience also in a converted desktop app with the Desktop Bridge. As usual, you can find the sample used in this blog post on my GitHub repository at https://github.com/qmatteoq/DesktopBridge/tree/master/Extras/StartupTask
Happy coding!
Comments
- Anonymous
March 18, 2017
Thanks for your helpful guide! But my app is written in C++, can you illustrate how to control the startup task via C++ code?- Anonymous
March 21, 2017
Hello,unfortunately you can't access directly to UWP APIs from C++ code, but you need to leverage C++ / CX, which is a special C++ variant that supports the Universal Windows Platform and all of his requirements (like the ability to perform asynchronous calls). The easiest way to achieve your goal is to create an intermediate C++ / CX library, that performs all the UWP interactions, and then reference it from your C++ app. You can find a good sample of this approach in the official Desktop Bridge samples from Microsoft. This application (https://github.com/Microsoft/DesktopBridgeToUWP-Samples/tree/master/Samples/MFCwithPush) is a MFC / C++ application that uses a C++ / CX wrapper (the project called UWPFeatures) to act as a "middle man" between the C++ app and the UWP world. I hope it helps!Best- Anonymous
March 26, 2017
Thank you very much! Your information is very valuable to me! I will look into it :-) - Anonymous
July 11, 2017
In addition to Matteo's reply: you can now use C++/WinRT, it's a header-only library for using UWP APIs from standard C++https://github.com/Microsoft/cppwinrt/
- Anonymous
- Anonymous
- Anonymous
September 27, 2017
Hi,MatteoDoes Desktop Bridge support “launch at startup” in exe inside an UWP app? at present, we can launch the exe by call FullTrustProcessLauncher.LaunchFullTrustProcessForCurrentAppAsync();in order to achieve “launch at startup”, I tried these: ERROR:Reason: Windows.FullTrustApplication entry points are only valid for UWP desktop applications whose minVersion >= 10.0.14257.0and ERROR:Reason: The use of startuptask extension requires the containing Application use the Windows.FullTrustApplication entry point.- Anonymous
September 27, 2017
Hello Hao,I'm sorry but your scenario isn't supported. The "launch at startup" feature must be enabled at the main application's level so, eventually, you can leverage it in the manifest of the full UWP app. You can't set any Win32 executable inside the package to be executed when Windows starts.Best- Anonymous
September 27, 2017
Thanks, Matteo.so, you mean UWP apps can be launched when Windows starts? how to?- Anonymous
September 27, 2017
Yes, it's one of the new features that is part of the upcoming Fall Creators Update. It works in the same way I've described about Desktop Bridge apps. You can find more details here: https://blogs.windows.com/buildingapps/2017/08/01/configure-app-start-log- Anonymous
September 27, 2017
got you now. The new feature is great.Thanks.
- Anonymous
- Anonymous
- Anonymous
November 16, 2017
Hi Matteohttps://github.com/Microsoft/AppModelSamples/tree/master/Samples/TestStartup/TestStartuphttps://blogs.windows.com/buildingapps/2017/08/01/configure-app-start-log/#Gt7KbgKh4IYjb3uZ.97This demo shows how to enable ‘launch at startup’ for win32 executable inside UWP package, but you said ‘You can’t set any Win32 executable inside the package to be executed when Windows starts.’What is wrong?Thanks.- Anonymous
November 16, 2017
Hello Xiaoyan,if it's a Desktop Bridge application, you can point the startup task to any executable inside the package. If it's a full UWP application, instead, the startup is connected by default to the main application.I hope it helps!- Anonymous
November 16, 2017
Thank you.
- Anonymous
- Anonymous
- Anonymous
- Anonymous
- Anonymous
October 23, 2017
Hi,Matteo, I face a exception in the function code StartupRask.GetAsync() , could you please give me some advice for it ?var startupTask = await Windows.ApplicationModel.StartupTask.GetAsync("HelloCentennialTask");-->Message "The process has no package identity. (Exception from HRESULT: 0x80073D54)" stringJay- Anonymous
October 23, 2017
Hello Jay, according to the error your application is running outside the Desktop Bridge context. StartupTask is a UWP API and can be used only when the application is running packaged with the Desktop Bridge, not when it's executed as a classic desktop app.- Anonymous
November 09, 2017
Hi Matteo, my app converted by Desktop bridge , I tried what you shared method in xml file and I can see startup status in task manager .but I still got fail result when I call the UWP API StartupRask.GetAsync() . DAC converted UWP app can be enable for startup task ?Jay - Anonymous
November 16, 2017
I think there might be a BUG, because my Desktop Bridge UWP app pops up this error when it is launched on one of my PCs:“The process has no package identity”, but It is OK on my other PCs. Actually I call ‘FullTrustProcessLauncher.LaunchFullTrustProcessForCurrentAppAsync();’ in 'OnLaunched'.Hope you can help.- Anonymous
November 17, 2017
Hello,actually I never faced this issue. It's like if the manifest isn't well formed. Can you try to check that the Package identity in the manifest is properly set and to recreate the package?Best- Anonymous
November 17, 2017
Like I said, it just happen on one of my PCs, but it runs well on my other PCs, really weird.
- Anonymous
- Anonymous
- Anonymous
- Anonymous