WMI Helps Those Who Help Themselves

Greg Stemp
Microsoft Corporation

February 11, 2004

Summary: Demonstrates how script writers can create custom MOF (Managed Object Format) files that enable them to extend the capabilities of the Windows Management Instrumentation service. (13 printed pages)

When historians sit down to write the story of the twenty-first century, their first sentence will likely be, "Back then no one was ever satisfied with anything." And who can argue with that sentiment? Suppose you go into a cell phone store and the salesperson tries to sell you a phone that only allows you to make and receive phone calls. Most likely you'll be outraged. A cell phone that only makes and receives phone calls?!? What about the built-in digital camera? What about the MP3 player? No text messaging? You're trying to sell me a phone that I can't even play Tetris on? What is this, some kind of a joke?

**Historical footnote **Way back in the mists of time, people not only didn't have cell phones, they didn't even have telephones. (It's true; you can look it up.) To communicate over any sort of distance, people relied on the telegraph. Due to the limitations of the technology and to the costs involved, people also had to resort to using cryptic messages like this: C U 2MAR O. Talk about primitive, huh? Today, of course, you can use your fancy, high-tech digital cell phone to send really cool text messages like this: C U 2MAR O. We've come a long way, baby.

And what about Microsoft® Windows® Management Instrumentation (WMI)? WMI is the best and most extensive scripting technology ever invented. It allows you to write scripts that can manage services, printers, disk drives, event logs, network adapter settings, desktop settings—the list goes on and on. You can use WMI to manage Microsoft® DNS Server, to manage Microsoft® Internet Information Services (IIS) 6.0, to manage Microsoft® SQL Server and Microsoft® Exchange Server 2003. All in all, a pretty incredible technology.

But are we grateful that WMI enables us to manage Terminal Services or System Restore? Of course not. Instead, we want to know why WMI can't also manage Dynamic Host Configuration Protocol (DHCP) Server. Why can't WMI manage NT Backup? Why, for heaven's sake, can't WMI give me a list of all the items found in Add or Remove Programs?

Well, if you're hoping for an insightful technical answer along the lines of, "Actually, the stigmatistical contabulator function used in DHCP Server precludes you from accessing the service using the IDispatch interface," I'm afraid you're not going to get one. All I can do is tell you that the main reason WMI can't do certain tasks—like enumerate the items in Add or Remove Programs—is because no one ever created a MOF (Managed Object Format) file that would allow a WMI script to do this. (Actually, that's a bit simplistic; you also have to have a provider available as well. But we're not going to worry about those other things today.)

The point is, as useful as WMI might be, there's no magic behind it. WMI needs to be given step-by-step instructions on how to carry out a task; otherwise, it's totally helpless. Can plain, vanilla WMI (assuming there was such a thing) manage services? No. However, as long as you provide WMI with the proper instructions, then it can manage services just fine.

That might not sound like a big deal, but it really is. Programming in Microsoft® Windows® has always been a real challenge, mainly because most programming tasks require you to use the Windows APIs (Application Programming Interfaces). There's nothing inherently wrong with that, except that these APIs tend to be maddeningly inconsistent. For instance, learning how to use the APIs for managing services won't necessarily give you a head start on using the APIs for managing event logs. (This by, by the way, is one of the great advantages of the Microsoft® .NET Framework class library: it brings consistency to Windows programming.)

So how does WMI help? Well, when you use WMI, you're still using the Windows APIs. However, WMI saves you from having to deal directly with those APIs. Instead, WMI presents you with a consistent and easy-to-use interface. You pass WMI a simple command, and WMI takes it upon itself to translate that simple command into the equivalent, but typically far more complex, Win32 API call. That's why we love WMI so much: it translates our simple script commands into far more complicated API calls.

And that's where MOF files come in. MOF files, along with their accompanying provider, tell WMI how to map script commands into API calls. How come you can manage DNS Server using WMI? Well, in part because someone wrote a MOF file and a provider that maps WMI commands to DNS Server commands. How come you can't manage DHCP Server using WMI? Because no one has created a MOF file and a provider that maps WMI commands to DHCP Server commands.

So what difference does that make to you? Well, if you want to manage something using WMI, there has to be a MOF file that tells WMI how to manage that item. Can you write a MOF file that tells WMI how to manage DHCP Server? Probably not, at least not unless you know a whole heck of a lot about DHCP Server and its associated APIs, and unless you're up to writing a provider for the DHCP service as well. Can you write a MOF file that retrieves information from Add or Remove Programs? Well, what do you think?

Ha! That last one was a trick question. It turns out that there's a loophole in our story. If an entity (a service, an application, an operating system component, whatever) stores data in the registry, then there is a way for a scripter to write a MOF file. Ah, you say, but don't we need a provider as well as a MOF file? You bet we do, but in this case we can take advantage of a provider (the Registry Provider) that's built right into the operating system. If you want to manage DHCP Server using WMI, we can't help you. But at least we can show you how to finally get at those items in Add or Remove Programs.

MOF Files and the Registry

The key here is the fact that Add o\r Remove Programs stores its data in the registry. In fact, you can find this information at HKEY_LOCAL_MACHINE\Software\Microsoft\Windows\CurrentVersion\Uninstall. If you open up that part of the registry, you'll see something like this:

Figure 1. Registry settings for Add or Remove Programs

Of course, if all this information is stored in the registry, your first thought might be, "Why go to all the trouble of creating a MOF file? Why not just write a script that reads the registry directly?" Actually, you can do that. However, there are a few factors that make that a less than optimal approach.

To begin with (and as you can see from the preceding screenshot), information about each Add or Remove Programs item is stored as a separate registry key. That means you can't simply connect to the Uninstall key and get back the information you need. Instead, you first have to use the EnumKey method (part of the Standard Registry Provider) to get a list of all the subkeys.

Unfortunately, that only gets you, well, the list of subkeys; you then have to connect to each subkey and, in turn, enumerate the registry values (things like AuthorizedCDFPrefix, Comments, Contacts, and so on). Can you do that with a script? Sure you can; just use the EnumValues method, which returns a list of all the registry values.

So far that's not too bad, but remember, this only gives you the names of the values (for example, Publisher); it doesn't tell you the value of the values (for example, Publisher = Microsoft Corporation). Can a script return the values of registry values? You bet, as long as you first get the data type (like REG_SZ or REG_DWORD). Why? Well, WMI uses different methods to read different data types. You can't just issue a command like, "Read this registry value, whatever it is." Instead, you have to use GetDWORDValue to read a DWORD value, GetStringValue to read a string value, and so on. Is any of that impossible? No. Is much of it tedious and time-consuming? That ought to be fairly obvious by now. (In fact, I'm worn out just from describing the process, let alone trying to script the process.)

So how can a MOF file help? After you write a MOF file, you can then enumerate the items found in Add or Remove Programs the same way you enumerate the services or the printers installed on a computer. You don't have to worry about EnumKeys and EnumValues and GetStringValue; you just write the same old WMI script you've written a million times, a script similar to this:

strComputer = "."
Set objWMIService = GetObject _
    ("winmgmts:\\" & strComputer & "\root\default")
Set colItems = objWMIService.ExecQuery _
    ("Select * From InstalledSoftware")
For Each objItem in colItems
    Wscript.Echo "Key Name: " & objItem.KeyName

But can you, a scripter, really write a MOF file that lets you manage Add or Remove Programs in such a simple fashion? Well, we're about to find out.

Creating Your Very Own MOF File

Let's start by taking a look at a cut-down version of our MOF file (I'm only specifying a single property for the new class in this example). From there, I'll go on to explain each line in the MOF file, and then show you how to write a more complete version (that is, a class that has all sorts of properties). Here's the simple MOF:

[dynamic, provider("RegProv"),
class InstalledSoftware {
   [key] string KeyName;
   [read, propertycontext("DisplayName")]      string DisplayName;

Note **The line that begins with **ClassContext should actually be a single line of code. Unfortunately, our onscreen columns aren't wide enough to display it as a single line.

Yes, I know it looks hard; the first rule of scripting seems to be that things have to look hard. But as you've already discovered, scripting's bark is usually much worse than its bite. And the same thing is true here: this looks hard, but most of the above code is boilerplate that you don't really have to worry about.

Let's start by going over the items found in the header portion of the MOF file:

  • Dynamic. The first thing we have to do when constructing a MOF file is to let WMI know what type of class we are creating. Any time we use the Registry Provider we're creating a dynamic class. Dynamic classes don't store data in the CIM repository; instead, any time you request information from a dynamic class, WMI goes out and "mines" the latest and greatest data from the appropriate spot. In this MOF file, we're grabbing data stored in the registry; each time we invoke this class, we want WMI to go snag Add or Remove Program information from the registry. That's a dynamic process. Logically enough then, we must be creating a dynamic class.

  • Provider("RegProv"). A provider's job is to serve as an intermediary between WMI and a managed object. What does that mean? Well, pretend for a moment that we know nothing about the registry. (OK, so maybe some of us don't have to pretend very hard.) However, suppose we do know a little bit about WMI. Because we know something about WMI, and because WMI has a Registry Provider, we can manage the registry despite our complete ignorance of the registry APIs. Why? Because we just write some WMI code and then let the Registry Provider worry about translating that code into commands the registry understands. In this part of the MOF file, all we're doing is telling WMI who will do the work for us: the Registry Provider (RegProv for short).

  • ProviderClsid("{fe9af5c0-d3b6-11ce-a5b6-00aa00680c3f}"). Yes, this looks like gibberish but, again, you don't have to worry about it. A Clsid is nothing more than a unique identifier used to distinguish one COM object from another. All we're doing here is telling WMI that the Clsid for the Registry Provider is this: {fe9af5c0-d3b6-11ce-a5b6-00aa00680c3f}. It looks crazy, but don't panic: this will be the same in every MOF file you write (assuming that the MOF file uses the Registry Provider).

  • ClassContext("local|HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall"). So far everything we've done has been boilerplate; the dynamic qualifier, the provider, and the ProviderClsid are exactly the same in every Registry Provider MOF file. When we get to the ClassContext, however, you'll have to do a little bit of work. (Take it easy; we said a little bit of work.) The ClassContext simply specifies the registry key where our data resides. Our data lives in HKEY_LOCAL_MACHINE\Software\Microsoft\Windows\CurrentVersion\Uninstall, so our ClassContext looks like this:


Note two important things here. First, we use double slashes (\\ instead of \) to separate items in the registry path. Why? Well, \ is a reserved character in WMI, and thus we need to "escape" it. When WMI sees two slashes together, it knows that we are using \ to separate paths and not as some reserved character.

Second, we preface the registry path with local (and note the use of the pipe separator symbol — | — to separate local from the registry path). This tells WMI, "Get the data from the local computer." (That is, from the computer we are connected to when we run the script.) In other words, this says, "If we connect to atl-ws-01, then go get the Add or Remove Programs information from atl-ws-01. If we connect to sea-ts-30, get the data from sea-ts-30."

Note *We actually *could specify a computer name here; we could say **sea-ts-30. Why don't we do that? Well, if we did, every time we ran the script, regardless of what computer we were connecting to, the script would retrieve data only from sea-ts-30. Probably not what we want. (Unless, of course, you are simply obsessed with knowing what's going on with sea-ts-30.)

Let's try a pop quiz to make sure we understand everything. Suppose we wanted to retrieve:

  • User profile information...
  • From the computer our script connects to (rather than a specific computer)...
  • Which happens to be stored in HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\ProfileList.

In that case, what would our ClassContext string look like? (Hey, no fair looking at someone else's paper!) If you said this, you are absolutely right:

Windows NT\\CurrentVersion\\ProfileList")

I know, it's kind of embarrassing. After all, Scripting Clinic is supposed to deal with advanced scripting issues, and this is child's play. What can I say except this: I'm sorry creating MOF files turned out to be this easy. Who would have guessed it?

Defining Properties

So far we've told WMI that our new class is going to use the Registry Provider to grab data out of the HKEY_LOCAL_MACHINE\Software\Microsoft\Windows\CurrentVersion\Uninstall portion of the registry. Now we just need to tell WMI which data to grab. (And, yes, now you have to do a little more work.)

Let's review the information section of the MOF file for a moment:

class InstalledSoftware {
   [key] string KeyName;
   [read, propertycontext("DisplayName")]      string DisplayName;

And now let's take look at what's going on in each of these three lines:

  • Class InstalledSoftware. All classes have to have a name, so here we're telling WMI that our new class is going to be named InstalledSoftware. Does it have to be named InstalledSoftware? Of course not. Give it any name you want; just make sure that the name is one big string, with no spaces or punctuation marks in it. (In other words, don't name your class Installed Software or Software,Installed; it ain't gonna work.)

    A note regarding namespaces **When you work with WMI, you probably spend most of your time in the root\cimv2 namespace; after all, that's where the vast majority of the WMI classes live. However, in this MOF file, I'm storing the new class in root\default. Why? Well, root\cimv2 is populated primarily by operating system classes derived from the base CIM classes. Because the new class isn't derived from a base CIM class, it seemed more appropriate to put it in root\default, keeping root\cimv2 in its pure and natural state. However, there's no reason why you can't put **InstalledSoftware in root\cimv2. If you want to do that, just make this the very first line of your MOF file:

    #pragma namespace ("\\\\.\\Root\\CIMV2")
  • [key] string KeyName. WMI classes—at least those that return data—must have a key property, a property that is unique for all instances of the class. For example, with the Win32_Process class, the handle (the process ID) is the key property. If you use a script to return all the instances of the Win32_Process class, you'll get back a list of processes sorted by the handle.

    Why handle? That's easy: because no two processes have the same handle. Because handles are unique, the process handle enables us to distinguish between individual process instances. Why not use something a bit more familiar, such as process name? That's another easy one: process names are not unique. Two processes won't have the same handle, but two processes could very easily share the same name. (For example, you could have scores of processes with the name notepad.exe, all running at the same time.)

    When it comes to items stored under the Uninstall key, the unique identifier is the name of the subkey in which the information about a particular application resides. If you refer back to Figure 1, you'll see that each application has its own subkey, and each subkey has a unique name. (Sometimes these are the names of the application and sometimes these are GUIDs.) To uniquely identify individual instances in our class, we can use the name of the registry subkey as our key property.

    And how do we do that? We just specify the key qualifier, the datatype (for a registry key, this will always be a string), and the standard Registry Provider term Keyname. That sounds like a lot, but it's just one line of—you guessed it—boilerplate code:

    [key] string KeyName;
  • [read, propertycontext("DisplayName")]string DisplayName. Now we're ready to define the properties for our new class. To do this, open up Regedit, and start thumbing through the items listed under HKEY_LOCAL_MACHINE\Software\Microsoft\Windows\CurrentVersion\Uninstall. What we're looking for are registry values that are typically used by items stored under the Uninstall key.

    An interesting note regarding properties used in your MOF file **We said we were looking for values typically used by items stored under the Uninstall key. But what if you find something (say, HelpLink) that's used by some applications but not by others? No problem; that's one of the beauties of using MOF files. Let's say we write a script that uses our new **InstalledSoftware class. Suppose in that script we say something like Wscript.Echo objItem.HelpLink, but some of the instances of our class don't have a HelpLink value. Relax; WMI will simply report the value as Null. The fact that the value doesn't even exist in the registry won't bother WMI in the least.

    And what if you try to modify this registry value that doesn't even exist; that is, what if you try to configure HelpLink? Hey, no sweat: WMI will create the value in the registry, and then configure that value accordingly.

    As it turns out, DisplayName is used by almost every application, so let's make that one of our class properties. To define DisplayName as a class property we just need to:

    1. Indicate whether this is a read-only or a read-write property. Because it's probably not a good idea to change the DisplayName, we'll make this one read-only. Consequently, we start off our property definition by using the read qualifier. (The read qualifier is actually optional, but adding it makes the MOF file a bit more intuitive.)

    2. Indicate the name of the value *in the registry. In the registry, the **DisplayName* is listed as, well, DisplayName. Thus we set the propertycontext (which is just the name of registry value) to DisplayName: propertycontext("DisplayName"). What if the registry value we want to add is named HelpLink? Well, then our propertycontext string would look like this:

    3. Specify the datatype of the registry value in question. DisplayName happens to be a REG_SZ type. Because this is a REG_SZ datatype, we specify string as part of our property definition. How do we know that we should use string any time we work with a REG_SZ datatype? Why, because we used this table:

      Registry Datatype Datatype Used in MOF File
      REG_BINARY * unint8
      REG_DWORD uint32
      REG_SZ string
      REG_EXPAND_SZ string
      REG_MULTI_SZ * string

      * Note that REG_BINARY and REG_MULTI_SZ values are returned as arrays.

      Thus if we had a REG_DWORD registry value (and we will have at least one of those in this MOF file), we'd have a property definition that looked like this (again, this should be a single line of code in the MOF file):

      [read, propertycontext("EstimatedSize")]      uint32 EstimatedSize;
    4. Give our property a name. We already know the name of the registry value; in this case, it's DisplayName. If we want to, we can use the same name for our WMI property name. Or, if you prefer, give it a different name (NameAsDisplayed). For once in your scripting life, you can do whatever you want to! (Well, as long as you don't use punctuation marks or reserved characters, and as long as there are no spaces in the name, and ...)

Okay, here's another pop quiz. We have a REG_SZ registry value named DisplayVersion that we want to add to our class as a read-only property. What does the property definition look like? That's right! It looks just like this:

[read, propertycontext("DisplayVersion")]      string  DisplayVersion;

**Note **Although we haven't mentioned this specifically, be aware that all the surrounding punctuation marks—the brackets, double quotes, semi-colons, and so one—are required. The items shown below in boldface might need to be changed each time you add a new property; the items not in boldface are boilerplate that should be left alone:

[read, propertycontext("DisplayVersion")]    string  DisplayVersion;

The only thing that doesn't really matter are spaces. We put several spaces between string and DisplayVersion, but that was just for aesthetic purposes. A single space works just fine.

Now you simply repeat the process for each and every property in your new class. When you get done, you'll have a completed MOF file that looks something like this:

[dynamic, provider("RegProv"),
class InstalledSoftware {
   [key] string KeyName;
   [read, propertycontext("DisplayName")]      string DisplayName;
   [read, propertycontext("DisplayVersion")]   string  DisplayVersion;
   [read, propertycontext("InstallLocation")]  string InstallLocation;
   [read, propertycontext("EstimatedSize")]    uint32 EstimatedSize;
   [read, propertycontext("InstallDate")]      string InstallDate;
   [read, propertycontext("QuietUninstallString")]   string 
   [write(TRUE), propertycontext("ReadMe")]    string ReadMe;
   [read, propertycontext("InstallSource")]    string InstallSource;
   [read, propertycontext("UninstallString")]  string UninstallString;

Creating Read/Write Properties

If you looked at the preceding MOF file, you might have noticed this line:

[write(TRUE), propertycontext("ReadMe")]      string ReadMe;

All the other property definitions use the read qualifier. Why does this one use the write qualifier, with the parameter set to TRUE?

Ah, that's because all the other properties in the MOF file are read-only; this one we're making read-write. (In other words, you'll be able to use a script to change the value of the ReadMe property.) To make a property read-write, all you have to do is set the write qualifier to TRUE, just like we did above. (You don't have to include the read qualifier because, by default, you can read all the properties of a class.)

Making properties read-write can be incredibly useful, but remember, some properties shouldn't be read-write. For example, suppose you make DisplayVersion a read-write property. That means you can use a script to change the DisplayVersion. For example, if the DisplayVersion for Microsoft® Office is 10.0 (indicating Office XP), you could write a script that changes the DisplayVersion to 11.0 (indicating Office 2003). Before you get too excited, however, changing the DisplayVersion doesn't mean you have magically upgraded from Office XP to Office 2003. All it means is that the DisplayVersion in the registry is now wrong, and thus any scripts you use to retrieve data from Add or Remove Programs will report incorrect information.

Compiling a MOF File

Creating a MOF file (and, by extension, creating a new WMI class) is only half the fun. The other half of the fun is getting that new class included within the CIM repository, a process known as compiling. That might sound a bit forbidding; who the heck knows how to compile a MOF class? To tell you the truth, probably nobody does. But that doesn't matter, because there's a command-line tool (Mofcomp.exe) that will take care of everything.

To compile a MOF file (and thus add a class to the CIM Repository) simply run mofcomp.exe, using the path name of the MOF file as a command-line argument. For example, to compile the MOF file C:\MyMOFs\Software.mof, use this command:

mofcomp c:\mymofs\software.mof

The only catch here (and you knew there had to be a catch somewhere, didn't you?) is that it's not enough to compile the MOF on your computer; you'll also need to compile it on every computer that you want to extract Add or Remove Programs data from. Unfortunately, Mofcomp.exe can't be used against remote computers. Instead, you'll either need to run it as part of a logon or computer startup script, or use the Win32_Process class and the CreateProcess method to start and run Mofcomp.exe on a remote computer. A bit of a hassle, sure, but worth it.

Accessing Your New MOF File

There's not much fun in creating a fancy new MOF file if you never use it. So how do you retrieve information from this new class? Remember, this is now a plain old WMI class; therefore, you access it the same way you would any other WMI class. Just write a script that specifies the proper namespace (in this case root\default) and the name of the class (InstalledSoftware, remember?):

strComputer = "."
Set objWMIService = GetObject _
    ("winmgmts:\\" & strComputer & "\root\default")
Set colItems = objWMIService.ExecQuery _
    ("Select * From InstalledSoftware")
For Each objItem in colItems
    Wscript.Echo "Key name: " & objItem.KeyName
    Wscript.Echo "Display Name: " & objItem.DisplayName
    Wscript.Echo "Display Version: " & objItem.DisplayVersion
    Wscript.Echo "Read Me File: " & objItem.ReadMe
    Wscript.Echo "Estimated Size: " & objItem.EstimatedSize
    Wscript.Echo "Install Location: " & objItem.InstallLocation
    Wscript.Echo "Installation Date: " & objItem.InstallDate
    Wscript.Echo "Install Source: " & objItem.InstallSource
    Wscript.Echo "Uninstall String: " & objItem.UninstallString
    Wscript.Echo "Quiet Uninstall String: " & _

And what if you want to change the value of a read-write property? Again, it's the same old WMI you know and love:

strComputer = "."
Set objWMIService = GetObject _
    ("winmgmts:\\" & strComputer & "\root\default")
Set colItems = objWMIService.ExecQuery _
    ("Select * From InstalledSoftware Where DisplayName = 'AP'")
For Each objItem in colItems
    objItem.ReadMe = "\\helpdesk\accounts_payable_manual.doc"

Deleting Your New Class

What, after going through all this work you want to delete your new class? Fine; after all, when it comes to crazy ideas, I'm not allowed to throw the first stone. If you want to or need to delete this new class, here's a script that will do just that. Just connect to the appropriate namespace and then specify the class you want to delete:

strComputer = "."
Set objWMIService = GetObject _
    ("winmgmts:\\" & strComputer & "\root\default")
errReturn = objWMIService.Delete("InstalledSoftware")

That's all it takes.

*Note **Can you use this script to delete *any WMI class? Yes, you can. So use this power wisely, okay?

So Now What?

Some of you are likely sitting at your desk thinking, "Ok, so now what? Are there more things I can do with MOF files? Now that I have an Add or Remove Programs MOF that I can just copy out of this column, are there any additional Registry Provider MOF files for me to write?" Well, to answer that first question, yes, there actually are more things you can do with MOF files, and in future columns we'll talk a little bit about those.

As for additional MOF files you can write, well, that depends on what you want to do, and whether the information you'd like to get at is stored in the registry. But here are a few registry keys you might look at if you're searching for potential MOF files. These were chosen mainly because they fit the same pattern as Add or Remove Programs: a single registry key with a whole bunch of subkeys stored inside it.

  • HKEY_CURRENT_USER\Software\Microsoft\Cryptography\CertificateTemplateCache
  • HKEY_CURRENT_USER\Software\Microsoft\Internet Account Manager\Accounts
  • HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Explorer\
  • HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Explorer\
  • HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\
    Internet Settings\Zones
  • HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Active Setup\Installed Components
  • HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Updates\Windows XP\SP2
  • HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\
    App Paths
  • HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\
    CurrentVersion\Accessibility\Utility Manager
  • HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\ProfileList

Do you have questions or comments about this column? Do you have suggestions for future columns? Drop us a line at scripter@microsoft.com. And see you next month.

Scripting Clinic

Greg Stemp has long been acknowledged as one of the country's foremost authorities on scripting, and has been widely acclaimed as a world-class... huh? Well, how come they let football coaches make up stuff on their resumes? Really? He got fired? Oh, all right. Greg Stemp works at... Oh, come on now, can't I even say that? Fine. Greg Stemp gets paid by Microsoft, where he tenuously holds the title of lead writer for the System Administration Scripting Guide.