Introduction
This article explains how to run a ClickOnce application as an Administrator under Windows 8, when the app also includes a SQL Compact Database.
My particular application must run as Administrator because it needs to pass data to a MYOB accounting data file, and the MYOB ODBC Driver requires elevated permission under Windows 8. Because the app is to be distributed to many different customers, I like the deployment simplicity that ClickOnce offers. However, ClickOnce deployments do not support changing the requestedExecutionLevel
in the manifest to "requireAdministrator".
Background
I saw a few articles such as this which put me on the right track. The code checks if the app is running as Administrator, and if not, restarts the app as an Administrator. However, I found a couple of problems with this solution. First, "Application.Current.Shutdown()" does not actually shutdown the application immediately, but continues to execute code, as I found described in several articles. I found this caused my app to keep restarting ad inifinitum under Windows 7. Secondly, when the app is restarted as Administrator, it loses its "ClickOnce" status. Therefore, any references to System.Deployment.Application result in error - which has implications for the path to my app's SQL Compact database. The reference to ApplicationDeployment.CurrentDeployment.DataDirectory
becomes invalid and the app chooses to look in the executable's working directory for the database, which of course it cannot find, and returns the error "The underlying provider failed on Open. Database was not found".
After several hours (actually, several days), I finally found the workarounds to all these issues, and my ClickOnce WPF app now runs beautifully under Windows 8, happily connecting to MYOB and sharing data.
Using the code
Here is the full code listing.
Class Application
Protected Overrides Sub OnStartup(e As StartupEventArgs)
Dim blnCloseInstance As Boolean = False
Dim osVer As System.OperatingSystem = System.Environment.OSVersion
If osVer.Version.Major > 6 Or (osVer.Version.Major = 6 And osVer.Version.Minor >= 2) Then
If Not IsRunningAsAdministrator() Then
Dim processStartInfo As New ProcessStartInfo(Assembly.GetEntryAssembly().CodeBase)
processStartInfo.Arguments = "" & Chr(34) & ApplicationDeployment.CurrentDeployment.DataDirectory & Chr(34) & ""
processStartInfo.UseShellExecute = True
processStartInfo.Verb = "runas"
Process.Start(processStartInfo)
blnCloseInstance = True
Application.Current.Shutdown()
End If
End If
If blnCloseInstance = False Then
If IsRunningAsAdministrator() = True Then
Dim arguments As String() = Environment.GetCommandLineArgs()
Try
Dim datadir As String = arguments(1)
AppDomain.CurrentDomain.SetData("DataDirectory", datadir)
Catch ex As Exception
End Try
End If
MyBase.OnStartup(e)
End If
End Sub
Public Function IsRunningAsAdministrator() As Boolean
Dim windowsIdentity__1 As WindowsIdentity = WindowsIdentity.GetCurrent()
Dim windowsPrincipal As New WindowsPrincipal(windowsIdentity__1)
Return windowsPrincipal.IsInRole(WindowsBuiltInRole.Administrator)
End Function
End Class
This is a WPF application, so I have overridden Application_OnStartup
.
First I check the current operating system. I found the ClickOnce app runs fine under Windows 7, therefore there is no need to check whether the app is running as Administrator under Windows 7.
Dim osVer As System.OperatingSystem = System.Environment.OSVersion
If osVer.Version.Major > 6 Or (osVer.Version.Major = 6 And osVer.Version.Minor >= 2) Then
If the OS is Windows 8 or higher, the app then checks if it is running as Administrator. On the first execution, this will be False - so the app gets restarted by starting a new process using the same assembly name. The important thing to note here is the argument passed to the process - which is the path to the SQL Compact datbase. This is necessary for when the app restarts in non-ClickOnce mode.
If Not IsRunningAsAdministrator() Then
Dim processStartInfo As New ProcessStartInfo(Assembly.GetEntryAssembly().CodeBase)
processStartInfo.Arguments = "" & Chr(34) & ApplicationDeployment.CurrentDeployment.DataDirectory & Chr(34) & ""
processStartInfo.UseShellExecute = True
processStartInfo.Verb = "runas"
Process.Start(processStartInfo)
blnCloseInstance = True
Application.Current.Shutdown()
End If
The boolean variable blnCloseInstance
determines whether the current instance will be closed. It only gets set to True when the current OS is Windows 8 and the app is not running as Administrator.
The current instance of the app is then shutdown. As noted above, Application.Current.Shutdown()
does not immediately exit the application - the thread still runs and code continues to execute. Therefore, I have ensured the remaining code in the sub is only executed when blnCloseInstance = False. So on the first execution under Windows 8, this code will not be executed.
When the app restarts in non-ClickOnce mode, it will be running as Administrator, and blnCloseInstance will be False. Therefore only this code will execute:
If blnCloseInstance = False Then
If IsRunningAsAdministrator() = True Then
Dim arguments As String() = Environment.GetCommandLineArgs()
Try
Dim datadir As String = arguments(1)
AppDomain.CurrentDomain.SetData("DataDirectory", datadir)
Catch ex As Exception
End Try
End If
MyBase.OnStartup(e)
End If
Being in non-ClickOnce mode, the app does not support references to System.Deployment.Application, and it will report an error trying to locate the database. The code above sets the SQL Compact database location manually, using the argument passed in when the process was started.
History
Version 1 (19 July 2014): Original version posted to CodeProject.
Version 2 (21 July 2014): Minor change to text.