Public/system/Set-PageFile.ps1

#Requires -Version 5.1
function Set-PageFile {
    <#
    .SYNOPSIS
        Configure the Windows pagefile on local or remote computers
 
    .DESCRIPTION
        Configures the Windows pagefile by disabling automatic management and setting explicit
        initial and maximum sizes. Supports auto-calculation based on installed RAM, manual size
        specification, restoring automatic management, and ensuring the pagefile is large enough
        for a complete memory dump. All changes require a restart to take effect.
 
    .PARAMETER ComputerName
        One or more computer names to configure. Defaults to the local computer.
        Accepts pipeline input by value and by property name.
 
    .PARAMETER DriveLetter
        The drive letter where the pagefile resides, in the format 'X:'.
        Defaults to 'C:'.
 
    .PARAMETER InitialSizeMB
        The initial (minimum) pagefile size in megabytes.
        Used with the Manual parameter set.
 
    .PARAMETER MaximumSizeMB
        The maximum pagefile size in megabytes.
        Must be greater than or equal to InitialSizeMB. Used with the Manual parameter set.
 
    .PARAMETER AutoCalculate
        Automatically calculate pagefile sizes based on the installed RAM.
        Uses a tiered sizing table derived from Microsoft best practices.
 
    .PARAMETER EnsureCompleteDump
        Ensure the initial pagefile size is at least RAM + 257 MB so that a
        complete memory dump can be written. Can be combined with AutoCalculate or Manual.
 
    .PARAMETER RestoreAutoManaged
        Re-enable Windows automatic pagefile management and remove any custom
        pagefile settings. This effectively reverts all manual configuration.
 
    .EXAMPLE
        Set-PageFile -AutoCalculate
 
        Configures the pagefile on the local computer with sizes calculated from installed RAM.
 
    .EXAMPLE
        Set-PageFile -ComputerName 'SRV01' -InitialSizeMB 8192 -MaximumSizeMB 16384
 
        Sets the pagefile on SRV01 to a fixed 8 GB initial / 16 GB maximum size.
 
    .EXAMPLE
        'SRV01', 'SRV02' | Set-PageFile -AutoCalculate -EnsureCompleteDump
 
        Calculates pagefile sizes for each server via pipeline and ensures the size
        is sufficient for a complete memory dump.
 
    .EXAMPLE
        Set-PageFile -RestoreAutoManaged -ComputerName 'SRV01'
 
        Restores automatic pagefile management on SRV01.
 
    .OUTPUTS
        PSWinOps.PageFileConfiguration
        Returns an object per computer with pagefile configuration details and status.
 
    .NOTES
        Author: Franck SALLET
        Version: 1.0.0
        Last Modified: 2026-03-25
        Requires: PowerShell 5.1+ / Windows only
        Requires: Administrator privileges on each target computer
 
    .LINK
        https://github.com/k9fr4n/PSWinOps
 
    .LINK
        https://learn.microsoft.com/en-us/troubleshoot/windows-client/performance/how-to-determine-the-appropriate-page-file-size
    #>

    [CmdletBinding(SupportsShouldProcess, ConfirmImpact = 'High', DefaultParameterSetName = 'Auto')]
    [OutputType([PSCustomObject])]
    param(
        [Parameter(Mandatory = $false, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true)]
        [ValidateNotNullOrEmpty()]
        [Alias('CN', 'Name', 'DNSHostName')]
        [string[]]$ComputerName = @($env:COMPUTERNAME),

        [Parameter(Mandatory = $false)]
        [ValidatePattern('^[A-Z]:$')]
        [string]$DriveLetter = 'C:',

        [Parameter(Mandatory = $true, ParameterSetName = 'Manual')]
        [ValidateRange(0, 65536)]
        [int]$InitialSizeMB,

        [Parameter(Mandatory = $true, ParameterSetName = 'Manual')]
        [ValidateRange(0, 65536)]
        [int]$MaximumSizeMB,

        [Parameter(Mandatory = $true, ParameterSetName = 'Auto')]
        [switch]$AutoCalculate,

        [Parameter(Mandatory = $false, ParameterSetName = 'Auto')]
        [Parameter(Mandatory = $false, ParameterSetName = 'Manual')]
        [switch]$EnsureCompleteDump,

        [Parameter(Mandatory = $true, ParameterSetName = 'Restore')]
        [switch]$RestoreAutoManaged
    )

    begin {
        Write-Verbose -Message "[$($MyInvocation.MyCommand)] Starting"
        # Satisfy PSScriptAnalyzer - these switches drive parameter-set selection
        $null = $AutoCalculate, $RestoreAutoManaged


        # --- Admin check -------------------------------------------------------
        $isAdmin = Test-IsAdministrator
        if (-not $isAdmin) {
            $adminException = [System.Security.SecurityException]::new(
                'This function requires administrator privileges.'
            )
            $adminRecord = [System.Management.Automation.ErrorRecord]::new(
                $adminException,
                'InsufficientPrivilege',
                [System.Management.Automation.ErrorCategory]::PermissionDenied,
                $null
            )
            $PSCmdlet.ThrowTerminatingError($adminRecord)
        }

        # --- Manual parameter-set cross-validation -----------------------------
        if ($PSCmdlet.ParameterSetName -eq 'Manual') {
            if ($MaximumSizeMB -lt $InitialSizeMB) {
                $validationException = [System.ArgumentException]::new(
                    "MaximumSizeMB ($MaximumSizeMB) must be greater than or equal to InitialSizeMB ($InitialSizeMB)."
                )
                $validationRecord = [System.Management.Automation.ErrorRecord]::new(
                    $validationException,
                    'InvalidSizeRange',
                    [System.Management.Automation.ErrorCategory]::InvalidArgument,
                    $null
                )
                $PSCmdlet.ThrowTerminatingError($validationRecord)
            }
        }

        # --- Registry path constant -------------------------------------------
        $registryPath = 'HKLM:\SYSTEM\CurrentControlSet\Control\Session Manager\Memory Management'
    }

    process {
        foreach ($targetComputer in $ComputerName) {
            Write-Verbose -Message "[$($MyInvocation.MyCommand)] Processing $targetComputer"

            $isLocal = ($targetComputer -eq $env:COMPUTERNAME) -or
            ($targetComputer -eq 'localhost') -or
            ($targetComputer -eq '.')

            try {
                # ===============================================================
                # RESTORE AUTO-MANAGED
                # ===============================================================
                if ($PSCmdlet.ParameterSetName -eq 'Restore') {
                    if ($PSCmdlet.ShouldProcess($targetComputer, 'Restore automatic pagefile management')) {
                        if ($isLocal) {
                            $compSystem = Get-CimInstance -ClassName 'Win32_ComputerSystem' -ErrorAction Stop
                            $ramBytes = $compSystem.TotalPhysicalMemory
                            $ramGB = [math]::Round($ramBytes / 1GB, 2)

                            Set-CimInstance -Query 'SELECT * FROM Win32_ComputerSystem' -Property @{ AutomaticManagedPagefile = $true } -ErrorAction Stop

                            $existingPageFiles = Get-CimInstance -ClassName 'Win32_PageFileSetting' -ErrorAction SilentlyContinue
                            if ($existingPageFiles) {
                                Remove-CimInstance -Query 'SELECT * FROM Win32_PageFileSetting' -ErrorAction Stop
                            }

                            Set-ItemProperty -Path $registryPath -Name 'PagingFiles' -Value '?:\pagefile.sys' -ErrorAction Stop
                        } else {
                            $restoreResult = Invoke-Command -ComputerName $targetComputer -ScriptBlock {
                                $regPath = $using:registryPath
                                $cs = Get-CimInstance -ClassName 'Win32_ComputerSystem' -ErrorAction Stop
                                $ramGB = [math]::Round($cs.TotalPhysicalMemory / 1GB, 2)
                                $cs | Set-CimInstance -Property @{ AutomaticManagedPagefile = $true } -ErrorAction Stop
                                $existing = Get-CimInstance -ClassName 'Win32_PageFileSetting' -ErrorAction SilentlyContinue
                                if ($existing) {
                                    $existing | Remove-CimInstance -ErrorAction Stop
                                }
                                Set-ItemProperty -Path $regPath -Name 'PagingFiles' -Value '?:\pagefile.sys' -ErrorAction Stop
                                [PSCustomObject]@{ RamGB = $ramGB }
                            } -ErrorAction Stop

                            $ramGB = $restoreResult.RamGB
                        }

                        [PSCustomObject]@{
                            PSTypeName          = 'PSWinOps.PageFileConfiguration'
                            ComputerName        = $targetComputer
                            DriveLetter         = $DriveLetter
                            PageFilePath        = "$DriveLetter\pagefile.sys"
                            InitialSizeMB       = 0
                            MaximumSizeMB       = 0
                            AutoManagedPagefile = $true
                            RamTotalGB          = $ramGB
                            EnsureCompleteDump  = $false
                            RestartRequired     = $true
                            Status              = 'RestoredAutoManaged'
                            Timestamp           = Get-Date -Format 'o'
                        }
                    }
                    continue
                }

                # ===============================================================
                # DETECT RAM
                # ===============================================================
                if ($isLocal) {
                    $compSystem = Get-CimInstance -ClassName 'Win32_ComputerSystem' -ErrorAction Stop
                } else {
                    $compSystem = Get-CimInstance -ClassName 'Win32_ComputerSystem' -ComputerName $targetComputer -ErrorAction Stop
                }

                $ramBytes = $compSystem.TotalPhysicalMemory
                $ramGB = [math]::Round($ramBytes / 1GB, 2)

                Write-Verbose -Message "[$($MyInvocation.MyCommand)] $targetComputer --> RAM detected: $ramGB GB"

                # ===============================================================
                # DETERMINE SIZES
                # ===============================================================
                if ($PSCmdlet.ParameterSetName -eq 'Auto') {
                    if ($ramGB -le 4) {
                        $initial = 4096
                        $maximum = 6144
                    } elseif ($ramGB -le 8) {
                        $initial = 6144
                        $maximum = 8192
                    } elseif ($ramGB -le 16) {
                        $initial = 8192
                        $maximum = 12288
                    } else {
                        # RAM > 16 GB (covers both <=32 and >32)
                        $initial = 8192
                        $maximum = 16384
                    }
                } else {
                    $initial = $InitialSizeMB
                    $maximum = $MaximumSizeMB
                }

                # ===============================================================
                # ENSURE COMPLETE DUMP
                # ===============================================================
                if ($EnsureCompleteDump.IsPresent) {
                    $ramMB = [math]::Ceiling($ramBytes / 1MB)
                    $dumpMinimumMB = $ramMB + 257

                    if ($initial -lt $dumpMinimumMB) {
                        Write-Verbose -Message "[$($MyInvocation.MyCommand)] Adjusting InitialSizeMB from $initial to $dumpMinimumMB for complete memory dump"
                        $initial = $dumpMinimumMB
                    }
                    if ($maximum -lt $initial) {
                        Write-Verbose -Message "[$($MyInvocation.MyCommand)] Adjusting MaximumSizeMB from $maximum to $initial to match InitialSizeMB"
                        $maximum = $initial
                    }
                }

                Write-Verbose -Message "[$($MyInvocation.MyCommand)] $targetComputer --> Sizes: Initial=$initial MB, Maximum=$maximum MB"

                # ===============================================================
                # APPLY CONFIGURATION
                # ===============================================================
                $pageFilePath = "$DriveLetter\pagefile.sys"
                $pagingFileValue = "$pageFilePath $initial $maximum"
                $shouldMsg = "Set pagefile $pageFilePath to Initial=$initial MB, Maximum=$maximum MB"

                if ($PSCmdlet.ShouldProcess($targetComputer, $shouldMsg)) {
                    if ($isLocal) {
                        # Disable auto-managed
                        Set-CimInstance -Query 'SELECT * FROM Win32_ComputerSystem' -Property @{ AutomaticManagedPagefile = $false } -ErrorAction Stop

                        # Remove existing custom pagefiles
                        $existingPageFiles = Get-CimInstance -ClassName 'Win32_PageFileSetting' -ErrorAction SilentlyContinue
                        if ($existingPageFiles) {
                            Remove-CimInstance -Query 'SELECT * FROM Win32_PageFileSetting' -ErrorAction Stop
                        }

                        # Create new pagefile setting via CIM
                        $newPageFileArgs = @{
                            ClassName   = 'Win32_PageFileSetting'
                            Property    = @{
                                Name        = $pageFilePath
                                InitialSize = [uint32]$initial
                                MaximumSize = [uint32]$maximum
                            }
                            ErrorAction = 'Stop'
                        }
                        $null = New-CimInstance @newPageFileArgs

                        # Update registry
                        Set-ItemProperty -Path $registryPath -Name 'PagingFiles' -Value $pagingFileValue -ErrorAction Stop
                    } else {
                        $null = Invoke-Command -ComputerName $targetComputer -ScriptBlock {
                            $pfPath = $using:pageFilePath; $pfInitial = $using:initial; $pfMaximum = $using:maximum
                            $pfPagingValue = $using:pagingFileValue; $regPath = $using:registryPath

                            $cs = Get-CimInstance -ClassName 'Win32_ComputerSystem' -ErrorAction Stop
                            $cs | Set-CimInstance -Property @{ AutomaticManagedPagefile = $false } -ErrorAction Stop

                            $existing = Get-CimInstance -ClassName 'Win32_PageFileSetting' -ErrorAction SilentlyContinue
                            if ($existing) {
                                $existing | Remove-CimInstance -ErrorAction Stop
                            }

                            $null = New-CimInstance -ClassName 'Win32_PageFileSetting' -Property @{
                                Name        = $pfPath
                                InitialSize = [uint32]$pfInitial
                                MaximumSize = [uint32]$pfMaximum
                            } -ErrorAction Stop

                            Set-ItemProperty -Path $regPath -Name 'PagingFiles' -Value $pfPagingValue -ErrorAction Stop
                        } -ErrorAction Stop
                    }

                    [PSCustomObject]@{
                        PSTypeName          = 'PSWinOps.PageFileConfiguration'
                        ComputerName        = $targetComputer
                        DriveLetter         = $DriveLetter
                        PageFilePath        = $pageFilePath
                        InitialSizeMB       = $initial
                        MaximumSizeMB       = $maximum
                        AutoManagedPagefile = $false
                        RamTotalGB          = $ramGB
                        EnsureCompleteDump  = $EnsureCompleteDump.IsPresent
                        RestartRequired     = $true
                        Status              = 'Configured'
                        Timestamp           = Get-Date -Format 'o'
                    }
                }
            } catch [System.UnauthorizedAccessException] {
                Write-Error -Message "[$($MyInvocation.MyCommand)] Access denied on ${targetComputer}: $_"
                continue
            } catch [System.Runtime.InteropServices.COMException] {
                Write-Error -Message "[$($MyInvocation.MyCommand)] CIM/WMI error on ${targetComputer}: $_"
                continue
            } catch {
                Write-Error -Message "[$($MyInvocation.MyCommand)] Failed to configure pagefile on ${targetComputer}: $_"
                continue
            }
        }
    }

    end {
        Write-Verbose -Message "[$($MyInvocation.MyCommand)] Completed"
    }
}