Public/vss/New-ShadowCopy.ps1

#Requires -Version 5.1
function New-ShadowCopy {
    <#
        .SYNOPSIS
            Create a VSS shadow copy on a specified volume
 
        .DESCRIPTION
            Creates a Volume Shadow Copy Service (VSS) snapshot for the specified drive letter
            on one or more computers. Uses CIM methods to invoke Win32_ShadowCopy.Create and
            returns a structured result object with the shadow copy ID and status information.
 
        .PARAMETER DriveLetter
            Single letter (A-Z) identifying the volume to snapshot. Do not include the colon.
 
        .PARAMETER ComputerName
            One or more target computer names. Defaults to the local machine.
            Accepts pipeline input by property name.
 
        .PARAMETER Credential
            Optional credential for remote execution. When omitted the current
            user context is used.
 
        .EXAMPLE
            New-ShadowCopy -DriveLetter 'C'
 
            Creates a shadow copy of volume C: on the local computer.
 
        .EXAMPLE
            New-ShadowCopy -DriveLetter 'D' -ComputerName 'SRV01'
 
            Creates a shadow copy of volume D: on the remote server SRV01.
 
        .EXAMPLE
            'SRV01', 'SRV02' | New-ShadowCopy -DriveLetter 'C' -Credential (Get-Credential)
 
            Creates a shadow copy of volume C: on SRV01 and SRV02 using explicit credentials.
 
        .OUTPUTS
            PSWinOps.ShadowCopyResult
            Returns one object per target computer with ComputerName, DriveLetter,
            ShadowCopyId, CreationTime, Success, ReturnCode, ReturnMessage and Timestamp.
 
        .NOTES
            Author: Franck SALLET
            Version: 1.0.0
            Last Modified: 2026-04-10
            Requires: PowerShell 5.1+ / Windows only
            Requires: Administrator privileges
 
        .LINK
            https://github.com/k9fr4n/PSWinOps
 
        .LINK
            https://learn.microsoft.com/en-us/windows/win32/vss/volume-shadow-copy-service-overview
    #>

    [CmdletBinding(SupportsShouldProcess = $true, ConfirmImpact = 'Medium')]
    [OutputType('PSWinOps.ShadowCopyResult')]
    param(
        [Parameter(Mandatory = $true)]
        [ValidatePattern('^[A-Za-z]$')]
        [string]$DriveLetter,

        [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 - Drive: ${DriveLetter}"

        $returnCodeMap = @{
            0 = 'Success'
            1 = 'AccessDenied'
            2 = 'InvalidArgument'
            3 = 'VolumeNotSupported'
            4 = 'InsufficientStorage'
            5 = 'VolumeShadowCopyInProgress'
            6 = 'MaxShadowCopiesReached'
        }

        $scriptBlock = {
            param([string]$drv)

            $resultHash = @{
                ReturnValue = [uint32]99
                ShadowId    = ''
                VolumePath  = ''
            }

            try {
                $filterString = "DriveLetter='${drv}:'"
                $volume = Get-CimInstance -ClassName Win32_Volume -Filter $filterString -ErrorAction Stop

                if (-not $volume) {
                    $resultHash['ReturnValue'] = [uint32]2
                    return $resultHash
                }

                $resultHash['VolumePath'] = $volume.DeviceID

                $cimResult = Invoke-CimMethod -ClassName Win32_ShadowCopy -MethodName Create -Arguments @{
                    Volume = $volume.DeviceID
                } -ErrorAction Stop

                $resultHash['ReturnValue'] = [uint32]$cimResult.ReturnValue

                if ($cimResult.ReturnValue -eq 0) {
                    $resultHash['ShadowId'] = $cimResult.ShadowID
                }
            } catch {
                $resultHash['ReturnValue'] = [uint32]99
                $resultHash['ErrorDetail'] = $_.ToString()
            }

            return $resultHash
        }
    }

    process {
        foreach ($machine in $ComputerName) {
            if ($PSCmdlet.ShouldProcess($machine, "Create VSS shadow copy for volume ${DriveLetter}:")) {
                try {
                    $invokeParams = @{
                        ComputerName = $machine
                        ScriptBlock  = $scriptBlock
                        ArgumentList = @($DriveLetter.ToUpper())
                    }
                    if ($PSBoundParameters.ContainsKey('Credential')) {
                        $invokeParams['Credential'] = $Credential
                    }

                    $raw = Invoke-RemoteOrLocal @invokeParams

                    $code = [uint32]$raw.ReturnValue
                    $isSuccess = ($code -eq 0)

                    $message = if ($returnCodeMap.ContainsKey([int]$code)) {
                        $returnCodeMap[[int]$code]
                    } else {
                        'Unknown'
                    }

                    if (-not $isSuccess -and $raw.ErrorDetail) {
                        $message = "${message} - $($raw.ErrorDetail)"
                    }

                    [PSCustomObject]@{
                        PSTypeName    = 'PSWinOps.ShadowCopyResult'
                        ComputerName  = $machine
                        DriveLetter   = $DriveLetter.ToUpper()
                        ShadowCopyId  = if ($isSuccess) {
                            $raw.ShadowId 
                        } else {
                            '' 
                        }
                        CreationTime  = Get-Date
                        Success       = $isSuccess
                        ReturnCode    = $code
                        ReturnMessage = $message
                        Timestamp     = Get-Date -Format 'yyyy-MM-dd HH:mm:ss'
                    }
                } catch {
                    Write-Error -Message "[$($MyInvocation.MyCommand)] Failed on '${machine}': $_"

                    [PSCustomObject]@{
                        PSTypeName    = 'PSWinOps.ShadowCopyResult'
                        ComputerName  = $machine
                        DriveLetter   = $DriveLetter.ToUpper()
                        ShadowCopyId  = ''
                        CreationTime  = Get-Date
                        Success       = $false
                        ReturnCode    = [uint32]99
                        ReturnMessage = "Exception: $_"
                        Timestamp     = Get-Date -Format 'yyyy-MM-dd HH:mm:ss'
                    }
                }
            }
        }
    }

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