Wednesday 23 February 2011

Running a ClickOnce Application as Administrator

I've been using ClickOnce to deploy our Windows apps for about 12 months now, and it's been an absolutely painless way to handle installation and application updates.
One of the reasons it's so painless is that ClickOnce doesn't require admin privileges to install apps on a machine. However this also means that you cannot launch a ClickOnce app with admin privileges.
Until recently this hasn't been a problem for us, but due to some obscure requirement that 64-bit Windows requires admin privileges to access a 32-bit ODBC data source, this shortfall has reared its ugly head.
The official Microsoft stance is that you cannot use ClickOnce if admin privilges are required, and instead install using Windows Installer or similar. However this really didn't appeal because I'd need implement a new way of handling application updates and have to deal with a whole lot of change.
I came across this article by Charles Engelke, which discusses how he used a ClickOnce app to launch a secondary app as Administrator. This sounded really cool, so I wondered if it would be possible for a ClickOnce app to re-launch itself as administrator. It turns out you can.
Here's how we did it:
Our ClickOnce app is WPF, so it's entry point is via the Application_Startup method App.xaml - however you probably already know the entry point for your app. We added the following:
private bool IsRunAsAdministrator()
{
var wi = WindowsIdentity.GetCurrent();
var wp = new WindowsPrincipal(wi);

return wp.IsInRole(WindowsBuiltInRole.Administrator);
}

private void Application_Startup(object sender, StartupEventArgs e)
{
if (!IsRunAsAdministrator())
{
    // It is not possible to launch a ClickOnce app as administrator directly, so instead we launch the
    // app as administrator in a new process.
    var processInfo = new ProcessStartInfo(Assembly.GetExecutingAssembly().CodeBase);

    // The following properties run the new process as administrator
    processInfo.UseShellExecute = true;
    processInfo.Verb = "runas";
        
    // Start the new process
    try
    {
        Process.Start(processInfo);
    }
    catch (Exception)
    {
        // The user did not allow the application to run as administrator
        MessageBox.Show("Sorry, this application must be run as Administrator.");
    }

    // Shut down the current process
    Application.Current.Shutdown();
}
else
{
    // We are running as administrator
        
    // Do normal startup stuff...
}
}
The idea here is that if the current process is not running as administrator, then launch a new process as administrator, then shut down the current process. The new process will realise it's running as administrator and function as normal.
The method we're using to run as administrator is to set the following properties or ProcessStartInfo:
    // The following properties run the new process as administrator
    processInfo.UseShellExecute = true;
    processInfo.Verb = "runas";
I have a feeling this is not best practice (I believe the "correct" way is to embed a manifest in your application with a requestedExecutionLevel element) - but this way was much easier since the process will only ever be launched from within ClickOnce.