<< Back to Script Library
Trim Process Working Sets
Completely empty or trim process working sets to a specific size in order to make more memory available for other processes/users. The memory is paged oiut and pages of this will be paged back in if the process needs it.
WARNING. If too many processes are trimmed too frequently, performance can suffer due to hard page faults so use with caution
WARNING. If too many processes are trimmed too frequently, performance can suffer due to hard page faults so use with caution
Version: 1.6.9
Created: 2019-07-25
Modified: 2019-08-04
Creator: Guy Leech
Downloads: 655
Tags:
Created: 2019-07-25
Modified: 2019-08-04
Creator: Guy Leech
Downloads: 655
Tags:
The Script
Copy Script
Copied to clipboard
<#
.SYNOPSIS
Empty working sets or trim them down to a specific value for a given process or all processes in a specified session
.PARAMETER id
Process id or session id to act on
.PARAMETER trimToMB
The size of the working set to trim down to. If 0 or negative then the entire working set will be emptied. The WS can grow above this as long as hard limit is not in place.
.CONTEXT
Computer, Session
.LINK
Based on code from https://github.com/guyrleech/Microsoft/blob/master/Trimmer.ps1
.NOTES
If too many processes are trimmed too frequently, performance can suffer due to hard page faults so use with caution
.MODIFICATION_HISTORY:
@guyrleech 25/07/19
#>
[CmdletBinding()]
Param
(
[Parameter(Mandatory=$true,HelpMessage='Process or session id to act on')]
[int]$id ,
[Parameter(Mandatory=$true,HelpMessage='Working set size to trim down to')]
[int]$trimToMB,
[Parameter(Mandatory=$false,HelpMessage='Processes to exclude from memory trim (eg, cmd/powershell/winlogon)')]
[string]$excludedProcesses
)
[bool]$sessionContext = $true ## works on either a single process all all processes in a session
$VerbosePreference = 'SilentlyContinue'
$DebugPreference = 'SilentlyContinue'
$ErrorActionPreference = 'Stop'
[int]$thePid = -1
[int]$theSessionId = -1
[array]$processes = @()
if( $sessionContext )
{
$theSessionId = $id
$processes = @( Get-Process -ErrorAction SilentlyContinue | Where-Object { $_.SessionId -eq $theSessionId } )
if( ! $processes -or ! $processes.Count )
{
Throw "Failed to get any processes for session $theSessionId"
}
}
else
{
$thePid = $id
$processes = @( Get-Process -Id $thePid -ErrorAction SilentlyContinue )
if( ! $processes -or ! $processes.Count )
{
Throw "Failed to get a process for pid $thePid"
}
}
[int]$maxWorkingSet = $trimToMB * 1MB
Add-Type -Debug:$false @'
using System;
using System.Diagnostics;
using System.Runtime.InteropServices;
namespace PInvoke.Win32
{
public static class Memory
{
[DllImport("kernel32.dll", SetLastError=true)]
public static extern bool SetProcessWorkingSetSizeEx( IntPtr hProcess, int min, int max , int flags );
[DllImport("kernel32.dll", SetLastError=true)]
public static extern bool GetProcessWorkingSetSizeEx( IntPtr hProcess, ref int min, ref int max , ref int flags );
}
}
'@
[int]$thisMinimumWorkingSet = -1
[int]$thisMaximumWorkingSet = -1
[int]$thisFlags = 0
[int]$counter = 0
$listOfExcludedProcesses = $excludedProcesses -split "/"
ForEach( $process in $processes )
{
if ($listOfExcludedProcesses -like "$($process.ProcessName)") {
Write-Output "Excluded process from memory trim: $($Process.ProcessName) pid $($Process.Id)"
continue
}
else
{
Write-Verbose "Trimming memory for $($process.ProcessName)"
if( ! $process.Handle )
{
Write-Warning -Message "No process handle for process $($process.ProcessName) pid $($process.Id)"
}
else
{
## https://msdn.microsoft.com/en-us/library/windows/desktop/ms683227(v=vs.85).aspx
[bool]$result = [PInvoke.Win32.Memory]::GetProcessWorkingSetSizeEx( $process.Handle, [ref]$thisMinimumWorkingSet , [ref]$thisMaximumWorkingSet , [ref]$thisFlags );$LastError = [ComponentModel.Win32Exception][Runtime.InteropServices.Marshal]::GetLastWin32Error()
if( ! $result )
{
Write-Warning ( "Failed to get working set info for {0} pid {1} - {2}" -f $process.ProcessName , $process.Id , $LastError)
$thisMinimumWorkingSet = 1KB ## will be set to the minimum by the call
}
else
{
[int]$originalMaxWorkingSet = 0
if( $maxWorkingSet -le 0 )
{
$thisMaximumWorkingSet = $thisMinimumWorkingSet = -1 ## emptying the working set
}
else
{
$originalMaxWorkingSet = $thisMaximumWorkingSet
$thisMaximumWorkingSet = $maxWorkingSet ## not completely emptying & can grow above this , assuming it doesn't have a hard WS limit flag set
}
## see https://msdn.microsoft.com/en-us/library/windows/desktop/ms686237(v=vs.85).aspx
$result = [PInvoke.Win32.Memory]::SetProcessWorkingSetSizeEx( $process.Handle , $thisMinimumWorkingSet , $thisMaximumWorkingSet , $thisFlags );$LastError = [ComponentModel.Win32Exception][Runtime.InteropServices.Marshal]::GetLastWin32Error()
if( ! $result )
{
Write-Error ( "Failed to set working set size for {0} pid {1} to {2}MB - {3}" -f $process.ProcessName , $process.Id , $maxWorkingSet , $LastError)
}
else
{
$counter++
}
}
}
}
}
Write-Output -InputObject ( "{0} working sets of {1} process{2}" -f $(if( $maxWorkingSet -le 0 ) { 'Emptied' } else { 'Trimmed' }) , $counter , $(if( $counter -ne 1 ) { 'es' } ) )