PureStoragePowerShellToolkit.DatabaseTools.psm1
<#
=========================================================================== Release version: 3.0.1 Revision information: Refer to the changelog.md file --------------------------------------------------------------------------- Maintained by: FlashArray Integrations and Evangelsigm Team @ Pure Storage Organization: Pure Storage, Inc. Filename: PureStoragePowerShellToolkit.DatabaseTools.psm1 Copyright: (c) 2023 Pure Storage, Inc. Module Name: PureStoragePowerShellToolkit.DatabaseTools.Dba Description: PowerShell Script Module (.psm1) -------------------------------------------------------------------------- Disclaimer: The sample module and documentation are provided AS IS and are not supported by the author or the author’s employer, unless otherwise agreed in writing. You bear all risk relating to the use or performance of the sample script and documentation. The author and the author’s employer disclaim all express or implied warranties (including, without limitation, any warranties of merchantability, title, infringement or fitness for a particular purpose). In no event shall the author, the author’s employer or anyone else involved in the creation, production, or delivery of the scripts be liable for any damages whatsoever arising out of the use or performance of the sample script and documentation (including, without limitation, damages for loss of business profits, business interruption, loss of business information, or other pecuniary loss), even if such person has been advised of the possibility of such damages. -------------------------------------------------------------------------- Contributors: Rob "Barkz" Barker @purestorage, Robert "Q" Quimbey @purestorage, Mike "Chief" Nelson, Julian "Doctor" Cates, Marcel Dussil @purestorage - https://en.pureflash.blog/ , Craig Dayton - https://github.com/cadayton , Jake Daniels - https://github.com/JakeDennis, Richard Raymond - https://github.com/data-sciences-corporation/PureStorage , The dbatools Team - https://dbatools.io , many more Puritans, and all of the Pure Code community who provide excellent advice, feedback, & scripts now and in the future. =========================================================================== #> #Requires -Version 5 #Requires -Modules @{ ModuleName='PureStoragePowerShellToolkit.FlashArray'; ModuleVersion='3.0.1' } #Requires -Modules @{ ModuleName='dbatools'; ModuleVersion='1.0.173' } #region Helper functions function Convert-UnitOfSize { [CmdletBinding()] param ( [Parameter(Mandatory, ValueFromPipeline, ValueFromPipelineByPropertyName)] [AllowNull()] $Value, $To = 1GB, $From = 1, $Decimals = 2 ) process { return [math]::Round($Value * $From / $To, $Decimals) } } function Write-Color { [CmdletBinding()] param( [Parameter(Position = 0, Mandatory, ValueFromPipeline)] [string[]] $Text, [ConsoleColor[]] $ForegroundColor = ([console]::ForegroundColor), [ConsoleColor[]] $BackgroundColor = ([console]::BackgroundColor), [int] $Indent = 0, [int] $LeadingSpace = 0, [int] $TrailingSpace = 0, [switch] $NoNewLine ) begin { $baseParams = @{ ForegroundColor = [console]::ForegroundColor BackgroundColor = [console]::BackgroundColor NoNewline = $true } # Add leading lines Write-Host ("`n" * $LeadingSpace) @baseParams } process { # Add TABs before text Write-Host ("`t" * $Indent) @baseParams if ($PSBoundParameters.ContainsKey('ForegroundColor') -or $PSBoundParameters.ContainsKey('BackgroundColor')) { $writeParams = $baseParams.Clone() for ($i = 0; $i -lt $Text.Count; $i++) { if ($i -lt $ForegroundColor.Count) { $writeParams['ForegroundColor'] = $ForegroundColor[$i] } if ($i -lt $BackgroundColor.Count) { $writeParams['BackgroundColor'] = $BackgroundColor[$i] } Write-Host $Text[$i] @writeParams } } else { Write-Host $Text -NoNewline } if (-not $NoNewLine) { Write-Host } } end { if (-not $NoNewLine) { Write-Host ("`n" * $TrailingSpace) @baseParams } } } #endregion Helper functions function Invoke-Pfa2DynamicDataMasking { <# .SYNOPSIS A PowerShell function to apply data masks to database columns using the SQL Server dynamic data masking feature. .DESCRIPTION This function uses the information stored in the extended properties of a database: sys.extended_properties.name = 'DATAMASK' to obtain the dynamic data masking function to apply at column level. Columns of the following data type are currently supported: - int - bigint - char - nchar - varchar - nvarchar Using the c_address column in the tpch customer table as an example, the DATAMASK extended property can be applied to the column as follows: exec sp_addextendedproperty @name = N'DATAMASK' ,@value = N'(FUNCTION = 'partial(0, "XX", 20)'' ,@level0type = N'Schema', @level0name = 'dbo' ,@level1type = N'Table', @level1name = 'customer' ,@level2type = N'Column', @level2name = 'c_address' GO .PARAMETER SqlInstance Required. The SQL Server instance of the database that data masking is to be applied to. .PARAMETER Database Required. The name of the database that data masking is to be applied to. .PARAMETER SqlCredential Optional. Credential for the SQL Server instance. .EXAMPLE Invoke-Pfa2DynamicDataMasking -SqlInstance Z-STN-WIN2016-A\DEVOPSDEV -Database tpch-no-compression Applies data masks to database columns using the SQL Server dynamic data masking feature. .EXAMPLE Invoke-Pfa2DynamicDataMasking -SqlInstance Z-STN-WIN2016-A\DEVOPSDEV -Database tpch-no-compression -SqlCredential (Get-Credential) Applies data masks to database columns using the SQL Server dynamic data masking feature. Asks for SQL Server instance credentials. .NOTES Note that it has dependencies on the dbatools module which is installed with this module. #> [CmdletBinding()] param( [parameter(mandatory = $true)][Sqlcollaborative.Dbatools.Parameter.DbaInstanceParameter] $SqlInstance, [parameter(mandatory = $true)][string] $Database, [parameter(mandatory = $false)][pscredential] $SqlCredential ) $sql = @" BEGIN DECLARE @sql_statement nvarchar(1024) ,@error_message varchar(1024) DECLARE apply_data_masks CURSOR FOR SELECT 'ALTER TABLE ' + tb.name + ' ALTER COLUMN ' + c.name + + ' ADD MASKED WITH ' + CAST(p.value AS char) + ''')' FROM sys.columns c JOIN sys.types t ON c.user_type_id = t.user_type_id LEFT JOIN sys.index_columns ic ON ic.object_id = c.object_id AND ic.column_id = c.column_id LEFT JOIN sys.indexes i ON ic.object_id = i.object_id AND ic.index_id = i.index_id JOIN sys.tables tb ON tb.object_id = c.object_id JOIN sys.extended_properties AS p ON p.major_id = tb.object_id AND p.minor_id = c.column_id AND p.class = 1 WHERE t.name IN ('int', 'bigint', 'char', 'nchar', 'varchar', 'nvarchar'); OPEN apply_data_masks FETCH NEXT FROM apply_data_masks INTO @sql_statement; WHILE @@FETCH_STATUS = 0 BEGIN PRINT 'Applying data mask: ' + @sql_statement; BEGIN TRY EXEC sp_executesql @stmt = @sql_statement END TRY BEGIN CATCH SELECT @error_message = ERROR_MESSAGE(); PRINT 'Application of data mask failed with: ' + @error_message; END CATCH; FETCH NEXT FROM apply_data_masks INTO @sql_statement END; CLOSE apply_data_masks DEALLOCATE apply_data_masks; END; "@ Invoke-DbaQuery -Query $sql @PSBoundParameters } function Invoke-Pfa2DbRefresh { <# .SYNOPSIS A PowerShell function to refresh one or more SQL Server databases (the destination) from either a snapshot, volume, or database. .DESCRIPTION A PowerShell function to refresh one or more SQL Server databases either from: - a snapshot specified by its name - a volume specified by its name - a source database directly - a snapshot picked from a list of snapshots associated with the specified volume or volume the source database resides on This function will detect and repair orpaned users in refreshed databases and optionally apply data masking, based on either: - the dynamic data masking functionality available in SQL Server version 2016 onwards, - static data masking as specified by JSON file .PARAMETER DatabaseName Required. The name of the database to refresh, note that it is assumed that source and target database(s) are named the same. .PARAMETER SourceSnapshotName Required. The name of the source snapshot. .PARAMETER SourceVolumeName Required. The name of the source volume. .PARAMETER SourceSqlInstance Required. The source SQL Server instance. .PARAMETER SqlInstance Required. This can be one or multiple SQL Server instance(s) that host the database(s) to be refreshed, in the case that the function is invoked to refresh databases across more than one instance, the list of target instances should be spedcified as an array. .PARAMETER Endpoint Required. FQDN or IP address representing the FlashArray that the volumes for the source and refresh target databases reside on. .PARAMETER Credential Optional. Credential for the FlashArray. .PARAMETER PromptForSnapshot Optional. This is an optional flag that if specified will result in a list of snapshots being displayed for the specified volume or volume the source database resides on that the user can select one from. .PARAMETER ForceOffline Optional. Specifying this switch will cause refresh target databases for be forced offline via WITH ROLLBACK IMMEDIATE. .PARAMETER ApplyDataMasks Optional. Specifying this optional flag will cause data masks to be applied, in the sense that function Invoke-Pfa2DynamicDataMasking will be invoked from this function. For documentation on Invoke-Pfa2DynamicDataMasking, use the command Get-Help Invoke-Pfa2DynamicDataMasking -Detailed. .PARAMETER StaticDataMaskFile Optional. Specifying this optional flag will cause static data masks to be applied, in the sense that function Invoke-Pfa2StaticDataMasking will be invoked from this function. For documentation on Invoke-Pfa2StaticDataMasking, use the command Get-Help Invoke-Pfa2StaticDataMasking -Detailed. .PARAMETER JobPollInterval Optional. Interval at which background job status is poll. Default is 1 second. .EXAMPLE Invoke-Pfa2DbRefresh -DatabaseName 'devops-db' -SourceSnapshotName 'devops.snap05' -SqlInstance 'devops-tst' -Endpoint 'myarray.mydomain.com' Refresh 'devops-db' database on the 'devops-tst' SQL Server instance from 'devops.snap05' snapshot on the 'myarray.mydomain.com' FlashArray. .EXAMPLE Invoke-Pfa2DbRefresh -DatabaseName 'devops-db' -Snapshot 'devops.snap05' -SqlInstance 'devops-tst' -Endpoint 'myarray.mydomain.com' Refresh 'devops-db' database on the 'devops-tst' SQL Server instance from 'devops.snap05' snapshot on the 'myarray.mydomain.com' FlashArray. .EXAMPLE Invoke-Pfa2DbRefresh -DatabaseName 'devops-db' -SourceVolumeName 'devops' -SqlInstance 'devops-tst' -Endpoint 'myarray.mydomain.com' Refresh 'devops-db' database on the 'devops-tst' SQL Server instance from 'devops' volume on the 'myarray.mydomain.com' FlashArray. .EXAMPLE Invoke-Pfa2DbRefresh -DatabaseName 'devops-db' -Volume 'devops' -SqlInstance 'devops-tst' -Endpoint 'myarray.mydomain.com' Refresh 'devops-db' database on the 'devops-tst' SQL Server instance from 'devops' volume on the 'myarray.mydomain.com' FlashArray. .EXAMPLE Invoke-Pfa2DbRefresh -DatabaseName 'devops-db' -Volume 'devops' -SqlInstance 'devops-tst' -Endpoint 'myarray.mydomain.com' -PromptForSnapshot Refresh 'devops-db' database on the 'devops-tst' SQL Server instance from a snapshot selected from a list of snapshots associated with the 'devops' volume on the 'myarray.mydomain.com' FlashArray. .EXAMPLE Invoke-Pfa2DbRefresh -DatabaseName 'devops-db' -SourceSqlInstance 'devops-prod' -SqlInstance 'devops-tst' -Endpoint 'myarray.mydomain.com' Refresh 'devops-db' database on the 'devops-tst' SQL Server instance from the volume on wich database on the 'devops-prod' SQL Server instance resides on. .EXAMPLE Invoke-Pfa2DbRefresh -DatabaseName 'devops-db' -SourceSqlInstance 'devops-prod' -SqlInstance 'devops-tst' -Endpoint 'myarray.mydomain.com' -PromptForSnapshot Refresh 'devops-db' database on the 'devops-tst' SQL Server instance from a snapshot selected from a list of snapshots associated with the volume on wich database on the 'devops-prod' SQL Server instance resides on. .EXAMPLE Invoke-Pfa2DbRefresh -DatabaseName 'devops-db' -Volume 'devops' -SqlInstance 'devops-tst01', 'devops-tst02' -Endpoint 'myarray.mydomain.com' Refresh 'devops-db' database on the 'devops-tst01' and 'devops-tst02' SQL Server instances from 'devops' volume on the 'myarray.mydomain.com' FlashArray. .EXAMPLE Invoke-Pfa2DbRefresh -DatabaseName 'devops-db' -Volume 'devops' -SqlInstance 'devops-tst' -Endpoint 'myarray.mydomain.com' -ForceOffline Refresh 'devops-db' database on the 'devops-tst' SQL Server instance from 'devops' volume on the 'myarray.mydomain.com' FlashArray. The database on the 'devops-tst' SQL Server instance is forced offline prior to its underlying volume being overwritten. .NOTES This cmdlet can utilize the global credential variable for FlashArray authentication. Set the credential variable by using the command Set-Pfa2Credential. Known Restrictions ------------------ 1. This function does not work for databases associated with failover cluster instances. 2. This function cannot be used to seed secondary replicas in availability groups using databases in the primary replica. 3. The function assumes that all database files and the transaction log reside on a single FlashArray volume. Note that it has dependencies on the dbatools module which is installed with this module. #> [CmdletBinding()] param( [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] [string]$DatabaseName, [Parameter(Mandatory = $true, ParameterSetName = 'Snapshot')] [ValidateNotNullOrEmpty()] [Alias('Snapshot')] [string]$SourceSnapshotName, [Parameter(Mandatory = $true, ParameterSetName = 'Volume')] [ValidateNotNullOrEmpty()] [Alias('Volume')] [string]$SourceVolumeName, [Parameter(Mandatory = $true, ParameterSetName = 'Database')] [ValidateNotNull()] [DbaInstanceParameter]$SourceSqlInstance, [Parameter(ParameterSetName = 'Volume')] [Parameter(ParameterSetName = 'Database')] [switch]$PromptForSnapshot, [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] [DbaInstanceParameter[]]$SqlInstance, [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] [string]$Endpoint, [switch]$ForceOffline, [switch]$ApplyDataMask, [string]$StaticDataMaskFile, [int]$JobPollInterval = 1, [pscredential]$Credential = (Get-Pfa2Credential) ) trap { $exceptionMessage = $_.Exception.Message Write-Error "Failed to $goal. $exceptionMessage" return } $ErrorActionPreference = 'Stop' $s = [int][math]::Floor([console]::WindowWidth / 3) $x = [console]::WindowWidth - $s $start = Get-Date $goal = "connect to FlashArray endpoint $Endpoint" $flashArray = Connect-Pfa2Array -Endpoint $Endpoint -Credential $Credential -IgnoreCertificateError try { Write-Color "FlashArray endpoint $Endpoint".PadRight($x), 'CONNECTED'.PadLeft($s) -ForegroundColor Yellow, Green if ($PSCmdlet.ParameterSetName -in 'Database', 'Volume') { if ($PSCmdlet.ParameterSetName -eq 'Database') { $goal = "connect to source SQL Server $SourceSqlInstance" $instance = Connect-DbaInstance -SqlInstance $SourceSqlInstance try { Write-Color "Source SQL Server instance $instance".PadRight($x), 'CONNECTED'.PadLeft($s) -ForegroundColor Yellow, Green $goal = "connect to source database $DatabaseName" $database = Get-DbaDatabase -SqlInstance $instance -Database $DatabaseName if ($null -eq $database) { throw 'Database not found.' } $goal = 'connect to source server' $sp = @{} if (-not $SourceSqlInstance.IsLocalHost) { $sp.Add('ComputerName', $SourceSqlInstance.ComputerName) } $cimSession = New-CimSession @sp try { Write-Color "Source server $($cimSession.ComputerName)".PadRight($x), 'CONNECTED'.PadLeft($s) -ForegroundColor Yellow, Green $goal = 'get source disk' $v = Get-Volume -FilePath $database.PrimaryFilePath -CimSession $cimSession $disk = $v | Get-Partition -CimSession $cimSession | Get-Disk -CimSession $cimSession } finally { Remove-CimSession $cimSession } $goal = 'get source volume' $sn = $disk.SerialNumber $source = Get-Pfa2Volume -Array $flashArray -Filter "serial='$sn'" -Limit 1 if (-not $source) { throw 'Source volume not found.' } } finally { $instance | Disconnect-DbaInstance | Out-Null } } else { $goal = 'get source volume' $source = Get-Pfa2Volume -Array $flashArray -Name $SourceVolumeName } if ($PromptForSnapshot) { $goal = 'get source volume snapshot' $snapshots = Get-Pfa2VolumeSnapshot -Array $flashArray -SourceNames $source.name | ForEach-Object { $i = 1 } { [pscustomobject]@{'Number' = $i++; 'Name' = $_.name; 'Created' = $_.created } } if ($snapshots) { $snapshots | Format-Table [int]$num = Read-Host -Prompt 'Select snapshot number' if ($num -gt 0) { $snap = $snapshots[$num - 1] } } if (-not $snap) { throw 'No snapshot found\selected.' } $source = $snap } } else { $goal = 'get source snapshot' $source = Get-Pfa2VolumeSnapshot -Array $flashArray -Name $SourceSnapshotName } Write-Color "Get source $($source.name)".PadRight($x), 'DONE'.PadLeft($s) -ForegroundColor Yellow, Green } finally { Disconnect-Pfa2Array -Array $flashArray } $init = [scriptblock]::Create('function Write-Color {' + ${function:Write-Color} + "}`n`nImport-Module dbatools") $goal = 'start background job' $jobs = foreach ($di in $SqlInstance) { Start-Job -InitializationScript $init -ScriptBlock $function:CoreDbRefresh -ArgumentList $di.FullName, ` $DatabaseName, ` $Endpoint, ` $Credential, ` $source.name, ` $ForceOffline.IsPresent, ` $ApplyDataMask.IsPresent, ` $StaticDataMaskFile, ` $s, $x } Write-Color "Background job ($($jobs.Count))".PadRight($x), 'PROCESSING'.PadLeft($s) -ForegroundColor Yellow, Green $goal = 'process background job' $running = $jobs | Where-Object State -eq 'Running' while ($running) { $running | Receive-Job -ea Continue Start-Sleep $JobPollInterval $running = $jobs | Where-Object State -eq 'Running' } $goal = 'clear background job' $jobs | Receive-Job -ea Continue $jobs | Remove-Job Write-Color "Background job ($($jobs.Count))".PadRight($x), 'DONE'.PadLeft($s) -ForegroundColor Yellow, Green Write-Host ' ' Write-Host '-------------------------------------------------------' -ForegroundColor Green Write-Host ' ' Write-Host 'D A T A B A S E R E F R E S H C O M P L E T E' -ForegroundColor Green Write-Host ' ' Write-Host ' Duration (s) = ' ((Get-Date) - $start).TotalSeconds -ForegroundColor White Write-Host ' ' Write-Host '-------------------------------------------------------' -ForegroundColor Green } function CoreDbRefresh { param( [DbaInstanceParameter]$sqlInstance, [string]$databaseName, [string]$endpoint, [pscredential]$credential, [string]$source, [bool]$forceOffline, [bool]$applyDataMask, [string]$staticDataMaskFile, [int]$s, [int]$x ) trap { $exceptionMessage = $_.Exception.Message Write-Error "Failed to $goal. $exceptionMessage" return } $ErrorActionPreference = 'Stop' $goal = "connect to FlashArray endpoint $endpoint" $flashArray = Connect-Pfa2Array -Endpoint $endpoint -Credential $Credential -IgnoreCertificateError try { Write-Color "FlashArray endpoint $endpoint".PadRight($x), 'CONNECTED'.PadLeft($s) -ForegroundColor Yellow, Green $goal = "connect to destination SQL Server $sqlInstance" $instance = Connect-DbaInstance -SqlInstance $sqlInstance try { Write-Color "Destination SQL Server instance $instance".PadRight($x), 'CONNECTED'.PadLeft($s) -ForegroundColor Yellow, Green $goal = "connect to destination database $databaseName" $database = Get-DbaDatabase -SqlInstance $instance -Database $databaseName if ($null -eq $database) { throw 'Database not found.' } $goal = 'connect to destination server' $sp = @{} if (-not $sqlInstance.IsLocalHost) { $sp.Add('ComputerName', $sqlInstance.ComputerName) } $cimSession = New-CimSession @sp try { Write-Color "Destination server $($cimSession.ComputerName)".PadRight($x), 'CONNECTED'.PadLeft($s) -ForegroundColor Yellow, Green $goal = 'get destination disk' $v = Get-Volume -FilePath $database.PrimaryFilePath -CimSession $cimSession $disk = $v | Get-Partition -CimSession $cimSession | Get-Disk -CimSession $cimSession $goal = 'get destination volume' $sn = $disk.SerialNumber $volume = Get-Pfa2Volume -Array $flashArray -Filter "serial='$sn'" -Limit 1 if (-not $volume) { throw 'Volume not found.' } $goal = "offline destination database" if ($forceOffline) { $database | Invoke-DbaQuery -Query "ALTER DATABASE [$databaseName] SET OFFLINE WITH ROLLBACK IMMEDIATE" $dff = ' (force)' } else { $database.SetOffline() } try { Write-Color ('Destination database' + $dff).PadRight($x), 'OFFLINE'.PadLeft($s) -ForegroundColor Yellow, Green $goal = 'offline destination disk' $disk | Set-Disk -IsOffline $true -CimSession $cimSession try { Write-Color 'Destination disk'.PadRight($x), 'OFFLINE'.PadLeft($s) -ForegroundColor Yellow, Green $start = Get-Date $goal = 'overwrite volume' New-Pfa2Volume -Array $flashArray -Name $volume.name -SourceName $source -Overwrite $true | Out-Null Write-Color "Volume overwrite ($(((Get-Date) - $start).TotalSeconds) sec.)".PadRight($x), 'DONE'.PadLeft($s) -ForegroundColor Yellow, Green } finally { $goal = 'online destination disk' $disk | Set-Disk -IsOffline $false -CimSession $cimSession Set-Volume -DriveLetter $v.DriveLetter -NewFileSystemLabel $v.FileSystemLabel -CimSession $cimSession Write-Color 'Destination disk'.PadRight($x), 'ONLINE'.PadLeft($s) -ForegroundColor Yellow, Green } } finally { $goal = "online destination database" $database.SetOnline() Write-Color 'Destination database'.PadRight($x), 'ONLINE'.PadLeft($s) -ForegroundColor Yellow, Green } } finally { Remove-CimSession $cimSession } } finally { $instance | Disconnect-DbaInstance | Out-Null } } finally { Disconnect-Pfa2Array -Array $flashArray } if ($applyDataMask) { $goal = "apply dynamic data masking to $databaseName" Invoke-Pfa2DynamicDataMasking -SqlInstance $instance -Database $databaseName | Out-Null Write-Color 'Dynamic data masking'.PadRight($x), 'DONE'.PadLeft($s) -ForegroundColor Yellow, Green } if ($staticDataMaskFile) { $goal = "apply static data masking to $databaseName" Invoke-Pfa2StaticDataMasking -SqlInstance $instance -Database $databaseName -DataMaskFile $staticDataMaskFile | Out-Null Write-Color 'Static data masking'.PadRight($x), 'DONE'.PadLeft($s) -ForegroundColor Yellow, Green } $goal = 'repair orphaned users' Repair-DbaDbOrphanUser -SqlInstance $instance -Database $database | Out-Null Write-Color 'Repair orphaned users'.PadRight($x), 'DONE'.PadLeft($s) -ForegroundColor Yellow, Green } function Invoke-Pfa2StaticDataMasking { <# .SYNOPSIS A PowerShell function to statically mask data in char, varchar and/or nvarchar columns using a MD5 hashing function. .DESCRIPTION This PowerShell function uses as input a JSON file created by calling the New-DbaDbMaskingConfig PowerShell function. Data in the columns specified in this file which are of the type char, varchar or nvarchar are envrypted using a MD5 hash. .PARAMETER SqlInstance Required. The SQL Server instance of the database that static data masking is to be applied to. .PARAMETER Database Required. The name of the database that static data masking is to be applied to. .PARAMETER DataMaskFile Required. Absolute path to the JSON file generated by invoking New-DbaDbMaskingConfig. The file can be subsequently editted by hand to suit the data masking requirements of this function's user. Currently, static data masking is only supported for columns with char, varchar, nvarchar, int and bigint data types. .PARAMETER Table Optional. Applies data masking only on specified tables, ignoring other tables in JSON file. .PARAMETER SqlCredential Optional. Credential for the SQL Server instance. .EXAMPLE Invoke-Pfa2StaticDataMasking -SqlInstance Z-STN-WIN2016-A\DEVOPSDEV -Database tpch-no-compression -DataMaskFile 'C:\devops\tpch-no-compression.tables.json' Statically masks data in columns specified by JSON file. .EXAMPLE Invoke-Pfa2StaticDataMasking -SqlInstance Z-STN-WIN2016-A\DEVOPSDEV -Database tpch-no-compression -DataMaskFile 'C:\devops\tpch-no-compression.tables.json' -SqlCredential (Get-Credential) Statically masks data in columns specified by JSON file. Asks for SQL Server instance credentials. .NOTES Note that it has dependencies on the dbatools module which is installed with this module. #> [CmdletBinding(SupportsShouldProcess)] param( [parameter(mandatory = $true)] [Sqlcollaborative.Dbatools.Parameter.DbaInstanceParameter] $SqlInstance, [parameter(mandatory = $true)] [string] $Database, [parameter(mandatory = $true)] [string] $DataMaskFile, [parameter()] [pscredential] $SqlCredential, [parameter()] [string[]] $Table ) $queryParams = @{ SqlInstance = $SqlInstace Database = $Database QueryTimeout = 999999 } if ($PSBoundParameters.ContainsKey('SqlCredential')) { $queryParams.Add('SqlCredential', $SqlCredential) } if ($DataMaskFile.ToString().StartsWith('http')) { $config = Invoke-RestMethod -Uri $DataMaskFile } else { $config = Get-Content -Path $DataMaskFile -ErrorAction Stop | ConvertFrom-Json -ErrorAction Stop } foreach ($tabletest in $config.Tables) { if (($Table -and $tabletest.Name -notin $Table) -or -not $PSCmdlet.ShouldProcess("[$($tabletest.Name)]", 'Statically mask table')){ continue } $columnExpressions = $tabletest.Columns.foreach{ $column = $_ $statement = switch($column.ColumnType) { {$_ -in 'varchar', 'char', 'nvarchar'} { "SUBSTRING(CONVERT(VARCHAR, HASHBYTES('MD5', $($column.Name)), 1), 1, $($column.MaxValue))" } 'int' { "ABS(CHECKSUM(NEWID())) % 2147483647" } 'bigint' { "ABS(CHECKSUM(NEWID()))" } default { Write-Error "$($column.ColumnType) is not supported, please remove the column $($column.Name) from the $($tabletest.Name) table" } } "$($column.Name) = $statement" } $queryParams['Query'] = "UPDATE $($tabletest.Name) SET $($columnExpressions -join ', ')" Write-Verbose "Statically masking table $($tabletest.Name) using $($queryParams['Query'])" Invoke-DbaQuery @queryParams } } function New-Pfa2DbSnapshot { <# .SYNOPSIS A PowerShell function to create a FlashArray snapshot of the volume that a database resides on. .DESCRIPTION A PowerShell function to create a FlashArray snapshot of the volume that a database resides on, based in the values of the following parameters: .PARAMETER SqlInstance Required. The SQL Server instance of the database that resides on a FlashArray volume. .PARAMETER Database Required. The name of the database that resides on a FlashArray volume. .PARAMETER Endpoint Required. FQDN or IP address of the FlashArray. .PARAMETER SqlCredential Optional. Credential for the SQL Server instance. .PARAMETER Credential Optional. Credential for the FlashArray. .EXAMPLE New-Pfa2DbSnapshot -SqlInstance devops-prd -Database devops-db -Endpoint myarray.mydomain.com Creates a snapshot of volume on the myarray.mydomain.com FlashArray that stores the devops-db database on the devops-prd instance. .EXAMPLE New-Pfa2DbSnapshot -SqlInstance devops-prd -Database devops-db -Endpoint myarray.mydomain.com -Credential (Get-Credential) Creates a snapshot of volume on the myarray.mydomain.com FlashArray that stores the devops-db database on the devops-prd instance. Asks for FlashArray credentials. .EXAMPLE New-Pfa2DbSnapshot -SqlInstance devops-prd -Database devops-db -SqlCredential (Get-Credential) -Endpoint myarray.mydomain.com Creates a snapshot of volume on the myarray.mydomain.com FlashArray that stores the devops-db database on the devops-prd instance. Asks for SQL Server instance credentials. .NOTES This cmdlet can utilize the global credential variable for FlashArray authentication. Set the credential variable by using the command Set-Pfa2Credential. Known Restrictions ------------------ 1. This function does not work for databases associated with failover cluster instances. 2. This function cannot be used to seed secondary replicas in availability groups using databases in the primary replica. 3. The function assumes that all database files and the transaction log reside on a single FlashArray volume. Note that it has dependencies on the dbatools module which is installed with this module. #> param( [parameter(mandatory = $true)] [Sqlcollaborative.Dbatools.Parameter.DbaInstanceParameter] $SqlInstance, [parameter(mandatory = $true)] [string] $Database, [parameter(mandatory = $true)] [string] $Endpoint, [parameter()] [pscredential] $SqlCredential, [Parameter()] [pscredential] $Credential = ( Get-Pfa2Credential ) ) $sqlParams = @{ SqlInstance = $SqlInstance } if ($PSBoundParameters.ContainsKey('SqlCredential')) { $sqlParams.Add('SqlCredential', $SqlCredential) } # Connect to FlashArray try { $flashArray = Connect-Pfa2Array -EndPoint $EndPoint -Credential $Credential -IgnoreCertificateError } catch { $exceptionMessage = $_.Exception.Message Write-Error "Failed to connect to FlashArray endpoint $Endpoint with: $exceptionMessage" Return } try { Write-Color -Text 'FlashArray endpoint : ', 'CONNECTED' -ForegroundColor Yellow, Green try { $destDb = Get-DbaDatabase @sqlParams -Database $Database } catch { $exceptionMessage = $_.Exception.Message Write-Error "Failed to connect to destination database $SqlInstance.$Database with: $exceptionMessage" Return } Write-Color -Text 'Target SQL Server instance: ', $SqlInstance, ' - ', 'CONNECTED' -ForegroundColor Yellow, Green, Green, Green Write-Color -Text 'Target windows drive : ', $destDb.PrimaryFilePath.Split(':')[0] -ForegroundColor Yellow, Green try { $instance = Connect-DbaInstance @sqlParams $targetServer = $instance.ComputerNamePhysicalNetBIOS $instance | Disconnect-DbaInstance | Out-Null } catch { $exceptionMessage = $_.Exception.Message Write-Error "Failed to determine target server name with: $exceptionMessage" Return } Write-Color -Text 'Target SQL Server host : ', $targetServer -ForegroundColor Yellow, Green $getDbDisk = { param ( $filePath ) $dbDisk = Get-Partition -DriveLetter $filePath.Split(':')[0] | Get-Disk return $dbDisk } try { $targetDisk = Invoke-Command -ComputerName $targetServer -ScriptBlock $getDbDisk -ArgumentList $destDb.PrimaryFilePath } catch { $exceptionMessage = $_.Exception.Message Write-Error "Failed to determine the windows disk snapshot target with: $exceptionMessage" Return } Write-Color -Text 'Target disk serial number : ', $targetDisk.SerialNumber -ForegroundColor Yellow, Green try { $targetVolume = (Get-Pfa2Volume -Array $flashArray | Where-Object { $_.serial -eq $targetDisk.SerialNumber }).Name } catch { $exceptionMessage = $_.Exception.Message Write-Error "Failed to determine snapshot FlashArray volume with: $exceptionMessage" Return } $snapshotSuffix = '{0}-{1}-{2:HHmmss}' -f $SqlInstance.FullName.Replace('\', '-'), $Database, (Get-Date) Write-Color -Text 'Snapshot target Pfa volume: ', $targetVolume -ForegroundColor Yellow, Green Write-Color -Text 'Snapshot suffix : ', $snapshotSuffix -ForegroundColor Yellow, Green try { New-Pfa2VolumeSnapshot -Array $flashArray -SourceNames $targetVolume -Suffix $snapshotSuffix } catch { $exceptionMessage = $_.Exception.Message Write-Error "Failed to create snapshot for target database FlashArray volume with: $exceptionMessage" Return } } finally { Disconnect-Pfa2Array -Array $flashArray } } # Declare exports Export-ModuleMember -Function Invoke-Pfa2DynamicDataMasking Export-ModuleMember -Function Invoke-Pfa2StaticDataMasking Export-ModuleMember -Function New-Pfa2DbSnapshot Export-ModuleMember -Function Invoke-Pfa2DbRefresh # END |