From 131d9c406725d5fb26454d03d0e64fb6f033a7ea Mon Sep 17 00:00:00 2001 From: rquimbey <48227137+rquimbey@users.noreply.github.com> Date: Tue, 4 Jun 2024 10:55:13 -0700 Subject: [PATCH 01/19] Add examples with the backupsdk Would like to start showing examples using the backupsdk --- ...up Database Refresh physical BackupSDK.ps1 | 77 ++++++++++++++++++ ... Group Database Refresh vVol BackupSDK.ps1 | 79 +++++++++++++++++++ .../README.md | 43 ++++++++++ 3 files changed, 199 insertions(+) create mode 100644 demos-backupsdk/Protection Group Database Refresh/Protection Group Database Refresh physical BackupSDK.ps1 create mode 100644 demos-backupsdk/Protection Group Database Refresh/Protection Group Database Refresh vVol BackupSDK.ps1 create mode 100644 demos-backupsdk/Protection Group Database Refresh/README.md diff --git a/demos-backupsdk/Protection Group Database Refresh/Protection Group Database Refresh physical BackupSDK.ps1 b/demos-backupsdk/Protection Group Database Refresh/Protection Group Database Refresh physical BackupSDK.ps1 new file mode 100644 index 0000000..cf2ddd4 --- /dev/null +++ b/demos-backupsdk/Protection Group Database Refresh/Protection Group Database Refresh physical BackupSDK.ps1 @@ -0,0 +1,77 @@ +############################################################################################################################## +# Protection Group Database Refresh +# +# Scenario: +# This script will refresh a database on the target server from a source database on a different server. This script +# utilizes a FlashArray Protection Group, to snapshot and clone two volumes simultaneously. +# +# Prerequisities: +# 1. Two SQL Server instances with a single database, whose data file(s) are contained within 1 volume and log file(s) +# are contained within a 2nd volume. +# 2. A Protection Group defined with the two volumes (data and log) as members +# +# Usage Notes: +# This simple example assumes there is only one database residing on two different volumes (data & log). If multiple +# databases are present, additional code must be added to offline/online all databases present on the affected volumes +# in the Protection Group. Also note that the Protection Group use may include other volumes without negative impact. +# Any extraneous volumes will simply not be utilized during the cloning step. +# +# Disclaimer: +# This example script is provided AS-IS and meant to be a building block to be adapted to fit an individual +# organization's infrastructure. +############################################################################################################################## + + + +# Import powershell modules +Import-Module PureStorage.FlashArray.Backup + + + +# Declare variables +$SourceSQLServer = 'SqlServer1' # Name of source SQL Server +$TargetSQLServer = 'SqlServer2' # Name of target SQL Server +$SourceArrayName = 'flasharray1.example.com' # Source FlashArray FQDN +$TargetArrayName = 'flasharray2.example.com' # Target FlashArray FQDN +$DatabaseName = 'ExampleDb1' # Name of the database being snapshotted & cloned +$ProtectionGroupName = 'SqlServer1_Pg' # Protection Group name in the FlashArray +$VolumeSet = 'volset1' # Name of the Volume Set +$SourcePath = 's:\,t:\' # Path of Volumes to Snapshot +$TargetPath = 'n:\,m:\' # Path of Volumes to Mount +$VolumeType = 'physical' # Physical, vVol, or pRDM + + +# Set Credentials - this assumes the same credential for the target SQL Server and the FlashArray. If this is a VMware VM using pRDM or vVol, a vCenter credential is required. +$FlashArrayCredential = Get-Credential +$SQLServerCredential = Get-Credential + + +# Offline the target database +$Query = "ALTER DATABASE [$DatabaseName] SET OFFLINE WITH ROLLBACK IMMEDIATE" +Invoke-Sqlcmd -ServerInstance $TargetSQLServer -Database master -Query $Query + + + +# Create a new snapshot of the Protection Group +$Snapshot = Invoke-PsbSnapshotJob -FlashArrayAddress $ArrayName -FlashArrayCredential $FlashArrayCredential -VolumeSetName $VolumeSet -VolumeType $VolumeType -ComputerAddress $SourceSQLServer -ComputerCredential $SQLServerCredential -Path $SourcePath -pgroupname $ProtectionGroupName -ReplicateNow + + + +# Find the existing mounted snapshot so it can be dismounted +$FindMount = Get-PsbSnapshotSetMountHistory -FlashArrayAddress $ArrayName -FlashArrayCredential $FlashArrayCredential | where {($_.Computer -contains $TargetSQLServer -and $_.HistoryId -match $VolumeSet)} + + + +# Dismount the snapshot +Dismount-PsbSnapshotSet -flasharrayaddress $ArrayName -flasharraycredential $FlashArrayCredential -mountid $FindMount[0].mountid -computeraddress $TargetSQLServer -computercredential $SQLServerCredential + + + +# Mount the newer snapshot +Mount-PsbSnapshotSet -HistoryId $Snapshot.HistoryId -FlashArrayAddress $ArrayName -flasharraycredential $FlashArrayCredential -computeraddress $TargetSQLServer -computercredential $SQLServerCredential -Path $TargetPath + + + +# Online the database +$Query = "ALTER DATABASE [$DatabaseName] SET ONLINE WITH ROLLBACK IMMEDIATE" +Invoke-Sqlcmd -ServerInstance $TargetSQLServer -Database master -Query $Query \ No newline at end of file diff --git a/demos-backupsdk/Protection Group Database Refresh/Protection Group Database Refresh vVol BackupSDK.ps1 b/demos-backupsdk/Protection Group Database Refresh/Protection Group Database Refresh vVol BackupSDK.ps1 new file mode 100644 index 0000000..13be5fc --- /dev/null +++ b/demos-backupsdk/Protection Group Database Refresh/Protection Group Database Refresh vVol BackupSDK.ps1 @@ -0,0 +1,79 @@ +############################################################################################################################## +# Protection Group Database Refresh - vVol +# +# Scenario: +# This script will refresh a database on the target server from a source database on a different server. This script +# utilizes a FlashArray Protection Group, to snapshot and clone two volumes simultaneously. +# +# Prerequisities: +# 1. Two SQL Server instances with a single database, whose data file(s) are contained within 1 volume and log file(s) +# are contained within a 2nd volume. +# 2. A Protection Group defined with the two volumes (data and log) as members +# +# Usage Notes: +# This simple example assumes there is only one database residing on two different volumes (data & log). If multiple +# databases are present, additional code must be added to offline/online all databases present on the affected volumes +# in the Protection Group. Also note that the Protection Group use may include other volumes without negative impact. +# Any extraneous volumes will simply not be utilized during the cloning step. +# +# Disclaimer: +# This example script is provided AS-IS and meant to be a building block to be adapted to fit an individual +# organization's infrastructure. +############################################################################################################################## + + + +# Import powershell modules +Import-Module PureStorage.FlashArray.Backup + + + +# Declare variables +$SourceSQLServer = 'SqlServer1' # Name of source SQL Server +$TargetSQLServer = 'SqlServer2' # Name of target SQL Server +$SourceArrayName = 'flasharray1.example.com' # Source FlashArray FQDN +$TargetArrayName = 'flasharray2.example.com' # Target FlashArray FQDN +$DatabaseName = 'ExampleDb1' # Name of the database being snapshotted & cloned +$ProtectionGroupName = 'SqlServer1_Pg' # Protection Group name in the FlashArray +$VolumeSet = 'volset1' # Name of the Volume Set +$SourcePath = 's:\' # Path of Volumes to Snapshot +$TargetPath = 'n:\' # Path of Volumes to Mount +$VolumeType = 'physical' # Physical, vVol, or pRDM +$vCenterAddress = 'vcenter.example.com' # vCenter Server +$SourceVMName = 'sqlvm4' # Source VM +$TargetVMName = 'sqlvm5' # Target VM + +# Set Credentials - this assumes the same credential for the target SQL Server and the FlashArray. If this is a VMware VM using pRDM or vVol, a vCenter credential is required. +$FlashArrayCredential = Get-Credential +$SQLServerCredential = Get-Credential +$vCenterCredential = Get-Credential + +# Offline the target database +$Query = "ALTER DATABASE [$DatabaseName] SET OFFLINE WITH ROLLBACK IMMEDIATE" +Invoke-Sqlcmd -ServerInstance $TargetSQLServer -Database master -Query $Query + + + +# Create a new snapshot of the Protection Group +$Snapshot = Invoke-PsbSnapshotJob -vcenteraddress $vcenteraddress -VcenterCredential $vcentercredential -vmname $sourceVMName -FlashArrayAddress $ArrayName -FlashArrayCredential $FlashArrayCredential -VolumeSetName $VolumeSet -VolumeType $VolumeType -ComputerAddress $SourceSQLServer -ComputerCredential $SQLServerCredential -Path $SourcePath -pgroupname $ProtectionGroupName -ReplicateNow + + + +# Find the existing mounted snapshot so it can be dismounted +$FindMount = Get-PsbSnapshotSetMountHistory -FlashArrayAddress $ArrayName -FlashArrayCredential $FlashArrayCredential | where {($_.Computer -contains $TargetSQLServer -and $_.HistoryId -match $VolumeSet)} + + + +# Dismount the snapshot +Dismount-PsbSnapshotSet -flasharrayaddress $ArrayName -flasharraycredential $FlashArrayCredential -mountid $FindMount[0].mountid -computeraddress $TargetSQLServer -computercredential $SQLServerCredential + + + +# Mount the newer snapshot +Mount-PsbSnapshotSet -HistoryId $Snapshot.HistoryId -FlashArrayAddress $ArrayName -flasharraycredential $FlashArrayCredential -computeraddress $TargetSQLServer -computercredential $SQLServerCredential -Path $TargetPath -VMName $targetvmname -VCenterAddress $vCenterAddress -VCenterCredential $vCenterCredential + + + +# Online the database +$Query = "ALTER DATABASE [$DatabaseName] SET ONLINE WITH ROLLBACK IMMEDIATE" +Invoke-Sqlcmd -ServerInstance $TargetSQLServer -Database master -Query $Query \ No newline at end of file diff --git a/demos-backupsdk/Protection Group Database Refresh/README.md b/demos-backupsdk/Protection Group Database Refresh/README.md new file mode 100644 index 0000000..d68964f --- /dev/null +++ b/demos-backupsdk/Protection Group Database Refresh/README.md @@ -0,0 +1,43 @@ +# Protection Group Database Refresh +**Protection Group Database Refresh Scripts** +

+This folder contains an example script to take a Protection Group snapshot of a SQL Server database, whose files are split between two volumes (typically data & log volumes). +

+ + +**Files:** +- Protection Group Database Refresh.ps1 + + +
+ + +**Scenario:** +
This example script shows steps to snapshot a FlashArray Protection Group that contains the data and log volumes of a SQL Server database. It will then clone those two volumes, from the Protection Group snapshot, and overlay another pre-existing set of volumes on a different, non-production SQL Server. + +All references to a "target" refer to the non-production side. + +**Prerequisites:** +1. Two SQL Server instances with a single database, whose data file(s) are contained within 1 volume and log file(s) are contained within a 2nd volume. +2. A Protection Group defined with the two volumes (data and log) as members + +**Usage Notes:** +
This simple example assumes there is only one database residing on two different volumes (data & log). If multiple databases are present, additional code must be added to offline/online all databases present on the affected volumes in the Protection Group. Also note that the Protection Group use may include other volumes without negative impact. Any extraneous volumes will simply not be utilized during the cloning step. + + +
+ + +**Disclaimer:** +
+This example script is provided AS-IS and meant to be a building block to be adapted to fit an individual organization's infrastructure. +
+
+ +We encourage the modification and expansion of these scripts by the community. Although not necessary, please issue a Pull Request (PR) if you wish to request merging your modified code in to this repository. + + +
+ + +_The contents of the repository are intended as examples only and should be modified to work in your individual environments. No script examples should be used in a production environment without fully testing them in a development or lab environment. There are no expressed or implied warranties or liability for the use of these example scripts and templates presented by Pure Storage and/or their creators._ From 5314b35c714b7f24e9ff740865f509b821dd616d Mon Sep 17 00:00:00 2001 From: rquimbey <48227137+rquimbey@users.noreply.github.com> Date: Tue, 4 Jun 2024 11:05:36 -0700 Subject: [PATCH 02/19] Update README.md fixed filenames and more details on how it isn't overwriting volumes but dismount and mounting new copies. --- .../Protection Group Database Refresh/README.md | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/demos-backupsdk/Protection Group Database Refresh/README.md b/demos-backupsdk/Protection Group Database Refresh/README.md index d68964f..10f6885 100644 --- a/demos-backupsdk/Protection Group Database Refresh/README.md +++ b/demos-backupsdk/Protection Group Database Refresh/README.md @@ -1,25 +1,27 @@ # Protection Group Database Refresh **Protection Group Database Refresh Scripts**

-This folder contains an example script to take a Protection Group snapshot of a SQL Server database, whose files are split between two volumes (typically data & log volumes). +This folder contains example scripts to take a Protection Group snapshot of a SQL Server database, whose files are split between two volumes (typically data & log volumes).

**Files:** -- Protection Group Database Refresh.ps1 +- Protection Group Database Refresh physical BackupSDK.ps1 +- Protection Group Database Refresh vVol BackupSDK.ps1
**Scenario:** -
This example script shows steps to snapshot a FlashArray Protection Group that contains the data and log volumes of a SQL Server database. It will then clone those two volumes, from the Protection Group snapshot, and overlay another pre-existing set of volumes on a different, non-production SQL Server. +
This example script shows steps to snapshot a FlashArray Protection Group that contains the data and log volumes of a SQL Server database. It will then dismount a prior clone of those two volumes on the non-production SQL Server. Finally it will create a new clone from the most recent Protection Group snapshot and mount it to the non-production SQL Server. -All references to a "target" refer to the non-production side. +All references to a "target" refer to the non-production side. If the source and target FlashArrays are the same FlashArray only one variable is required. **Prerequisites:** 1. Two SQL Server instances with a single database, whose data file(s) are contained within 1 volume and log file(s) are contained within a 2nd volume. 2. A Protection Group defined with the two volumes (data and log) as members +3. Install the PureStorage.FlashArray.Backup module. **Usage Notes:**
This simple example assumes there is only one database residing on two different volumes (data & log). If multiple databases are present, additional code must be added to offline/online all databases present on the affected volumes in the Protection Group. Also note that the Protection Group use may include other volumes without negative impact. Any extraneous volumes will simply not be utilized during the cloning step. From f54537afcfeadb39c52bbae95c7a7078e583da73 Mon Sep 17 00:00:00 2001 From: rquimbey <48227137+rquimbey@users.noreply.github.com> Date: Tue, 4 Jun 2024 12:15:03 -0700 Subject: [PATCH 03/19] script typo --- .../Protection Group Database Refresh vVol BackupSDK.ps1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/demos-backupsdk/Protection Group Database Refresh/Protection Group Database Refresh vVol BackupSDK.ps1 b/demos-backupsdk/Protection Group Database Refresh/Protection Group Database Refresh vVol BackupSDK.ps1 index 13be5fc..bba25c5 100644 --- a/demos-backupsdk/Protection Group Database Refresh/Protection Group Database Refresh vVol BackupSDK.ps1 +++ b/demos-backupsdk/Protection Group Database Refresh/Protection Group Database Refresh vVol BackupSDK.ps1 @@ -38,7 +38,7 @@ $ProtectionGroupName = 'SqlServer1_Pg' # Protection $VolumeSet = 'volset1' # Name of the Volume Set $SourcePath = 's:\' # Path of Volumes to Snapshot $TargetPath = 'n:\' # Path of Volumes to Mount -$VolumeType = 'physical' # Physical, vVol, or pRDM +$VolumeType = 'vvol' # Physical, vVol, or pRDM $vCenterAddress = 'vcenter.example.com' # vCenter Server $SourceVMName = 'sqlvm4' # Source VM $TargetVMName = 'sqlvm5' # Target VM From bd577bf7f34977e6d01d22c363e1d39106f6c7a5 Mon Sep 17 00:00:00 2001 From: rquimbey <48227137+rquimbey@users.noreply.github.com> Date: Tue, 4 Jun 2024 13:54:01 -0700 Subject: [PATCH 04/19] removed alias where and updated scripts to have independent source/target FAs. --- ...otection Group Database Refresh physical BackupSDK.ps1 | 2 +- .../Protection Group Database Refresh vVol BackupSDK.ps1 | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/demos-backupsdk/Protection Group Database Refresh/Protection Group Database Refresh physical BackupSDK.ps1 b/demos-backupsdk/Protection Group Database Refresh/Protection Group Database Refresh physical BackupSDK.ps1 index cf2ddd4..f6066dc 100644 --- a/demos-backupsdk/Protection Group Database Refresh/Protection Group Database Refresh physical BackupSDK.ps1 +++ b/demos-backupsdk/Protection Group Database Refresh/Protection Group Database Refresh physical BackupSDK.ps1 @@ -58,7 +58,7 @@ $Snapshot = Invoke-PsbSnapshotJob -FlashArrayAddress $ArrayName -FlashArrayCrede # Find the existing mounted snapshot so it can be dismounted -$FindMount = Get-PsbSnapshotSetMountHistory -FlashArrayAddress $ArrayName -FlashArrayCredential $FlashArrayCredential | where {($_.Computer -contains $TargetSQLServer -and $_.HistoryId -match $VolumeSet)} +$FindMount = Get-PsbSnapshotSetMountHistory -FlashArrayAddress $ArrayName -FlashArrayCredential $FlashArrayCredential | Where-Object {($_.Computer -contains $TargetSQLServer -and $_.HistoryId -match $VolumeSet)} diff --git a/demos-backupsdk/Protection Group Database Refresh/Protection Group Database Refresh vVol BackupSDK.ps1 b/demos-backupsdk/Protection Group Database Refresh/Protection Group Database Refresh vVol BackupSDK.ps1 index bba25c5..9965db9 100644 --- a/demos-backupsdk/Protection Group Database Refresh/Protection Group Database Refresh vVol BackupSDK.ps1 +++ b/demos-backupsdk/Protection Group Database Refresh/Protection Group Database Refresh vVol BackupSDK.ps1 @@ -55,22 +55,22 @@ Invoke-Sqlcmd -ServerInstance $TargetSQLServer -Database master -Query $Query # Create a new snapshot of the Protection Group -$Snapshot = Invoke-PsbSnapshotJob -vcenteraddress $vcenteraddress -VcenterCredential $vcentercredential -vmname $sourceVMName -FlashArrayAddress $ArrayName -FlashArrayCredential $FlashArrayCredential -VolumeSetName $VolumeSet -VolumeType $VolumeType -ComputerAddress $SourceSQLServer -ComputerCredential $SQLServerCredential -Path $SourcePath -pgroupname $ProtectionGroupName -ReplicateNow +$Snapshot = Invoke-PsbSnapshotJob -vcenteraddress $vcenteraddress -VcenterCredential $vcentercredential -vmname $sourceVMName -FlashArrayAddress $SourceArrayName -FlashArrayCredential $FlashArrayCredential -VolumeSetName $VolumeSet -VolumeType $VolumeType -ComputerAddress $SourceSQLServer -ComputerCredential $SQLServerCredential -Path $SourcePath -pgroupname $ProtectionGroupName -ReplicateNow # Find the existing mounted snapshot so it can be dismounted -$FindMount = Get-PsbSnapshotSetMountHistory -FlashArrayAddress $ArrayName -FlashArrayCredential $FlashArrayCredential | where {($_.Computer -contains $TargetSQLServer -and $_.HistoryId -match $VolumeSet)} +$FindMount = Get-PsbSnapshotSetMountHistory -FlashArrayAddress $TargetArrayName -FlashArrayCredential $FlashArrayCredential | Where-Object {($_.Computer -contains $TargetSQLServer -and $_.HistoryId -match $VolumeSet)} # Dismount the snapshot -Dismount-PsbSnapshotSet -flasharrayaddress $ArrayName -flasharraycredential $FlashArrayCredential -mountid $FindMount[0].mountid -computeraddress $TargetSQLServer -computercredential $SQLServerCredential +Dismount-PsbSnapshotSet -flasharrayaddress $TargetArrayName -flasharraycredential $FlashArrayCredential -mountid $FindMount[0].mountid -computeraddress $TargetSQLServer -computercredential $SQLServerCredential -vcenteraddress $vcenteraddress -vcentercredential $vcentercredential # Mount the newer snapshot -Mount-PsbSnapshotSet -HistoryId $Snapshot.HistoryId -FlashArrayAddress $ArrayName -flasharraycredential $FlashArrayCredential -computeraddress $TargetSQLServer -computercredential $SQLServerCredential -Path $TargetPath -VMName $targetvmname -VCenterAddress $vCenterAddress -VCenterCredential $vCenterCredential +Mount-PsbSnapshotSet -HistoryId $Snapshot.HistoryId -FlashArrayAddress $TargetArrayName -flasharraycredential $FlashArrayCredential -computeraddress $TargetSQLServer -computercredential $SQLServerCredential -Path $TargetPath -VMName $targetvmname -VCenterAddress $vCenterAddress -VCenterCredential $vCenterCredential From afd916cfeef609a255ffd2c909b70d3662400020 Mon Sep 17 00:00:00 2001 From: rquimbey <48227137+rquimbey@users.noreply.github.com> Date: Thu, 6 Jun 2024 11:00:19 -0700 Subject: [PATCH 05/19] create/modify volumeset example to create the initial volume set.. example to modify a volume set by either adding or removing volumes. --- ...up Database Refresh physical BackupSDK.ps1 | 4 +- ... Group Database Refresh vVol BackupSDK.ps1 | 4 +- .../Volume Set Create or Modify/README.md | 46 ++++++++++ .../VolumeSet-Example.ps1 | 88 +++++++++++++++++++ 4 files changed, 138 insertions(+), 4 deletions(-) create mode 100644 demos-backupsdk/Volume Set Create or Modify/README.md create mode 100644 demos-backupsdk/Volume Set Create or Modify/VolumeSet-Example.ps1 diff --git a/demos-backupsdk/Protection Group Database Refresh/Protection Group Database Refresh physical BackupSDK.ps1 b/demos-backupsdk/Protection Group Database Refresh/Protection Group Database Refresh physical BackupSDK.ps1 index f6066dc..6dfdd54 100644 --- a/demos-backupsdk/Protection Group Database Refresh/Protection Group Database Refresh physical BackupSDK.ps1 +++ b/demos-backupsdk/Protection Group Database Refresh/Protection Group Database Refresh physical BackupSDK.ps1 @@ -38,10 +38,10 @@ $ProtectionGroupName = 'SqlServer1_Pg' # Protection $VolumeSet = 'volset1' # Name of the Volume Set $SourcePath = 's:\,t:\' # Path of Volumes to Snapshot $TargetPath = 'n:\,m:\' # Path of Volumes to Mount -$VolumeType = 'physical' # Physical, vVol, or pRDM +$VolumeType = 'physical' # Physical, vVol, or RDM -# Set Credentials - this assumes the same credential for the target SQL Server and the FlashArray. If this is a VMware VM using pRDM or vVol, a vCenter credential is required. +# Set Credentials - this assumes the same credential for the target SQL Server and the FlashArray. If this is a VMware VM using RDM or vVol, a vCenter credential is required. $FlashArrayCredential = Get-Credential $SQLServerCredential = Get-Credential diff --git a/demos-backupsdk/Protection Group Database Refresh/Protection Group Database Refresh vVol BackupSDK.ps1 b/demos-backupsdk/Protection Group Database Refresh/Protection Group Database Refresh vVol BackupSDK.ps1 index 9965db9..984dfdb 100644 --- a/demos-backupsdk/Protection Group Database Refresh/Protection Group Database Refresh vVol BackupSDK.ps1 +++ b/demos-backupsdk/Protection Group Database Refresh/Protection Group Database Refresh vVol BackupSDK.ps1 @@ -38,12 +38,12 @@ $ProtectionGroupName = 'SqlServer1_Pg' # Protection $VolumeSet = 'volset1' # Name of the Volume Set $SourcePath = 's:\' # Path of Volumes to Snapshot $TargetPath = 'n:\' # Path of Volumes to Mount -$VolumeType = 'vvol' # Physical, vVol, or pRDM +$VolumeType = 'vvol' # Physical, vVol, or RDM $vCenterAddress = 'vcenter.example.com' # vCenter Server $SourceVMName = 'sqlvm4' # Source VM $TargetVMName = 'sqlvm5' # Target VM -# Set Credentials - this assumes the same credential for the target SQL Server and the FlashArray. If this is a VMware VM using pRDM or vVol, a vCenter credential is required. +# Set Credentials - this assumes the same credential for the target SQL Server and the FlashArray. If this is a VMware VM using RDM or vVol, a vCenter credential is required. $FlashArrayCredential = Get-Credential $SQLServerCredential = Get-Credential $vCenterCredential = Get-Credential diff --git a/demos-backupsdk/Volume Set Create or Modify/README.md b/demos-backupsdk/Volume Set Create or Modify/README.md new file mode 100644 index 0000000..6358298 --- /dev/null +++ b/demos-backupsdk/Volume Set Create or Modify/README.md @@ -0,0 +1,46 @@ +# Protection Group Database Refresh +**Protection Group Database Refresh Scripts** +

+These examples demonstrate how to initially create a Volume Set. Additional examples demonstrate how to add or +subtrace volumes from a volume set. Remember that cmdlets involving Volume Sets will check to ensure all Volumes +are members of the declared Protection Group. +

+ + +**Files:** +- VolumeSet-Example.ps1 + + +
+ + +**Scenario:** +
These examples demonstrate how to initially create a Volume Set. Additional examples demonstrate how to add or +subtrace volumes from a volume set. Remember that cmdlets involving Volume Sets will check to ensure all Volumes +are members of the declared Protection Group. + +**Prerequisites:** +1. Administrator Credentials for a Windows Server and FlashArray. +2. Install the PureStorage.FlashArray.Backup module. +3. (optional) If a VMware VM using RDM or vVol, an Administrator Credential for vCenter. + +**Usage Notes:** +
Each example is an independent example showing how to initially create a Volume Set, or to modify the members of a Volume Set. + + +
+ + +**Disclaimer:** +
+This example script is provided AS-IS and meant to be a building block to be adapted to fit an individual organization's infrastructure. +
+
+ +We encourage the modification and expansion of these scripts by the community. Although not necessary, please issue a Pull Request (PR) if you wish to request merging your modified code in to this repository. + + +
+ + +_The contents of the repository are intended as examples only and should be modified to work in your individual environments. No script examples should be used in a production environment without fully testing them in a development or lab environment. There are no expressed or implied warranties or liability for the use of these example scripts and templates presented by Pure Storage and/or their creators._ diff --git a/demos-backupsdk/Volume Set Create or Modify/VolumeSet-Example.ps1 b/demos-backupsdk/Volume Set Create or Modify/VolumeSet-Example.ps1 new file mode 100644 index 0000000..f4e1989 --- /dev/null +++ b/demos-backupsdk/Volume Set Create or Modify/VolumeSet-Example.ps1 @@ -0,0 +1,88 @@ +############################################################################################################################## +# VolumeSet Example - Create or Modify +# +# Scenario: +# This script will show examples in how to initially create a Volume Set, and what has to be changed if you need +# Modify the number of volumes in the Volume Set. +# Prerequisities: +# 1. Install the PureStorage.FlashArray.Backup module +# 2. Have FlashArray administrator, Windows Server, and if a VMware VM, vCenter credentials. +# +# Usage Notes: +# These simple example show how to initially create and later modify a Volume Set that can be used +# for creating snapshots and mounting those snapshots. +# +# Disclaimer: +# This example script is provided AS-IS and meant to be a building block to be adapted to fit an individual +# organization's infrastructure. +############################################################################################################################## + + + +# Import powershell modules +Import-Module PureStorage.FlashArray.Backup + + + +# Declare variables +$SourceSQLServer = 'SqlServer1' # Name of source SQL Server +$SourceArrayName = 'flasharray1.example.com' # Source FlashArray FQDN +$ProtectionGroupName = 'SqlServer1_Pg' # Protection Group name in the FlashArray +$VolumeSet = 'volset1' # Name of the Volume Set +$SourcePath = 's:\' # Path of Volumes to Snapshot +$VolumeType = 'vvol' # Physical, vVol, or RDM +$vCenterAddress = 'vcenter.example.com' # vCenter Server +$SourceVMName = 'sqlvm4' # Source VM + +# Set Credentials - this assumes the same credential for the target SQL Server and the FlashArray. If this is a VMware VM using pRDM or vVol, a vCenter credential is required. +$FlashArrayCredential = Get-Credential +$SQLServerCredential = Get-Credential +$vCenterCredential = Get-Credential + +# Volume Sets can be manually created by specifying a set of volumes on a target Windows Server. +# New-PsbVolumeSet will connected to the declared ComputerAddress and match the drive letters and mount points +# to the corresponding volumes on the FlashArray. If VMware RDM/vVol additional parameters are required to +# assist in matching those supported disk types to Pure Storage Volumes. + +# Example 1: Create a new volume set where the target computeraddress is a server that has a Host Record on +# the FlashArray. This includes vHBA, in-guest iSCSI, and bare metal servers. + +$VolumeType = 'Physical' +New-PsbVolumeSet -VolumeSetName $VolumeSet -ComputerAddress $SourceSQLServer -ComputerCredential $SQLServerCredential -FlashArrayAddress $SourceArrayName -FlashArrayCredential $FlashArrayCredential -Path $SourcePath -VolumeType $VolumeType + +# Example 2: Create a new volume set where the target computeraddress is a VMware VM using physical RDMs. +# Note the query for the VMPID is optional, but if that is not passed and VM is renamed in vCenter, +# it will fail to find the VM if the -VMname parameter is not modified to the new VM name. +$VMPID = Get-PSBVMPersistentId -VCenterAddress $vCenterAddress -VCenterCredential $vCenterCredential -VMName $SourceVMName +$VolumeType = 'RDM' +New-PsbVolumeSet -VolumeSetName $VolumeSet -ComputerAddress $SourceSQLServer -ComputerCredential $SQLServerCredential -FlashArrayAddress $SourceArrayName -FlashArrayCredential $FlashArrayCredential -Path $SourcePath -VolumeType $VolumeType -VCenterAddress $vCenterCredential -VMName $SourceVMName -VMPersistentId $VMPID + +# Example 3: Create a new volume set where the target computeraddress is a VMware VM using virtual volumes (vVol). +# Note the query for the VMPID is optional, but if that is not passed and VM is renamed in vCenter, +# it will fail to find the VM if the -VMname parameter is not modified to the new VM name. +$VMPID = Get-PSBVMPersistentId -VCenterAddress $vCenterAddress -VCenterCredential $vCenterCredential -VMName $SourceVMName +$VolumeType = 'vvol' +New-PsbVolumeSet -VolumeSetName $VolumeSet -ComputerAddress $SourceSQLServer -ComputerCredential $SQLServerCredential -FlashArrayAddress $SourceArrayName -FlashArrayCredential $FlashArrayCredential -Path $SourcePath -VolumeType $VolumeType -VCenterAddress $vCenterCredential -VMName $SourceVMName -VMPersistentId $VMPID + +# Example 4: Building upon Example 1 +# Modify a volume set by adding a disk in the path. In this example the Volume Set already exists, and only +# one disk, the 's:\' disk, is in the volume set. The Invoke-PsbSnapshotJob will see the drive letters +# and mount points in the path, and check that they are all marked as belonging to the Volume Set. If any of the +# declared volumes are not in the volume set, powershell will ask you to confirm overwriting the volume set on +# the FlashArray with the new set of disks. Simply changing the -Path parameter is not sufficient, as all +# volumes declared in the -path must be members of the declared -pgroupname or the invoke-psbsnapshotjob will fail +# with an error indicating that all of the volumes in the Volume Set are not members of the declared Protection Group. + +$SourcePath = 's:\,t:\' # Path of Volumes to Snapshot +Invoke-PsbSnapshotJob -vcenteraddress $vcenteraddress -VcenterCredential $vcentercredential -vmname $sourceVMName -FlashArrayAddress $SourceArrayName -FlashArrayCredential $FlashArrayCredential -VolumeSetName $VolumeSet -VolumeType $VolumeType -ComputerAddress $SourceSQLServer -ComputerCredential $SQLServerCredential -Path $SourcePath -pgroupname $ProtectionGroupName + +# Example 5: Building upon Example 4 +# Modify a volume set by removing a disk in the path. In this example the Volume Set already exists, and +# two disks the 's:\,t:\' disks are members of the volume set. The Invoke-PsbSnapshotJob will see the drive letters +# and mount points in the path, and check that they are all marked as belonging to the Volume Set. If any volumes on +# the FlashArray are members of the Volume Set but not included in the -path parameter, powershell will ask you to +# confirm overwriting the volume set on the FlashArray with the new set of disks. This action will not remove +# volumes from the declared protection group. + +$SourcePath = 's:\' # Path of Volumes to Snapshot +Invoke-PsbSnapshotJob -vcenteraddress $vcenteraddress -VcenterCredential $vcentercredential -vmname $sourceVMName -FlashArrayAddress $SourceArrayName -FlashArrayCredential $FlashArrayCredential -VolumeSetName $VolumeSet -VolumeType $VolumeType -ComputerAddress $SourceSQLServer -ComputerCredential $SQLServerCredential -Path $SourcePath -pgroupname $ProtectionGroupName From c14cd79a4cf7200b3b1b7012e50a6c0d5f5de8ca Mon Sep 17 00:00:00 2001 From: rquimbey <48227137+rquimbey@users.noreply.github.com> Date: Thu, 6 Jun 2024 11:02:11 -0700 Subject: [PATCH 06/19] variable name typos --- ...otection Group Database Refresh physical BackupSDK.ps1 | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/demos-backupsdk/Protection Group Database Refresh/Protection Group Database Refresh physical BackupSDK.ps1 b/demos-backupsdk/Protection Group Database Refresh/Protection Group Database Refresh physical BackupSDK.ps1 index 6dfdd54..7af6767 100644 --- a/demos-backupsdk/Protection Group Database Refresh/Protection Group Database Refresh physical BackupSDK.ps1 +++ b/demos-backupsdk/Protection Group Database Refresh/Protection Group Database Refresh physical BackupSDK.ps1 @@ -53,22 +53,22 @@ Invoke-Sqlcmd -ServerInstance $TargetSQLServer -Database master -Query $Query # Create a new snapshot of the Protection Group -$Snapshot = Invoke-PsbSnapshotJob -FlashArrayAddress $ArrayName -FlashArrayCredential $FlashArrayCredential -VolumeSetName $VolumeSet -VolumeType $VolumeType -ComputerAddress $SourceSQLServer -ComputerCredential $SQLServerCredential -Path $SourcePath -pgroupname $ProtectionGroupName -ReplicateNow +$Snapshot = Invoke-PsbSnapshotJob -FlashArrayAddress $SourceArrayName -FlashArrayCredential $FlashArrayCredential -VolumeSetName $VolumeSet -VolumeType $VolumeType -ComputerAddress $SourceSQLServer -ComputerCredential $SQLServerCredential -Path $SourcePath -pgroupname $ProtectionGroupName -ReplicateNow # Find the existing mounted snapshot so it can be dismounted -$FindMount = Get-PsbSnapshotSetMountHistory -FlashArrayAddress $ArrayName -FlashArrayCredential $FlashArrayCredential | Where-Object {($_.Computer -contains $TargetSQLServer -and $_.HistoryId -match $VolumeSet)} +$FindMount = Get-PsbSnapshotSetMountHistory -FlashArrayAddress $TargetArrayName -FlashArrayCredential $FlashArrayCredential | Where-Object {($_.Computer -contains $TargetSQLServer -and $_.HistoryId -match $VolumeSet)} # Dismount the snapshot -Dismount-PsbSnapshotSet -flasharrayaddress $ArrayName -flasharraycredential $FlashArrayCredential -mountid $FindMount[0].mountid -computeraddress $TargetSQLServer -computercredential $SQLServerCredential +Dismount-PsbSnapshotSet -flasharrayaddress $TargetArrayName -flasharraycredential $FlashArrayCredential -mountid $FindMount[0].mountid -computeraddress $TargetSQLServer -computercredential $SQLServerCredential # Mount the newer snapshot -Mount-PsbSnapshotSet -HistoryId $Snapshot.HistoryId -FlashArrayAddress $ArrayName -flasharraycredential $FlashArrayCredential -computeraddress $TargetSQLServer -computercredential $SQLServerCredential -Path $TargetPath +Mount-PsbSnapshotSet -HistoryId $Snapshot.HistoryId -FlashArrayAddress $TargetArrayName -flasharraycredential $FlashArrayCredential -computeraddress $TargetSQLServer -computercredential $SQLServerCredential -Path $TargetPath From 4dcea835cf107b4f5f319b2b9bdb5cbf1427ccd9 Mon Sep 17 00:00:00 2001 From: Andy Yun Date: Thu, 30 Jan 2025 17:18:42 -0500 Subject: [PATCH 07/19] First commit of Hyper-V and SQL Server --- .../Hyper-V CSV w SQL Server Snapshot.ps1 | 226 ++++++++++++++++++ .../readme.md | 57 +++++ 2 files changed, 283 insertions(+) create mode 100644 demos-sdk2/Hyper-V CSV w SQL Server Snapshot/Hyper-V CSV w SQL Server Snapshot.ps1 create mode 100644 demos-sdk2/Hyper-V CSV w SQL Server Snapshot/readme.md diff --git a/demos-sdk2/Hyper-V CSV w SQL Server Snapshot/Hyper-V CSV w SQL Server Snapshot.ps1 b/demos-sdk2/Hyper-V CSV w SQL Server Snapshot/Hyper-V CSV w SQL Server Snapshot.ps1 new file mode 100644 index 0000000..d898ae8 --- /dev/null +++ b/demos-sdk2/Hyper-V CSV w SQL Server Snapshot/Hyper-V CSV w SQL Server Snapshot.ps1 @@ -0,0 +1,226 @@ +############################################################################################################################## +# Hyper-V Cluster Shared Volume (CSV) with SQL Server Snapshot Example +# +# Scenario: +# This script will clone a Hyper-V Cluster Shared Volume (CSV), using a crash consistent snapshot, and present it back +# to the originating Hyper-V cluster as a second CSV "copy." +# +# This example scenario is useful if you have isolated a VLDB SQL Server database exclusively onto this CSV +# +# See https://github.com/PureStorage-OpenConnect/sqlserver-scripts/tree/master/demos-sdk2/Hyper-V%20CSV%20Snapshot +# for more details +# +# +# Prerequisities: +# 1. An additional Windows server (referred to as a staging server). This staging server does not have to be a +# Hyper-V host. +# 2. A pre-created volume of equal size to the source CSV, pre-attached to the staging server. +# 3. The 'Failover Cluster Module for Windows PowerShell' Feature in Windows is required on the Hyper-V host. +# Add-WindowsFeature RSAT-Clustering-PowerShell +# +# +# Usage Notes: +# +# The staging server is needed because each CSV has a unique signature. If the CSV is presented back to the Hyper-V +# host unaltered, a signature collision will be detected and the new CSV will not be able to be used by Windows. +# Hyper-V is unable to resignature in this state either. Instead, the CSV must be presented to another machine (aka +# the staging server), resignatured there, then can be re-snapshotted and cloned back to the originating Hyper-V +# host. +# +# This script may be adjusted to clone and present the CSV snapshot to a different Hyper-V host. If this is done, then +# the staging server and resignature step is not required, since the new target Hyper-V host will not have two of the +# same CSV causing a signature conflict. +# +# +# Disclaimer: +# This example script is provided AS-IS and meant to be a building block to be adapted to fit an individual +# organization's infrastructure. +############################################################################################################################## +Import-Module PureStoragePowerShellSDK2 + + + +# Variables +$FlashArrayEndPoint = 'flasharray1.example.com' +$SourceVMCluster = 'hyperv-cluster-01.fsa.lab' +$SourceVMHost = 'hyperv-host-01.example.com' +$SourceVM = 'hyperv-vm-source' # No FQDN +$SourceVolumeName = 'hyperv-vm-source-csv-01' # Name of the volume in FlashArray +$StagingServer = 'windows-staging-server' +$StagingVolumeName = 'temporary-volume-for-csv-resignature' +$StagingDiskSerialNumber = '6000c2945ce069b03b9750d2afe72828' +$TargetVMHost = 'hyperv-host-02.example.com' # No FQDN +$TargetVM = 'hyperv-vm-target' # No FQDN +$TargetVolumeName = 'hyperv-vm-target-csv-01-cloned' +$TargetClusterDiskNumber = 'Cluster Disk 3' +$DatabaseName = 'MyDatabaseName' +$ClusteredStorageFolder = "C:\ClusterStorage\volume4\hv-sqldata-01\data\*.*" # Target Host Folder containing cloned VHDX/AVHDX files + + + +# Establish credential to use for all connections +$Credential = Get-Credential -Message 'Enter your Pure credentials' + + + +# Connect to the FlashArray +$FlashArray = Connect-Pfa2Array -Endpoint $FlashArrayEndPoint -Credential ($Credential) -IgnoreCertificateError + + + +# Determine which Hyper-V node each role currently resides on +$HyperVClusterSession = New-PSSession -ComputerName $SourceVMCluster -Credential $Credential + +$SourceClusterGroup = Invoke-Command -Session $HyperVClusterSession -ScriptBlock { Get-ClusterGroup -Name $Using:SourceVM } +$TargetClusterGroup = Invoke-Command -Session $HyperVClusterSession -ScriptBlock { Get-ClusterGroup -Name $Using:TargetVM } + +$SourceVMHost = $SourceClusterGroup.OwnerNode +$TargetVMHost = $TargetClusterGroup.OwnerNode + +# Verify +$SourceVM +$SourceVMHost + +$TargetVM +$TargetVMHost + + + +# Prepare the staging CSV for overlay +# Connect to staging VM +$StagingServerSession = New-PSSession -ComputerName $StagingServer -Credential $Credential + + + +# Offline the volume +# NOTE: use Get-Disk prior to get the correct Serial Number +Invoke-Command -Session $StagingServerSession -ScriptBlock { Get-Disk | Where-Object { $_.SerialNumber -eq $using:StagingDiskSerialNumber } | Set-Disk -IsOffline $True } + +# Verify +Invoke-Command -Session $StagingServerSession -ScriptBlock { Get-Disk | Where-Object { $_.SerialNumber -eq $using:StagingDiskSerialNumber }} + + + +# Snapshot the source CSV +# This example is for an on-demand snapshot. Can adjust code to also use a prior snapshot; ex. regularly scheduled +# snapshots or an asynchronously replicated snapshot from another FlashArray + +# Clone the source CSV to the staging CSV +New-Pfa2Volume -Array $FlashArray -Name $StagingVolumeName -SourceName $SourceVolumeName -Overwrite $true + + + +# Now must resignature the CSV on the staging VM +# Build DISKPART script commands for resignature +$StagingDisk = Invoke-Command -Session $StagingServerSession -ScriptBlock { Get-Disk | Where-Object { $_.SerialNumber -eq $Using:StagingDiskSerialNumber }} +$DiskNumber = $StagingDisk.Number +$NewUniqueID = [GUID]::NewGuid() +$Commands = "`"SELECT DISK $DiskNumber`"", + "`"UNIQUEID DISK ID=$NewUniqueID`"" +$ScriptBlock = [string]::Join(",",$Commands) +$DiskpartScriptBlock = $ExecutionContext.InvokeCommand.NewScriptBlock("$ScriptBlock | DISKPART") + +# Verify DISKPART command +$DiskpartScriptBlock + +# Issue resignature command +Invoke-Command -Session $StagingServerSession -ScriptBlock $DiskpartScriptBlock + + + +# Prepare target VM +$TargetVMSession = New-PSSession -ComputerName $TargetVM -Credential $Credential + +# Offline the database +$Query = "ALTER DATABASE $DatabaseName SET OFFLINE WITH ROLLBACK IMMEDIATE" +Invoke-Command -Session $TargetVMSession -ScriptBlock {Param($querytask) Invoke-Sqlcmd -ServerInstance . -Database master -Query $querytask} -ArgumentList ($Query) + +# Offline the volume +# Because this is a Hyper-V VM, volume serial numbers are not populated by Hyper-V into a virtual machine +# Therefore must use a different method identify the proper volume to offline + +# Confirm which drive you want +Invoke-Command -Session $TargetVMSession -ScriptBlock { Get-Disk | Format-Table } + + + +# Specify the drive number +$DiskNumber = 1 +Invoke-Command -Session $TargetVMSession -ScriptBlock { Get-Disk -Number $using:DiskNumber | Get-Disk | Set-Disk -IsOffline $True } + +# Verify offline +Invoke-Command -Session $TargetVMSession -ScriptBlock { Get-Disk | Format-Table } + + + +# Prepare target VM Host +$TargetVMHostSession = New-PSSession -ComputerName $TargetVMHost -Credential $Credential + +# Remove SQL Server cluster resource dependency on database volume +# Only use this if you are using Clustered Disks (NOT Clustered Shared Volumes) +# Invoke-Command -Session $TargetVMHostSession -ScriptBlock { Get-ClusterResource 'SQL Server' | Remove-ClusterResourceDependency $TargetVolumeName } + +# Stop the disk cluster resource +# NOTE: need to know which Cluster Disk Number first +# This will put the target Hyper-V VM into a Saved state +Invoke-Command -Session $TargetVMHostSession -ScriptBlock { Stop-ClusterResource $Using:TargetClusterDiskNumber } + +# Verify +Invoke-Command -Session $TargetVMHostSession -ScriptBlock { Get-ClusterSharedVolume $Using:TargetClusterDiskNumber } + + + +# Clone the staging CSV to the target CSV +New-Pfa2Volume -Array $FlashArray -Name $TargetVolumeName -SourceName $StagingVolumeName -Overwrite $true + + + +# Start the disk cluster resource +Invoke-Command -Session $TargetVMHostSession -ScriptBlock { Start-ClusterResource $Using:TargetClusterDiskNumber } + +# Verify +Invoke-Command -Session $TargetVMHostSession -ScriptBlock { Get-ClusterSharedVolume $Using:TargetClusterDiskNumber } + + + +# Must now update permissions in Windows to grant the new VM access to the VHDX files + +Invoke-Command -Session $TargetVMHostSession -ScriptBlock { + $VMID = "NT VIRTUAL MACHINE\" + Get-VM -name $Using:TargetVM | Select-Object -ExpandProperty VMID + + $fileAclList = Get-Acl $Using:ClusteredStorageFolder + Foreach ($acl in $fileAclList) { + # Add a new rule to grant full control to a user + $rule = New-Object System.Security.AccessControl.FileSystemAccessRule($VMID, "FullControl", "Allow") + $acl.AddAccessRule($rule) + Set-Acl -Path $acl.PSPath -AclObject $acl + } +} + + + +# Online the volume + +# Confirm which drive you want +Invoke-Command -Session $TargetVMSession -ScriptBlock { Get-Disk | Format-Table } + + + +# Specify the drive number +$DiskNumber = 1 +Invoke-Command -Session $TargetVMSession -ScriptBlock { Get-Disk -Number $using:DiskNumber | Get-Disk | Set-Disk -IsOffline $False } + +# Verify +Invoke-Command -Session $TargetVMSession -ScriptBlock { Get-Disk | Format-Table } + + + +# Online the database +$Query = "ALTER DATABASE $DatabaseName SET ONLINE" +Invoke-Command -Session $TargetVMSession -ScriptBlock {Param($querytask) Invoke-Sqlcmd -ServerInstance . -Database master -Query $querytask} -ArgumentList ($Query) + +# Verify +$Query = "SELECT @@SERVERNAME, name, state_desc, GETDATE() FROM sys.databases WHERE database_id = DB_ID('$DatabaseName')" +Invoke-Command -Session $TargetVMSession -ScriptBlock {Param($querytask) Invoke-Sqlcmd -ServerInstance . -Database master -Query $querytask} -ArgumentList ($Query) + diff --git a/demos-sdk2/Hyper-V CSV w SQL Server Snapshot/readme.md b/demos-sdk2/Hyper-V CSV w SQL Server Snapshot/readme.md new file mode 100644 index 0000000..e77eb26 --- /dev/null +++ b/demos-sdk2/Hyper-V CSV w SQL Server Snapshot/readme.md @@ -0,0 +1,57 @@ +**Hyper-V Cluster Shared Volume + SQL Server Snapshot Scripts** +

+This folder contains Hyper-V Cluster Shared Volume + SQL Server example snapshot scripts. + +**Files:** +- Hyper-V CSV w SQL Server Snapshot.ps1 + + +
+ + +**Scenario:** +
This example script shows steps to snapshot a Hyper-V Cluster Shared Volume (CSV) that contains data & log VHDX/AVHDX files for a SQL Server. + +
+
+This scenario has a SQL Server Hyper-V VM (ex: Production) with at least two CSVs. The first CSV is the primary, which will contain VHDX files for the VM itself, OS drive, SQL Server system files, tempdb, etc. The second CSV, which is what this script will clone, will ONLY contain the data and log files for 1 or more user databases ONLY. The data and log files can be on two different VHDX files or the same VHDX file - it only matters that they reside in this second CSV, not the first. +
+
+The second SQL Server Hyper-V VM (ex: non-Production) will also be set up similarly, with two CSVs, so we can clone the Production CSV's user database(s) and refresh this second VM's second CSV with a clone of Production's database. +
+
+All references to a "source" refer to the production side (VM, CSV, etc). +All references to a "target" refer to the non-production side (VM, CSV, etc). + +**Prerequisites:** +1. The production CSV must already be cloned and presented once, to the non-production side. +2. This script assumes the database(s) are already attached on the target, non-production SQL Server. + +**Important Usage Notes:** +
You must pre-setup the target VM with a cloned CSV from the source already. You will ONLY be utilizing the specific VHDX(s) that contain the data/log files of interest, from the cloned CSV. Also note that the CSV does not need to only exclusively contain VHDXs for the SQL Server in question. If other VHDXs are present in the CSV, used by the either the source SQL Server VM or other VMs, they do not need to be deleted or otherwise manipulated during this cloning process. Remember FlashArray deduplicates data, thus a clone's set of additional, unused VHDXs will not have a negative impact. + +For the cloned CSV pre-setup, you can use subsets of the code below to clone the source CSV, present it to the target server, then attach the VHDX(s) containing the production databases that will be re-cloned with this script. Once "staged," you can then use this script fully to refresh the data files in the cloned CSV that is attached to the target server. + +This script also assumes that all database files (data and log) are on the same volume/single VHDX. If multiple volumes/VHDXs are being used, you will have to adjust the code (ex: add additional foreach loops for manipulating multiple VHDXs). + +The staging server is needed because each CSV has a unique signature. If the CSV is presented back to the Hyper-V host unaltered, a signature collision will be detected and the new CSV will not be able to be used by Windows. Hyper-V is unable to resignature in this state either. Instead, the CSV must be presented to another machine (aka the staging server), resignatured there, then can be re-snapshotted and cloned back to the originating Hyper-V host. + +This script may be adjusted to clone and present the CSV snapshot to a different Hyper-V host. If this is done, then the staging server and resignature step is not required, since the new target Hyper-V host will not have two of the same CSV causing a signature conflict. + + +
+ + +**Disclaimer:** +
+This example script is provided AS-IS and meant to be a building block to be adapted to fit an individual organization's infrastructure. +
+
+ +We encourage the modification and expansion of these scripts by the community. Although not necessary, please issue a Pull Request (PR) if you wish to request merging your modified code in to this repository. + + +
+ + +_The contents of the repository are intended as examples only and should be modified to work in your individual environments. No script examples should be used in a production environment without fully testing them in a development or lab environment. There are no expressed or implied warranties or liability for the use of these example scripts and templates presented by Pure Storage and/or their creators._ \ No newline at end of file From 0d8c9d9a3766198688625f0ee41aa9fea9a78368 Mon Sep 17 00:00:00 2001 From: "Anthony E. Nocentino" Date: Thu, 30 Jan 2025 17:23:05 -0500 Subject: [PATCH 08/19] Update Seeding an Availability Group.ps1 --- .../Seeding an Availability Group.ps1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/demos-sdk2/Seeding an Availability Group/Seeding an Availability Group.ps1 b/demos-sdk2/Seeding an Availability Group/Seeding an Availability Group.ps1 index 98eeb44..7de7c61 100644 --- a/demos-sdk2/Seeding an Availability Group/Seeding an Availability Group.ps1 +++ b/demos-sdk2/Seeding an Availability Group/Seeding an Availability Group.ps1 @@ -67,7 +67,7 @@ $FlashArrayPrimary = Connect-Pfa2Array –EndPoint $PrimaryArrayName -Credential -# Freeze the database +# Freeze the database for write IOs $Query = "ALTER DATABASE [$DbName] SET SUSPEND_FOR_SNAPSHOT_BACKUP = ON" Invoke-DbaQuery -SqlInstance $SqlInstancePrimary -Query $Query -Verbose From f8a8af9b6959dc8d4c6807e2ac107e8f3bfb3d8a Mon Sep 17 00:00:00 2001 From: "Anthony E. Nocentino" Date: Tue, 30 Sep 2025 15:17:53 -0700 Subject: [PATCH 09/19] removed SSMS extension demos --- .../SSMS Extension Database Attach/README.md | 0 .../SSMS Extension Database Attach.ps1 | 0 .../Protection Group Database Refresh physical BackupSDK.ps1 | 0 .../Protection Group Database Refresh vVol BackupSDK.ps1 | 0 .../demos-backupsdk}/Protection Group Database Refresh/README.md | 0 .../demos-backupsdk}/Volume Set Create or Modify/README.md | 0 .../Volume Set Create or Modify/VolumeSet-Example.ps1 | 0 7 files changed, 0 insertions(+), 0 deletions(-) rename {demos-sdk2 => demos-archive}/SSMS Extension Database Attach/README.md (100%) rename {demos-sdk2 => demos-archive}/SSMS Extension Database Attach/SSMS Extension Database Attach.ps1 (100%) rename {demos-backupsdk => demos-archive/demos-backupsdk}/Protection Group Database Refresh/Protection Group Database Refresh physical BackupSDK.ps1 (100%) rename {demos-backupsdk => demos-archive/demos-backupsdk}/Protection Group Database Refresh/Protection Group Database Refresh vVol BackupSDK.ps1 (100%) rename {demos-backupsdk => demos-archive/demos-backupsdk}/Protection Group Database Refresh/README.md (100%) rename {demos-backupsdk => demos-archive/demos-backupsdk}/Volume Set Create or Modify/README.md (100%) rename {demos-backupsdk => demos-archive/demos-backupsdk}/Volume Set Create or Modify/VolumeSet-Example.ps1 (100%) diff --git a/demos-sdk2/SSMS Extension Database Attach/README.md b/demos-archive/SSMS Extension Database Attach/README.md similarity index 100% rename from demos-sdk2/SSMS Extension Database Attach/README.md rename to demos-archive/SSMS Extension Database Attach/README.md diff --git a/demos-sdk2/SSMS Extension Database Attach/SSMS Extension Database Attach.ps1 b/demos-archive/SSMS Extension Database Attach/SSMS Extension Database Attach.ps1 similarity index 100% rename from demos-sdk2/SSMS Extension Database Attach/SSMS Extension Database Attach.ps1 rename to demos-archive/SSMS Extension Database Attach/SSMS Extension Database Attach.ps1 diff --git a/demos-backupsdk/Protection Group Database Refresh/Protection Group Database Refresh physical BackupSDK.ps1 b/demos-archive/demos-backupsdk/Protection Group Database Refresh/Protection Group Database Refresh physical BackupSDK.ps1 similarity index 100% rename from demos-backupsdk/Protection Group Database Refresh/Protection Group Database Refresh physical BackupSDK.ps1 rename to demos-archive/demos-backupsdk/Protection Group Database Refresh/Protection Group Database Refresh physical BackupSDK.ps1 diff --git a/demos-backupsdk/Protection Group Database Refresh/Protection Group Database Refresh vVol BackupSDK.ps1 b/demos-archive/demos-backupsdk/Protection Group Database Refresh/Protection Group Database Refresh vVol BackupSDK.ps1 similarity index 100% rename from demos-backupsdk/Protection Group Database Refresh/Protection Group Database Refresh vVol BackupSDK.ps1 rename to demos-archive/demos-backupsdk/Protection Group Database Refresh/Protection Group Database Refresh vVol BackupSDK.ps1 diff --git a/demos-backupsdk/Protection Group Database Refresh/README.md b/demos-archive/demos-backupsdk/Protection Group Database Refresh/README.md similarity index 100% rename from demos-backupsdk/Protection Group Database Refresh/README.md rename to demos-archive/demos-backupsdk/Protection Group Database Refresh/README.md diff --git a/demos-backupsdk/Volume Set Create or Modify/README.md b/demos-archive/demos-backupsdk/Volume Set Create or Modify/README.md similarity index 100% rename from demos-backupsdk/Volume Set Create or Modify/README.md rename to demos-archive/demos-backupsdk/Volume Set Create or Modify/README.md diff --git a/demos-backupsdk/Volume Set Create or Modify/VolumeSet-Example.ps1 b/demos-archive/demos-backupsdk/Volume Set Create or Modify/VolumeSet-Example.ps1 similarity index 100% rename from demos-backupsdk/Volume Set Create or Modify/VolumeSet-Example.ps1 rename to demos-archive/demos-backupsdk/Volume Set Create or Modify/VolumeSet-Example.ps1 From 2af23e23da2f9fe92c9bf9c918fb9cc449265e62 Mon Sep 17 00:00:00 2001 From: Andy Yun Date: Mon, 22 Dec 2025 11:14:35 -0500 Subject: [PATCH 10/19] T-SQL Snapshot w VMFS - V1 --- .../Point in Time Recovery - VMFS.ps1 | 350 ++++++++++++++++++ .../Point in Time Recovery - VMFS/README.md | 40 ++ 2 files changed, 390 insertions(+) create mode 100644 demos-sdk2/Point in Time Recovery - VMFS/Point in Time Recovery - VMFS.ps1 create mode 100644 demos-sdk2/Point in Time Recovery - VMFS/README.md diff --git a/demos-sdk2/Point in Time Recovery - VMFS/Point in Time Recovery - VMFS.ps1 b/demos-sdk2/Point in Time Recovery - VMFS/Point in Time Recovery - VMFS.ps1 new file mode 100644 index 0000000..e2ad494 --- /dev/null +++ b/demos-sdk2/Point in Time Recovery - VMFS/Point in Time Recovery - VMFS.ps1 @@ -0,0 +1,350 @@ +############################################################################################################################## +# Point In Time Recovery - Using SQL Server 2022's T-SQL Snapshot Backup feature w. VMFS/VMDK datastore/files. +# +# Scenario: +# Perform a point in time restore using SQL Server 2022's T-SQL Snapshot Backup +# feature. This uses a FlashArray snapshot as the base of the restore, then restores +# a log backup. +# +# IMPORTANT NOTE: +# This example script is built for 1 database spanned across two VMDK files/volumes +# from a single datastore. +# +# The granularity or unit of work for this workflow is a VMDK file(s) and the entirety +# of its contents. Therefore, everything in the VMDK file(s) including files for other +# databases will be impacted/overwritten. +# +# This example will need to be adapted if you wish to support multiple databases on +# the same set of VMDK(s). +# +# Prerequisites: +# 1. PowerShell Modules: dbatools & PureStoragePowerShellSDK2 +# +# Usage Notes: +# * Each section of the script is meant to be run individually, one after another. +# * The script is NOT meant to be executed all at once. +# +# Disclaimer: +# This example script is provided AS-IS and is meant to be a building +# block to be adapted to fit an individual organization's +# infrastructure. +############################################################################################################################## + + + +# Import powershell modules +Import-Module dbatools +Import-Module PureStoragePowerShellSDK2 + + + +# Declare all variables +# VMware variables +$VIServerName = 'vcenter.example.com' +$SourceDatastoreName = 'source_sql_datastore' +$SourceVMDKPaths = @('source_vm/sqldata.vmdk','source_vm/sqllog.vmdk') + + + +# FlashArray variables +$ArrayName = 'flasharray1.example.com' # FlashArray FQDN +$FAHostGroupName = 'FAHostGroupName' # HostGroup Name on FlashArray for the ESXi cluster +$SourceVolumeName = 'volume_name' # Volume name on FlashArray containing database files +$PGroupName = 'protection_group' # Name of the Protection Group on FlashArray1 + + + +# Windows/SQL Server variables +$TargetSQLServer = 'target_sqlserver.example.com' # SQL Server Instance FQDN +$TargetVM = 'target_sqlserver' # SQL Server VM name in VCenter +$DbName = 'AdventureWorks' # Name of database +$BackupShare = '\\flashblade1.example.com\backups' # File system location to write the backup metadata file +$TargetDisks = @('1234c29689bc0888d32dcd2919a67z89', '1234c299721c4ba4a937552fb298a76') # The serial numbers of the Windows volume containing database files; use get-disk + + + +# Build a PowerShell Remoting Session to the Server +$SqlServerSession = New-PSSession -ComputerName $TargetSQLServer + + + +# Build a persistent SMO connection +$SqlInstance = Connect-DbaInstance -SqlInstance $TargetSQLServer -TrustServerCertificate -NonPooledConnection + + + +# Let's get some information about our database, take note of the size +Get-DbaDatabase -SqlInstance $SqlInstance -Database $DbName | + Select-Object Name, SizeMB + + + +# Connect to the FlashArray's REST API +$Credential = Get-Credential -UserName "$env:USERNAME" -Message 'Enter your credential information...' +$FlashArray = Connect-Pfa2Array –EndPoint $ArrayName -Credential $Credential -IgnoreCertificateError + + + +#### +# Execute our backup + +# Freeze the database +$Query = "ALTER DATABASE $DbName SET SUSPEND_FOR_SNAPSHOT_BACKUP = ON" +Invoke-DbaQuery -SqlInstance $SqlInstance -Query $Query -Verbose + + + +# Take a snapshot of the Protection Group while the database is frozen +$Snapshot = New-Pfa2ProtectionGroupSnapshot -Array $FlashArray -SourceName $PGroupName + + + +# Take a metadata backup of the database, this will automatically unfreeze +# if successful +# We'll use MEDIADESCRIPTION to hold some information about our snapshot and +# the flasharray its held on +$BackupFile = "$BackupShare\$DbName-$(Get-Date -Format FileDateTime).bkm" + +$Query = "BACKUP DATABASE $DbName + TO DISK='$BackupFile' + WITH METADATA_ONLY, MEDIADESCRIPTION='$($Snapshot.Name)|$($FlashArray.ArrayName)'" +Invoke-DbaQuery -SqlInstance $SqlInstance -Query $Query -Verbose + +### +# Backup completed + + + +### +# Backup Verification + +# Let's check out the error log to see what SQL Server thinks happened +Get-DbaErrorLog -SqlInstance $SqlInstance -LogNumber 0 -After (Get-Date).AddMinutes(-15) | Format-Table + + + +# The backup is recorded in MSDB as a Full backup with snapshot +$BackupHistory = Get-DbaDbBackupHistory -SqlInstance $SqlInstance -Database $DbName -Last +$BackupHistory + + + +# Let's explore the stuff in the backup header... +# Remember, VDI is just a contract saying what's in the backup matches what SQL Server thinks is in the backup. +Read-DbaBackupHeader -SqlInstance $SqlInstance -Path $BackupFile + + + +### +# Take a Transaction Log backup +# +# NOTE: If you are testing this with a database in SIMPLE RECOVERY, there seems to be an occasional bug in +# Backup-DbaDatabase that keeps a DataReader connection open. Subsequent dbatools cmdlet steps may fail. +# Skip this step if your database in SIMPLE RECOVERY. +$LogBackup = Backup-DbaDatabase -SqlInstance $SqlInstance -Database $DbName -Type Log -Path $BackupShare -CompressBackup + + + +### +# DEMO - Delete a table +Invoke-DbaQuery -SqlInstance $SqlInstance -Database $DbName -Query "SELECT TOP 10 * FROM Sales.Customer" + + + +# Delete a table +Invoke-DbaQuery -SqlInstance $SqlInstance -Database $DbName -Query "DROP TABLE Sales.Customer" + + + +# Confirm it is gone +Invoke-DbaQuery -SqlInstance $SqlInstance -Database $DbName -Query "SELECT TOP 10 * FROM Sales.Customer" + + + +### +# Review State of Database and backup + +# Let's check out the state of the database, size, last full and last log +Get-DbaDatabase -SqlInstance $SqlInstance -Database $DbName | + Select-Object Name, Size, LastFullBackup, LastLogBackup + + + +# We can get the snapshot name from the $Snapshot variable above, but what if we didn't know this ahead of time? +# We can also get the snapshot name from the MEDIADESCRIPTION in the backup file. +$Query = "RESTORE LABELONLY FROM DISK = '$BackupFile'" +$Labels = Invoke-DbaQuery -SqlInstance $SqlInstance -Query $Query -Verbose +$SnapshotName = (($Labels | Select-Object MediaDescription -ExpandProperty MediaDescription).Split('|'))[0] +$ArrayName = (($Labels | Select-Object MediaDescription -ExpandProperty MediaDescription).Split('|'))[1] + + + +### +# Start the Restore Process + +# Connect to vCenter +$VIServer = Connect-VIServer -Server $VIServerName -Protocol https -Credential $Credential +$TargetSQLServerVM = Get-VM -Server $VIServer -Name $TargetVM +$VMESXiHost = Get-VMhost -VM $TargetSQLServerVM + + + +# Create a new volume from the selected snapshot of the source +$SnapshotSuffix = (Get-Date).ToString("yyyyMMdd-HHmmss") +$NewClonedVolumeName = "$($SourceVolumeName)-clone-$($SnapshotSuffix)" +$SnapshotSourceVolumeName = $SnapshotName + ".$SourceVolumeName" +New-Pfa2Volume -Array $FlashArray -Name $NewClonedVolumeName -SourceName $SnapshotSourceVolumeName -Overwrite $true + + + +# Present the new volume to the ESXi host group +New-Pfa2Connection -Array $FlashArray -HostGroupName $FAHostGroupName -VolumeName $NewClonedVolumeName + + + +# ESXi host must now rescan storage +Get-VMHostStorage -RescanAllHba -RescanVmfs -VMHost $VMESXiHost + + + +# Connect to EsxCli +$EsxCli = Get-EsxCli -VMHost $VMESXiHost + + + +### Diagnostic +# Retrieve a list of the snapshots that have been presented to the host (our cloned volume should be present) +# $snapInfo = $EsxCli.storage.vmfs.snapshot.list() +# $snapInfo | where-object { ($_.VolumeName -match $SourceDatastoreName) } +# $snapInfo + + + +# Resignature the cloned datastore +$EsxCli.storage.vmfs.snapshot.resignature($SourceDatastoreName) + + + +# Find the newly resignatured datastore name +# NOTE: +# After a datastore is resignatured, its name will be "snap-[GUID chars]-[original DS name]" +# This is why the wildcard match below is needed. +$clonedDatastore = (Get-Datastore | ? { $_.name -match 'snap' -and $_.name -match $SourceDatastoreName }) + +while ($clonedDatastore -eq $null) { + # We may have to wait a little bit before the datastore is fully operational + Start-Sleep -Seconds 5 + $clonedDatastore = (Get-Datastore | Where-Object { $_.name -match 'snap' -and $_.name -match $SourceDatastoreName }) +} + + + +# Must rescan storage again so ESXi hosts(s) can see the new cloned datastore +Get-VMHostStorage -RescanAllHba -RescanVmfs -VMHost $VMESXiHost + + + +######################################## +# Prepare SQL Server & Windows for the +# snapshot overlay operation +######################################## +# Offline the database, which we'd have to do anyway if we were restoring a full backup +$Query = "ALTER DATABASE $DbName SET OFFLINE WITH ROLLBACK IMMEDIATE" +Invoke-DbaQuery -SqlInstance $SqlInstance -Database master -Query $Query + + + +# Offline the volume(s) in Windows +Foreach ($TargetDisk in $TargetDisks) { + Invoke-Command -Session $SqlServerSession -ScriptBlock { Get-Disk | Where-Object { $_.SerialNumber -eq $using:TargetDisk } | Set-Disk -IsOffline $True } +} + + + +# Remove the original VMDK(s), within the original datastore +Foreach ($SourceVMDKPath in $SourceVMDKPaths) { + $harddisk = Get-HardDisk -VM $TargetSQLServerVM | ? { $_.FileName -match $SourceVMDKPath } + Remove-HardDisk -HardDisk $harddisk -Confirm:$false -DeletePermanently +} + + + +# Attach the new VMDK(s) from the newly cloned datastore back to the target VM +Foreach ($SourceVMDKPath in $SourceVMDKPaths) { + $newlyAttachedDisk = New-HardDisk -VM $TargetSQLServerVM -DiskPath "[$($clonedDatastore.Name)] $SourceVMDKPath" +} + + + +# Online the volume(s) in Windows +Foreach ($TargetDisk in $TargetDisks) { + Invoke-Command -Session $SqlServerSession -ScriptBlock { Get-Disk | Where-Object { $_.SerialNumber -eq $using:TargetDisk } | Set-Disk -IsOffline $False } +} + + + +# Restore the database with no recovery, which means we can restore LOG native SQL Server backups +$Query = "RESTORE DATABASE $DbName FROM DISK = '$BackupFile' WITH METADATA_ONLY, REPLACE, NORECOVERY" +Invoke-DbaQuery -SqlInstance $SqlInstance -Database master -Query $Query -Verbose + + + +# Let's check the current state of the database...its RESTORING +Get-DbaDbState -SqlInstance $SqlInstance -Database $DbName + + + +# Restore the log backup. +Restore-DbaDatabase -SqlInstance $SqlInstance -Database $DbName -Path $LogBackup.BackupPath -NoRecovery -Continue + + + +# Online the database +$Query = "RESTORE DATABASE $DbName WITH RECOVERY" +Invoke-DbaQuery -SqlInstance $SqlInstance -Database master -Query $Query + + + +# Verify Restore +Invoke-DbaQuery -SqlInstance $SqlInstance -Database $DbName -Query "SELECT TOP 10 * FROM dbo.Recipes" + + + +######################### +# Begin Clean Up Steps +######################### +$destinationDatastore = Get-Datastore -Name $SourceDatastoreName + + + +# Perform Storage vMotion to move the new VMDK disk(s) to the original source datastore. Should be fast +# thanks to XCOPY +Foreach ($SourceVMDKPath in $SourceVMDKPaths) { + $newlyAttachedDisk = Get-HardDisk -VM $TargetSQLServerVM | ? { $_.FileName -match $SourceVMDKPath } + Move-HardDisk -HardDisk $newlyAttachedDisk -Datastore $destinationDatastore -Confirm:$false +} + + + +# Now that the VMDKs have been moved back to the primary datastore, we can remove the temporary cloned +# datastore - this can take a min or two. +# First, removing from VCenter +Remove-Datastore -Datastore $clonedDatastore -VMHost $VMESXiHost -Confirm:$false + + + +# On FlashArray, disconnect the cloned volume from the ESXi cluster +Remove-Pfa2Connection -Array $FlashArray -HostGroupName $FAHostGroupName -VolumeName $NewClonedVolumeName + + + +# On FlashArray, destroy the cloned volume +Remove-Pfa2Volume -Array $FlashArray -Name $NewClonedVolumeName + + + +# Clean up +Remove-PSSession $SqlServerSession + + + diff --git a/demos-sdk2/Point in Time Recovery - VMFS/README.md b/demos-sdk2/Point in Time Recovery - VMFS/README.md new file mode 100644 index 0000000..ecbc7d2 --- /dev/null +++ b/demos-sdk2/Point in Time Recovery - VMFS/README.md @@ -0,0 +1,40 @@ +# Point In Time Recovery - Using SQL Server 2022's T-SQL Snapshot Backup feature w. VMFS/VMDK datastore/files. + + + +
+ + +# Scenario: +Perform a point in time restore using SQL Server 2022's T-SQL Snapshot Backup feature. This uses a FlashArray snapshot as the base of the restore, then restores a log backup. + +# IMPORTANT NOTE: +This example script is built for 1 database spanned across two VMDK files/volumes from a single datastore. + +The granularity or unit of work for this workflow is a VMDK file(s) and the entirety of its contents. Therefore, everything in the VMDK file(s) including files for other databases will be impacted/overwritten. + +This example will need to be adapted if you wish to support multiple databases on the same set of VMDK(s). + +# Prerequisites: +1. PowerShell Modules: dbatools & PureStoragePowerShellSDK2 + +# Usage Notes: +Each section of the script is meant to be run one after the other. The script is not meant to be executed all at once. + + +
+ + +# Disclaimer: +This example script is provided AS-IS and is meant to be a building block to be adapted to fit an individual organization's infrastructure. +

+_PLEASE_ do not save your passwords in cleartext here. +Use NTFS secured, encrypted files or whatever else -- never cleartext! +

+We encourage the modification and expansion of these scripts by the community. Although not necessary, please issue a Pull Request (PR) if you wish to request merging your modified code in to this repository. + + +
+ + +_The contents of the repository are intended as examples only and should be modified to work in your individual environments. No script examples should be used in a production environment without fully testing them in a development or lab environment. There are no expressed or implied warranties or liability for the use of these example scripts and templates presented by Pure Storage and/or their creators._ From a6898ef76ab93598a7443b5dcdae90da3ac8aae6 Mon Sep 17 00:00:00 2001 From: Andy Yun Date: Mon, 22 Dec 2025 11:24:09 -0500 Subject: [PATCH 11/19] VMFS-VMDK Snapshot Update Moved old PS1 script example to demos-archive\VMFS-VMDK Snapshot. Replaced with new VMFS script example that leverages storage vMotion. --- .../README - VMFS-VMDK Snapshot - V1.md | 46 +++ .../VMFS-VMDK Snapshot - V1.ps1 | 171 +++++++++++ demos-sdk2/VMFS-VMDK Snapshot/README.md | 61 ++-- .../VMFS-VMDK Snapshot/VMFS-VMDK Snapshot.ps1 | 272 +++++++++++++----- 4 files changed, 439 insertions(+), 111 deletions(-) create mode 100644 demos-archive/VMFS-VMDK Snapshot/README - VMFS-VMDK Snapshot - V1.md create mode 100644 demos-archive/VMFS-VMDK Snapshot/VMFS-VMDK Snapshot - V1.ps1 diff --git a/demos-archive/VMFS-VMDK Snapshot/README - VMFS-VMDK Snapshot - V1.md b/demos-archive/VMFS-VMDK Snapshot/README - VMFS-VMDK Snapshot - V1.md new file mode 100644 index 0000000..c80bd67 --- /dev/null +++ b/demos-archive/VMFS-VMDK Snapshot/README - VMFS-VMDK Snapshot - V1.md @@ -0,0 +1,46 @@ +**VMFS/VMDK + SQL Server Snapshot Scripts** +

+This folder contains VMFS/VMDK + SQL Server example snapshot scripts. + +**Files:** +- VMFS-VMDK Snapshot.ps1 + + +
+ + +**Scenario:** +
This example script shows steps to snapshot a VMFS datastore that contains data & log VMDKs for a SQL Server. The overall scenario is taking a snapshot of a production SQL Server's underlying datastore, and cloning the datastore to then overlay a pre-existing non-production datastore for a non-production SQL Server. + +All references to a "target" refer to the non-production side (VM, datastore, etc). + +**Prerequisites:** +1. The production datastore must already be cloned and presented once, to the non-production side. +2. This script assumes the database(s) are already attached on the target, non-production SQL Server. + +**Important Usage Notes:** +
You must pre-setup the target VM with a cloned datastore from the source already. You will ONLY be utilizing the specific VMDK(s) that contain the data/log files of interest, from the cloned datastore. Also note that the VMFS datastore does not need to only exclusively contain VMDKs for the SQL Server in question. If other VMDKs are present in the datastore, used by the either the source SQL Server VM or other VMs, they do not need to be deleted or otherwise manipulated during this cloning process. Remember FlashArray deduplicates data, thus a clone's set of additional, unused VMDKs will not have a negative impact. + +For the cloned datastore pre-setup, you can use subsets of the code below to clone the source datastore, present it to the target server, then attach the VMDK(s) containing the production databases that will be re-cloned with this script. Once "staged," you can then use this script fully to refresh the data files in the cloned datastore that is attached to the target server. + +When cloning, note that the target datastore is dropped and replaced entirely. This is because when cloning a datastore, it must be resignatured and the datastore will be renamed with a non-deterministic naming scheme (snap-[[GUID chars]]-[[original DS name]]). Thus it is not possible to know what the new datastore name will be until the resignature step is executed. + +This script also assumes that all database files (data and log) are on the same volume/single VMDK. If multiple volumes/VMDKs are being used, you will have to adjust the code (ex: add additional foreach loops for manipulating multiple VMDKs). + + +
+ + +**Disclaimer:** +
+This example script is provided AS-IS and meant to be a building block to be adapted to fit an individual organization's infrastructure. +
+
+ +We encourage the modification and expansion of these scripts by the community. Although not necessary, please issue a Pull Request (PR) if you wish to request merging your modified code in to this repository. + + +
+ + +_The contents of the repository are intended as examples only and should be modified to work in your individual environments. No script examples should be used in a production environment without fully testing them in a development or lab environment. There are no expressed or implied warranties or liability for the use of these example scripts and templates presented by Pure Storage and/or their creators._ diff --git a/demos-archive/VMFS-VMDK Snapshot/VMFS-VMDK Snapshot - V1.ps1 b/demos-archive/VMFS-VMDK Snapshot/VMFS-VMDK Snapshot - V1.ps1 new file mode 100644 index 0000000..882c09d --- /dev/null +++ b/demos-archive/VMFS-VMDK Snapshot/VMFS-VMDK Snapshot - V1.ps1 @@ -0,0 +1,171 @@ +############################################################################################################################## +# Refresh VMFS VMDK with Snapshot Demo +# +# +# Scenario: +# Snapshot and clone a "production" VMDK in a VMFS datastore, then present it to a "non-production" server. +# +# This example has two databases: ExampleDb1, ExampleDb2, whose data and log files both reside on a single disk/VMDK. +# +# +# Usage Notes: +# +# You must pre-setup the target VM with a cloned datastore from the source already. You will ONLY be utilizing +# the SPECIFIC VMDK(s) that contain the data/log files of interest, from the cloned datastore. Other VMDKs can safely be +# ignored since they are deduped on FlashArray. +# +# For the cloned datastore pre-setup, you can use subsets of the code below to clone the source datastore, present it to +# the target server, then attach the VMDK(s) containing the production databases that will be re-cloned with this script. +# Once "staged," you can then use this script fully to refresh the data files in the cloned datastore that is attached +# to the target server. +# +# This script also assumes that all database files (data and log) are on the same volume/single VMDK. If multiple +# volumes/VMDKs are being used, adjust the code to add additional foreach loops when manipulating the VMDKs. +# +# 2025/12/22: AYun - Renamed to "VMFS-VMDK Snapshot - V1.ps1" and migrated to archive in +# PureStorage-OpenConnect\sqlserver-scripts\demos-archive\VMFS-VMDK Snapshot +# +# Disclaimer: +# This example script is provided AS-IS and meant to be a building block to be adapted to fit an individual +# organization's infrastructure. +############################################################################################################################## + + + +# Import powershell modules +Import-Module PureStoragePowerShellSDK2 +Import-Module VMware.VimAutomation.Core +Import-Module SqlServer + + + +# Declare variables +$TargetVM = 'SqlServer1' # Name of target VM +$Databases = @('ExampleDb1','ExampleDb2') # Array of database names +$TargetDiskSerialNumber = '6000c02022cb876dcd321example01b' # Target Disk Serial Number +$VIServerName = 'vcenter.example.com' # vCenter FQDN +$ClusterName = 'WorkloadCluster1' # VMware Cluster +$SourceDatastoreName = 'vmware_sql_datastore' # VMware datastore name +$SourceVMDKPath = 'SqlServer1_1/SqlServer1.vmdk' # VMDK path inside the VMFS datastore +$ArrayName = 'flasharray1.example.com' # FlashArray FQDN +$SourceVolumeName = 'sql_volume_1' # Source volume name on FlashArray (may be same as your datastore name) +$TargetVolumeName = 'sql_volume_2' # Target volume name on FlashArray (may be same as your datastore name) + + + +# Set Credential - this assumes the same credential for the target VM and vCenter +$Credential = Get-Credential + + + +# Create a Powershell session against the target VM +$TargetVMSession = New-PSSession -ComputerName $TargetVM -Credential $Credential + + + +# Connect to vCenter +$VIServer = Connect-VIServer -Server $VIServerName -Protocol https -Credential $Credential + + + +# Offline the target database(s) by looping through $Databases array +foreach ($Database in $Databases) { + $Query = "ALTER DATABASE [$Database] SET OFFLINE WITH ROLLBACK IMMEDIATE" + Invoke-Sqlcmd -ServerInstance $TargetVM -Database master -Query $Query +} + + + +# Offline the volumes that have SQL data +Invoke-Command -Session $TargetVMSession -ScriptBlock { Get-Disk | Where-Object { $_.SerialNumber -eq $using:TargetDiskSerialNumber } | Set-Disk -IsOffline $True } + + + +# Prepare to remove the VMDK from the VM +$VM = Get-VM -Server $VIServer -Name $TargetVM +$HardDisk = Get-HardDisk -VM $VM | Where-Object { $_.FileName -match $SourceVMDKPath } + + + +# Remove the VMDK from the VM +Remove-HardDisk -HardDisk $HardDisk -Confirm:$false + + + +# Prepare to remove the stale datastore +$DataStore = $HardDisk.Filename.Substring(1, ($HardDisk.Filename.LastIndexOf(']') - 1)) +$Hosts = Get-Cluster $ClusterName | Get-VMHost | Where-Object { ($_.ConnectionState -eq 'Connected') } + + + +# Guest hard disk removed, now remove the stale datastore - this can take a min or two +Get-Datastore $DataStore | Remove-Datastore -VMHost $Hosts[0] -Confirm:$False + + + +# Connect to the array, authenticate. Remember disclaimer at the top! +$FlashArray = Connect-Pfa2Array -Endpoint $ArrayName -Credential ($Credential) -IgnoreCertificateError + + + +# Perform the volume overwrite (no intermediate snapshot needed!) +New-Pfa2Volume -Array $FlashArray -Name $TargetVolumeName -SourceName $SourceVolumeName -Overwrite $True + + + +# Rescan storage on each ESX host in the $Hosts array +foreach ($VmHost in $Hosts) { + Get-VMHostStorage -RescanAllHba -RescanVmfs -VMHost $VmHost | Out-Null +} + + + +# Connect to EsxCli +$esxcli = Get-EsxCli -VMHost $Hosts[0] + + + +# Resignature the cloned datastore +$EsxCli.Storage.Vmfs.Snapshot.Resignature($SourceDatastoreName) + + + +# Find the assigned datastore name, this may take a few seconds +# NOTE: when a datastore comes back, it's name will be "snap-[GUID chars]-[original DS name]" +# This is why the wildcard match below is needed. +$DataStore = (Get-Datastore | Where-Object { $_.Name -match 'snap' -and $_.Name -match $SourceDatastoreName }) + + + +# Rescan storage again to make sure all hosts can see the new datastore +foreach ($VmHost in $Hosts) { + Get-VMHostStorage -RescanAllHba -RescanVmfs -VMHost $VmHost | Out-Null +} + + + +# Attach the VMDK from the newly cloned datastore back to the target VM +New-HardDisk -VM $VM -DiskPath "[$DataStore] $SourceVMDKPath" + + + +# Online the volume on the target VM +Invoke-Command -Session $TargetVMSession -ScriptBlock { Get-Disk | Where-Object { $_.SerialNumber -eq $using:TargetDiskSerialNumber } | Set-Disk -IsOffline $False } + + + +# Volume might be read-only, ensure it's read/write +Invoke-Command -Session $TargetVMSession -ScriptBlock { Get-Disk | Where-Object { $_.SerialNumber -eq $using:TargetDiskSerialNumber } | Set-Disk -IsReadOnly $False } + + + +# Online the target database(s) by looping through $Databases array +foreach ($Database in $Databases) { + $Query = "ALTER DATABASE [$Database] SET ONLINE WITH ROLLBACK IMMEDIATE" + Invoke-Sqlcmd -ServerInstance $TargetVM -Database master -Query $Query +} + + + +# Remove powershell session +Remove-PSSession $TargetVMSession diff --git a/demos-sdk2/VMFS-VMDK Snapshot/README.md b/demos-sdk2/VMFS-VMDK Snapshot/README.md index e67b0dd..c66f32d 100644 --- a/demos-sdk2/VMFS-VMDK Snapshot/README.md +++ b/demos-sdk2/VMFS-VMDK Snapshot/README.md @@ -1,46 +1,39 @@ -**VMFS/VMDK + SQL Server Snapshot Scripts** -

-This folder contains VMFS/VMDK + SQL Server example snapshot scripts. - -**Files:** -- VMFS-VMDK Snapshot.ps1 - +# Refresh VMFS VMDK(s) with Snapshot Demo
-**Scenario:** -
This example script shows steps to snapshot a VMFS datastore that contains data & log VMDKs for a SQL Server. The overall scenario is taking a snapshot of a production SQL Server's underlying datastore, and cloning the datastore to then overlay a pre-existing non-production datastore for a non-production SQL Server. - -All references to a "target" refer to the non-production side (VM, datastore, etc). - -**Prerequisites:** -1. The production datastore must already be cloned and presented once, to the non-production side. -2. This script assumes the database(s) are already attached on the target, non-production SQL Server. - -**Important Usage Notes:** -
You must pre-setup the target VM with a cloned datastore from the source already. You will ONLY be utilizing the specific VMDK(s) that contain the data/log files of interest, from the cloned datastore. Also note that the VMFS datastore does not need to only exclusively contain VMDKs for the SQL Server in question. If other VMDKs are present in the datastore, used by the either the source SQL Server VM or other VMs, they do not need to be deleted or otherwise manipulated during this cloning process. Remember FlashArray deduplicates data, thus a clone's set of additional, unused VMDKs will not have a negative impact. - -For the cloned datastore pre-setup, you can use subsets of the code below to clone the source datastore, present it to the target server, then attach the VMDK(s) containing the production databases that will be re-cloned with this script. Once "staged," you can then use this script fully to refresh the data files in the cloned datastore that is attached to the target server. - -When cloning, note that the target datastore is dropped and replaced entirely. This is because when cloning a datastore, it must be resignatured and the datastore will be renamed with a non-deterministic naming scheme (snap-[[GUID chars]]-[[original DS name]]). Thus it is not possible to know what the new datastore name will be until the resignature step is executed. - -This script also assumes that all database files (data and log) are on the same volume/single VMDK. If multiple volumes/VMDKs are being used, you will have to adjust the code (ex: add additional foreach loops for manipulating multiple VMDKs). +# Scenario: +Production SQL Server & database(s) reside on a VMFS datastore. Non-production SQL Server resides on +a different VMFS datastore. User database(s) data and log files reside on two different VMDK disks +in each datastore. +

+Each datastore also resides on a different FlashArray, to demonstrate use of async snapshot replication. +

+This example is for a repeatable refresh scenario, such as a nightly refresh of a production database on +another non-production SQL Server. +

+This example's workflow takes an on-demand snapshot of the Production datastore and async replicates it to +the second FlashArray. Then the snapshot is cloned as a new temporary volume/datastore. The VMDKs with the +production database files, residing on the temporary cloned datastore are attached to the target SQL Server, +replacing the prior VMDKs that stored the database files previously. Finally Storage vMotion is used to +migrate the VMDKs to the non-production datastore, then the temporary cloned datastore is discarded. +

+This workflow is intended to only be impact select Windows Disks/VMDKs that contain user databases. + +# Disclaimer: +This example script is provided AS-IS and is meant to be a building block to be adapted to fit an individual organization's infrastructure. +

+_PLEASE_ do not save your passwords in cleartext here. +Use NTFS secured, encrypted files or whatever else -- never cleartext! +

+We encourage the modification and expansion of these scripts by the community. Although not necessary, please issue a Pull Request (PR) if you wish to request merging your modified code in to this repository.
-**Disclaimer:** -
-This example script is provided AS-IS and meant to be a building block to be adapted to fit an individual organization's infrastructure. -
-
+_The contents of the repository are intended as examples only and should be modified to work in your individual environments. No script examples should be used in a production environment without fully testing them in a development or lab environment. There are no expressed or implied warranties or liability for the use of these example scripts and templates presented by Pure Storage and/or their creators._ -We encourage the modification and expansion of these scripts by the community. Although not necessary, please issue a Pull Request (PR) if you wish to request merging your modified code in to this repository. - -
- -_The contents of the repository are intended as examples only and should be modified to work in your individual environments. No script examples should be used in a production environment without fully testing them in a development or lab environment. There are no expressed or implied warranties or liability for the use of these example scripts and templates presented by Pure Storage and/or their creators._ diff --git a/demos-sdk2/VMFS-VMDK Snapshot/VMFS-VMDK Snapshot.ps1 b/demos-sdk2/VMFS-VMDK Snapshot/VMFS-VMDK Snapshot.ps1 index a1b05c7..797491b 100644 --- a/demos-sdk2/VMFS-VMDK Snapshot/VMFS-VMDK Snapshot.ps1 +++ b/demos-sdk2/VMFS-VMDK Snapshot/VMFS-VMDK Snapshot.ps1 @@ -1,57 +1,91 @@ ############################################################################################################################## -# Refresh VMFS VMDK with Snapshot Demo +# Refresh VMFS VMDK(s) with Snapshot Demo # -# -# Scenario: -# Snapshot and clone a "production" VMDK in a VMFS datastore, then present it to a "non-production" server. +# Example Scenario: +# Production SQL Server & database(s) reside on a VMFS datastore. Non-production SQL Server resides on +# a different VMFS datastore. User database(s) data and log files reside on two different VMDK disks +# in each datastore. # -# This example has two databases: ExampleDb1, ExampleDb2, whose data and log files both reside on a single disk/VMDK. -# -# -# Usage Notes: -# -# You must pre-setup the target VM with a cloned datastore from the source already. You will ONLY be utilizing -# the SPECIFIC VMDK(s) that contain the data/log files of interest, from the cloned datastore. Other VMDKs can safely be -# ignored since they are deduped on FlashArray. -# -# For the cloned datastore pre-setup, you can use subsets of the code below to clone the source datastore, present it to -# the target server, then attach the VMDK(s) containing the production databases that will be re-cloned with this script. -# Once "staged," you can then use this script fully to refresh the data files in the cloned datastore that is attached -# to the target server. +# Each datastore also resides on a different FlashArray, to demonstrate use of async snapshot replication. # -# This script also assumes that all database files (data and log) are on the same volume/single VMDK. If multiple -# volumes/VMDKs are being used, adjust the code to add additional foreach loops when manipulating the VMDKs. +# This example is for a repeatable refresh scenario, such as a nightly refresh of a production database on +# another non-production SQL Server. # +# This example's workflow takes an on-demand snapshot of the Production datastore and async replicates it to +# the second FlashArray. Then the snapshot is cloned as a new temporary volume/datastore. The VMDKs with the +# production database files, residing on the temporary cloned datastore are attached to the target SQL Server, +# replacing the prior VMDKs that stored the database files previously. Finally Storage vMotion is used to +# migrate the VMDKs to the non-production datastore, then the temporary cloned datastore is discarded. +# +# This workflow is intended to only be impact select Windows Disks/VMDKs that contain user databases. +# # Disclaimer: # This example script is provided AS-IS and meant to be a building block to be adapted to fit an individual # organization's infrastructure. +# +# _PLEASE_ do not save your passwords in cleartext here. +# Use NTFS secured, encrypted files or whatever else -- never cleartext! +# ############################################################################################################################## # Import powershell modules -Import-Module PureStoragePowerShellSDK2 Import-Module VMware.VimAutomation.Core -Import-Module SqlServer +Import-Module PureStoragePowerShellSDK2 + + + +# Declare all variables +# VMware variables +$VIServerName = 'vcenter.example.com' +$ClusterName = 'WorkloadCluster1' +$SourceDatastoreName = 'source_sql_datastore' +$TargetDatastoreName = 'target_sql_datastore' +$SourceVMDKPaths = @('source_vm/sqldata.vmdk','source_vm/sqllog.vmdk') +$TargetVMDKPaths = @('target_vm/sqldata.vmdk','target_vm/sqllog.vmdk') + +# FlashArray variables +$SourceArrayName = 'flasharray1.example.com' # FlashArray FQDN +$SourceArrayShortName = 'flasharray1' +$TargetArrayName = 'flasharray2.example.com' # FlashArray FQDN +$FAHostGroupName = 'FAHostGroupName' # HostGroup Name on FlashArray for the ESXi cluster +$SourceVolumeName = 'volume_name' +$SourceProtectionGroup = 'protection_group' +$TargetVolumeName = 'target_volume_name' +$TargetProtectionGroup = "$($SourceArrayShortName):$($SourceProtectionGroup)" # [source array name (not FQDN)]:[source protection group name] + +# Windows/SQL Server variables +$SourceVM = 'source_vm' # Not FQDN +$TargetVM = 'target_vm' # Not FQDN +$Databases = @('AdventureWorks','WideWorldImporters') +$TargetDevices = @('1234c29689bc0888d32dcd2919a67z89', '1234c299721c4ba4a937552fb298a76') # The serial numbers of the Windows volume containing database files; use get-disk + +# Get Credentials - this demo example assumes the same credential for the target VM and vCenter +$Credential = Get-Credential -UserName "$env:USERNAME" -Message 'Enter your credential information...' -# Declare variables -$TargetVM = 'SqlServer1' # Name of target VM -$Databases = @('ExampleDb1','ExampleDb2') # Array of database names -$TargetDiskSerialNumber = '6000c02022cb876dcd321example01b' # Target Disk Serial Number -$VIServerName = 'vcenter.example.com' # vCenter FQDN -$ClusterName = 'WorkloadCluster1' # VMware Cluster -$SourceDatastoreName = 'vmware_sql_datastore' # VMware datastore name -$SourceVMDKPath = 'SqlServer1_1/SqlServer1.vmdk' # VMDK path inside the VMFS datastore -$ArrayName = 'flasharray1.example.com' # FlashArray FQDN -$SourceVolumeName = 'sql_volume_1' # Source volume name on FlashArray (may be same as your datastore name) -$TargetVolumeName = 'sql_volume_2' # Target volume name on FlashArray (may be same as your datastore name) +# Connect to the source array +$FlashArray = Connect-Pfa2Array -Endpoint $SourceArrayName -Credential ($Credential) -IgnoreCertificateError -# Set Credential - this assumes the same credential for the target VM and vCenter -$Credential = Get-Credential + + +# Create an on-demand Protection Group snapshot +# NOTE: +# This example uses async replication to generate a snapshot on $SourceArrayName and +# replicate it to $TargetArrayName. Remove -Replication flag if snapshots are only +# being used on local array. +# Alternatively, you may substitute other code to select an existing snapshot here +$MostRecentSnapshot = New-Pfa2ProtectionGroupSnapshot -Array $FlashArray -SourceNames $SourceProtectionGroup -ApplyRetention $true -ReplicateNow $true +$MostRecentSnapshot + + + +### +# Prepare for snapshot overlay @@ -60,109 +94,193 @@ $TargetVMSession = New-PSSession -ComputerName $TargetVM -Credential $Credential +# Import the SQLPS module so SQL commands are available +Import-Module SQLPS -PSSession $TargetVMSession -DisableNameChecking + + + # Connect to vCenter $VIServer = Connect-VIServer -Server $VIServerName -Protocol https -Credential $Credential +$TargetSQLServerVM = Get-VM -Server $VIServer -Name $TargetVM +$VMESXiHost = Get-VMhost -VM $TargetSQLServerVM -# Offline the target database(s) by looping through $Databases array -foreach ($Database in $Databases) { - $Query = "ALTER DATABASE [$Database] SET OFFLINE WITH ROLLBACK IMMEDIATE" - Invoke-Sqlcmd -ServerInstance $TargetVM -Database master -Query $Query -} +# Get discrete hosts connected to the ESXi cluster +$Hosts = Get-Cluster $ClusterName | Get-VMHost | where-object { ($_.ConnectionState -eq 'Connected') } -# Offline the volumes that have SQL data -Invoke-Command -Session $TargetVMSession -ScriptBlock { Get-Disk | Where-Object { $_.SerialNumber -eq $using:TargetDiskSerialNumber } | Set-Disk -IsOffline $True } +# Connect to the target array, authenticate. Remember disclaimer at the top! +$FlashArray = Connect-Pfa2Array -Endpoint $TargetArrayName -Credential ($Credential) -IgnoreCertificateError -# Prepare to remove the VMDK from the VM -$VM = Get-VM -Server $VIServer -Name $TargetVM -$HardDisk = Get-HardDisk -VM $VM | Where-Object { $_.FileName -match $SourceVMDKPath } +# Get the most recent snapshot +# NOTE: +# This next segment may be simplified if async snapshot replication is not being used. +# Alternatively, substitute code to take a new protection group snapshot or use +# one created prior. +$MostRecentSnapshots = Get-Pfa2ProtectionGroupSnapshot -Array $FlashArray -Name $TargetProtectionGroup | Sort-Object created -Descending | Select-Object -Property name -First 5 -# Remove the VMDK from the VM -Remove-HardDisk -HardDisk $HardDisk -Confirm:$false +# Check that the last snapshot has been fully replicated +$FirstSnapStatus = Get-Pfa2ProtectionGroupSnapshotTransfer -Array $FlashArray -Name $MostRecentSnapshots[0].name +if ($FirstSnapStatus.completed -ne $null) { # If $FirstSnapStatus.completed, then it hasn't been fully replicated + $MostRecentSnapshot = $MostRecentSnapshots[0].name +} +else { + # Use prior snapshot instead + $MostRecentSnapshot = $MostRecentSnapshots[1].name +} -# Prepare to remove the stale datastore -$DataStore = $HardDisk.Filename.Substring(1, ($HardDisk.Filename.LastIndexOf(']') - 1)) -$Hosts = Get-Cluster $ClusterName | Get-VMHost | Where-Object { ($_.ConnectionState -eq 'Connected') } +# Create a new volume from the selected snapshot of the source +$SnapshotSuffix = (Get-Date).ToString("yyyyMMdd-HHmmss") +$NewClonedVolumeName = "$($SourceVolumeName)-repl-clone-$($SnapshotSuffix)" +$ReplicatedSourceVolumeName = "$($MostRecentSnapshot).$($SourceVolumeName)" +New-Pfa2Volume -Array $FlashArray -Name $NewClonedVolumeName -SourceName $ReplicatedSourceVolumeName -Overwrite $true -# Guest hard disk removed, now remove the stale datastore - this can take a min or two -Get-Datastore $DataStore | Remove-Datastore -VMHost $Hosts[0] -Confirm:$False + + +# Present the new volume to the ESXi host group. +New-Pfa2Connection -Array $FlashArray -HostGroupName $FAHostGroupName -VolumeName $NewClonedVolumeName + + + +# ESXi host(s) must now rescan storage +Get-VMHostStorage -RescanAllHba -RescanVmfs -VMHost $VMESXiHost -# Connect to the array, authenticate. Remember disclaimer at the top! -$FlashArray = Connect-Pfa2Array -Endpoint $ArrayName -Credential ($Credential) -IgnoreCertificateError +# Connect to EsxCli +$esxcli = Get-EsxCli -VMHost $Hosts[0] + +### Diagnostic +# Retrieve a list of the snapshots that have been presented to the host (our cloned volume should be present) +# $snapInfo = $esxcli.storage.vmfs.snapshot.list() +# $snapInfo | where-object { ($_.VolumeName -match $SourceDatastoreName) } +# $snapInfo -# Perform the volume overwrite (no intermediate snapshot needed!) -New-Pfa2Volume -Array $FlashArray -Name $TargetVolumeName -SourceName $SourceVolumeName -Overwrite $True +# Resignature the cloned datastore +$esxcli.storage.vmfs.snapshot.resignature($SourceDatastoreName) + -# Rescan storage on each ESX host in the $Hosts array -foreach ($VmHost in $Hosts) { - Get-VMHostStorage -RescanAllHba -RescanVmfs -VMHost $VmHost | Out-Null + +# Find the newly resignatured datastore name +# NOTE: +# After a datastore is resignatured, its name will be "snap-[GUID chars]-[original DS name]" +# This is why the wildcard match below is needed. +$clonedDatastore = (Get-Datastore | ? { $_.name -match 'snap' -and $_.name -match $SourceDatastoreName }) + +while ($clonedDatastore -eq $null) { + # We may have to wait a little bit before the datastore is fully operational + Start-Sleep -Seconds 5 + $clonedDatastore = (Get-Datastore | Where-Object { $_.name -match 'snap' -and $_.name -match $SourceDatastoreName }) } +# $clonedDatastore -# Connect to EsxCli -$esxcli = Get-EsxCli -VMHost $Hosts[0] +# Must rescan storage again so ESXi hosts(s) can see the new cloned datastore +Get-VMHostStorage -RescanAllHba -RescanVmfs -VMHost $VMESXiHost -# Resignature the cloned datastore -$EsxCli.Storage.Vmfs.Snapshot.Resignature($SourceDatastoreName) +### +# Prep SQL & Windows for VMDK overlay + +# Offline the target database(s) in SQL Server by looping through $Databases array +Foreach ($database in $Databases) { + # Offline the database + $Query = "ALTER DATABASE " + $($database) + " SET OFFLINE WITH ROLLBACK IMMEDIATE" + Invoke-Command -Session $TargetVMSession -ScriptBlock {Param($querytask) Invoke-Sqlcmd -ServerInstance . -Database master -Query $querytask} -ArgumentList ($Query) +} -# Find the assigned datastore name, this may take a few seconds -# NOTE: when a datastore comes back, it's name will be "snap-[GUID chars]-[original DS name]" -# This is why the wildcard match below is needed. -$DataStore = (Get-Datastore | Where-Object { $_.Name -match 'snap' -and $_.Name -match $SourceDatastoreName }) +# Offline the volumes that have SQL data in Windows by looping through $TargetDevices array +Foreach ($targetdevice in $TargetDevices) { + Invoke-Command -Session $TargetVMSession -ScriptBlock {Param($currentdisk) Get-Disk | ? { $_.SerialNumber -eq $($currentdisk) } | Set-Disk -IsOffline $True } -ArgumentList ($targetdevice) +} -# Rescan storage again to make sure all hosts can see the new datastore -foreach ($VmHost in $Hosts) { - Get-VMHostStorage -RescanAllHba -RescanVmfs -VMHost $VmHost | Out-Null +# Remove the VMDK(s) with stale database files from the VM +Foreach ($TargetVMDKPath in $TargetVMDKPaths) { + $harddisk = Get-HardDisk -VM $TargetSQLServerVM | ? { $_.FileName -match $TargetVMDKPath } + Remove-HardDisk -HardDisk $harddisk -Confirm:$false -DeletePermanently } # Attach the VMDK from the newly cloned datastore back to the target VM -New-HardDisk -VM $VM -DiskPath "[$DataStore] $SourceVMDKPath" +Foreach ($SourceVMDKPath in $SourceVMDKPaths) { + $newlyAttachedDisk = New-HardDisk -VM $TargetSQLServerVM -DiskPath "[$($clonedDatastore.Name)] $SourceVMDKPath" +} -# Online the volume on the target VM -Invoke-Command -Session $TargetVMSession -ScriptBlock { Get-Disk | Where-Object { $_.SerialNumber -eq $using:TargetDiskSerialNumber } | Set-Disk -IsOffline $False } +# Online the volume(s) on the target VM by looping through $TargetDevices array +Foreach ($targetdevice in $TargetDevices) { + Invoke-Command -Session $TargetVMSession -ScriptBlock {Param($currentdisk) Get-Disk | Where-Object { $_.SerialNumber -eq $($currentdisk) } | Set-Disk -IsOffline $False } -ArgumentList ($targetdevice) +} # Volume might be read-only, ensure it's read/write -Invoke-Command -Session $TargetVMSession -ScriptBlock { Get-Disk | Where-Object { $_.SerialNumber -eq $using:TargetDiskSerialNumber } | Set-Disk -IsReadOnly $False } +Foreach ($targetdevice in $TargetDevices) { + Invoke-Command -Session $TargetVMSession -ScriptBlock {Param($currentdisk) Get-Disk | Where-Object { $_.SerialNumber -eq $($currentdisk) } | Set-Disk -IsReadOnly $False } -ArgumentList ($targetdevice) +} # Online the target database(s) by looping through $Databases array -foreach ($Database in $Databases) { - $Query = "ALTER DATABASE [$Database] SET ONLINE WITH ROLLBACK IMMEDIATE" - Invoke-Sqlcmd -ServerInstance $TargetVM -Database master -Query $Query +Foreach ($database in $databases) { + $Query = "ALTER DATABASE " + $($database) + " SET ONLINE WITH ROLLBACK IMMEDIATE" + Invoke-Command -Session $TargetVMSession -ScriptBlock {Param($querytask) Invoke-Sqlcmd -ServerInstance . -Database master -Query $querytask} -ArgumentList ($Query) +} + + + +### +# Databases should now be online and usable +# Start cleanup next + + + +# Perform Storage vMotion to move the new VMDK disk(s) to the original source datastore. +$destinationDatastore = Get-Datastore -Name $TargetDatastoreName + +Foreach ($SourceVMDKPath in $SourceVMDKPaths) { + $newlyAttachedDisk = Get-HardDisk -VM $TargetSQLServerVM | ? { $_.FileName -match $SourceVMDKPath } + Move-HardDisk -HardDisk $newlyAttachedDisk -Datastore $destinationDatastore -Confirm:$false } -# Remove powershell session +# Guest hard disk removed, now remove the stale datastore - this can take a min or two +Remove-Datastore -Datastore $clonedDatastore -VMHost $Hosts[0] -Confirm:$false + + + +# On FlashArray, disconnect the cloned volume from the ESXi cluster +Remove-Pfa2Connection -Array $FlashArray -HostGroupName $FAHostGroupName -VolumeName $NewClonedVolumeName + + + +# On FlashArray, destroy the cloned volume +Remove-Pfa2Volume -Array $FlashArray -Name $NewClonedVolumeName + + + +# Clean up Remove-PSSession $TargetVMSession From 30c3881ee0c8e49dccac618495ba0048b99ca1f6 Mon Sep 17 00:00:00 2001 From: Andy Yun Date: Wed, 7 Jan 2026 14:54:33 -0500 Subject: [PATCH 12/19] Update README Adjusted Scenario description --- demos-sdk2/VMFS-VMDK Snapshot/README.md | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/demos-sdk2/VMFS-VMDK Snapshot/README.md b/demos-sdk2/VMFS-VMDK Snapshot/README.md index c66f32d..9a459c5 100644 --- a/demos-sdk2/VMFS-VMDK Snapshot/README.md +++ b/demos-sdk2/VMFS-VMDK Snapshot/README.md @@ -4,22 +4,22 @@ # Scenario: -Production SQL Server & database(s) reside on a VMFS datastore. Non-production SQL Server resides on -a different VMFS datastore. User database(s) data and log files reside on two different VMDK disks -in each datastore. -

-Each datastore also resides on a different FlashArray, to demonstrate use of async snapshot replication. -

This example is for a repeatable refresh scenario, such as a nightly refresh of a production database on another non-production SQL Server.

+Production SQL Server & database(s) reside on a VMFS datastore. Non-production SQL Server resides on +a different VMFS datastore. User database(s) data and log files reside on two different VMDK disks +in each datastore. This workflow is intended to only be impact select Windows Disks/VMDKs that contain user +databases. Each datastore also resides on a different FlashArray, to demonstrate use of async snapshot +replication. +

This example's workflow takes an on-demand snapshot of the Production datastore and async replicates it to the second FlashArray. Then the snapshot is cloned as a new temporary volume/datastore. The VMDKs with the production database files, residing on the temporary cloned datastore are attached to the target SQL Server, replacing the prior VMDKs that stored the database files previously. Finally Storage vMotion is used to migrate the VMDKs to the non-production datastore, then the temporary cloned datastore is discarded.

-This workflow is intended to only be impact select Windows Disks/VMDKs that contain user databases. + # Disclaimer: This example script is provided AS-IS and is meant to be a building block to be adapted to fit an individual organization's infrastructure. @@ -37,3 +37,4 @@ _The contents of the repository are intended as examples only and should be modi + From 8b0381b4315393e1b4b1e8b7ec47b91ba50ecf50 Mon Sep 17 00:00:00 2001 From: Andy Yun Date: Wed, 7 Jan 2026 15:31:03 -0500 Subject: [PATCH 13/19] Refactor VMFS VMDK Snapshot PowerShell script Removed legacy @Hosts[] array usage --- .../VMFS-VMDK Snapshot/VMFS-VMDK Snapshot.ps1 | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/demos-sdk2/VMFS-VMDK Snapshot/VMFS-VMDK Snapshot.ps1 b/demos-sdk2/VMFS-VMDK Snapshot/VMFS-VMDK Snapshot.ps1 index 797491b..d312796 100644 --- a/demos-sdk2/VMFS-VMDK Snapshot/VMFS-VMDK Snapshot.ps1 +++ b/demos-sdk2/VMFS-VMDK Snapshot/VMFS-VMDK Snapshot.ps1 @@ -106,12 +106,6 @@ $VMESXiHost = Get-VMhost -VM $TargetSQLServerVM - -# Get discrete hosts connected to the ESXi cluster -$Hosts = Get-Cluster $ClusterName | Get-VMHost | where-object { ($_.ConnectionState -eq 'Connected') } - - - # Connect to the target array, authenticate. Remember disclaimer at the top! $FlashArray = Connect-Pfa2Array -Endpoint $TargetArrayName -Credential ($Credential) -IgnoreCertificateError @@ -158,7 +152,7 @@ Get-VMHostStorage -RescanAllHba -RescanVmfs -VMHost $VMESXiHost # Connect to EsxCli -$esxcli = Get-EsxCli -VMHost $Hosts[0] +$esxcli = Get-EsxCli -VMHost $VMESXiHost @@ -186,7 +180,7 @@ while ($clonedDatastore -eq $null) { Start-Sleep -Seconds 5 $clonedDatastore = (Get-Datastore | Where-Object { $_.name -match 'snap' -and $_.name -match $SourceDatastoreName }) } -# $clonedDatastore +$clonedDatastore @@ -268,7 +262,7 @@ Foreach ($SourceVMDKPath in $SourceVMDKPaths) { # Guest hard disk removed, now remove the stale datastore - this can take a min or two -Remove-Datastore -Datastore $clonedDatastore -VMHost $Hosts[0] -Confirm:$false +Remove-Datastore -Datastore $clonedDatastore -VMHost $VMESXiHost -Confirm:$false @@ -284,3 +278,4 @@ Remove-Pfa2Volume -Array $FlashArray -Name $NewClonedVolumeName # Clean up Remove-PSSession $TargetVMSession + From 558551963a27c39245bc32eae79d4abc40b2a08f Mon Sep 17 00:00:00 2001 From: Andy Yun Date: Thu, 8 Jan 2026 10:05:55 -0500 Subject: [PATCH 14/19] Refresh VMFS VMDK with Snapshot Demo script update Corrected if/else --- demos-sdk2/VMFS-VMDK Snapshot/VMFS-VMDK Snapshot.ps1 | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/demos-sdk2/VMFS-VMDK Snapshot/VMFS-VMDK Snapshot.ps1 b/demos-sdk2/VMFS-VMDK Snapshot/VMFS-VMDK Snapshot.ps1 index d312796..417db12 100644 --- a/demos-sdk2/VMFS-VMDK Snapshot/VMFS-VMDK Snapshot.ps1 +++ b/demos-sdk2/VMFS-VMDK Snapshot/VMFS-VMDK Snapshot.ps1 @@ -125,8 +125,7 @@ $FirstSnapStatus = Get-Pfa2ProtectionGroupSnapshotTransfer -Array $FlashArray -N if ($FirstSnapStatus.completed -ne $null) { # If $FirstSnapStatus.completed, then it hasn't been fully replicated $MostRecentSnapshot = $MostRecentSnapshots[0].name -} -else { +} else { # Use prior snapshot instead $MostRecentSnapshot = $MostRecentSnapshots[1].name } @@ -279,3 +278,4 @@ Remove-Pfa2Volume -Array $FlashArray -Name $NewClonedVolumeName # Clean up Remove-PSSession $TargetVMSession + From be62412ae90fd068a78ac32aff923ad2647a0178 Mon Sep 17 00:00:00 2001 From: "Anthony E. Nocentino" Date: Sat, 9 May 2026 15:21:41 +0000 Subject: [PATCH 15/19] updates for hyperv Co-authored-by: Copilot --- README.md | 8 + .../Hyper-V CSV w SQL Server Snapshot.ps1 | 226 -------------- .../HyperV CrashConsistentClone.ps1 | 225 ++++++++++++++ .../HyperV-TSQL-SnapshotBackup.ps1 | 276 ++++++++++++++++++ .../readme.md | 108 ++++--- 5 files changed, 582 insertions(+), 261 deletions(-) delete mode 100644 demos-sdk2/Hyper-V CSV w SQL Server Snapshot/Hyper-V CSV w SQL Server Snapshot.ps1 create mode 100644 demos-sdk2/Hyper-V CSV w SQL Server Snapshot/HyperV CrashConsistentClone.ps1 create mode 100644 demos-sdk2/Hyper-V CSV w SQL Server Snapshot/HyperV-TSQL-SnapshotBackup.ps1 diff --git a/README.md b/README.md index 86abb12..965cd7c 100644 --- a/README.md +++ b/README.md @@ -46,6 +46,14 @@ Array-based snapshots are used to decouple database operations from the size of | **Volume Database Refresh on VMDK Virtual Disks** | Refresh a database from Volume snapshot where all of the databases's files are using a VMware VMDK Virtual Disk type on a single VMFS Datastore on the same Volume on the same FlashArray. | [More Info](./demos-sdk2/VMFS-VMDK%20Snapshot/) | [Sample Code](./demos-sdk2/VMFS-VMDK%20Snapshot/VMFS-VMDK%20Snapshot.ps1) | +## Using Snapshots for Databases on Hyper-V Cluster Shared Volumes (CSV) + +| Demo | Description | | | +| ----------- | ----------- | ----------- | ----------- | +| **Hyper-V CSV Crash-Consistent Clone** | Refresh a dev/test SQL Server from a crash-consistent FlashArray snapshot of a production Hyper-V CSV, with no source downtime. | [More Info](./demos-sdk2/Hyper-V%20CSV%20w%20SQL%20Server%20Snapshot/) | [Sample Code](./demos-sdk2/Hyper-V%20CSV%20w%20SQL%20Server%20Snapshot/HyperV%20CrashConsistentClone.ps1) | +| **Hyper-V CSV T-SQL Snapshot Backup** | Combine a FlashArray volume snapshot with SQL Server 2022 T-SQL Snapshot Backup on a Hyper-V CSV for application-consistent snapshots and point-in-time recovery. | [More Info](./demos-sdk2/Hyper-V%20CSV%20w%20SQL%20Server%20Snapshot/) | [Sample Code](./demos-sdk2/Hyper-V%20CSV%20w%20SQL%20Server%20Snapshot/HyperV-TSQL-SnapshotBackup.ps1) | + + ## Using ActiveDR | Demo | Description | | | diff --git a/demos-sdk2/Hyper-V CSV w SQL Server Snapshot/Hyper-V CSV w SQL Server Snapshot.ps1 b/demos-sdk2/Hyper-V CSV w SQL Server Snapshot/Hyper-V CSV w SQL Server Snapshot.ps1 deleted file mode 100644 index d898ae8..0000000 --- a/demos-sdk2/Hyper-V CSV w SQL Server Snapshot/Hyper-V CSV w SQL Server Snapshot.ps1 +++ /dev/null @@ -1,226 +0,0 @@ -############################################################################################################################## -# Hyper-V Cluster Shared Volume (CSV) with SQL Server Snapshot Example -# -# Scenario: -# This script will clone a Hyper-V Cluster Shared Volume (CSV), using a crash consistent snapshot, and present it back -# to the originating Hyper-V cluster as a second CSV "copy." -# -# This example scenario is useful if you have isolated a VLDB SQL Server database exclusively onto this CSV -# -# See https://github.com/PureStorage-OpenConnect/sqlserver-scripts/tree/master/demos-sdk2/Hyper-V%20CSV%20Snapshot -# for more details -# -# -# Prerequisities: -# 1. An additional Windows server (referred to as a staging server). This staging server does not have to be a -# Hyper-V host. -# 2. A pre-created volume of equal size to the source CSV, pre-attached to the staging server. -# 3. The 'Failover Cluster Module for Windows PowerShell' Feature in Windows is required on the Hyper-V host. -# Add-WindowsFeature RSAT-Clustering-PowerShell -# -# -# Usage Notes: -# -# The staging server is needed because each CSV has a unique signature. If the CSV is presented back to the Hyper-V -# host unaltered, a signature collision will be detected and the new CSV will not be able to be used by Windows. -# Hyper-V is unable to resignature in this state either. Instead, the CSV must be presented to another machine (aka -# the staging server), resignatured there, then can be re-snapshotted and cloned back to the originating Hyper-V -# host. -# -# This script may be adjusted to clone and present the CSV snapshot to a different Hyper-V host. If this is done, then -# the staging server and resignature step is not required, since the new target Hyper-V host will not have two of the -# same CSV causing a signature conflict. -# -# -# Disclaimer: -# This example script is provided AS-IS and meant to be a building block to be adapted to fit an individual -# organization's infrastructure. -############################################################################################################################## -Import-Module PureStoragePowerShellSDK2 - - - -# Variables -$FlashArrayEndPoint = 'flasharray1.example.com' -$SourceVMCluster = 'hyperv-cluster-01.fsa.lab' -$SourceVMHost = 'hyperv-host-01.example.com' -$SourceVM = 'hyperv-vm-source' # No FQDN -$SourceVolumeName = 'hyperv-vm-source-csv-01' # Name of the volume in FlashArray -$StagingServer = 'windows-staging-server' -$StagingVolumeName = 'temporary-volume-for-csv-resignature' -$StagingDiskSerialNumber = '6000c2945ce069b03b9750d2afe72828' -$TargetVMHost = 'hyperv-host-02.example.com' # No FQDN -$TargetVM = 'hyperv-vm-target' # No FQDN -$TargetVolumeName = 'hyperv-vm-target-csv-01-cloned' -$TargetClusterDiskNumber = 'Cluster Disk 3' -$DatabaseName = 'MyDatabaseName' -$ClusteredStorageFolder = "C:\ClusterStorage\volume4\hv-sqldata-01\data\*.*" # Target Host Folder containing cloned VHDX/AVHDX files - - - -# Establish credential to use for all connections -$Credential = Get-Credential -Message 'Enter your Pure credentials' - - - -# Connect to the FlashArray -$FlashArray = Connect-Pfa2Array -Endpoint $FlashArrayEndPoint -Credential ($Credential) -IgnoreCertificateError - - - -# Determine which Hyper-V node each role currently resides on -$HyperVClusterSession = New-PSSession -ComputerName $SourceVMCluster -Credential $Credential - -$SourceClusterGroup = Invoke-Command -Session $HyperVClusterSession -ScriptBlock { Get-ClusterGroup -Name $Using:SourceVM } -$TargetClusterGroup = Invoke-Command -Session $HyperVClusterSession -ScriptBlock { Get-ClusterGroup -Name $Using:TargetVM } - -$SourceVMHost = $SourceClusterGroup.OwnerNode -$TargetVMHost = $TargetClusterGroup.OwnerNode - -# Verify -$SourceVM -$SourceVMHost - -$TargetVM -$TargetVMHost - - - -# Prepare the staging CSV for overlay -# Connect to staging VM -$StagingServerSession = New-PSSession -ComputerName $StagingServer -Credential $Credential - - - -# Offline the volume -# NOTE: use Get-Disk prior to get the correct Serial Number -Invoke-Command -Session $StagingServerSession -ScriptBlock { Get-Disk | Where-Object { $_.SerialNumber -eq $using:StagingDiskSerialNumber } | Set-Disk -IsOffline $True } - -# Verify -Invoke-Command -Session $StagingServerSession -ScriptBlock { Get-Disk | Where-Object { $_.SerialNumber -eq $using:StagingDiskSerialNumber }} - - - -# Snapshot the source CSV -# This example is for an on-demand snapshot. Can adjust code to also use a prior snapshot; ex. regularly scheduled -# snapshots or an asynchronously replicated snapshot from another FlashArray - -# Clone the source CSV to the staging CSV -New-Pfa2Volume -Array $FlashArray -Name $StagingVolumeName -SourceName $SourceVolumeName -Overwrite $true - - - -# Now must resignature the CSV on the staging VM -# Build DISKPART script commands for resignature -$StagingDisk = Invoke-Command -Session $StagingServerSession -ScriptBlock { Get-Disk | Where-Object { $_.SerialNumber -eq $Using:StagingDiskSerialNumber }} -$DiskNumber = $StagingDisk.Number -$NewUniqueID = [GUID]::NewGuid() -$Commands = "`"SELECT DISK $DiskNumber`"", - "`"UNIQUEID DISK ID=$NewUniqueID`"" -$ScriptBlock = [string]::Join(",",$Commands) -$DiskpartScriptBlock = $ExecutionContext.InvokeCommand.NewScriptBlock("$ScriptBlock | DISKPART") - -# Verify DISKPART command -$DiskpartScriptBlock - -# Issue resignature command -Invoke-Command -Session $StagingServerSession -ScriptBlock $DiskpartScriptBlock - - - -# Prepare target VM -$TargetVMSession = New-PSSession -ComputerName $TargetVM -Credential $Credential - -# Offline the database -$Query = "ALTER DATABASE $DatabaseName SET OFFLINE WITH ROLLBACK IMMEDIATE" -Invoke-Command -Session $TargetVMSession -ScriptBlock {Param($querytask) Invoke-Sqlcmd -ServerInstance . -Database master -Query $querytask} -ArgumentList ($Query) - -# Offline the volume -# Because this is a Hyper-V VM, volume serial numbers are not populated by Hyper-V into a virtual machine -# Therefore must use a different method identify the proper volume to offline - -# Confirm which drive you want -Invoke-Command -Session $TargetVMSession -ScriptBlock { Get-Disk | Format-Table } - - - -# Specify the drive number -$DiskNumber = 1 -Invoke-Command -Session $TargetVMSession -ScriptBlock { Get-Disk -Number $using:DiskNumber | Get-Disk | Set-Disk -IsOffline $True } - -# Verify offline -Invoke-Command -Session $TargetVMSession -ScriptBlock { Get-Disk | Format-Table } - - - -# Prepare target VM Host -$TargetVMHostSession = New-PSSession -ComputerName $TargetVMHost -Credential $Credential - -# Remove SQL Server cluster resource dependency on database volume -# Only use this if you are using Clustered Disks (NOT Clustered Shared Volumes) -# Invoke-Command -Session $TargetVMHostSession -ScriptBlock { Get-ClusterResource 'SQL Server' | Remove-ClusterResourceDependency $TargetVolumeName } - -# Stop the disk cluster resource -# NOTE: need to know which Cluster Disk Number first -# This will put the target Hyper-V VM into a Saved state -Invoke-Command -Session $TargetVMHostSession -ScriptBlock { Stop-ClusterResource $Using:TargetClusterDiskNumber } - -# Verify -Invoke-Command -Session $TargetVMHostSession -ScriptBlock { Get-ClusterSharedVolume $Using:TargetClusterDiskNumber } - - - -# Clone the staging CSV to the target CSV -New-Pfa2Volume -Array $FlashArray -Name $TargetVolumeName -SourceName $StagingVolumeName -Overwrite $true - - - -# Start the disk cluster resource -Invoke-Command -Session $TargetVMHostSession -ScriptBlock { Start-ClusterResource $Using:TargetClusterDiskNumber } - -# Verify -Invoke-Command -Session $TargetVMHostSession -ScriptBlock { Get-ClusterSharedVolume $Using:TargetClusterDiskNumber } - - - -# Must now update permissions in Windows to grant the new VM access to the VHDX files - -Invoke-Command -Session $TargetVMHostSession -ScriptBlock { - $VMID = "NT VIRTUAL MACHINE\" - Get-VM -name $Using:TargetVM | Select-Object -ExpandProperty VMID - - $fileAclList = Get-Acl $Using:ClusteredStorageFolder - Foreach ($acl in $fileAclList) { - # Add a new rule to grant full control to a user - $rule = New-Object System.Security.AccessControl.FileSystemAccessRule($VMID, "FullControl", "Allow") - $acl.AddAccessRule($rule) - Set-Acl -Path $acl.PSPath -AclObject $acl - } -} - - - -# Online the volume - -# Confirm which drive you want -Invoke-Command -Session $TargetVMSession -ScriptBlock { Get-Disk | Format-Table } - - - -# Specify the drive number -$DiskNumber = 1 -Invoke-Command -Session $TargetVMSession -ScriptBlock { Get-Disk -Number $using:DiskNumber | Get-Disk | Set-Disk -IsOffline $False } - -# Verify -Invoke-Command -Session $TargetVMSession -ScriptBlock { Get-Disk | Format-Table } - - - -# Online the database -$Query = "ALTER DATABASE $DatabaseName SET ONLINE" -Invoke-Command -Session $TargetVMSession -ScriptBlock {Param($querytask) Invoke-Sqlcmd -ServerInstance . -Database master -Query $querytask} -ArgumentList ($Query) - -# Verify -$Query = "SELECT @@SERVERNAME, name, state_desc, GETDATE() FROM sys.databases WHERE database_id = DB_ID('$DatabaseName')" -Invoke-Command -Session $TargetVMSession -ScriptBlock {Param($querytask) Invoke-Sqlcmd -ServerInstance . -Database master -Query $querytask} -ArgumentList ($Query) - diff --git a/demos-sdk2/Hyper-V CSV w SQL Server Snapshot/HyperV CrashConsistentClone.ps1 b/demos-sdk2/Hyper-V CSV w SQL Server Snapshot/HyperV CrashConsistentClone.ps1 new file mode 100644 index 0000000..eba7478 --- /dev/null +++ b/demos-sdk2/Hyper-V CSV w SQL Server Snapshot/HyperV CrashConsistentClone.ps1 @@ -0,0 +1,225 @@ +############################################################################################################################## +# Pure Storage FlashArray Snapshot Clone - Hyper-V Edition +# Crash-Consistent Snapshot Clone to a Dev/Test SQL Server +# +# Scenario: +# This script will clone a Hyper-V Cluster Shared Volume (CSV), using a crash-consistent snapshot, and present +# it to a second VM running a dev/test SQL Server instance. The source SQL Server continues running uninterrupted +# throughout the entire operation. The target SQL Server performs automatic crash recovery when the database is +# brought online. +# +# Storage topology: +# Source : hyperv-csv-01-DATA-PROD (CSV backing VM1 VHDXs) -> SQL 01 (Production) +# Target : hyperv-csv-01-DATA-DEV (CSV backing VM2 VHDXs) -> SQL 02 (Dev/Test) +# +# This example scenario is useful for refreshing a dev/test SQL Server database from a production FlashArray CSV +# without any downtime on the source. +# +# +# Prerequisites: +# +# FlashArray: +# 1. Source and target volumes must be pre-configured on the FlashArray. +# The target volume must be the same size as the source CSV volume. +# +# Hyper-V Cluster: +# 2. The Failover Cluster PowerShell module must be installed on the Hyper-V node(s). +# Add-WindowsFeature RSAT-Clustering-PowerShell +# 3. PowerShell Remoting (WinRM) must be enabled on all Hyper-V cluster nodes. +# 4. The target VM must have a SCSI controller — hot-add/remove of virtual disks requires SCSI (not IDE). +# +# Modules: +# 5. PureStoragePowerShellSDK2 must be installed on the machine running this script. +# Install-Module PureStoragePowerShellSDK2 +# 6. dbatools must be installed. +# Install-Module dbatools +# +# Credentials: +# 7. Saved credentials file: $HOME\FA_Cred.xml +# $FACred | Export-CliXml -Path "$HOME\FA_Cred.xml" +# 8. $DbName must already exist on the target SQL Server. +# +# +# Usage Notes: +# +# This is a crash-consistent snapshot — SQL Server write IO is NOT frozen before the snapshot is taken. +# The source database remains online and unaffected. The target SQL Server will perform automatic crash +# recovery (roll forward committed transactions, roll back uncommitted) when the database is brought online. +# +# +# Disclaimer: +# This example script is provided AS-IS and meant to be a building block to be adapted to fit an individual +# organization's infrastructure. +############################################################################################################################## + + + +# Import PowerShell modules +Import-Module dbatools +Import-Module PureStoragePowerShellSDK2 + + + +# Initialize variables -- edit these to match your environment +$SourceSQLServer = 'sql-01.example.com' # SQL Server hosting the source database +$TargetSQLServer = 'sql-02.example.com' # SQL Server that will receive the clone +$ArrayName = 'flasharray1.example.com' # FlashArray endpoint +$DbName = 'MyDatabaseName' # Database name + +# Hyper-V cluster details +$HVNode2 = 'hyperv-node-02.example.com' # Hyper-V cluster node that owns the target CSV +$TargetCSVResource = 'Cluster Disk 3' # Cluster resource name for the target CSV + +# FlashArray volume names +$SourceVolName = 'hyperv-csv-01-DATA-PROD' # FA volume backing the source CSV +$TargetVolName = 'hyperv-csv-01-DATA-DEV' # FA volume backing the target CSV + +# VHDX paths on the target CSV (mirrors the source CSV layout after clone) +$ClonedDataVhdx = 'C:\ClusterStorage\Volume3\hyperv-vm-01\hyperv-vm-01-Data.vhdx' +$ClonedLogVhdx = 'C:\ClusterStorage\Volume3\hyperv-vm-01\hyperv-vm-01-Log.vhdx' + +# Target VM name +$TargetVM = 'hyperv-vm-02' +$DataCtrlNum = 0; $DataCtrlLoc = 1 # Data VHDX at SCSI 0:1 +$LogCtrlNum = 0; $LogCtrlLoc = 2 # Log VHDX at SCSI 0:2 + + + +# Build connections +# PowerShell remoting session to the Hyper-V cluster node hosting the target CSV +$HVSession = New-PSSession -ComputerName $HVNode2 + +# SQL connections -- dbatools maintains a persistent SMO connection across cmdlet calls +$SqlInstance1 = Connect-DbaInstance -SqlInstance $SourceSQLServer -TrustServerCertificate -NonPooledConnection +$SqlInstance2 = Connect-DbaInstance -SqlInstance $TargetSQLServer -TrustServerCertificate -NonPooledConnection + +# Connect to the FlashArray's REST API +$FACred = Import-CliXml -Path "$HOME\FA_Cred.xml" +$FlashArray = Connect-Pfa2Array -EndPoint $ArrayName -Credential $FACred -IgnoreCertificateError + + + + +# Let's get some information about the source database on SQL 01; take note of the size +Get-DbaDatabase -SqlInstance $SqlInstance1 -Database $DbName | + Select-Object Name, SizeMB, Status + + + +############################################################################# +# Take a Crash-Consistent Snapshot +############################################################################# + +# Time the full operation from snapshot to database online on SQL 02 +$Start = (Get-Date) + + +# Take a crash-consistent volume snapshot -- SQL Server is running, no freeze +# This is equivalent to pulling the power cord and taking a picture of the disk. +# SQL 02 will perform automatic crash recovery when the database is attached. +$Snapshot = New-Pfa2VolumeSnapshot -Array $FlashArray -SourceName $SourceVolName +$Snapshot + + + +############################################################################# +# Prepare SQL 02 -- Offline Databases and Release File Handles +############################################################################# + +# Offline any user databases on SQL 02 so their file handles are released +$Query = "ALTER DATABASE [$DbName] SET OFFLINE WITH ROLLBACK IMMEDIATE" +Invoke-DbaQuery -SqlInstance $SqlInstance2 -Query $Query + + + +############################################################################# +# Prepare the Hyper-V Storage -- Detach, Clone, Reattach +############################################################################# + +# Detach the data and log VHDXs from the target VM at the hypervisor level +# The CSV must be offlined before overwriting the FlashArray volume, and the +# VHDXs must be detached before the CSV can be taken offline cleanly +Invoke-Command -Session $HVSession -ScriptBlock { + param($vm) + Remove-VMHardDiskDrive -VMName $vm -ControllerType SCSI -ControllerNumber 0 -ControllerLocation 1 + Remove-VMHardDiskDrive -VMName $vm -ControllerType SCSI -ControllerNumber 0 -ControllerLocation 2 + Write-Output "Data VHDX detached (SCSI 0:1)" + Write-Output "Log VHDX detached (SCSI 0:2)" +} -ArgumentList $TargetVM + + +# Take the target CSV offline so the underlying FlashArray volume can be overwritten +Invoke-Command -Session $HVSession -ScriptBlock { + param($res) + Stop-ClusterResource -Name $res -Cluster (Get-Cluster).Name | Out-Null + Write-Output "$res offline" +} -ArgumentList $TargetCSVResource + + +# Clone the snapshot to the target volume -- instantaneous on FlashArray +# New-Pfa2Volume with -Overwrite $true reverts the target volume to the snapshot's contents. +# On a Pure Storage FlashArray this is a metadata operation -- no data is copied. +New-Pfa2Volume -Array $FlashArray -Name $TargetVolName -SourceName $Snapshot.Name -Overwrite $true + + +# Bring the target CSV back online -- the cluster will mount the now-cloned volume +Invoke-Command -Session $HVSession -ScriptBlock { + param($res) + Start-ClusterResource -Name $res -Cluster (Get-Cluster).Name | Out-Null + Write-Output "$res online" +} -ArgumentList $TargetCSVResource + +Start-Sleep -Seconds 5 + + +# Re-attach the cloned VHDXs to the target VM at the same SCSI controller locations +Invoke-Command -Session $HVSession -ScriptBlock { + param($dataVhdx, $logVhdx, $dCN, $dCL, $lCN, $lCL, $vm) + Add-VMHardDiskDrive -VMName $vm -Path $dataVhdx -ControllerType SCSI -ControllerNumber $dCN -ControllerLocation $dCL + Add-VMHardDiskDrive -VMName $vm -Path $logVhdx -ControllerType SCSI -ControllerNumber $lCN -ControllerLocation $lCL + Write-Output "Attached: $dataVhdx (SCSI $($dCN):$($dCL))" + Write-Output "Attached: $logVhdx (SCSI $($lCN):$($lCL))" +} -ArgumentList $ClonedDataVhdx, $ClonedLogVhdx, $DataCtrlNum, $DataCtrlLoc, $LogCtrlNum, $LogCtrlLoc, $TargetVM + + + + +############################################################################# +# Bring the Cloned Database Online on SQL 02 +############################################################################# + +# Wait briefly for CSV and VHDXs to settle +Start-Sleep -Seconds 5 + +# Bring the database online -- SQL Server performs automatic crash recovery +# The database was never cleanly shut down before the snapshot, so SQL Server will +# roll forward the log and roll back any incomplete transactions, just as it would +# after a server restart +$Query = "ALTER DATABASE [$DbName] SET ONLINE" +Invoke-DbaQuery -SqlInstance $SqlInstance2 -Query $Query +Write-Output "$DbName is online on $TargetSQLServer" + + + +############################################################################# +# Verify +############################################################################# + +# Check the database state on SQL 02 -- it should be ONLINE after crash recovery +Get-DbaDbState -SqlInstance $SqlInstance2 -Database $DbName + +# Show all user databases on SQL 02 +Get-DbaDatabase -SqlInstance $SqlInstance2 -Database $DbName | + Select-Object Name, Status, SizeMB + +$Stop = (Get-Date) +Write-Output "Total time from snapshot to database online: $(($Stop - $Start).Seconds) seconds" + + + +############################################################################# +# Cleanup +############################################################################# + +Remove-PSSession $HVSession +Disconnect-Pfa2Array -Array $FlashArray diff --git a/demos-sdk2/Hyper-V CSV w SQL Server Snapshot/HyperV-TSQL-SnapshotBackup.ps1 b/demos-sdk2/Hyper-V CSV w SQL Server Snapshot/HyperV-TSQL-SnapshotBackup.ps1 new file mode 100644 index 0000000..a37e13d --- /dev/null +++ b/demos-sdk2/Hyper-V CSV w SQL Server Snapshot/HyperV-TSQL-SnapshotBackup.ps1 @@ -0,0 +1,276 @@ +############################################################################################################################## +# T-SQL Snapshot Backup and Point-in-Time Recovery - Hyper-V Edition +# +# Scenario: +# This script demonstrates SQL Server 2022 T-SQL Snapshot Backup on a Hyper-V cluster where database files +# reside on VHDXs stored on a Pure Storage FlashArray Cluster Shared Volume (CSV). An application-consistent +# snapshot is taken while SQL Server is running on VM1 (SQL 01), then the cloned volume is presented to VM2 +# (SQL 02) for a point-in-time restore. +# +# Storage topology: +# Source : hyperv-csv-01-DATA-PROD (CSV backing VM1 VHDXs) -> SQL 01 (Production) +# Target : hyperv-csv-01-DATA-DEV (CSV backing VM2 VHDXs) -> SQL 02 (Dev/Test) +# +# This example scenario is useful for seeding a dev/test SQL Server from a production FlashArray CSV with +# a full point-in-time recovery chain. +# +# +# Prerequisites: +# +# FlashArray: +# 1. Source and target volumes must be pre-configured on the FlashArray. +# The target volume must be the same size as the source CSV volume. +# +# Hyper-V Cluster: +# 2. The Failover Cluster PowerShell module must be installed on the Hyper-V node(s). +# Add-WindowsFeature RSAT-Clustering-PowerShell +# 3. PowerShell Remoting (WinRM) must be enabled on all Hyper-V cluster nodes. +# 4. The target VM must have a SCSI controller — hot-add/remove of virtual disks requires SCSI (not IDE). +# +# Modules: +# 5. PureStoragePowerShellSDK2 must be installed on the machine running this script. +# Install-Module PureStoragePowerShellSDK2 +# 6. dbatools must be installed. +# Install-Module dbatools +# +# SQL Server: +# 7. SQL Server 2022 or later is required on both instances for T-SQL Snapshot Backup support. +# 8. $DbName must already exist on the target SQL Server. +# 9. A backup share must be accessible from both SQL 01 and SQL 02 for metadata and log backup files. +# +# Credentials: +# 10. Saved credentials file: $HOME\FA_Cred.xml +# $FACred | Export-CliXml -Path "$HOME\FA_Cred.xml" +# +# +# Usage Notes: +# +# This script uses a volume snapshot (New-Pfa2VolumeSnapshot) rather than a Protection Group snapshot because +# the data and log VHDXs share a single CSV volume — a single volume snapshot captures both files consistently. +# The SUSPEND_FOR_SNAPSHOT_BACKUP and BACKUP METADATA_ONLY commands must share the same SQL Server session; +# this script uses a persistent, non-pooled dbatools connection to satisfy that requirement. +# +# +# Disclaimer: +# This example script is provided AS-IS and meant to be a building block to be adapted to fit an individual +# organization's infrastructure. +############################################################################################################################## + + + +# Import PowerShell modules +Import-Module dbatools +Import-Module PureStoragePowerShellSDK2 + + + +# Initialize variables — edit these to match your environment +$SourceSQLServer = 'sql-01.example.com' # SQL Server hosting the source database +$TargetSQLServer = 'sql-02.example.com' # SQL Server that will receive the clone +$ArrayName = 'flasharray1.example.com' # FlashArray endpoint +$DbName = 'MyDatabaseName' # Database name +$BackupShare = '\\backup-server\BACKUP' # UNC path for metadata and log backups + +# Hyper-V cluster details +$HVNode2 = 'hyperv-node-02.example.com' # Hyper-V cluster node that owns the target CSV +$TargetVM = 'hyperv-vm-02' # VM name receiving the cloned VHDXs +$TargetCSVResource = 'Cluster Disk 3' # Cluster resource name for the target CSV + +# FlashArray volume names +$SourceVolName = 'hyperv-csv-01-DATA-PROD' # FA volume backing the source CSV +$TargetVolName = 'hyperv-csv-01-DATA-DEV' # FA volume backing the target CSV + +# VHDX paths on the target CSV (after clone) +$ClonedDataVhdx = 'C:\ClusterStorage\Volume3\hyperv-vm-01\hyperv-vm-01-Data.vhdx' +$ClonedLogVhdx = 'C:\ClusterStorage\Volume3\hyperv-vm-01\hyperv-vm-01-Log.vhdx' + +# SCSI controller locations for VHDXs on the target VM +$DataCtrlNum = 0; $DataCtrlLoc = 1 # Data VHDX at SCSI 0:1 +$LogCtrlNum = 0; $LogCtrlLoc = 2 # Log VHDX at SCSI 0:2 + + + +# Build connections +# PowerShell remoting session to the Hyper-V cluster node hosting the target CSV +$HVSession = New-PSSession -ComputerName $HVNode2 + +# Persistent, non-pooled SMO connections — required so SUSPEND_FOR_SNAPSHOT_BACKUP +# and BACKUP METADATA_ONLY share the same session (SUSPEND is session-scoped) +$SqlInstance1 = Connect-DbaInstance -SqlInstance $SourceSQLServer -TrustServerCertificate -NonPooledConnection +$SqlInstance2 = Connect-DbaInstance -SqlInstance $TargetSQLServer -TrustServerCertificate -NonPooledConnection + +# Connect to the FlashArray's REST API +$FACred = Import-CliXml -Path "$HOME\FA_Cred.xml" +$FlashArray = Connect-Pfa2Array -EndPoint $ArrayName -Credential $FACred -IgnoreCertificateError + + + + +# Let's get some information about the source database; take note of the size +Get-DbaDatabase -SqlInstance $SqlInstance1 -Database $DbName | + Select-Object Name, SizeMB, Status + + + +############################################################################# +# Take a T-SQL Snapshot Backup +############################################################################# + +# Time the freeze window — this measures how long SQL Server write IO is frozen +$Start = (Get-Date) + + +# Freeze the database for write IO on SQL 01 +$Query = "ALTER DATABASE [$DbName] SET SUSPEND_FOR_SNAPSHOT_BACKUP = ON" +Invoke-DbaQuery -SqlInstance $SqlInstance1 -Query $Query -Verbose + + +# Take a volume snapshot while the database is frozen +$Snapshot = New-Pfa2VolumeSnapshot -Array $FlashArray -SourceName $SourceVolName +$Snapshot + + +# Write the metadata-only backup — this releases the write IO freeze +# MEDIADESCRIPTION stores the snapshot name and array so we can locate the snapshot later +$BackupFile = "$BackupShare\${DbName}_$(Get-Date -Format FileDateTime).bkm" +$Query = "BACKUP DATABASE [$DbName] + TO DISK='$BackupFile' + WITH METADATA_ONLY, + MEDIADESCRIPTION='$($Snapshot.Name)|$($FlashArray.ArrayName)'" +Invoke-DbaQuery -SqlInstance $SqlInstance1 -Query $Query -Verbose + +$Stop = (Get-Date) +Write-Output "The snapshot time takes...$(($Stop - $Start).TotalMilliseconds)ms!" + + +# Check the error log to see what SQL Server thinks happened +Get-DbaErrorLog -SqlInstance $SqlInstance1 -LogNumber 0 | Format-Table + + +# The backup is recorded in MSDB as a Full backup with snapshot +Get-DbaDbBackupHistory -SqlInstance $SqlInstance1 -Database $DbName -Last + + + +############################################################################# +# Take a Log Backup — this extends the restore chain for point-in-time recovery +############################################################################# + +$LogBackup = Backup-DbaDatabase -SqlInstance $SqlInstance1 ` + -Database $DbName ` + -Type Log ` + -Path $BackupShare ` + -CompressBackup + +$LogBackup + + + +############################################################################# +# Point in Time Recovery — Clone the Snapshot to SQL 02 +# +# This is the Hyper-V equivalent of taking the database disk offline, cloning +# the storage snapshot, and bringing the disk back online as described in the +# blog series. Instead of managing a Windows disk serial number, we manage the +# CSV cluster resource and the VHDXs attached to the target Hyper-V VM. +############################################################################# + +# Retrieve the snapshot name from the metadata backup file +# MEDIADESCRIPTION holds the pipe-delimited string we wrote during the backup +$Query = "RESTORE LABELONLY FROM DISK = '$BackupFile'" +$Labels = Invoke-DbaQuery -SqlInstance $SqlInstance2 -Query $Query -Verbose +$SnapshotName = (($Labels | Select-Object MediaDescription -ExpandProperty MediaDescription).Split('|'))[0] +$ArrayName = (($Labels | Select-Object MediaDescription -ExpandProperty MediaDescription).Split('|'))[1] +$SnapshotName +$ArrayName + + +# Offline the database on SQL 02 to release file handles +$Query = "ALTER DATABASE [$DbName] SET OFFLINE WITH ROLLBACK IMMEDIATE" +Invoke-DbaQuery -SqlInstance $SqlInstance2 -Query $Query +$RestoreStart = (Get-Date) + + + +# Detach VHDXs from the target VM before taking the CSV offline +Invoke-Command -Session $HVSession -ScriptBlock { + param($vm) + Remove-VMHardDiskDrive -VMName $vm -ControllerType SCSI -ControllerNumber 0 -ControllerLocation 1 + Remove-VMHardDiskDrive -VMName $vm -ControllerType SCSI -ControllerNumber 0 -ControllerLocation 2 + Write-Output "Data VHDX detached (SCSI 0:1)" + Write-Output "Log VHDX detached (SCSI 0:2)" +} -ArgumentList $TargetVM + + +# Take the target CSV offline so the FlashArray volume can be overwritten +Invoke-Command -Session $HVSession -ScriptBlock { + param($res) + Stop-ClusterResource -Name $res -Cluster (Get-Cluster).Name | Out-Null + Write-Output "$res offline" +} -ArgumentList $TargetCSVResource + + +# Clone the snapshot to the target volume — instantaneous on FlashArray +New-Pfa2Volume -Array $FlashArray -Name $TargetVolName -SourceName $SnapshotName -Overwrite $true + + +# Bring the target CSV back online +Invoke-Command -Session $HVSession -ScriptBlock { + param($res) + Start-ClusterResource -Name $res -Cluster (Get-Cluster).Name | Out-Null + Write-Output "$res online" +} -ArgumentList $TargetCSVResource + +Start-Sleep -Seconds 5 + + +# Re-attach the cloned VHDXs to the target VM +Invoke-Command -Session $HVSession -ScriptBlock { + param($dataVhdx, $logVhdx, $dCN, $dCL, $lCN, $lCL, $vm) + Add-VMHardDiskDrive -VMName $vm -Path $dataVhdx -ControllerType SCSI -ControllerNumber $dCN -ControllerLocation $dCL + Add-VMHardDiskDrive -VMName $vm -Path $logVhdx -ControllerType SCSI -ControllerNumber $lCN -ControllerLocation $lCL + Write-Output "Attached: $dataVhdx (SCSI $($dCN):$($dCL))" + Write-Output "Attached: $logVhdx (SCSI $($lCN):$($lCL))" +} -ArgumentList $ClonedDataVhdx, $ClonedLogVhdx, $DataCtrlNum, $DataCtrlLoc, $LogCtrlNum, $LogCtrlLoc, $TargetVM + + + +# Restore the database from the metadata-only backup file +# METADATA_ONLY tells SQL Server the files are already in place from the snapshot +# NORECOVERY leaves the database in RESTORING mode so we can apply log backups +$Query = "RESTORE DATABASE [$DbName] FROM DISK = '$BackupFile' WITH METADATA_ONLY, REPLACE, NORECOVERY" +Invoke-DbaQuery -SqlInstance $SqlInstance2 -Database master -Query $Query -Verbose + + +# Check the current state of the database — it should be in RESTORING mode +Get-DbaDbState -SqlInstance $SqlInstance2 -Database $DbName + + +# Restore the log backup up to the point in time — database remains in RESTORING mode +$Query = "RESTORE LOG [$DbName] FROM DISK = '$($LogBackup.BackupPath)' WITH NORECOVERY" +Invoke-DbaQuery -SqlInstance $SqlInstance2 -Database master -Query $Query -Verbose + + +# Bring the database online +$Query = "RESTORE DATABASE [$DbName] WITH RECOVERY" +Invoke-DbaQuery -SqlInstance $SqlInstance2 -Database master -Query $Query + +$RestoreStop = (Get-Date) +Write-Output "The restore time takes...$(($RestoreStop - $RestoreStart).TotalMilliseconds)ms!" + + +# Verify the database is online +Get-DbaDbState -SqlInstance $SqlInstance2 -Database $DbName + +Get-DbaDatabase -SqlInstance $SqlInstance2 -Database $DbName | + Select-Object Name, Status, SizeMB + + + + +############################################################################# +# Cleanup +############################################################################# + +Remove-PSSession $HVSession +Disconnect-Pfa2Array -Array $FlashArray diff --git a/demos-sdk2/Hyper-V CSV w SQL Server Snapshot/readme.md b/demos-sdk2/Hyper-V CSV w SQL Server Snapshot/readme.md index e77eb26..d8f6e6f 100644 --- a/demos-sdk2/Hyper-V CSV w SQL Server Snapshot/readme.md +++ b/demos-sdk2/Hyper-V CSV w SQL Server Snapshot/readme.md @@ -1,57 +1,95 @@ -**Hyper-V Cluster Shared Volume + SQL Server Snapshot Scripts** -

-This folder contains Hyper-V Cluster Shared Volume + SQL Server example snapshot scripts. +# Hyper-V Cluster Shared Volume + SQL Server Snapshot Scripts + +This folder contains Hyper-V Cluster Shared Volume (CSV) + SQL Server example snapshot scripts using the Pure Storage FlashArray. **Files:** -- Hyper-V CSV w SQL Server Snapshot.ps1 +- `Hyper-V CSV w SQL Server Snapshot.ps1` - CSV clone with disk resignature; presents the clone back to the originating Hyper-V cluster as a second CSV +- `HyperV CrashConsistentClone.ps1` - crash-consistent snapshot clone from a production CSV to a dev/test SQL Server VM +- `HyperV-TSQL-SnapshotBackup.ps1` - SQL Server 2022 T-SQL Snapshot Backup with point-in-time recovery on a Hyper-V CSV + +--- + +## Hyper-V CSV w SQL Server Snapshot.ps1 + +**Scenario:** + +This script clones a Hyper-V Cluster Shared Volume (CSV) using a crash-consistent snapshot and presents it back to the originating Hyper-V cluster as a second CSV. The target VM spans two CSVs: the first holds the VM OS disk (untouched by this script) and the second holds the SQL Server user database VHDX. Only the database CSV is swapped - the database VHDX is hot-removed, the CSV is overwritten with a fresh clone from the FlashArray, and the VHDX is hot-added back to the running VM. + +**Prerequisites:** + +1. A pre-created target volume on the FlashArray, connected to the target Hyper-V host, must be the same size as the source CSV volume. +2. The Failover Cluster PowerShell module must be installed: `Add-WindowsFeature RSAT-Clustering-PowerShell` +3. PowerShell Remoting (WinRM) must be enabled on the Hyper-V host and inside the target VM guest. +4. The target VM must have a SCSI controller - hot-add/remove requires SCSI (not IDE). +5. The `SqlServer` module must be installed in the guest: `Install-Module SqlServer` +6. The credential used must have Administrator rights on the Hyper-V host, cluster nodes, and the VM guest, plus FlashArray array admin or storage admin role. + +**Important Usage Notes:** + +The clone carries the same disk signature as the source CSV, so it must be resignatured on the target host while offline before it can be used as a CSV. This is handled automatically via DISKPART before the cluster resource is started. + +--- - -
- +## HyperV CrashConsistentClone.ps1 **Scenario:** -
This example script shows steps to snapshot a Hyper-V Cluster Shared Volume (CSV) that contains data & log VHDX/AVHDX files for a SQL Server. - -
-
-This scenario has a SQL Server Hyper-V VM (ex: Production) with at least two CSVs. The first CSV is the primary, which will contain VHDX files for the VM itself, OS drive, SQL Server system files, tempdb, etc. The second CSV, which is what this script will clone, will ONLY contain the data and log files for 1 or more user databases ONLY. The data and log files can be on two different VHDX files or the same VHDX file - it only matters that they reside in this second CSV, not the first. -
-
-The second SQL Server Hyper-V VM (ex: non-Production) will also be set up similarly, with two CSVs, so we can clone the Production CSV's user database(s) and refresh this second VM's second CSV with a clone of Production's database. -
-
-All references to a "source" refer to the production side (VM, CSV, etc). -All references to a "target" refer to the non-production side (VM, CSV, etc). + +This script clones a production SQL Server Hyper-V CSV to a dev/test SQL Server VM using a crash-consistent FlashArray snapshot. The source SQL Server remains running and unaffected throughout the entire operation. The target SQL Server performs automatic crash recovery (roll forward committed transactions, roll back uncommitted) when the database is brought online - equivalent to recovering after a power loss. + +Storage topology: +- **Source**: `hyperv-csv-01-DATA-PROD` (CSV backing VM1 VHDXs) → SQL 01 (Production) +- **Target**: `hyperv-csv-01-DATA-DEV` (CSV backing VM2 VHDXs) → SQL 02 (Dev/Test) **Prerequisites:** -1. The production CSV must already be cloned and presented once, to the non-production side. -2. This script assumes the database(s) are already attached on the target, non-production SQL Server. + +1. Source and target volumes must be pre-configured on the FlashArray and the same size. +2. The Failover Cluster PowerShell module must be installed: `Add-WindowsFeature RSAT-Clustering-PowerShell` +3. PowerShell Remoting (WinRM) must be enabled on all Hyper-V cluster nodes. +4. The target VM must have a SCSI controller. +5. `PureStoragePowerShellSDK2` and `dbatools` modules must be installed. +6. Saved FlashArray credentials at `$HOME\FA_Cred.xml`. +7. The target database must already exist on the target SQL Server. **Important Usage Notes:** -
You must pre-setup the target VM with a cloned CSV from the source already. You will ONLY be utilizing the specific VHDX(s) that contain the data/log files of interest, from the cloned CSV. Also note that the CSV does not need to only exclusively contain VHDXs for the SQL Server in question. If other VHDXs are present in the CSV, used by the either the source SQL Server VM or other VMs, they do not need to be deleted or otherwise manipulated during this cloning process. Remember FlashArray deduplicates data, thus a clone's set of additional, unused VHDXs will not have a negative impact. -For the cloned CSV pre-setup, you can use subsets of the code below to clone the source CSV, present it to the target server, then attach the VHDX(s) containing the production databases that will be re-cloned with this script. Once "staged," you can then use this script fully to refresh the data files in the cloned CSV that is attached to the target server. +This is a crash-consistent snapshot - SQL Server write IO is not frozen before the snapshot is taken. No source downtime or quiescing is required. The VHDXs are detached from the target VM, the target CSV cluster resource is taken offline, the FlashArray volume is overwritten with the snapshot clone (a metadata-only operation on the array), and the CSV and VHDXs are brought back online before the database is recovered. -This script also assumes that all database files (data and log) are on the same volume/single VHDX. If multiple volumes/VHDXs are being used, you will have to adjust the code (ex: add additional foreach loops for manipulating multiple VHDXs). +--- -The staging server is needed because each CSV has a unique signature. If the CSV is presented back to the Hyper-V host unaltered, a signature collision will be detected and the new CSV will not be able to be used by Windows. Hyper-V is unable to resignature in this state either. Instead, the CSV must be presented to another machine (aka the staging server), resignatured there, then can be re-snapshotted and cloned back to the originating Hyper-V host. +## HyperV-TSQL-SnapshotBackup.ps1 -This script may be adjusted to clone and present the CSV snapshot to a different Hyper-V host. If this is done, then the staging server and resignature step is not required, since the new target Hyper-V host will not have two of the same CSV causing a signature conflict. +**Scenario:** - -
- +This script demonstrates SQL Server 2022 T-SQL Snapshot Backup on a Hyper-V cluster where database files reside on VHDXs stored on a Pure Storage FlashArray CSV. An application-consistent snapshot is taken while SQL Server is running on VM1 (SQL 01). The clone is then presented to VM2 (SQL 02) and restored with a log backup for point-in-time recovery. + +Storage topology: +- **Source**: `hyperv-csv-01-DATA-PROD` (CSV backing VM1 VHDXs) → SQL 01 (Production) +- **Target**: `hyperv-csv-01-DATA-DEV` (CSV backing VM2 VHDXs) → SQL 02 (Dev/Test) + +**Prerequisites:** + +1. Source and target volumes must be pre-configured on the FlashArray and the same size. +2. The Failover Cluster PowerShell module must be installed: `Add-WindowsFeature RSAT-Clustering-PowerShell` +3. PowerShell Remoting (WinRM) must be enabled on all Hyper-V cluster nodes. +4. The target VM must have a SCSI controller. +5. SQL Server 2022 or later is required on both instances for T-SQL Snapshot Backup support. +6. `PureStoragePowerShellSDK2` and `dbatools` modules must be installed. +7. A backup share must be accessible from both SQL 01 and SQL 02 for metadata and log backup files. +8. Saved FlashArray credentials at `$HOME\FA_Cred.xml`. +9. The target database must already exist on the target SQL Server. + +**Important Usage Notes:** + +This script uses a volume snapshot (`New-Pfa2VolumeSnapshot`) rather than a Protection Group snapshot because the data and log VHDXs share a single CSV volume - a single volume snapshot captures both files consistently. The `SUSPEND_FOR_SNAPSHOT_BACKUP` and `BACKUP METADATA_ONLY` commands must share the same SQL Server session; this script uses a persistent, non-pooled dbatools connection (`-NonPooledConnection`) to satisfy that requirement. The snapshot name is embedded in the backup file's `MEDIADESCRIPTION` field so it can be retrieved later during restore. + +--- **Disclaimer:** -
+ This example script is provided AS-IS and meant to be a building block to be adapted to fit an individual organization's infrastructure. -
-
We encourage the modification and expansion of these scripts by the community. Although not necessary, please issue a Pull Request (PR) if you wish to request merging your modified code in to this repository. - -
- +--- _The contents of the repository are intended as examples only and should be modified to work in your individual environments. No script examples should be used in a production environment without fully testing them in a development or lab environment. There are no expressed or implied warranties or liability for the use of these example scripts and templates presented by Pure Storage and/or their creators._ \ No newline at end of file From f42abb36173e5fea6c5d0896a667bd848da19d38 Mon Sep 17 00:00:00 2001 From: "Anthony E. Nocentino" Date: Sat, 9 May 2026 15:27:30 +0000 Subject: [PATCH 16/19] Fix en-dash on Connect-Pfa2Array and correct verify query table name --- .../Point in Time Recovery - VMFS.ps1 | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/demos-sdk2/Point in Time Recovery - VMFS/Point in Time Recovery - VMFS.ps1 b/demos-sdk2/Point in Time Recovery - VMFS/Point in Time Recovery - VMFS.ps1 index e2ad494..e37ad3b 100644 --- a/demos-sdk2/Point in Time Recovery - VMFS/Point in Time Recovery - VMFS.ps1 +++ b/demos-sdk2/Point in Time Recovery - VMFS/Point in Time Recovery - VMFS.ps1 @@ -81,7 +81,7 @@ Get-DbaDatabase -SqlInstance $SqlInstance -Database $DbName | # Connect to the FlashArray's REST API $Credential = Get-Credential -UserName "$env:USERNAME" -Message 'Enter your credential information...' -$FlashArray = Connect-Pfa2Array –EndPoint $ArrayName -Credential $Credential -IgnoreCertificateError +$FlashArray = Connect-Pfa2Array -EndPoint $ArrayName -Credential $Credential -IgnoreCertificateError @@ -306,7 +306,7 @@ Invoke-DbaQuery -SqlInstance $SqlInstance -Database master -Query $Query # Verify Restore -Invoke-DbaQuery -SqlInstance $SqlInstance -Database $DbName -Query "SELECT TOP 10 * FROM dbo.Recipes" +Invoke-DbaQuery -SqlInstance $SqlInstance -Database $DbName -Query "SELECT TOP 10 * FROM Sales.Customer" From c0e63794f71c9125d43cfdf026129ac4bcdae324 Mon Sep 17 00:00:00 2001 From: "Anthony E. Nocentino" Date: Sat, 9 May 2026 15:30:36 +0000 Subject: [PATCH 17/19] Move PiTR-VMFS script into VMFS-VMDK Snapshot folder; combine READMEs; update main README --- README.md | 3 +- .../Point in Time Recovery - VMFS/README.md | 40 --------- .../Point in Time Recovery - VMFS.ps1 | 0 demos-sdk2/VMFS-VMDK Snapshot/README.md | 86 ++++++++++++------- 4 files changed, 56 insertions(+), 73 deletions(-) delete mode 100644 demos-sdk2/Point in Time Recovery - VMFS/README.md rename demos-sdk2/{Point in Time Recovery - VMFS => VMFS-VMDK Snapshot}/Point in Time Recovery - VMFS.ps1 (100%) diff --git a/README.md b/README.md index 965cd7c..7fe12a4 100644 --- a/README.md +++ b/README.md @@ -43,7 +43,8 @@ Array-based snapshots are used to decouple database operations from the size of | Demo | Description | | | | ----------- | ----------- | ----------- | ----------- | -| **Volume Database Refresh on VMDK Virtual Disks** | Refresh a database from Volume snapshot where all of the databases's files are using a VMware VMDK Virtual Disk type on a single VMFS Datastore on the same Volume on the same FlashArray. | [More Info](./demos-sdk2/VMFS-VMDK%20Snapshot/) | [Sample Code](./demos-sdk2/VMFS-VMDK%20Snapshot/VMFS-VMDK%20Snapshot.ps1) | +| **Volume Database Refresh on VMDK Virtual Disks** | Refresh a database from a Volume snapshot where all of the databases's files are using a VMware VMDK Virtual Disk type on a single VMFS Datastore on the same Volume on the same FlashArray. | [More Info](./demos-sdk2/VMFS-VMDK%20Snapshot/) | [Sample Code](./demos-sdk2/VMFS-VMDK%20Snapshot/VMFS-VMDK%20Snapshot.ps1) | +| **Point in Time Recovery on VMDK Virtual Disks** | Combine a FlashArray snapshot with SQL Server 2022 T-SQL Snapshot Backup on a VMFS datastore for application-consistent snapshots and point-in-time recovery. | [More Info](./demos-sdk2/VMFS-VMDK%20Snapshot/) | [Sample Code](./demos-sdk2/VMFS-VMDK%20Snapshot/Point%20in%20Time%20Recovery%20-%20VMFS.ps1) | ## Using Snapshots for Databases on Hyper-V Cluster Shared Volumes (CSV) diff --git a/demos-sdk2/Point in Time Recovery - VMFS/README.md b/demos-sdk2/Point in Time Recovery - VMFS/README.md deleted file mode 100644 index ecbc7d2..0000000 --- a/demos-sdk2/Point in Time Recovery - VMFS/README.md +++ /dev/null @@ -1,40 +0,0 @@ -# Point In Time Recovery - Using SQL Server 2022's T-SQL Snapshot Backup feature w. VMFS/VMDK datastore/files. - - - -
- - -# Scenario: -Perform a point in time restore using SQL Server 2022's T-SQL Snapshot Backup feature. This uses a FlashArray snapshot as the base of the restore, then restores a log backup. - -# IMPORTANT NOTE: -This example script is built for 1 database spanned across two VMDK files/volumes from a single datastore. - -The granularity or unit of work for this workflow is a VMDK file(s) and the entirety of its contents. Therefore, everything in the VMDK file(s) including files for other databases will be impacted/overwritten. - -This example will need to be adapted if you wish to support multiple databases on the same set of VMDK(s). - -# Prerequisites: -1. PowerShell Modules: dbatools & PureStoragePowerShellSDK2 - -# Usage Notes: -Each section of the script is meant to be run one after the other. The script is not meant to be executed all at once. - - -
- - -# Disclaimer: -This example script is provided AS-IS and is meant to be a building block to be adapted to fit an individual organization's infrastructure. -

-_PLEASE_ do not save your passwords in cleartext here. -Use NTFS secured, encrypted files or whatever else -- never cleartext! -

-We encourage the modification and expansion of these scripts by the community. Although not necessary, please issue a Pull Request (PR) if you wish to request merging your modified code in to this repository. - - -
- - -_The contents of the repository are intended as examples only and should be modified to work in your individual environments. No script examples should be used in a production environment without fully testing them in a development or lab environment. There are no expressed or implied warranties or liability for the use of these example scripts and templates presented by Pure Storage and/or their creators._ diff --git a/demos-sdk2/Point in Time Recovery - VMFS/Point in Time Recovery - VMFS.ps1 b/demos-sdk2/VMFS-VMDK Snapshot/Point in Time Recovery - VMFS.ps1 similarity index 100% rename from demos-sdk2/Point in Time Recovery - VMFS/Point in Time Recovery - VMFS.ps1 rename to demos-sdk2/VMFS-VMDK Snapshot/Point in Time Recovery - VMFS.ps1 diff --git a/demos-sdk2/VMFS-VMDK Snapshot/README.md b/demos-sdk2/VMFS-VMDK Snapshot/README.md index 9a459c5..8c26738 100644 --- a/demos-sdk2/VMFS-VMDK Snapshot/README.md +++ b/demos-sdk2/VMFS-VMDK Snapshot/README.md @@ -1,40 +1,62 @@ -# Refresh VMFS VMDK(s) with Snapshot Demo - -
- - -# Scenario: -This example is for a repeatable refresh scenario, such as a nightly refresh of a production database on -another non-production SQL Server. -

-Production SQL Server & database(s) reside on a VMFS datastore. Non-production SQL Server resides on -a different VMFS datastore. User database(s) data and log files reside on two different VMDK disks -in each datastore. This workflow is intended to only be impact select Windows Disks/VMDKs that contain user -databases. Each datastore also resides on a different FlashArray, to demonstrate use of async snapshot -replication. -

-This example's workflow takes an on-demand snapshot of the Production datastore and async replicates it to -the second FlashArray. Then the snapshot is cloned as a new temporary volume/datastore. The VMDKs with the -production database files, residing on the temporary cloned datastore are attached to the target SQL Server, -replacing the prior VMDKs that stored the database files previously. Finally Storage vMotion is used to -migrate the VMDKs to the non-production datastore, then the temporary cloned datastore is discarded. -

- - -# Disclaimer: -This example script is provided AS-IS and is meant to be a building block to be adapted to fit an individual organization's infrastructure. -

-_PLEASE_ do not save your passwords in cleartext here. -Use NTFS secured, encrypted files or whatever else -- never cleartext! -

+# VMFS-VMDK Snapshot Scripts for SQL Server + +This folder contains scripts for managing SQL Server databases on VMware VMFS datastores using Pure Storage FlashArray snapshots. + +**Files:** +- `VMFS-VMDK Snapshot.ps1` — repeatable database refresh from a FlashArray snapshot across two VMFS datastores +- `Point in Time Recovery - VMFS.ps1` — SQL Server 2022 T-SQL Snapshot Backup with point-in-time recovery on VMFS/VMDK + +--- + +## VMFS-VMDK Snapshot.ps1 + +**Scenario:** + +This script is for a repeatable refresh scenario, such as a nightly refresh of a production database onto a non-production SQL Server. Production SQL Server databases reside on a VMFS datastore on one FlashArray. The non-production SQL Server resides on a different VMFS datastore on a second FlashArray. + +The workflow takes an on-demand snapshot of the production datastore and async replicates it to the second FlashArray. The snapshot is then cloned as a new temporary volume/datastore. The VMDKs containing the production database files are attached to the target SQL Server, replacing the prior VMDKs. Finally, Storage vMotion migrates the VMDKs to the non-production datastore and the temporary cloned datastore is discarded. + +**Prerequisites:** + +1. PowerShell Modules: `dbatools` & `PureStoragePowerShellSDK2` +2. VMware PowerCLI must be installed. +3. Async replication must be configured between the source and target FlashArrays. + +--- + +## Point in Time Recovery - VMFS.ps1 + +**Scenario:** + +This script performs a point-in-time restore using SQL Server 2022's T-SQL Snapshot Backup feature with a FlashArray snapshot as the base, followed by restoring a native SQL Server log backup. + +**Important Note:** + +This script is built for a single database spanned across two VMDK files from a single datastore. The granularity of this workflow is a VMDK file and the entirety of its contents — everything in the VMDK, including files for other databases, will be impacted and overwritten. This script will need to be adapted to support multiple databases on the same VMDK(s). + +**Prerequisites:** + +1. PowerShell Modules: `dbatools` & `PureStoragePowerShellSDK2` +2. VMware PowerCLI must be installed. +3. SQL Server 2022 or later is required for T-SQL Snapshot Backup support. + +**Usage Notes:** + +Each section of the script is meant to be run one after the other. The script is not meant to be executed all at once. + +--- + +## Disclaimer + +This example script is provided AS-IS and is meant to be a building block to be adapted to fit an individual organization's infrastructure. + We encourage the modification and expansion of these scripts by the community. Although not necessary, please issue a Pull Request (PR) if you wish to request merging your modified code in to this repository. - -
- +--- _The contents of the repository are intended as examples only and should be modified to work in your individual environments. No script examples should be used in a production environment without fully testing them in a development or lab environment. There are no expressed or implied warranties or liability for the use of these example scripts and templates presented by Pure Storage and/or their creators._ + From ea6daffa966e0ce88a5dc8d0d3a2bf6808cea558 Mon Sep 17 00:00:00 2001 From: "Anthony E. Nocentino" Date: Sat, 9 May 2026 15:34:21 +0000 Subject: [PATCH 18/19] Fix en-dash parameter errors and logic bugs across demo scripts --- demos-sdk2/ActiveDR/ActiveDR Full Failover.ps1 | 2 +- .../SQL Server FCI + ActiveDR/ActiveDR-FCI-Testing.ps1 | 2 +- demos-sdk2/Multi-Array Snapshot/Multi-Array Snapshot.ps1 | 4 ++-- .../Point in Time Recovery/Point in Time Recovery.ps1 | 2 +- ...rotection Group Database Refresh Between FlashArrays.ps1 | 6 +++--- .../Protection Group Database Refresh.ps1 | 2 +- .../Seeding an Availability Group.ps1 | 4 ++-- .../Volume Database Refresh/Volume Database Refresh.ps1 | 2 +- 8 files changed, 12 insertions(+), 12 deletions(-) diff --git a/demos-sdk2/ActiveDR/ActiveDR Full Failover.ps1 b/demos-sdk2/ActiveDR/ActiveDR Full Failover.ps1 index 73b5d44..ffdc21d 100644 --- a/demos-sdk2/ActiveDR/ActiveDR Full Failover.ps1 +++ b/demos-sdk2/ActiveDR/ActiveDR Full Failover.ps1 @@ -52,7 +52,7 @@ $ProductionSQLServerSession = New-PSSession -ComputerName $ProductionSQLServer # Connect to FlashArray $Credential = Get-Credential -$FlashArray = Connect-Pfa2Array -Endpoint $DRArrayName -Credential $Credential -IgnoreCertificateError +$FlashArray = Connect-Pfa2Array -Endpoint $ProductionArrayName -Credential $Credential -IgnoreCertificateError diff --git a/demos-sdk2/ActiveDR/SQL Server FCI + ActiveDR/ActiveDR-FCI-Testing.ps1 b/demos-sdk2/ActiveDR/SQL Server FCI + ActiveDR/ActiveDR-FCI-Testing.ps1 index f739f62..da4c473 100644 --- a/demos-sdk2/ActiveDR/SQL Server FCI + ActiveDR/ActiveDR-FCI-Testing.ps1 +++ b/demos-sdk2/ActiveDR/SQL Server FCI + ActiveDR/ActiveDR-FCI-Testing.ps1 @@ -83,7 +83,7 @@ $SourcePodName = "PodNameOnSourceArray" # set Pure credentials -$PureCred = Get-Credentials +$PureCred = Get-Credential diff --git a/demos-sdk2/Multi-Array Snapshot/Multi-Array Snapshot.ps1 b/demos-sdk2/Multi-Array Snapshot/Multi-Array Snapshot.ps1 index b3a3e82..802b0dd 100644 --- a/demos-sdk2/Multi-Array Snapshot/Multi-Array Snapshot.ps1 +++ b/demos-sdk2/Multi-Array Snapshot/Multi-Array Snapshot.ps1 @@ -61,8 +61,8 @@ $SqlInstance = Connect-DbaInstance -SqlInstance $TargetSQLServer -TrustServerCer # Connect to the FlashArrays' REST APIs $Credential = Get-Credential -$FlashArray1 = Connect-Pfa2Array –EndPoint $ArrayName1 -Credential $Credential -IgnoreCertificateError -$FlashArray2 = Connect-Pfa2Array –EndPoint $ArrayName2 -Credential $Credential -IgnoreCertificateError +$FlashArray1 = Connect-Pfa2Array -EndPoint $ArrayName1 -Credential $Credential -IgnoreCertificateError +$FlashArray2 = Connect-Pfa2Array -EndPoint $ArrayName2 -Credential $Credential -IgnoreCertificateError diff --git a/demos-sdk2/Point in Time Recovery/Point in Time Recovery.ps1 b/demos-sdk2/Point in Time Recovery/Point in Time Recovery.ps1 index d900f85..2d57277 100644 --- a/demos-sdk2/Point in Time Recovery/Point in Time Recovery.ps1 +++ b/demos-sdk2/Point in Time Recovery/Point in Time Recovery.ps1 @@ -56,7 +56,7 @@ Get-DbaDatabase -SqlInstance $SqlInstance -Database $DbName | # Connect to the FlashArray's REST API $Credential = Get-Credential -$FlashArray = Connect-Pfa2Array –EndPoint $ArrayName -Credential $Credential -IgnoreCertificateError +$FlashArray = Connect-Pfa2Array -EndPoint $ArrayName -Credential $Credential -IgnoreCertificateError diff --git a/demos-sdk2/Protection Group Database Refresh Between FlashArrays/Protection Group Database Refresh Between FlashArrays.ps1 b/demos-sdk2/Protection Group Database Refresh Between FlashArrays/Protection Group Database Refresh Between FlashArrays.ps1 index d07cba2..455b3fb 100644 --- a/demos-sdk2/Protection Group Database Refresh Between FlashArrays/Protection Group Database Refresh Between FlashArrays.ps1 +++ b/demos-sdk2/Protection Group Database Refresh Between FlashArrays/Protection Group Database Refresh Between FlashArrays.ps1 @@ -53,12 +53,12 @@ $Credential = Get-Credential # Connect to the source FlashArray's REST API -$SourceFlashArray = Connect-Pfa2Array –EndPoint $TargetArrayName -Credential $Credential -IgnoreCertificateError +$SourceFlashArray = Connect-Pfa2Array -EndPoint $SourceArrayName -Credential $Credential -IgnoreCertificateError # Take a snapshot of the Protection Group and replicate it to the target array -$Snapshot = New-Pfa2ProtectionGroupSnapshot -Array $FlashArray -SourceName $ProtectionGroupName -ForReplication $true -ReplicateNow $true +$Snapshot = New-Pfa2ProtectionGroupSnapshot -Array $SourceFlashArray -SourceName $ProtectionGroupName -ForReplication $true -ReplicateNow $true @@ -80,7 +80,7 @@ Invoke-Command -Session $TargetSession -ScriptBlock { Get-Disk | Where-Object { # Connect to the target FlashArray's REST API -$TargetFlashArray = Connect-Pfa2Array –EndPoint $TargetArrayName -Credential $Credential -IgnoreCertificateError +$TargetFlashArray = Connect-Pfa2Array -EndPoint $TargetArrayName -Credential $Credential -IgnoreCertificateError diff --git a/demos-sdk2/Protection Group Database Refresh/Protection Group Database Refresh.ps1 b/demos-sdk2/Protection Group Database Refresh/Protection Group Database Refresh.ps1 index f753830..fdc05e3 100644 --- a/demos-sdk2/Protection Group Database Refresh/Protection Group Database Refresh.ps1 +++ b/demos-sdk2/Protection Group Database Refresh/Protection Group Database Refresh.ps1 @@ -64,7 +64,7 @@ Invoke-Command -Session $TargetSession -ScriptBlock { Get-Disk | Where-Object { # Connect to the FlashArray's REST API -$FlashArray = Connect-Pfa2Array –EndPoint $ArrayName -Credential $Credential -IgnoreCertificateError +$FlashArray = Connect-Pfa2Array -EndPoint $ArrayName -Credential $Credential -IgnoreCertificateError diff --git a/demos-sdk2/Seeding an Availability Group/Seeding an Availability Group.ps1 b/demos-sdk2/Seeding an Availability Group/Seeding an Availability Group.ps1 index 7de7c61..b08312b 100644 --- a/demos-sdk2/Seeding an Availability Group/Seeding an Availability Group.ps1 +++ b/demos-sdk2/Seeding an Availability Group/Seeding an Availability Group.ps1 @@ -63,7 +63,7 @@ $SqlInstanceSecondary = Connect-DbaInstance -SqlInstance $SecondarySqlServer -Tr # Connect to the FlashArray with for the AG Primary $Credential = Get-Credential -$FlashArrayPrimary = Connect-Pfa2Array –EndPoint $PrimaryArrayName -Credential $Credential -IgnoreCertificateError +$FlashArrayPrimary = Connect-Pfa2Array -EndPoint $PrimaryArrayName -Credential $Credential -IgnoreCertificateError @@ -89,7 +89,7 @@ Invoke-DbaQuery -SqlInstance $SqlInstancePrimary -Query $Query -Verbose # Connect to the FlashArray's REST API where the secondary's data is located -$FlashArraySecondary = Connect-Pfa2Array –EndPoint $SecondaryArrayName -Credential $Credential -IgnoreCertificateError +$FlashArraySecondary = Connect-Pfa2Array -EndPoint $SecondaryArrayName -Credential $Credential -IgnoreCertificateError # This is a loop that will block until the snapshot has completed replicating between the two arrays. diff --git a/demos-sdk2/Volume Database Refresh/Volume Database Refresh.ps1 b/demos-sdk2/Volume Database Refresh/Volume Database Refresh.ps1 index 0d4ac4f..9af5441 100644 --- a/demos-sdk2/Volume Database Refresh/Volume Database Refresh.ps1 +++ b/demos-sdk2/Volume Database Refresh/Volume Database Refresh.ps1 @@ -60,7 +60,7 @@ Invoke-Command -Session $TargetSession -ScriptBlock { Get-Disk | Where-Object { # Connect to the FlashArray's REST API -$FlashArray = Connect-Pfa2Array –EndPoint $ArrayName -Credential $Credential -IgnoreCertificateError +$FlashArray = Connect-Pfa2Array -EndPoint $ArrayName -Credential $Credential -IgnoreCertificateError From 8e8fed2fb991964fdde086fc2368e74b23e44aed Mon Sep 17 00:00:00 2001 From: "Anthony E. Nocentino" Date: Sat, 9 May 2026 15:38:55 +0000 Subject: [PATCH 19/19] Correctness fixes Co-authored-by: Copilot --- .../ActiveDR-FCI-Testing.ps1 | 132 ++++++++---------- 1 file changed, 59 insertions(+), 73 deletions(-) diff --git a/demos-sdk2/ActiveDR/SQL Server FCI + ActiveDR/ActiveDR-FCI-Testing.ps1 b/demos-sdk2/ActiveDR/SQL Server FCI + ActiveDR/ActiveDR-FCI-Testing.ps1 index da4c473..5a412b0 100644 --- a/demos-sdk2/ActiveDR/SQL Server FCI + ActiveDR/ActiveDR-FCI-Testing.ps1 +++ b/demos-sdk2/ActiveDR/SQL Server FCI + ActiveDR/ActiveDR-FCI-Testing.ps1 @@ -1,30 +1,45 @@ -#################################################################################################################### -#################################################################################################################### -## -## ActiveDR failover testing for SQL Server Failover Cluster Instance -## -## This demo script runs through two scenarios: - -## 1. Failover of clustered SQL Server role to node on same array -## 2. Failover of clustered SQL Server role to node on remote array -## -## The second test involves the following steps: - -###### Stop clustered SQL Server role in FCI -###### Demote source pod -###### Promote target pod -###### Move clustered role to node on target array -###### Start up clustered SQL Server role -## -## Author - Andrew Pruski -## apruski@purestorage.com -## -#################################################################################################################### -#################################################################################################################### +############################################################################################################################## +# ActiveDR Failover Testing for SQL Server Failover Cluster Instance +# +# Scenario: +# This demo script runs through two scenarios: +# 1. Failover of a clustered SQL Server role to a node on the same storage array +# 2. Failover of a clustered SQL Server role to a node on a remote storage array +# +# The remote array failover involves the following steps: +# - Stop the clustered SQL Server role in the FCI +# - Demote the source pod +# - Promote the target pod +# - Move the clustered role to a node on the target array +# - Start the clustered SQL Server role +# +# Disclaimer: +# This example script is provided AS-IS and meant to be a building block to be adapted to fit an individual +# organization's infrastructure. +############################################################################################################################## -# import powershell modules +# Import PowerShell modules Import-Module FailoverClusters -Import-Module PureStoragePowershellSDK2 +Import-Module PureStoragePowerShellSDK2 + + + +# Variables +$ClusterName = "WindowsClusterName" +$ClusterRole = "SQL Server (MSSQLSERVER)" # Clustered SQL Server role name +$NodeSameArray = "NodeOnSameArray" # Cluster node on the same storage array +$NodeRemoteArray = "NodeOnRemoteArray" # Cluster node on the remote storage array +$SourceFlashArrayIp = "flasharray1.example.com" # Source FlashArray endpoint +$SourcePodName = "PodNameOnSourceArray" # Pod name on the source FlashArray +$TargetFlashArrayIp = "flasharray2.example.com" # Target FlashArray endpoint +$TargetPodName = "PodNameOnTargetArray" # Pod name on the target FlashArray + + + +# Set credentials +$PureCred = Get-Credential @@ -36,124 +51,95 @@ Import-Module PureStoragePowershellSDK2 -# set variables -$ClusterName = "WindowsClusterName" -$ClusterRole = "SQL Server (MSSQLSERVER)" -$NodeSameArray = "NodeOnSameArray" - - - -# confirm cluster +# Confirm cluster Get-Cluster $ClusterName -# confirm cluster nodes +# Confirm cluster nodes Get-Cluster $ClusterName | Get-ClusterNode -# confirm clustered SQL Server service +# Confirm clustered SQL Server service Get-ClusterGroup -Cluster $ClusterName -Name $ClusterRole -# test failing over clustered service to node on same storage array +# Test failing over clustered service to node on same storage array Move-ClusterGroup -Cluster $ClusterName -Name $ClusterRole -Node $NodeSameArray -# confirm clustered SQL Server service +# Confirm clustered SQL Server service Get-ClusterGroup -Cluster $ClusterName -Name $ClusterRole -################################################################################################################ +#################################################################################################################### # # Performing failover to node on remote storage array # -################################################################################################################ - - - -# set source array details -$SourceFlashArrayIp = "SourceFlashArrayIpAddress" -$SourcePodName = "PodNameOnSourceArray" - - - -# set Pure credentials -$PureCred = Get-Credential +#################################################################################################################### -# connect to source flasharray +# Connect to the source FlashArray $SourceFlashArray = Connect-Pfa2Array -EndPoint $SourceFlashArrayIp -Credential $PureCred -IgnoreCertificateError -# confirm pod replication status +# Confirm pod replication status Get-Pfa2PodReplicaLink -Array $SourceFlashArray -LocalPodName $SourcePodName -# confirm clustered SQL Server service +# Confirm clustered SQL Server service Get-ClusterGroup -Cluster $ClusterName -Name $ClusterRole -# stop clustered service - taking volumes offline +# Stop clustered service - taking volumes offline Stop-ClusterGroup -Cluster $ClusterName -Name $ClusterRole -# confirm clustered service offline +# Confirm clustered service offline Get-ClusterGroup -Cluster $ClusterName -Name $ClusterRole -# demote Production Pod with Quiesce +# Demote the source pod with quiesce Update-Pfa2Pod -Array $SourceFlashArray -Name $SourcePodName -Quiesce $True -RequestedPromotionState "demoted" -# confirm Production Pod status - PromotionStatus : demoted +# Confirm source pod status - PromotionStatus : demoted Get-Pfa2Pod -Array $SourceFlashArray -Name $SourcePodName -# set target array details -$TargetFlashArrayIp = "TargetFlashArrayIpAddress" -$TargetPodName = "PodNameOnTargetArray" - - - -# connect to target flasharray +# Connect to the target FlashArray $TargetFlashArray = Connect-Pfa2Array -EndPoint $TargetFlashArrayIp -Credential $PureCred -IgnoreCertificateError -# promote pod +# Promote the target pod Update-Pfa2Pod -Array $TargetFlashArray -Name $TargetPodName -RequestedPromotionState "promoted" -# confirm pod promoted - PromotionStatus : promoted -Get-Pfa2Pod -Array $FlashArray -Name $TargetPodName - - - -# set node name on remote array -$NodeSameArray2 = "NodeOnRemoteArray" +# Confirm pod promoted - PromotionStatus : promoted +Get-Pfa2Pod -Array $TargetFlashArray -Name $TargetPodName -# move clustered role to node on target array -Move-ClusterGroup -Cluster $ClusterName -Name $ClusterRole -Node $NodeSameArray2 +# Move clustered role to node on the target array +Move-ClusterGroup -Cluster $ClusterName -Name $ClusterRole -Node $NodeRemoteArray -# start the clustered role +# Start the clustered role Start-ClusterGroup -Cluster $ClusterName -Name $ClusterRole