functions/image/New-PSDCImage.ps1
function New-PSDCImage { <# .SYNOPSIS New-PSDCImage creates a new image .DESCRIPTION New-PSDCImage will create a new image based on a SQL Server database The command will either create a full backup or use the last full backup to create the image. Every image is created with the name of the database and a time stamp yyyyMMddHHmmss i.e "DB1_20180622171819.vhdx" .PARAMETER SourceSqlInstance Source SQL Server name or SMO object representing the SQL Server to connect to. This will be where the database is currently located .PARAMETER SourceSqlCredential Allows you to login to servers using SQL Logins as opposed to Windows Auth/Integrated/Trusted. To use: $scred = Get-Credential, then pass $scred object to the -SourceSqlCredential parameter. Windows Authentication will be used if SqlCredential is not specified. SQL Server does not accept Windows credentials being passed as credentials. To connect as a different Windows user, run PowerShell as that user. .PARAMETER SourceCredential Allows you to login to other parts of a system like folders. To use: $scred = Get-Credential, then pass $scred object to the -SourceCredential parameter. .PARAMETER DestinationSqlInstance SQL Server name or SMO object representing the SQL Server to connect to. This is the server to use to temporarily restore the database to create the image. .PARAMETER DestinationSqlCredential Allows you to login to servers using SQL Logins as opposed to Windows Auth/Integrated/Trusted. To use: $scred = Get-Credential, then pass $scred object to the -DestinationSqlCredential parameter. Windows Authentication will be used if SqlCredential is not specified. SQL Server does not accept Windows credentials being passed as credentials. To connect as a different Windows user, run PowerShell as that user. .PARAMETER DestinationCredential Allows you to login to other parts of a system like folders. To use: $scred = Get-Credential, then pass $scred object to the -DestinationCredential parameter. .PARAMETER PSDCSqlCredential Allows you to login to servers using SQL Logins as opposed to Windows Auth/Integrated/Trusted. This works similar as SqlCredential but is only meant for authentication to the PSDatabaseClone database server and database. By default the script will try to retrieve the configuration value "psdatabaseclone.informationstore.credential" .PARAMETER ImageNetworkPath Network path where to save the image. This has to be a UNC path .PARAMETER ImageLocalPath Local path where to save the image .PARAMETER Database Databases to create an image of .PARAMETER VhdType The type of the harddisk. This can either by VHD (version 1) or VHDX (version 2) The default is VHDX. .PARAMETER CreateFullBackup Create a new full backup of the database. The backup will be saved in the default backup directory .PARAMETER UseLastFullBackup Use the last full backup created for the database .PARAMETER CopyOnlyBackup Create a backup as COPY_ONLY .PARAMETER MaskingFile Configuration file that contains the which tables and columns need to be masked .PARAMETER Force Forcefully execute commands when needed .PARAMETER EnableException By default, when something goes wrong we try to catch it, interpret it and give you a friendly warning message. This avoids overwhelming you with "sea of red" exceptions, but is inconvenient because it basically disables advanced scripting. Using this switch turns this "nice by default" feature off and enables you to catch exceptions with your own try/catch. .PARAMETER WhatIf If this switch is enabled, no actions are performed but informational messages will be displayed that explain what would happen if the command were to run. .PARAMETER Confirm If this switch is enabled, you will be prompted for confirmation before executing any operations that change state. .NOTES Author: Sander Stad (@sqlstad, sqlstad.nl) Website: https://psdatabaseclone.org Copyright: (C) Sander Stad, sander@sqlstad.nl License: MIT https://opensource.org/licenses/MIT .LINK https://psdatabaseclone.org/ .EXAMPLE New-PSDCImage -SourceSqlInstance SQLDB1 -DestinationSqlInstance SQLDB2 -ImageLocalPath C:\Temp\images\ -Database DB1 -CreateFullBackup Create an image for databas DB1 from SQL Server SQLDB1. The temporary destination will be SQLDB2. The image will be saved in C:\Temp\images. .EXAMPLE New-PSDCImage -SourceSqlInstance SQLDB1 -DestinationSqlInstance SQLDB2 -ImageLocalPath C:\Temp\images\ -Database DB1 -UseLastFullBackup Create an image from the database DB1 on SQLDB1 using the last full backup and use SQLDB2 as the temporary database server. The image is written to c:\Temp\images #> [CmdLetBinding(SupportsShouldProcess = $true)] [OutputType('PSDCImage')] param( [parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] [DbaInstanceParameter]$SourceSqlInstance, [PSCredential]$SourceSqlCredential, [PSCredential]$SourceCredential, [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] [DbaInstanceParameter]$DestinationSqlInstance, [PSCredential]$DestinationSqlCredential, [PSCredential]$DestinationCredential, [PSCredential]$PSDCSqlCredential, [Parameter(Mandatory = $true, ValueFromPipeline = $true)] [object[]]$Database, [string]$ImageNetworkPath, [string]$ImageLocalPath, [ValidateSet('VHD', 'VHDX', 'vhd', 'vhdx')] [string]$VhdType, [switch]$CreateFullBackup, [switch]$UseLastFullBackup, [switch]$CopyOnlyBackup, [Alias('MaskingConfigFile', 'MaskingConfigFilePath')] [string]$MaskingFile, [switch]$Force, [switch]$EnableException ) begin { # Check if the console is run in Administrator mode if ( -not (Test-PSDCElevated) ) { Stop-PSFFunction -Message "Module requires elevation. Please run the console in Administrator mode" } # Check if the setup has ran if (-not (Get-PSFConfigValue -FullName psdatabaseclone.setup.status)) { Stop-PSFFunction -Message "The module setup has NOT yet successfully run. Please run 'Set-PSDCConfiguration'" return } # Checking parameters if (-not $ImageNetworkPath -and -not $ImageLocalPath) { Stop-PSFFunction -Message "Please enter the network path where to save the images" return } # Check the vhd type if (-not $VhdType) { Write-PSFMessage -Message "Setting vhd type to 'VHDX'" -Level Verbose $VhdType = 'VHDX' } # Get the information store $informationStore = Get-PSFConfigValue -FullName psdatabaseclone.informationstore.mode if ($informationStore -eq 'SQL') { # Get the module configurations $pdcSqlInstance = Get-PSFConfigValue -FullName psdatabaseclone.database.Server $pdcDatabase = Get-PSFConfigValue -FullName psdatabaseclone.database.name if (-not $PSDCSqlCredential) { $pdcCredential = Get-PSFConfigValue -FullName psdatabaseclone.informationstore.credential -Fallback $null } else { $pdcCredential = $PSDCSqlCredential } # Test the module database setup if ($PSCmdlet.ShouldProcess("Test-PSDCConfiguration", "Testing module setup")) { try { Test-PSDCConfiguration -SqlCredential $pdcCredential -EnableException } catch { Stop-PSFFunction -Message "Something is wrong in the module configuration" -ErrorRecord $_ -Continue } } } Write-PSFMessage -Message "Started image creation" -Level Verbose # Try connecting to the instance Write-PSFMessage -Message "Attempting to connect to Sql Server $SourceSqlInstance.." -Level Verbose try { $sourceServer = Connect-DbaInstance -SqlInstance $SourceSqlInstance -SqlCredential $SourceSqlCredential } catch { Stop-PSFFunction -Message "Could not connect to Sql Server instance $SourceSqlInstance" -ErrorRecord $_ -Target $SourceSqlInstance return } # Cleanup the values in the network path if ($ImageNetworkPath.EndsWith("\")) { $ImageNetworkPath = $ImageNetworkPath.Substring(0, $ImageNetworkPath.Length - 1) } # Make up the data from the network path try { [uri]$uri = New-Object System.Uri($ImageNetworkPath) $uriHost = $uri.Host } catch { Stop-PSFFunction -Message "The image network path $ImageNetworkPath is not valid" -ErrorRecord $_ -Target $ImageNetworkPath return } # Setup the computer object $computer = [PsfComputer]$uriHost if (-not $computer.IsLocalhost) { # Get the result for the remote test $resultPSRemote = Test-PSDCRemoting -ComputerName $computer -Credential $Credential # Check the result if ($resultPSRemote.Result) { $command = [scriptblock]::Create("Import-Module PSDatabaseClone -Force") try { Invoke-PSFCommand -ComputerName $computer -ScriptBlock $command -Credential $Credential } catch { Stop-PSFFunction -Message "Couldn't import module remotely" -Target $command return } } else { Stop-PSFFunction -Message "Couldn't connect to host remotely.`nVerify that the specified computer name is valid, that the computer is accessible over the network, and that a firewall exception for the WinRM service is enabled and allows access from this computer" -Target $resultPSRemote -Continue } } # Get the local path from the network path if (-not $ImageLocalPath) { if ($PSCmdlet.ShouldProcess($ImageNetworkPath, "Converting UNC path to local path")) { try { # Check if computer is local if ($computer.IsLocalhost) { $ImageLocalPath = Convert-PSDCUncPathToLocalPath -UncPath $ImageNetworkPath -EnableException } else { $command = "Convert-PSDCUncPathToLocalPath -UncPath `"$ImageNetworkPath`" -EnableException" $commandGetLocalPath = [ScriptBlock]::Create($command) $ImageLocalPath = Invoke-PSFCommand -ComputerName $computer -ScriptBlock $commandGetLocalPath -Credential $DestinationCredential if (-not $ImageLocalPath) { return } } Write-PSFMessage -Message "Converted '$ImageNetworkPath' to '$ImageLocalPath'" -Level Verbose } catch { Stop-PSFFunction -Message "Something went wrong getting the local image path" -Target $ImageNetworkPath return } } } else { # Cleanup the values in the network path if ($ImageLocalPath.EndsWith("\")) { $ImageLocalPath = $ImageLocalPath.Substring(0, $ImageLocalPath.Length - 1) } # Check if the assigned value in the local path corresponds to the one retrieved try { # Check if computer is local if ($computer.IsLocalhost) { $convertedLocalPath = Convert-PSDCUncPathToLocalPath -UncPath $ImageNetworkPath -EnableException } else { $command = "Convert-PSDCUncPathToLocalPath -UncPath `"$ImageNetworkPath`" -EnableException" $commandGetLocalPath = [ScriptBlock]::Create($command) $convertedLocalPath = Invoke-PSFCommand -ComputerName $computer -ScriptBlock $commandGetLocalPath -Credential $DestinationCredential } Write-PSFMessage -Message "Converted '$ImageNetworkPath' to '$ImageLocalPath'" -Level Verbose # Check if the ImageLocalPath and convertedLocalPath are the same if ($ImageLocalPath -ne $convertedLocalPath) { Stop-PSFFunction -Message "The local path '$ImageLocalPath' is not the same location as the network path '$ImageNetworkPath'" -Target $ImageNetworkPath return } } catch { Stop-PSFFunction -Message "Something went wrong getting the local image path" -Target $ImageNetworkPath return } } # Check the image local path if ($PSCmdlet.ShouldProcess("Verifying image local path")) { if ((Test-DbaPath -Path $ImageLocalPath -SqlInstance $SourceSqlInstance -SqlCredential $DestinationCredential) -ne $true) { Stop-PSFFunction -Message "Image local path $ImageLocalPath is not valid directory or can't be reached." -Target $SourceSqlInstance return } # Clean up the paths if ($ImageLocalPath.EndsWith("\")) { $ImageLocalPath = $ImageLocalPath.Substring(0, $ImageLocalPath.Length - 1) } $imagePath = $ImageLocalPath } # Check the database parameter if ($Database) { foreach ($db in $Database) { if ($db -notin $sourceServer.Databases.Name) { Stop-PSFFunction -Message "Database $db cannot be found on instance $SourceSqlInstance" -Target $SourceSqlInstance } } $DatabaseCollection = $sourceServer.Databases | Where-Object { $_.Name -in $Database } } else { Stop-PSFFunction -Message "Please supply a database to create an image for" -Target $SourceSqlInstance -Continue } # Check the data masking file if ($MaskingFile -and -not (Test-Path -Path $MaskingFile -Credential $SourceCredential)) { Stop-PSFFunction -Message "Could not find the data masking configuration file" -Target $MaskingFile -Continue } # Set time stamp $timestamp = Get-Date -format "yyyyMMddHHmmss" } process { # Test if there are any errors if (Test-PSFFunctionInterrupt) { return } # Loop through each of the databases foreach ($db in $DatabaseCollection) { Write-PSFMessage -Message "Creating image for database $db from $SourceSqlInstance" -Level Verbose if ($PSCmdlet.ShouldProcess($db, "Checking available disk space for database")) { # Check the database size to the available disk space if ($computer.IsLocalhost) { $availableMB = (Get-PSDrive -Name $ImageLocalPath.Substring(0, 1)).Free / 1MB } else { $command = [ScriptBlock]::Create("(Get-PSDrive -Name $($ImageLocalPath.Substring(0, 1)) ).Free / 1MB") $availableMB = Invoke-PSFCommand -ComputerName $computer -ScriptBlock $commandGetLocalPath -Credential $DestinationCredential } $dbSizeMB = $db.Size if ($availableMB -lt $dbSizeMB) { Stop-PSFFunction -Message "Size of database $($db.Name) does not fit within the image local path" -Target $db -Continue } } # Setup the image variables $imageName = "$($db.Name)_$timestamp" # Setup the access path $accessPath = Join-PSFPath -Path $ImageLocalPath -Child $imageName # Setup the vhd path $vhdPath = "$($accessPath).$($VhdType.ToLower())" if ($CreateFullBackup) { if ($PSCmdlet.ShouldProcess($db, "Creating full backup for database $db")) { # Create the backup Write-PSFMessage -Message "Creating new full backup for database $db" -Level Verbose $lastFullBackup = Backup-DbaDatabase -SqlInstance $SourceSqlInstance -SqlCredential $SourceSqlCredential -Database $db.Name -CopyOnly:$CopyOnlyBackup } } else { Write-PSFMessage -Message "Trying to retrieve the last full backup for $db" -Level Verbose # Get the last full backup $lastFullBackup = Get-DbaDbBackupHistory -SqlInstance $SourceSqlInstance -SqlCredential $SourceSqlCredential -Database $db.Name -LastFull } if (-not $lastFullBackup.Path) { Stop-PSFFunction -Message "No full backup could be found. Please use -CreateFullBackup or create a full backup manually" -Target $lastFullBackup return } elseif (-not (Test-Path -Path $lastFullBackup.Path)) { Stop-PSFFunction -Message "Could not access the full backup file. Check if it exists or that you have enough privileges to access it" -Target $lastFullBackup return } if ($PSCmdlet.ShouldProcess("$imageName", "Creating the vhd")) { # try to create the new VHD try { Write-PSFMessage -Message "Create the vhd $imageName" -Level Verbose # Check if computer is local if ($computer.IsLocalhost) { $null = New-PSDCVhdDisk -Destination $imagePath -Name $imageName -VhdType $VhdType -EnableException } else { $command = [ScriptBlock]::Create("New-PSDCVhdDisk -Destination '$imagePath' -Name $imageName -VhdType $VhdType -EnableException") $null = Invoke-PSFCommand -ComputerName $computer -ScriptBlock $command -Credential $DestinationCredential } } catch { Stop-PSFFunction -Message "Couldn't create vhd(x) $imageName" -Target $imageName -ErrorRecord $_ -Continue } } if ($PSCmdlet.ShouldProcess("$imageName", "Initializing the vhd")) { # Try to initialize the vhd try { Write-PSFMessage -Message "Initializing the vhd $imageName" -Level Verbose # Check if computer is local if ($computer.IsLocalhost) { $diskResult = Initialize-PSDCVhdDisk -Path $vhdPath -Credential $DestinationCredential -EnableException } else { $command = [ScriptBlock]::Create("Initialize-PSDCVhdDisk -Path $vhdPath -EnableException") $diskResult = Invoke-PSFCommand -ComputerName $computer -ScriptBlock $command -Credential $DestinationCredential } } catch { Stop-PSFFunction -Message "Couldn't initialize vhd $vhdPath" -Target $imageName -ErrorRecord $_ } } # Create folder structure for image $imageDataFolder = Join-PSFPath -Path $imagePath -Child "$($imageName)\Data" $imageLogFolder = Join-PSFPath -Path $imagePath -Child "$($imageName)\Log" # try to create access path try { # Check if access path is already present if (-not (Test-Path -Path $accessPath)) { if ($PSCmdlet.ShouldProcess($accessPath, "Creating access path $accessPath")) { try { # Check if computer is local if ($computer.IsLocalhost) { $null = New-Item -Path $accessPath -ItemType Directory -Force Write-PSFMessage -Level Important -Message $accessPath } else { $command = [ScriptBlock]::Create("New-Item -Path $accessPath -ItemType Directory -Force") $null = Invoke-PSFCommand -ComputerName $computer -ScriptBlock $command -Credential $DestinationCredential # Set the permissions $script = " `$permission = 'Everyone', 'FullControl', 'ContainerInherit,ObjectInherit', 'None', 'Allow' `$accessRule = New-Object System.Security.AccessControl.FileSystemAccessRule `$permission `$acl = Get-Acl -Path '$accessPath' `$acl.SetAccessRule(`$accessRule) `$acl | Set-Acl '$accessPath' " $command = [ScriptBlock]::Create($script) $null = Invoke-PSFCommand -ComputerName $computer -ScriptBlock $command -Credential $DestinationCredential } } catch { Stop-PSFFunction -Message "Couldn't create access path directory" -ErrorRecord $_ -Target $accessPath -Continue } } # Set the permissions #$permission = "Everyone", "FullControl", "ContainerInherit,ObjectInherit", "None", "Allow" $accessRule = New-Object System.Security.AccessControl.FileSystemAccessRule("Everyone", "FullControl", "ContainerInherit,Objectinherit", "None", "Allow") $acl = Get-Acl -Path $accessPath $acl.SetAccessRule($accessRule) Set-Acl -Path $accessPath -AclObject $acl } # Get the properties of the disk and partition $disk = $diskResult.Disk $partition = Get-Partition -DiskNumber $disk.Number | Where-Object { $_.Type -ne "Reserved" } | Select-Object -First 1 if ($PSCmdlet.ShouldProcess($accessPath, "Adding access path '$accessPath' to mounted disk")) { # Add the access path to the mounted disk if ($computer.IsLocalhost) { $null = Add-PartitionAccessPath -DiskNumber $disk.Number -PartitionNumber $partition.PartitionNumber -AccessPath $accessPath -ErrorAction SilentlyContinue } else { $command = [ScriptBlock]::Create("Add-PartitionAccessPath -DiskNumber $($disk.Number) -PartitionNumber $($partition.PartitionNumber) -AccessPath $accessPath -ErrorAction SilentlyContinue") $null = Invoke-PSFCommand -ComputerName $computer -ScriptBlock $command -Credential $DestinationCredential } } } catch { Stop-PSFFunction -Message "Couldn't create access path for partition" -ErrorRecord $_ -Target $diskResult.partition } # Check if image data folder exist if (-not (Test-Path -Path $imageDataFolder)) { if ($PSCmdlet.ShouldProcess($accessPath, "Creating data folder in vhd")) { try { Write-PSFMessage -Message "Creating data folder for image" -Level Verbose # Check if computer is local if ($computer.IsLocalhost) { $null = New-Item -Path $imageDataFolder -ItemType Directory $acl = Get-ACL -Path $imageDataFolder $acl.SetAccessRule($accessRule) Set-Acl -Path $imageDataFolder -AclObject $acl } else { $command = [ScriptBlock]::Create("New-Item -Path $imageDataFolder -ItemType Directory") $null = Invoke-PSFCommand -ComputerName $computer -ScriptBlock $command -Credential $DestinationCredential } } catch { Stop-PSFFunction -Message "Couldn't create image data folder" -Target $imageName -ErrorRecord $_ -Continue } } } # Test if the image log folder exists if (-not (Test-Path -Path $imageLogFolder)) { if ($PSCmdlet.ShouldProcess($accessPath, "Creating log folder in vhd")) { try { Write-PSFMessage -Message "Creating transaction log folder for image" -Level Verbose # Check if computer is local if ($computer.IsLocalhost) { $null = New-Item -Path $imageLogFolder -ItemType Directory $acl = Get-ACL -Path $imageLogFolder $acl.SetAccessRule($accessRule) Set-Acl -Path $imageLogFolder -AclObject $acl } else { $command = [ScriptBlock]::Create("New-Item -Path $imageLogFolder -ItemType Directory") $null = Invoke-PSFCommand -ComputerName $computer -ScriptBlock $command -Credential $DestinationCredential } } catch { Stop-PSFFunction -Message "Couldn't create image data folder" -Target $imageName -ErrorRecord $_ -Continue } } } # Setup the temporary database name $tempDbName = "$($db.Name)-PSDatabaseClone" if ($PSCmdlet.ShouldProcess($tempDbName, "Restoring database")) { # Restore database to image folder try { Write-PSFMessage -Message "Restoring database $db on $DestinationSqlInstance" -Level Verbose $params = @{ SqlInstance = $DestinationSqlInstance SqlCredential = $DestinationSqlCredential DatabaseName = $tempDbName Path = $lastFullBackup DestinationDataDirectory = $imageDataFolder DestinationLogDirectory = $imageLogFolder WithReplace = $true EnableException = $true } $restore = Restore-DbaDatabase @params } catch { Stop-PSFFunction -Message "Couldn't restore database $db as $tempDbName on $DestinationSqlInstance.`n$($_)" -Target $restore -ErrorRecord $_ -Continue } } # Apply data masking if ($MaskingFile) { # Check the recovery model of the database $dbRecoveryModel = Get-DbaDbRecoveryModel -SqlInstance $DestinationSqlInstance -SqlCredential $DestinationSqlCredential -Database $tempDbName # Set the recovery model to simple to minimize growth during data masking if ($dbRecoveryModel.RecoveryModel -ne 'Simple') { try { $null = Set-DbaDbRecoveryModel -SqlInstance $DestinationSqlInstance -SqlCredential $DestinationSqlCredential -Database $tempDbName -RecoveryModel Simple -Confirm:$false -EnableException } catch { Stop-PSFFunction -Message "Couldn't change recovery model for database" -Target $restore -ErrorRecord $_ -Continue } [bool]$recoveryModelChanged = $true } else { [bool]$recoveryModelChanged = $false } # Execute the data masking try { Invoke-DbaDbDataMasking -SqlInstance $DestinationSqlInstance -SqlCredential $DestinationSqlCredential -Database $tempDbName -FilePath $MaskingFile -EnableException } catch { Stop-PSFFunction -Message "Something went wrong masking the data" -Target $MaskingFile -ErrorRecord $_ -Continue } # Change back the recovery model to it's original setting if ($recoveryModelChanged) { try { $null = Set-DbaDbRecoveryModel -SqlInstance $DestinationSqlInstance -SqlCredential $DestinationSqlCredential -Database $tempDbName -RecoveryModel $dbRecoveryModel.RecoveryModel -Confirm:$false -EnableException } catch { Stop-PSFFunction -Message "Couldn't change recovery model for database back to original" -Target $restore -ErrorRecord $_ -Continue } } } # Detach database if ($PSCmdlet.ShouldProcess($tempDbName, "Detaching database")) { try { Write-PSFMessage -Message "Detaching database $tempDbName on $DestinationSqlInstance" -Level Verbose $null = Dismount-DbaDatabase -SqlInstance $DestinationSqlInstance -Database $tempDbName -SqlCredential $DestinationSqlCredential -Force } catch { Stop-PSFFunction -Message "Couldn't detach database $db as $tempDbName on $DestinationSqlInstance" -Target $db -ErrorRecord $_ -Continue } } if ($PSCmdlet.ShouldProcess($vhdPath, "Dismounting the vhd")) { # Dismount the vhd try { Write-PSFMessage -Message "Dismounting vhd" -Level Verbose # Check if computer is local if ($computer.IsLocalhost) { # Dismount the VHD $null = Dismount-DiskImage -ImagePath $vhdPath # Remove the access path $null = Remove-Item -Path $accessPath -Recurse -Force } else { $command = [ScriptBlock]::Create("Dismount-DiskImage -ImagePath $vhdPath") $null = Invoke-PSFCommand -ComputerName $computer -ScriptBlock $command -Credential $DestinationCredential $command = [ScriptBlock]::Create("Remove-Item -Path $accessPath -Force") $null = Invoke-PSFCommand -ComputerName $computer -ScriptBlock $command -Credential $DestinationCredential } } catch { Stop-PSFFunction -Message "Couldn't dismount vhd" -Target $imageName -ErrorRecord $_ -Continue } } # Write the data to the database $imageLocation = Join-PSFPath $uri.LocalPath -Child "$($imageName).vhdx" $sizeMB = $dbSizeMB $databaseName = $db.Name $databaseTS = $lastFullBackup.Start if ($informationStore -eq 'SQL') { $query = " DECLARE @ImageID INT; EXECUTE dbo.Image_New @ImageID = @ImageID OUTPUT, -- int @ImageName = '$($imageName)', -- varchar(100) @ImageLocation = '$($imageLocation)', -- varchar(255) @SizeMB = $($sizeMB), -- int @DatabaseName = '$($databaseName)', -- varchar(100) @DatabaseTimestamp = '$($databaseTS)' -- datetime SELECT @ImageID as ImageID " # Add image to database if ($PSCmdlet.ShouldProcess($imageName, "Adding image to database")) { try { Write-PSFMessage -Message "Saving image information in database" -Level Verbose $result += Invoke-DbaQuery -SqlInstance $pdcSqlInstance -SqlCredential $pdcCredential -Database $pdcDatabase -Query $query -EnableException $imageID = $result.ImageID } catch { Stop-PSFFunction -Message "Couldn't add image to database" -Target $imageName -ErrorRecord $_ } } } elseif ($informationStore -eq 'File') { [array]$images = $null # Get all the images try { $images = Get-PSDCImage } catch { Stop-PSFFunction -Message "Couldn't get images" -Target $imageName -ErrorRecord $_ return } # Setup the new image id if ($images.Count -ge 1) { $imageID = ($images[-1].ImageID | Sort-Object ImageID) + 1 } else { $imageID = 1 } # Add the new information to the array $images += [PSCustomObject]@{ ImageID = $imageID ImageName = $imageName ImageLocation = $imageLocation SizeMB = $sizeMB DatabaseName = $databaseName DatabaseTimestamp = $databaseTS CreatedOn = (Get-Date -format "yyyyMMddHHmmss") } # Test if the JSON folder can be reached if (-not (Test-Path -Path "PSDCJSONFolder:\")) { $command = [scriptblock]::Create("Import-Module PSDatabaseClone -Force") try { Invoke-PSFCommand -ComputerName $computer -ScriptBlock $command -Credential $Credential } catch { Stop-PSFFunction -Message "Couldn't import module remotely" -Target $command return } } # Set the image file $jsonImageFile = "PSDCJSONFolder:\images.json" # Convert the data back to JSON $images | ConvertTo-Json | Set-Content $jsonImageFile } # Add the results to the custom object #[PSDCImage]$image = New-Object PSDCImage [PSCustomObject]@{ ImageID = $imageID ImageName = $imageName ImageLocation = $imageLocation SizeMB = $sizeMB DatabaseName = $databaseName DatabaseTimestamp = $databaseTS } } # for each database } # end process end { # Test if there are any errors if (Test-PSFFunctionInterrupt) { return } Write-PSFMessage -Message "Finished creating database image" -Level Verbose } } |