Ask Kathleen

Banish UAC Issues

User Account Control (UAC) issues usually result from improperly allowing administrator-level access in your applications. Learn how UAC works and make such problems a thing of the past.

Q. My users are getting a security error when they run my application in Windows Vista. This application ran fine on XP, and I think it has something to do with User Account Control (UAC). All my users have administrator privileges on their own machines. I'm using Visual Basic in Visual Studio 2005 on XP for development. What do you think is wrong and how do I fix it?

A. Your problem probably is a UAC issue. Historically, some programs have been written in a manner that required administrator privileges. After years of cajoling developers to write their applications in a more secure manner and encouraging users to run with lower privileges, Microsoft gave up and fundamentally changed the security model, knowing it would break some applications.

In Vista, users don't run with administrator privileges, even if they log on as administrator. When they log on to an account approved for administrator access, they run as a normal user, and Vista remembers their password so it can elevate the account when users approve doing so explicitly. This is critical to improving security because it limits the damage a rogue application can do without your consent.

Knowing that many programs were not ready for this change, Microsoft created a side channel work-around for the two most common ways applications can fail UAC: writing to the Registry and writing to files within system directories. These scenarios still work under Vista because accessing these locations was once common practice, and it's bad to break legacy applications. This isn't specifically part of your problem, but I'll include an explanation after addressing your problem because it can really trip programmers up, and it's tricky to solve in .NET 2.0. I'll also explain how .NET 3.5 makes your life easier.

The first thing you must do is find out what is causing the UAC problem. Ideally, you should write your applications so they don't need administrator control, but that isn't always possible. When you migrate these types of apps to Vista, you get UAC errors only if you do something that requires administrator control. The problem will also be difficult to find unless you can run Visual Studio in Vista. Under Vista, start Visual Studio without administrator privileges. To ease other debugging issues, my menu shortcut runs Visual Studio 2005 with elevated privileges, but I'm careful not to use my shortcut when testing this issue. Instead, I double-click on the executable in Windows Explorer to run as a normal user. When you're debugging in Visual Studio, you run at whatever privileges the debugger has. You should get the same exception your users get when you run an administrator task. I've included an application in the online code that fails (intentionally) because it accesses the event logs.

Once you find the problem, you have three choices: Remove the functionality from your application, isolate the administrator functionality in a different application, or require that your application run as administrator. One of the first two solutions is always preferable, and you should only require administrator privileges if the nature of your application is to do administrator tasks. Administrator tasks are generally not needed in most business applications, so you should make an effort to remove the functionality if there is any other way to accomplish the task. If your app requires administrator privileges, but the overall purpose of your application isn't to perform administrative tasks, you need to isolate the administrator functionality in an ancillary application.

Once you've refactored the administrative tasks into their own application, you might want to access the new administrative tasks app from the main application. You can do this through a button-click at a logical point in your main application. A key aspect of the Vista security model is that you warn your user when an action requires elevated permissions by displaying the shield icon (see Figure 1). You can display it on a button using the SendMessage API:

Public Class Win32
     Friend Declare Auto Function SendMessage _
          Lib "user32.dll" ( _
          ByVal hWnd As IntPtr, _
          ByVal msg As UInt32, _
          ByVal sparam As UInt32, _
          ByVal lparam As UInt32) _
          As UInt32
     Friend Const BCM_SETSHIELD As Int32 = &H160C
End Class 

I put the declaration in a separate class for encapsulation. Other Win API declarations could also be included in this class. A utility function sets the icon:

Private Shared Sub AddShield( _
     ByVal button As Button)
     button.FlatStyle = _
          Windows.Forms.FlatStyle.System
     Dim success As UInt32 = _
          Win32.SendMessage( _
          button.Handle, _
          Win32.BCM_SETSHIELD, 0, 1)
     End Sub

When your application runs under XP or another down-level operating system, the shield message is ignored.

In the test app, the AddShield method is on the form, but in a real application it would probably be in a utility library. The AddShield method sets the button's FlatStyle to System. The System style is required for the shield to appear, and it's easier to set the FlatStyle when the shield is used, rather than expecting later programmers to remember this detail. For debugging purposes, this code captures the success return value. It calls the AddShield method from the OnLoad method unless the user is already an administrator:

Protected Overrides Sub OnLoad( _
     ByVal e As System.EventArgs)
     MyBase.OnLoad(e)
     If Not IsAdmin() Then
          AddShield(Me.elevateButton)
     End If
End Sub

You can determine whether the user is an administrator by checking whether he or she is in the administrator group:

Private Function IsAdmin() As Boolean
     Return My.User.IsInRole( _
          Microsoft.VisualBasic.ApplicationServices. _
          BuiltInRole.Administrator)
End Function

Application Services is one of the coolest things in the Microsoft.VisualBasic namespace, but you can accomplish the same thing in C# with a little more code and literals. This works because defining a user as an administrator under Vista means the user's credentials are saved so they can elevate as required without retyping a password. The administrator-user runs as a normal user by default, and he or she isn't a member of the Administrator group unless his or her privileges are elevated explicitly for a specific process.

You can implement the button behavior once you display the shield. The shield indicates that pressing the button will attempt to elevate the user's privileges. Calling the ancillary application requires an instance of the ProcessStartInfo class, located in the diagnostics namespace (see Listing 1). The ProcessStartInfo class contains information about how the new process will run, including the name of the executable (FileName) and the application directory.

The ancillary application runs in the same directory and looks for the executable in the main application's executable directory. This requires manually copying the executable after you build it. This is inconvenient, but it better reflects application deployment. The ErrorDialog settings cause the UAC to be modal to the main application, which you should include for correct UAC behavior.

This statement starts the process once you populate the ProcessStartInfo instance with the correct values:

Dim process As Diagnostics.Process = _
     Diagnostics.Process.Start(startInfo)

You should call this code in a Try block because you should anticipate failure if the user cancels the elevation process.

You might or might not want the administrator application to appear modal with the main application. You can't actually make the new application truly modal with the main application, but you can make it appear that way if you block the main application:

     process.WaitForExit()

Vista's Task Manager and other applications restart themselves when the user needs to perform operations that require administrator privileges. This is undesirable in many business applications because the user's state and current navigation position are lost. But if you have a simple utility and want to restart the main application, you can replace the code that sets the ProcessStartInfo's file name, rather than isolating your administrator tasks in a second application:

startInfo.FileName = _
     Windows.Forms.Appliation.ExecutablePath

If you restart, you'll probably want to close the initial (current) application, so you don't have two instances running; two running instances are likely to be confusing and have concurrency issues. You can close the current application with a call to Application.Exit:

System.Windows.Forms.Application.Exit()

Also, be sure to remove the WaitForExit method if you restart the current application, so you don't block the initial app from closing.

What I've covered so far should be enough to help you solve most of the problems you might encounter related to UAC, but there are a few additional details you need to address. If the user navigates to the administrator application and double-clicks on its executable, it will run without administrator privileges and therefore fail. This is a symptom of a deeper problem. Vista supports embedded manifests, and these are required for Vista logo support. If your application does not have a manifest, it runs as a normal user. If a manifest is embedded, it can specify that the application demands administrator permissions:

<?xml version="1.0" encoding="utf-8" ?> 
     <assembly xmlns="urn:schemas-microsoft-com:asm.v1" 
          manifestVersion="1.0">
     <assemblyIdentity version="1.0.0.0" 
          processorArchitecture="X86" 
          name="MyProjectName" type="win32" /> 
     <description>MyProjectDescription</description> 
     <trustInfo xmlns=
          "urn:schemas-microsoft-com:asm.v2">
          <security>
          <requestedPrivileges>
               <requestedExecutionLevel level=
                    "requireAdministrator" /> 
               </requestedPrivileges>
     </security>
     </trustInfo>
</assembly>

Requesting an execution level of "requireAdministrator" means that you're requesting administrator privileges; in contrast, "asInvoker" runs as the normal user.

Windows Explorer recognizes this and displays the shield icon for applications that require elevation (see Figure 2). Creating the manifest is easy, but you must embed it as a resource. Some people suggest it's best to do this manually through resource file techniques, but embedding it in your executable after the build is a better approach. C# provides post-build events that recognize several important tokens for location and file names. In C#, you can include this instruction in your post-build events:

"$(DevEnvDir)..\..\Common7\Tools\Bin\mt.exe" 
     -manifest 
     "$(ProjectDir)$(TargetName).exe.manifest" 
     –outputresource:"$(TargetDir)$(
          TargetFileName)";#1

VB requires a little more effort: You must replace the [Path], [ManifestFileName], and [ExecutableFileName] with the project path and file names. Then run this code from the Visual Studio command prompt or as part of your build process:

mt -manifest 
     "[Path]\[ManifestFileName]" 
     -outputresource: 
     "[Path]\bin\Debug\[ExecutableFileName]" 

Note that you enter this snippet as a single line, and the outputresource argument is followed by a colon.

I mentioned at the start that Vista supplies a stopgap measure for the two most common scenarios where programmers historically demanded administrator privileges. Vista supplies virtualization for protected registry entries and files in system directories. This keeps applications from breaking outright, but it can lead to unexpected behavior, particularly when multiple users on a machine expect to share the same registry entries and files. You can fix this by removing your use of these protected locations. The problem lies in testing your application to ensure that you don't depend on virtualization.

You can disable virtualization by supplying a manifest.

Vista assumes that applications without a manifest are not Vista-aware and might need virtualization to help them operate correctly. Vista also assumes that applications with a manifest are Vista-aware and have their UAC issues in order, so they don't need virtualization.

Adding a manifest with the requestedExecutionLevel set to "asInvoker" is a great way to test your application. A benefit of using the manifest approach to testing is that it puts you one step closer to Vista logo certification.

If embedding a Vista manifest seems like a lot of trouble, you'll be happy to learn that Visual Studio 2008 includes tools for both C# and Visual Basic that make it easy to embed manifests and access them for editing.

[I'm always learning from other programmers, and this topic is no different. Special thanks to Daniel Moth and Bill McCarthy for providing information related to this topic. --K.D. ]

About the Author

Kathleen is a consultant, author, trainer and speaker. She’s been a Microsoft MVP for 10 years and is an active member of the INETA Speaker’s Bureau where she receives high marks for her talks. She wrote "Code Generation in Microsoft .NET" (Apress) and often speaks at industry conferences and local user groups around the U.S. Kathleen is the founder and principal of GenDotNet and continues to research code generation and metadata as well as leveraging new technologies springing forth in .NET 3.5. Her passion is helping programmers be smarter in how they develop and consume the range of new technologies, but at the end of the day, she’s a coder writing applications just like you. Reach her at [email protected].

comments powered by Disqus

Featured

  • Compare New GitHub Copilot Free Plan for Visual Studio/VS Code to Paid Plans

    The free plan restricts the number of completions, chat requests and access to AI models, being suitable for occasional users and small projects.

  • Diving Deep into .NET MAUI

    Ever since someone figured out that fiddling bits results in source code, developers have sought one codebase for all types of apps on all platforms, with Microsoft's latest attempt to further that effort being .NET MAUI.

  • Copilot AI Boosts Abound in New VS Code v1.96

    Microsoft improved on its new "Copilot Edit" functionality in the latest release of Visual Studio Code, v1.96, its open-source based code editor that has become the most popular in the world according to many surveys.

  • AdaBoost Regression Using C#

    Dr. James McCaffrey from Microsoft Research presents a complete end-to-end demonstration of the AdaBoost.R2 algorithm for regression problems (where the goal is to predict a single numeric value). The implementation follows the original source research paper closely, so you can use it as a guide for customization for specific scenarios.

  • Versioning and Documenting ASP.NET Core Services

    Building an API with ASP.NET Core is only half the job. If your API is going to live more than one release cycle, you're going to need to version it. If you have other people building clients for it, you're going to need to document it.

Subscribe on YouTube