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.
001 002 003 004 005 006 007 008 009 010 011 012 013 014 015 016 017 018 019 020 021 022 023 024 025 026 027 028 029 030 031 032 033 034 035 036 037 038 039 040 041 042 043 044 045 046 047 048 049 050 051 052 053 054 055 056 057 058 059 060 061 062 063 064 065 066 067 068 069 070 071 072 073 074 075 076 077 078 079 080 081 082 083 084 085 086 087 088 089 090 091 092 093 094 095 096 097 098 099 100 101 | # 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 |
01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 | # 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.
01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 | 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: