Show Changes Show Changes
Edit Edit
Print Print
Recent Changes Recent Changes
Subscriptions Subscriptions
Lost and Found Lost and Found
Find References Find References
Rename Rename
Search

History

4/14/2010 5:31:20 PM
List all versions List all versions
How To Develop Code As A Non Admin
.

HowToDevelopCodeAsANonAdminDiscussion

The trick to developing code in a nonprivileged environment is to have your main interactive logon be nonprivileged but to keep an admin logon in your pocket for those times that you need it. Look, it doesn't take administrative privileges to edit a text file, and that's what you're doing most of the time when you're programming, right? And you don't want your programs to require admin privileges to run, which is the point of this whole exercise. So your main logon, where you write, compile, link, and test your code (and where you surf the Internet, of course) should be nonprivileged. However, during development you will occasionally need to create a virtual directory in IIS, add a user account, or add an assembly to the global assembly cache, and that's where your secondary logon comes in handy.

By far the easiest and cleanest way to get a second log on is through Terminal Services. For example, if you develop code on Windows Server 2003, just run the command mstsc to bring up the remote desktop connection dialog; then press the Options button, fill out the form as I've done in Figure 9.1, and press "Connect." You'll get a window running in a second terminal services session, with a new desktop, running as whomever you specified when you filled out the form. Just minimize this window and bring it up whenever you need to do something as an administrator! If this doesn't work for you at first, then bring up the System control panel applet as an administrator. On the Remote tab, check the box that says "Allow users to connect remotely to this computer."

Figure 9.1 TermServ into your own computer!

Unfortunately, as of this writing this trick only works on Windows Server 2003, not on older platforms like Windows XP, because of licensing restrictions. With the version of Terminal Services that's built in to the operating system, you're allowed only a single connection to the console on Windows XP. Even on Windows Server 2003, you may not be able to use this feature because it's mutually incompatible with the "offline files" feature, which you may already be relying on. If for some reason you can't use this mechanism, don't give up hope. You can use the Secondary Logon Service instead. Read on.

The Secondary Logon Service

This service, introduced in Windows 2000, allows you to easily run with multiple logons. If you're familiar with UNIX, this facility is similar to the su command, at least in spirit. The secondary logon service can be accessed three ways:

If you're not familiar with this service, try the following experiment. Running as an administrator, log out and log back in as a non-admin (create a normal user account locally if necessary). Once you're running as a normal user, press the Start button in Explorer and navigate into All Programs, Accessories, and select (but don't click) the Command Prompt shortcut. Once it's highlighted, right-click it to bring up the context menu (if you're running Windows 2000, you'll need to hold down the Shift key while you right-click — for some screwy reason this feature was hidden in that operating system). When the context menu appears, you should see a menu item called "Run As." Click it to bring up the Run As dialog. Select the radio button that says "The following user:" and notice that the built-in local administrator account is selected by default. Type the password for the admin account and press OK. You should see a new command prompt appear — this one running as the administrator. Verify this by running the command whoami1 from this new command prompt or, if that's not available, just type set username instead. Now, imagine keeping a little command prompt like this down at the bottom of your desktop, always open, always waiting to execute your commands in a privileged security context. Any program you start from this command prompt will run as administrator. Nifty, eh? But we're nowhere near being done yet. Read on.

But I Hate the Command Prompt!

Many developers are comfortable using the command prompt to get tasks done. For example, I much prefer netsh for configuring my IP settings over messing around with the GUI. But for some tasks in Windows, using a GUI is pretty much unavoidable. For example, the command-line ACL editor 2, cacls, doesn't correctly propagate inheritable ACEs (in English this means it can screw up your file system ACLs over time), so I prefer to use the GUI to modify my file system ACLs (WhatIsAnAccessControlList). You really need a special Explorer window for performing administrative tasks. Unfortunately, simply typing explorer from your admin command prompt will very likely not do what you want. explorer.exe normally uses a single process, and when the new process starts it simply sends a message to the earlier instance and terminates. This means that the new window you get is not running in a different security context. There is an option to force every Explorer window to run in its own process, however I’ve never managed to get it to work. On the other hand, there is another facade that you can use to start an Explorer window in its own process: iexplore.exe. If you type \program files\internet\explorer\iexplore.exe from your admin command prompt, the new Explorer window will run in its own process, with admin privileges. If you type "Control Panel" into the address bar, you'll quickly see that there's not much you can't do from here. One odd thing that I've noticed, and that you should watch out for, is that this copy of Explorer won't refresh things automatically. For example, if you create a new file, it might not show up in your admin Explorer window. Don't fret, though, just press F5 to force a refresh.

Take a close look at this new window. You likely can't distinguish the admin Explorer window from any other Explorer window, and you will make a royal mess of your system if you leave things this way. For example, you might accidentally create a directory with your admin Explorer when you really intend to have your normal account own and manage that directory. Take it from my own painful experience: You don't want to stop here. The trick is to use per-user customization to make the admin window stand out so you can't miss it. There's an utterly cool way to do this, too. Skin it! Run regedit from your admin command prompt and drill down to the following registry key: HKEY_CURRENT_USER\Software\Microsoft\Internet Explorer\Toolbar. Add a named string value, BackBitmap, whose value is the path to a bitmap file. At the moment, I use a boring little red 32×32-pixel bitmap, but you can get more fancy if you like. Since Explorer tiles this all over the toolbar, this means my admin Explorer ends up with a bright red toolbar that I can't miss. The Toolbar key has a sibling called Main. Surf over there and add a named string value, Window Title. Set this to "ADMIN" or whatever you like. This will help you see which window is which when you press Alt-Tab repeatedly to switch between programs.

Network Credentials

The only problem with our setup so far is that our admin prompt lacks domain credentials. This may not be a big deal if you're not in a domain, but if you are it's really annoying to be prompted for domain credentials when you're simply trying to administer your local machine. Try to edit an ACL (WhatIsAnAccessControlList) on a file using your admin Explorer window, for example. At some point the GUI will contact the domain asking for a list of users and groups, and you'll be prompted for domain credentials because your local admin account can't be authenticated by the domain. To avoid your fingers falling off from typing your domain, user name, and password over and over, use this nifty trick to impart a set of domain credentials to a local admin command prompt: runas /netonly /u:MyDomain\MyUserAccount cmd. After prompting you for a password, the system creates a new command prompt that has a split personality. On your machine it's running as your local administrator, but on the network it will be authenticated using your domain credentials. Close your old admin command prompt. That was yesterday's news!

A Sample Setup for a VS.NET Developer

To be productive, it's important to have an admin command prompt ready at all times, so I find it useful to automate as much as possible the process of getting one. I suggest creating a couple of batch files to help (I call mine adminShell.cmd and adminShellInit.cmd). Use the first batch file to house two runas commands, the first initializing a local admin shell and the second creating a new shell that has your domain credentials. Here's my adminShell.cmd. Note that XYZZY\la is the local admin account on my box (I always rename the Administrator account to annoy potential hackers).

 REM adminShell.cmd
 REM Starts a command prompt running as the local admin (XYZZY\LA)
 REM but with my domain credentials (ps\kbrown)
 runas /u:xyzzy\la "runas /netonly /u:ps\kbrown \"cmd /K c:\etc\utils\adminShellInit.cmd\""

I've heard reports that, on some systems, allowing the first command prompt to exit results in painting problems for applications. I've not personally run into this, but if you do you might find knowledge base article 322906 helpful. In short, the workaround is to modify the batch file just shown so that the second reference to runas becomes cmd /K runas.

My second batch file initializes the environment and makes the command prompt stand out by changing its color to black on red and setting an obvious title. Because the current directory of the admin prompt will be SYSTEM32, I change to a less dangerous directory by default. I don't want to accidentally anything in there!

 REM adminShellInit.cmd
 @echo off
 title *** ADMIN ***
 color C0
 call  "c:\program files\microsoft visual studio .net 2003\common7\tools\vsvars32.bat"
 cd "%USERPROFILE%\My Documents"
 cls

Figure 9.2 shows what my desktop looks like with my normal and admin command prompts running.

Figure 9.2 An admin secondary logon

Debugging

Contrary to popular belief, you don't normally need any special privileges to debug programs on Windows. If you start a process, you own it and thus have full permissions to it. That's all a debugger needs. However, if you want to debug processes running in other logon sessions (services, for instance, or IIS worker processes) you'll need the Debug privilege. This privilege allows you (and therefore your debugger) to open any process on the machine for full permissions, even operating system services. It's a really powerful privilege normally granted to the local Administrators group, so instead of granting this to my account I simply start the debugger from my admin prompt (if you've set up your environment like I do for .NET, you can just type devenv to get it started).

If you're running Visual Studio .NET, you must add your low-privilege developer account to the local Debugger Users group. This is not an operating system requirement, rather it’s a policy enforced by Visual Studio .NET. If you aren't a member of this group, you aren't allowed to do JIT debugging, remote debugging, or pretty much debugging of any kind using Visual Studio .NET.

Creating Web Projects in VS.NET

The Visual Studio .NET Web Project wizard doesn't work very well if you're not running as an administrator. I've found the easiest way to get a Web project started as a non-admin is to first create the virtual directory using the IIS admin tool from the computer management console 3 and then run the wizard and point it to the URL I just created. You can also add yourself to the VS Developers group, which grants you write access to \inetpub\wwwroot and seems to make the wizard a lot happier.

Writing Code That Can Be Used by a Non-Admin

One result of developing code as a nonprivileged user is that you'll be more likely to produce programs that run without requiring elevated privileges. So this item wouldn't be complete without pointing out the most common reasons that programs break in nonprivileged contexts, and helping you do better.

If there were only one bit of advice I could give on this topic, it would be this: Separate program files (executables, DLLs, etc.) from data files! Normal users don't have write permission under the Program Files section of the file system,4 which means that your program won't be able to write here either, so don't try (this is an example where running as a normal user can help you catch silly mistakes early). This is by far the most common bug that causes programs to require elevated privileges in order to run.

As part of the Windows Logo program, Microsoft provides guidelines on where to store data files. The .NET Framework has methods that allow your program to discover the appropriate location for data files at runtime, so there's really no excuse for the plethora of apps that insist on writing data to their install directory. Figure 9.3 shows the various types of data, the recommended location, and the enumeration in the .NET Framework used to look up this location at runtime, because the actual paths I provide here may be different on each machine. Here's a link to the Logo requirements.

http://www.microsoft.com/winlogo/

I strongly recommend reading the section on data and settings management, as it provides further tips on using these common directories.

Description Recommended Section of File System Environment.SpecialFolder Enum
Static, read-only data files c:\Program Files ProgramFiles
Writable data files shared by all users of the application c:\Documents and Settings\All Users\Application Data CommonApplicationData
Writable data files specific to a single user c:\Documents and Settings\username\Application Data ApplicationData
Writable data files specific to a single user and machine c:\Documents and Settings\username\Local Settings\Application Data LocalApplicationData
User documents c:\Documents and Settings\username\My Documents Personal

Figure 9.3 Recommended data file locations

Note that I’m not recommending writing directly into any of these directories. Rather, you should create a subdirectory that's unique to your application and store your data files under it. The Logo requirements specify the directory structure as follows: "[company name]\[product name]\[version]". Here's some C# code that prints out the recommended directory for the second item in Figure 9.3.

 using System;


 class App {
   static void Main() {
     string path = Environment.GetFolderPath(
       Environment.SpecialFolder.CommonApplicationData
     );
     Console.WriteLine(path);
   }
 }

If you're building a Windows Forms application, your life is even easier, because the Logo requirements for forming your product's subfolders are implemented for you via the following properties on the Application class.

Here's a program that prints out these paths. Compile and run it; then go look at your user profile directory (WhatIsAUserProfile)! The subfolders for your product have been created automatically. The first time you read one of these properties, the Application class creates the folder on your behalf (if it doesn’t already exist), using the metadata provided by the three assembly-level attributes shown in the following code sample:

 using System;
 using System.Windows.Forms;
 using System.Reflection;


 [assembly: AssemblyCompany("ACME")]
 [assembly: AssemblyProduct("WidgetManager")]
 [assembly: AssemblyVersion("1.1.0.0")]


 class App {
   static void Main() {
     // simply accessing these properties creates
     // subfolders based on company/product name
     // and version, according to logo requirements!
     Console.WriteLine("Roaming Settings Folder: {0}",
       Application.UserAppDataPath);
     Console.WriteLine("Local Settings Folder: {0}",
       Application.LocalUserAppDataPath);
     Console.WriteLine("Shared Settings Folder: {0}",
       Application.CommonAppDataPath);
   }
 }

Isolated Storage

For storing per-user settings (as opposed to documents that the user might want to manipulate directly in the file system), you should consider using the .NET Framework's isolated storage system. This is really just another way to access files stored under the user profile directory, but the .NET Framework manages the location of the storage so that each assembly gets its very own private root directory where files can be stored. Once you realize that an isolated storage file is just a real file in the file system, you'll see how easy it is to use. Because IsolatedStorageFileStream derives from FileStream, if you've used the FileStream class using a file from Isolated Storage isn't any different. Here's an example that creates a text file using IsolatedStorage.

 using System;
 using System.IO;
 using System.IO.IsolatedStorage;


 class App {
   static void Main() {
     IsolatedStorageFile myRoot =
       IsolatedStorageFile.GetUserStoreForAssembly();
     using (FileStream media =
       new IsolatedStorageFileStream("myFile.txt",
            FileMode.Create, FileAccess.Write,
            FileShare.None, myRoot))
     using (StreamWriter w =
       new StreamWriter(media)) {
       w.Write("Hello, isolated storage!");
     }
   }
 }

There is now a file called myFile.txt somewhere on your hard drive. To find it, configure Explorer to show hidden files and drill down into your user profile directory a bit: c:\Documents and Settings\[user name]\Local Settings\Application Data\IsolatedStorage. From here names are mangled, but just keep drilling down and you'll eventually find your file. Open it in Notepad and see that there's nothing magical happening here. To see how your assembly's storage location is identified, use storeadm /list. You'll notice that by default your storage is indexed by your assembly's URL. This means that, if your assembly is loaded from a different location, it will be looking at a new storage location. If this is a problem, give your assembly a strong name. Now the storage location will depend only on your assembly's strong name, which means that, if the version of your assembly changes, it will be looking at a new storage location, but this is usually acceptable if you plan ahead. Run storeadm /list after giving the previous example a strong name and see how things change.

Installation Tips

Even well-written programs that don't require special privileges are usually installed by administrators. Let me say this another way, with emphasis: You must assume that your program will be run by one person and installed by another! This means that there's no point messing with per-user settings during an installation. For all you know, an administrator is setting up a machine for a new user who doesn't even have an account in the domain yet, let alone a user profile on the machine. So wait until your program is launched the first time to initialize per-user settings. Also consider that your program may be installed on a machine where more than one user normally logs in (think of the front desk at your company). Test for this! Use runas as a quick way to launch your app under different security contexts and ensure that it works properly.

Strive for a power user installation. Power Users is a special group granted read-write access to the Program Files directory tree. Unless you need to install an NT service or COM+ component, or put assemblies in the GAC, it's very likely that the person installing your software doesn't even need full admin privileges, which is a very good thing. When someone installs your app as a power user instead of an admin, she can rest assured that your installer won't be allowed to do nasty things such as overwrite parts of the operating system, install kernel-mode code like device drivers, which can do anything they want, and so forth. Even better would be an xcopy deploy that doesn't require any privilege at all, or a "no-touch" deployment over the network. Remember the principle of least privilege (WhatIsThePrincipleOfLeastPrivilege) when designing your installer as well as your app!

1 If you don't have this tool, I show where to get it in WhatIsAPrivilege.

2 If you’re not sure what an ACL or ACE is, be sure to read WhatIsAnAccessControlList.

3 The quickest way to run this very useful console as an administrator is to launch compmgmt.msc from your admin command prompt. But if you are a mouse clicker, you can also navigate from your admin explorer window into Control Panel | Administrative Tools | Computer Management.

4 There's a good reason for this: On a multiuser system, you wouldn't want one user overwriting WINWORD.EXE with a Trojan horse, for example.

PortedBy KeithBrown

PluralsightTraining

Keith's first book-in-a-wiki. If you would like to read the book online or order a physical copy to throw at annoying coworkers, surf to the HomePage. Please note that due to overwhelming wikispam, this particular wiki is no longer editable.

About FlexWiki.

Recent Topics