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:
- 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)
- 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).
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.
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; TOKEN_PRIVILEGES previousPrivs; HANDLE hToken;
OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &hToken);
LookupPrivilegeValue(NULL, SE_DEBUG_NAME, &(tp.Privileges.Luid));
tp.PrivilegeCount = 1;
tp.Privileges.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):
hProcessSnap = CreateToolhelp32Snapshot( TH32CS_SNAPPROCESS, 0 );
Now the list is walked with
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:
pe32.dwSize = sizeof( PROCESSENTRY32 );
Process32First( hProcessSnap, &pe32 );
hProcess = OpenProcess( PROCESS_ALL_ACCESS, FALSE, pe32.th32ProcessID );
dwPriorityClass = GetPriorityClass( hProcess );
if(_tcsicmp(hogProcessName, pe32.szExeFile) == 0)
dwNewPriorityClass = GetPriorityClass( hProcess );
CloseHandle( hProcess ); } 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.
- Version 1.0 10/20/2007: First public release