diff --git a/README.md b/README.md
index 86abb12..7fe12a4 100644
--- a/README.md
+++ b/README.md
@@ -43,7 +43,16 @@ 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)
+
+| 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
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-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-archive/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
new file mode 100644
index 0000000..7af6767
--- /dev/null
+++ b/demos-archive/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 RDM
+
+
+# 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
+
+
+# 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 $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 $TargetArrayName -FlashArrayCredential $FlashArrayCredential | Where-Object {($_.Computer -contains $TargetSQLServer -and $_.HistoryId -match $VolumeSet)}
+
+
+
+# Dismount the snapshot
+Dismount-PsbSnapshotSet -flasharrayaddress $TargetArrayName -flasharraycredential $FlashArrayCredential -mountid $FindMount[0].mountid -computeraddress $TargetSQLServer -computercredential $SQLServerCredential
+
+
+
+# Mount the newer snapshot
+Mount-PsbSnapshotSet -HistoryId $Snapshot.HistoryId -FlashArrayAddress $TargetArrayName -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-archive/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
new file mode 100644
index 0000000..984dfdb
--- /dev/null
+++ b/demos-archive/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 = '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 RDM 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 $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 $TargetArrayName -FlashArrayCredential $FlashArrayCredential | Where-Object {($_.Computer -contains $TargetSQLServer -and $_.HistoryId -match $VolumeSet)}
+
+
+
+# Dismount the snapshot
+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 $TargetArrayName -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-archive/demos-backupsdk/Protection Group Database Refresh/README.md b/demos-archive/demos-backupsdk/Protection Group Database Refresh/README.md
new file mode 100644
index 0000000..10f6885
--- /dev/null
+++ b/demos-archive/demos-backupsdk/Protection Group Database Refresh/README.md
@@ -0,0 +1,45 @@
+# Protection Group Database Refresh
+**Protection Group Database Refresh Scripts**
+
+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 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 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. 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.
+
+
+
+
+
+**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/demos-backupsdk/Volume Set Create or Modify/README.md b/demos-archive/demos-backupsdk/Volume Set Create or Modify/README.md
new file mode 100644
index 0000000..6358298
--- /dev/null
+++ b/demos-archive/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-archive/demos-backupsdk/Volume Set Create or Modify/VolumeSet-Example.ps1 b/demos-archive/demos-backupsdk/Volume Set Create or Modify/VolumeSet-Example.ps1
new file mode 100644
index 0000000..f4e1989
--- /dev/null
+++ b/demos-archive/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
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..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-Credentials
+####################################################################################################################
-# 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
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
new file mode 100644
index 0000000..d8f6e6f
--- /dev/null
+++ b/demos-sdk2/Hyper-V CSV w SQL Server Snapshot/readme.md
@@ -0,0 +1,95 @@
+# 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` - 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 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. 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:**
+
+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.
+
+---
+
+## HyperV-TSQL-SnapshotBackup.ps1
+
+**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
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 98eeb44..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,11 +63,11 @@ $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
-# 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
@@ -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/VMFS-VMDK Snapshot/Point in Time Recovery - VMFS.ps1 b/demos-sdk2/VMFS-VMDK Snapshot/Point in Time Recovery - VMFS.ps1
new file mode 100644
index 0000000..e37ad3b
--- /dev/null
+++ b/demos-sdk2/VMFS-VMDK Snapshot/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 Sales.Customer"
+
+
+
+#########################
+# 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/VMFS-VMDK Snapshot/README.md b/demos-sdk2/VMFS-VMDK Snapshot/README.md
index e67b0dd..8c26738 100644
--- a/demos-sdk2/VMFS-VMDK Snapshot/README.md
+++ b/demos-sdk2/VMFS-VMDK Snapshot/README.md
@@ -1,46 +1,62 @@
-**VMFS/VMDK + SQL Server Snapshot Scripts**
-
-This folder contains VMFS/VMDK + SQL Server example snapshot scripts.
+# 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
+- `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 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).
+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. 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.
+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.
-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.
+**Usage Notes:**
-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.
+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.
-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
-**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.
-
-
+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._
+
+
+
+
+
diff --git a/demos-sdk2/VMFS-VMDK Snapshot/VMFS-VMDK Snapshot.ps1 b/demos-sdk2/VMFS-VMDK Snapshot/VMFS-VMDK Snapshot.ps1
index a1b05c7..417db12 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.
+# Each datastore also resides on a different FlashArray, to demonstrate use of async snapshot replication.
#
-# 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.
+# 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
-# 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)
+# 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...'
-# Set Credential - this assumes the same credential for the target VM and vCenter
-$Credential = Get-Credential
+# Connect to the source array
+$FlashArray = Connect-Pfa2Array -Endpoint $SourceArrayName -Credential ($Credential) -IgnoreCertificateError
+
+
+
+# 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,188 @@ $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
-}
+# Connect to the target array, authenticate. Remember disclaimer at the top!
+$FlashArray = Connect-Pfa2Array -Endpoint $TargetArrayName -Credential ($Credential) -IgnoreCertificateError
-# Offline the volumes that have SQL data
-Invoke-Command -Session $TargetVMSession -ScriptBlock { Get-Disk | Where-Object { $_.SerialNumber -eq $using:TargetDiskSerialNumber } | Set-Disk -IsOffline $True }
+# 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
-# 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 }
+# 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
+}
-# Remove the VMDK from the VM
-Remove-HardDisk -HardDisk $HardDisk -Confirm:$false
+# 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
-# 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') }
+# Present the new volume to the ESXi host group.
+New-Pfa2Connection -Array $FlashArray -HostGroupName $FAHostGroupName -VolumeName $NewClonedVolumeName
-# 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
+# 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 $VMESXiHost
-# Perform the volume overwrite (no intermediate snapshot needed!)
-New-Pfa2Volume -Array $FlashArray -Name $TargetVolumeName -SourceName $SourceVolumeName -Overwrite $True
+### 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
-# Rescan storage on each ESX host in the $Hosts array
-foreach ($VmHost in $Hosts) {
- Get-VMHostStorage -RescanAllHba -RescanVmfs -VMHost $VmHost | Out-Null
+# 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 })
}
+$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)
}
-# Remove powershell session
+###
+# 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
+}
+
+
+
+# Guest hard disk removed, now remove the stale datastore - this can take a min or two
+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 $TargetVMSession
+
+
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