Public/vss/Set-ShadowCopyStorage.ps1
|
#Requires -Version 5.1 function Set-ShadowCopyStorage { <# .SYNOPSIS Configure the maximum shadow copy storage size for a specified drive .DESCRIPTION Sets or modifies the maximum shadow copy (VSS) storage allocation for a given drive letter. Uses vssadmin resize shadowstorage which is more reliable than Set-CimInstance for modifying Win32_ShadowStorage. Supports both explicit size limits and unbounded storage. .PARAMETER DriveLetter The single drive letter (A-Z) to configure shadow copy storage for. .PARAMETER MaxSizeMB The maximum shadow copy storage size in megabytes. Valid range is 1 to 10485760 MB. .PARAMETER Unbounded Sets the shadow copy storage to unbounded (no maximum limit). .PARAMETER ComputerName One or more computer names to target. Defaults to the local computer. .PARAMETER Credential Optional credential for remote execution. .EXAMPLE Set-ShadowCopyStorage -DriveLetter 'C' -MaxSizeMB 20480 Sets the VSS max storage for drive C: to 20480 MB on the local machine. .EXAMPLE Set-ShadowCopyStorage -DriveLetter 'D' -Unbounded -ComputerName 'SRV01' Sets the VSS storage for drive D: to unbounded on remote server SRV01. .EXAMPLE 'SRV01', 'SRV02' | Set-ShadowCopyStorage -DriveLetter 'C' -MaxSizeMB 10240 Sets the VSS max storage for drive C: to 10240 MB on multiple remote servers. .OUTPUTS PSWinOps.ShadowCopyStorageResult Returns an object with ComputerName, DriveLetter, PreviousMaxSpaceMB, NewMaxSpaceMB, Success, Message, and Timestamp properties. .NOTES Author: Franck SALLET Version: 1.0.0 Last Modified: 2026-04-10 Requires: PowerShell 5.1+ / Windows only Requires: Administrator privileges (vssadmin requires elevation) .LINK https://github.com/k9fr4n/PSWinOps .LINK https://learn.microsoft.com/en-us/windows-server/administration/windows-commands/vssadmin-resize-shadowstorage #> [CmdletBinding(SupportsShouldProcess = $true, ConfirmImpact = 'Medium', DefaultParameterSetName = 'BySize')] [OutputType('PSWinOps.ShadowCopyStorageResult')] param( [Parameter(Mandatory = $true)] [ValidatePattern('^[A-Za-z]$')] [string]$DriveLetter, [Parameter(Mandatory = $true, ParameterSetName = 'BySize')] [ValidateRange(1, 10485760)] [long]$MaxSizeMB, [Parameter(Mandatory = $true, ParameterSetName = 'Unbounded')] [switch]$Unbounded, [Parameter(Mandatory = $false, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true)] [ValidateNotNullOrEmpty()] [Alias('CN', 'Name', 'DNSHostName')] [string[]]$ComputerName = $env:COMPUTERNAME, [Parameter(Mandatory = $false)] [ValidateNotNull()] [System.Management.Automation.PSCredential] [System.Management.Automation.Credential()] $Credential ) begin { Write-Verbose -Message "[$($MyInvocation.MyCommand)] Starting" $DriveLetter = $DriveLetter.ToUpper() if ($Unbounded) { $effectiveMaxSizeMB = -1 $displaySize = 'Unbounded' } else { $effectiveMaxSizeMB = $MaxSizeMB $displaySize = "$MaxSizeMB MB" } $scriptBlock = { param( [string]$DrvLetter, [long]$MaxMB ) $previousMaxSpaceBytes = 0 try { $storageList = Get-CimInstance -ClassName 'Win32_ShadowStorage' -ErrorAction Stop foreach ($storageItem in $storageList) { $volumeRef = $storageItem.Volume if ($null -ne $volumeRef) { $volDeviceId = $volumeRef.ToString() $allVolumes = Get-CimInstance -ClassName 'Win32_Volume' -ErrorAction SilentlyContinue foreach ($volObj in $allVolumes) { if ($volObj.DriveLetter -eq "${DrvLetter}:" -and $volDeviceId -like "*$($volObj.DeviceID)*") { $previousMaxSpaceBytes = $storageItem.MaxSpace break } } } } } catch { $previousMaxSpaceBytes = 0 } if ($MaxMB -eq -1) { $vssArgs = "resize shadowstorage /For=${DrvLetter}: /On=${DrvLetter}: /MaxSize=UNBOUNDED" } else { $vssArgs = "resize shadowstorage /For=${DrvLetter}: /On=${DrvLetter}: /MaxSize=${MaxMB}MB" } $processInfo = New-Object -TypeName 'System.Diagnostics.ProcessStartInfo' $processInfo.FileName = 'vssadmin.exe' $processInfo.Arguments = $vssArgs $processInfo.RedirectStandardOutput = $true $processInfo.RedirectStandardError = $true $processInfo.UseShellExecute = $false $processInfo.CreateNoWindow = $true $proc = New-Object -TypeName 'System.Diagnostics.Process' $proc.StartInfo = $processInfo $null = $proc.Start() $stdout = $proc.StandardOutput.ReadToEnd() $stderr = $proc.StandardError.ReadToEnd() $proc.WaitForExit() $exitCode = $proc.ExitCode $combinedOutput = $stdout.Trim() if (-not [string]::IsNullOrWhiteSpace($stderr)) { $combinedOutput = $combinedOutput + ' ' + $stderr.Trim() } @{ DriveLetter = $DrvLetter PreviousMaxSpaceBytes = $previousMaxSpaceBytes NewMaxSizeArg = $MaxMB ExitCode = $exitCode Output = $combinedOutput } } } process { foreach ($machine in $ComputerName) { $shouldProcessTarget = "Set VSS max storage on $machine for ${DriveLetter}: to $displaySize" if (-not $PSCmdlet.ShouldProcess($shouldProcessTarget, 'Set-ShadowCopyStorage')) { continue } Write-Verbose -Message "[$($MyInvocation.MyCommand)] Processing '$machine' - Drive ${DriveLetter}: --> $displaySize" try { $invokeParams = @{ ComputerName = $machine ScriptBlock = $scriptBlock ArgumentList = @($DriveLetter, $effectiveMaxSizeMB) } if ($PSBoundParameters.ContainsKey('Credential')) { $invokeParams['Credential'] = $Credential } $raw = Invoke-RemoteOrLocal @invokeParams $previousMB = 0 if ($raw.PreviousMaxSpaceBytes -gt 0) { $previousMB = [math]::Round($raw.PreviousMaxSpaceBytes / 1MB, 2) } $isSuccess = ($raw.ExitCode -eq 0) $newMaxDisplay = if ($raw.NewMaxSizeArg -eq -1) { 'Unbounded' } else { [math]::Round($raw.NewMaxSizeArg, 2) } [PSCustomObject]@{ PSTypeName = 'PSWinOps.ShadowCopyStorageResult' ComputerName = $machine DriveLetter = $raw.DriveLetter PreviousMaxSpaceMB = $previousMB NewMaxSpaceMB = $newMaxDisplay Success = $isSuccess Message = $raw.Output Timestamp = Get-Date -Format 'yyyy-MM-dd HH:mm:ss' } } catch { Write-Error -Message "[$($MyInvocation.MyCommand)] Failed on '${machine}': $_" [PSCustomObject]@{ PSTypeName = 'PSWinOps.ShadowCopyStorageResult' ComputerName = $machine DriveLetter = $DriveLetter PreviousMaxSpaceMB = 0 NewMaxSpaceMB = $displaySize Success = $false Message = "Error: $_" Timestamp = Get-Date -Format 'yyyy-MM-dd HH:mm:ss' } } } } end { Write-Verbose -Message "[$($MyInvocation.MyCommand)] Completed" } } |