Lowering IO Priority of PowerShell Process

powershell

priority

process

pinvoke

This week brought quite a few challenges. One of them was a question asked by a friend:

How do I search contents of all the files for given string, without killing the performance of the computer?

This seemed like a simple question to answer: Just lower the priority of the PowerShell process to Idle.

(Get-Process -Id $pid).PriorityClass = 'Idle'

The only problem is, that it does not work.

That piece of code actually lowers the priority of the process. But this priority only applies to compute-bound tasks. In other words, this option is great if we don’t want the CPU to run on 100 %, but it won’t help us much with disk operations.
To confirm that you can run the following code on Idle priority and view the PowerShell.exe in Task Manager.

$header = [Byte[]]("0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16" -split "\s")
gci (Resolve-Path $env:SystemRoot).Drive.Root -Recurse |
where { -not $_.PsIsContainer } |
foreach {
$content = Get-Content -TotalCount ($header.Count) -Encoding Byte -Path $_.FullName
if ($null -ne $content) {
if ($null -eq (Compare-Object $header $content ))
{
$_.FullName
}
}
}
}

On my system I can see PowerShell.exe exhausting 90-100 % of disk I/O resources.

At this point I was not sure if I am even setting that priority correctly so I used ProcessExplorer to check the priority and spotted this:

Now I wonder, how do I lower the priority for I/O bound operations? My first was checking the enum of values you can set to the PriorityClass:

[Enum]::GetNames(((Get-Process -Id $pid).PriorityClass).GetType())

Which yields:

Normal
Idle
High
RealTime
BelowNormal
AboveNormal

As you can see, nothing specific to IO. No luck there. Let’s Google, because I remember reading about this in Windows via C++.

The solution to this problem is setting the priority of the process to PROCESS_MODE_BACKGROUND_BEGIN, but unfortunately .NET does not offer this functionality, so we’ll need to P/Invoke. Pretty easy to do in PowerShell. Just create a piece of C# code that’s compiled on runtime using the Add-Type cmdlet and you should be good to go.

Add-Type @"
using System;
using System.Runtime.InteropServices;

namespace Utility
{
public class PriorityHelper
{
[DllImport("kernel32.dll", CharSet=CharSet.Auto, SetLastError=true)]
static extern bool SetPriorityClass(IntPtr handle, PriorityClass priorityClass);

public enum PriorityClass : uint
{
ABOVE_NORMAL_PRIORITY_CLASS = 0x8000,
BELOW_NORMAL_PRIORITY_CLASS = 0x4000,
HIGH_PRIORITY_CLASS = 0x80,
IDLE_PRIORITY_CLASS = 0x40,
NORMAL_PRIORITY_CLASS = 0x20,
PROCESS_MODE_BACKGROUND_BEGIN = 0x100000,// 'Windows Vista/2008 and higher
PROCESS_MODE_BACKGROUND_END = 0x200000,// 'Windows Vista/2008 and higher
REALTIME_PRIORITY_CLASS = 0x100
}

public static int SetBackgroundIoPriority ()
{
IntPtr id = System.Diagnostics.Process.GetCurrentProcess().Handle;
if (SetPriorityClass(id, PriorityClass.PROCESS_MODE_BACKGROUND_BEGIN)) return (int) id;
return -1;
}
}
}
"@

$result = [Utility.PriorityHelper]::SetBackgroundIoPriority()
if ($result -eq -1) {
throw "Background IO priority could not be set or is already set"
}

In the previous code I am creating a static class (to make it easy to call), and importing single method from kernel32.dll. That method is called SetPriorityClass and is well documented on MSDN.aspx). I also define an enum of priorities, which defines the PROCESS_MODE_BACKGROUND_BEGIN, but I could also use the value directly and explain it in a comment.

Other than that I define a public method SetBackgroundIoPriority which does all the work and sets the current process IO priority to Very Low. In the last three lines of PowerShell code I am just calling that public method and throwing an exception that is not very helpful :D

Running the same code as above now exhausts 1-3 % of my disk at best.

(It goes without saying that the script runs a lot longer now, but that’s a tradeoff for letting the foreground work to be done first.)

written at