App Elevation Samples – Part 3

Picture5

If you haven’t read part 1 and part 2 of this series yet, please start there first. I have covered the “hello world” version of a packaged app that requires elevation at launch time and showed how a packaged app can dynamically elevate itself. In this post we’ll get to the fun part: using the ‘allowElevation’ capability from a UWP app to execute code with elevated privileges on Windows 10 desktop with the 1809 update.

TL;DR

I already have the 1809 update and the Windows SDK 17763 (or later) installed. Just show me the code on GitHub …

Sample Code Repo on Github

Step-by-Step Tutorial

1. Create a new Universal Windows App

Make sure you run Windows 10 with the 1809 update and have the Windows SDK 17763 (or later) installed. Now create a new C# Universal Windows application in Visual Studio 2017 (can use any supported programming language as well, e.g. C++, JS, …):

FileNewProject2

Be sure to set both “Minimum” and “Target” version to 17763 or higher as build number 17763 maps to the 1809 update for Windows 10. The ‘allowElevation’ capability we will be using has been introduced in 1809 and is not available in earlier versions of Windows 10.

setVersions

Now we need to add a ‘fullTrustProcess’ desktop extension to our UWP that will contain the code we want to run with elevated privileges. For a complete tutorial on UWP desktop extension read up here on all the details. In this post I will quickly move through the steps of setting up the desktop extension for our new UWP app:

2. Add the desktop extension project

For this example we want the desktop extension process to be headless (i.e. a background process without UI). It can have a UI as well if that’s what you need for your scenario. So let’s add a C# desktop console application (of course this could be C++, VB, etc. as well) and call it “Elevated Extension”:

addConsole2We don’t want a console box to pop up for our elevated background process, so we need to configure this project to run headless. For that go into the project settings and change the Output Type from “Console Application” to “Windows Application” (I know, not very intuitive …)

outputType

5. Packaging everything together

Next we need to add a “Windows Application Packaging Project” (look for it in the “Windows Universal” category). Name it “Package” and make it the startup project,  so your solution setup looks like in the screenshot below. In order to package both the UWP and the desktop code in one app package, add both as Application references to the Packaging project:

addref3

6. Configuring the appx manifest

Now to connect the two projects we need to declare the “fullTrustProcess” desktop extension in the package manifest. For this open the Package.appxmanifest file in the Packaging project (“Package”) in XML editing mode and add the extension within the <Application> node. While we are in this file we are also adding the “appExecutionAlias” extension that we learned about in part 2, as we will need it to launch our extension with elevated privileges. Last but not least, you will also need to add namespace definitions for “uap3” and “desktop” in the root <Package> element per below snippet.

<Package
  ...
  xmlns:uap3="http://schemas.microsoft.com/appx/manifest/uap/windows10/3"
  xmlns:desktop="http://schemas.microsoft.com/appx/manifest/desktop/windows10"
  IgnorableNamespaces="uap3 desktop ...">
  ...

  <Extensions>

    <desktop:Extension Category="windows.fullTrustProcess"
                       Executable="Elevated Extension\Elevated Extension.exe">
      <desktop:FullTrustProcess>
        <desktop:ParameterGroup GroupId="User" Parameters="/user" />
        <desktop:ParameterGroup GroupId="Admin" Parameters="/admin" />
      </desktop:FullTrustProcess>
    </desktop:Extension>

    <uap3:Extension Category="windows.appExecutionAlias"
                    Executable="Elevated Extension\Elevated Extension.exe"
                    EntryPoint="Windows.FullTrustApplication">
      <uap3:AppExecutionAlias>
        <desktop:ExecutionAlias Alias="ElevatedExtension.exe" />
      </uap3:AppExecutionAlias>
    </uap3:Extension>

  </Extensions> 
  ...
</Package>

Note that the the two parameter groups I am defining for the desktop extension are not mandatory. I am doing it here only for this sample to later demonstrate how we can launch the extension with either user privileges or admin privileges.

7. Adding the launcher code

Now that solution and manifest are set up and configured correctly, we are ready to add the code to launch the extension from the UWP – as either elevated or regular process. First lets add some XAML to MainPage.xaml:

<StackPanel HorizontalAlignment="Center" VerticalAlignment="Center">
    <Button Content="Run Backgroundprocess as User"
            Click="ButtonUser_Click"
            HorizontalAlignment="Stretch"
            FontSize="32" Margin="3"/>
    <Button Content="Run Backgroundprocess as Admin"
            Click="ButtonAdmin_Click"
            HorizontalAlignment="Stretch"
            FontSize="32" Margin="3"/>
</StackPanel>

Before we can add the code-behind for the button click handlers, we need to add a reference to the UWP desktop extension SDK, because that’s where the FullTrustProcessLauncher API is defined. This API, btw, is not new in the 1809 update. It has been around since the Creators Update (SDK build 14393), but it’s recommended you reference the Desktop extension SDK that matches your target version, i.e. 17763 in our case.

addDeskExte

Next we add the two button click handlers in MainPage.xaml.cs. The key piece is the FullTrustProcessLauncher API. We are passing in the parametergroup to indicate to the extension whether we need to run it as user or with admin privileges. The processing of this parameter is application logic as we will see in the next step. Wrapping this call into an “IsApiContractPresent” is an important practice to ensure our universal app can run without crashing also on non-desktop devices that don’t support the FullTrustProcessLauncher API:

private async void ButtonUser_Click(object sender, RoutedEventArgs e)
{
  if (ApiInformation.IsApiContractPresent(
      "Windows.ApplicationModel.FullTrustAppContract", 1, 0))
  {
    await
      FullTrustProcessLauncher.LaunchFullTrustProcessForCurrentAppAsync("User");
  }
}

private async void ButtonAdmin_Click(object sender, RoutedEventArgs e)
{
  if (ApiInformation.IsApiContractPresent(
      "Windows.ApplicationModel.FullTrustAppContract", 1, 0))
  {
    await
      FullTrustProcessLauncher.LaunchFullTrustProcessForCurrentAppAsync("Admin");
  }
}

The final piece of code we need to add now goes into Program.cs of the “Elevated Extension” project. This is the code that handles the “/user” vs “/admin” parameter. If the latter has been specified we want to do the same as in part 2, i.e. relaunch the process as admin using the AppExecutionAlias. In case you are wondering why the strings we are handling here are different from the strings being passed to the launch API (“/user vs “User”), take a look at the ParameterGroup entries we added to the manifest. It maps a friendly name (“User”) to what could be in some case a fairly convoluted long string of parameters (“/user” in our case).

static void Main(string[] args)
{
    if (args.Length > 2)
    {
        if (args[2] == "/admin")
        {
          string aliasPath =
      Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData) +
      @"\microsoft\windowsapps\ElevatedExtension.exe";

          ProcessStartInfo info = new ProcessStartInfo();
          info.Verb = "runas";
          info.UseShellExecute = true;
          info.FileName = aliasPath ;
          Process.Start(info);
          return;
        }
    }

    AutoResetEvent areWeDone = new AutoResetEvent(false);
    // do some task, then exit
    areWeDone.WaitOne(10000); 
}

8. Run and test the solution

Now we are ready to run and test our solution, before hitting F5 make sure the “Package” project is set as startup project and you have added the “allowElevation” capability in its appx manifest:

  <Capabilities>
    <Capability Name="internetClient" />
    <rescap:Capability Name="runFullTrust" />
    <rescap:Capability Name="allowElevation" />
  </Capabilities>

When the UWP is up-and-running use the buttons to launch the extension either as user or as admin. Observe the result in TaskManager, remember that the extension has been coded to exit itself after 10sec in this example:

uwpElevated

This concludes my 3-part tutorial about the new ‘allowElevation’ capability. Please leave your feedback and questions as comments and I will do my best to address them in a timely fashion.

Picture0

Advertisements

App Elevation Samples – Part 2

Picture4

If you haven’t read part 1 of this series yet, please start there first. I have covered the “hello world” version of a packaged app that requires elevation at launch time. In this post I want to show how a packaged app can dynamically elevate itself after it has been launched normally.

TL;DR

I already have the 1809 update and the Windows SDK 17763 (or later) installed. Just show me the code on GitHub …
Sample Code Repo on Github

Step-by-Step Tutorial

1. Create a new desktop application

Make sure you run Windows 10 with the 1809 update and have the Windows SDK 17763 (or later) installed. Now create a new C# WPF desktop application in Visual Studio 2017 (can be any type of desktop application really):

FileNewProject

2. Convert to a packaged app

Add a Windows Packaging Project to the solution and call it “Self Elevation”:

addPackage2

Be sure to set both “Minimum” and “Target” version to 17763 or higher as build number 17763 maps to the 1809 update for Windows 10:

setVersions

Reference your WPF application from the packaging project by right-clicking on the “Application” node under the “Package” project and selecting “Add Reference …” :

addRef2

Now set the “Self Elevation” package project as your startup project in the solutions settings per below screenshot, and then hit F5 to see your empty packaged WPF app running.

setStartup2

3. Add code to elevate dynamically

In contrast to the previous example, we are not adding an application manifest to require elevation. We want the application to run at normal user privileges by default, and only request elevation dynamically when it is indeed needed.

To demonstrate this, let’s add a button to the XAML user interface in MainPage.xaml:

<Grid>
    <Button Content="Elevate Me!"                
            HorizontalAlignment="Center"
            VerticalAlignment="Center"
            FontSize="48"
            Click="Button_Click"/>
</Grid>

And add the following code-behind in MainPage.xaml.cs, to re-launch the app with elevated privileges:

private void Button_Click(object sender, RoutedEventArgs e)
{
  string aliasPath =
    Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData) +
    @"\microsoft\windowsapps\SelfElevationWPF.exe";

  ProcessStartInfo info = new ProcessStartInfo();
  info.Verb = "runas";
  info.UseShellExecute = true;
  info.FileName = aliasPath;
  Process.Start(info);

  App.Current.Shutdown();
}

To re-launch as admin we need to call Process.Start (or CreateProcess), but packaged apps can’t just be launched via these APIs. Luckily, packaged apps have the concept of AppExecutionAlias, which is a 0-byte EXE that facilitates the packaged app launch for classic invokers – such as CMD or Process.Start.

The first line is about getting the full path to this 0-byte AppExecutionAlias EXE file for our WPF app. Then we are creating the parameters for the Process.Start call. Key piece here is the “runas” verb, that will trigger the request for elevation for the relaunched app.

Finally, once we have relaunched ourselves as elevated, we can shutdown our current instance.

4. Declare the AppExecutionAlias

Packaged apps don’t have an AppExecutionAlias by default – the developer needs to be explicit about supporting this additional launch contract in the appx manifest. So let’s add this extension to our Package.appxmanifest file inside the <Application> node.

<Package
  ...
  xmlns:uap3="http://schemas.microsoft.com/appx/manifest/uap/windows10/3"
  xmlns:desktop="http://schemas.microsoft.com/appx/manifest/desktop/windows10"
  IgnorableNamespaces="uap3 desktop ...">
  ...

  <Extensions>
    <uap3:Extension Category="windows.appExecutionAlias"
                    Executable="Self Elevation WPF\Self Elevation WPF.exe"
                    EntryPoint="Windows.FullTrustApplication">
      <uap3:AppExecutionAlias>
        <desktop:ExecutionAlias Alias="SelfElevationWPF.exe" />
      </uap3:AppExecutionAlias>
    </uap3:Extension>
  </Extensions> 
  ...
</Package>

Now we are almost done – last thing to add to our appx manifest is the same “AllowElevation” capability that we have already used in the previous post:

  <Capabilities>
    <Capability Name="internetClient" />
    <rescap:Capability Name="runFullTrust" />
    <rescap:Capability Name="allowElevation" />
  </Capabilities>

That’s it. Now we can test via F5 or by deploying the app package on our test machine. Use TaskManager to observe how the “Elevation” status changes from ‘false’ to ‘true’ after clicking the button and accepting the elevation request.

taskman

In the next post in this series I will cover how to trigger elevated code execution from a pure UWP process running in the app container by combining the concepts of the “fullTrustProcess” desktop extension, the “allowElevation” capability and the “appExecutionAlias” extension.

Part 3 – UWP with elevated extension

App Elevation Samples – Part 1

Picture0

Elevate your packaged apps with the new ‘allowElevation’ capability. With the 1809 update for Windows 10, apps can now declare this new capability in order to require elevation – or elevate themselves dynamically when needed. I will explain the new capability with three samples in this mini-series of posts:

1 – “Hello Elevated World” – simple packaged elevated desktop app (this post)
2 – Desktop app self-elevates dynamically when needed
3 – Elevated extension to a UWP application

Hello Elevated World

TL;DR

I already have the 1809 update and the Windows SDK 17763 (or later) installed. Just show me the code  …
Sample Code Repo on Github

Step-by-Step Tutorial

1. Create a new desktop application

Make sure you run Windows 10 with the 1809 update and have the Windows SDK 17763 (or later) installed. Now create a new C# desktop console application in Visual Studio 2017 (can be any type of desktop application really):
createApp

2. Add a few lines for testing

static void Main(string[] args)
{
    Console.Title = "Hello Elevated World!";
    Console.WriteLine("Press any key ...");
    Console.ReadKey();
}

3. Add app manifest and require elevation

Project | Add New Item … | Application Manifest File
addManifestOpen the app.manifest file and change the requested execution level from “asInvoker” to “highestAvailable”:

<requestedExecutionLevel level="highestAvailable" uiAccess="false" />

Now verify that running the unpackaged app will run as elevated after popping up the UAC prompt (unless you have turned this off).

4. Convert to a packaged app

Add a Windows Packaging Project to the solution and call it “Package”:addPackage

Be sure to set both “Minimum” and “Target” version to 17763 or higher as build number 17763 maps to the 1809 update for Windows 10:

setVersions

Reference your desktop console application from the packaging project by right-clicking on the “Application” node under the “Package” project and selecting “Add Reference …” :addRef

Now set the “Package” project as your startup project in the solutions settings:setStartup

5. Final Step: add the ‘allowElevation’ capability declaration

If you now hit F5 to run your packaged console app you will see the expected error stating that the launch failed because it requires elevation. This is the correct default behavior for packaged applications.

error

With the new ‘allowElevation’ capability apps can now properly disclose that they will/may request to run with elevated privileges. So let’s add this capability in Package.appxmanifest:

  <Capabilities>
    <Capability Name="internetClient" />
    <rescap:Capability Name="runFullTrust" />
    <rescap:Capability Name="allowElevation" />
  </Capabilities>

Hit F5 again and you will see your packaged app requesting elevated privileges and then run as elevated process:elevated

And we are done! You have now implemented your first packaged app that runs with elevated privileges. To create the actual distribution package for your application you would follow the usual steps: Select the “Package” project in solution explorer, then go to Project | Store | Create App Packages …

In the next post I will cover how a packaged app can self-elevate itself dynamically, for example to access a file that requires administrative privileges.

AllowElevation Samples Part 2 – Dynamic Self Elevation