Click here to Skip to main content
Click here to Skip to main content

WMI Tamer -- Making WMI Behave at Login

, 29 Oct 2007 CPOL
Rate this:
Please Sign up or sign in to vote.
This is a simple program which demonstrates how to find and change a process' priority, such as wmiprvse.exe

Introduction

This is a simple demonstration of how to find a process and change its priority. In this case, it is for wmiprvse.exe (Windows Management Instrumentation), whose priority cannot be changed via the task manager. Oddly, its priority can be changed using the SysInternals Process Explorer (Process Explorer) by Mark Russinovich. Go figure!

I present this little tool for two purposes:

  1. Demonstrate in very simple code how to get a list of all processes and adjust the process class (e.g. process priority, not thread priority)
  2. Provide a simple fix for what seems to be a common problem

There are other articles that address many of the same issues for process priority, but it never hurts to have another working example. Particularly one that doesn't have so much other stuff going on that it is hard to tell what is needed and what isn't. There is nothing clever or magic here, it just does the job in a simple (but brute force) manner and can be used as an example for developing your own code. It uses C++, but most of it could be in straight C. No MFC or STL is used.

The only C++ item is a strange macro for getting the number of elements in an array at compile time in a typesafe manner (see COUNTOF() macro by Ivan J. Johnson).

Background

Recently (actually, most of this year, 2007) I have found that when I restart my PC (Windows XP, SP2, in an enterprise environment), it can take 10-20 minutes before I can get prompt and reliable keyboard and mouse response. During this interval, I see from the task manager that wmiprvse.exe is taking 99% of the CPU. Upon researching via the Internet, I found that I am not alone and that many people have the same problem. This process doesn't play nicely with others. It runs at normal priority but it just doesn't seem to take any real-time breaks, possibly because it spawns so many threads that each assumes it will block enough to give other normal priority processes a chance to run. That part is just conjecture, of course.

For some people, the problem with wmiprvse.exe was mis-configured hardware (like a missing printer where there is something queued up for that printer) and it would go into some kind of tight loop. For others, it was because of a virus (W32/Sonebot-B, for instance). And for yet others, it was due to hardware-inventory operations (see the Microsoft article on High CPU usage on client computers during hardware-inventory operations). This last one was my problem. The hardware-inventory did not need to be completed before I used the machine, but until it was finished, the response to even the simplest task (such as right-clicking on the Recycle Bin) could take several minutes! That is not hyperbole; it was useless for human interface until the WMI operations were finished.

So, to improve response time and general usability, I had to reduce the priority of wmiprvse.exe, which is somewhat counter-intuitive. However, when the process is set to "below normal" via this tool or via Process Explorer, the human interface was vastly improved while the WMI process still got 99% of the CPU!

Using the Code

If you just want to use the code, then merely drop the executable into your Startup folder. It may be possible to put it into one of the registry "Run" lists, but I don't know when wmiprvse.exe is started and that process has to be running for this program to be effective so you don't want to start it too soon. The Startup folder works for me. Right now, the code is set to open and write a log file in C:/WMI_Tamer.txt with a report of the number of processes that were "tamed". There is a flag near the top of the code to adjust the level of verbosity (yes, you could write a command-line parser to adjust it at run time).

Note: This probably won't work on Win98 as it doesn't seem to have the "Below Normal" process priority class. That shouldn't be a problem for most people, I hope.

The code is written as a console program and I have tried to make it Unicode compatible and use the "safe" versions of the basic I/O interfaces. Oddly, this makes it harder for me to read, but I am trying to be more portable and safe in my programming, so you get stuck reading it.

As I am a big fan of embedding documentation for the code in the code, I have used doxygen to document this and have attached the resulting WMI_Tamer.chm help file to this article. I will only touch on the highlights of the code here.

Getting Permissions

Oddly, the hardest part for me was getting permission to change the process. The individual steps for changing process class (priority) are well documented by Microsoft as well as the requirement that permissions to change process class (priority) must be obtained. Making the connection was a little harder although once done, it is easy. Below is the code (with the error checking removed for readability):

TOKEN_PRIVILEGES tp;            // adjust this to set the new privilege
TOKEN_PRIVILEGES previousPrivs; // store previous value here for later restoral
HANDLE hToken;                  // current process token used to adjust privileges

OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &hToken);

// This query is why we needed TOKEN_QUERY. 
LookupPrivilegeValue(NULL, SE_DEBUG_NAME, &(tp.Privileges[0].Luid));

tp.PrivilegeCount = 1;
tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;

DWORD bufSize = 0;
AdjustTokenPrivileges(hToken, FALSE, &tp, sizeof(previousPrivs), 
    &previousPrivs, (PDWORD) &bufSize);

It turns out that changing process priority requires debugging permissions (who knew?). The value listed in the documentation is SE_DEBUG_NAME which I thought was a typo for SET_DEBUG_NAME. Nope. The SE means Security and not Set. In hindsight, this makes sense, but it didn't help when I was trying to figure this stuff out!

Another point that I still don't completely understand is that once I have set debugger permissions, they stay on even after my program exits. So, I have to save the previous permissions for later restoral. The previousPrivs value above is used later:

AdjustTokenPrivileges(hToken, FALSE, &previousPrivs, 0, NULL, NULL)

Once the debugging permissions have been obtained, the code calls the function GetProcessList() to get a snapshot of all running processes and step through them looking for the "hog" process. This is the main loop of the program.

Getting and Processing List of All Processes

The code for getting the snapshot is trivial (once you know how):

// Take a snapshot of all processes in the system.
// last parameter is ignored here
hProcessSnap = CreateToolhelp32Snapshot( TH32CS_SNAPPROCESS, 0 ); 

Now the list is walked with Process32First() and Process32Next(). For each process, we get the gory details, see if it is an instance of the process that needs to be tamed, and use SetPriorityClass() to drop the priority to "below normal". As usual, error checking and verbose reports are removed for clarity:

// Set the size of the structure before using it.
pe32.dwSize = sizeof( PROCESSENTRY32 );

// Retrieve information about the first process
Process32First( hProcessSnap, &pe32 );

// Now walk the snapshot of processes
do
{
    // Retrieve the priority class.
    hProcess = OpenProcess( PROCESS_ALL_ACCESS, FALSE, pe32.th32ProcessID );
    dwPriorityClass = GetPriorityClass( hProcess );

    // Here is where we tame the wild beast by dropping its priority.
    // Use a caseless comparison (which requires the call to setlocale() in _main())  
    if(_tcsicmp(hogProcessName, pe32.szExeFile) == 0)
    {
        SetPriorityClass(hProcess, BELOW_NORMAL_PRIORITY_CLASS);
        dwNewPriorityClass = GetPriorityClass( hProcess );
    }
    CloseHandle( hProcess );    // avoid a memory leak
} while( Process32Next( hProcessSnap, &pe32 ) );

Note: This is the part that will break in Win98 as BELOW_NORMAL_PRIORITY_CLASS is not available until Win2K.

Other Articles of Interest

If you want to convert this project to VC++ 6.0, see VC++7 to VC++6 Project Converter.

History

  • Version 1.0 10/20/2007: First public release

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author

Harold Bamford
Software Developer (Senior) Thales Visionix
United States United States
No Biography provided

Comments and Discussions

 
-- There are no messages in this forum --
| Advertise | Privacy | Terms of Use | Mobile
Web01 | 2.8.141223.1 | Last Updated 29 Oct 2007
Article Copyright 2007 by Harold Bamford
Everything else Copyright © CodeProject, 1999-2014
Layout: fixed | fluid