The foreground boost seems ineffective

Started by login404, May 13, 2025, 02:34:13 PM

Previous topic - Next topic

login404

I use System Informer to check the thread priority
After enabling "Foreground Boost", there is no change in the thread priority of the foreground process
However, manually changing the CPU priority of the process will immediately change the thread priority

Jeremy Collake

#1
Foreground boosting will change the priority class ("CPU priority") of the foreground process to Above Normal, but individual thread priorities are not adjusted. As the priority class of the entire process is raised, so is the effective priorities all threads in that process (priority class + thread priority = effective priority).

Similarly, changing the priority class of the process manually should not impact individual thread priorities. So, I'm not sure what you are seeing. Perhaps if you clarify your exact testing sequence we can get to the bottom of it.
Software Engineer. Bitsum LLC.

login404

The process CPU priority is normal

Disable foreground boost
Enable "Windows dynamic thread priority boost"
When in the foreground, its highest thread priority is 12
When in background, its highest thread priority is 10

Disable foreground boost
Disable "Windows dynamic thread priority boost"
When in foreground, its highest thread priority is 8
When in the background, its highest thread priority is 8

Enable foreground boost
Enable "Windows dynamic thread priority boost"
When in the foreground, its highest thread priority is 12
When in background, its highest thread priority is 10

Enable foreground boost
Disable "Windows dynamic thread priority boost"
When in foreground, its highest thread priority is 8
When in the background, its highest thread priority is 8
---------------------------------------
Process CPU priority is higher than normal

Disable foreground boost
Enable "Windows Dynamic Thread Priority Boost"
Highest thread priority is 14 when in the foreground
Highest thread priority is 12 when in the background

Disable foreground boost
Disable "Windows Dynamic Thread Priority Boost"
When in the foreground, its highest thread priority is 10
When in the background, its highest thread priority is 10
---------------------------------------
It can be seen that enabling or disabling foreground boost makes no difference

Expected behavior of foreground boost:
Process CPU priority is normal

Enable foreground boost
Enable "Windows Dynamic Thread Priority Boost"
Highest thread priority is 14 when in the foreground
Highest thread priority is 10 when in the background

Enable foreground boost
Disable "Windows Dynamic Thread Priority Boost"
When in the foreground, its highest thread priority is 10
When in the background, its highest thread priority is 8


Jeremy Collake

The behavior you describe sounds consistent with the design.

Note that "Windows dynamic thread priority boost" is unrelated to the "Foreground Boosting" feature of Process Lasso. The former is a native feature of Windows that will temporarily boost a thread priority when it wakes.

The "Foreground Boost" of Process Lasso will only raise the priority class of the entire process. Individual thread priorities will not be impacted by it, whether it is on or off.

Does that clear it up?
Software Engineer. Bitsum LLC.

login404

In my test, enabling "foreground boost" does not increase the priority of foreground processes
Therefore, I mistakenly thought that this feature uses special technology to boost the priority of threads
So, is it a BUG that "foreground boost" does not increase process priority?

Jeremy Collake

#5
To our knowledge, it is working correctly. The following caveats exist:

1. The default setting is to ignore any processes of non-normal priority class.
2. There will be a delay of up to 1 refresh interval (default 1 second) before a new foreground process is raised in priority class. Of course, it will also be lowered when the process is moved out of the foreground.
3. The bitsumsessionagent.exe process should be running so that the foreground process can be correctly identified.
Software Engineer. Bitsum LLC.

login404

It seems that 3 is the reason
bitsummsessionagent.exe is not running
After running it manually, the foreground upgrade works normally

Jeremy Collake

Ah, I'm glad we determined the cause!

Had you disabled that service?
Software Engineer. Bitsum LLC.

login404

Yes, I deleted the task plan
Does bitsumsessionagent.exe carry other functions in addition to foreground elevation?

Jeremy Collake

Yes, it is also used to detect idle input time for the IdleSaver feature.

Foreground detection is also relevant to ProBalance.
Software Engineer. Bitsum LLC.

johnhsmith

Thanks @Jeremy and @login404 for the detailed explanation about the "Foreground Boost" mechanism!

I've also encountered a similar situation when disabling bitsumsessionagent.exe without realizing it affects multiple critical features like this.

If you want to optimize the system, you could keep this service but adjust the Task Scheduler to run it with lower priority (e.g., trigger at system startup instead of running continuously).

P.S.: This thread is super helpful for Process Lasso users!                                                          block blast

Jeremy Collake

The bitsumsessionagent.exe process actually already starts only at user login and at a Below Normal priority class. It also has virtually zero overhead, so I don't recommend disabling it in an effort to reduce resource use.

We'll consider adding some notice when the session agent is found to not be running, so that users are aware of the impact of that.
Software Engineer. Bitsum LLC.

login404

If the front-end upgrade could also adjust the I/O priority at the same time, that would be even better.

There is a way to add dwm.exe and csrss.exe to MMCSS scheduling to boost their thread priority. 
The boosted priority is set to 20-21, which is less aggressive than setting the process to real-time at 31. 
I currently use a PS1 script to achieve this. It would be even better if Process Lasso could add this feature.

Here is the implementation of this script
It runs in a loop every 60 seconds because all fullscreen exclusive and partial borderless fullscreen programs will disable it
I also set the process of this script to E-Core with a priority of idle to reduce overhead

$signature = @'
[DllImport("dwmapi.dll", SetLastError=true)]
public static extern int DwmEnableMMCSS(bool enable);
'@
$dwmapi = Add-Type -MemberDefinition $signature -Name "DwmApi" -Namespace "API" -PassThru

while ($true) {
    $result = $dwmapi::DwmEnableMMCSS($true)
    Start-Sleep -Seconds 60
}

This is another script to implement automatic process suspension.
I once suggested adding this feature.
Since Process Lasso hasn't implemented it yet,
I'm sharing it here.
The example is to suspend chrome.exe when game.exe is in the foreground,
and to unsuspend chrome.exe when game.exe is in the background.
This script calls https://github.com/craftwar/suspend to suspend processes.

Add-Type @"
using System;
using System.Runtime.InteropServices;
public class Win32 {
    [DllImport("user32.dll")]
    public static extern IntPtr GetForegroundWindow();

    [DllImport("user32.dll", SetLastError=true)]
    public static extern uint GetWindowThreadProcessId(IntPtr hWnd, out uint lpdwProcessId);
}
"@

$WasForeground = $null

while ($true) {
    $foregroundHandle = [Win32]::GetForegroundWindow()
    $processId = 0
    [Win32]::GetWindowThreadProcessId($foregroundHandle, [ref]$processId) > $null

    $foregroundProcessName = try { (Get-Process -Id $processId).ProcessName } catch { "" }

    $isForeground = $foregroundProcessName -eq "Game"

    if ($isForeground -ne $WasForeground)
{
        if ($isForeground)
{ & "D:\Portable\Suspend.exe" chrome.exe > $null }
else
{ & "D:\Portable\Suspend.exe" -r chrome.exe > $null }
        $WasForeground = $isForeground
    }

    Start-Sleep -Seconds 1
}

login404

I tried using a PS1 script to boost the I/O priority of the foreground process, 
but due to the inheritance mechanism, unnecessary child processes were also boosted. 
Process Lasso's foreground boosting doesn't have this issue. How do they manage to do that?

Jeremy Collake

I'd guess that the script is less responsive than Process Lasso, thus child processes inherited the priority even when the parent process wasn't actually in the foreground when they launched.
Software Engineer. Bitsum LLC.

login404

It doesn't seem to be an issue of response speed, 
but rather that the inheritance mechanisms for CPU and I/O priority are different. 
Manually set the CPU and I/O priority of cmd.exe to high, 
and run processes without a GUI in this CMD to ensure the foreground process doesn't change. 
The launched process will inherit the I/O priority, but not the CPU priority.

Jeremy Collake

That's correct. Process Lasso's foreground boosting only adjusts the priority class, not the I/O priority directly. To avoid this type of propagation of the I/O priority, I think that's the best approach.
Software Engineer. Bitsum LLC.

login404

Since changing I/O priority to high requires very high privileges, 
most programs won't use it by default. 
Based on this observation, the solution can be designed as follows: 
if a process has high I/O priority 
and doesn't match any I/O priority rule, 
then change its I/O priority to normal. 
Even if a rare process is mistakenly modified, it will hardly cause any problems.

This is the implementation of a PS1 script written by Gemini. 
Since the I/O priority rules of Process Lasso cannot be accessed, 
subprocesses with a default CPU priority higher than normal will be ignored 
to avoid conflicts with manually set rules. 
Administrative privileges are required to run it using NSudo.

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

public static class NativeMethods
{
    // --- I/O Priority Functions ---

    [DllImport("ntdll.dll", SetLastError = true)]
    public static extern int NtSetInformationProcess(
        IntPtr processHandle,
        PROCESS_INFORMATION_CLASS processInformationClass,
        ref IO_PRIORITY_HINT ioPriority,
        int processInformationLength
    );

    [DllImport("ntdll.dll", SetLastError = true)]
    public static extern int NtQueryInformationProcess(
        IntPtr processHandle,
        PROCESS_INFORMATION_CLASS processInformationClass,
        out IO_PRIORITY_HINT ioPriority,
        int processInformationLength,
        out int returnLength
    );

    public enum PROCESS_INFORMATION_CLASS { ProcessIoPriority = 33 }

    public enum IO_PRIORITY_HINT
    {
        IoPriorityVeryLow = 0,
        IoPriorityLow = 1,
        IoPriorityNormal = 2,
        IoPriorityHigh = 3,
        IoPriorityCritical = 4, // 虽然存在,但不建议普通应用使用
        MaxIoPriorityTypes
    }

    // --- Foreground Window Functions ---

    [DllImport("user32.dll")]
    public static extern IntPtr GetForegroundWindow();

    [DllImport("user32.dll", SetLastError=true)]
    public static extern uint GetWindowThreadProcessId(IntPtr hWnd, out uint lpdwProcessId);
}
"@ -PassThru

# 获取并返回当前前台窗口的进程对象
function Get-ForegroundProcess {
    $hwnd = [NativeMethods]::GetForegroundWindow()
    if ($hwnd -eq [IntPtr]::Zero) { return $null }

    $processId = 0
    [NativeMethods]::GetWindowThreadProcessId($hwnd, [ref]$processId) | Out-Null
    if ($processId -eq 0) { return $null }

    return Get-Process -Id $processId -ErrorAction SilentlyContinue
}

# 获取指定进程的I/O优先级
function Get-ProcessIoPriority {
    param(
        [Parameter(Mandatory=$true, ValueFromPipeline=$true)]
        [System.Diagnostics.Process] $Process
    )

    $priorityHint = [NativeMethods+IO_PRIORITY_HINT]::IoPriorityNormal
    $returnLength = 0
    $infoLength = 4

    $result = [NativeMethods]::NtQueryInformationProcess(
        $Process.Handle,
        [NativeMethods+PROCESS_INFORMATION_CLASS]::ProcessIoPriority,
        [ref]$priorityHint,
        $infoLength,
        [ref]$returnLength
    )

    if ($result -eq 0) {
        return $priorityHint.ToString().Replace("IoPriority", "")
    } else {
        return $null
    }
}

# 设置指定进程的I/O优先级
function Set-ProcessIoPriority {
    param(
        [Parameter(Mandatory=$true, ValueFromPipeline=$true)]
        [System.Diagnostics.Process] $Process,
        [Parameter(Mandatory=$true)]
        [ValidateSet("VeryLow", "Low", "Normal", "High")]
        [string] $IoPriority
    )

    $priorityHint = [NativeMethods+IO_PRIORITY_HINT]::$("IoPriority" + $IoPriority)
    $infoLength = 4

    $result = [NativeMethods]::NtSetInformationProcess(
        $Process.Handle,
        [NativeMethods+PROCESS_INFORMATION_CLASS]::ProcessIoPriority,
        [ref]$priorityHint,
        $infoLength
    )

    if ($result -ne 0) {
        $win32Error = [System.Runtime.InteropServices.Marshal]::GetExceptionForHR($result)
    }
}

# --- 主循环 ---
$lastProcess = $null
$originalIoPriority = $null

# 进程黑名单
#$processBlacklist = @("explorer", "cmd", "powershell", "conhost", "XYplorer", "XY64ctxmenu", "NSudoLG")

try {
    while ($true) {
        $currentProcess = Get-ForegroundProcess

        # 检查是否获取到进程 以及进程是否和上一个相同
        if ($null -ne $currentProcess -and $currentProcess.Id -ne $lastProcess.Id) {

            # 如果存在上一个被修改的进程 则恢复其原始I/O优先级
            if ($null -ne $lastProcess -and $null -ne $originalIoPriority) {
                try {
                    # 检查进程是否还在运行
                    $procCheck = Get-Process -Id $lastProcess.Id -ErrorAction SilentlyContinue
                    if ($procCheck) {
                       Set-ProcessIoPriority -Process $lastProcess -IoPriority $originalIoPriority
                    }
                } catch {
                }
            }

            # 处理新的前台进程
            # 检查进程是否在黑名单中
            if ($currentProcess.Name -notin $processBlacklist) {
                $newOriginalPriority = Get-ProcessIoPriority -Process $currentProcess

                # 确保成功获取到原始优先级 仅修改正常优先级进程
                if ($null -ne $newOriginalPriority -and $newOriginalPriority -ne "High" -and $newOriginalPriority -ne "Low" -and $newOriginalPriority -ne "VeryLow") {
                    Set-ProcessIoPriority -Process $currentProcess -IoPriority "High"

                    # 更新状态变量 记录当前被修改的进程和其原始优先级
                    $lastProcess = $currentProcess
                    $originalIoPriority = $newOriginalPriority
                } else {
                    # 如果优先级已经是High或无法获取则不处理 并将lastProcess置空
                    $lastProcess = $null
                    $originalIoPriority = $null
                }
            } else {
                 # 如果是黑名单进程也置空
                 $lastProcess = $null
                 $originalIoPriority = $null
            }
        }
        # 检查所有进程
        $allProcesses = Get-Process -ErrorAction SilentlyContinue
        foreach ($proc in $allProcesses) {
            try {
                # CPU优先级为 Normal/BelowNormal/Idle
                $cpuPriority = $proc.PriorityClass.ToString()
                if ($cpuPriority -in "Normal", "BelowNormal", "Idle") {
                   
                    # I/O优先级为 High
                    $ioPriority = Get-ProcessIoPriority -Process $proc
                    if ($null -ne $ioPriority -and $ioPriority -eq "High") {
                       
                        # 如果该进程是当前被脚本锁定的前台进程则跳过
                        if (($null -ne $lastProcess) -and ($proc.Id -eq $lastProcess.Id)) {
                            continue
                        }
                        # I/O优先级设置为正常
                        Set-ProcessIoPriority -Process $proc -IoPriority "Normal"
                    }
                }
            }
            catch {
                # 捕获异常防止脚本中断 例如进程在检查时已退出
            }
        }
        Start-Sleep -Seconds 1
    }
}
finally {
    # 当脚本停止时恢复最后进程的优先级
    if ($null -ne $lastProcess -and $null -ne $originalIoPriority) {
        try {
             $procCheck = Get-Process -Id $lastProcess.Id -ErrorAction SilentlyContinue
             if ($procCheck) {
                Set-ProcessIoPriority -Process $lastProcess -IoPriority $originalIoPriority
             }
        } catch {
        }
    }
}

login404

#18
Using D3DKMTSetProcessSchedulingPriorityClass allows you to modify the GPU priority.

Check GPU priority
.\GpuPriority.ps1 -ProcessName "Game" -Query
Set GPU priority
.\GpuPriority.ps1 -ProcessName "Game" -Priority "High"

The available priority levels are 0-5. To set priority level 5, you need to use NSudo to elevate privileges.
Idle = 0, BelowNormal = 1, Normal = 2, AboveNormal = 3 , High = 4 , RealTime = 5

[CmdletBinding(DefaultParameterSetName = 'Set')]
param(
    [Parameter(Mandatory=$true, Position=0, ParameterSetName='Set')]
    [Parameter(Mandatory=$true, Position=0, ParameterSetName='Query')]
    [string]$ProcessName,

    [Parameter(Mandatory=$true, ParameterSetName='Set')]
    [ValidateSet("Idle", "BelowNormal", "Normal", "AboveNormal", "High", "RealTime")]
    [string]$Priority,

    [Parameter(Mandatory=$true, ParameterSetName='Query')]
    [Switch]$Query
)

$cSharpSignature = @"
using System;
using System.Runtime.InteropServices;

public enum D3DKMT_SCHEDULING_PRIORITY_CLASS
{
    Idle = 0,
    BelowNormal = 1,
    Normal = 2,
    AboveNormal = 3,
    High = 4,
    RealTime = 5
}

public static class D3DKMT
{
    [DllImport("gdi32.dll", SetLastError = true)]
    public static extern int D3DKMTSetProcessSchedulingPriorityClass(
        IntPtr hProcess,
        D3DKMT_SCHEDULING_PRIORITY_CLASS Priority
    );

    [DllImport("gdi32.dll", SetLastError = true)]
    public static extern int D3DKMTGetProcessSchedulingPriorityClass(
        IntPtr hProcess,
        out D3DKMT_SCHEDULING_PRIORITY_CLASS Priority
    );
}
"@

try {
    Add-Type -TypeDefinition $cSharpSignature -Language CSharp
}
catch {
    Write-Error "Failed to compile the P/Invoke signature. Error: $_"
    Exit
}

try {
    Write-Host "Attempting to find process '$ProcessName'..."
    # 获取进程对象 如果找不到则会抛出错误
    $process = Get-Process -Name $ProcessName -ErrorAction Stop
    $processHandle = $process.Handle
    Write-Host "Found process $($process.ProcessName) with ID $($process.Id)."

    # --- 根据参数集决定是查询还是设置 ---

    if ($PSCmdlet.ParameterSetName -eq 'Query') {
        # --- 查询模式 ---
        Write-Host "Querying GPU priority..."
       
        # 声明一个变量用于接收 out 参数的返回值
        $currentPriority = [D3DKMT_SCHEDULING_PRIORITY_CLASS]::Normal

        # 调用 Get 函数 注意要使用 [ref] 将变量传递给 out 参数
        $result = [D3DKMT]::D3DKMTGetProcessSchedulingPriorityClass($processHandle, [ref]$currentPriority)

        if ($result -eq 0) { # NTSTATUS 0 (STATUS_SUCCESS) 表示成功
            Write-Host -ForegroundColor Green "Success! Current GPU priority for '$ProcessName' is: $currentPriority"
        }
        else {
            Write-Error "Failed to get GPU priority. The function returned NTSTATUS code: 0x$($result.ToString('X'))"
        }
    }
    else {
        # --- 设置模式 ---
        $priorityValue = [D3DKMT_SCHEDULING_PRIORITY_CLASS]::$Priority
        Write-Host "Setting GPU priority to '$Priority' (Value: $($priorityValue.value__))"

        # 调用 Set 函数
        $result = [D3DKMT]::D3DKMTSetProcessSchedulingPriorityClass($processHandle, $priorityValue)

        if ($result -eq 0) {
            Write-Host -ForegroundColor Green "Successfully set GPU priority for process '$ProcessName'."
        }
        else {
            Write-Error "Failed to set GPU priority. The function returned NTSTATUS code: 0x$($result.ToString('X'))"
        }
    }
}
catch {
    Write-Error "An error occurred: $_"
    if ($_.CategoryInfo.Reason -eq 'ProcessNotFoundException') {
        Write-Error "Process with name '$ProcessName' was not found."
    }
}

Monitoring the GPU priority of processes
.\GpuPriorityMonitoring.ps1 -ProcessName "Game"

param(
    [Parameter(Mandatory=$true)]
    [string]$ProcessName
)

#region Admin Check
# ------------------------------------------------------------------------------------
# 1. 检查并请求管理员权限
# ------------------------------------------------------------------------------------
if (-NOT ([Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)) {
    Write-Warning "Administrator privileges are required. Attempting to elevate..."
    Start-Process powershell.exe -Verb RunAs -ArgumentList ("-NoProfile -File `"{0}`" -ProcessName `"{1}`"" -f $MyInvocation.MyCommand.Path, $ProcessName)
    Exit
}
#endregion

#region P/Invoke C# Signature
# ------------------------------------------------------------------------------------
# 2. 定义需要调用的原生函数和枚举
# ------------------------------------------------------------------------------------
Add-Type -TypeDefinition @"
using System;
using System.Runtime.InteropServices;

public static class NativeMethods
{
    [DllImport("gdi32.dll", SetLastError = true)]
    public static extern int D3DKMTGetProcessSchedulingPriorityClass(
        IntPtr hProcess,
        out D3DKMT_SCHEDULING_PRIORITY_CLASS Priority
    );

    public enum D3DKMT_SCHEDULING_PRIORITY_CLASS
    {
        Idle = 0,
        BelowNormal = 1,
        Normal = 2,
        AboveNormal = 3,
        High = 4,
        RealTime = 5
    }
}
"@ -PassThru | Out-Null
#endregion

#region Helper Function
# ------------------------------------------------------------------------------------
# 3. 封装原生调用的辅助函数
# ------------------------------------------------------------------------------------
function Get-ProcessGpuPriority {
    param(
        [Parameter(Mandatory=$true, ValueFromPipeline=$true)]
        [System.Diagnostics.Process] $Process
    )
   
    # 声明一个变量用于接收 out 参数的返回值
    $priority = [NativeMethods+D3DKMT_SCHEDULING_PRIORITY_CLASS]::Normal
   
    # 调用原生函数
    $result = [NativeMethods]::D3DKMTGetProcessSchedulingPriorityClass($Process.Handle, [ref]$priority)
   
    if ($result -eq 0) { # NTSTATUS 0 (STATUS_SUCCESS) 表示成功
        return $priority
    } else {
        return $null
    }
}
#endregion

# --- 主循环 ---
Write-Host "Starting GPU priority monitor for process '$ProcessName.exe'."
Write-Host "Press Ctrl+C to stop."

try {
    while ($true) {
        # 查找进程,忽略错误,以便在进程未运行时继续循环
        $process = Get-Process -Name $ProcessName -ErrorAction SilentlyContinue

        # 清除当前行以便写入新状态
        Write-Host "`r" -NoNewline

        if ($process) {
            $gpuPriority = Get-ProcessGpuPriority -Process $process
           
            if ($null -ne $gpuPriority) {
                $priorityValue = $gpuPriority.value__
                $priorityName = $gpuPriority.ToString()
                $timestamp = Get-Date -Format "HH:mm:ss"
               
                # 构造输出字符串
                $output = "[$timestamp] Watching '$($process.Name)': GPU Priority is $priorityValue ($priorityName)"
            } else {
                $output = "[$(Get-Date -Format "HH:mm:ss")] Watching '$($process.Name)': Failed to query GPU priority (Access may be denied)."
            }
        }
        else {
            $output = "[$(Get-Date -Format "HH:mm:ss")] Waiting for process '$ProcessName.exe' to start..."
        }

        # 写入状态到控制台,并用空格填充行尾以清除上一条消息的残留字符
        Write-Host ($output + (" " * ($Host.UI.RawUI.WindowSize.Width - $output.Length - 1))) -NoNewline
       
        Start-Sleep -Seconds 1
    }
}
catch [System.Management.Automation.ActionPreferenceStopException] {
    # 捕获 Ctrl+C, 正常退出
    Write-Host "`nMonitor stopped."
}
finally {
    # 确保光标换到新行
    Write-Host ""
}

foreground boost
Merged I/O and GPU priority boosts 
Since the stability of GPU priority 5 hasn't been tested, 
this script elevates the GPU priority to 4
The GPU priority is not inherited.

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

public static class NativeMethods
{
    // --- I/O Priority ---
    [DllImport("ntdll.dll")]
    public static extern int NtSetInformationProcess(IntPtr processHandle, PROCESS_INFORMATION_CLASS processInformationClass, ref IO_PRIORITY_HINT ioPriority, int processInformationLength);

    [DllImport("ntdll.dll")]
    public static extern int NtQueryInformationProcess(IntPtr processHandle, PROCESS_INFORMATION_CLASS processInformationClass, out IO_PRIORITY_HINT ioPriority, int processInformationLength, out int returnLength);

    public enum PROCESS_INFORMATION_CLASS { ProcessIoPriority = 33 }
    public enum IO_PRIORITY_HINT { IoPriorityVeryLow = 0, IoPriorityLow = 1, IoPriorityNormal = 2, IoPriorityHigh = 3, IoPriorityCritical = 4, MaxIoPriorityTypes }

    // --- GPU Priority ---
    [DllImport("gdi32.dll")]
    public static extern int D3DKMTSetProcessSchedulingPriorityClass(IntPtr hProcess, D3DKMT_SCHEDULING_PRIORITY_CLASS Priority);

    [DllImport("gdi32.dll")]
    public static extern int D3DKMTGetProcessSchedulingPriorityClass(IntPtr hProcess, out D3DKMT_SCHEDULING_PRIORITY_CLASS Priority);

    public enum D3DKMT_SCHEDULING_PRIORITY_CLASS { Idle = 0, BelowNormal = 1, Normal = 2, AboveNormal = 3 , High = 4 , RealTime = 5 }

    // --- Foreground Window ---
    [DllImport("user32.dll")]
    public static extern IntPtr GetForegroundWindow();

    [DllImport("user32.dll", SetLastError=true)]
    public static extern uint GetWindowThreadProcessId(IntPtr hWnd, out uint lpdwProcessId);
}
"@ -PassThru | Out-Null

# --- 辅助函数区域 ---
function Get-ForegroundProcess {
    $hwnd = [NativeMethods]::GetForegroundWindow()
    if ($hwnd -eq [IntPtr]::Zero) { return $null }
    $processId = 0
    [NativeMethods]::GetWindowThreadProcessId($hwnd, [ref]$processId) | Out-Null
    if ($processId -eq 0) { return $null }
    return Get-Process -Id $processId -ErrorAction SilentlyContinue
}

# --- I/O 优先级函数 ---
function Get-ProcessIoPriority {
    param([Parameter(Mandatory=$true, ValueFromPipeline=$true)][System.Diagnostics.Process] $Process)
    $priorityHint = [NativeMethods+IO_PRIORITY_HINT]::IoPriorityNormal
    $returnLength = 0
    $result = [NativeMethods]::NtQueryInformationProcess($Process.Handle, [NativeMethods+PROCESS_INFORMATION_CLASS]::ProcessIoPriority, [ref]$priorityHint, 4, [ref]$returnLength)
    if ($result -eq 0) { return $priorityHint.ToString().Replace("IoPriority", "") } else { return $null }
}

function Set-ProcessIoPriority {
    param(
        [Parameter(Mandatory=$true, ValueFromPipeline=$true)][System.Diagnostics.Process] $Process,
        [Parameter(Mandatory=$true)][ValidateSet("VeryLow", "Low", "Normal", "High")][string] $IoPriority
    )
    $priorityHint = [NativeMethods+IO_PRIORITY_HINT]::$("IoPriority" + $IoPriority)
    $infoLength = 4
    [NativeMethods]::NtSetInformationProcess($Process.Handle, [NativeMethods+PROCESS_INFORMATION_CLASS]::ProcessIoPriority, [ref]$priorityHint, $infoLength) | Out-Null
}

# --- GPU 优先级函数 ---
function Get-ProcessGpuPriority {
    param([Parameter(Mandatory=$true, ValueFromPipeline=$true)][System.Diagnostics.Process] $Process)
    $priority = [NativeMethods+D3DKMT_SCHEDULING_PRIORITY_CLASS]::Normal
    $result = [NativeMethods]::D3DKMTGetProcessSchedulingPriorityClass($Process.Handle, [ref]$priority)
    if ($result -eq 0) { return $priority } else { return $null }
}

function Set-ProcessGpuPriority {
    param([Parameter(Mandatory=$true, ValueFromPipeline=$true)][System.Diagnostics.Process] $Process, [Parameter(Mandatory=$true)][NativeMethods+D3DKMT_SCHEDULING_PRIORITY_CLASS] $Priority)
    [NativeMethods]::D3DKMTSetProcessSchedulingPriorityClass($Process.Handle, $Priority) | Out-Null
}

# --- 主循环 ---
$lastProcess = $null
$originalIoPriority = $null
$originalGpuPriority = $null

# 进程黑名单
$processBlacklist = @("explorer", "dwm")

try {
    while ($true) {
        $currentProcess = Get-ForegroundProcess


        if ($null -ne $currentProcess -and $currentProcess.Id -ne $lastProcess.Id) {

            # --- 恢复上个前台进程的优先级 ---
            if ($null -ne $lastProcess) {
                try {
                    $procCheck = Get-Process -Id $lastProcess.Id -ErrorAction SilentlyContinue
                    if ($procCheck) {
                       if ($null -ne $originalIoPriority) {
                           Set-ProcessIoPriority -Process $lastProcess -IoPriority $originalIoPriority
                       }
                       if ($null -ne $originalGpuPriority) {
                           Set-ProcessGpuPriority -Process $lastProcess -Priority $originalGpuPriority
                       }
                    }
                } catch {}
            }

            # 重置状态 为处理新进程做准备
            $lastProcess = $null; $originalIoPriority = $null; $originalGpuPriority = $null

            # --- 处理新的前台进程 ---
            if ($currentProcess.Name -notin $processBlacklist) {
               
                # 标记我们正在管理这个进程
                $lastProcess = $currentProcess
                $didModify = $false

                # --- 独立处理 I/O 优先级 ---
                $newOriginalIoPriority = Get-ProcessIoPriority -Process $currentProcess
                if ($null -ne $newOriginalIoPriority -and $newOriginalIoPriority -ne "High" -and $newOriginalIoPriority -ne "Low" -and $newOriginalIoPriority -ne "VeryLow") {
                    Set-ProcessIoPriority -Process $currentProcess -IoPriority "High"
                    $originalIoPriority = $newOriginalIoPriority # 仅在修改时记录原始值
                    $didModify = $true
                }

                # --- 独立处理 GPU 优先级 ---
                $newOriginalGpuPriority = Get-ProcessGpuPriority -Process $currentProcess
                if ($null -ne $newOriginalGpuPriority -and $newOriginalGpuPriority -ne "RealTime" -and $newOriginalGpuPriority -ne "High") {
                    Set-ProcessGpuPriority -Process $currentProcess -Priority "High"
                    $originalGpuPriority = $newOriginalGpuPriority # 仅在修改时记录原始值
                    $didModify = $true
                }

                # 如果两个优先级都已经是最高 实际上没有修改
                if (-not $didModify) {
                    # 取消标记 因为没有修改 不需要恢复
                    $lastProcess = $null
                }
            }
        }

        # --- 检查并重置子进程I/O优先级 ---
        $allProcesses = Get-Process -ErrorAction SilentlyContinue
        foreach ($proc in $allProcesses) {
            try {
                if (($null -ne $lastProcess -and $proc.Id -eq $lastProcess.Id) -or ($proc.Name -in $processBlacklist)) {
                    continue
                }
                $cpuPriority = $proc.PriorityClass.ToString()
                if ($cpuPriority -in "Normal", "BelowNormal", "Idle") {
                    $ioPriority = Get-ProcessIoPriority -Process $proc
                    if ($null -ne $ioPriority -and $ioPriority -eq "High") {
                        Set-ProcessIoPriority -Process $proc -IoPriority "Normal"
                    }
                }
            } catch {}
        }
        Start-Sleep -Seconds 1
    }
}
finally {
    # --- 当脚本停止时恢复最后进程的优先级 ---
    if ($null -ne $lastProcess) {
        try {
             $procCheck = Get-Process -Id $lastProcess.Id -ErrorAction SilentlyContinue
             if ($procCheck) {
                if ($null -ne $originalIoPriority) {
                    Set-ProcessIoPriority -Process $lastProcess -IoPriority $originalIoPriority
                }
                if ($null -ne $originalGpuPriority) {
                    Set-ProcessGpuPriority -Process $lastProcess -Priority $originalGpuPriority
                }
             }
        } catch {
        }
    }
}

login404

Script Implementation for Enforcing Affinity Binding and Modifying CPU Time Slice Allocation Using Job Objects

.\ProcessController.ps1 -ProcessName "Game" -Affinity "0-7" -Weight 1
.\ProcessController.ps1 -ProcessId 12345 -Affinity "8 10 12-15" -Weight 9

# =========================================================================
#  ProcessController.ps1
#
#  功能:
#  - 支持交互式控制和一次性参数化设置。
#  - 按 Ctrl+C 或输入 'exit' 可随时安全退出并自动解锁。
#
#  用法:
#  1. 交互模式 (无参数运行):
#     > .\ProcessController.ps1
#
#  2. 一次性模式 (提供 -Affinity 或 -Weight 参数):
#     > .\ProcessController.ps1 -ProcessName "Game" -Affinity "0-7" -Weight 9
#     > .\ProcessController.ps1 -ProcessId 12345 -Affinity "8 10 12-15" -Weight 1
#
#  警告:必须以管理员身份运行!
# =========================================================================

# 步骤 0: 定义脚本参数,以支持不同的启动模式
param(
    # 用于一次性模式的目标选择参数
    [string]$ProcessName,
    [int]$ProcessId,

    # 用于触发一次性模式的可选参数
    [string]$Affinity,
    [ValidateRange(1,9)]
    [int]$Weight
)

# 步骤 1: 使用 Add-Type 定义所有需要的 Win32 API 签名、结构体和枚举
try {
    Add-Type -TypeDefinition @"
    using System;
    using System.Runtime.InteropServices;

    public static class NativeMethods {
        // --- Job Object API ---
        [DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
        public static extern IntPtr CreateJobObjectW(IntPtr lpJobAttributes, string lpName);

        [DllImport("kernel32.dll", SetLastError = true)]
        [return: MarshalAs(UnmanagedType.Bool)]
        public static extern bool AssignProcessToJobObject(IntPtr hJob, IntPtr hProcess);

        [DllImport("kernel32.dll", SetLastError = true)]
        [return: MarshalAs(UnmanagedType.Bool)]
        public static extern bool SetInformationJobObject(IntPtr hJob, JOBOBJECT_INFO_CLASS JobObjectInfoClass, IntPtr lpJobObjectInfo, uint cbJobObjectInfoLength);

        [DllImport("kernel32.dll", SetLastError = true)]
        [return: MarshalAs(UnmanagedType.Bool)]
        public static extern bool CloseHandle(IntPtr hObject);

        // --- 枚举定义 ---
        public enum JOBOBJECT_INFO_CLASS {
            JobObjectBasicLimitInformation = 2,
            JobObjectCpuRateControlInformation = 15
        }

        [Flags]
        public enum JOB_OBJECT_LIMIT_FLAGS : uint {
            JOB_OBJECT_LIMIT_AFFINITY              = 0x00000010,
            JOB_OBJECT_CPU_RATE_CONTROL_ENABLE     = 0x1,
            JOB_OBJECT_CPU_RATE_CONTROL_WEIGHT_BASED = 0x2
        }

        // --- 结构体定义 ---
        [StructLayout(LayoutKind.Sequential)]
        public struct JOBOBJECT_BASIC_LIMIT_INFORMATION {
            public long PerProcessUserTimeLimit;
            public long PerJobUserTimeLimit;
            public JOB_OBJECT_LIMIT_FLAGS LimitFlags;
            public UIntPtr MinimumWorkingSetSize;
            public UIntPtr MaximumWorkingSetSize;
            public uint ActiveProcessLimit;
            public UIntPtr Affinity;
            public uint PriorityClass;
            public uint SchedulingClass;
        }

        [StructLayout(LayoutKind.Explicit)]
        public struct JOBOBJECT_CPU_RATE_CONTROL_INFORMATION {
            [FieldOffset(0)] public JOB_OBJECT_LIMIT_FLAGS ControlFlags;
            [FieldOffset(4)] public uint Weight;
        }
    }
"@ -PassThru -ErrorAction Stop | Out-Null
} catch {
    Write-Host "C# 编译失败: $($_.Exception.Message)" -ForegroundColor Red
    return
}

# --- 步骤 2: 辅助函数 ---

# 解析复杂的亲和性字符串 (例如 "2 5 8-11")
function Parse-AffinityString {
    param([string]$InputString)
   
    [uint64]$totalMask = 0
    # 使用正则表达式替换所有非数字和非连字符的分隔符为空格,然后按空格分割
    $tokens = $InputString -replace '[^\d\-,]', ' ' -split ' ' | Where-Object { $_ }

    foreach ($token in $tokens) {
        if ($token -match '^\d+$') { # 如果是单个数字
            $cpu = [int]$token
            $totalMask = $totalMask -bor (1L -shl $cpu)
        } elseif ($token -match '^(\d+)-(\d+)$') { # 如果是范围
            $startCpu = [int]$matches[1]
            $endCpu = [int]$matches[2]
            if ($startCpu -gt $endCpu) { continue } # 忽略无效范围
            for ($i = $startCpu; $i -le $endCpu; $i++) {
                $totalMask = $totalMask -bor (1L -shl $i)
            }
        } else {
            Write-Warning "已忽略无效的亲和性片段: '$token'"
        }
    }
    return $totalMask
}

# 应用亲和性设置
function Set-JobAffinity {
    param($JobHandle, [uint64]$Mask)

    $limitInfo = New-Object NativeMethods+JOBOBJECT_BASIC_LIMIT_INFORMATION
    $limitInfo.LimitFlags = [NativeMethods+JOB_OBJECT_LIMIT_FLAGS]::JOB_OBJECT_LIMIT_AFFINITY
    $limitInfo.Affinity = New-Object System.UIntPtr($Mask)
   
    # 手动管理内存以传递结构体指针
    $limitInfoPtr = [System.Runtime.InteropServices.Marshal]::AllocHGlobal([System.Runtime.InteropServices.Marshal]::SizeOf($limitInfo))
    [System.Runtime.InteropServices.Marshal]::StructureToPtr($limitInfo, $limitInfoPtr, $false)
   
    $result = [NativeMethods]::SetInformationJobObject($JobHandle, [NativeMethods+JOBOBJECT_INFO_CLASS]::JobObjectBasicLimitInformation, $limitInfoPtr, [System.Runtime.InteropServices.Marshal]::SizeOf($limitInfo))
   
    [System.Runtime.InteropServices.Marshal]::FreeHGlobal($limitInfoPtr)
    return $result
}

# 应用时间片权重设置
function Set-JobWeight {
    param($JobHandle, [int]$Weight)

    $cpuInfo = New-Object NativeMethods+JOBOBJECT_CPU_RATE_CONTROL_INFORMATION
    $cpuInfo.ControlFlags = [NativeMethods+JOB_OBJECT_LIMIT_FLAGS]::JOB_OBJECT_CPU_RATE_CONTROL_ENABLE -bor [NativeMethods+JOB_OBJECT_LIMIT_FLAGS]::JOB_OBJECT_CPU_RATE_CONTROL_WEIGHT_BASED
    $cpuInfo.Weight = $Weight

    # 手动管理内存以传递结构体指针
    $cpuInfoPtr = [System.Runtime.InteropServices.Marshal]::AllocHGlobal([System.Runtime.InteropServices.Marshal]::SizeOf($cpuInfo))
    [System.Runtime.InteropServices.Marshal]::StructureToPtr($cpuInfo, $cpuInfoPtr, $false)

    $result = [NativeMethods]::SetInformationJobObject($JobHandle, [NativeMethods+JOBOBJECT_INFO_CLASS]::JobObjectCpuRateControlInformation, $cpuInfoPtr, [System.Runtime.InteropServices.Marshal]::SizeOf($cpuInfo))
   
    [System.Runtime.InteropServices.Marshal]::FreeHGlobal($cpuInfoPtr)
    return $result
}

# --- 步骤 3: 主逻辑 ---

# 模式检测:检查是否提供了 -Affinity 或 -Weight 参数
$isOneShotMode = $PSBoundParameters.ContainsKey('Affinity') -or $PSBoundParameters.ContainsKey('Weight')

# 获取目标进程
$targetProcesses = $null
if ($isOneShotMode) {
    if ($PSBoundParameters.ContainsKey('ProcessName')) {
        $targetProcesses = Get-Process -Name $ProcessName -ErrorAction SilentlyContinue
    } elseif ($PSBoundParameters.ContainsKey('ProcessId')) {
        $targetProcesses = Get-Process -Id $ProcessId -ErrorAction SilentlyContinue
    } else {
        Write-Error "在一次性模式下,必须提供 -ProcessName 或 -ProcessId 参数。"
        return
    }
} else { # 交互模式
    $processNameInput = Read-Host -Prompt "请输入目标进程名 (不包含.exe), 或留空以输入进程ID"
    if (-not [string]::IsNullOrWhiteSpace($processNameInput)) {
        $targetProcesses = Get-Process -Name $processNameInput -ErrorAction SilentlyContinue
        $ProcessName = $processNameInput # 保存以用于Job名
    } else {
        $processIdInput = Read-Host -Prompt "请输入目标进程ID"
        if (-not [string]::IsNullOrWhiteSpace($processIdInput)) {
            try {
                $ProcessId = [int]$processIdInput
                $targetProcesses = Get-Process -Id $ProcessId -ErrorAction SilentlyContinue
            } catch {}
        }
    }
}

if (-not $targetProcesses) {
    Write-Host "未找到任何目标进程。脚本将退出。" -ForegroundColor Yellow
    return
}

Write-Host "已找到 $($targetProcesses.Count) 个目标进程:" -ForegroundColor Green
$targetProcesses | ForEach-Object { Write-Host "  - $($_.ProcessName) (PID: $($_.Id))" }

# 为 Job Object 起一个唯一的名字
$jobId = if ($ProcessName) { "Name_$($ProcessName)" } else { "PID_$($ProcessId)" }
$jobObjectName = "Global\ProcessControllerJob_For_$($jobId)"

# 创建/打开 Job Object
$jobHandle = [NativeMethods]::CreateJobObjectW([IntPtr]::Zero, $jobObjectName)
if ($jobHandle -eq [IntPtr]::Zero) {
    Write-Host "CreateJobObjectW 失败!错误码: $([System.Runtime.InteropServices.Marshal]::GetLastWin32Error())" -ForegroundColor Red
    return
}
Write-Host "Job Object '$jobObjectName' 已创建/打开。" -ForegroundColor Green

# 分配所有目标进程
foreach ($proc in $targetProcesses) {
    [NativeMethods]::AssignProcessToJobObject($jobHandle, $proc.Handle)
}
Write-Host "已将所有目标进程分配到 Job Object。" -ForegroundColor Cyan
Write-Host "----------------------------------------------------"

# --- 步骤 4: 根据模式执行不同逻辑 ---

if ($isOneShotMode) {
    # --- 一次性模式 ---
    Write-Host "正在应用一次性设置..." -ForegroundColor Yellow
    if ($PSBoundParameters.ContainsKey('Affinity')) {
        $parsedMask = Parse-AffinityString -InputString $Affinity
        if ($parsedMask -gt 0) {
            if (Set-JobAffinity -JobHandle $jobHandle -Mask $parsedMask) {
                Write-Host "成功!亲和性已设置为 0x$($parsedMask.ToString('X'))" -F Green
            } else {
                Write-Host "设置亲和性失败!" -F Red
            }
        }
    }
    if ($PSBoundParameters.ContainsKey('Weight')) {
        if (Set-JobWeight -JobHandle $jobHandle -Weight $Weight) {
            Write-Host "成功!时间片权重已设置为 $Weight" -F Green
        } else {
            Write-Host "设置时间片权重失败!" -F Red
        }
    }
    Write-Host "设置已应用,脚本将退出。限制将持续有效。"
    # 在一次性模式下,我们故意不关闭句柄,以使 Job Object 持续存在
    # [NativeMethods]::CloseHandle($jobHandle)
} else {
    # --- 交互式模式 ---
    # 将主循环包裹在 try...finally 中,以确保无论如何都能执行清理代码
    try {
        Write-Host "已进入交互模式。按 Ctrl+C 可随时安全退出并自动解锁。" -ForegroundColor Cyan
        $currentAffinityMask = $null
        $currentWeight = $null
       
        while ($true) {
            # -- 亲和性设置 --
            $affinityPrompt = "请输入亲和性, 或留空跳过, 或 'exit' 退出"
            if ($currentAffinityMask -ne $null) { $affinityPrompt += " [当前: 0x$($currentAffinityMask.ToString('X'))]" }
            $affinityInput = Read-Host -Prompt $affinityPrompt
            if ($affinityInput -eq 'exit') { break }

            if (-not [string]::IsNullOrWhiteSpace($affinityInput)) {
                $parsedMask = Parse-AffinityString -InputString $affinityInput
                if ($parsedMask -gt 0) {
                    if (Set-JobAffinity -JobHandle $jobHandle -Mask $parsedMask) {
                        $currentAffinityMask = $parsedMask
                        Write-Host "成功!亲和性已更新为 0x$($currentAffinityMask.ToString('X'))" -F Green
                    } else {
                        Write-Host "设置亲和性失败!" -F Red
                    }
                } else {
                    Write-Warning "未从输入中解析出有效的 CPU 核心,亲和性未改变。"
                }
            } else {
                Write-Host "亲和性设置已跳过。" -F Gray
            }

            # -- 时间片权重设置 --
            $weightPrompt = "请输入时间片权重 (1-9), 或留空跳过, 或 'exit' 退出"
            if ($currentWeight -ne $null) { $weightPrompt += " [当前: $currentWeight]" }
            $weightInput = Read-Host -Prompt $weightPrompt
            if ($weightInput -eq 'exit') { break }

            if (-not [string]::IsNullOrWhiteSpace($weightInput)) {
                if ($weightInput -match '^[1-9]$') {
                    if (Set-JobWeight -JobHandle $jobHandle -Weight ([int]$weightInput)) {
                        $currentWeight = [int]$weightInput
                        Write-Host "成功!时间片权重已更新为 $currentWeight" -F Green
                    } else {
                        Write-Host "设置时间片权重失败!" -F Red
                    }
                } else {
                    Write-Warning "无效的输入!权重必须是 1-9 之间的单个数字,权重未改变。"
                }
            } else {
                Write-Host "时间片权重设置已跳过。" -F Gray
            }
            Write-Host "----------------------------------------------------"
            Write-Host "设置完毕,等待下一次修改... (按 Enter 键以再次输入)"
            Read-Host | Out-Null
        }
    }
    finally {
        # --- 统一的、保证执行的解锁和清理逻辑 ---
        Write-Host "" # 换行
        Write-Host "正在退出... 自动移除所有限制以进行解锁..." -ForegroundColor Yellow

        # 检查句柄是否有效,防止在创建失败时也尝试解锁
        if ($jobHandle -and $jobHandle -ne [IntPtr]::Zero) {
            # 1. 解锁亲和性
            $unlockLimitInfo = New-Object NativeMethods+JOBOBJECT_BASIC_LIMIT_INFORMATION
            $unlockLimitInfo.LimitFlags = 0
            $unlockLimitInfoPtr = [System.Runtime.InteropServices.Marshal]::AllocHGlobal([System.Runtime.InteropServices.Marshal]::SizeOf($unlockLimitInfo))
            [System.Runtime.InteropServices.Marshal]::StructureToPtr($unlockLimitInfo, $unlockLimitInfoPtr, $false)
            [NativeMethods]::SetInformationJobObject($jobHandle, [NativeMethods+JOBOBJECT_INFO_CLASS]::JobObjectBasicLimitInformation, $unlockLimitInfoPtr, [System.Runtime.InteropServices.Marshal]::SizeOf($unlockLimitInfo)) | Out-Null
            [System.Runtime.InteropServices.Marshal]::FreeHGlobal($unlockLimitInfoPtr)
           
            # 2. 解锁时间片权重
            $unlockCpuInfo = New-Object NativeMethods+JOBOBJECT_CPU_RATE_CONTROL_INFORMATION
            $unlockCpuInfo.ControlFlags = 0
            $unlockCpuInfoPtr = [System.Runtime.InteropServices.Marshal]::AllocHGlobal([System.Runtime.InteropServices.Marshal]::SizeOf($unlockCpuInfo))
            [System.Runtime.InteropServices.Marshal]::StructureToPtr($unlockCpuInfo, $unlockCpuInfoPtr, $false)
            [NativeMethods]::SetInformationJobObject($jobHandle, [NativeMethods+JOBOBJECT_INFO_CLASS]::JobObjectCpuRateControlInformation, $unlockCpuInfoPtr, [System.Runtime.InteropServices.Marshal]::SizeOf($unlockCpuInfo)) | Out-Null
            [System.Runtime.InteropServices.Marshal]::FreeHGlobal($unlockCpuInfoPtr)
           
            Write-Host "解锁成功!" -ForegroundColor Green

            # 最终清理句柄
            [NativeMethods]::CloseHandle($jobHandle)
        }
        Write-Host "控制器已终止。"
    }
}

Jeremy Collake

Software Engineer. Bitsum LLC.

login404

The method for modifying CPU time slices in the previous script was wrong. 
It should use `DUMMYUNIONNAME.Weight`, 
not `SchedulingClass`.

This is the revised script.

# =========================================================================
#  ProcessController.ps1
#
#  功能:
#  - 支持交互式控制和一次性参数化设置。
#  - 按 Ctrl+C 或输入 'exit' 可随时安全退出并自动解锁。
#
#  用法:
#  1. 交互模式 (无参数运行):
#     > .\ProcessController.ps1
#
#  2. 一次性模式 (提供 -Affinity 或 -Weight 参数):
#     > .\ProcessController.ps1 -ProcessName "Game" -Affinity "0-7" -Weight 9
#     > .\ProcessController.ps1 -ProcessId 12345 -Affinity "8 10 12-15" -Weight 1
#
#  警告:必须以管理员身份运行!
# =========================================================================

# 步骤 0: 定义脚本参数,以支持不同的启动模式
param(
    # 用于一次性模式的目标选择参数
    [string]$ProcessName,
    [int]$ProcessId,

    # 用于触发一次性模式的可选参数
    [string]$Affinity,
    [ValidateRange(1,9)]
    [int]$Weight
)

# 步骤 1: 使用 Add-Type 定义所有需要的 Win32 API 签名、结构体和枚举
try {
    Add-Type -TypeDefinition @"
    using System;
    using System.Runtime.InteropServices;

    public static class NativeMethods {
        // --- Job Object API ---
        [DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
        public static extern IntPtr CreateJobObjectW(IntPtr lpJobAttributes, string lpName);

        [DllImport("kernel32.dll", SetLastError = true)]
        [return: MarshalAs(UnmanagedType.Bool)]
        public static extern bool AssignProcessToJobObject(IntPtr hJob, IntPtr hProcess);

        // 为 SetInformationJobObject 创建两个不同的入口点,以处理不同的结构体,避免类型冲突
        [DllImport("kernel32.dll", SetLastError = true, EntryPoint="SetInformationJobObject")]
        public static extern bool SetInformationJobObjectBasic(IntPtr hJob, JOBOBJECT_INFO_CLASS JobObjectInfoClass, ref JOBOBJECT_BASIC_LIMIT_INFORMATION lpJobObjectInfo, uint cbJobObjectInfoLength);

        [DllImport("kernel32.dll", SetLastError = true, EntryPoint="SetInformationJobObject")]
        public static extern bool SetInformationJobObjectCpuRate(IntPtr hJob, JOBOBJECT_INFO_CLASS JobObjectInfoClass, ref JOBOBJECT_CPU_RATE_CONTROL_INFORMATION lpJobObjectInfo, uint cbJobObjectInfoLength);

        [DllImport("kernel32.dll", SetLastError = true)]
        [return: MarshalAs(UnmanagedType.Bool)]
        public static extern bool CloseHandle(IntPtr hObject);

        // --- 枚举定义 ---
        public enum JOBOBJECT_INFO_CLASS {
            JobObjectBasicLimitInformation = 2,
            JobObjectCpuRateControlInformation = 15
        }

        [Flags]
        public enum JOB_OBJECT_LIMIT_FLAGS : uint {
            JOB_OBJECT_LIMIT_AFFINITY              = 0x00000010,
            JOB_OBJECT_CPU_RATE_CONTROL_ENABLE     = 0x1,
            JOB_OBJECT_CPU_RATE_CONTROL_WEIGHT_BASED = 0x2
        }

        // --- 结构体定义 ---
        [StructLayout(LayoutKind.Sequential)]
        public struct JOBOBJECT_BASIC_LIMIT_INFORMATION {
            public long PerProcessUserTimeLimit;
            public long PerJobUserTimeLimit;
            public JOB_OBJECT_LIMIT_FLAGS LimitFlags;
            public UIntPtr MinimumWorkingSetSize;
            public UIntPtr MaximumWorkingSetSize;
            public uint ActiveProcessLimit;
            public UIntPtr Affinity;
            public uint PriorityClass;
            public uint SchedulingClass;
        }

        [StructLayout(LayoutKind.Explicit)]
        public struct JOBOBJECT_CPU_RATE_CONTROL_INFORMATION {
            [FieldOffset(0)] public JOB_OBJECT_LIMIT_FLAGS ControlFlags;
            [FieldOffset(4)] public uint Weight;
        }
    }
"@ -PassThru -ErrorAction Stop | Out-Null
} catch {
    Write-Host "C# 编译失败: $($_.Exception.Message)" -ForegroundColor Red
    return
}

# --- 步骤 2: 辅助函数 ---

# 解析复杂的亲和性字符串 (例如 "2 5 8-11")
function Parse-AffinityString {
    param([string]$InputString)
   
    [uint64]$totalMask = 0
    # 使用正则表达式替换所有非数字和非连字符的分隔符为空格,然后按空格分割
    $tokens = $InputString -replace '[^\d\-,]', ' ' -split ' ' | Where-Object { $_ }

    foreach ($token in $tokens) {
        if ($token -match '^\d+$') { # 如果是单个数字
            $cpu = [int]$token
            $totalMask = $totalMask -bor (1L -shl $cpu)
        } elseif ($token -match '^(\d+)-(\d+)$') { # 如果是范围
            $startCpu = [int]$matches[1]
            $endCpu = [int]$matches[2]
            if ($startCpu -gt $endCpu) { continue } # 忽略无效范围
            for ($i = $startCpu; $i -le $endCpu; $i++) {
                $totalMask = $totalMask -bor (1L -shl $i)
            }
        } else {
            Write-Warning "已忽略无效的亲和性片段: '$token'"
        }
    }
    return $totalMask
}

# 应用亲和性设置
function Set-JobAffinity {
    param($JobHandle, [uint64]$Mask)

    $limitInfo = New-Object NativeMethods+JOBOBJECT_BASIC_LIMIT_INFORMATION
    $limitInfo.LimitFlags = [NativeMethods+JOB_OBJECT_LIMIT_FLAGS]::JOB_OBJECT_LIMIT_AFFINITY
    $limitInfo.Affinity = New-Object System.UIntPtr($Mask)
   
    # 使用专门为基础限制信息创建的 P/Invoke 重载
    return [NativeMethods]::SetInformationJobObjectBasic(
        $JobHandle,
        [NativeMethods+JOBOBJECT_INFO_CLASS]::JobObjectBasicLimitInformation,
        [ref]$limitInfo,
        [System.Runtime.InteropServices.Marshal]::SizeOf($limitInfo)
    )
}

# 应用时间片权重设置
function Set-JobWeight {
    param($JobHandle, [int]$Weight)

    $cpuInfo = New-Object NativeMethods+JOBOBJECT_CPU_RATE_CONTROL_INFORMATION
    $cpuInfo.ControlFlags = [NativeMethods+JOB_OBJECT_LIMIT_FLAGS]::JOB_OBJECT_CPU_RATE_CONTROL_ENABLE -bor [NativeMethods+JOB_OBJECT_LIMIT_FLAGS]::JOB_OBJECT_CPU_RATE_CONTROL_WEIGHT_BASED
    $cpuInfo.Weight = $Weight

    # 使用专门为 CPU 速率控制创建的 P/Invoke 重载
    return [NativeMethods]::SetInformationJobObjectCpuRate(
        $JobHandle,
        [NativeMethods+JOBOBJECT_INFO_CLASS]::JobObjectCpuRateControlInformation,
        [ref]$cpuInfo,
        [System.Runtime.InteropServices.Marshal]::SizeOf($cpuInfo)
    )
}

# --- 步骤 3: 主逻辑 ---

# 模式检测:检查是否提供了 -Affinity 或 -Weight 参数
$isOneShotMode = $PSBoundParameters.ContainsKey('Affinity') -or $PSBoundParameters.ContainsKey('Weight')

# 获取目标进程
$targetProcesses = $null
if ($isOneShotMode) {
    if ($PSBoundParameters.ContainsKey('ProcessName')) {
        $targetProcesses = Get-Process -Name $ProcessName -ErrorAction SilentlyContinue
    } elseif ($PSBoundParameters.ContainsKey('ProcessId')) {
        $targetProcesses = Get-Process -Id $ProcessId -ErrorAction SilentlyContinue
    } else {
        Write-Error "在一次性模式下,必须提供 -ProcessName 或 -ProcessId 参数。"
        return
    }
} else { # 交互模式
    $processNameInput = Read-Host -Prompt "请输入目标进程名 (不包含.exe), 或留空以输入进程ID"
    if (-not [string]::IsNullOrWhiteSpace($processNameInput)) {
        $targetProcesses = Get-Process -Name $processNameInput -ErrorAction SilentlyContinue
        $ProcessName = $processNameInput # 保存以用于Job名
    } else {
        $processIdInput = Read-Host -Prompt "请输入目标进程ID"
        if (-not [string]::IsNullOrWhiteSpace($processIdInput)) {
            try {
                $ProcessId = [int]$processIdInput
                $targetProcesses = Get-Process -Id $ProcessId -ErrorAction SilentlyContinue
            } catch {}
        }
    }
}

if (-not $targetProcesses) {
    Write-Host "未找到任何目标进程。脚本将退出。" -ForegroundColor Yellow
    return
}

Write-Host "已找到 $($targetProcesses.Count) 个目标进程:" -ForegroundColor Green
$targetProcesses | ForEach-Object { Write-Host "  - $($_.ProcessName) (PID: $($_.Id))" }

# 为 Job Object 起一个唯一的名字
$jobId = if ($ProcessName) { "Name_$($ProcessName)" } else { "PID_$($ProcessId)" }
$jobObjectName = "Global\ProcessControllerJob_For_$($jobId)"

# 创建/打开 Job Object
$jobHandle = [NativeMethods]::CreateJobObjectW([IntPtr]::Zero, $jobObjectName)
if ($jobHandle -eq [IntPtr]::Zero) {
    Write-Host "CreateJobObjectW 失败!错误码: $([System.Runtime.InteropServices.Marshal]::GetLastWin32Error())" -ForegroundColor Red
    return
}
Write-Host "Job Object '$jobObjectName' 已创建/打开。" -ForegroundColor Green

# 分配所有目标进程
foreach ($proc in $targetProcesses) {
    [NativeMethods]::AssignProcessToJobObject($jobHandle, $proc.Handle)
}
Write-Host "已将所有目标进程分配到 Job Object。" -ForegroundColor Cyan
Write-Host "----------------------------------------------------"

# --- 步骤 4: 根据模式执行不同逻辑 ---

if ($isOneShotMode) {
    # --- 一次性模式 ---
    Write-Host "正在应用一次性设置..." -ForegroundColor Yellow
    if ($PSBoundParameters.ContainsKey('Affinity')) {
        $parsedMask = Parse-AffinityString -InputString $Affinity
        if ($parsedMask -gt 0) {
            if (Set-JobAffinity -JobHandle $jobHandle -Mask $parsedMask) {
                Write-Host "成功!亲和性已设置为 0x$($parsedMask.ToString('X'))" -F Green
            } else {
                Write-Host "设置亲和性失败!" -F Red
            }
        }
    }
    if ($PSBoundParameters.ContainsKey('Weight')) {
        if (Set-JobWeight -JobHandle $jobHandle -Weight $Weight) {
            Write-Host "成功!时间片权重已设置为 $Weight" -F Green
        } else {
            Write-Host "设置时间片权重失败!" -F Red
        }
    }
    Write-Host "设置已应用,脚本将退出。限制将持续有效。"
    # 在一次性模式下,我们故意不关闭句柄,以使 Job Object 持续存在
    # [NativeMethods]::CloseHandle($jobHandle)
} else {
    # --- 交互式模式 ---
    # 将主循环包裹在 try...finally 中,以确保无论如何都能执行清理代码
    try {
        Write-Host "已进入交互模式。按 Ctrl+C 可随时安全退出并自动解锁。" -ForegroundColor Cyan
        $currentAffinityMask = $null
        $currentWeight = $null
       
        while ($true) {
            # -- 亲和性设置 --
            $affinityPrompt = "请输入亲和性, 或留空跳过, 或 'exit' 退出"
            if ($currentAffinityMask -ne $null) { $affinityPrompt += " [当前: 0x$($currentAffinityMask.ToString('X'))]" }
            $affinityInput = Read-Host -Prompt $affinityPrompt
            if ($affinityInput -eq 'exit') { break }

            if (-not [string]::IsNullOrWhiteSpace($affinityInput)) {
                $parsedMask = Parse-AffinityString -InputString $affinityInput
                if ($parsedMask -gt 0) {
                    if (Set-JobAffinity -JobHandle $jobHandle -Mask $parsedMask) {
                        $currentAffinityMask = $parsedMask
                        Write-Host "成功!亲和性已更新为 0x$($currentAffinityMask.ToString('X'))" -F Green
                    } else {
                        Write-Host "设置亲和性失败!" -F Red
                    }
                } else {
                    Write-Warning "未从输入中解析出有效的 CPU 核心,亲和性未改变。"
                }
            } else {
                Write-Host "亲和性设置已跳过。" -F Gray
            }

            # -- 时间片权重设置 --
            $weightPrompt = "请输入时间片权重 (1-9), 或留空跳过, 或 'exit' 退出"
            if ($currentWeight -ne $null) { $weightPrompt += " [当前: $currentWeight]" }
            $weightInput = Read-Host -Prompt $weightPrompt
            if ($weightInput -eq 'exit') { break }

            if (-not [string]::IsNullOrWhiteSpace($weightInput)) {
                if ($weightInput -match '^[1-9]$') {
                    if (Set-JobWeight -JobHandle $jobHandle -Weight ([int]$weightInput)) {
                        $currentWeight = [int]$weightInput
                        Write-Host "成功!时间片权重已更新为 $currentWeight" -F Green
                    } else {
                        Write-Host "设置时间片权重失败!" -F Red
                    }
                } else {
                    Write-Warning "无效的输入!权重必须是 1-9 之间的单个数字,权重未改变。"
                }
            } else {
                Write-Host "时间片权重设置已跳过。" -F Gray
            }
            Write-Host "----------------------------------------------------"
            Write-Host "设置完毕,等待下一次修改... (按 Enter 键以再次输入)"
            Read-Host | Out-Null
        }
    }
    finally {
        # --- 统一的、保证执行的解锁和清理逻辑 ---
        Write-Host "" # 换行
        Write-Host "正在退出... 自动移除所有限制以进行解锁..." -ForegroundColor Yellow

        # 检查句柄是否有效,防止在创建失败时也尝试解锁
        if ($jobHandle -and $jobHandle -ne [IntPtr]::Zero) {
            # 1. 解锁亲和性
            $unlockLimitInfo = New-Object NativeMethods+JOBOBJECT_BASIC_LIMIT_INFORMATION
            $unlockLimitInfo.LimitFlags = 0
            [NativeMethods]::SetInformationJobObjectBasic($jobHandle, [NativeMethods+JOBOBJECT_INFO_CLASS]::JobObjectBasicLimitInformation, [ref]$unlockLimitInfo, [System.Runtime.InteropServices.Marshal]::SizeOf($unlockLimitInfo)) | Out-Null
           
            # 2. 解锁时间片权重
            $unlockCpuInfo = New-Object NativeMethods+JOBOBJECT_CPU_RATE_CONTROL_INFORMATION
            $unlockCpuInfo.ControlFlags = 0
            [NativeMethods]::SetInformationJobObjectCpuRate($jobHandle, [NativeMethods+JOBOBJECT_INFO_CLASS]::JobObjectCpuRateControlInformation, [ref]$unlockCpuInfo, [System.Runtime.InteropServices.Marshal]::SizeOf($unlockCpuInfo)) | Out-Null
           
            Write-Host "解锁成功!" -ForegroundColor Green

            # 最终清理句柄
            [NativeMethods]::CloseHandle($jobHandle)
        }
        Write-Host "控制器已终止。"
    }
}

Jeremy Collake

GPU priority classes are now available as of Process Lasso v16.0 BETA. They've also been added to the Foreground Boosting feature. If you play with it, please let me know if you run into any trouble.

One interesting note is that D3DKMTGetProcessSchedulingPriorityClass requires set permissions on the process handle, even though it's only getting a value.
Software Engineer. Bitsum LLC.

login404

It seems that Windows 11 can use SetProcessInformation along with ProcessTimerResolution to set a process's timer resolution. 
Since I'm currently using Windows 10, I can't test it.

# =========================================================================
#  Set-Remote-Timer.ps1: 使用 SetProcessInformation 远程修改进程的计时器精度
#  这是一个一次性的、设置后即退出的工具。
#  警告:必须以管理员身份运行!
# =========================================================================

# 步骤 1: 定义所有需要的 Win32 API 签名
try {
    Add-Type -TypeDefinition @"
    using System;
    using System.Runtime.InteropServices;

    public static class NativeMethods {
        // --- API 函数签名 ---
        [DllImport("kernel32.dll", SetLastError = true)]
        public static extern IntPtr OpenProcess(ProcessAccessFlags processAccess, bool bInheritHandle, int processId);

        [DllImport("ntdll.dll", SetLastError = true)]
        public static extern int NtSetInformationProcess(
            IntPtr processHandle,
            PROCESS_INFORMATION_CLASS ProcessInformationClass,
            ref PROCESS_TIMER_RESOLUTION_INFORMATION ProcessInformation,
            uint ProcessInformationLength
        );

        [DllImport("kernel32.dll", SetLastError = true)]
        [return: MarshalAs(UnmanagedType.Bool)]
        public static extern bool CloseHandle(IntPtr hObject);

        // --- 枚举和结构体 ---
        [Flags]
        public enum ProcessAccessFlags : uint {
            PROCESS_SET_INFORMATION = 0x0200
        }

        public enum PROCESS_INFORMATION_CLASS : int {
            ProcessTimerResolution = 46
        }

        [StructLayout(LayoutKind.Sequential)]
        public struct PROCESS_TIMER_RESOLUTION_INFORMATION {
            public uint DesiredResolution;
            public uint ActualResolution;
            public uint SetResolution; // 1 to set, 0 to clear
        }
    }
"@ -PassThru -ErrorAction Stop | Out-Null
} catch { Write-Host "C# 编译失败: $($_.Exception.Message)" -ForegroundColor Red; return }

# 步骤 2: PowerShell 逻辑

# --- 在这里配置目标 ---
$processName = "MeasureSleep"
# true 表示设置,false 表示清除
$enable = $true
# 期望的精度 (5000 = 0.5ms)
[uint32]$desiredResolution = 5000
# -------------------------

Write-Host "正在查找进程 '$processName'..."
$process = Get-Process -Name $processName -ErrorAction SilentlyContinue
if (-not $process) { Write-Host "未找到进程 '$processName'。" -F Yellow; return }
$targetProcess = $process[0]

Write-Host "已找到进程! PID: $($targetProcess.Id)" -ForegroundColor Green

# 1. 打开目标进程的句柄,请求设置信息的权限
$hProcess = [NativeMethods]::OpenProcess([NativeMethods+ProcessAccessFlags]::PROCESS_SET_INFORMATION, $false, $targetProcess.Id)
if ($hProcess -eq [IntPtr]::Zero) { Write-Error "OpenProcess 失败!请确保以管理员身份运行。"; return }

try {
    # 2. 准备数据结构
    $timerInfo = New-Object NativeMethods+PROCESS_TIMER_RESOLUTION_INFORMATION
    $timerInfo.DesiredResolution = $desiredResolution
    $timerInfo.SetResolution = if ($enable) { 1 } else { 0 }

    $action = if ($enable) { "设置" } else { "清除" }
    Write-Host "正在尝试为 PID $($targetProcess.Id) $($action) 计时器精度..."

    # 3. 调用 NtSetInformationProcess
    $status = [NativeMethods]::NtSetInformationProcess(
        $hProcess,
        [NativeMethods+PROCESS_INFORMATION_CLASS]::ProcessTimerResolution,
        [ref]$timerInfo,
        [System.Runtime.InteropServices.Marshal]::SizeOf($timerInfo)
    )

    # 检查结果
    if ($status -eq 0) {
        Write-Host "成功!操作已完成。脚本将退出,但效果会持续存在。" -ForegroundColor Cyan
        Write-Host "实际设置的精度为: $($timerInfo.ActualResolution / 10000) ms"
    } else {
        Write-Host "失败!NtSetInformationProcess 返回了非零的 NTSTATUS: 0x$($status.ToString('X'))" -ForegroundColor Red
    }
}
finally {
    # 4. 无论成功与否,都必须关闭句柄
    [NativeMethods]::CloseHandle($hProcess)
}

login404

It seems that boosting normal priority is only determined by CPU priority.
If the CPU is not at normal priority but the GPU is at normal priority, it won't be boosted.
Some programs will automatically boost CPU priority but not GPU priority. 
I think whether the CPU and GPU priorities are normal should be judged separately.

Jeremy Collake

#25
We've now added a distinct setting to avoid boosting of non-normal GPU priorities as of v16.0.0.39 BETA.

Thanks for the feedback!
Software Engineer. Bitsum LLC.