From 6529140de257cee553a410d894102b967383889d Mon Sep 17 00:00:00 2001 From: Bill Long Date: Thu, 30 Nov 2023 00:02:38 -0600 Subject: [PATCH] VSSTester: Allow specifying volumes to back up In previous versions of VSSTester, the user had to specify a database when testing a DiskShadow backup. This would cause VSSTester to look up the volumes that stored the database and log files, and then it would snap those volumes. This approach works for most cases, but it makes it hard to compare with backup software that only allows specifying volumes, not databases. This change allows running a DiskShadow backup of specified volumes. To get a list of acceptable volume names, the user can specify one that is invalid. For example: ``` [PS] C:\>.\VSSTester.ps1 -DiskShadow -VolumesToBackup foo C:\VSSTester.ps1 : Cannot validate argument on parameter 'VolumesToBackup'. Invalid volume specified. Please specify one of the following values: C:\ C:\Databases\DB1\ At line:1 char:70 + ... code\CSS-Exchange\dist\VSSTester.ps1 -DiskShadow -VolumesToBackup foo ``` One or two volumes can be specified. As usual, drive letters must be provided. For this syntax, the number of provided drive letters must match the number of specified volumes. Example: ``` VSSTester.ps1 -DiskShadow -VolumesToBackup C:\Databases\DB1\ -ExposeSnapshotsOnDriveLetters X ``` This PR includes several other changes: * Get-WmiObject has been replaced with Get-CimInstance. * Drive letters are now specified with -ExposeSnapshotsOnDriveLetters, which more clearly communicates the intent and allows specifying one or more drive letters with a single parameter. --- .../Invoke-CreateDiskShadowFile.ps1 | 245 +++++++----------- .../DiskShadow/Invoke-DiskShadow.ps1 | 8 +- .../Logging/Invoke-DisableExtraTracing.ps1 | 8 +- .../Logging/Invoke-EnableExtraTracing.ps1 | 8 +- Databases/VSSTester/VSSTester.ps1 | 92 ++++--- docs/Databases/VSSTester.md | 10 +- 6 files changed, 177 insertions(+), 194 deletions(-) diff --git a/Databases/VSSTester/DiskShadow/Invoke-CreateDiskShadowFile.ps1 b/Databases/VSSTester/DiskShadow/Invoke-CreateDiskShadowFile.ps1 index 57a67598e1..5f1ccb5b4c 100644 --- a/Databases/VSSTester/DiskShadow/Invoke-CreateDiskShadowFile.ps1 +++ b/Databases/VSSTester/DiskShadow/Invoke-CreateDiskShadowFile.ps1 @@ -2,32 +2,30 @@ # Licensed under the MIT License. function Invoke-CreateDiskShadowFile { - [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidUsingWMICmdlet', '', Justification = 'Required to get drives on old systems')] [OutputType([string[]])] param( [Parameter(Mandatory = $true)] [string] $OutputPath, - [Parameter(Mandatory = $true)] [string] $ServerName, - [Parameter(Mandatory = $true)] + [Parameter(Mandatory = $true, ParameterSetName = "BackupByDatabase")] [object[]] $Databases, - [Parameter(Mandatory = $true)] + [Parameter(Mandatory = $true, ParameterSetName = "BackupByDatabase")] [object] $DatabaseToBackup, - [Parameter(Mandatory = $true)] - [string] - $DatabaseDriveLetter, + [Parameter(Mandatory = $true, ParameterSetName = "BackupByVolume")] + [object[]] + $VolumesToBackup, [Parameter(Mandatory = $true)] - [string] - $LogDriveLetter + [string[]] + $DriveLetters ) function Out-DHSFile { @@ -74,171 +72,124 @@ function Invoke-CreateDiskShadowFile { Out-DHSFile "writer exclude {a65faa63-5ea8-4ebc-9dbd-a0c4db26912a}" Out-DHSFile " " - # add databases to exclude - # ------------------------ - foreach ($db in $Databases) { - if ($db.Identity -ne $DatabaseToBackup.Identity) { - if ($db.Server.Name -eq $ServerName) { - Out-DHSFile "writer exclude `"Microsoft Exchange Writer:\Microsoft Exchange Server\Microsoft Information Store\$serverName\$($db.Guid)`"" - } else { - #if passive copy, add it with replica in the string - Out-DHSFile "writer exclude `"Microsoft Exchange Replica Writer:\Microsoft Exchange Server\Microsoft Information Store\Replica\$serverName\$($db.Guid)`"" + if ($DatabaseToBackup) { + # add databases to exclude + # ------------------------ + foreach ($db in $Databases) { + if ($db.Identity -ne $DatabaseToBackup.Identity) { + if ($db.Server.Name -eq $ServerName) { + Out-DHSFile "writer exclude `"Microsoft Exchange Writer:\Microsoft Exchange Server\Microsoft Information Store\$serverName\$($db.Guid)`"" + } else { + #if passive copy, add it with replica in the string + Out-DHSFile "writer exclude `"Microsoft Exchange Replica Writer:\Microsoft Exchange Server\Microsoft Information Store\Replica\$serverName\$($db.Guid)`"" + } } } } + Out-DHSFile " " Out-DHSFile "Begin backup" - # add the volumes for the included database - # ----------------------------------------- - #gets a list of mount points on local server - $mpVolumes = Get-WmiObject -Query "select name, DeviceId from win32_volume where DriveType=3 AND DriveLetter=NULL" - $deviceIDs = @() - - $dbMP = $false - $logMP = $false - - #if no MountPoints ($mpVolumes) causes null-valued error, need to handle - if ($null -ne $mpVolumes) { - foreach ($mp in $mpVolumes) { - $mpName = (($mp.name).substring(0, $mp.name.length - 1)) - #if following mount point path exists in database path use deviceID in DiskShadow config file - if ($DatabaseToBackup.EdbFilePath.PathName.StartsWith($mpName, [System.StringComparison]::OrdinalIgnoreCase)) { - Write-Host " Mount point: $($mp.name) in use for database path: " - #Write-host "Yes. I am a database in MountPoint" - Write-Host " The selected database path is: $($DatabaseToBackup.EdbFilePath.PathName)" - $dbEdbVol = $mp.DeviceId - Write-Host " adding deviceID to file: $dbEdbVol" - - #add device ID to array - $deviceID1 = $mp.DeviceID - $dbMP = $true + if ($DatabaseToBackup) { + # add the volumes for the included database + # ----------------------------------------- + #gets a list of mount points on local server + $mpVolumes = Get-CimInstance -Query "select name, DeviceId from win32_volume where DriveType=3 AND DriveLetter=NULL" + $deviceIDs = @() + + $dbMP = $false + $logMP = $false + + #if no MountPoints ($mpVolumes) causes null-valued error, need to handle + if ($null -ne $mpVolumes) { + foreach ($mp in $mpVolumes) { + $mpName = (($mp.name).substring(0, $mp.name.length - 1)) + #if following mount point path exists in database path use deviceID in DiskShadow config file + if ($DatabaseToBackup.EdbFilePath.PathName.StartsWith($mpName, [System.StringComparison]::OrdinalIgnoreCase)) { + Write-Host " Mount point: $($mp.name) in use for database path: " + Write-Host " The selected database path is: $($DatabaseToBackup.EdbFilePath.PathName)" + $dbEdbVol = $mp.DeviceId + Write-Host " adding deviceID to file: $dbEdbVol" + + #add device ID to array + $deviceID1 = $mp.DeviceID + $dbMP = $true + } + + #if following mount point path exists in log path use deviceID in DiskShadow config file + if ($DatabaseToBackup.LogFolderPath.PathName.ToLower().Contains($mpName.ToLower())) { + Write-Host + Write-Host " Mount point: $($mp.name) in use for log path: " + Write-Host " The log folder path of selected database is: $($DatabaseToBackup.LogFolderPath.PathName)" + $dbLogVol = $mp.DeviceId + Write-Host " adding deviceID to file: $dbLogVol" + $deviceID2 = $mp.DeviceID + $logMP = $true + } } + } - #if following mount point path exists in log path use deviceID in DiskShadow config file - if ($DatabaseToBackup.LogFolderPath.PathName.ToLower().Contains($mpName.ToLower())) { - Write-Host - Write-Host " Mount point: $($mp.name) in use for log path: " - #Write-host "Yes. My logs are in a MountPoint" - Write-Host " The log folder path of selected database is: $($DatabaseToBackup.LogFolderPath.PathName)" - $dbLogVol = $mp.DeviceId - Write-Host " adding deviceID to file: $dbLogVol" - $deviceID2 = $mp.DeviceID - $logMP = $true - } + if ($dbMP -eq $false) { + $dbEdbVol = ($DatabaseToBackup.EdbFilePath.PathName).substring(0, 2) + Write-Host " The selected database path is '$($DatabaseToBackup.EdbFilePath.PathName)' so adding volume $dbEdbVol to backup scope" + $deviceID1 = $dbEdbVol } - $deviceIDs = $deviceID1, $deviceID2 - } - if ($dbMP -eq $false) { - $dbEdbVol = ($DatabaseToBackup.EdbFilePath.PathName).substring(0, 2) - Write-Host " The selected database path is '$($DatabaseToBackup.EdbFilePath.PathName)' so adding volume $dbEdbVol to backup scope" - $deviceID1 = $dbEdbVol - } + if ($logMP -eq $false) { + $dbLogVol = ($DatabaseToBackup.LogFolderPath.PathName).substring(0, 2) + Write-Host " The selected database log folder path is '$($DatabaseToBackup.LogFolderPath.PathName)' so adding volume $dbLogVol to backup scope" + $deviceID2 = $dbLogVol + } - if ($logMP -eq $false) { - $dbLogVol = ($DatabaseToBackup.LogFolderPath.PathName).substring(0, 2) - Write-Host " The selected database log folder path is '$($DatabaseToBackup.LogFolderPath.PathName)' so adding volume $dbLogVol to backup scope" - $deviceID2 = $dbLogVol + $deviceIDs = @($deviceID1) + if ($deviceID2 -ne $deviceID1) { + $deviceIDs += $deviceID2 + } + } else { + $validVolumes = Get-CimInstance -Query "select name, DeviceId from win32_volume where DriveType=3" | + Where-Object { $_.Name -match "^\w:" } | Select-Object Name, DeviceID + $deviceIDs = @() + foreach ($v in $VolumesToBackup) { + $volToBackup = $validVolumes | Where-Object { $_.Name -eq $v } + if ($null -eq $volToBackup) { + Write-Warning "Failed to find volume by name: $v. Available volumes:`n$([string]::Join("`n", $validVolumes))" + exit + } + + $deviceIDs += $volToBackup.DeviceID + } } # Here is where we start adding the appropriate volumes or MountPoints to the DiskShadow config file # We make sure that we add only one Logical volume when we detect the EDB and log files # are on the same volume - Write-Host - $deviceIDs = $deviceID1, $deviceID2 - $comp = [string]::Compare($deviceID1, $deviceID2, $True) - if ($comp -eq 0) { - $dID = $deviceIDs[0] - Write-Debug -Message ('$dID = ' + $dID.ToString()) - Write-Debug "When the database and log files are on the same volume, we add the volume only once" - if ($dID.length -gt "2") { - $addVol = "add volume $dID alias vss_test_" + ($dID).ToString().substring(11, 8) - Write-Host $addVol - Out-DHSFile $addVol - } else { - $addVol = "add volume $dID alias vss_test_" + ($dID).ToString().substring(0, 1) - Write-Host $addVol - Out-DHSFile $addVol - } - } else { - Write-Host " " - foreach ($device in $deviceIDs) { - if ($device.length -gt "2") { - Write-Host " Adding the Mount Point for DSH file" - $addVol = "add volume $device alias vss_test_" + ($device).ToString().substring(11, 8) - Write-Host " $addVol" - Out-DHSFile $addVol - } else { - Write-Host " Adding the volume for DSH file" - $addVol = "add volume $device alias vss_test_" + ($device).ToString().substring(0, 1) - Write-Host " $addVol" - Out-DHSFile $addVol - } - } + for ($i = 0; $i -lt $deviceIDs.Count; $i++) { + $id = $deviceIDs[$i] + Write-Debug -Message ('$id = ' + $id.ToString()) + $addVol = "add volume $id alias vss_test_$i" + Write-Host $addVol + Out-DHSFile $addVol } + Out-DHSFile "create" Out-DHSFile " " Write-Host "$(Get-Date) Getting drive letters for exposing backup snapshot" - # check to see if the drives are the same for both database and logs - # if the same volume is used, only one drive letter is needed for exposure - # if two volumes are used, two drive letters are needed - - $matchCondition = "^[a-z]:$" - Write-Debug $matchCondition - - $dbSnapVol = $DatabaseDriveLetter - if ($comp -eq 0) { - $logSnapVol = $dbSnapVol - Write-Host " Since the same volume is used for this database's EDB and logs, we only need a single drive" - Write-Host " letter to expose the backup snapshot." - } else { - $logSnapVol = $LogDriveLetter - Write-Host " Since different volumes are used for this database's EDB and logs, we need two drive" - Write-Host " letters to expose the backup snapshot." - } - - Write-Debug "dbSnapVol: $dbSnapVol | logSnapVol: $logSnapVol" - # expose the drives - # if volumes are the same only one entry is needed - if ($dbEdbVol -eq $dbLogVol) { - if ($dbEdbVol.length -gt "2") { - $dbVolStr = "expose %vss_test_" + ($dbEdbVol).substring(11, 8) + "% $($dbSnapVol):" - Out-DHSFile $dbVolStr - } else { - $dbVolStr = "expose %vss_test_" + ($dbEdbVol).substring(0, 1) + "% $($dbSnapVol):" - Out-DHSFile $dbVolStr - } - } else { - # volumes are different, getting both - # if MountPoint use first part of string, if not use first letter - if ($dbEdbVol.length -gt "2") { - $dbVolStr = "expose %vss_test_" + ($dbEdbVol).substring(11, 8) + "% $($dbSnapVol)" - Out-DHSFile $dbVolStr - } else { - $dbVolStr = "expose %vss_test_" + ($dbEdbVol).substring(0, 1) + "% $($dbSnapVol)" - Out-DHSFile $dbVolStr - } + if ($deviceIDs.Count -lt $DriveLetters.Count) { + Write-Warning "Determined that we need $($deviceIDs.Count) drive letters to expose the snapshots, but only $($DriveLetters.Count) were provided. Exiting." + exit + } - # if MountPoint use first part of string, if not use first letter - if ($dbLogVol.length -gt "2") { - $logVolStr = "expose %vss_test_" + ($dbLogVol).substring(11, 8) + "% $($logSnapVol):" - Out-DHSFile $logVolStr - } else { - $logVolStr = "expose %vss_test_" + ($dbLogVol).substring(0, 1) + "% $($logSnapVol):" - Out-DHSFile $logVolStr - } + for ($i = 0; $i -lt $deviceIDs.Count; $i++) { + $dbVolStr = "expose %vss_test_$($i)% $($DriveLetters[$i]):" + Out-DHSFile $dbVolStr } # ending data of file Out-DHSFile "end backup" - if ($dbSnapVol -eq $logSnapVol) { - return @($dbSnapVol) - } else { - return @($dbSnapVol, $logSnapVol) - } + # return the drive letters we used + return $DriveLetters | Select-Object -First ($deviceIDs.Count) } diff --git a/Databases/VSSTester/DiskShadow/Invoke-DiskShadow.ps1 b/Databases/VSSTester/DiskShadow/Invoke-DiskShadow.ps1 index 32844758e8..a121d9fb4e 100644 --- a/Databases/VSSTester/DiskShadow/Invoke-DiskShadow.ps1 +++ b/Databases/VSSTester/DiskShadow/Invoke-DiskShadow.ps1 @@ -8,14 +8,10 @@ function Invoke-DiskShadow { param( [Parameter(Mandatory = $true)] [string] - $OutputPath, - - [Parameter(Mandatory = $true)] - [object] - $DatabaseToBackup + $OutputPath ) - Write-Host "$(Get-Date) Starting DiskShadow copy of Exchange database: $Database" + Write-Host "$(Get-Date) Starting DiskShadow copy." Write-Host " Running the following command:" Write-Host " `"C:\Windows\System32\DiskShadow.exe /s $OutputPath\DiskShadow.dsh /l $OutputPath\DiskShadow.log`"" diff --git a/Databases/VSSTester/Logging/Invoke-DisableExtraTracing.ps1 b/Databases/VSSTester/Logging/Invoke-DisableExtraTracing.ps1 index 05f9428764..a183c1ef1b 100644 --- a/Databases/VSSTester/Logging/Invoke-DisableExtraTracing.ps1 +++ b/Databases/VSSTester/Logging/Invoke-DisableExtraTracing.ps1 @@ -15,7 +15,7 @@ function Invoke-DisableExTRATracing { [string] $ServerName, - [Parameter(Mandatory = $true)] + [Parameter(Mandatory = $false)] [object] $DatabaseToBackup, @@ -24,9 +24,8 @@ function Invoke-DisableExTRATracing { $OutputPath ) Write-Host "$(Get-Date) Disabling ExTRA Tracing..." - $dbMountedOn = $DatabaseToBackup.Server.Name - if ($dbMountedOn -eq "$ServerName") { - #stop active copy + $traceLocalServerOnly = $null -eq $DatabaseToBackup -or $DatabaseToBackup.Server.Name -eq $ServerName + if ($traceLocalServerOnly) { Write-Host Write-Host " Stopping Exchange Trace data collector on $ServerName..." logman stop vssTester -s $ServerName @@ -35,6 +34,7 @@ function Invoke-DisableExTRATracing { Write-Host } else { #stop passive copy + $dbMountedOn = $DatabaseToBackup.Server.Name Write-Host " Stopping Exchange Trace data collector on $ServerName..." logman stop vssTester-Passive -s $ServerName Write-Host " Deleting Exchange Trace data collector on $ServerName..." diff --git a/Databases/VSSTester/Logging/Invoke-EnableExtraTracing.ps1 b/Databases/VSSTester/Logging/Invoke-EnableExtraTracing.ps1 index 7c51a55ddf..3c3b366b3f 100644 --- a/Databases/VSSTester/Logging/Invoke-EnableExtraTracing.ps1 +++ b/Databases/VSSTester/Logging/Invoke-EnableExtraTracing.ps1 @@ -8,7 +8,7 @@ function Invoke-EnableExTRATracing { [string] $ServerName, - [Parameter(Mandatory = $true)] + [Parameter(Mandatory = $false)] [object] $DatabaseToBackup, @@ -53,10 +53,9 @@ function Invoke-EnableExTRATracing { } } - $dbMountedOn = $DatabaseToBackup.Server.Name + $traceLocalServerOnly = $null -eq $DatabaseToBackup -or $DatabaseToBackup.Server.Name -eq $ServerName - #active server, only get tracing from active node - if ($dbMountedOn -eq $ServerName) { + if ($traceLocalServerOnly) { Write-Host "Creating Exchange Trace data collector set..." Invoke-ExtraTracingCreate -ComputerName $ServerName -LogmanName "VSSTester" -OutputPath $OutputPath Write-Host "Starting Exchange Trace data collector..." @@ -70,6 +69,7 @@ function Invoke-EnableExTRATracing { Write-Host } else { #passive server, get tracing from both active and passive nodes + $dbMountedOn = $DatabaseToBackup.Server.Name Write-Host "Copying the ExTRA config file 'EnabledTraces.config' file to $dbMountedOn..." #copy EnabledTraces.config from current passive copy to active copy server Copy-Item "c:\EnabledTraces.Config" "\\$dbMountedOn\c$\EnabledTraces.config" -Force diff --git a/Databases/VSSTester/VSSTester.ps1 b/Databases/VSSTester/VSSTester.ps1 index cd746f1cd8..ded23979ba 100644 --- a/Databases/VSSTester/VSSTester.ps1 +++ b/Databases/VSSTester/VSSTester.ps1 @@ -13,7 +13,7 @@ Enables tracing of the specified database. The user may then attempt a backup of that database and use Ctrl-C to stop data collection after the backup attempt completes. .EXAMPLE - .\VSSTester -DiskShadow -DatabaseName "Mailbox Database 1637196748" -DatabaseDriveLetter M -LogDriveLetter N + .\VSSTester -DiskShadow -DatabaseName "Mailbox Database 1637196748" -ExposeSnapshotsOnDriveLetters M, N Enables tracing and then uses DiskShadow to snapshot the specified database. If the database and logs are on the same drive, the snapshot is exposed as M: drive. If they are on separate drives, the snapshots are exposed as M: and N:. The user is prompted to stop data collection and should typically wait until @@ -32,7 +32,8 @@ param( $TraceOnly, # Enable tracing and perform a database snapshot with DiskShadow. - [Parameter(Mandatory = $true, ParameterSetName = "DiskShadow")] + [Parameter(Mandatory = $true, ParameterSetName = "DiskShadowByDatabase")] + [Parameter(Mandatory = $true, ParameterSetName = "DiskShadowByVolume")] [switch] $DiskShadow, @@ -41,36 +42,52 @@ param( [switch] $WaitForWriterFailure, - # Name of the database to focus tracing on. + # Name of the database to focus tracing on and/or snapshot. [Parameter(Mandatory = $true, ParameterSetName = "TraceOnly")] - [Parameter(Mandatory = $true, ParameterSetName = "DiskShadow")] + [Parameter(Mandatory = $true, ParameterSetName = "DiskShadowByDatabase")] [Parameter(Mandatory = $true, ParameterSetName = "WaitForWriterFailure")] [string] $DatabaseName, - # Drive letter on which to expose the database snapshot. - [Parameter(Mandatory = $true, ParameterSetName = "DiskShadow")] - [ValidateLength(1, 1)] - [string] - $DatabaseDriveLetter, + # Names of the volumes to snapshot. + [Parameter(Mandatory = $true, ParameterSetName = "DiskShadowByVolume")] + [ValidateCount(1, 2)] + [ValidateScript({ + $validVolumeNames = @((Get-CimInstance -Query "select name, DeviceId from win32_volume where DriveType=3" | + Where-Object { $_.Name -match "^\w:" }).Name) + if ($validVolumeNames -contains $_) { + $true + } else { + throw "Invalid volume specified. Please specify one of the following values:`n$([string]::Join("`n", $validVolumeNames))" + } + })] + [string[]] + $VolumesToBackup, - # Drive letter on which to expose the log snapshot. Only used when the log volume - # is different than the database volume. - [Parameter(Mandatory = $true, ParameterSetName = "DiskShadow")] + # Drive letters on which to expose the snapshots. + [Parameter(Mandatory = $true, ParameterSetName = "DiskShadowByDatabase")] + [Parameter(Mandatory = $true, ParameterSetName = "DiskShadowByVolume")] [ValidateLength(1, 1)] - [string] - $LogDriveLetter, + [ValidateCount(1, 2)] + [string[]] + $ExposeSnapshotsOnDriveLetters, # Path in which to put the collected traces. A subfolder named with the time of # the data collection is created in this path, and all files are put in that subfolder. # Defaults to the folder the script is in. [Parameter(Mandatory = $false, ParameterSetName = "TraceOnly")] - [Parameter(Mandatory = $false, ParameterSetName = "DiskShadow")] + [Parameter(Mandatory = $false, ParameterSetName = "DiskShadowByDatabase")] + [Parameter(Mandatory = $false, ParameterSetName = "DiskShadowByVolume")] [Parameter(Mandatory = $false, ParameterSetName = "WaitForWriterFailure")] [string] $LoggingPath = $PSScriptRoot ) +if ($VolumesToBackup -and ($VolumesToBackup.Count -ne $ExposeSnapshotsOnDriveLetters.Count)) { + Write-Host "The count of VolumesToBackup must match the count of ExposeSnapshotsOnDriveLetters." + exit +} + . $PSScriptRoot\..\..\Shared\ScriptUpdateFunctions\Get-ScriptUpdateAvailable.ps1 . $PSScriptRoot\..\..\Shared\Confirm-ExchangeShell.ps1 . .\DiskShadow\Invoke-CreateDiskShadowFile.ps1 @@ -130,32 +147,43 @@ try { Get-ExchangeVersion -ServerName $serverName Get-VSSWritersBefore -OutputPath $LoggingPath - $databases = Get-Databases -ServerName $serverName - $dbForBackup = $databases | Where-Object { $_.Name -eq $DatabaseName } - if ($null -eq $dbForBackup) { - Write-Warning "The specified database $DatabaseName does not exist on this server. Please enter a valid database name." - exit - } - Get-CopyStatus -ServerName $serverName -Database $dbForBackup -OutputPath $LoggingPath + if ($DatabaseName) { + $databases = Get-Databases -ServerName $serverName + $dbForBackup = $databases | Where-Object { $_.Name -eq $DatabaseName } + if ($null -eq $dbForBackup) { + Write-Warning "The specified database $DatabaseName does not exist on this server. Please enter a valid database name." + exit + } + + Get-CopyStatus -ServerName $serverName -Database $dbForBackup -OutputPath $LoggingPath + } if ($DiskShadow) { - $p = @{ - OutputPath = $LoggingPath - ServerName = $serverName - Databases = $databases - DatabaseToBackup = $dbForBackup - DatabaseDriveLetter = $DatabaseDriveLetter - LogDriveLetter = $LogDriveLetter + if ($DatabaseName) { + $p = @{ + OutputPath = $LoggingPath + ServerName = $serverName + Databases = $databases + DatabaseToBackup = $dbForBackup + DriveLetters = $ExposeSnapshotsOnDriveLetters + } + } else { + $p = @{ + OutputPath = $LoggingPath + ServerName = $serverName + VolumesToBackup = $VolumesToBackup + DriveLetters = $ExposeSnapshotsOnDriveLetters + } } - Write-Host "$p" + $p | Out-Host $exposedDrives = Invoke-CreateDiskShadowFile @p } Invoke-EnableDiagnosticsLogging Invoke-EnableVSSTracing -OutputPath $LoggingPath -Circular $WaitForWriterFailure Invoke-CreateExTRATracingConfig - Invoke-EnableExTRATracing -ServerName $serverName -Database $dbForBackup -OutputPath $LoggingPath -Circular $WaitForWriterFailure + Invoke-EnableExTRATracing -ServerName $serverName -DatabaseToBackup $dbForBackup -OutputPath $LoggingPath -Circular $WaitForWriterFailure $collectEventLogs = $false @@ -164,7 +192,7 @@ try { # Always collect event logs for this scenario $collectEventLogs = $true - Invoke-DiskShadow -OutputPath $LoggingPath -DatabaseToBackup $dbForBackup + Invoke-DiskShadow -OutputPath $LoggingPath Invoke-RemoveExposedDrives -OutputPath $LoggingPath -ExposedDrives $exposedDrives } elseif ($TraceOnly) { # Always collect event logs for this scenario diff --git a/docs/Databases/VSSTester.md b/docs/Databases/VSSTester.md index f2169b6fba..777a61041d 100644 --- a/docs/Databases/VSSTester.md +++ b/docs/Databases/VSSTester.md @@ -13,13 +13,21 @@ and use Ctrl-C to stop data collection after the backup attempt completes. ### Trace a snapshot using the DiskShadow tool -`.\VSSTester -DiskShadow -DatabaseName "Mailbox Database 1637196748" -DatabaseDriveLetter M -LogDriveLetter N` +`.\VSSTester -DiskShadow -DatabaseName "Mailbox Database 1637196748" -ExposeSnapshotsOnDriveLetters M, N` Enables tracing and then uses DiskShadow to snapshot the specified database. If the database and logs are on the same drive, the snapshot is exposed as M: drive. If they are on separate drives, the snapshots are exposed as M: and N:. The user is prompted to stop data collection and should typically wait until log truncation has occurred before doing so, so that the truncation is traced. +### Trace a snapshot using the DiskShadow tool by volume instead of by Database + +`.\VSSTester -DiskShadow -VolumesToBackup D:\, E:\ -ExposeSnapshotsOnDriveLetters M, N` + +Enables tracing and then uses DiskShadow to snapshot the specified volumes. To see a list of available +volumes, including mount points, pass an invalid volume name, such as `-VolumesToBackup foo`. The error +will show the available volumes. Volume names must be typed exactly as shown in that output. + ### Trace in circular mode until the Microsoft Exchange Writer fails `.\VSSTester -WaitForWriterFailure -DatabaseName "Mailbox Database 1637196748"`