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
Categories: