PowerShell: Taking a Snapshot of a Windows Volume

I have this snippet embedded in various programs. Perhaps, it’s useful to be posted as an independent script to be refactored into other codes.

# createVssSnapshot.ps1
# Version 0.02

$targetVolume="E:\"
$vssAccessLink="C:\vssSnapshot"

function createVssSnapshot{
    [cmdletbinding()]
    param(
        [string]$targetVolume="C:\",
        $vssAccessLink="C:\shadowcopy"
    )
    # Sanitation
    if (!($targetVolume -like "*\")){$targetVolume+="\"}
    if(Test-Path $vssAccessLink){(Get-Item $vssAccessLink).Delete()}
   
    write-host "Initiating VSS snapshot..."
    $shadowCopyClass=[WMICLASS]"root\cimv2:win32_shadowcopy"
    $thisSnapshot = $shadowCopyClass.Create($targetVolume, "ClientAccessible")
    $thisShadow = Get-WmiObject Win32_ShadowCopy | Where-Object { $_.ID -eq $thisSnapshot.ShadowID }
    $thisShadowPath  = $thisShadow.DeviceObject + "\"
    
    # Creating symlink
    $null=cd C:
    $null=cmd /c mklink /d $vssAccessLink $thisShadowPath
    write-host "Vss Snapshot of $targetVolume has been made and it's accessible at this local file system (LFS): $vssAccessLink."

    # Validation
    if(Test-Path $vssAccessLink){
        $snapshotId=$thisShadow.ID;
        write-host "Snapshot $snapshotId has been created.";
        return $snapshotId;
    }else{
        write-host "Failed to create client accessible VSS Snapshot.";
        return $false;
        }
}

createVssSnapshot $targetVolume $vssAccessLink


function deleteVssSnapshot{
    [cmdletbinding()]
    param(
        [string]$targetVolume="C:\",
        [string]$snapShotId,
        $vssAccessLink
    )
    
    # Deterministic method of obtaining newest snapshot ID if it is not specified
    if(!($snapShotId)){
        $lastSnapshotIdString=vssadmin list shadows /for=$targetVolume|`
                        %{if($_ -like "*Shadow Copy ID:*"){$_}}|`
                        select-object -last 1|`
                        %{[void]($_ -match "{(.*)}$"); $matches[1]}
        $snapShotId="{$lastSnapshotIdString}"
        }
    
    # Remove a single snapshot
    write-host "Removing snapshot Id $snapShotId..."
    #$removeSnapshotCommand="cmd.exe /c vssadmin delete shadows /Shadow=$snapShotId /quiet"
    #$voidOutput=cmd.exe /c vssadmin delete shadows /Shadow=$lastSnapshotId /quiet
    <#
    invoke-expression 'cmd /c start powershell -Command {
                                                        param($snapShotId);                                                        
                                                        $command="cmd.exe /c vssadmin delete shadows /Shadow=$snapShotId /quiet";
                                                        write-host $command;
                                                        invoke-expression $command;
                                                        pause; } -Args $snapShotId'
                                                        #>       
    # This is the workaround to the annoyance of antivirus software terminating sessions upon invoking the snapshot removal procedure
    $newSession=New-PSSession
    Invoke-Command -Session $newSession -ScriptBlock{param($snapShotId);vssadmin delete shadows /Shadow=$snapShotId /quiet} -args $snapShotId
    $newSession|Remove-PSSession
    
    # Remove symlink
    write-host "Removing symlink $vssAccessLink..."
    (Get-Item $vssAccessLink).Delete()

    # Remove all Snapshots
    #Get-WmiObject Win32_ShadowCopy | % {$_.delete()}
	#vssadmin delete shadows /For=$targetVolume /Quiet

    # Validation
    #vssadmin list shadows /for=$targetVolume
    $validateLastSnapshot=vssadmin list shadows /for=$targetVolume|`
                    %{if($_ -like "*Shadow Copy ID:*"){$_}}|`
                    select-object -last 1|`
                    %{[void]($_ -match "{(.*)}$"); $matches[1]}
    $validateLastSnapshotId="{$validateLastSnapshot}"
    if(!($validateLastSnapshotId -eq $snapShotId)){
        write-host "Last snapshot Id is now $validateLastSnapshotId"
        return $true
        }else{
            write-host "$snapShotId still exists. There was an error in its removal";
            return $false
            }
}

$lastSnapshotId=createVssSnapshot $targetVolume $vssAccessLink
deleteVssSnapshot $targetVolume $lastSnapshotId $vssAccessLink
# createVssSnapshot.ps1
# version 0.0.1

function createVssSnapshot{
    [cmdletbinding()]
    param(
        [string]$targetVolume="C:\",
        [string]$accessPath='C:\vssSnapshot',
        [bool]$openSnapshotAfter=$true
    )
    # Fix targetVolume input if it's missing the suffix
    if (!($targetVolume -like "*\")){$targetVolume+="\"}    
    $volumeLetter=$targetVolume[0]
    $driveLabel=(get-volume -DriveLetter $volumeLetter).FileSystemLabel
    $volumelabel="Volume_$volumeLetter$(if($driveLabel){'_'+$driveLabel})"
    $localSnapshotPath="$accessPath\$env:computername\$volumeLabel\$(Get-Date -Format 'yyyy-MM-dd_hh.mm.ss')"  
     
    # Create the VSS snapshot
    $shadowCopyClass=[WMICLASS]"root\cimv2:win32_shadowcopy";
    $thisSnapshot = $shadowCopyClass.Create($targetVolume, "ClientAccessible");
    $thisShadow = Get-WmiObject Win32_ShadowCopy | Where-Object { $_.ID -eq $thisSnapshot.ShadowID };
    $thisShadowPath  = $thisShadow.DeviceObject + "\";        

    # Create snapshot parent directory
    $null=New-Item -ItemType Directory -Force -Path $(Split-Path $localSnapshotPath -Parent);

    # Make links to this snapshot
    $null=cmd /c mklink /J $localSnapshotPath $thisShadowPath;    
     
    if(test-path $localSnapshotPath){
        write-host "Snapshot of $targetVolume has been made and it's accessible at this path: $localSnapshotPath"
        if($openSnapshotAfter){invoke-item $localSnapshotPath}
        return $true
    }else{
        write-warning "Something went wrong. Snapshot was not taken."
        return $false
        }
}
 
createVssSnapshot

Removing VSS Snapshots via PowerShell is classified as high-risk by most antivirus software – read my Active Directory PEN testing articles for more background on such logic. This is an example of an interception by “rule heuristic.b.14” by this flavor of anti-malware. Hence, the below script will not run on such hosts.

function deleteVssSnapshot{
    [cmdletbinding()]
    param(
        [string]$targetVolume="C:\",
        [string]$snapShotId,
        [bool]$removeAllSnapshots=$false
    )
    $ErrorActionPreference='stop'
    if(!(Test-WSMan)){Enable-PSRemoting –force}
 
    function removeOneSnapshot($snapShotId){
        # Remove a single snapshot is a function by itself because Antivirus will terminate any session that tries to remove a VSS snapshot
        write-host "Removing snapshot Id $snapShotId... Ctrl+C to Cancel"
        pause

        # This is the workaround to the annoyance of antivirus software terminating sessions upon invoking the snapshot removal procedure
        $session=New-PSSession
        Invoke-Command -Session $session -AsJob -ScriptBlock{param($snapShotId);$null=vssadmin delete shadows /Shadow=$snapShotId /quiet} -args $snapShotId|Receive-Job -Wait
        if($session.state -ne 'Opened'){                    
            Write-Warning "An error (probably antivirus heuristics detection) has prevented removal of snapshot ID $snapShotId"
            Remove-PSSession $session
            return $false
        }else{
            Remove-PSSession $session
            return $true
            }
        }

    if($removeAllSnapshots){
        try{
            #vssadmin delete shadows /For=$targetVolume /Quiet
            $volumeId=(get-volume|?{$_.DriveLetter -eq $targetVolume[0]}).UniqueId
            $targetSnapshotIds=Get-WmiObject Win32_ShadowCopy|?{$_.VolumeName -eq $volumeId}|%{[void]($_.__PATH -match '{(.*)}');($matches[1]).tolower()}
            Write-host "These snapshots IDs are detected on $targetVolume`r`n$(($targetSnapshotIds|out-string).trim())`r`n------------------------------`r`n"
            foreach ($targetSnapshotId in $targetSnapshotIds){
                $snapShotId="{$targetSnapshotId`}"
                $success=removeOneSnapshot $snapShotId
                if(!$success){
                    $remainingSnapshotIds=Get-WmiObject Win32_ShadowCopy|?{$_.VolumeName -eq $volumeId}|%{[void]($_.__PATH -match '{(.*)}');($matches[1]).tolower()}
                    Write-Warning "Program cannot proceed to remove these snapshots:`r`n$(($remainingSnapshotIds|out-string).trim())"
                    return $false
                    }
            }            
            $checkSnapshotIds=Get-WmiObject Win32_ShadowCopy|?{$_.VolumeName -eq $volumeId}|%{[void]($_.__PATH -match '{(.*)}');($matches[1]).tolower()}
            if($checkSnapshotIds){
                write-warning "These snapshot IDs still persists: $checkSnapshotIds"
                return $false
            }else{
                write-host "All snapshots of volume $targetVolume have been purged."
                return $true
                }
        }catch{
            return $false
            }
    }else{   
        # LIFO: select the last snapshot ID if it is not specified
        if(!($snapShotId)){
            $lastSnapshotIdString=vssadmin list shadows /for=$targetVolume|`
                            %{if($_ -like "*Shadow Copy ID:*"){$_}}|`
                            select-object -last 1|`
                            %{[void]($_ -match "{(.*)}$"); $matches[1]}
            $snapShotId="{$lastSnapshotIdString`}"
            }
        removeOneSnapshot $snapShotId

        # Validation
        #vssadmin list shadows /for=$targetVolume
        $validateLastSnapshot=vssadmin list shadows /for=$targetVolume|`
                        %{if($_ -like "*Shadow Copy ID:*"){$_}}|`
                        select-object -last 1|`
                        %{[void]($_ -match "{(.*)}$"); $matches[1]}
        $validateLastSnapshotId="{$validateLastSnapshot}"
        if(!($validateLastSnapshotId -eq $snapShotId)){
            write-host "Last snapshot Id is now $validateLastSnapshotId"
            return $true
            }else{
                write-host "$snapShotId still exists. There was an error in its removal";
                return $false
                }
    }
}
   
deleteVssSnapshot

Leave a Reply

Your email address will not be published. Required fields are marked *

Other project: DragonCoin.com