(Updated 5/10/2012 – I originally claimed that it was “trivial” to make a ClickOnce app require elevation – a commenter pointed out that ClickOnce does not support the requireAdministrator execution level at all. This does not let the vendor off the hook – they should not have used ClickOnce to deploy software that requires features ClickOnce does not support… FYI – there are workarounds from the deployment side)
One of our vendors distributes a couple tools as ClickOnce applications, but these applications perform privileged tasks that do not work with UAC enabled. A ClickOnce application cannot be elevated, nor can it be specified that it requires elevation, so the only way this application would have ever passed testing is if UAC was disabled on the developers’ machines, or Visual Studio is run as administrator)
Rant: UAC has been around since Windows Vista was released in 2006. As of this writing, that would be 6 years. Windows 7 has been out for 3 years. As much as some people might dislike the extra dialogs, UAC is a very good thing, and should not be disabled. There is no excuse for applications that do not handle UAC correctly. Adding the appropriate application manifest is not difficult, and when using ClickOnce it is practically trivial (5/10/2012: Errata: ClickOnce Doesn’t support UAC – I was thinking about Full Trust) and if an application requires elevation it should be distributed standalone or with an MSI setup project.
Don’t use ClickOnce for applications that require elevation; Add the Application Manifest with a setup project to require elevation; or better yet modify your app to not require elevation at all, but do not just pretend it doesn’t exist. 6 years… come on!
Ok, now that my rant is out of the way, if you are in a similar situation – a ClickOnce app that needs elevation to run, but doesn’t request it is a real pain in the butt.
“Run as Administrator” is conspicuously missing from the context menu:
if you try to create a shortcut to the app you will have the same problem. If you try to open the raw .appref-ms file, notepad automatically gives you the contents of the executable… It’s a bloody mess.
The (simple) solution requires both a batch file and a shortcut.
- Right Click on the ClickOnce shortcut, and select Properties. Copy the content of the location field and paste into notepad.
Add the trailing slash, and paste (or type) the filename (in this case “GoSyncUpdater”) followed by “.appref-ms”
eg: “C:\Users\user\AppData\Roaming\Microsoft\Windows\Start Menu\Programs\Company Name\GoSyncUpdater.appref-ms”
Finally surround this with quotes, and save the file as a .bat file (e.g. runasadmin.bat)
At this point, you can right-click on the batch file, and you will have the option to “Run as Administrator” – If this is all you need you are done.
However, if you go to the Compatibility property tab to make the elevation required, the Privilege Level is disabled / grayed out…
- Create a shortcut to your batch file.
(You will notice that the Compatibility tab is similarly useless) - Edit the properties, Under the Shortcut tab Click “Advanced…”
- OK out of the dialogs.
You now have a shortcut to a batch file that runs a shortcut to an executable. Convoluted, but it works. (The reason we don’t just make a shortcut to the executable directly is that the path will change when it is upgraded)
If you know of a better way, feel free to share.
In some cases this batch file solution won’t always work. If this is the case download and run Application Compatibility Toolkit, – (1) new database – (2) fix – (3) choose application -(4.1) click as administrator, (4.2) as invoker, (5) save, then click (6) install and you’re done.
Microsoft Application Compatibility Toolkit
http://www.microsoft.com/downloads/details.aspx?FamilyID=24da89e9-b581-47b0-b45e-492dd6da2971&displaylang=en
Any idea under what circumstances the batch file doesn’t work?
I’ve use the class below in one of my apps.
Imports System.Security.Principal
Imports System.Security.AccessControl
Class clsAdmin
'Declare API
Private Declare Ansi Function SendMessage Lib "user32.dll" Alias "SendMessageA" (ByVal hwnd As Integer, ByVal wMsg As Integer, ByVal wParam As Integer, ByVal lParam As String) As Integer
Private Const BCM_FIRST As Int32 = &H1600
Private Const BCM_SETSHIELD As Int32 = (BCM_FIRST + &HC)
Public Shared Function IsVistaOrHigher() As Boolean
Return Environment.OSVersion.Version.Major < 6
End Function
' Checks if the process is elevated
Public Shared Function IsAdmin() As Boolean
Dim id As WindowsIdentity = WindowsIdentity.GetCurrent()
Dim p As WindowsPrincipal = New WindowsPrincipal(id)
Return p.IsInRole(WindowsBuiltInRole.Administrator)
End Function
Public Shared Function IsDebug() As Boolean
Return System.Diagnostics.Debugger.IsAttached
End Function
Public Shared Function Is64Bit() As Boolean
If Microsoft.Win32.Registry.LocalMachine.OpenSubKey("Hardware\Description\System\CentralProcessor").GetValue("Identifier").ToString.Contains("x86") Then Return False
Return True
End Function
' Add a shield icon to a button
Public Shared Sub AddShieldToButton(ByRef b As Button)
b.FlatStyle = FlatStyle.System
SendMessage(CInt(b.Handle), BCM_SETSHIELD, 0, CStr(&HFFFFFFFF))
End Sub
Public Shared Sub RestartElevated(Optional ByVal sArgs As String = "", Optional ByVal bKillThisProcess As Boolean = True)
Restart(sArgs, bKillThisProcess, True)
End Sub
' Restart the current process with administrator credentials
Public Shared Sub Restart(Optional ByVal sArgs As String = "", Optional ByVal bKillThisProcess As Boolean = True, Optional ByVal bAsAdmin As Boolean = False)
Dim startInfo As ProcessStartInfo = New ProcessStartInfo()
startInfo.UseShellExecute = True
startInfo.WorkingDirectory = Environment.CurrentDirectory
startInfo.FileName = Application.ExecutablePath
If bAsAdmin Then startInfo.Verb = "runas"
If sArgs = "-" Then
' uses the same command line arguments as this current process
sArgs = ""
Dim s() As String = System.Environment.GetCommandLineArgs()
For i As Integer = 1 To s.Length - 1
Dim sArg As String = s(i)
If sArg = "" Then Continue For
sArgs = sArgs & " """ & sArg & """"
Next
sArgs = sArgs.Trim()
End If
startInfo.Arguments = sArgs
Try
Dim p As Process = Process.Start(startInfo)
Catch ex As Exception
Return 'If cancelled, do nothing
End Try
If bKillThisProcess Then Application.Exit()
End Sub
Public Shared Function SetPermModifyEveryone(ByVal sFile As String, ByVal bAllow As Boolean) As Boolean
Dim UserAccount As String = "Everyone"
Dim FileInfo As IO.FileInfo = New IO.FileInfo(sFile)
Dim FileAcl As New FileSecurity
Dim bRet As Boolean = True
Try
If bAllow Then
FileAcl.AddAccessRule(New FileSystemAccessRule(UserAccount, FileSystemRights.Modify, AccessControlType.Allow))
Else
FileAcl.AddAccessRule(New FileSystemAccessRule(UserAccount, FileSystemRights.Modify, AccessControlType.Deny))
End If
'FolderAcl.SetAccessRuleProtection(True, False) 'uncomment to remove existing permissions
FileInfo.SetAccessControl(FileAcl)
bRet = True
Catch ex As Exception
bRet = False
End Try
Return bRet
End Function
End Class
The code above, when using clsAdmin.RestartElevated, will ask the user if they want to allow the app to run as administrator or cancel. There are a couple other useful functions there as well.
Sorry for double posting.
And in case anyone else reads this article because they found it with a Google search, another version of Todd T’s code for relaunching the app with elevated privilege can be found here:
https://www.codeproject.com/tips/627850/clickonce-deployment-vs-requestedexecutionlevel-eq