Downloads:
192,270
Downloads of v 0.9.450:
194
Last Update:
01 Oct 2018
Package Maintainer(s):
Software Author(s):
- Chrissy LeMaire
Tags:
admin powershell module template dba sqlserver sql tools database- Software Specific:
- Software Site
- Software Source
- Software License
- Software Docs
- Software Issues
- Package Specific:
- Package Source
- Package outdated?
- Package broken?
- Contact Maintainers
- Contact Site Admins
- Software Vendor?
- Report Abuse
- Download
dbatools (PowerShell Module)
This is not the latest version of dbatools (PowerShell Module) available.
- 1
- 2
- 3
0.9.450 | Updated: 01 Oct 2018
- Software Specific:
- Software Site
- Software Source
- Software License
- Software Docs
- Software Issues
- Package Specific:
- Package Source
- Package outdated?
- Package broken?
- Contact Maintainers
- Contact Site Admins
- Software Vendor?
- Report Abuse
- Download
Downloads:
192,270
Downloads of v 0.9.450:
194
Maintainer(s):
Software Author(s):
- Chrissy LeMaire
dbatools (PowerShell Module) 0.9.450
This is not the latest version of dbatools (PowerShell Module) available.
Legal Disclaimer: Neither this package nor Chocolatey Software, Inc. are affiliated with or endorsed by Chrissy LeMaire. The inclusion of Chrissy LeMaire trademark(s), if any, upon this webpage is solely to identify Chrissy LeMaire goods or services and not for commercial purposes.
- 1
- 2
- 3
All Checks are Passing
3 Passing Tests
Deployment Method: Individual Install, Upgrade, & Uninstall
To install dbatools (PowerShell Module), run the following command from the command line or from PowerShell:
To upgrade dbatools (PowerShell Module), run the following command from the command line or from PowerShell:
To uninstall dbatools (PowerShell Module), run the following command from the command line or from PowerShell:
Deployment Method:
This applies to both open source and commercial editions of Chocolatey.
1. Enter Your Internal Repository Url
(this should look similar to https://community.chocolatey.org/api/v2/)
2. Setup Your Environment
1. Ensure you are set for organizational deployment
Please see the organizational deployment guide
2. Get the package into your environment
Option 1: Cached Package (Unreliable, Requires Internet - Same As Community)-
Open Source or Commercial:
- Proxy Repository - Create a proxy nuget repository on Nexus, Artifactory Pro, or a proxy Chocolatey repository on ProGet. Point your upstream to https://community.chocolatey.org/api/v2/. Packages cache on first access automatically. Make sure your choco clients are using your proxy repository as a source and NOT the default community repository. See source command for more information.
- You can also just download the package and push it to a repository Download
-
Open Source
-
Download the package:
Download - Follow manual internalization instructions
-
-
Package Internalizer (C4B)
-
Run: (additional options)
choco download dbatools --internalize --version=0.9.450 --source=https://community.chocolatey.org/api/v2/
-
For package and dependencies run:
choco push --source="'INTERNAL REPO URL'"
- Automate package internalization
-
Run: (additional options)
3. Copy Your Script
choco upgrade dbatools -y --source="'INTERNAL REPO URL'" --version="'0.9.450'" [other options]
See options you can pass to upgrade.
See best practices for scripting.
Add this to a PowerShell script or use a Batch script with tools and in places where you are calling directly to Chocolatey. If you are integrating, keep in mind enhanced exit codes.
If you do use a PowerShell script, use the following to ensure bad exit codes are shown as failures:
choco upgrade dbatools -y --source="'INTERNAL REPO URL'" --version="'0.9.450'"
$exitCode = $LASTEXITCODE
Write-Verbose "Exit code was $exitCode"
$validExitCodes = @(0, 1605, 1614, 1641, 3010)
if ($validExitCodes -contains $exitCode) {
Exit 0
}
Exit $exitCode
- name: Install dbatools
win_chocolatey:
name: dbatools
version: '0.9.450'
source: INTERNAL REPO URL
state: present
See docs at https://docs.ansible.com/ansible/latest/modules/win_chocolatey_module.html.
chocolatey_package 'dbatools' do
action :install
source 'INTERNAL REPO URL'
version '0.9.450'
end
See docs at https://docs.chef.io/resource_chocolatey_package.html.
cChocoPackageInstaller dbatools
{
Name = "dbatools"
Version = "0.9.450"
Source = "INTERNAL REPO URL"
}
Requires cChoco DSC Resource. See docs at https://github.com/chocolatey/cChoco.
package { 'dbatools':
ensure => '0.9.450',
provider => 'chocolatey',
source => 'INTERNAL REPO URL',
}
Requires Puppet Chocolatey Provider module. See docs at https://forge.puppet.com/puppetlabs/chocolatey.
4. If applicable - Chocolatey configuration/installation
See infrastructure management matrix for Chocolatey configuration elements and examples.
This package was approved as a trusted package on 01 Oct 2018.
dbatools logo dbatools is sort of like a command-line SQL Server Management Studio. The project initially started out as Start-SqlMigration.ps1, but has now grown into a collection of over 300 commands that help automate SQL Server tasks and encourage best practices.
NOTE: This module requires a minimum of PowerShell v3.
NOTE: This is an automatically updated package. If you find it is out of date by more than a week, please contact the maintainer(s) and let them know the package is no longer updating correctly.
$ErrorActionPreference = 'Stop'
$moduleName = 'dbatools' # this could be different from package name
$module = Get-Module -Name $moduleName
if ($module) {
Write-Verbose "Module '$moduleName' is imported into the session. Removing it."
Remove-Module -Name $moduleName -Force -ErrorAction SilentlyContinue
if ($lib = [appdomain]::CurrentDomain.GetAssemblies() | Where-Object FullName -like "dbatools, *") {
Write-Verbose "Found locked DLL files for module '$moduleName'."
$moduleDir = Split-Path $module.Path -Parent
if ($lib.Location -like "$moduleDir\*") {
Write-Warning @"
We have detected dbatools to be already imported from '$moduleDir' and the dll files have been locked and cannot be updated.
Please close all consoles that have dbatools imported (Remove-Module dbatools is NOT enough).
"@
throw
}
}
}
$ErrorActionPreference = 'Stop'
$moduleName = 'dbatools'
$sourcePath = Join-Path -Path $env:ProgramFiles -ChildPath "WindowsPowerShell\Modules\$moduleName"
Write-Verbose "Removing all version of '$moduleName' from '$sourcePath'."
Remove-Item -Path $sourcePath -Recurse -Force -ErrorAction SilentlyContinue
if ($PSVersionTable.PSVersion.Major -lt 4) {
$modulePaths = [Environment]::GetEnvironmentVariable('PSModulePath', 'Machine') -split ';'
Write-Verbose "Removing '$sourcePath' from PSModulePath."
$newModulePath = $modulePaths | Where-Object { $_ -ne $sourcePath }
[Environment]::SetEnvironmentVariable('PSModulePath', $newModulePath, 'Machine')
$env:PSModulePath = $newModulePath
}
### DO NOT EDIT THIS FILE DIRECTLY ###
#ValidationTags#Messaging,FlowControl,Pipeline,CodeStyle#
function Add-DbaCmsRegServer {
<#
.SYNOPSIS
Adds registered servers to SQL Server Central Management Server (CMS)
.DESCRIPTION
Adds registered servers to SQL Server Central Management Server (CMS). If you need more flexiblity, look into Import-DbaCmsRegServer which
accepts multiple kinds of input and allows you to add reg servers from different CMSes.
.PARAMETER SqlInstance
The target SQL Server instance
.PARAMETER SqlCredential
Login to the target instance using alternative credentials. Windows and SQL Authentication supported. Accepts credential objects (Get-Credential)
.PARAMETER ServerName
Server Name is the actual SQL instance name (labeled Server Name)
.PARAMETER Name
Name is basically the nickname in SSMS CMS interface (labeled Registered Server Name)
.PARAMETER Description
Adds a description for the registered server
.PARAMETER Group
Adds the registered server to a specific group.
.PARAMETER InputObject
Allows the piping of a registered server group
.PARAMETER WhatIf
Shows what would happen if the command were to run. No actions are actually performed.
.PARAMETER Confirm
Prompts you for confirmation before executing any changing operations within the command.
.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.
.NOTES
Author: Chrissy LeMaire (@cl), netnerds.net
Tags: RegisteredServer, CMS
Website: https://dbatools.io
Copyright: (C) Chrissy LeMaire, [email protected]
License: MIT https://opensource.org/licenses/MIT
.LINK
https://dbatools.io/Add-DbaCmsRegServer
.EXAMPLE
Add-DbaCmsRegServer -SqlInstance sql2008 -ServerName sql01
Creates a registered server on sql2008's CMS which points to the SQL Server, sql01. When scrolling in CMS, the name "sql01" will be visible.
.EXAMPLE
Add-DbaCmsRegServer -SqlInstance sql2008 -ServerName sql01 -Name "The 2008 Clustered Instance" -Description "HR's Dedicated SharePoint instance"
Creates a registered server on sql2008's CMS which points to the SQL Server, sql01. When scrolling in CMS, "The 2008 Clustered Instance" will be visible.
Clearly this is hard to explain ;)
.EXAMPLE
Add-DbaCmsRegServer -SqlInstance sql2008 -ServerName sql01 -Group hr\Seattle
Creates a registered server on sql2008's CMS which points to the SQL Server, sql01. When scrolling in CMS, the name "sql01" will be visible within the Seattle group which is in the hr group.
.EXAMPLE
Get-DbaCmsRegServerGroup -SqlInstance sql2008 -Group hr\Seattle | Add-DbaCmsRegServer -ServerName sql01111
Creates a registered server on sql2008's CMS which points to the SQL Server, sql01. When scrolling in CMS, the name "sql01" will be visible within the Seattle group which is in the hr group.
#>
[CmdletBinding(SupportsShouldProcess)]
param (
[Alias("ServerInstance", "SqlServer")]
[DbaInstanceParameter[]]$SqlInstance,
[PSCredential]$SqlCredential,
[parameter(Mandatory)]
[string]$ServerName,
[string]$Name = $ServerName,
[string]$Description,
[object]$Group,
[parameter(ValueFromPipeline)]
[Microsoft.SqlServer.Management.RegisteredServers.ServerGroup[]]$InputObject,
[switch]$EnableException
)
process {
if (-not $InputObject -and -not $SqlInstance) {
Stop-Function -Message "You must either pipe in a registered server group or specify a sqlinstance"
return
}
# double check in case a null name was bound
if (-not $Name) {
$Name = $ServerName
}
foreach ($instance in $SqlInstance) {
if (($Group)) {
if ($Group -is [Microsoft.SqlServer.Management.RegisteredServers.ServerGroup]) {
$InputObject += Get-DbaCmsRegServerGroup -SqlInstance $instance -SqlCredential $SqlCredential -Group $Group.Name
}
else {
$InputObject += Get-DbaCmsRegServerGroup -SqlInstance $instance -SqlCredential $SqlCredential -Group $Group
}
}
else {
$InputObject += Get-DbaCmsRegServerGroup -SqlInstance $instance -SqlCredential $SqlCredential -Id 1
}
if (-not $InputObject) {
Stop-Function -Message "No matching groups found on $instance" -Continue
}
}
foreach ($reggroup in $InputObject) {
$parentserver = Get-RegServerParent -InputObject $reggroup
if ($null -eq $parentserver) {
Stop-Function -Message "Something went wrong and it's hard to explain, sorry. This basically shouldn't happen." -Continue
}
$server = $parentserver.ServerConnection.SqlConnectionObject
if ($Pscmdlet.ShouldProcess($parentserver.SqlInstance, "Adding $ServerName")) {
try {
$newserver = New-Object Microsoft.SqlServer.Management.RegisteredServers.RegisteredServer($reggroup, $Name)
$newserver.ServerName = $ServerName
$newserver.Description = $Description
$newserver.Create()
Get-DbaCmsRegServer -SqlInstance $server -Name $Name -ServerName $ServerName
}
catch {
Stop-Function -Message "Failed to add $ServerName on $($parentserver.SqlInstance)" -ErrorRecord $_ -Continue
}
}
}
}
end {
Test-DbaDeprecation -DeprecatedOn "1.0.0" -Alias Add-DbaRegisteredServer
}
}
#ValidationTags#Messaging,FlowControl,Pipeline,CodeStyle#
function Add-DbaCmsRegServerGroup {
<#
.SYNOPSIS
Adds registered server groups to SQL Server Central Management Server (CMS)
.DESCRIPTION
Adds registered server groups to SQL Server Central Management Server (CMS). If you need more flexiblity, look into Import-DbaCmsRegServer which
accepts multiple kinds of input and allows you to add reg servers and groups from different CMSes.
.PARAMETER SqlInstance
The target SQL Server instance
.PARAMETER SqlCredential
Login to the target instance using alternative credentials. Windows and SQL Authentication supported. Accepts credential objects (Get-Credential)
.PARAMETER Name
The name of the registered server group
.PARAMETER Description
The description for the registered server group
.PARAMETER Group
The SQL Server Central Management Server group. If no groups are specified, the new group will be created at the root.
.PARAMETER InputObject
Allows results from Get-DbaCmsRegServerGroup to be piped in
.PARAMETER IncludeRegisteredServers
Create the registered servers within the group, too
.PARAMETER InputObject
Allows results from Get-DbaCmsRegServerGroup to be piped in
.PARAMETER WhatIf
Shows what would happen if the command were to run. No actions are actually performed.
.PARAMETER Confirm
Prompts you for confirmation before executing any changing operations within the command.
.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.
.NOTES
Author: Chrissy LeMaire (@cl), netnerds.net
Tags: RegisteredServer, CMS
Website: https://dbatools.io
Copyright: (C) Chrissy LeMaire, [email protected]
License: MIT https://opensource.org/licenses/MIT
.LINK
https://dbatools.io/Add-DbaCmsRegServerGroup
.EXAMPLE
Add-DbaCmsRegServerGroup -SqlInstance sql2012 -Name HR
Creates a registered server group called HR, in the root of sql2012's CMS
.EXAMPLE
Add-DbaCmsRegServerGroup -SqlInstance sql2012, sql2014 -Name subfolder -Group HR
Creates a registered server group on sql2012 and sql2014 called subfolder within the HR group
.EXAMPLE
Get-DbaCmsRegServerGroup -SqlInstance sql2012, sql2014 -Group HR | Add-DbaCmsRegServerGroup -Name subfolder
Creates a registered server group on sql2012 and sql2014 called subfolder within the HR group of each server
#>
[CmdletBinding(SupportsShouldProcess)]
param (
[Alias("ServerInstance", "SqlServer")]
[DbaInstanceParameter[]]$SqlInstance,
[PSCredential]$SqlCredential,
[parameter(Mandatory)]
[string]$Name,
[string]$Description,
[string]$Group,
[parameter(ValueFromPipeline)]
[Microsoft.SqlServer.Management.RegisteredServers.ServerGroup[]]$InputObject,
[switch]$EnableException
)
process {
if (-not $InputObject -and -not $SqlInstance) {
Stop-Function -Message "You must either pipe in a registered server group or specify a sqlinstance"
return
}
foreach ($instance in $SqlInstance) {
if ((Test-Bound -ParameterName Group)) {
$InputObject += Get-DbaCmsRegServerGroup -SqlInstance $instance -SqlCredential $SqlCredential -Group $Group
}
else {
$InputObject += Get-DbaCmsRegServerGroup -SqlInstance $instance -SqlCredential $SqlCredential -Id 1
}
}
foreach ($reggroup in $InputObject) {
$parentserver = Get-RegServerParent -InputObject $reggroup
$server = $parentserver.ServerConnection.ServerInstance.SqlConnectionObject
if ($null -eq $parentserver) {
Stop-Function -Message "Something went wrong and it's hard to explain, sorry. This basically shouldn't happen." -Continue
}
if ($Pscmdlet.ShouldProcess($parentserver.SqlInstance, "Adding $Name")) {
try {
$newgroup = New-Object Microsoft.SqlServer.Management.RegisteredServers.ServerGroup($reggroup, $Name)
$newgroup.Description = $Description
$newgroup.Create()
Get-DbaCmsRegServerGroup -SqlInstance $parentserver.ServerConnection.SqlConnectionObject -Group (Get-RegServerGroupReverseParse -object $newgroup)
$parentserver.ServerConnection.Disconnect()
}
catch {
Stop-Function -Message "Failed to add $reggroup on $server" -ErrorRecord $_ -Continue
}
}
}
}
end {
Test-DbaDeprecation -DeprecatedOn "1.0.0" -Alias Add-DbaRegisteredServerGroup
}
}
#ValidationTags#Messaging,FlowControl,Pipeline,CodeStyle#
function Add-DbaComputerCertificate {
<#
.SYNOPSIS
Adds a computer certificate - useful for older systems.
.DESCRIPTION
Adds a computer certificate from a local or remote computer.
.PARAMETER ComputerName
The target SQL Server. Defaults to localhost.
.PARAMETER Credential
Allows you to login to $ComputerName using alternative credentials.
.PARAMETER Password
The password for the certificate, if it is password protected.
.PARAMETER Certificate
The target certificate object.
.PARAMETER Path
The local path to the target certificate object.
.PARAMETER Store
Certificate store. Default is LocalMachine.
.PARAMETER Folder
Certificate folder. Default is My (Personal).
.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
Tags: Certificate
Author: Chrissy LeMaire (@cl), netnerds.net
Website: https://dbatools.io
Copyright: (C) Chrissy LeMaire, [email protected]
License: MIT https://opensource.org/licenses/MIT
.EXAMPLE
Add-DbaComputerCertificate -ComputerName Server1 -Path C:\temp\cert.cer
Adds the local C:\temp\cert.cer to the remote server Server1 in LocalMachine\My (Personal).
.EXAMPLE
Add-DbaComputerCertificate -Path C:\temp\cert.cer
Adds the local C:\temp\cert.cer to the local computer's LocalMachine\My (Personal) certificate store.
#>
[CmdletBinding(SupportsShouldProcess, ConfirmImpact = "High")]
param (
[Alias("ServerInstance", "SqlServer", "SqlInstance")]
[DbaInstance[]]$ComputerName = $env:COMPUTERNAME,
[PSCredential]$Credential,
[securestring]$Password,
[parameter(ValueFromPipeline)]
[System.Security.Cryptography.X509Certificates.X509Certificate2[]]$Certificate,
[string]$Path,
[string]$Store = "LocalMachine",
[string]$Folder = "My",
[Alias('Silent')]
[switch]$EnableException
)
begin {
if ($Path) {
if (!(Test-Path -Path $Path)) {
Stop-Function -Message "Path ($Path) does not exist." -Category InvalidArgument
return
}
try {
# This may be too much, but ¯\_(ツ)_/¯
$bytes = [System.IO.File]::ReadAllBytes($Path)
$Certificate = New-Object System.Security.Cryptography.X509Certificates.X509Certificate2
$Certificate.Import($bytes, $Password, [System.Security.Cryptography.X509Certificates.X509KeyStorageFlags]::DefaultKeySet)
}
catch {
Stop-Function -Message "Can't import certificate." -ErrorRecord $_
return
}
}
#region Remoting Script
$scriptBlock = {
param (
$CertificateData,
[securestring]$Password,
$Store,
$Folder
)
$cert = New-Object System.Security.Cryptography.X509Certificates.X509Certificate2
$cert.Import($CertificateData, $Password, [System.Security.Cryptography.X509Certificates.X509KeyStorageFlags]::DefaultKeySet)
Write-Message -Level Verbose -Message "Importing cert to $Folder\$Store"
$tempStore = New-Object System.Security.Cryptography.X509Certificates.X509Store($Folder, $Store)
$tempStore.Open('ReadWrite')
$tempStore.Add($cert)
$tempStore.Close()
Write-Message -Level Verbose -Message "Searching Cert:\$Store\$Folder"
Get-ChildItem "Cert:\$Store\$Folder" -Recurse | Where-Object { $_.Thumbprint -eq $cert.Thumbprint }
}
#endregion Remoting Script
}
process {
if (Test-FunctionInterrupt) { return }
if (-not $Certificate) {
Stop-Function -Message "You must specify either Certificate or Path" -Category InvalidArgument
return
}
foreach ($cert in $Certificate) {
try {
$certData = $cert.Export([System.Security.Cryptography.X509Certificates.X509ContentType]::PFX, $Password)
}
catch {
Stop-Function -Message "Can't export certificate" -ErrorRecord $_ -Continue
}
foreach ($computer in $ComputerName) {
if ($PScmdlet.ShouldProcess("local", "Connecting to $computer to import cert")) {
try {
Invoke-Command2 -ComputerName $computer -Credential $Credential -ArgumentList $certdata, $Password, $Store, $Folder -ScriptBlock $scriptblock -ErrorAction Stop |
Select-DefaultView -Property FriendlyName, DnsNameList, Thumbprint, NotBefore, NotAfter, Subject, Issuer
}
catch {
Stop-Function -Message "Failure" -ErrorRecord $_ -Target $computer -Continue
}
}
}
}
}
}
#ValidationTags#Messaging,FlowControl,Pipeline,CodeStyle#
function Add-DbaPfDataCollectorCounter {
<#
.SYNOPSIS
Adds a Performance Data Collector Counter.
.DESCRIPTION
Adds a Performance Data Collector Counter.
.PARAMETER ComputerName
The target computer. Defaults to localhost.
.PARAMETER Credential
Allows you to login to $ComputerName using alternative credentials. To use:
$cred = Get-Credential, then pass $cred object to the -Credential parameter.
.PARAMETER CollectorSet
The Collector Set name.
.PARAMETER Collector
The Collector name.
.PARAMETER Counter
The Counter name. This must be in the form of '\Processor(_Total)\% Processor Time'.
.PARAMETER InputObject
Accepts the object output by Get-DbaPfDataCollector via the pipeline.
.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.
.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.
.NOTES
Tags: PerfMon
Author: Chrissy LeMaire (@cl), netnerds.net
Website: https://dbatools.io
Copyright: (C) Chrissy LeMaire, [email protected]
License: MIT https://opensource.org/licenses/MIT
.LINK
https://dbatools.io/Add-DbaPfDataCollectorCounter
.EXAMPLE
Add-DbaPfDataCollectorCounter -ComputerName sql2017 -CollectorSet 'System Correlation' -Collector DataCollector01 -Counter '\LogicalDisk(*)\Avg. Disk Queue Length'
Adds the '\LogicalDisk(*)\Avg. Disk Queue Length' counter within the DataCollector01 collector within the System Correlation collector set on sql2017.
.EXAMPLE
Get-DbaPfDataCollector | Out-GridView -PassThru | Add-DbaPfDataCollectorCounter -Counter '\LogicalDisk(*)\Avg. Disk Queue Length' -Confirm
Allows you to select which Data Collector you'd like to add the counter '\LogicalDisk(*)\Avg. Disk Queue Length' on localhost and prompts for confirmation.
#>
[CmdletBinding(SupportsShouldProcess, ConfirmImpact = "Low")]
param (
[DbaInstance[]]$ComputerName = $env:COMPUTERNAME,
[PSCredential]$Credential,
[Alias("DataCollectorSet")]
[string[]]$CollectorSet,
[Alias("DataCollector")]
[string[]]$Collector,
[Alias("Name")]
[parameter(Mandatory, ValueFromPipelineByPropertyName)]
[object[]]$Counter,
[parameter(ValueFromPipeline)]
[object[]]$InputObject,
[switch]$EnableException
)
begin {
$setscript = {
$setname = $args[0]; $Addxml = $args[1]
$set = New-Object -ComObject Pla.DataCollectorSet
$set.SetXml($Addxml)
$set.Commit($setname, $null, 0x0003) #add or modify.
$set.Query($setname, $Null)
}
}
process {
if ($InputObject.Credential -and (Test-Bound -ParameterName Credential -Not)) {
$Credential = $InputObject.Credential
}
if (($InputObject | Get-Member -MemberType NoteProperty -ErrorAction SilentlyContinue).Count -le 3 -and $InputObject.ComputerName -and $InputObject.Name) {
# it's coming from Get-DbaPfAvailableCounter
$ComputerName = $InputObject.ComputerName
$Counter = $InputObject.Name
$InputObject = $null
}
if (-not $InputObject -or ($InputObject -and (Test-Bound -ParameterName ComputerName))) {
foreach ($computer in $ComputerName) {
$InputObject += Get-DbaPfDataCollector -ComputerName $computer -Credential $Credential -CollectorSet $CollectorSet -Collector $Collector
}
}
if ($InputObject) {
if (-not $InputObject.DataCollectorObject) {
Stop-Function -Message "InputObject is not of the right type. Please use Get-DbaPfDataCollector or Get-DbaPfAvailableCounter."
return
}
}
foreach ($object in $InputObject) {
$computer = $InputObject.ComputerName
$null = Test-ElevationRequirement -ComputerName $computer -Continue
$setname = $InputObject.DataCollectorSet
$collectorname = $InputObject.Name
$xml = [xml]($InputObject.DataCollectorSetXml)
foreach ($countername in $counter) {
$node = $xml.SelectSingleNode("//Name[.='$collectorname']")
$newitem = $xml.CreateElement('Counter')
$null = $newitem.PsBase.InnerText = $countername
$null = $node.ParentNode.AppendChild($newitem)
$newitem = $xml.CreateElement('CounterDisplayName')
$null = $newitem.PsBase.InnerText = $countername
$null = $node.ParentNode.AppendChild($newitem)
}
$plainxml = $xml.OuterXml
if ($Pscmdlet.ShouldProcess("$computer", "Adding $counters to $collectorname with the $setname collection set")) {
try {
$results = Invoke-Command2 -ComputerName $computer -Credential $Credential -ScriptBlock $setscript -ArgumentList $setname, $plainxml -ErrorAction Stop
Write-Message -Level Verbose -Message " $results"
Get-DbaPfDataCollectorCounter -ComputerName $computer -Credential $Credential -CollectorSet $setname -Collector $collectorname -Counter $counter
}
catch {
Stop-Function -Message "Failure importing $Countername to $computer." -ErrorRecord $_ -Target $computer -Continue
}
}
}
}
}
function Backup-DbaDatabase {
<#
.SYNOPSIS
Backup one or more SQL Sever databases from a single SQL Server SqlInstance.
.DESCRIPTION
Performs a backup of a specified type of 1 or more databases on a single SQL Server Instance. These backups may be Full, Differential or Transaction log backups.
.PARAMETER SqlInstance
The SQL Server instance hosting the databases to be backed up.
.PARAMETER SqlCredential
Credentials to connect to the SQL Server instance if the calling user doesn't have permission.
.PARAMETER Database
The database(s) to process. This list is auto-populated from the server. If unspecified, all databases will be processed.
.PARAMETER ExcludeDatabase
The database(s) to exclude. This list is auto-populated from the server.
.PARAMETER BackupFileName
The name of the file to backup to. This is only accepted for single database backups.
If no name is specified then the backup files will be named DatabaseName_yyyyMMddHHmm (i.e. "Database1_201714022131") with the appropriate extension.
If the same name is used repeatedly, SQL Server will add backups to the same file at an incrementing position.
SQL Server needs permissions to write to the specified location. Path names are based on the SQL Server (C:\ is the C drive on the SQL Server, not the machine running the script).
.PARAMETER BackupDirectory
Path in which to place the backup files. If not specified, the backups will be placed in the default backup location for SqlInstance.
If multiple paths are specified, the backups will be striped across these locations. This will overwrite the FileCount option.
If the path does not exist, Sql Server will attempt to create it. Folders are created by the Sql Instance, and checks will be made for write permissions.
File Names with be suffixed with x-of-y to enable identifying striped sets, where y is the number of files in the set and x ranges from 1 to y.
.PARAMETER CopyOnly
If this switch is enabled, CopyOnly backups will be taken. By default function performs a normal backup, these backups interfere with the restore chain of the database. CopyOnly backups will not interfere with the restore chain of the database.
For more details please refer to this MSDN article - https://msdn.microsoft.com/en-us/library/ms191495.aspx
.PARAMETER Type
The type of SQL Server backup to perform. Accepted values are "Full", "Log", "Differential", "Diff", "Database"
.PARAMETER FileCount
This is the number of striped copies of the backups you wish to create. This value is overwritten if you specify multiple Backup Directories.
.PARAMETER CreateFolder
If this switch is enabled, each database will be backed up into a separate folder on each of the paths specified by BackupDirectory.
.PARAMETER CompressBackup
If this switch is enabled, the function will try to perform a compressed backup if supported by the version and edition of SQL Server. Otherwise, this function will use the server's default setting for compression.
.PARAMETER MaxTransferSize
Sets the size of the unit of transfer. Values must be a multiple of 64kb.
.PARAMETER Blocksize
Specifies the block size to use. Must be one of 0.5KB, 1KB, 2KB, 4KB, 8KB, 16KB, 32KB or 64KB. This can be specified in bytes.
Refer to https://msdn.microsoft.com/en-us/library/ms178615.aspx for more detail
.PARAMETER BufferCount
Number of I/O buffers to use to perform the operation.
Refer to https://msdn.microsoft.com/en-us/library/ms178615.aspx for more detail
.PARAMETER Checksum
If this switch is enabled, the backup checksum will be calculated.
.PARAMETER Verify
If this switch is enabled, the backup will be verified by running a RESTORE VERIFYONLY against the SqlInstance
.PARAMETER WithFormat
Formats the media as the first step of the backup operation. NOTE: This will set Initialize and SkipTapeHeader to $true.
.PARAMETER Initialize
Initializes the media as part of the backup operation.
.PARAMETER SkipTapeHeader
Initializes the media as part of the backup operation.
.PARAMETER InputObject
Internal parameter
.PARAMETER AzureBaseUrl
The URL to the basecontainer of an Azure storage account to write backups to.
If specified, the only other parameters than can be used are "NoCopyOnly", "Type", "CompressBackup", "Checksum", "Verify", "AzureCredential", "CreateFolder".
.PARAMETER AzureCredential
The name of the credential on the SQL instance that can write to the AzureBaseUrl.
.PARAMETER NoRecovery
This is passed in to perform a tail log backup if needed
.PARAMETER BuildPath
By default this command won't attempt to create missing paths, this switch will change the behavious so that it wll
.PARAMETER IgnoreFileChecks
This switch stops the function from checking for the validity of paths. This can be useful if SQL Server only has read access to the backup area.
Note, that as we can't check the path you may well end up with errors.
.PARAMETER OutputScriptOnly
Switch causes only the T-SQL script for the backup to be generated. Will not create any paths if they do not exist
.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
Tags: DisasterRecovery, Backup, Restore
Author: Stuart Moore (@napalmgram), stuart-moore.com
Website: https://dbatools.io
Copyright: (C) Chrissy LeMaire, [email protected]
License: MIT https://opensource.org/licenses/MIT
.EXAMPLE
Backup-DbaDatabase -SqlInstance Server1 -Database HR, Finance
This will perform a full database backup on the databases HR and Finance on SQL Server Instance Server1 to Server1's default backup directory.
.EXAMPLE
Backup-DbaDatabase -SqlInstance sql2016 -BackupDirectory C:\temp -Database AdventureWorks2014 -Type Full
Backs up AdventureWorks2014 to sql2016's C:\temp folder.
.EXAMPLE
Backup-DbaDatabase -SqlInstance sql2016 -AzureBaseUrl https://dbatoolsaz.blob.core.windows.net/azbackups/ -AzureCredential dbatoolscred -Type Full -CreateFolder
Performs a full backup of all databases on the sql2016 instance to their own containers under the https://dbatoolsaz.blob.core.windows.net/azbackups/ container on Azure blog storage using the sql credential "dbatoolscred" registered on the sql2016 instance.
#>
[CmdletBinding(DefaultParameterSetName = "Default", SupportsShouldProcess)]
[Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSAvoidUsingPlainTextForPassword", "")] #For AzureCredential
param (
[parameter(ParameterSetName = "Pipe", Mandatory)]
[DbaInstanceParameter]$SqlInstance,
[PSCredential]$SqlCredential,
[Alias("Databases")]
[object[]]$Database,
[object[]]$ExcludeDatabase,
[string[]]$BackupDirectory,
[string]$BackupFileName,
[switch]$CopyOnly,
[ValidateSet('Full', 'Log', 'Differential', 'Diff', 'Database')]
[string]$Type = 'Database',
[parameter(ParameterSetName = "NoPipe", Mandatory, ValueFromPipeline)]
[object[]]$InputObject,
[switch]$CreateFolder,
[int]$FileCount = 0,
[switch]$CompressBackup,
[switch]$Checksum,
[switch]$Verify,
[int]$MaxTransferSize,
[int]$BlockSize,
[int]$BufferCount,
[string]$AzureBaseUrl,
[string]$AzureCredential,
[switch]$NoRecovery,
[switch]$BuildPath,
[switch]$WithFormat,
[switch]$Initialize,
[switch]$SkipTapeHeader,
[switch]$IgnoreFileChecks,
[switch]$OutputScriptOnly,
[Alias('Silent')]
[switch]$EnableException
)
begin {
if ($SqlInstance.length -ne 0) {
Write-Message -Level Verbose -Message "Connecting to $SqlInstance"
try {
$Server = Connect-SqlInstance -SqlInstance $SqlInstance -SqlCredential $SqlCredential -AzureUnsupported
}
catch {
Stop-Function -Message "Cannot connect to $SqlInstance" -ErrorRecord $_
return
}
if ($Database) {
$InputObject = $server.Databases | Where-Object Name -in $Database
}
else {
$InputObject = $server.Databases | Where-Object Name -ne 'tempdb'
}
if ($ExcludeDatabase) {
$InputObject = $InputObject | Where-Object Name -notin $ExcludeDatabase
}
if ($null -eq $BackupDirectory -and $backupfileName -ne 'NUL') {
Write-Message -Message 'No backupfolder passed in, setting it to instance default'
$BackupDirectory = (Get-DbaDefaultPath -SqlInstance $SqlInstance).Backup
}
if ($BackupDirectory.Count -gt 1) {
Write-Message -Level Verbose -Message "Multiple Backup Directories, striping"
$Filecount = $BackupDirectory.Count
}
if ($InputObject.Count -gt 1 -and $BackupFileName -ne '') {
Stop-Function -Message "1 BackupFile specified, but more than 1 database."
return
}
if (($MaxTransferSize % 64kb) -ne 0 -or $MaxTransferSize -gt 4mb) {
Stop-Function -Message "MaxTransferSize value must be a multiple of 64kb and no greater than 4MB"
return
}
if ($BlockSize) {
if ($BlockSize -notin (0.5kb, 1kb, 2kb, 4kb, 8kb, 16kb, 32kb, 64kb)) {
Stop-Function -Message "Block size must be one of 0.5kb,1kb,2kb,4kb,8kb,16kb,32kb,64kb"
return
}
}
if ('' -ne $AzureBaseUrl) {
if ($null -eq $AzureCredential) {
Stop-Function -Message "You must provide the credential name for the Azure Storage Account"
return
}
$AzureBaseUrl = $AzureBaseUrl.Trim("/")
$FileCount = 1
$BackupDirectory = $AzureBaseUrl
}
if ($OutputScriptOnly) {
$IgnoreFileChecks = $true
}
}
}
process {
if (!$SqlInstance -and !$InputObject) {
Stop-Function -Message "You must specify a server and database or pipe some databases"
return
}
Write-Message -Level Verbose -Message "$($InputObject.Count) database to backup"
foreach ($Database in $InputObject) {
$ProgressId = Get-Random
$failures = @()
$dbname = $Database.Name
if ($dbname -eq "tempdb") {
Stop-Function -Message "Backing up tempdb not supported" -Continue
}
if ('Normal' -notin ($Database.Status -split ',')) {
Stop-Function -Message "Database status not Normal. $dbname skipped." -Continue
}
if ($Database.DatabaseSnapshotBaseName) {
Stop-Function -Message "Backing up snapshots not supported. $dbname skipped." -Continue
}
if ($null -eq $server) { $server = $Database.Parent }
Write-Message -Level Verbose -Message "Backup database $database"
if ($null -eq $Database.RecoveryModel) {
$Database.RecoveryModel = $server.Databases[$Database.Name].RecoveryModel
Write-Message -Level Verbose -Message "$dbname is in $($Database.RecoveryModel) recovery model"
}
# Fixes one-off cases of StackOverflowException crashes, see issue 1481
$dbRecovery = $Database.RecoveryModel.ToString()
if ($dbRecovery -eq 'Simple' -and $Type -eq 'Log') {
$failreason = "$database is in simple recovery mode, cannot take log backup"
$failures += $failreason
Write-Message -Level Warning -Message "$failreason"
}
$lastfull = $database.Refresh().LastBackupDate.Year
if ($Type -notin @("Database", "Full") -and $lastfull -eq 1) {
$failreason = "$database does not have an existing full backup, cannot take log or differentialbackup"
$failures += $failreason
Write-Message -Level Warning -Message "$failreason"
}
if ($CopyOnly -ne $true) {
$CopyOnly = $false
}
$server.ConnectionContext.StatementTimeout = 0
$backup = New-Object Microsoft.SqlServer.Management.Smo.Backup
$backup.Database = $Database.Name
$Suffix = "bak"
if ($CompressBackup) {
if ($database.EncryptionEnabled) {
Write-Message -Level Warning -Message "$dbname is enabled for encryption, will not compress"
$backup.CompressionOption = 2
} elseif ($server.Edition -like 'Express*' -or ($server.VersionMajor -eq 10 -and $server.VersionMinor -eq 0 -and $server.Edition -notlike '*enterprise*') -or $server.VersionMajor -lt 10) {
Write-Message -Level Warning -Message "Compression is not supported with this version/edition of Sql Server"
}
else {
Write-Message -Level Verbose -Message "Compression enabled"
$backup.CompressionOption = 1
}
}
if ($Checksum) {
$backup.Checksum = $true
}
if ($Type -in 'Diff', 'Differential') {
Write-Message -Level VeryVerbose -Message "Creating differential backup"
$SMOBackuptype = "Database"
$backup.Incremental = $true
$outputType = 'Differential'
}
$Backup.NoRecovery = $false
if ($Type -eq "Log") {
Write-Message -Level VeryVerbose -Message "Creating log backup"
$Suffix = "trn"
$OutputType = 'Log'
$SMOBackupType = 'Log'
$Backup.NoRecovery = $NoRecovery
}
if ($Type -in 'Full', 'Database') {
Write-Message -Level VeryVerbose -Message "Creating full backup"
$SMOBackupType = "Database"
$OutputType = 'Full'
}
$backup.CopyOnly = $copyonly
$backup.Action = $SMOBackupType
if ('' -ne $AzureBaseUrl) {
$backup.CredentialName = $AzureCredential
}
Write-Message -Level Verbose -Message "Building file name"
$BackupFinalName = ''
$FinalBackupPath = @()
if ('NUL' -eq $BackupFileName) {
$FinalBackupPath += 'NUL:'
$IgnoreFileChecks = $true
}
elseif ('' -ne $BackupFileName) {
$File = New-Object System.IO.FileInfo($BackupFileName)
$BackupFinalName = $file.Name
$suffix = $file.extension -Replace '^\.',''
if ( '' -ne (Split-Path $BackupFileName)) {
Write-Message -Level Verbose -Message "Fully qualified path passed in"
$FinalBackupPath += [IO.Path]::GetFullPath($file.DirectoryName)
}
}
else {
$timestamp = (Get-Date -Format yyyyMMddHHmm)
Write-Message -Level VeryVerbose -Message "Setting filename"
$BackupFinalName = "$($dbname)_$timestamp.$suffix"
}
Write-Message -Level Verbose -Message "Building backup path"
if ($FinalBackupPath.Count -eq 0) {
$FinalBackupPath += $BackupDirectory
}
if ($BackupDirectory.Count -eq 1 -and $Filecount -gt 1) {
for ($i = 0; $i -lt ($Filecount - 1); $i++) {
$FinalBackupPath += $FinalBackupPath[0]
}
}
if ($AzureBaseUrl -or $AzureCredential) {
$slash = "/"
}
else {
$slash = "\"
}
if ($FinalBackupPath.Count -gt 1) {
$File = New-Object System.IO.FileInfo($BackupFinalName)
for ($i = 0; $i -lt $FinalBackupPath.Count; $i++) {
$FinalBackupPath[$i] = $FinalBackupPath[$i] + $slash + $($File.BaseName) + "-$($i+1)-of-$FileCount.$suffix"
}
}
elseif ($FinalBackupPath[0] -ne 'NUL:') {
$FinalBackupPath[0] = $FinalBackupPath[0] + $slash + $BackupFinalName
}
if ($CreateFolder -and $FinalBackupPath[0] -ne 'NUL:') {
for ($i = 0; $i -lt $FinalBackupPath.Count; $i++) {
$parent = [IO.Path]::GetDirectoryName($FinalBackupPath[$i])
$leaf = [IO.Path]::GetFileName($FinalBackupPath[$i])
$FinalBackupPath[$i] = [IO.Path]::Combine($parent, $dbname, $leaf)
}
}
if (-not $IgnoreFileChecks -and -not $AzureBaseUrl) {
$parentPaths = ($FinalBackupPath | ForEach-Object { Split-Path $_ } | Select-Object -Unique)
foreach ($parentPath in $parentPaths) {
if (-not (Test-DbaPath -SqlInstance $server -Path $parentPath)) {
if (($BuildPath -eq $true) -or ($CreateFolder -eq $True)) {
$null = New-DbaDirectory -SqlInstance $server -Path $parentPath
}
else {
$failreason += "SQL Server cannot check if $parentPath exists. You can try disabling this check with -IgnoreFileChecks"
$failures += $failreason
Write-Message -Level Warning -Message "$failreason"
}
}
}
}
if ('' -eq $AzureBaseUrl -and $BackupDirectory) {
$FinalBackupPath = $FinalBackupPath | ForEach-Object { [IO.Path]::GetFullPath($_) }
}
$script = $null
$backupComplete = $false
if (!$failures) {
$Filecount = $FinalBackupPath.Count
foreach ($backupfile in $FinalBackupPath) {
$device = New-Object Microsoft.SqlServer.Management.Smo.BackupDeviceItem
if ('' -ne $AzureBaseUrl) {
$device.DeviceType = "URL"
}
else {
$device.DeviceType = "File"
}
if ($WithFormat) {
Write-Message -Message "WithFormat specified. Ensuring Initialize and SkipTapeHeader are set to true." -Level Verbose
$Initialize = $true
$SkipTapeHeader = $true
}
$backup.FormatMedia = $WithFormat
$backup.Initialize = $Initialize
$backup.SkipTapeHeader = $SkipTapeHeader
$device.Name = $backupfile
$backup.Devices.Add($device)
}
$humanBackupFile = $FinalBackupPath -Join ','
Write-Message -Level Verbose -Message "Devices added"
$percent = [Microsoft.SqlServer.Management.Smo.PercentCompleteEventHandler] {
Write-Progress -id $ProgressId -activity "Backing up database $dbname to $humanBackupFile" -percentcomplete $_.Percent -status ([System.String]::Format("Progress: {0} %", $_.Percent))
}
$backup.add_PercentComplete($percent)
$backup.PercentCompleteNotification = 1
$backup.add_Complete($complete)
if ($MaxTransferSize) {
$backup.MaxTransferSize = $MaxTransferSize
}
if ($BufferCount) {
$backup.BufferCount = $BufferCount
}
if ($BlockSize) {
$backup.Blocksize = $BlockSize
}
Write-Progress -id $ProgressId -activity "Backing up database $dbname to $humanBackupFile" -percentcomplete 0 -status ([System.String]::Format("Progress: {0} %", 0))
try {
if ($Pscmdlet.ShouldProcess($server.Name, "Backing up $dbname to $humanBackupFile")) {
if ($OutputScriptOnly -ne $True) {
$Filelist = @()
$FileList += $server.Databases[$dbname].FileGroups.Files | Select-Object @{ Name = "FileType"; Expression = { "D" } }, @{ Name = "Type"; Expression = { "D" } }, @{ Name = "LogicalName"; Expression = { $_.Name } }, @{ Name = "PhysicalName"; Expression = { $_.FileName } }
$FileList += $server.Databases[$dbname].LogFiles | Select-Object @{ Name = "FileType"; Expression = { "L" } }, @{ Name = "Type"; Expression = { "L" } }, @{ Name = "LogicalName"; Expression = { $_.Name } }, @{ Name = "PhysicalName"; Expression = { $_.FileName } }
$backup.SqlBackup($server)
$script = $backup.Script($server)
Write-Progress -id $ProgressId -activity "Backing up database $dbname to $backupfile" -status "Complete" -Completed
$BackupComplete = $true
if ($server.VersionMajor -eq '8') {
$HeaderInfo = Get-BackupAncientHistory -SqlInstance $server -Database $dbname
}
else {
$HeaderInfo = Get-DbaBackupHistory -SqlInstance $server -Database $dbname -Last -IncludeCopyOnly | Sort-Object -Property End -Descending | Select-Object -First 1
}
$Verified = $false
if ($Verify) {
$verifiedresult = [PSCustomObject]@{
SqlInstance = $server.name
DatabaseName = $dbname
BackupComplete = $BackupComplete
BackupFilesCount = $FinalBackupPath.Count
BackupFile = (Split-Path $FinalBackupPath -Leaf)
BackupFolder = (Split-Path $FinalBackupPath | Sort-Object -Unique)
BackupPath = ($FinalBackupPath | Sort-Object -Unique)
Script = $script
Notes = $failures -join (',')
FullName = ($FinalBackupPath | Sort-Object -Unique)
FileList = $FileList
SoftwareVersionMajor = $server.VersionMajor
Type = $outputType
FirstLsn = $HeaderInfo.FirstLsn
DatabaseBackupLsn = $HeaderInfo.DatabaseBackupLsn
CheckPointLsn = $HeaderInfo.CheckPointLsn
LastLsn = $HeaderInfo.LastLsn
BackupSetId = $HeaderInfo.BackupSetId
LastRecoveryForkGUID = $HeaderInfo.LastRecoveryForkGUID
} | Restore-DbaDatabase -SqlInstance $server -DatabaseName DbaVerifyOnly -VerifyOnly -TrustDbBackupHistory -DestinationFilePrefix DbaVerifyOnly
if ($verifiedResult[0] -eq "Verify successful") {
$failures += $verifiedResult[0]
$Verified = $true
}
else {
$failures += $verifiedResult[0]
$Verified = $false
}
}
$HeaderInfo | Add-Member -Type NoteProperty -Name BackupComplete -Value $BackupComplete
$HeaderInfo | Add-Member -Type NoteProperty -Name BackupFile -Value (Split-Path $FinalBackupPath -Leaf)
$HeaderInfo | Add-Member -Type NoteProperty -Name BackupFilesCount -Value $FinalBackupPath.Count
if ($FinalBackupPath[0] -eq 'NUL:') {
$pathresult = "NUL:"
}
else {
$pathresult = (Split-Path $FinalBackupPath | Sort-Object -Unique)
}
$HeaderInfo | Add-Member -Type NoteProperty -Name BackupFolder -Value $pathresult
$HeaderInfo | Add-Member -Type NoteProperty -Name BackupPath -Value ($FinalBackupPath | Sort-Object -Unique)
$HeaderInfo | Add-Member -Type NoteProperty -Name DatabaseName -Value $dbname
$HeaderInfo | Add-Member -Type NoteProperty -Name Notes -Value ($failures -join (','))
$HeaderInfo | Add-Member -Type NoteProperty -Name Script -Value $script
$HeaderInfo | Add-Member -Type NoteProperty -Name Verified -Value $Verified
}
else {
$backup.Script($server)
}
}
}
catch {
if ($NoRecovery -and ($_.Exception.InnerException.InnerException.InnerException -like '*cannot be opened. It is in the middle of a restore.')) {
Write-Message -Message "Exception thrown by db going into restoring mode due to recovery" -Leve Verbose
}
else {
Write-Progress -id $ProgressId -activity "Backup" -status "Failed" -completed
Stop-Function -message "Backup Failed: $($_.Exception.Message)" -EnableException $EnableException -ErrorRecord $_ -Continue
$BackupComplete = $false
}
}
}
$OutputExclude = 'FullName', 'FileList', 'SoftwareVersionMajor'
if ($failures.Count -eq 0) {
$OutputExclude += ('Notes', 'FirstLsn', 'DatabaseBackupLsn', 'CheckpointLsn', 'LastLsn', 'BackupSetId', 'LastRecoveryForkGuid')
}
$headerinfo | Select-DefaultView -ExcludeProperty $OutputExclude
$BackupFileName = $null
}
}
}
function Backup-DbaDbCertificate {
<#
.SYNOPSIS
Exports database certificates from SQL Server using SMO.
.DESCRIPTION
Exports database certificates from SQL Server using SMO and outputs the .cer and .pvk files.
.PARAMETER SqlInstance
SQL Server name or SMO object representing the SQL Server to connect to. This can be a collection and receive pipeline input to allow the function to be executed against multiple SQL Server instances.
.PARAMETER SqlCredential
Login to the target instance using alternative credentials. Windows and SQL Authentication supported. Accepts credential objects (Get-Credential)
.PARAMETER Certificate
Exports certificate that matches the name(s).
.PARAMETER Database
Exports the encryptor for specific database(s).
.PARAMETER ExcludeDatabase
Database(s) to skip when exporting encryptors.
.PARAMETER EncryptionPassword
A string value that specifies the system path to encrypt the private key.
.PARAMETER DecryptionPassword
A string value that specifies the system path to decrypt the private key.
.PARAMETER Path
The path to output the files to. The path is relative to the SQL Server itself. If no path is specified, the default data directory will be used.
.PARAMETER Suffix
The suffix of the filename of the exported certificate.
.PARAMETER InputObject
Certificate object
.PARAMETER Confirm
If this switch is enabled, you will be prompted for confirmation before executing any operations that change state.
.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.
.NOTES
Author: Jess Pomfret (@jpomfret)
Tags: Migration, Certificate
Website: https://dbatools.io
Copyright: (C) Chrissy LeMaire, [email protected]
License: MIT https://opensource.org/licenses/MIT
.EXAMPLE
Backup-DbaDbCertificate -SqlInstance Server1
Exports all the certificates on the specified SQL Server to the default data path for the instance.
.EXAMPLE
$cred = Get-Credential sqladmin
Backup-DbaDbCertificate -SqlInstance Server1 -SqlCredential $cred
Connects using sqladmin credential and exports all the certificates on the specified SQL Server to the default data path for the instance.
.EXAMPLE
Backup-DbaDbCertificate -SqlInstance Server1 -Certificate Certificate1
Exports only the certificate named Certificate1 on the specified SQL Server to the default data path for the instance.
.EXAMPLE
Backup-DbaDbCertificate -SqlInstance Server1 -Database AdventureWorks
Exports only the certificates for AdventureWorks on the specified SQL Server to the default data path for the instance.
.EXAMPLE
Backup-DbaDbCertificate -SqlInstance Server1 -ExcludeDatabase AdventureWorks
Exports all certificates except those for AdventureWorks on the specified SQL Server to the default data path for the instance.
.EXAMPLE
Backup-DbaDbCertificate -SqlInstance Server1 -Path \\Server1\Certificates -EncryptionPassword (ConvertTo-SecureString -force -AsPlainText GoodPass1234!!)
Exports all the certificates and private keys on the specified SQL Server.
.EXAMPLE
$EncryptionPassword = ConvertTo-SecureString -AsPlainText "GoodPass1234!!" -force
$DecryptionPassword = ConvertTo-SecureString -AsPlainText "Password4567!!" -force
Backup-DbaDbCertificate -SqlInstance Server1 -EncryptionPassword $EncryptionPassword -DecryptionPassword $DecryptionPassword
Exports all the certificates on the specified SQL Server using the supplied DecryptionPassword, since an EncryptionPassword is specified private keys are also exported.
.EXAMPLE
Backup-DbaDbCertificate -SqlInstance Server1 -Path \\Server1\Certificates
Exports all certificates on the specified SQL Server to the specified path.
.EXAMPLE
Backup-DbaDbCertificate -SqlInstance Server1 -Suffix DbaTools
Exports all certificates on the specified SQL Server to the specified path, appends DbaTools to the end of the filenames.
.EXAMPLE
Get-DbaDbCertificate -SqlInstance sql2016 | Backup-DbaDbCertificate
Exports all certificates found on sql2016 to the default data directory.
#>
[CmdletBinding(DefaultParameterSetName = "Default", SupportsShouldProcess = $true)]
param (
[parameter(Mandatory, ParameterSetName = "instance")]
[Alias("ServerInstance", "SqlServer")]
[DbaInstanceParameter[]]$SqlInstance,
[PSCredential]$SqlCredential,
[parameter(ParameterSetName = "instance")]
[object[]]$Certificate,
[parameter(ParameterSetName = "instance")]
[object[]]$Database,
[parameter(ParameterSetName = "instance")]
[object[]]$ExcludeDatabase,
[parameter(Mandatory = $false)]
[Security.SecureString]$EncryptionPassword,
[parameter(Mandatory = $false)]
[Security.SecureString]$DecryptionPassword,
[System.IO.FileInfo]$Path,
[string]$Suffix = "$(Get-Date -format 'yyyyMMddHHmmssms')",
[parameter(ValueFromPipeline, ParameterSetName = "collection")]
[Microsoft.SqlServer.Management.Smo.Certificate[]]$InputObject,
[Alias('Silent')]
[switch]$EnableException
)
begin {
if ($EncryptionPassword.Length -eq 0 -and $DecryptionPassword.Length -gt 0) {
Stop-Function -Message "If you specify an decryption password, you must also specify an encryption password" -Target $DecryptionPassword
}
Test-DbaDeprecation -DeprecatedOn "1.0.0" -Alias Backup-DbaDatabaseCertificate
function export-cert ($cert) {
$certName = $cert.Name
$db = $cert.Parent
$server = $db.Parent
$instance = $server.Name
$actualPath = $Path
if ($null -eq $actualPath) {
$actualPath = Get-SqlDefaultPaths -SqlInstance $server -filetype Data
}
$actualPath = "$actualPath".TrimEnd('\')
$fullCertName = "$actualPath\$certName$Suffix"
$exportPathKey = "$fullCertName.pvk"
if (!(Test-DbaPath -SqlInstance $server -Path $actualPath)) {
Stop-Function -Message "$SqlInstance cannot access $actualPath" -Target $actualPath
}
if ($Pscmdlet.ShouldProcess($instance, "Exporting certificate $certName from $db on $instance to $actualPath")) {
Write-Message -Level Verbose -Message "Exporting Certificate: $certName to $fullCertName"
try {
$exportPathCert = "$fullCertName.cer"
# because the password shouldn't go to memory...
if ($EncryptionPassword.Length -gt 0 -and $DecryptionPassword.Length -gt 0) {
Write-Message -Level Verbose -Message "Both passwords passed in. Will export both cer and pvk."
$cert.export(
$exportPathCert,
$exportPathKey,
[System.Runtime.InteropServices.marshal]::PtrToStringAuto([System.Runtime.InteropServices.marshal]::SecureStringToBSTR($EncryptionPassword)),
[System.Runtime.InteropServices.marshal]::PtrToStringAuto([System.Runtime.InteropServices.marshal]::SecureStringToBSTR($DecryptionPassword))
)
}
elseif ($EncryptionPassword.Length -gt 0 -and $DecryptionPassword.Length -eq 0) {
Write-Message -Level Verbose -Message "Only encryption password passed in. Will export both cer and pvk."
$cert.export(
$exportPathCert,
$exportPathKey,
[System.Runtime.InteropServices.marshal]::PtrToStringAuto([System.Runtime.InteropServices.marshal]::SecureStringToBSTR($EncryptionPassword))
)
}
else {
Write-Message -Level Verbose -Message "No passwords passed in. Will export just cer."
$exportPathKey = "Password required to export key"
$cert.export($exportPathCert)
}
[pscustomobject]@{
ComputerName = $server.ComputerName
InstanceName = $server.ServiceName
SqlInstance = $server.DomainInstanceName
Database = $db.Name
Certificate = $certName
Path = $exportPathCert
Key = $exportPathKey
ExportPath = $exportPathCert
ExportKey = $exportPathKey
exportPathCert = $exportPathCert
exportPathKey = $exportPathKey
Status = "Success"
} | Select-DefaultView -ExcludeProperty exportPathCert, exportPathKey, ExportPath, ExportKey
}
catch {
if ($_.Exception.InnerException) {
$exception = $_.Exception.InnerException.ToString() -Split "System.Data.SqlClient.SqlException: "
$exception = ($exception[1] -Split "at Microsoft.SqlServer.Management.Common.ConnectionManager")[0]
}
else {
$exception = $_.Exception
}
[pscustomobject]@{
ComputerName = $server.ComputerName
InstanceName = $server.ServiceName
SqlInstance = $server.DomainInstanceName
Database = $db.Name
Certificate = $certName
Path = $exportPathCert
Key = $exportPathKey
ExportPath = $exportPathCert
ExportKey = $exportPathKey
exportPathCert = $exportPathCert
exportPathKey = $exportPathKey
Status = "Failure: $exception"
} | Select-DefaultView -ExcludeProperty exportPathCert, exportPathKey, ExportPath, ExportKey
Stop-Function -Message "$certName from $db on $instance cannot be exported." -Continue -Target $cert -ErrorRecord $_
}
}
}
}
process {
if (Test-FunctionInterrupt) { return }
foreach ($instance in $SqlInstance) {
try {
Write-Message -Level Verbose -Message "Connecting to $instance"
$server = Connect-SqlInstance -SqlInstance $instance -SqlCredential $SqlCredential
}
catch {
Stop-Function -Message "Failure" -Category ConnectionError -ErrorRecord $_ -Target $instance -Continue
return
}
$databases = Get-DbaDatabase -SqlInstance $server | Where-Object IsAccessible
if ($Database) {
$databases = $databases | Where-Object Name -in $Database
}
if ($ExcludeDatabase) {
$databases = $databases | Where-Object Name -NotIn $ExcludeDatabase
}
foreach ($db in $databases.Name) {
$DBInputObject = Get-DbaDbCertificate -SqlInstance $server -Database $db
if ($Certificate) {
$InputObject += $DBInputObject | Where-Object Name -In $Certificate
}
else {
$InputObject += $DBInputObject | Where-Object Name -NotLike "##*"
}
if (!$InputObject) {
Write-Message -Level Output -Message "No certificates found to export in $db."
continue
}
}
}
foreach ($cert in $InputObject) {
if ($cert.Name.StartsWith("##")) {
Write-Message -Level Output -Message "Skipping system cert $cert"
}
else {
export-cert $cert
}
}
}
}
function Backup-DbaDbMasterKey {
<#
.SYNOPSIS
Backs up specified database master key.
.DESCRIPTION
Backs up specified database master key.
.PARAMETER SqlInstance
The target SQL Server instance.
.PARAMETER SqlCredential
Allows you to login to SQL Server using alternative credentials.
.PARAMETER Database
Backup master key from specific database(s).
.PARAMETER ExcludeDatabase
The database(s) to exclude - this list is auto-populated from the server.
.PARAMETER Path
The directory to export the key. If no path is specified, the default backup directory for the instance will be used.
.PARAMETER Credential
Pass a credential object for the password
.PARAMETER Password
The password to encrypt the exported key. This must be a SecureString.
.PARAMETER InputObject
Database object piped in from Get-DbaDatabase
.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.
.NOTES
Tags: Certificate, Database
Author: Chrissy LeMaire (@cl), netnerds.net
Website: https://dbatools.io
Copyright: (C) Chrissy LeMaire, [email protected]
License: MIT https://opensource.org/licenses/MIT
.EXAMPLE
Backup-DbaDbMasterKey -SqlInstance server1\sql2016
Prompts for export password, then logs into server1\sql2016 with Windows credentials then backs up all database keys to the default backup directory.
ComputerName : SERVER1
InstanceName : SQL2016
SqlInstance : SERVER1\SQL2016
Database : master
Filename : E:\MSSQL13.SQL2016\MSSQL\Backup\server1$sql2016-master-20170614162311.key
Status : Success
.EXAMPLE
Backup-DbaDbMasterKey -SqlInstance Server1 -Database db1 -Path \\nas\sqlbackups\keys
Logs into sql2016 with Windows credentials then backs up db1's keys to the \\nas\sqlbackups\keys directory.
#>
[CmdletBinding()]
param (
[parameter(Mandatory, ValueFromPipeline)]
[Alias("ServerInstance", "SqlServer")]
[DbaInstanceParameter[]]$SqlInstance,
[PSCredential]$SqlCredential,
[PSCredential]$Credential,
[string[]]$Database,
[string[]]$ExcludeDatabase,
[Security.SecureString]$Password,
[string]$Path,
[Microsoft.SqlServer.Management.Smo.Database[]]$InputObject,
[Alias('Silent')]
[switch]$EnableException
)
begin {
if ($Credential) {
$Password = $Credential.Password
}
}
process {
foreach ($instance in $SqlInstance) {
$InputObject += Get-DbaDatabase -SqlInstance $SqlInstance -SqlCredential $SqlCredential -Database $Database -ExcludeDatabase $ExcludeDatabase
}
foreach ($db in $InputObject) {
$server = $db.Parent
if (Test-Bound -ParameterName Path -Not) {
$Path = $server.BackupDirectory
}
if (!$Path) {
Stop-Function -Message "Path discovery failed. Please explicitly specify -Path" -Target $server -Continue
}
if (!(Test-DbaPath -SqlInstance $server -Path $Path)) {
Stop-Function -Message "$instance cannot access $Path" -Target $server -ErrorRecord $_ -Continue
}
if (!$db.IsAccessible) {
Write-Message -Level Warning -Message "Database $db is not accessible. Skipping."
continue
}
$masterkey = $db.MasterKey
if (!$masterkey) {
Write-Message -Message "No master key exists in the $db database on $instance" -Target $db -Level Verbose
continue
}
# If you pass a password param, then you will not be prompted for each database, but it wouldn't be a good idea to build in insecurity
if ((Test-Bound -ParameterName Password -Not) -and (Test-Bound -ParameterName Credential -Not)) {
$password = Read-Host -AsSecureString -Prompt "You must enter Service Key password for $instance"
$password2 = Read-Host -AsSecureString -Prompt "Type the password again"
if (([System.Runtime.InteropServices.marshal]::PtrToStringAuto([System.Runtime.InteropServices.marshal]::SecureStringToBSTR($password))) -ne ([System.Runtime.InteropServices.marshal]::PtrToStringAuto([System.Runtime.InteropServices.marshal]::SecureStringToBSTR($password2)))) {
Stop-Function -Message "Passwords do not match" -Continue
}
}
$time = (Get-Date -Format yyyMMddHHmmss)
$dbname = $db.name
$Path = $Path.TrimEnd("\")
$fileinstance = $instance.ToString().Replace('\', '$')
$filename = "$Path\$fileinstance-$dbname-$time.key"
try {
$masterkey.Export($filename, [System.Runtime.InteropServices.marshal]::PtrToStringAuto([System.Runtime.InteropServices.marshal]::SecureStringToBSTR($password)))
$status = "Success"
}
catch {
$status = "Failure"
Write-Message -Level Warning -Message "Backup failure: $($_.Exception.InnerException)"
}
Add-Member -Force -InputObject $masterkey -MemberType NoteProperty -Name ComputerName -value $server.ComputerName
Add-Member -Force -InputObject $masterkey -MemberType NoteProperty -Name InstanceName -value $server.ServiceName
Add-Member -Force -InputObject $masterkey -MemberType NoteProperty -Name SqlInstance -value $server.DomainInstanceName
Add-Member -Force -InputObject $masterkey -MemberType NoteProperty -Name Database -value $dbname
Add-Member -Force -InputObject $masterkey -MemberType NoteProperty -Name Filename -value $filename
Add-Member -Force -InputObject $masterkey -MemberType NoteProperty -Name Status -value $status
Select-DefaultView -InputObject $masterkey -Property ComputerName, InstanceName, SqlInstance, Database, 'Filename as Path', Status
}
}
end {
Test-DbaDeprecation -DeprecatedOn "1.0.0" -EnableException:$false -Alias Backup-DbaDatabaseMasterKey
}
}
function Clear-DbaConnectionPool {
<#
.SYNOPSIS
Resets (or empties) the connection pool.
.DESCRIPTION
This command resets (or empties) the connection pool.
If there are connections in use at the time of the call, they are marked appropriately and will be discarded (instead of being returned to the pool) when Close() is called on them.
Ref: https://msdn.microsoft.com/en-us/library/system.data.sqlclient.sqlconnection.clearallpools(v=vs.110).aspx
.PARAMETER ComputerName
Target computer(s). If no computer name is specified, the local computer is targeted.
.PARAMETER Credential
Alternate credential object to use for accessing the target computer(s).
.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.
.NOTES
Tags: Connection
Author: Chrissy LeMaire (@cl), netnerds.net
Website: https://dbatools.io
Copyright: (C) Chrissy LeMaire, [email protected]
License: MIT https://opensource.org/licenses/MIT
.LINK
https://dbatools.io/Clear-DbaConnectionPool
.EXAMPLE
Clear-DbaConnectionPool
Clears all local connection pools.
.EXAMPLE
Clear-DbaConnectionPool -ComputerName workstation27
Clears all connection pools on workstation27.
#>
[CmdletBinding()]
param (
[Parameter(ValueFromPipeline)]
[Alias("cn", "host", "Server")]
[DbaInstanceParameter[]]$ComputerName = $env:COMPUTERNAME,
[PSCredential]$Credential,
[switch][Alias('Silent')]
$EnableException
)
process {
# TODO: https://jamessdixon.wordpress.com/2013/01/22/ado-net-and-connection-pooling
foreach ($computer in $ComputerName) {
try {
if (-not $computer.IsLocalhost) {
Write-Message -Level Verbose -Message "Clearing all pools on remote computer $computer"
if (Test-Bound 'Credential') {
Invoke-Command2 -ComputerName $computer -Credential $Credential -ScriptBlock { [System.Data.SqlClient.SqlConnection]::ClearAllPools() }
}
else {
Invoke-Command2 -ComputerName $computer -ScriptBlock { [System.Data.SqlClient.SqlConnection]::ClearAllPools() }
}
}
else {
Write-Message -Level Verbose -Message "Clearing all local pools"
if (Test-Bound 'Credential') {
Invoke-Command2 -Credential $Credential -ScriptBlock { [System.Data.SqlClient.SqlConnection]::ClearAllPools() }
}
else {
Invoke-Command2 -ScriptBlock { [System.Data.SqlClient.SqlConnection]::ClearAllPools() }
}
}
}
catch {
Stop-Function -Message "Failure" -ErrorRecord $_ -Target $computer -Continue
}
}
}
end {
Test-DbaDeprecation -DeprecatedOn "1.0.0" -EnableException:$false -Alias Clear-DbaSqlConnectionPool
}
}
function Clear-DbaPlanCache {
<#
.SYNOPSIS
Removes adhoc and prepared plan caches is single use plans are over defined threshold.
.DESCRIPTION
Checks ahoc and prepared plan cache for each database, if over 100 MBs removes from the cache.
This command automates that process.
References: https://www.sqlskills.com/blogs/kimberly/plan-cache-adhoc-workloads-and-clearing-the-single-use-plan-cache-bloat/
.PARAMETER SqlInstance
The target SQL Server instance.
.PARAMETER SqlCredential
Login to the target instance using alternative credentials. Windows and SQL Authentication supported. Accepts credential objects (Get-Credential)
.PARAMETER Threshold
Memory used threshold.
.PARAMETER InputObject
Enables results to be piped in from Get-DbaPlanCache.
.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.
.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.
.NOTES
Tags: Memory
Author: Tracy Boggiano, databasesuperhero.com
dbatools PowerShell module (https://dbatools.io, [email protected])
Copyright (C) 2016 Chrissy LeMaire
License: GNU GPL v3 https://opensource.org/licenses/GPL-3.0
.LINK
https://dbatools.io/Clear-DbaPlanCache
.EXAMPLE
Clear-DbaPlanCache -SqlInstance sql2017 -Threshold 200
Logs into the SQL Server instance "sql2017" and removes plan caches if over 200 MB.
.EXAMPLE
Clear-DbaPlanCache -SqlInstance sql2017 -SqlCredential sqladmin
Logs into the SQL instance using the SQL Login 'sqladmin' and then Windows instance as 'ad\sqldba'
and removes if Threshold over 100 MB.
#>
[CmdletBinding(SupportsShouldProcess)]
Param (
[Alias("ServerInstance", "SqlServer", "SqlServers")]
[DbaInstanceParameter[]]$SqlInstance,
[PSCredential]$SqlCredential,
[int]$Threshold = 100,
[parameter(ValueFromPipeline)]
[object[]]$InputObject,
[switch]$EnableException
)
process {
foreach ($instance in $SqlInstance) {
$InputObject += Get-DbaPlanCache -SqlInstance $instance -SqlCredential $SqlCredential
}
foreach ($result in $InputObject) {
if ($result.MB -ge $Threshold) {
if ($Pscmdlet.ShouldProcess($($result.SqlInstance), "Cleared SQL Plans plan cache")) {
$server.Query("DBCC FREESYSTEMCACHE('SQL Plans')")
[pscustomobject]@{
ComputerName = $result.ComputerName
InstanceName = $result.InstanceName
SqlInstance = $result.SqlInstance
Size = $result.Size
Status = "Plan cache cleared"
}
}
}
else {
if ($Pscmdlet.ShouldProcess($($result.SqlInstance), "Results $($result.Size) below threshold")) {
[pscustomobject]@{
ComputerName = $result.ComputerName
InstanceName = $result.InstanceName
SqlInstance = $result.SqlInstance
Size = $result.Size
Status = "Plan cache size below threshold ($Threshold) "
}
Write-Message -Level Verbose -Message "Plan cache size below threshold ($Threshold) "
}
}
}
}
}
function Clear-DbaWaitStatistics {
<#
.SYNOPSIS
Clears wait statistics
.DESCRIPTION
Reset the aggregated statistics - basically just executes DBCC SQLPERF (N'sys.dm_os_wait_stats', CLEAR)
.PARAMETER SqlInstance
Allows you to specify a comma separated list of servers to query.
.PARAMETER SqlCredential
Login to the target instance using alternative credentials. Windows and SQL Authentication supported. Accepts credential objects (Get-Credential)
.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.
.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.
.NOTES
Tags: WaitStatistic
Author: Chrissy LeMaire (@cl)
Website: https://dbatools.io
Copyright: (C) Chrissy LeMaire, [email protected]
License: MIT https://opensource.org/licenses/MIT
.LINK
https://dbatools.io/Clear-DbaWaitStatistics
.EXAMPLE
Clear-DbaWaitStatistics -SqlInstance sql2008, sqlserver2012
After confirmation, clears wait stats on servers sql2008 and sqlserver2012
.EXAMPLE
Clear-DbaWaitStatistics -SqlInstance sql2008, sqlserver2012 -Confirm:$false
Clears wait stats on servers sql2008 and sqlserver2012, without prompting
#>
[CmdletBinding(ConfirmImpact = 'High', SupportsShouldProcess)]
param (
[parameter(Position = 0, Mandatory, ValueFromPipeline)]
[Alias("ServerInstance", "SqlServer", "SqlServers")]
[DbaInstance[]]$SqlInstance,
[PSCredential]$SqlCredential,
[Alias('Silent')]
[switch]$EnableException
)
process {
foreach ($instance in $SqlInstance) {
Write-Message -Level Verbose -Message "Connecting to $instance"
try {
$server = Connect-SqlInstance -SqlInstance $instance -SqlCredential $SqlCredential -MinimumVersion 9
}
catch {
Stop-Function -Message "Failure" -Category ConnectionError -ErrorRecord $_ -Target $instance -Continue
}
if ($Pscmdlet.ShouldProcess($instance, "Performing CLEAR of sys.dm_os_wait_stats")) {
try {
$server.Query("DBCC SQLPERF (N'sys.dm_os_wait_stats', CLEAR);")
$status = "Success"
}
catch {
$status = $_.Exception
}
[PSCustomObject]@{
ComputerName = $server.ComputerName
InstanceName = $server.ServiceName
SqlInstance = $server.DomainInstanceName
Status = $status
}
}
}
}
}
function Connect-DbaInstance {
<#
.SYNOPSIS
Creates a robust SMO SQL Server object.
.DESCRIPTION
This command is robust because it initializes properties that do not cause enumeration by default. It also supports both Windows and SQL Server authentication methods, and detects which to use based upon the provided credentials.
By default, this command also sets the connection's ApplicationName property to "dbatools PowerShell module - dbatools.io - custom connection". If you're doing anything that requires profiling, you can look for this client name.
Alternatively, you can pass in whichever client name you'd like using the -ClientName parameter. There are a ton of other parameters for you to explore as well.
See https://msdn.microsoft.com/en-us/library/system.data.sqlclient.sqlconnection.connectionstring.aspx
and https://msdn.microsoft.com/en-us/library/system.data.sqlclient.sqlconnectionstringbuilder.aspx,
and https://msdn.microsoft.com/en-us/library/system.data.sqlclient.sqlconnection.aspx
To execute SQL commands, you can use $server.ConnectionContext.ExecuteReader($sql) or $server.Databases['master'].ExecuteNonQuery($sql)
.PARAMETER SqlInstance
SQL Server name or SMO object representing the SQL Server to connect to. This can be a collection and receive pipeline input to allow the function to be executed against multiple SQL Server instances.
.PARAMETER Credential
Credential object used to connect to the SQL Server Instance as a different user. This can be a Windows or SQL Server account. Windows users are determined by the existence of a backslash, so if you are intending to use an alternative Windows connection instead of a SQL login, ensure it contains a backslash.
.PARAMETER Database
The database(s) to process. This list is auto-populated from the server.
.PARAMETER AccessToken
Gets or sets the access token for the connection.
.PARAMETER AppendConnectionString
Appends to the current connection string. Note that you cannot pass authentication information using this method. Use -SqlInstance and optionally -SqlCredential to set authentication information.
.PARAMETER ApplicationIntent
Declares the application workload type when connecting to a server.
Valid values are "ReadOnly" and "ReadWrite".
.PARAMETER BatchSeparator
A string to separate groups of SQL statements being executed. By default, this is "GO".
.PARAMETER ClientName
By default, this command sets the client's ApplicationName property to "dbatools PowerShell module - dbatools.io - custom connection" if you're doing anything that requires profiling, you can look for this client name. Using -ClientName allows you to set your own custom client application name.
.PARAMETER ConnectTimeout
The length of time (in seconds) to wait for a connection to the server before terminating the attempt and generating an error.
Valid values are integers between 0 and 2147483647.
When opening a connection to a Azure SQL Database, set the connection timeout to 30 seconds.
.PARAMETER EncryptConnection
If this switch is enabled, SQL Server uses SSL encryption for all data sent between the client and server if the server has a certificate installed.
For more information, see Connection String Syntax. https://docs.microsoft.com/en-us/dotnet/framework/data/adonet/connection-string-syntax
Beginning in .NET Framework 4.5, when TrustServerCertificate is false and Encrypt is true, the server name (or IP address) in a SQL Server SSL certificate must exactly match the server name (or IP address) specified in the connection string. Otherwise, the connection attempt will fail. For information about support for certificates whose subject starts with a wildcard character (*), see Accepted wildcards used by server certificates for server authentication. https://support.microsoft.com/en-us/help/258858/accepted-wildcards-used-by-server-certificates-for-server-authenticati
.PARAMETER FailoverPartner
The name of the failover partner server where database mirroring is configured.
If the value of this key is "" (an empty string), then Initial Catalog must be present in the connection string, and its value must not be "".
The server name can be 128 characters or less.
If you specify a failover partner but the failover partner server is not configured for database mirroring and the primary server (specified with the Server keyword) is not available, then the connection will fail.
If you specify a failover partner and the primary server is not configured for database mirroring, the connection to the primary server (specified with the Server keyword) will succeed if the primary server is available.
.PARAMETER IsActiveDirectoryUniversalAuth
If this switch is enabled, the connection will be configured to use Azure Active Directory authentication.
.PARAMETER LockTimeout
Sets the time in seconds required for the connection to time out when the current transaction is locked.
.PARAMETER MaxPoolSize
Sets the maximum number of connections allowed in the connection pool for this specific connection string.
.PARAMETER MinPoolSize
Sets the minimum number of connections allowed in the connection pool for this specific connection string.
.PARAMETER MultipleActiveResultSets
If this switch is enabled, an application can maintain multiple active result sets (MARS).
If this switch is not enabled, an application must process or cancel all result sets from one batch before it can execute any other batch on that connection.
.PARAMETER MultiSubnetFailover
If this switch is enabled, and your application is connecting to an AlwaysOn availability group (AG) on different subnets, detection of and connection to the currently active server will be faster. For more information about SqlClient support for Always On Availability Groups, see https://docs.microsoft.com/en-us/dotnet/framework/data/adonet/sql/sqlclient-support-for-high-availability-disaster-recovery
.PARAMETER NetworkProtocol
Explicitly sets the network protocol used to connect to the server.
Valid values are "TcpIp","NamedPipes","Multiprotocol","AppleTalk","BanyanVines","Via","SharedMemory" and "NWLinkIpxSpx"
.PARAMETER NonPooledConnection
If this switch is enabled, a non-pooled connection will be requested.
.PARAMETER PacketSize
Sets the size in bytes of the network packets used to communicate with an instance of SQL Server. Must match at server.
.PARAMETER PooledConnectionLifetime
When a connection is returned to the pool, its creation time is compared with the current time and the connection is destroyed if that time span (in seconds) exceeds the value specified by Connection Lifetime. This is useful in clustered configurations to force load balancing between a running server and a server just brought online.
A value of zero (0) causes pooled connections to have the maximum connection timeout.
.PARAMETER SqlExecutionModes
The SqlExecutionModes enumeration contains values that are used to specify whether the commands sent to the referenced connection to the server are executed immediately or saved in a buffer.
Valid values include "CaptureSql", "ExecuteAndCaptureSql" and "ExecuteSql".
.PARAMETER StatementTimeout
Sets the number of seconds a statement is given to run before failing with a timeout error.
.PARAMETER TrustServerCertificate
When this switch is enabled, the channel will be encrypted while bypassing walking the certificate chain to validate trust.
.PARAMETER WorkstationId
Sets the name of the workstation connecting to SQL Server.
.PARAMETER SqlConnectionOnly
Instead of returning a rich SMO server object, this command will only return a SqlConnection object when setting this switch.
.NOTES
Tags: Connect, Connection
Author: Chrissy LeMaire (@cl), netnerds.net
Website: https://dbatools.io
Copyright: (C) Chrissy LeMaire, [email protected]
License: MIT https://opensource.org/licenses/MIT
.LINK
https://dbatools.io/Connect-DbaInstance
.EXAMPLE
Connect-DbaInstance -SqlInstance sql2014
Creates an SMO Server object that connects using Windows Authentication
.EXAMPLE
$wincred = Get-Credential ad\sqladmin
Connect-DbaInstance -SqlInstance sql2014 -Credential $wincred
Creates an SMO Server object that connects using alternative Windows credentials
.EXAMPLE
$sqlcred = Get-Credential sqladmin
$server = Connect-DbaInstance -SqlInstance sql2014 -Credential $sqlcred
Login to sql2014 as SQL login sqladmin.
.EXAMPLE
$server = Connect-DbaInstance -SqlInstance sql2014 -ClientName "my connection"
Creates an SMO Server object that connects using Windows Authentication and uses the client name "my connection". So when you open up profiler or use extended events, you can search for "my connection".
.EXAMPLE
$server = Connect-DbaInstance -SqlInstance sql2014 -AppendConnectionString "Packet Size=4096;AttachDbFilename=C:\MyFolder\MyDataFile.mdf;User Instance=true;"
Creates an SMO Server object that connects to sql2014 using Windows Authentication, then it sets the packet size (this can also be done via -PacketSize) and other connection attributes.
.EXAMPLE
$server = Connect-DbaInstance -SqlInstance sql2014 -NetworkProtocol TcpIp -MultiSubnetFailover
Creates an SMO Server object that connects using Windows Authentication that uses TCP/IP and has MultiSubnetFailover enabled.
.EXAMPLE
$server = Connect-DbaInstance sql2016 -ApplicationIntent ReadOnly
Connects with ReadOnly ApplicationIntent.
#>
[CmdletBinding()]
param (
[Parameter(Mandatory, ValueFromPipeline)]
[Alias("ServerInstance", "SqlServer")]
[DbaInstanceParameter[]]$SqlInstance,
[Alias("SqlCredential")]
[PSCredential]$Credential,
[object[]]$Database,
[string]$AccessToken,
[ValidateSet('ReadOnly', 'ReadWrite')]
[string]$ApplicationIntent,
[string]$BatchSeparator,
[string]$ClientName = "dbatools PowerShell module - dbatools.io - custom connection",
[int]$ConnectTimeout = ([Sqlcollaborative.Dbatools.Connection.ConnectionHost]::SqlConnectionTimeout),
[switch]$EncryptConnection,
[string]$FailoverPartner,
[switch]$IsActiveDirectoryUniversalAuth,
[int]$LockTimeout,
[int]$MaxPoolSize,
[int]$MinPoolSize,
[switch]$MultipleActiveResultSets,
[switch]$MultiSubnetFailover,
[ValidateSet('TcpIp', 'NamedPipes', 'Multiprotocol', 'AppleTalk', 'BanyanVines', 'Via', 'SharedMemory', 'NWLinkIpxSpx')]
[string]$NetworkProtocol,
[switch]$NonPooledConnection,
[int]$PacketSize,
[int]$PooledConnectionLifetime,
[ValidateSet('CaptureSql', 'ExecuteAndCaptureSql', 'ExecuteSql')]
[string]$SqlExecutionModes,
[int]$StatementTimeout,
[switch]$TrustServerCertificate,
[string]$WorkstationId,
[string]$AppendConnectionString,
[switch]$SqlConnectionOnly
)
begin {
Test-DbaDeprecation -DeprecatedOn "1.0.0" -EnableException:$false -Alias Connect-DbaServer
Test-DbaDeprecation -DeprecatedOn "1.0.0" -EnableException:$false -Alias Get-DbaInstance
$loadedSmoVersion = [AppDomain]::CurrentDomain.GetAssemblies() | Where-Object { $_.Fullname -like "Microsoft.SqlServer.SMO,*" }
if ($loadedSmoVersion) {
$loadedSmoVersion = $loadedSmoVersion | ForEach-Object {
if ($_.Location -match "__") {
((Split-Path (Split-Path $_.Location) -Leaf) -split "__")[0]
}
else {
((Get-ChildItem -Path $_.Location).VersionInfo.ProductVersion)
}
}
}
#'PrimaryFilePath' seems the culprit for slow SMO on databases
$Fields2000_Db = 'Collation', 'CompatibilityLevel', 'CreateDate', 'ID', 'IsAccessible', 'IsFullTextEnabled', 'IsSystemObject', 'IsUpdateable', 'LastBackupDate', 'LastDifferentialBackupDate', 'LastLogBackupDate', 'Name', 'Owner', 'ReadOnly', 'RecoveryModel', 'ReplicationOptions', 'Status', 'Version'
$Fields200x_Db = $Fields2000_Db + @('BrokerEnabled', 'DatabaseSnapshotBaseName', 'IsMirroringEnabled', 'Trustworthy')
$Fields201x_Db = $Fields200x_Db + @('ActiveConnections', 'AvailabilityDatabaseSynchronizationState', 'AvailabilityGroupName', 'ContainmentType', 'EncryptionEnabled')
$Fields2000_Login = 'CreateDate' , 'DateLastModified' , 'DefaultDatabase' , 'DenyWindowsLogin' , 'IsSystemObject' , 'Language' , 'LanguageAlias' , 'LoginType' , 'Name' , 'Sid' , 'WindowsLoginAccessType'
$Fields200x_Login = $Fields2000_Login + @('AsymmetricKey', 'Certificate', 'Credential', 'ID', 'IsDisabled', 'IsLocked', 'IsPasswordExpired', 'MustChangePassword', 'PasswordExpirationEnabled', 'PasswordPolicyEnforced')
$Fields201x_Login = $Fields200x_Login + @('PasswordHashAlgorithm')
}
process {
foreach ($instance in $SqlInstance) {
if ($instance.Type -like "Server") {
if ($instance.InputObject.ConnectionContext.IsOpen -eq $false) {
$instance.InputObject.ConnectionContext.Connect()
}
if ($SqlConnectionOnly) { return $instance.InputObject.ConnectionContext.SqlConnectionObject }
else { return $instance.InputObject }
}
if ($instance.Type -like "SqlConnection") {
$server = New-Object Microsoft.SqlServer.Management.Smo.Server($instance.InputObject)
if ($server.ConnectionContext.IsOpen -eq $false) {
$server.ConnectionContext.Connect()
}
if ($SqlConnectionOnly) { return $server.ConnectionContext.SqlConnectionObject }
else {
if (-not $server.ComputerName) {
$parsedcomputername = $server.NetName
if (-not $parsedcomputername) {
$parsedcomputername = ([dbainstance]$instance).ComputerName
}
Add-Member -InputObject $server -NotePropertyName ComputerName -NotePropertyValue $parsedcomputername -Force
}
return $server
}
}
if ($instance.IsConnectionString) { $server = New-Object Microsoft.SqlServer.Management.Smo.Server($instance.InputObject) }
else { $server = New-Object Microsoft.SqlServer.Management.Smo.Server $instance.FullSmoName }
if ($AppendConnectionString) {
$connstring = $server.ConnectionContext.ConnectionString
$server.ConnectionContext.ConnectionString = "$connstring;$appendconnectionstring"
$server.ConnectionContext.Connect()
}
else {
$server.ConnectionContext.ApplicationName = $ClientName
if (Test-Bound -ParameterName 'AccessToken') { $server.ConnectionContext.AccessToken = $AccessToken }
if (Test-Bound -ParameterName 'BatchSeparator') { $server.ConnectionContext.BatchSeparator = $BatchSeparator }
if (Test-Bound -ParameterName 'ConnectTimeout') { $server.ConnectionContext.ConnectTimeout = $ConnectTimeout }
if (Test-Bound -ParameterName 'Database') { $server.ConnectionContext.DatabaseName = $Database }
if (Test-Bound -ParameterName 'EncryptConnection') { $server.ConnectionContext.EncryptConnection = $true }
if (Test-Bound -ParameterName 'IsActiveDirectoryUniversalAuth') { $server.ConnectionContext.IsActiveDirectoryUniversalAuth = $true }
if (Test-Bound -ParameterName 'LockTimeout') { $server.ConnectionContext.LockTimeout = $LockTimeout }
if (Test-Bound -ParameterName 'MaxPoolSize') { $server.ConnectionContext.MaxPoolSize = $MaxPoolSize }
if (Test-Bound -ParameterName 'MinPoolSize') { $server.ConnectionContext.MinPoolSize = $MinPoolSize }
if (Test-Bound -ParameterName 'MultipleActiveResultSets') { $server.ConnectionContext.MultipleActiveResultSets = $true }
if (Test-Bound -ParameterName 'NetworkProtocol') { $server.ConnectionContext.NetworkProtocol = $NetworkProtocol }
if (Test-Bound -ParameterName 'NonPooledConnection') { $server.ConnectionContext.NonPooledConnection = $true }
if (Test-Bound -ParameterName 'PacketSize') { $server.ConnectionContext.PacketSize = $PacketSize }
if (Test-Bound -ParameterName 'PooledConnectionLifetime') { $server.ConnectionContext.PooledConnectionLifetime = $PooledConnectionLifetime }
if (Test-Bound -ParameterName 'StatementTimeout') { $server.ConnectionContext.StatementTimeout = $StatementTimeout }
if (Test-Bound -ParameterName 'SqlExecutionModes') { $server.ConnectionContext.SqlExecutionModes = $SqlExecutionModes }
if (Test-Bound -ParameterName 'TrustServerCertificate') { $server.ConnectionContext.TrustServerCertificate = $true }
if (Test-Bound -ParameterName 'WorkstationId') { $server.ConnectionContext.WorkstationId = $WorkstationId }
$connstring = $server.ConnectionContext.ConnectionString
if (Test-Bound -ParameterName 'MultiSubnetFailover') { $connstring = "$connstring;MultiSubnetFailover=True" }
if (Test-Bound -ParameterName 'FailoverPartner') { $connstring = "$connstring;Failover Partner=$FailoverPartner" }
if (Test-Bound -ParameterName 'ApplicationIntent') { $connstring = "$connstring;ApplicationIntent=$ApplicationIntent" }
if ($connstring -ne $server.ConnectionContext.ConnectionString) {
$server.ConnectionContext.ConnectionString = $connstring
}
try {
if ($null -ne $Credential.username) {
$username = ($Credential.username).TrimStart("\")
if ($username -like "*\*") {
$username = $username.Split("\")[1]
$authtype = "Windows Authentication with Credential"
$server.ConnectionContext.LoginSecure = $true
$server.ConnectionContext.ConnectAsUser = $true
$server.ConnectionContext.ConnectAsUserName = $username
$server.ConnectionContext.ConnectAsUserPassword = ($Credential).GetNetworkCredential().Password
}
else {
$authtype = "SQL Authentication"
$server.ConnectionContext.LoginSecure = $false
$server.ConnectionContext.set_Login($username)
$server.ConnectionContext.set_SecurePassword($Credential.Password)
}
}
if ($NonPooled) {
$server.ConnectionContext.Connect()
}
elseif ($authtype -eq "Windows Authentication with Credential") {
# Make it connect in a natural way, hard to explain.
$null = $server.IsMemberOfWsfcCluster
}
else {
$server.ConnectionContext.SqlConnectionObject.Open()
}
}
catch {
$message = $_.Exception.InnerException.InnerException
$message = $message.ToString()
$message = ($message -Split '-->')[0]
$message = ($message -Split 'at System.Data.SqlClient')[0]
$message = ($message -Split 'at System.Data.ProviderBase')[0]
throw "Can't connect to $instance`: $message "
}
}
if ($loadedSmoVersion -ge 11) {
if ($server.VersionMajor -eq 8) {
# 2000
$initFieldsDb = New-Object System.Collections.Specialized.StringCollection
[void]$initFieldsDb.AddRange($Fields2000_Db)
$initFieldsLogin = New-Object System.Collections.Specialized.StringCollection
[void]$initFieldsLogin.AddRange($Fields2000_Login)
$server.SetDefaultInitFields([Microsoft.SqlServer.Management.Smo.Database], $initFieldsDb)
$server.SetDefaultInitFields([Microsoft.SqlServer.Management.Smo.Login], $initFieldsLogin)
}
elseif ($server.VersionMajor -eq 9 -or $server.VersionMajor -eq 10) {
# 2005 and 2008
$initFieldsDb = New-Object System.Collections.Specialized.StringCollection
[void]$initFieldsDb.AddRange($Fields200x_Db)
$initFieldsLogin = New-Object System.Collections.Specialized.StringCollection
[void]$initFieldsLogin.AddRange($Fields200x_Login)
$server.SetDefaultInitFields([Microsoft.SqlServer.Management.Smo.Database], $initFieldsDb)
$server.SetDefaultInitFields([Microsoft.SqlServer.Management.Smo.Login], $initFieldsLogin)
}
else {
# 2012 and above
$initFieldsDb = New-Object System.Collections.Specialized.StringCollection
[void]$initFieldsDb.AddRange($Fields201x_Db)
$initFieldsLogin = New-Object System.Collections.Specialized.StringCollection
[void]$initFieldsLogin.AddRange($Fields201x_Login)
$server.SetDefaultInitFields([Microsoft.SqlServer.Management.Smo.Database], $initFieldsDb)
$server.SetDefaultInitFields([Microsoft.SqlServer.Management.Smo.Login], $initFieldsLogin)
}
}
if ($SqlConnectionOnly) {
return $server.ConnectionContext.SqlConnectionObject
}
else {
if (-not $server.ComputerName) {
$parsedcomputername = $server.NetName
if (-not $parsedcomputername) {
$parsedcomputername = ([dbainstance]$instance).ComputerName
}
Add-Member -InputObject $server -NotePropertyName ComputerName -NotePropertyValue $parsedcomputername -Force
}
return $server
}
}
}
}
#ValidationTags#Messaging,FlowControl,Pipeline,CodeStyle#
function ConvertTo-DbaDataTable {
<#
.SYNOPSIS
Creates a DataTable for an object.
.DESCRIPTION
Creates a DataTable based on an object's properties. This allows you to easily write to SQL Server tables.
Thanks to Chad Miller, this is based on his script. https://gallery.technet.microsoft.com/scriptcenter/4208a159-a52e-4b99-83d4-8048468d29dd
If the attempt to convert to datatable fails, try the -Raw parameter for less accurate datatype detection.
.PARAMETER InputObject
The object to transform into a DataTable.
.PARAMETER TimeSpanType
Specifies the type to convert TimeSpan objects into. Default is 'TotalMilliseconds'. Valid options are: 'Ticks', 'TotalDays', 'TotalHours', 'TotalMinutes', 'TotalSeconds', 'TotalMilliseconds', and 'String'.
.PARAMETER SizeType
Specifies the type to convert DbaSize objects to. Default is 'Int64'. Valid options are 'Int32', 'Int64', and 'String'.
.PARAMETER IgnoreNull
If this switch is enabled, objects with null values will be ignored (empty rows will be added by default).
.PARAMETER Raw
If this switch is enabled, the DataTable will be created with strings. No attempt will be made to parse/determine data types.
.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.
.NOTES
Tags: DataTable, Table, Data
Website: https://dbatools.io/
Copyright: (C) 2016 Chrissy LeMaire
License: MIT https://opensource.org/licenses/MIT
.LINK
https://dbatools.io/ConvertTo-DbaDataTable
.OUTPUTS
System.Object[]
.EXAMPLE
Get-Service | ConvertTo-DbaDataTable
Creates a DataTable from the output of Get-Service.
.EXAMPLE
ConvertTo-DbaDataTable -InputObject $csv.cheesetypes
Creates a DataTable from the CSV object $csv.cheesetypes.
.EXAMPLE
$dblist | ConvertTo-DbaDataTable
Creates a DataTable from the $dblist object passed in via pipeline.
.EXAMPLE
Get-Process | ConvertTo-DbaDataTable -TimeSpanType TotalSeconds
Creates a DataTable with the running processes and converts any TimeSpan property to TotalSeconds.
#>
[CmdletBinding()]
[OutputType([System.Object[]])]
param (
[Parameter(Position = 0,
Mandatory,
ValueFromPipeline)]
[AllowNull()]
[PSObject[]]$InputObject,
[Parameter(Position = 1)]
[ValidateSet("Ticks",
"TotalDays",
"TotalHours",
"TotalMinutes",
"TotalSeconds",
"TotalMilliseconds",
"String")]
[ValidateNotNullOrEmpty()]
[string]$TimeSpanType = "TotalMilliseconds",
[ValidateSet("Int64", "Int32", "String")]
[string]$SizeType = "Int64",
[switch]$IgnoreNull,
[switch]$Raw,
[Alias('Silent')]
[switch]$EnableException
)
begin {
Write-Message -Level Debug -Message "Bound parameters: $($PSBoundParameters.Keys -join ", ")"
Write-Message -Level Debug -Message "TimeSpanType = $TimeSpanType | SizeType = $SizeType"
Test-DbaDeprecation -DeprecatedOn 1.0.0 -Alias Out-DbaDataTable
function Convert-Type {
# This function will check so that the type is an accepted type which could be used when inserting into a table.
# If a type is accepted (included in the $type array) then it will be passed on, otherwise it will first change type before passing it on.
# Special types will have both their types converted as well as the value.
# TimeSpan is a special type and will be converted into the $timespantype. (default: TotalMilliseconds) so that the timespan can be stored in a database further down the line.
[CmdletBinding()]
param (
$type,
$value,
$timespantype = 'TotalMilliseconds',
$sizetype = 'Int64'
)
$types = [System.Collections.ArrayList]@(
'System.Int32',
'System.UInt32',
'System.Int16',
'System.UInt16',
'System.Int64',
'System.UInt64',
'System.Decimal',
'System.Single',
'System.Double',
'System.Byte',
'System.SByte',
'System.Boolean',
'System.DateTime',
'System.Guid',
'System.Char'
)
# The $special variable is used to mark the return value if a conversion was made on the value itself.
# If this is set to true the original value will later be ignored when updating the DataTable.
# And the value returned from this function will be used instead. (cannot modify existing properties)
$special = $false
$specialType = ""
# Special types need to be converted in some way.
# This attempt is to convert timespan into something that works in a table.
# I couldn't decide on what to convert it to so the user can decide.
# If the parameter is not used, TotalMilliseconds will be used as default.
# Ticks are more accurate but I think milliseconds are more useful most of the time.
if (($type -eq 'System.TimeSpan') -or ($type -eq 'Sqlcollaborative.Dbatools.Utility.DbaTimeSpan') -or ($type -eq 'Sqlcollaborative.Dbatools.Utility.DbaTimeSpanPretty')) {
$special = $true
if ($timespantype -eq 'String') {
$value = $value.ToString()
$type = 'System.String'
}
else {
# Let's use Int64 for all other types than string.
# We could match the type more closely with the timespantype but that can be added in the future if needed.
$value = $value.$timespantype
$type = 'System.Int64'
}
$specialType = 'Timespan'
}
elseif ($type -eq 'Sqlcollaborative.Dbatools.Utility.Size') {
$special = $true
switch ($sizetype) {
'Int64' {
$value = $value.Byte
$type = 'System.Int64'
}
'Int32' {
$value = $value.Byte
$type = 'System.Int32'
}
'String' {
$value = $value.ToString()
$type = 'System.String'
}
}
$specialType = 'Size'
}
elseif (-not ($type -in $types)) {
# All types which are not found in the array will be converted into strings.
# In this way we dont ignore it completely and it will be clear in the end why it looks as it does.
$type = 'System.String'
}
# return a hashtable instead of an object. I like hashtables :)
return @{ type = $type; Value = $value; Special = $special; SpecialType = $specialType }
}
function Convert-SpecialType {
<#
.SYNOPSIS
Converts a value for a known column.
.DESCRIPTION
Converts a value for a known column.
.PARAMETER Value
The value to convert
.PARAMETER Type
The special type for which to convert
.PARAMETER SizeType
The size type defined by the user
.PARAMETER TimeSpanType
The timespan type defined by the user
#>
[CmdletBinding()]
Param (
$Value,
[ValidateSet('Timespan', 'Size')]
[string]$Type,
[string]$SizeType,
[string]$TimeSpanType
)
switch ($Type) {
'Size' {
if ($SizeType -eq 'String') { return $Value.ToString() }
else { return $Value.Byte }
}
'Timespan' {
if ($TimeSpanType -eq 'String') {
$Value.ToString()
}
else {
$Value.$TimeSpanType
}
}
}
}
function Add-Column {
<#
.SYNOPSIS
Adds a column to the datatable in progress.
.DESCRIPTION
Adds a column to the datatable in progress.
.PARAMETER Property
The property for which to add a column.
.PARAMETER DataTable
Autofilled. The table for which to add a column.
.PARAMETER TimeSpanType
Autofilled. How should timespans be handled?
.PARAMETER SizeType
Autofilled. How should sizes be handled?
.PARAMETER Raw
Autofilled. Whether the column should be string, no matter the input.
#>
[CmdletBinding()]
Param (
[System.Management.Automation.PSPropertyInfo]$Property,
[System.Data.DataTable]$DataTable = $datatable,
[string]$TimeSpanType = $TimeSpanType,
[string]$SizeType = $SizeType,
[bool]$Raw = $Raw
)
$type = $property.TypeNameOfValue
try {
if ($Property.MemberType -like 'ScriptProperty') {
$type = $Property.GetType().FullName
}
}
catch { $type = 'System.String' }
$converted = Convert-Type -type $type -value $property.Value -timespantype $TimeSpanType -sizetype $SizeType
$column = New-Object System.Data.DataColumn
$column.ColumnName = $property.Name.ToString()
if (-not $Raw) {
$column.DataType = [System.Type]::GetType($converted.type)
}
$null = $DataTable.Columns.Add($column)
$converted
}
$datatable = New-Object System.Data.DataTable
# Accelerate subsequent lookups of columns and special type columns
$columns = @()
$specialColumns = @()
$specialColumnsType = @{ }
$ShouldCreateColumns = $true
}
process {
#region Handle null objects
if ($null -eq $InputObject) {
if (-not $IgnoreNull) {
$datarow = $datatable.NewRow()
$datatable.Rows.Add($datarow)
}
# Only ends the current process block
return
}
#endregion Handle null objects
foreach ($object in $InputObject) {
#region Handle null objects
if ($null -eq $object) {
if (-not $IgnoreNull) {
$datarow = $datatable.NewRow()
$datatable.Rows.Add($datarow)
}
continue
}
#endregion Handle null objects
#Handle rows already being System.Data.DataRow
if ($object.GetType().FullName -eq 'System.Data.DataRow') {
if ($ShouldCreateColumns) {
$datatable = $object.Table.Copy()
$ShouldCreateColumns = $false
}
continue
}
# The new row to insert
$datarow = $datatable.NewRow()
#region Process Properties
$objectProperties = $object.PSObject.Properties
foreach ($property in $objectProperties) {
#region Create Columns as needed
if ($ShouldCreateColumns) {
$newColumn = Add-Column -Property $property
$columns += $property.Name
if ($newColumn.Special) {
$specialColumns += $property.Name
$specialColumnsType[$property.Name] = $newColumn.SpecialType
}
}
#endregion Create Columns as needed
# Handle null properties, as well as properties with access errors
try {
$propValueLength = $property.value.length
}
catch {
$propValueLength = 0
}
#region Insert value into column of row
if ($propValueLength -gt 0) {
# If the typename was a special typename we want to use the value returned from Convert-Type instead.
# We might get error if we try to change the value for $property.value if it is read-only. That's why we use $converted.value instead.
if ($property.Name -in $specialColumns) {
$datarow.Item($property.Name) = Convert-SpecialType -Value $property.value -Type $specialColumnsType[$property.Name] -SizeType $SizeType -TimeSpanType $TimeSpanType
}
else {
if ($property.value.ToString().length -eq 15) {
if ($property.value.ToString() -eq 'System.Object[]') {
$value = $property.value -join ", "
}
elseif ($property.value.ToString() -eq 'System.String[]') {
$value = $property.value -join ", "
}
else {
$value = $property.value
}
}
else {
$value = $property.value
}
try {
$datarow.Item($property.Name) = $value
}
catch {
if ($property.Name -notin $columns) {
try {
$newColumn = Add-Column -Property $property
$columns += $property.Name
if ($newColumn.Special) {
$specialColumns += $property.Name
$specialColumnsType[$property.Name] = $newColumn.SpecialType
}
$datarow.Item($property.Name) = $newColumn.Value
}
catch {
Write-Message -Level Warning -Message "Failed to add property $($property.Name) from $object" -ErrorRecord $_ -Target $object
}
}
else {
Write-Message -Level Warning -Message "Failed to add property $($property.Name) from $object" -ErrorRecord $_ -Target $object
}
}
}
}
#endregion Insert value into column of row
}
$datatable.Rows.Add($datarow)
# If this is the first non-null object then the columns has just been created.
# Set variable to false to skip creating columns from now on.
if ($ShouldCreateColumns) {
$ShouldCreateColumns = $false
}
#endregion Process Properties
}
}
end {
Write-Message -Level InternalComment -Message "Finished."
, $datatable
}
}
function ConvertTo-DbaTimeline {
<#
.SYNOPSIS
Converts InputObject to a html timeline using Google Chart
.DESCRIPTION
This function accepts input as pipeline from the following psdbatools functions:
Get-DbaAgentJobHistory
Get-DbaBackupHistory
(more to come...)
And generates Bootstrap based, HTML file with Google Chart Timeline
.PARAMETER InputObject
Pipe input, must an output from the above functions.
.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.
.NOTES
Tags: Chart
Author: Marcin Gminski (@marcingminski)
Dependency: ConvertTo-JsDate, Convert-DbaTimelineStatusColor
Website: https://dbatools.io
Copyright: (C) Chrissy LeMaire, [email protected]
- License: MIT https://opensource.org/licenses/MIT
.LINK
https://dbatools.io/ConvertTo-DbaTimeline
.EXAMPLE
Get-DbaAgentJobHistory -SqlInstance sql-1 -StartDate '2018-08-13 00:00' -EndDate '2018-08-13 23:59' -NoJobSteps | ConvertTo-DbaTimeline | Out-File C:\temp\DbaAgentJobHistory.html -Encoding ASCII
Creates an output file containing a pretty timeline for all of the agent job history results for sql-1 the whole day of 2018-08-13
.EXAMPLE
Get-DbaCmsRegServer -SqlInstance sqlcm | Get-DbaBackupHistory -Since '2018-08-13 00:00' | ConvertTo-DbaTimeline | Out-File C:\temp\DbaBackupHistory.html -Encoding ASCII
Creates an output file containing a pretty timeline for the agent job history since 2018-08-13 for all of the registered servers on sqlcm
.EXAMPLE
$messageParameters = @{
Subject = "Backup history for sql2017 and sql2016"
Body = Get-DbaBackupHistory -SqlInstance sql2017, sql2016 -Since '2018-08-13 00:00' | ConvertTo-DbaTimeline
From = "[email protected]"
To = "[email protected]"
SmtpServer = "smtp.ad.local"
}
Send-MailMessage @messageParameters -BodyAsHtml
Sends an email to [email protected] with the results of Get-DbaBackupHistory. Note that viewing these reports may not be supported in all email clients.
#>
[CmdletBinding()]
Param (
[parameter(Mandatory, ValueFromPipeline)]
[object[]]$InputObject,
[switch]$EnableException
)
begin {
$body = $servers = @()
$begin = @"
<html>
<head>
<!-- Developed by Marcin Gminski, https://marcin.gminski.net, 2018 -->
<!-- Load jQuery required to autosize timeline -->
<script src="https://code.jquery.com/jquery-3.3.1.min.js" integrity="sha256-FgpCb/KJQlLNfOu91ta32o/NMZxltwRo8QtmkMRdAu8=" crossorigin="anonymous"></script>
<!-- Load Bootstrap -->
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js" integrity="sha384-Tc5IQib027qvyjSMfHjOMaLkfuWVxZxUPnCJA7l2mCWNIpG9mGCD8wGNIcPD7Txa" crossorigin="anonymous"></script>
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous">
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap-theme.min.css" integrity="sha384-rHyoN1iRsVXV4nD0JutlnGaslCJuC7uwjduW9SVrLvRYooPp2bWYgmgJQIXwl/Sp" crossorigin="anonymous">
<!-- Load Google Charts library -->
<script type="text/javascript" src="https://www.gstatic.com/charts/loader.js"></script>
<!-- a bit of custom styling to work with bootstrap grid -->
<style>
html,body{height:100%;background-color:#c2c2c2;}
.viewport {height:100%}
.chart{
background-color:#fff;
text-align:left;
padding:0;
border:1px solid #7D7D7D;
-webkit-box-shadow:1px 1px 3px 0 rgba(0,0,0,.45);
-moz-box-shadow:1px 1px 3px 0 rgba(0,0,0,.45);
box-shadow:1px 1px 3px 0 rgba(0,0,0,.45)
}
.badge-custom{background-color:#939}
.container {
height:100%;
}
.fill{
width:100%;
height:100%;
min-height:100%;
padding:10px;
}
.timeline-tooltip{
border:1px solid #E0E0E0;
font-family:Arial,Helvetica;
font-size:10pt;
padding:12px
}
.timeline-tooltip div{padding:6px}
.timeline-tooltip span{font-weight:700}
</style>
<script type="text/javascript">
google.charts.load('43', {'packages':['timeline']});
google.charts.setOnLoadCallback(drawChart);
function drawChart() {
var container = document.getElementById('Chart');
var chart = new google.visualization.Timeline(container);
var dataTable = new google.visualization.DataTable();
dataTable.addColumn({type: 'string', id: 'vLabel'});
dataTable.addColumn({type: 'string', id: 'hLabel'});
dataTable.addColumn({type: 'string', role: 'style' });
dataTable.addColumn({type: 'date', id: 'date_start'});
dataTable.addColumn({type: 'date', id: 'date_end'});
dataTable.addRows([
"@
}
process {
# build html container
$BaseObject = $InputObject.PsObject.BaseObject
# create server list to support multiple servers
if ($InputObject[0].SqlInstance -notin $servers) {
$servers += $InputObject[0].SqlInstance
}
# This is where do column mapping.
# Check for types - this will help support if someone assigns a variable then pipes
# AgentJobHistory is a forced type while backuphistory is a legit type
if ($InputObject[0].TypeName -eq 'AgentJobHistory') {
$CallerName = "Get-DbaAgentJobHistory"
$data = $InputObject | Select-Object @{ Name = "SqlInstance"; Expression = { $_.SqlInstance } }, @{ Name = "InstanceName"; Expression = { $_.InstanceName } }, @{ Name = "vLabel"; Expression = { $_.Job -replace "\'",''} }, @{ Name = "hLabel"; Expression = { $_.Status } }, @{ Name = "Style"; Expression = { $(Convert-DbaTimelineStatusColor($_.Status)) } }, @{ Name = "StartDate"; Expression = { $(ConvertTo-JsDate($_.StartDate)) } }, @{ Name = "EndDate"; Expression = { $(ConvertTo-JsDate($_.EndDate)) } }
}
elseif ($InputObject[0] -is [Sqlcollaborative.Dbatools.Database.BackupHistory]) {
$CallerName = "Get-DbaBackupHistory"
$data = $InputObject | Select-Object @{ Name = "SqlInstance"; Expression = { $_.SqlInstance } }, @{ Name = "InstanceName"; Expression = { $_.InstanceName } }, @{ Name = "vLabel"; Expression = { $_.Database } }, @{ Name = "hLabel"; Expression = { $_.Type } }, @{ Name = "StartDate"; Expression = { $(ConvertTo-JsDate($_.Start)) } }, @{ Name = "EndDate"; Expression = { $(ConvertTo-JsDate($_.End)) } }
}
else {
# sorry to be so formal, can't help it ;)
Stop-Function -Message "Unsupported input data. To request support for additional commands, please file an issue at dbatools.io/issues and we'll take a look"
return
}
$body += "$($data | ForEach-Object{ "['$($_.vLabel)','$($_.hLabel)','$($_.Style)',$($_.StartDate), $($_.EndDate)]," })"
}
end {
if (Test-FunctionInterrupt) { return }
$end = @"
]);
var paddingHeight = 20;
var rowHeight = dataTable.getNumberOfRows() * 41;
var chartHeight = rowHeight + paddingHeight;
dataTable.insertColumn(2, {type: 'string', role: 'tooltip', p: {html: true}});
var dateFormat = new google.visualization.DateFormat({
pattern: 'dd/MM/yy HH:mm:ss'
});
for (var i = 0; i < dataTable.getNumberOfRows(); i++) {
var duration = (dataTable.getValue(i, 5).getTime() - dataTable.getValue(i, 4).getTime()) / 1000;
var hours = parseInt( duration / 3600 ) % 24;
var minutes = parseInt( duration / 60 ) % 60;
var seconds = duration % 60;
var tooltip = '<div class="timeline-tooltip"><span>' +
dataTable.getValue(i, 1).split(",").join("<br />") + '</span></div><div class="timeline-tooltip"><span>' +
dataTable.getValue(i, 0) + '</span>: ' +
dateFormat.formatValue(dataTable.getValue(i, 4)) + ' - ' +
dateFormat.formatValue(dataTable.getValue(i, 5)) + '</div>' +
'<div class="timeline-tooltip"><span>Duration: </span>' +
hours + 'h ' + minutes + 'm ' + seconds + 's ';
dataTable.setValue(i, 2, tooltip);
}
var options = {
timeline: {
rowLabelStyle: { },
barLabelStyle: { },
},
hAxis: {
format: 'dd/MM HH:mm',
},
}
// Autosize chart. It would not be enough to just count rows and expand based on row height as there can be overlappig rows.
// this will draw the chart, get the size of the underlying div and apply that size to the parent container and redraw:
chart.draw(dataTable, options);
// get the size of the chold div:
var realheight= parseInt(`$("#Chart div:first-child div:first-child div:first-child div svg").attr( "height"))+70;
// set the height:
options.height=realheight
// draw again:
chart.draw(dataTable, options);
}
</script>
</head>
<body>
<div class="container-fluid">
<div class="pull-left"><h3><code>$($CallerName)</code> timeline for server <code>$($servers -join ', ')</code></h3></div><div class="pull-right text-right"><img class="text-right" style="vertical-align:bottom; margin-top: 10px;" src="https://dbatools.io/wp-content/uploads/2016/05/dbatools-logo-1.png" width=150></div>
<div class="clearfix"></div>
<div class="col-12">
<div class="chart" id="Chart"></div>
</div>
<hr>
<p><a href="https://dbatools.io">dbatools.io</a> - the community's sql powershell module. Find us on Twitter: <a href="https://twitter.com/psdbatools">@psdbatools</a> | Chart by <a href="https://twitter.com/marcingminski">@marcingminski</a></p>
</div>
</body>
</html>
"@
$begin, $body, $end
}
}
#ValidationTags#Messaging,FlowControl,Pipeline,CodeStyle#
function ConvertTo-DbaXESession {
<#
.SYNOPSIS
Uses a slightly modified version of sp_SQLskills_ConvertTraceToExtendedEvents.sql to convert Traces to Extended Events.
.DESCRIPTION
Uses a slightly modified version of sp_SQLskills_ConvertTraceToExtendedEvents.sql to convert Traces to Extended Events.
T-SQL code by: Jonathan M. Kehayias, SQLskills.com. T-SQL can be found in this module directory and at
https://www.sqlskills.com/blogs/jonathan/converting-sql-trace-to-extended-events-in-sql-server-2012/
.PARAMETER InputObject
Specifies a Trace object output by Get-DbaTrace.
.PARAMETER Name
The name of the Trace to convert. If the name exists, characters will be appended to it.
.PARAMETER OutputScriptOnly
Outputs the T-SQL script to create the XE session and does not execute it.
.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.
.NOTES
Tags: Trace, ExtendedEvent
Author: Chrissy LeMaire (@cl), netnerds.net
Website: https://dbatools.io
Copyright: (C) Chrissy LeMaire, [email protected]
License: MIT https://opensource.org/licenses/MIT
.EXAMPLE
Get-DbaTrace -SqlInstance sql2017, sql2012 | Where Id -eq 2 | ConvertTo-DbaXESession -Name 'Test'
Converts Trace with ID 2 to a Session named Test on SQL Server instances named sql2017 and sql2012
and creates the Session on each respective server.
.EXAMPLE
Get-DbaTrace -SqlInstance sql2014 | Out-GridView -PassThru | ConvertTo-DbaXESession -Name 'Test' | Start-DbaXESession
Converts selected traces on sql2014 to sessions, creates the session, and starts it.
.EXAMPLE
Get-DbaTrace -SqlInstance sql2014 | Where Id -eq 1 | ConvertTo-DbaXESession -Name 'Test' -OutputScriptOnly
Converts trace ID 1 on sql2014 to an Extended Event and outputs the resulting T-SQL.
#>
[CmdletBinding()]
Param (
[parameter(Mandatory, ValueFromPipeline)]
[object[]]$InputObject,
[parameter(Mandatory)]
[string]$Name,
[switch]$OutputScriptOnly,
[switch]$EnableException
)
begin {
$rawsql = Get-Content "$script:PSModuleRoot\bin\sp_SQLskills_ConvertTraceToEEs.sql" -Raw
}
process {
foreach ($trace in $InputObject) {
if (-not $trace.id -and -not $trace.Parent) {
Stop-Function -Message "Input is of the wrong type. Use Get-DbaTrace." -Continue
return
}
$server = $trace.Parent
if ($server.VersionMajor -lt 11) {
Stop-Function -Message "SQL Server version 2012+ required - $server not supported."
return
}
$tempdb = $server.Databases['tempdb']
$traceid = $trace.id
if ((Get-DbaXESession -SqlInstance $server -Session $PSBoundParameters.Name)) {
$oldname = $name
$Name = "$name-$traceid"
Write-Message -Level Output -Message "XE Session $oldname already exists on $server, trying $name."
}
if ((Get-DbaXESession -SqlInstance $server -Session $Name)) {
$oldname = $name
$Name = "$name-$(Get-Random)"
Write-Message -Level Output -Message "XE Session $oldname already exists on $server, trying $name."
}
$sql = $rawsql.Replace("--TRACEID--", $traceid)
$sql = $sql.Replace("--SESSIONNAME--", $name)
try {
Write-Message -Level Verbose -Message "Executing SQL in tempdb."
$results = $tempdb.ExecuteWithResults($sql).Tables.Rows.SqlString
}
catch {
Stop-Function -Message "Issue creating, dropping or executing sp_SQLskills_ConvertTraceToExtendedEvents in tempdb on $server." -Target $server -ErrorRecord $_
}
$results = $results -join "`r`n"
if ($OutputScriptOnly) {
$results
}
else {
Write-Message -Level Verbose -Message "Creating XE Session $name."
try {
$tempdb.ExecuteNonQuery($results)
}
catch {
Stop-Function -Message "Issue creating extended event $name on $server." -Target $server -ErrorRecord $_
}
Get-DbaXESession -SqlInstance $server -Session $name
}
}
}
}
function Copy-DbaAgentAlert {
<#
.SYNOPSIS
Copy-DbaAgentAlert migrates alerts from one SQL Server to another.
.DESCRIPTION
By default, all alerts are copied. The -Alert parameter is auto-populated for command-line completion and can be used to copy only specific alerts.
If the alert already exists on the destination, it will be skipped unless -Force is used.
.PARAMETER Source
Source SQL Server. You must have sysadmin access and server version must be SQL Server version 2000 or higher.
.PARAMETER SourceSqlCredential
Login to the target instance using alternative credentials. Windows and SQL Authentication supported. Accepts credential objects (Get-Credential)
.PARAMETER Destination
Destination SQL Server. You must have sysadmin access and the server must be SQL Server 2000 or higher.
.PARAMETER DestinationSqlCredential
Login to the target instance using alternative credentials. Windows and SQL Authentication supported. Accepts credential objects (Get-Credential)
.PARAMETER Alert
The alert(s) to process. This list is auto-populated from the server. If unspecified, all alerts will be processed.
.PARAMETER ExcludeAlert
The alert(s) to exclude. This list is auto-populated from the server.
.PARAMETER IncludeDefaults
Copy SQL Agent defaults such as FailSafeEmailAddress, ForwardingServer, and PagerSubjectTemplate.
.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.
.PARAMETER Force
If this switch is enabled, the Alert will be dropped and recreated on Destination.
.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.
.NOTES
Tags: Migration, Agent
Author: Chrissy LeMaire (@cl), netnerds.net
Requires: sysadmin access on SQL Servers
Website: https://dbatools.io
Copyright: (C) Chrissy LeMaire, [email protected]
License: MIT https://opensource.org/licenses/MIT
.LINK
https://dbatools.io/Copy-DbaAgentAlert
.EXAMPLE
Copy-DbaAgentAlert -Source sqlserver2014a -Destination sqlcluster
Copies all alerts from sqlserver2014a to sqlcluster using Windows credentials. If alerts with the same name exist on sqlcluster, they will be skipped.
.EXAMPLE
Copy-DbaAgentAlert -Source sqlserver2014a -Destination sqlcluster -Alert PSAlert -SourceSqlCredential $cred -Force
Copies a only the alert named PSAlert from sqlserver2014a to sqlcluster using SQL credentials for sqlserver2014a and Windows credentials for sqlcluster. If a alert with the same name exists on sqlcluster, it will be dropped and recreated because -Force was used.
.EXAMPLE
Copy-DbaAgentAlert -Source sqlserver2014a -Destination sqlcluster -WhatIf -Force
Shows what would happen if the command were executed using force.
#>
[cmdletbinding(DefaultParameterSetName = "Default", SupportsShouldProcess = $true)]
param (
[parameter(Mandatory)]
[DbaInstanceParameter]$Source,
[PSCredential]$SourceSqlCredential,
[parameter(Mandatory)]
[DbaInstanceParameter[]]$Destination,
[PSCredential]$DestinationSqlCredential,
[object[]]$Alert,
[object[]]$ExcludeAlert,
[switch]$IncludeDefaults,
[switch]$Force,
[Alias('Silent')]
[switch]$EnableException
)
begin {
try {
Write-Message -Level Verbose -Message "Connecting to $Source"
$sourceServer = Connect-SqlInstance -SqlInstance $Source -SqlCredential $SourceSqlCredential
$serverAlerts = $sourceServer.JobServer.Alerts
}
catch {
Stop-Function -Message "Failure" -Category ConnectionError -ErrorRecord $_ -Target $Source
return
}
}
process {
if (Test-FunctionInterrupt) { return }
foreach ($destinstance in $Destination) {
try {
Write-Message -Level Verbose -Message "Connecting to $destinstance"
$destServer = Connect-SqlInstance -SqlInstance $destinstance -SqlCredential $DestinationSqlCredential
}
catch {
Stop-Function -Message "Failure" -Category ConnectionError -ErrorRecord $_ -Target $destinstance -Continue
}
$destAlerts = $destServer.JobServer.Alerts
if ($IncludeDefaults -eq $true) {
if ($PSCmdlet.ShouldProcess($destinstance, "Creating Alert Defaults")) {
$copyAgentAlertStatus = [pscustomobject]@{
SourceServer = $sourceServer.Name
DestinationServer = $destServer.Name
Name = "Alert Defaults"
Type = "Alert Defaults"
Status = $null
Notes = $null
DateTime = [Sqlcollaborative.Dbatools.Utility.DbaDateTime](Get-Date)
}
try {
Write-Message -Message "Creating Alert Defaults" -Level Verbose
$sql = $sourceServer.JobServer.AlertSystem.Script() | Out-String
$sql = $sql -replace [Regex]::Escape("'$source'"), "'$destinstance'"
Write-Message -Message $sql -Level Debug
$null = $destServer.Query($sql)
$copyAgentAlertStatus.Status = "Successful"
}
catch {
$copyAgentAlertStatus.Status = "Failed"
$copyAgentAlertStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
Stop-Function -Message "Issue creating alert defaults." -Category InvalidOperation -ErrorRecord $_ -Target $destServer -Continue
}
$copyAgentAlertStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
}
}
foreach ($serverAlert in $serverAlerts) {
$alertName = $serverAlert.name
$copyAgentAlertStatus = [pscustomobject]@{
SourceServer = $sourceServer.Name
DestinationServer = $destServer.Name
Name = $alertName
Type = "Agent Alert"
Notes = $null
Status = $null
DateTime = [Sqlcollaborative.Dbatools.Utility.DbaDateTime](Get-Date)
}
if (($Alert -and $Alert -notcontains $alertName) -or ($ExcludeAlert -and $ExcludeAlert -contains $alertName)) {
continue
}
if ($destAlerts.name -contains $serverAlert.name) {
if ($force -eq $false) {
if ($PSCmdlet.ShouldProcess($destinstance, "Alert [$alertName] exists at destination. Use -Force to drop and migrate.")) {
$copyAgentAlertStatus.Status = "Skipped"
$copyAgentAlertStatus.Notes = "Already exists"
$copyAgentAlertStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
Write-Message -Message "Alert [$alertName] exists at destination. Use -Force to drop and migrate." -Level Verbose
}
continue
}
if ($PSCmdlet.ShouldProcess($destinstance, "Dropping alert $alertName and recreating")) {
try {
Write-Message -Message "Dropping Alert $alertName on $destServer." -Level Verbose
$sql = "EXEC msdb.dbo.sp_delete_alert @name = N'$($alertname)';"
Write-Message -Message $sql -Level Debug
$null = $destServer.Query($sql)
$destAlerts.Refresh()
}
catch {
$copyAgentAlertStatus.Status = "Failed"
$copyAgentAlertStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
Stop-Function -Message "Issue dropping/recreating alert" -Category InvalidOperation -ErrorRecord $_ -Target $destServer -Continue
}
}
}
if ($destAlerts | Where-Object { $_.Severity -eq $serverAlert.Severity -and $_.MessageID -eq $serverAlert.MessageID -and $_.DatabaseName -eq $serverAlert.DatabaseName -and $_.EventDescriptionKeyword -eq $serverAlert.EventDescriptionKeyword }) {
if ($PSCmdlet.ShouldProcess($destinstance, "Checking for conflicts")) {
$conflictMessage = "Alert [$alertName] has already been defined to use"
if ($serverAlert.Severity -gt 0) { $conflictMessage += " severity $($serverAlert.Severity)" }
if ($serverAlert.MessageID -gt 0) { $conflictMessage += " error number $($serverAlert.MessageID)" }
if ($serverAlert.DatabaseName) { $conflictMessage += " on database '$($serverAlert.DatabaseName)'" }
if ($serverAlert.EventDescriptionKeyword) { $conflictMessage += " with error text '$($serverAlert.Severity)'" }
$conflictMessage += ". Skipping."
Write-Message -Level Verbose -Message $conflictMessage
$copyAgentAlertStatus.Status = "Skipped"
$copyAgentAlertStatus.Notes = $conflictMessage
$copyAgentAlertStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
}
continue
}
if ($serverAlert.JobName -and $destServer.JobServer.Jobs.Name -NotContains $serverAlert.JobName) {
Write-Message -Level Verbose -Message "Alert [$alertName] has job [$($serverAlert.JobName)] configured as response. The job does not exist on destination $destServer. Skipping."
if ($PSCmdlet.ShouldProcess($destinstance, "Checking for conflicts")) {
$copyAgentAlertStatus.Status = "Skipped"
$copyAgentAlertStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
}
continue
}
if ($PSCmdlet.ShouldProcess($destinstance, "Creating Alert $alertName")) {
try {
Write-Message -Message "Copying Alert $alertName" -Level Verbose
$sql = $serverAlert.Script() | Out-String
$sql = $sql -replace "@job_id=N'........-....-....-....-............", "@job_id=N'00000000-0000-0000-0000-000000000000"
Write-Message -Message $sql -Level Debug
$null = $destServer.Query($sql)
$copyAgentAlertStatus.Status = "Successful"
$copyAgentAlertStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
}
catch {
$copyAgentAlertStatus.Status = "Failed"
$copyAgentAlertStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
Stop-Function -Message "Issue creating alert" -Category InvalidOperation -ErrorRecord $_ -Target $destServer -Continue
}
}
$destServer.JobServer.Alerts.Refresh()
$destServer.JobServer.Jobs.Refresh()
$newAlert = $destServer.JobServer.Alerts[$alertName]
$notifications = $serverAlert.EnumNotifications()
$jobName = $serverAlert.JobName
# JobId = 00000000-0000-0000-0000-000 means the Alert does not execute/is attached to a SQL Agent Job.
if ($serverAlert.JobId -ne '00000000-0000-0000-0000-000000000000') {
$copyAgentAlertStatus = [pscustomobject]@{
SourceServer = $sourceServer.Name
DestinationServer = $destServer.Name
Name = $alertName
Type = "Agent Alert Job Association"
Notes = "Associated with $jobName"
Status = $null
DateTime = [Sqlcollaborative.Dbatools.Utility.DbaDateTime](Get-Date)
}
if ($PSCmdlet.ShouldProcess($destinstance, "Adding $alertName to $jobName")) {
try {
<# THERE needs to be validation within this block to see if the $jobName actually exists on the source server. #>
Write-Message -Message "Adding $alertName to $jobName" -Level Verbose
$newJob = $destServer.JobServer.Jobs[$jobName]
$newJobId = ($newJob.JobId) -replace " ", ""
$sql = $sql -replace '00000000-0000-0000-0000-000000000000', $newJobId
$sql = $sql -replace 'sp_add_alert', 'sp_update_alert'
Write-Message -Message $sql -Level Debug
$null = $destServer.Query($sql)
$copyAgentAlertStatus.Status = "Successful"
$copyAgentAlertStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
}
catch {
$copyAgentAlertStatus.Status = "Failed"
$copyAgentAlertStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
Stop-Function -Message "Issue adding alert to job" -Category InvalidOperation -ErrorRecord $_ -Target $destServer
}
}
}
if ($PSCmdlet.ShouldProcess($destinstance, "Moving Notifications $alertName")) {
try {
$copyAgentAlertStatus = [pscustomobject]@{
SourceServer = $sourceServer.Name
DestinationServer = $destServer.Name
Name = $alertName
Type = "Agent Alert Notification"
Notes = $null
Status = $null
DateTime = [Sqlcollaborative.Dbatools.Utility.DbaDateTime](Get-Date)
}
# cant add them this way, we need to modify the existing one or give all options that are supported.
foreach ($notify in $notifications) {
$notifyCollection = @()
if ($notify.UseNetSend -eq $true) {
Write-Message -Message "Adding net send" -Level Verbose
$notifyCollection += "NetSend"
}
if ($notify.UseEmail -eq $true) {
Write-Message -Message "Adding email" -Level Verbose
$notifyCollection += "NotifyEmail"
}
if ($notify.UsePager -eq $true) {
Write-Message -Message "Adding pager" -Level Verbose
$notifyCollection += "Pager"
}
$notifyMethods = $notifyCollection -join ", "
$newAlert.AddNotification($notify.OperatorName, [Microsoft.SqlServer.Management.Smo.Agent.NotifyMethods]$notifyMethods)
}
$copyAgentAlertStatus.Status = "Successful"
$copyAgentAlertStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
}
catch {
$copyAgentAlertStatus.Status = "Failed"
$copyAgentAlertStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
Stop-Function -Message "Issue moving notifications for the alert" -Category InvalidOperation -ErrorRecord $_ -Target $destServer
}
}
}
}
}
end {
Test-DbaDeprecation -DeprecatedOn "1.0.0" -EnableException:$false -Alias Copy-SqlAlert
}
}
#ValidationTags#Messaging#
function Copy-DbaAgentCategory {
<#
.SYNOPSIS
Copy-DbaAgentCategory migrates SQL Agent categories from one SQL Server to another. This is similar to sp_add_category.
https://msdn.microsoft.com/en-us/library/ms181597.aspx
.DESCRIPTION
By default, all SQL Agent categories for Jobs, Operators and Alerts are copied.
The -OperatorCategories parameter is auto-populated for command-line completion and can be used to copy only specific operator categories.
The -AgentCategories parameter is auto-populated for command-line completion and can be used to copy only specific agent categories.
The -JobCategories parameter is auto-populated for command-line completion and can be used to copy only specific job categories.
If the category already exists on the destination, it will be skipped unless -Force is used.
.PARAMETER Source
Source SQL Server. You must have sysadmin access and server version must be SQL Server version 2000 or higher.
.PARAMETER SourceSqlCredential
Login to the target instance using alternative credentials. Windows and SQL Authentication supported. Accepts credential objects (Get-Credential)
.PARAMETER Destination
Destination SQL Server. You must have sysadmin access and the server must be SQL Server 2000 or higher.
.PARAMETER DestinationSqlCredential
Login to the target instance using alternative credentials. Windows and SQL Authentication supported. Accepts credential objects (Get-Credential)
.PARAMETER CategoryType
Specifies the Category Type to migrate. Valid options are "Job", "Alert" and "Operator". When CategoryType is specified, all categories from the selected type will be migrated. For granular migrations, use the three parameters below.
.PARAMETER OperatorCategory
This parameter is auto-populated for command-line completion and can be used to copy only specific operator categories.
.PARAMETER AgentCategory
This parameter is auto-populated for command-line completion and can be used to copy only specific agent categories.
.PARAMETER JobCategory
This parameter is auto-populated for command-line completion and can be used to copy only specific job categories.
.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.
.PARAMETER Force
If this switch is enabled, the Category will be dropped and recreated on Destination.
.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.
.NOTES
Tags: Migration, Agent
Author: Chrissy LeMaire (@cl), netnerds.net
Requires: sysadmin access on SQL Servers
Website: https://dbatools.io
Copyright: (C) Chrissy LeMaire, [email protected]
License: MIT https://opensource.org/licenses/MIT
.LINK
https://dbatools.io/Copy-DbaAgentCategory
.EXAMPLE
Copy-DbaAgentCategory -Source sqlserver2014a -Destination sqlcluster
Copies all operator categories from sqlserver2014a to sqlcluster using Windows authentication. If operator categories with the same name exist on sqlcluster, they will be skipped.
.EXAMPLE
Copy-DbaAgentCategory -Source sqlserver2014a -Destination sqlcluster -OperatorCategory PSOperator -SourceSqlCredential $cred -Force
Copies a single operator category, the PSOperator operator category from sqlserver2014a to sqlcluster using SQL credentials to authenticate to sqlserver2014a and Windows credentials for sqlcluster. If a operator category with the same name exists on sqlcluster, it will be dropped and recreated because -Force was used.
.EXAMPLE
Copy-DbaAgentCategory -Source sqlserver2014a -Destination sqlcluster -WhatIf -Force
Shows what would happen if the command were executed using force.
#>
[CmdletBinding(DefaultParameterSetName = "Default", SupportsShouldprocess = $true)]
param (
[parameter(Mandatory)]
[DbaInstanceParameter]$Source,
[PSCredential]$SourceSqlCredential,
[parameter(Mandatory)]
[DbaInstanceParameter[]]$Destination,
[PSCredential]$DestinationSqlCredential,
[Parameter(ParameterSetName = 'SpecificAlerts')]
[ValidateSet('Job', 'Alert', 'Operator')]
[string[]]$CategoryType,
[string[]]$JobCategory,
[string[]]$AgentCategory,
[string[]]$OperatorCategory,
[switch]$Force,
[Alias('Silent')]
[switch]$EnableException
)
begin {
function Copy-JobCategory {
<#
.SYNOPSIS
Copy-JobCategory migrates job categories from one SQL Server to another.
.DESCRIPTION
By default, all job categories are copied. The -JobCategories parameter is auto-populated for command-line completion and can be used to copy only specific job categories.
If the associated credential for the category does not exist on the destination, it will be skipped. If the job category already exists on the destination, it will be skipped unless -Force is used.
#>
param (
[string[]]$jobCategories
)
process {
$serverJobCategories = $sourceServer.JobServer.JobCategories | Where-Object ID -ge 100
$destJobCategories = $destServer.JobServer.JobCategories | Where-Object ID -ge 100
foreach ($jobCategory in $serverJobCategories) {
$categoryName = $jobCategory.Name
$copyJobCategoryStatus = [pscustomobject]@{
SourceServer = $sourceServer.Name
DestinationServer = $destServer.Name
Name = $categoryName
Type = "Agent Job Category"
Status = $null
Notes = $null
DateTime = [Sqlcollaborative.Dbatools.Utility.DbaDateTime](Get-Date)
}
if ($jobCategories.Count -gt 0 -and $jobCategories -notcontains $categoryName) {
continue
}
if ($destJobCategories.Name -contains $jobCategory.name) {
if ($force -eq $false) {
$copyJobCategoryStatus.Status = "Skipped"
$copyJobCategoryStatus.Notes = "Already exists"
$copyJobCategoryStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
Write-Message -Level Verbose -Message "Job category $categoryName exists at destination. Use -Force to drop and migrate."
continue
}
else {
if ($Pscmdlet.ShouldProcess($destinstance, "Dropping job category $categoryName")) {
try {
Write-Message -Level Verbose -Message "Dropping Job category $categoryName"
$destServer.JobServer.JobCategories[$categoryName].Drop()
}
catch {
$copyJobCategoryStatus.Status = "Failed"
$copyJobCategoryStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
Stop-Function -Message "Issue dropping job category" -Target $categoryName -ErrorRecord $_ -Continue
}
}
}
}
if ($Pscmdlet.ShouldProcess($destinstance, "Creating Job category $categoryName")) {
try {
Write-Message -Level Verbose -Message "Copying Job category $categoryName"
$sql = $jobCategory.Script() | Out-String
Write-Message -Level Debug -Message "SQL Statement: $sql"
$destServer.Query($sql)
$copyJobCategoryStatus.Status = "Successful"
$copyJobCategoryStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
}
catch {
$copyJobCategoryStatus.Status = "Failed"
$copyJobCategoryStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
Stop-Function -Message "Issue copying job category" -Target $categoryName -ErrorRecord $_
}
}
}
}
}
function Copy-OperatorCategory {
<#
.SYNOPSIS
Copy-OperatorCategory migrates operator categories from one SQL Server to another.
.DESCRIPTION
By default, all operator categories are copied. The -OperatorCategories parameter is auto-populated for command-line completion and can be used to copy only specific operator categories.
If the associated credential for the category does not exist on the destination, it will be skipped. If the operator category already exists on the destination, it will be skipped unless -Force is used.
#>
[CmdletBinding(DefaultParameterSetName = "Default", SupportsShouldprocess = $true)]
param (
[string[]]$operatorCategories
)
process {
$serverOperatorCategories = $sourceServer.JobServer.OperatorCategories | Where-Object ID -ge 100
$destOperatorCategories = $destServer.JobServer.OperatorCategories | Where-Object ID -ge 100
foreach ($operatorCategory in $serverOperatorCategories) {
$categoryName = $operatorCategory.Name
$copyOperatorCategoryStatus = [pscustomobject]@{
SourceServer = $sourceServer.Name
DestinationServer = $destServer.Name
Type = "Agent Operator Category"
Name = $categoryName
Status = $null
Notes = $null
DateTime = [Sqlcollaborative.Dbatools.Utility.DbaDateTime](Get-Date)
}
if ($operatorCategories.Count -gt 0 -and $operatorCategories -notcontains $categoryName) {
continue
}
if ($destOperatorCategories.Name -contains $operatorCategory.Name) {
if ($force -eq $false) {
$copyOperatorCategoryStatus.Status = "Skipped"
$copyOperatorCategoryStatus.Notes = "Already exists"
$copyOperatorCategoryStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
Write-Message -Level Verbose -Message "Operator category $categoryName exists at destination. Use -Force to drop and migrate."
continue
}
else {
if ($Pscmdlet.ShouldProcess($destinstance, "Dropping operator category $categoryName and recreating")) {
try {
Write-Message -Level Verbose -Message "Dropping Operator category $categoryName"
$destServer.JobServer.OperatorCategories[$categoryName].Drop()
Write-Message -Level Verbose -Message "Copying Operator category $categoryName"
$sql = $operatorCategory.Script() | Out-String
Write-Message -Level Debug -Message $sql
$destServer.Query($sql)
}
catch {
$copyOperatorCategoryStatus.Status = "Failed"
$copyOperatorCategoryStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
Stop-Function -Message "Issue dropping operator category" -Target $categoryName -ErrorRecord $_
}
}
}
}
else {
if ($Pscmdlet.ShouldProcess($destinstance, "Creating Operator category $categoryName")) {
try {
Write-Message -Level Verbose -Message "Copying Operator category $categoryName"
$sql = $operatorCategory.Script() | Out-String
Write-Message -Level Debug -Message $sql
$destServer.Query($sql)
$copyOperatorCategoryStatus.Status = "Successful"
$copyOperatorCategoryStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
}
catch {
$copyOperatorCategoryStatus.Status = "Failed"
$copyOperatorCategoryStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
Stop-Function -Message "Issue copying operator category" -Target $categoryName -ErrorRecord $_
}
}
}
}
}
}
function Copy-AlertCategory {
<#
.SYNOPSIS
Copy-AlertCategory migrates alert categories from one SQL Server to another.
.DESCRIPTION
By default, all alert categories are copied. The -AlertCategories parameter is auto-populated for command-line completion and can be used to copy only specific alert categories.
If the associated credential for the category does not exist on the destination, it will be skipped. If the alert category already exists on the destination, it will be skipped unless -Force is used.
#>
[CmdletBinding(DefaultParameterSetName = "Default", SupportsShouldprocess = $true)]
param (
[string[]]$AlertCategories
)
process {
if ($sourceServer.VersionMajor -lt 9 -or $destServer.VersionMajor -lt 9) {
throw "Server AlertCategories are only supported in SQL Server 2005 and above. Quitting."
}
$serverAlertCategories = $sourceServer.JobServer.AlertCategories | Where-Object ID -ge 100
$destAlertCategories = $destServer.JobServer.AlertCategories | Where-Object ID -ge 100
foreach ($alertCategory in $serverAlertCategories) {
$categoryName = $alertCategory.Name
$copyAlertCategoryStatus = [pscustomobject]@{
SourceServer = $sourceServer.Name
DestinationServer = $destServer.Name
Type = "Agent Alert Category"
Name = $categoryName
Status = $null
Notes = $null
DateTime = [Sqlcollaborative.Dbatools.Utility.DbaDateTime](Get-Date)
}
if ($alertCategories.Length -gt 0 -and $alertCategories -notcontains $categoryName) {
continue
}
if ($destAlertCategories.Name -contains $alertCategory.name) {
if ($force -eq $false) {
$copyAlertCategoryStatus.Status = "Skipped"
$copyAlertCategoryStatus.Notes = "Already exists"
$copyAlertCategoryStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
Write-Message -Level Verbose -Message "Alert category $categoryName exists at destination. Use -Force to drop and migrate."
continue
}
else {
if ($Pscmdlet.ShouldProcess($destinstance, "Dropping alert category $categoryName and recreating")) {
try {
Write-Message -Level Verbose -Message "Dropping Alert category $categoryName"
$destServer.JobServer.AlertCategories[$categoryName].Drop()
Write-Message -Level Verbose -Message "Copying Alert category $categoryName"
$sql = $alertcategory.Script() | Out-String
Write-Message -Level Debug -Message "SQL Statement: $sql"
$destServer.Query($sql)
}
catch {
$copyAlertCategoryStatus.Status = "Failed"
$copyAlertCategoryStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
Stop-Function -Message "Issue dropping alert category" -Target $categoryName -ErrorRecord $_
}
}
}
}
else {
if ($Pscmdlet.ShouldProcess($destinstance, "Creating Alert category $categoryName")) {
try {
Write-Message -Level Verbose -Message "Copying Alert category $categoryName"
$sql = $alertCategory.Script() | Out-String
Write-Message -Level Debug -Message $sql
$destServer.Query($sql)
$copyAlertCategoryStatus.Status = "Successful"
$copyAlertCategoryStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
}
catch {
$copyAlertCategoryStatus.Status = "Failed"
$copyAlertCategoryStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
Stop-Function -Message "Issue creating alert category" -Target $categoryName -ErrorRecord $_
}
}
}
}
}
}
try {
Write-Message -Level Verbose -Message "Connecting to $Source"
$sourceServer = Connect-SqlInstance -SqlInstance $Source -SqlCredential $SourceSqlCredential
}
catch {
Stop-Function -Message "Failure" -Category ConnectionError -ErrorRecord $_ -Target $Source
return
}
}
process {
if (Test-FunctionInterrupt) { return }
foreach ($destinstance in $Destination) {
try {
Write-Message -Level Verbose -Message "Connecting to $destinstance"
$destServer = Connect-SqlInstance -SqlInstance $destinstance -SqlCredential $DestinationSqlCredential
}
catch {
Stop-Function -Message "Failure" -Category ConnectionError -ErrorRecord $_ -Target $destinstance -Continue
}
if ($CategoryType.count -gt 0) {
switch ($CategoryType) {
"Job" {
Copy-JobCategory
}
"Alert" {
Copy-AlertCategory
}
"Operator" {
Copy-OperatorCategory
}
}
continue
}
if (($OperatorCategory.Count + $AlertCategory.Count + $jobCategory.Count) -gt 0) {
if ($OperatorCategory.Count -gt 0) {
Copy-OperatorCategory -OperatorCategories $OperatorCategory
}
if ($AlertCategory.Count -gt 0) {
Copy-AlertCategory -AlertCategories $AlertCategory
}
if ($jobCategory.Count -gt 0) {
Copy-JobCategory -JobCategories $jobCategory
}
continue
}
Copy-OperatorCategory
Copy-AlertCategory
Copy-JobCategory
}
}
end {
Test-DbaDeprecation -DeprecatedOn "1.0.0" -EnableException:$false -Alias Copy-SqlAgentCategory
}
}
function Copy-DbaAgentJob {
<#
.SYNOPSIS
Copy-DbaAgentJob migrates jobs from one SQL Server to another.
.DESCRIPTION
By default, all jobs are copied. The -Job parameter is auto-populated for command-line completion and can be used to copy only specific jobs.
If the job already exists on the destination, it will be skipped unless -Force is used.
.PARAMETER Source
Source SQL Server. You must have sysadmin access and server version must be SQL Server version 2000 or higher.
.PARAMETER SourceSqlCredential
Login to the target instance using alternative credentials. Windows and SQL Authentication supported. Accepts credential objects (Get-Credential)
.PARAMETER Destination
Destination SQL Server. You must have sysadmin access and the server must be SQL Server 2000 or higher.
.PARAMETER DestinationSqlCredential
Login to the target instance using alternative credentials. Windows and SQL Authentication supported. Accepts credential objects (Get-Credential)
.PARAMETER Job
The job(s) to process. This list is auto-populated from the server. If unspecified, all jobs will be processed.
.PARAMETER ExcludeJob
The job(s) to exclude. This list is auto-populated from the server.
.PARAMETER DisableOnSource
If this switch is enabled, the job will be disabled on the source server.
.PARAMETER DisableOnDestination
If this switch is enabled, the newly migrated job will be disabled on the destination server.
.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.
.PARAMETER Force
If this switch is enabled, the Job will be dropped and recreated on Destination.
.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.
.NOTES
Tags: Migration, Agent, Job
Author: Chrissy LeMaire (@cl), netnerds.net
Website: https://dbatools.io
Copyright: (C) Chrissy LeMaire, [email protected]
License: MIT https://opensource.org/licenses/MIT
.LINK
https://dbatools.io/Copy-DbaAgentJob
.EXAMPLE
Copy-DbaAgentJob -Source sqlserver2014a -Destination sqlcluster
Copies all jobs from sqlserver2014a to sqlcluster, using Windows credentials. If jobs with the same name exist on sqlcluster, they will be skipped.
.EXAMPLE
Copy-DbaAgentJob -Source sqlserver2014a -Destination sqlcluster -Job PSJob -SourceSqlCredential $cred -Force
Copies a single job, the PSJob job from sqlserver2014a to sqlcluster, using SQL credentials for sqlserver2014a and Windows credentials for sqlcluster. If a job with the same name exists on sqlcluster, it will be dropped and recreated because -Force was used.
.EXAMPLE
Copy-DbaAgentJob -Source sqlserver2014a -Destination sqlcluster -WhatIf -Force
Shows what would happen if the command were executed using force.
#>
[cmdletbinding(DefaultParameterSetName = "Default", SupportsShouldProcess = $true)]
param (
[parameter(Mandatory)]
[DbaInstanceParameter]$Source,
[PSCredential]$SourceSqlCredential,
[parameter(Mandatory)]
[DbaInstanceParameter[]]$Destination,
[PSCredential]$DestinationSqlCredential,
[object[]]$Job,
[object[]]$ExcludeJob,
[switch]$DisableOnSource,
[switch]$DisableOnDestination,
[switch]$Force,
[Alias('Silent')]
[switch]$EnableException
)
begin {
try {
Write-Message -Level Verbose -Message "Connecting to $Source"
$sourceServer = Connect-SqlInstance -SqlInstance $Source -SqlCredential $SourceSqlCredential
}
catch {
Stop-Function -Message "Failure" -Category ConnectionError -ErrorRecord $_ -Target $Source
return
}
$serverJobs = $sourceServer.JobServer.Jobs
}
process {
if (Test-FunctionInterrupt) { return }
foreach ($destinstance in $Destination) {
try {
Write-Message -Level Verbose -Message "Connecting to $destinstance"
$destServer = Connect-SqlInstance -SqlInstance $destinstance -SqlCredential $DestinationSqlCredential
}
catch {
Stop-Function -Message "Failure" -Category ConnectionError -ErrorRecord $_ -Target $destinstance -Continue
}
$destJobs = $destServer.JobServer.Jobs
foreach ($serverJob in $serverJobs) {
$jobName = $serverJob.name
$jobId = $serverJob.JobId
$copyJobStatus = [pscustomobject]@{
SourceServer = $sourceServer.Name
DestinationServer = $destServer.Name
Name = $jobName
Type = "Agent Job"
Status = $null
Notes = $null
DateTime = [DbaDateTime](Get-Date)
}
if ($Job -and $jobName -notin $Job -or $jobName -in $ExcludeJob) {
Write-Message -Level Verbose -Message "Job [$jobName] filtered. Skipping."
continue
}
Write-Message -Message "Working on job: $jobName" -Level Verbose
$sql = "
SELECT sp.[name] AS MaintenancePlanName
FROM msdb.dbo.sysmaintplan_plans AS sp
INNER JOIN msdb.dbo.sysmaintplan_subplans AS sps
ON sps.plan_id = sp.id
WHERE job_id = '$($jobId)'"
Write-Message -Message $sql -Level Debug
$MaintenancePlanName = $sourceServer.Query($sql).MaintenancePlanName
if ($MaintenancePlanName) {
if ($Pscmdlet.ShouldProcess($destinstance, "Job [$jobName] is associated with Maintenance Plan: $MaintenancePlanNam")) {
$copyJobStatus.Status = "Skipped"
$copyJobStatus.Notes = "Job is associated with maintenance plan"
$copyJobStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
Write-Message -Level Verbose -Message "Job [$jobName] is associated with Maintenance Plan: $MaintenancePlanName"
}
continue
}
$dbNames = ($serverJob.JobSteps | where-object {$_.SubSystem -ne 'ActiveScripting'}).DatabaseName | Where-Object { $_.Length -gt 0 }
$missingDb = $dbNames | Where-Object { $destServer.Databases.Name -notcontains $_ }
if ($missingDb.Count -gt 0 -and $dbNames.Count -gt 0) {
if ($Pscmdlet.ShouldProcess($destinstance, "Database(s) $missingDb doesn't exist on destination. Skipping job [$jobName].")) {
$missingDb = ($missingDb | Sort-Object | Get-Unique) -join ", "
$copyJobStatus.Status = "Skipped"
$copyJobStatus.Notes = "Job is dependent on database: $missingDb"
$copyJobStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
Write-Message -Level Verbose -Message "Database(s) $missingDb doesn't exist on destination. Skipping job [$jobName]."
}
continue
}
$missingLogin = $serverJob.OwnerLoginName | Where-Object { $destServer.Logins.Name -notcontains $_ }
if ($missingLogin.Count -gt 0) {
if ($force -eq $false) {
if ($Pscmdlet.ShouldProcess($destinstance, "Login(s) $missingLogin doesn't exist on destination. Use -Force to set owner to [sa]. Skipping job [$jobName].")) {
$missingLogin = ($missingLogin | Sort-Object | Get-Unique) -join ", "
$copyJobStatus.Status = "Skipped"
$copyJobStatus.Notes = "Job is dependent on login $missingLogin"
$copyJobStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
Write-Message -Level Verbose -Message "Login(s) $missingLogin doesn't exist on destination. Use -Force to set owner to [sa]. Skipping job [$jobName]."
}
continue
}
}
$proxyNames = $serverJob.JobSteps.ProxyName | Where-Object { $_.Length -gt 0 }
$missingProxy = $proxyNames | Where-Object { $destServer.JobServer.ProxyAccounts.Name -notcontains $_ }
if ($missingProxy.Count -gt 0 -and $proxyNames.Count -gt 0) {
if ($Pscmdlet.ShouldProcess($destinstance, "Proxy Account(s) $($proxyNames[0]) doesn't exist on destination. Skipping job [$jobName].")) {
$missingProxy = ($missingProxy | Sort-Object | Get-Unique) -join ", "
$copyJobStatus.Status = "Skipped"
$copyJobStatus.Notes = "Job is dependent on proxy $($proxyNames[0])"
$copyJobStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
Write-Message -Level Verbose -Message "Proxy Account(s) $($proxyNames[0]) doesn't exist on destination. Skipping job [$jobName]."
}
continue
}
$operators = $serverJob.OperatorToEmail, $serverJob.OperatorToNetSend, $serverJob.OperatorToPage | Where-Object { $_.Length -gt 0 }
$missingOperators = $operators | Where-Object { $destServer.JobServer.Operators.Name -notcontains $_ }
if ($missingOperators.Count -gt 0 -and $operators.Count -gt 0) {
if ($Pscmdlet.ShouldProcess($destinstance, "Operator(s) $($missingOperator) doesn't exist on destination. Skipping job [$jobName]")) {
$missingOperator = ($operators | Sort-Object | Get-Unique) -join ", "
$copyJobStatus.Status = "Skipped"
$copyJobStatus.Notes = "Job is dependent on operator $missingOperator"
$copyJobStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
Write-Message -Level Verbose -Message "Operator(s) $($missingOperator) doesn't exist on destination. Skipping job [$jobName]"
}
continue
}
if ($destJobs.name -contains $serverJob.name) {
if ($force -eq $false) {
if ($Pscmdlet.ShouldProcess($destinstance, "Job $jobName exists at destination. Use -Force to drop and migrate.")) {
$copyJobStatus.Status = "Skipped"
$copyJobStatus.Notes = "Job already exists on destination"
$copyJobStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
Write-Message -Level Verbose -Message "Job $jobName exists at destination. Use -Force to drop and migrate."
}
continue
}
else {
if ($Pscmdlet.ShouldProcess($destinstance, "Dropping job $jobName and recreating")) {
try {
Write-Message -Message "Dropping Job $jobName" -Level Verbose
$destServer.JobServer.Jobs[$jobName].Drop()
}
catch {
$copyJobStatus.Status = "Failed"
$copyJobStatus.Notes = (Get-ErrorMessage -Record $_).Message
$copyJobStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
Stop-Function -Message "Issue dropping job" -Target $jobName -ErrorRecord $_ -Continue
}
}
}
}
if ($Pscmdlet.ShouldProcess($destinstance, "Creating Job $jobName")) {
try {
Write-Message -Message "Copying Job $jobName" -Level Verbose
$sql = $serverJob.Script() | Out-String
if ($missingLogin.Count -gt 0 -and $force) {
$saLogin = Get-SqlSaLogin -SqlInstance $destServer
$sql = $sql -replace [Regex]::Escape("@owner_login_name=N'$missingLogin'"), [Regex]::Escape("@owner_login_name=N'$saLogin'")
}
Write-Message -Message $sql -Level Debug
$destServer.Query($sql)
$destServer.JobServer.Jobs.Refresh()
$destServer.JobServer.Jobs[$serverJob.name].IsEnabled = $sourceServer.JobServer.Jobs[$serverJob.name].IsEnabled
$destServer.JobServer.Jobs[$serverJob.name].Alter()
}
catch {
$copyJobStatus.Status = "Failed"
$copyJobStatus.Notes = (Get-ErrorMessage -Record $_)
$copyJobStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
Stop-Function -Message "Issue copying job" -Target $jobName -ErrorRecord $_ -Continue
}
}
if ($DisableOnDestination) {
if ($Pscmdlet.ShouldProcess($destinstance, "Disabling $jobName")) {
Write-Message -Message "Disabling $jobName on $destinstance" -Level Verbose
$destServer.JobServer.Jobs[$serverJob.name].IsEnabled = $False
$destServer.JobServer.Jobs[$serverJob.name].Alter()
}
}
if ($DisableOnSource) {
if ($Pscmdlet.ShouldProcess($source, "Disabling $jobName")) {
Write-Message -Message "Disabling $jobName on $source" -Level Verbose
$serverJob.IsEnabled = $false
$serverJob.Alter()
}
}
if ($Pscmdlet.ShouldProcess($destinstance, "Reporting status of migration for $jobname")) {
$copyJobStatus.Status = "Successful"
$copyJobStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
}
}
}
}
end {
Test-DbaDeprecation -DeprecatedOn "1.0.0" -EnableException:$false -Alias Copy-SqlJob
}
}
function Copy-DbaAgentOperator {
<#
.SYNOPSIS
Copy-DbaAgentOperator migrates operators from one SQL Server to another.
.DESCRIPTION
By default, all operators are copied. The -Operators parameter is auto-populated for command-line completion and can be used to copy only specific operators.
If the associated credentials for the operator do not exist on the destination, it will be skipped. If the operator already exists on the destination, it will be skipped unless -Force is used.
.PARAMETER Source
Source SQL Server. You must have sysadmin access and server version must be SQL Server version 2000 or higher.
.PARAMETER SourceSqlCredential
Login to the target instance using alternative credentials. Windows and SQL Authentication supported. Accepts credential objects (Get-Credential)
.PARAMETER Destination
Destination SQL Server. You must have sysadmin access and the server must be SQL Server 2000 or higher.
.PARAMETER DestinationSqlCredential
Login to the target instance using alternative credentials. Windows and SQL Authentication supported. Accepts credential objects (Get-Credential)
.PARAMETER Operator
The operator(s) to process. This list is auto-populated from the server. If unspecified, all operators will be processed.
.PARAMETER ExcludeOperator
The operators(s) to exclude. This list is auto-populated from the server.
.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.
.PARAMETER Force
If this switch is enabled, the Operator will be dropped and recreated on Destination.
.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.
.NOTES
Tags: Migration, Agent, Operator
Author: Chrissy LeMaire (@cl), netnerds.net
Requires: sysadmin access on SQL Servers
Website: https://dbatools.io
Copyright: (C) Chrissy LeMaire, [email protected]
License: MIT https://opensource.org/licenses/MIT
.LINK
https://dbatools.io/Copy-DbaAgentOperator
.EXAMPLE
Copy-DbaAgentOperator -Source sqlserver2014a -Destination sqlcluster
Copies all operators from sqlserver2014a to sqlcluster using Windows credentials. If operators with the same name exist on sqlcluster, they will be skipped.
.EXAMPLE
Copy-DbaAgentOperator -Source sqlserver2014a -Destination sqlcluster -Operator PSOperator -SourceSqlCredential $cred -Force
Copies only the PSOperator operator from sqlserver2014a to sqlcluster using SQL credentials for sqlserver2014a and Windows credentials for sqlcluster. If an operator with the same name exists on sqlcluster, it will be dropped and recreated because -Force was used.
.EXAMPLE
Copy-DbaAgentOperator -Source sqlserver2014a -Destination sqlcluster -WhatIf -Force
Shows what would happen if the command were executed using force.
#>
[CmdletBinding(DefaultParameterSetName = "Default", SupportsShouldProcess = $true)]
param (
[parameter(Mandatory)]
[DbaInstanceParameter]$Source,
[PSCredential]
$SourceSqlCredential,
[parameter(Mandatory)]
[DbaInstanceParameter[]]$Destination,
[PSCredential]
$DestinationSqlCredential,
[object[]]$Operator,
[object[]]$ExcludeOperator,
[switch]$Force,
[Alias('Silent')]
[switch]$EnableException
)
begin {
try {
Write-Message -Level Verbose -Message "Connecting to $Source"
$sourceServer = Connect-SqlInstance -SqlInstance $Source -SqlCredential $SourceSqlCredential
}
catch {
Stop-Function -Message "Failure" -Category ConnectionError -ErrorRecord $_ -Target $Source
return
}
$serverOperator = $sourceServer.JobServer.Operators
}
process {
if (Test-FunctionInterrupt) { return }
foreach ($destinstance in $Destination) {
try {
Write-Message -Level Verbose -Message "Connecting to $destinstance"
$destServer = Connect-SqlInstance -SqlInstance $destinstance -SqlCredential $DestinationSqlCredential
}
catch {
Stop-Function -Message "Failure" -Category ConnectionError -ErrorRecord $_ -Target $destinstance -Continue
}
$destOperator = $destServer.JobServer.Operators
$failsafe = $destServer.JobServer.AlertSystem | Select-Object FailSafeOperator
foreach ($sOperator in $serverOperator) {
$operatorName = $sOperator.Name
$copyOperatorStatus = [pscustomobject]@{
SourceServer = $sourceServer.Name
DestinationServer = $destServer.Name
Name = $operatorName
Type = "Agent Operator"
Status = $null
Notes = $null
DateTime = [DbaDateTime](Get-Date)
}
if ($Operator -and $Operator -notcontains $operatorName -or $ExcludeOperator -in $operatorName) {
continue
}
if ($destOperator.Name -contains $sOperator.Name) {
if ($force -eq $false) {
if ($Pscmdlet.ShouldProcess($destinstance, "Operator $operatorName exists at destination. Use -Force to drop and migrate.")) {
$copyOperatorStatus.Status = "Skipped"
$copyOperatorStatus.Notes = "Already exists"
$copyOperatorStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
Write-Message -Level Verbose -Message "Operator $operatorName exists at destination. Use -Force to drop and migrate."
}
continue
}
else {
if ($failsafe.FailSafeOperator -eq $operatorName) {
Write-Message -Level Verbose -Message "$operatorName is the failsafe operator. Skipping drop."
continue
}
if ($Pscmdlet.ShouldProcess($destinstance, "Dropping operator $operatorName and recreating")) {
try {
Write-Message -Level Verbose -Message "Dropping Operator $operatorName"
$destServer.JobServer.Operators[$operatorName].Drop()
}
catch {
$copyOperatorStatus.Status = "Failed"
$copyOperatorStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
Stop-Function -Message "Issue dropping operator" -Category InvalidOperation -ErrorRecord $_ -Target $destServer -Continue
}
}
}
}
if ($Pscmdlet.ShouldProcess($destinstance, "Creating Operator $operatorName")) {
try {
Write-Message -Level Verbose -Message "Copying Operator $operatorName"
$sql = $sOperator.Script() | Out-String
Write-Message -Level Debug -Message $sql
$destServer.Query($sql)
$copyOperatorStatus.Status = "Successful"
$copyOperatorStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
}
catch {
$copyOperatorStatus.Status = "Failed"
$copyOperatorStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
Stop-Function -Message "Issue creating operator." -Category InvalidOperation -ErrorRecord $_ -Target $destServer
}
}
}
}
}
end {
Test-DbaDeprecation -DeprecatedOn "1.0.0" -EnableException:$false -Alias Copy-SqlOperator
}
}
function Copy-DbaAgentProxyAccount {
<#
.SYNOPSIS
Copy-DbaAgentProxyAccount migrates proxy accounts from one SQL Server to another.
.DESCRIPTION
By default, all proxy accounts are copied. The -ProxyAccounts parameter is auto-populated for command-line completion and can be used to copy only specific proxy accounts.
If the associated credential for the account does not exist on the destination, it will be skipped. If the proxy account already exists on the destination, it will be skipped unless -Force is used.
.PARAMETER Source
Source SQL Server. You must have sysadmin access and server version must be SQL Server version 2000 or higher.
.PARAMETER SourceSqlCredential
Login to the target instance using alternative credentials. Windows and SQL Authentication supported. Accepts credential objects (Get-Credential)
.PARAMETER Destination
Destination SQL Server. You must have sysadmin access and the server must be SQL Server 2000 or higher.
.PARAMETER DestinationSqlCredential
Login to the target instance using alternative credentials. Windows and SQL Authentication supported. Accepts credential objects (Get-Credential)
.PARAMETER ProxyAccount
Only migrate specific proxy accounts
.PARAMETER ExcludeProxyAccount
Migrate all proxy accounts except the ones explicitly excluded
.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.
.PARAMETER Force
If this switch is enabled, the Operator will be dropped and recreated on Destination.
.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.
.NOTES
Tags: Migration, Agent
Author: Chrissy LeMaire (@cl), netnerds.net
Requires: sysadmin access on SQL Servers
Website: https://dbatools.io
Copyright: (C) Chrissy LeMaire, [email protected]
License: MIT https://opensource.org/licenses/MIT
.LINK
https://dbatools.io/Copy-DbaAgentProxyAccount
.EXAMPLE
Copy-DbaAgentProxyAccount -Source sqlserver2014a -Destination sqlcluster
Copies all proxy accounts from sqlserver2014a to sqlcluster using Windows credentials. If proxy accounts with the same name exist on sqlcluster, they will be skipped.
.EXAMPLE
Copy-DbaAgentProxyAccount -Source sqlserver2014a -Destination sqlcluster -ProxyAccount PSProxy -SourceSqlCredential $cred -Force
Copies only the PSProxy proxy account from sqlserver2014a to sqlcluster using SQL credentials for sqlserver2014a and Windows credentials for sqlcluster. If a proxy account with the same name exists on sqlcluster, it will be dropped and recreated because -Force was used.
.EXAMPLE
Copy-DbaAgentProxyAccount -Source sqlserver2014a -Destination sqlcluster -WhatIf -Force
Shows what would happen if the command were executed using force.
#>
[CmdletBinding(DefaultParameterSetName = "Default", SupportsShouldProcess = $true)]
param (
[parameter(Mandatory)]
[DbaInstanceParameter]$Source,
[PSCredential]$SourceSqlCredential,
[parameter(Mandatory)]
[DbaInstanceParameter[]]$Destination,
[PSCredential]$DestinationSqlCredential,
[string[]]$ProxyAccount,
[string[]]$ExcludeProxyAccount,
[switch]$Force,
[Alias('Silent')]
[switch]$EnableException
)
begin {
try {
Write-Message -Level Verbose -Message "Connecting to $Source"
$sourceServer = Connect-SqlInstance -SqlInstance $Source -SqlCredential $SourceSqlCredential -MinimumVersion 9
}
catch {
Stop-Function -Message "Failure" -Category ConnectionError -ErrorRecord $_ -Target $Source
return
}
$serverProxyAccounts = $sourceServer.JobServer.ProxyAccounts
if ($ProxyAccount) {
$serverProxyAccounts | Where-Object Name -in $ProxyAccount
}
if ($ExcludeProxyAccount) {
$serverProxyAccounts | Where-Object Name -notin $ProxyAccount
}
}
process {
if (Test-FunctionInterrupt) { return }
foreach ($destinstance in $Destination) {
try {
Write-Message -Level Verbose -Message "Connecting to $destinstance"
$destServer = Connect-SqlInstance -SqlInstance $destinstance -SqlCredential $DestinationSqlCredential -MinimumVersion 9
}
catch {
Stop-Function -Message "Failure" -Category ConnectionError -ErrorRecord $_ -Target $destinstance -Continue
}
$destProxyAccounts = $destServer.JobServer.ProxyAccounts
foreach ($account in $serverProxyAccounts) {
$proxyName = $account.Name
$copyAgentProxyAccountStatus = [pscustomobject]@{
SourceServer = $sourceServer.Name
DestinationServer = $destServer.Name
Name = $null
Type = "Agent Proxy"
Status = $null
Notes = $null
DateTime = [Sqlcollaborative.Dbatools.Utility.DbaDateTime](Get-Date)
}
# Proxy accounts rely on Credential accounts
$credentialName = $account.CredentialName
$copyAgentProxyAccountStatus.Name = $credentialName
$copyAgentProxyAccountStatus.Type = "Credential"
try {
$credentialtest = $destServer.Credentials[$CredentialName]
}
catch {
# don't care
}
if ($null -eq $credentialtest) {
$copyAgentProxyAccountStatus.Status = "Skipped"
$copyAgentProxyAccountStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
Write-Message -Level Verbose -Message "Associated credential account, $CredentialName, does not exist on $destinstance. Skipping migration of $proxyName."
continue
}
if ($destProxyAccounts.Name -contains $proxyName) {
$copyAgentProxyAccountStatus.Name = $proxyName
$copyAgentProxyAccountStatus.Type = "ProxyAccount"
if ($force -eq $false) {
$copyAgentProxyAccountStatus.Status = "Skipped"
$copyAgentProxyAccountStatus
Write-Message -Level Verbose -Message "Server proxy account $proxyName exists at destination. Use -Force to drop and migrate."
continue
}
else {
if ($Pscmdlet.ShouldProcess($destinstance, "Dropping server proxy account $proxyName and recreating")) {
try {
Write-Message -Level Verbose -Message "Dropping server proxy account $proxyName"
$destServer.JobServer.ProxyAccounts[$proxyName].Drop()
}
catch {
$copyAgentProxyAccountStatus.Status = "Failed"
$copyAgentProxyAccountStatus.Notes = "Could not drop"
$copyAgentProxyAccountStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
Stop-Function -Message "Issue dropping proxy account" -Target $proxyName -ErrorRecord $_ -Continue
}
}
}
}
if ($Pscmdlet.ShouldProcess($destinstance, "Creating server proxy account $proxyName")) {
$copyAgentProxyAccountStatus.Name = $proxyName
$copyAgentProxyAccountStatus.Type = "ProxyAccount"
try {
Write-Message -Level Verbose -Message "Copying server proxy account $proxyName"
$sql = $account.Script() | Out-String
Write-Message -Level Debug -Message $sql
$destServer.Query($sql)
# Will fixing this misspelled status cause problems downstream?
$copyAgentProxyAccountStatus.Status = "Successful"
$copyAgentProxyAccountStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
}
catch {
$exceptionstring = $_.Exception.InnerException.ToString()
if ($exceptionstring -match 'subsystem') {
$copyAgentProxyAccountStatus.Status = "Skipping"
$copyAgentProxyAccountStatus.Notes = "Failure"
$copyAgentProxyAccountStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
Write-Message -Level Verbose -Message "One or more subsystems do not exist on the destination server. Skipping that part."
}
else {
$copyAgentProxyAccountStatus.Status = "Failed"
$copyAgentProxyAccountStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
Stop-Function -Message "Issue creating proxy account" -Target $proxyName -ErrorRecord $_
}
}
}
}
}
}
end {
Test-DbaDeprecation -DeprecatedOn "1.0.0" -EnableException:$false -Alias Copy-SqlProxyAccount
}
}
function Copy-DbaAgentServer {
<#
.SYNOPSIS
Copy SQL Server Agent from one server to another.
.DESCRIPTION
A wrapper function that calls the associated Copy command for each of the object types seen in SSMS under SQL Server Agent. This also copies all of the the SQL Agent properties (job history max rows, DBMail profile name, etc.).
You must have sysadmin access and server version must be SQL Server version 2000 or greater.
.PARAMETER Source
Source SQL Server. You must have sysadmin access and server version must be SQL Server version 2000 or higher.
.PARAMETER SourceSqlCredential
Login to the target instance using alternative credentials. Windows and SQL Authentication supported. Accepts credential objects (Get-Credential)
.PARAMETER Destination
Destination SQL Server. You must have sysadmin access and the server must be SQL Server 2000 or higher.
.PARAMETER DestinationSqlCredential
Login to the target instance using alternative credentials. Windows and SQL Authentication supported. Accepts credential objects (Get-Credential)
.PARAMETER DisableJobsOnDestination
If this switch is enabled, the jobs will be disabled on Destination after copying.
.PARAMETER DisableJobsOnSource
If this switch is enabled, the jobs will be disabled on Source after copying.
.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.
.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 Force
If this switch is enabled, existing objects on Destination with matching names from Source will be dropped, then copied.
.NOTES
Tags: Migration, SqlServerAgent, SqlAgent
Author: Chrissy LeMaire (@cl), netnerds.net
Requires: sysadmin access on SQL Servers
Website: https://dbatools.io
Copyright: (C) Chrissy LeMaire, [email protected]
License: MIT https://opensource.org/licenses/MIT
.LINK
https://dbatools.io/Copy-DbaAgentServer
.EXAMPLE
Copy-DbaAgentServer -Source sqlserver2014a -Destination sqlcluster
Copies all job server objects from sqlserver2014a to sqlcluster using Windows credentials for authentication. If job objects with the same name exist on sqlcluster, they will be skipped.
.EXAMPLE
Copy-DbaAgentServer -Source sqlserver2014a -Destination sqlcluster -SourceSqlCredential $cred
Copies all job objects from sqlserver2014a to sqlcluster using SQL credentials to authentication to sqlserver2014a and Windows credentials to authenticate to sqlcluster.
.EXAMPLE
Copy-DbaAgentServer -Source sqlserver2014a -Destination sqlcluster -WhatIf
Shows what would happen if the command were executed.
#>
[cmdletbinding(SupportsShouldProcess = $true)]
param (
[parameter(Mandatory)]
[DbaInstanceParameter]$Source,
[PSCredential]$SourceSqlCredential,
[parameter(Mandatory)]
[DbaInstanceParameter[]]$Destination,
[PSCredential]$DestinationSqlCredential,
[Switch]$DisableJobsOnDestination,
[Switch]$DisableJobsOnSource,
[switch]$Force,
[Alias('Silent')]
[switch]$EnableException
)
begin {
try {
Write-Message -Level Verbose -Message "Connecting to $Source"
$sourceServer = Connect-SqlInstance -SqlInstance $Source -SqlCredential $SourceSqlCredential
}
catch {
Stop-Function -Message "Failure" -Category ConnectionError -ErrorRecord $_ -Target $Source
return
}
Invoke-SmoCheck -SqlInstance $sourceServer
$sourceAgent = $sourceServer.JobServer
}
process {
if (Test-FunctionInterrupt) { return }
foreach ($destinstance in $Destination) {
try {
Write-Message -Level Verbose -Message "Connecting to $destinstance"
$destServer = Connect-SqlInstance -SqlInstance $destinstance -SqlCredential $DestinationSqlCredential
}
catch {
Stop-Function -Message "Failure" -Category ConnectionError -ErrorRecord $_ -Target $destinstance -Continue
}
Invoke-SmoCheck -SqlInstance $destServer
# All of these support whatif inside of them
Copy-DbaAgentCategory -Source $sourceServer -Destination $destServer -Force:$force
$destServer.JobServer.JobCategories.Refresh()
$destServer.JobServer.OperatorCategories.Refresh()
$destServer.JobServer.AlertCategories.Refresh()
Copy-DbaAgentOperator -Source $sourceServer -Destination $destServer -Force:$force
$destServer.JobServer.Operators.Refresh()
Copy-DbaAgentAlert -Source $sourceServer -Destination $destServer -Force:$force -IncludeDefaults
$destServer.JobServer.Alerts.Refresh()
Copy-DbaAgentProxyAccount -Source $sourceServer -Destination $destServer -Force:$force
$destServer.JobServer.ProxyAccounts.Refresh()
Copy-DbaAgentSharedSchedule -Source $sourceServer -Destination $destServer -Force:$force
$destServer.JobServer.SharedSchedules.Refresh()
$destServer.JobServer.Refresh()
$destServer.Refresh()
Copy-DbaAgentJob -Source $sourceServer -Destination $destServer -Force:$force -DisableOnDestination:$DisableJobsOnDestination -DisableOnSource:$DisableJobsOnSource
# To do
<#
Copy-DbaAgentMasterServer -Source $sourceServer -Destination $destServer -Force:$force
Copy-DbaAgentTargetServer -Source $sourceServer -Destination $destServer -Force:$force
Copy-DbaAgentTargetServerGroup -Source $sourceServer -Destination $destServer -Force:$force
#>
<# Here are the properties which must be migrated separately #>
$copyAgentPropStatus = [pscustomobject]@{
SourceServer = $sourceServer.Name
DestinationServer = $destServer.Name
Name = "Server level properties"
Type = "Agent Properties"
Status = $null
Notes = $null
DateTime = [DbaDateTime](Get-Date)
}
if ($Pscmdlet.ShouldProcess($destinstance, "Copying Agent Properties")) {
try {
Write-Message -Level Verbose -Message "Copying SQL Agent Properties"
$sql = $sourceAgent.Script() | Out-String
$sql = $sql -replace [Regex]::Escape("'$source'"), "'$destinstance'"
$sql = $sql -replace [Regex]::Escape("@errorlog_file="), [Regex]::Escape("--@errorlog_file=")
$sql = $sql -replace [Regex]::Escape("@auto_start="), [Regex]::Escape("--@auto_start=")
Write-Message -Level Debug -Message $sql
$null = $destServer.Query($sql)
$copyAgentPropStatus.Status = "Successful"
$copyAgentPropStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
}
catch {
$message = $_.Exception.InnerException.InnerException.InnerException.Message
if (-not $message) { $message = $_.Exception.Message }
$copyAgentPropStatus.Status = "Failed"
$copyAgentPropStatus.Notes = $message
$copyAgentPropStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
Stop-Function -Message $message -Target $destinstance
}
}
}
}
end {
Test-DbaDeprecation -DeprecatedOn "1.0.0" -EnableException:$false -Alias Copy-SqlServerAgent
Test-DbaDeprecation -DeprecatedOn "1.0.0" -EnableException:$false -Alias Copy-DbaSqlServerAgent
}
}
function Copy-DbaAgentSharedSchedule {
<#
.SYNOPSIS
Copy-DbaAgentSharedSchedule migrates shared job schedules from one SQL Server to another.
.DESCRIPTION
All shared job schedules are copied.
If the associated credential for the account does not exist on the destination, it will be skipped. If the shared job schedule already exists on the destination, it will be skipped unless -Force is used.
.PARAMETER Source
Source SQL Server. You must have sysadmin access and server version must be SQL Server version 2000 or higher.
.PARAMETER SourceSqlCredential
Login to the target instance using alternative credentials. Windows and SQL Authentication supported. Accepts credential objects (Get-Credential)
.PARAMETER Destination
Destination SQL Server. You must have sysadmin access and the server must be SQL Server 2000 or higher.
.PARAMETER DestinationSqlCredential
Login to the target instance using alternative credentials. Windows and SQL Authentication supported. Accepts credential objects (Get-Credential)
.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.
.PARAMETER Force
If this switch is enabled, the Operator will be dropped and recreated on Destination.
.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.
.NOTES
Tags: Migration, Agent
Author: Chrissy LeMaire (@cl), netnerds.net
Requires: sysadmin access on SQL Servers
Website: https://dbatools.io
Copyright: (C) Chrissy LeMaire, [email protected]
License: MIT https://opensource.org/licenses/MIT
.LINK
https://dbatools.io/Copy-DbaAgentSharedSchedule
.EXAMPLE
Copy-DbaAgentSharedSchedule -Source sqlserver2014a -Destination sqlcluster
Copies all shared job schedules from sqlserver2014a to sqlcluster using Windows credentials. If shared job schedules with the same name exist on sqlcluster, they will be skipped.
.EXAMPLE
Copy-DbaAgentSharedSchedule -Source sqlserver2014a -Destination sqlcluster -WhatIf -Force
Shows what would happen if the command were executed using force.
#>
[CmdletBinding(DefaultParameterSetName = "Default", SupportsShouldProcess = $true)]
param (
[parameter(Mandatory)]
[DbaInstanceParameter]$Source,
[PSCredential]
$SourceSqlCredential,
[parameter(Mandatory)]
[DbaInstanceParameter[]]$Destination,
[PSCredential]
$DestinationSqlCredential,
[switch]$Force,
[Alias('Silent')]
[switch]$EnableException
)
begin {
try {
Write-Message -Level Verbose -Message "Connecting to $Source"
$sourceServer = Connect-SqlInstance -SqlInstance $Source -SqlCredential $SourceSqlCredential -MinimumVersion 9
}
catch {
Stop-Function -Message "Failure" -Category ConnectionError -ErrorRecord $_ -Target $Source
return
}
$serverSchedules = $sourceServer.JobServer.SharedSchedules
}
process {
if (Test-FunctionInterrupt) { return }
foreach ($destinstance in $Destination) {
try {
Write-Message -Level Verbose -Message "Connecting to $destinstance"
$destServer = Connect-SqlInstance -SqlInstance $destinstance -SqlCredential $DestinationSqlCredential -MinimumVersion 9
}
catch {
Stop-Function -Message "Failure" -Category ConnectionError -ErrorRecord $_ -Target $destinstance -Continue
}
$destSchedules = $destServer.JobServer.SharedSchedules
foreach ($schedule in $serverSchedules) {
$scheduleName = $schedule.Name
$copySharedScheduleStatus = [pscustomobject]@{
SourceServer = $sourceServer.Name
DestinationServer = $destServer.Name
Type = "Agent Schedule"
Name = $scheduleName
Status = $null
Notes = $null
DateTime = [Sqlcollaborative.Dbatools.Utility.DbaDateTime](Get-Date)
}
if ($schedules.Length -gt 0 -and $schedules -notcontains $scheduleName) {
continue
}
if ($destSchedules.Name -contains $scheduleName) {
if ($force -eq $false) {
if ($Pscmdlet.ShouldProcess($destinstance, "Shared job schedule $scheduleName exists at destination. Use -Force to drop and migrate.")) {
$copySharedScheduleStatus.Status = "Skipped"
$copySharedScheduleStatus.Notes = "Already exists"
$copySharedScheduleStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
Write-Message -Level Verbose -Message "Shared job schedule $scheduleName exists at destination. Use -Force to drop and migrate."
continue
}
}
else {
if ($Pscmdlet.ShouldProcess($destinstance, "Schedule [$scheduleName] has associated jobs. Skipping.")) {
if ($destServer.JobServer.Jobs.JobSchedules.Name -contains $scheduleName) {
$copySharedScheduleStatus.Status = "Skipped"
$copySharedScheduleStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
Write-Message -Level Verbose -Message "Schedule [$scheduleName] has associated jobs. Skipping."
}
continue
}
else {
if ($Pscmdlet.ShouldProcess($destinstance, "Dropping schedule $scheduleName and recreating")) {
try {
Write-Message -Level Verbose -Message "Dropping schedule $scheduleName"
$destServer.JobServer.SharedSchedules[$scheduleName].Drop()
}
catch {
$copySharedScheduleStatus.Status = "Failed"
$copySharedScheduleStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
Stop-Function -Message "Issue dropping schedule" -Target $scheduleName -ErrorRecord $_ -Continue
}
}
}
}
}
if ($Pscmdlet.ShouldProcess($destinstance, "Creating schedule $scheduleName")) {
try {
Write-Message -Level Verbose -Message "Copying schedule $scheduleName"
$sql = $schedule.Script() | Out-String
Write-Message -Level Debug -Message $sql
$destServer.Query($sql)
$copySharedScheduleStatus.Status = "Successful"
$copySharedScheduleStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
}
catch {
$copySharedScheduleStatus.Status = "Failed"
$copySharedScheduleStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
Stop-Function -Message "Issue creating schedule" -Target $scheduleName -ErrorRecord $_ -Continue
}
}
}
}
}
end {
Test-DbaDeprecation -DeprecatedOn "1.0.0" -EnableException:$false -Alias Copy-SqlSharedSchedule
}
}
function Copy-DbaBackupDevice {
<#
.SYNOPSIS
Copies backup devices one by one. Copies both SQL code and the backup file itself.
.DESCRIPTION
Backups are migrated using Admin shares. If the destination directory does not exist, SQL Server's default backup directory will be used.
If a backup device with same name exists on destination, it will not be dropped and recreated unless -Force is used.
.PARAMETER Source
Source SQL Server. You must have sysadmin access and server version must be SQL Server version 2000 or higher.
.PARAMETER SourceSqlCredential
Login to the target instance using alternative credentials. Windows and SQL Authentication supported. Accepts credential objects (Get-Credential)
.PARAMETER Destination
Destination SQL Server. You must have sysadmin access and the server must be SQL Server 2000 or higher.
.PARAMETER DestinationSqlCredential
Login to the target instance using alternative credentials. Windows and SQL Authentication supported. Accepts credential objects (Get-Credential)
.PARAMETER BackupDevice
BackupDevice to be copied. Auto-populated list of devices. If not provided all BackupDevice(s) will be copied.
.PARAMETER Force
If this switch is enabled, backup device(s) will be dropped and recreated if they already exists on destination.
.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.
.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.
.NOTES
Tags: Migration, Backup
Author: Chrissy LeMaire (@cl), netnerds.net
Requires: sysadmin access on SQL Servers
Website: https://dbatools.io
Copyright: (C) Chrissy LeMaire, [email protected]
License: MIT https://opensource.org/licenses/MIT
.LINK
https://dbatools.io/Copy-DbaBackupDevice
.EXAMPLE
Copy-DbaBackupDevice -Source sqlserver2014a -Destination sqlcluster
Copies all server backup devices from sqlserver2014a to sqlcluster using Windows credentials. If backup devices with the same name exist on sqlcluster, they will be skipped.
.EXAMPLE
Copy-DbaBackupDevice -Source sqlserver2014a -Destination sqlcluster -BackupDevice backup01 -SourceSqlCredential $cred -Force
Copies only the backup device named backup01 from sqlserver2014a to sqlcluster using SQL credentials for sqlserver2014a and Windows credentials for sqlcluster. If a backup device with the same name exists on sqlcluster, it will be dropped and recreated because -Force was used.
.EXAMPLE
Copy-DbaBackupDevice -Source sqlserver2014a -Destination sqlcluster -WhatIf -Force
Shows what would happen if the command were executed using force.
#>
[CmdletBinding(DefaultParameterSetName = "Default", SupportsShouldProcess = $true)]
param (
[parameter(Mandatory)]
[DbaInstanceParameter]$Source,
[PSCredential]$SourceSqlCredential,
[parameter(Mandatory)]
[DbaInstanceParameter[]]$Destination,
[PSCredential]$DestinationSqlCredential,
[object[]]$BackupDevice,
[switch]$Force,
[Alias('Silent')]
[switch]$EnableException
)
begin {
try {
Write-Message -Level Verbose -Message "Connecting to $Source"
$sourceServer = Connect-SqlInstance -SqlInstance $Source -SqlCredential $SourceSqlCredential
}
catch {
Stop-Function -Message "Failure" -Category ConnectionError -ErrorRecord $_ -Target $Source
return
}
$serverBackupDevices = $sourceServer.BackupDevices
$sourceNetBios = $Source.ComputerName
}
process {
if (Test-FunctionInterrupt) { return }
foreach ($destinstance in $Destination) {
try {
Write-Message -Level Verbose -Message "Connecting to $destinstance"
$destServer = Connect-SqlInstance -SqlInstance $destinstance -SqlCredential $DestinationSqlCredential
}
catch {
Stop-Function -Message "Failure" -Category ConnectionError -ErrorRecord $_ -Target $destinstance -Continue
}
$destBackupDevices = $destServer.BackupDevices
$destNetBios = $destinstance.ComputerName
foreach ($currentBackupDevice in $serverBackupDevices) {
$deviceName = $currentBackupDevice.Name
$copyBackupDeviceStatus = [pscustomobject]@{
SourceServer = $sourceServer.Name
DestinationServer = $destServer.Name
Name = $deviceName
Type = "Backup Device"
Status = $null
Notes = $null
DateTime = [Sqlcollaborative.Dbatools.Utility.DbaDateTime](Get-Date)
}
if ($BackupDevice -and $BackupDevice -notcontains $deviceName) {
continue
}
if ($destBackupDevices.Name -contains $deviceName) {
if ($force -eq $false) {
$copyBackupDeviceStatus.Status = "Skipped"
$copyBackupDeviceStatus.Notes = "Already exists"
$copyBackupDeviceStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
Write-Message -Level Verbose -Message "backup device $deviceName exists at destination. Use -Force to drop and migrate."
continue
}
else {
if ($Pscmdlet.ShouldProcess($destinstance, "Dropping backup device $deviceName")) {
try {
Write-Message -Level Verbose -Message "Dropping backup device $deviceName"
$destServer.BackupDevices[$deviceName].Drop()
}
catch {
$copyBackupDeviceStatus.Status = "Failed"
$copyBackupDeviceStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
Stop-Function -Message "Issue dropping backup device" -Target $deviceName -ErrorRecord $_ -Continue
}
}
}
}
if ($Pscmdlet.ShouldProcess($destinstance, "Generating SQL code for $deviceName")) {
Write-Message -Level Verbose -Message "Scripting out SQL for $deviceName"
try {
$sql = $currentBackupDevice.Script() | Out-String
$sql = $sql -replace [Regex]::Escape("'$source'"), "'$destinstance'"
}
catch {
$copyBackupDeviceStatus.Status = "Failed"
$copyBackupDeviceStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
Stop-Function -Message "Issue scripting out backup device" -Target $deviceName -ErrorRecord $_ -Continue
}
}
if ($Pscmdlet.ShouldProcess("console", "Stating that the actual file copy is about to occur")) {
Write-Message -Level Verbose -Message "Preparing to copy actual backup file"
}
$path = Split-Path $sourceServer.BackupDevices[$deviceName].PhysicalLocation
$destPath = Join-AdminUnc $destNetBios $path
$sourcepath = Join-AdminUnc $sourceNetBios $sourceServer.BackupDevices[$deviceName].PhysicalLocation
Write-Message -Level Verbose -Message "Checking if directory $destPath exists"
if ($(Test-DbaPath -SqlInstance $destinstance -Path $path) -eq $false) {
$backupDirectory = $destServer.BackupDirectory
$destPath = Join-AdminUnc $destNetBios $backupDirectory
if ($Pscmdlet.ShouldProcess($destinstance, "Updating create code to use new path")) {
Write-Message -Level Verbose -Message "$path doesn't exist on $destinstance"
Write-Message -Level Verbose -Message "Using default backup directory $backupDirectory"
try {
Write-Message -Level Verbose -Message "Updating $deviceName to use $backupDirectory"
$sql = $sql -replace [Regex]::Escape($path), $backupDirectory
}
catch {
$copyBackupDeviceStatus.Status = "Failed"
$copyBackupDeviceStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
Stop-Function -Message "Issue updating script of backup device with new path" -Target $deviceName -ErrorRecord $_ -Continue
}
}
}
if ($Pscmdlet.ShouldProcess($destinstance, "Copying $sourcepath to $destPath using BITSTransfer")) {
try {
Start-BitsTransfer -Source $sourcepath -Destination $destPath -ErrorAction Stop
Write-Message -Level Verbose -Message "Backup device $deviceName successfully copied"
}
catch {
$copyBackupDeviceStatus.Status = "Failed"
$copyBackupDeviceStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
Stop-Function -Message "Issue copying backup device to destination" -Target $deviceName -ErrorRecord $_ -Continue
}
}
if ($Pscmdlet.ShouldProcess($destinstance, "Adding backup device $deviceName")) {
Write-Message -Level Verbose -Message "Adding backup device $deviceName on $destinstance"
try {
$destServer.Query($sql)
$destServer.BackupDevices.Refresh()
$copyBackupDeviceStatus.Status = "Successful"
$copyBackupDeviceStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
}
catch {
$copyBackupDeviceStatus.Status = "Failed"
$copyBackupDeviceStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
Stop-Function -Message "Issue adding backup device" -Target $deviceName -ErrorRecord $_ -Continue
}
}
}
}
}
end {
Test-DbaDeprecation -DeprecatedOn "1.0.0" -EnableException:$false -Alias Copy-SqlBackupDevice
}
}
function Copy-DbaCentralManagementServer {
<#
.SYNOPSIS
Migrates SQL Server Central Management groups and server instances from one SQL Server to another.
.DESCRIPTION
Copy-DbaCentralManagementServer copies all groups, subgroups, and server instances from one SQL Server to another.
.PARAMETER Source
Source SQL Server. You must have sysadmin access and server version must be SQL Server version 2000 or higher.
.PARAMETER SourceSqlCredential
Login to the target instance using alternative credentials. Windows and SQL Authentication supported. Accepts credential objects (Get-Credential)
.PARAMETER Destination
Destination SQL Server. You must have sysadmin access and the server must be SQL Server 2000 or higher.
.PARAMETER DestinationSqlCredential
Login to the target instance using alternative credentials. Windows and SQL Authentication supported. Accepts credential objects (Get-Credential)
.PARAMETER CMSGroup
This is an auto-populated array that contains your Central Management Server top-level groups on Source. You can specify one, many or none.
If CMSGroup is not specified, all groups in your Central Management Server will be copied.
.PARAMETER SwitchServerName
If this switch is enabled, all instance names will be changed from Source to Destination.
Central Management Server does not allow you to add a shared registered server with the same name as the Configuration Server.
.PARAMETER Force
If this switch is enabled, group(s) will be dropped and recreated if they already exists on destination.
.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.
.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.
.NOTES
Tags: Migration
Author: Chrissy LeMaire (@cl), netnerds.net
Requires: sysadmin access on SQL Servers
Website: https://dbatools.io
Copyright: (C) Chrissy LeMaire, [email protected]
License: MIT https://opensource.org/licenses/MIT
.LINK
https://dbatools.io/Copy-DbaCentralManagementServer
.EXAMPLE
Copy-DbaCentralManagementServer -Source sqlserver2014a -Destination sqlcluster
All groups, subgroups, and server instances are copied from sqlserver's Central Management Server to sqlcluster's Central Management Server.
.EXAMPLE
Copy-DbaCentralManagementServer -Source sqlserver2014a -Destination sqlcluster -ServerGroup Group1,Group3
Top-level groups Group1 and Group3 along with their subgroups and server instances are copied from sqlserver to sqlcluster.
.EXAMPLE
Copy-DbaCentralManagementServer -Source sqlserver2014a -Destination sqlcluster -ServerGroup Group1,Group3 -SwitchServerName -SourceSqlCredential $SourceSqlCredential -DestinationSqlCredential $DestinationSqlCredential
Top-level groups Group1 and Group3 along with their subgroups and server instances are copied from sqlserver to sqlcluster. When adding sql instances to sqlcluster, if the server name of the migrating instance is "sqlcluster", it will be switched to "sqlserver".
If SwitchServerName is not specified, "sqlcluster" will be skipped.
#>
[CmdletBinding(DefaultParameterSetName = "Default", SupportsShouldProcess = $true)]
Param (
[parameter(Mandatory)]
[DbaInstanceParameter]$Source,
[PSCredential]$SourceSqlCredential,
[parameter(Mandatory)]
[DbaInstanceParameter[]]$Destination,
[PSCredential]$DestinationSqlCredential,
[object[]]$CMSGroup,
[switch]$SwitchServerName,
[switch]$Force,
[Alias('Silent')]
[switch]$EnableException
)
begin {
function Invoke-ParseServerGroup {
[cmdletbinding()]
param (
$sourceGroup,
$destinationGroup,
$SwitchServerName
)
if ($destinationGroup.Name -eq "DatabaseEngineServerGroup" -and $sourceGroup.Name -ne "DatabaseEngineServerGroup") {
$currentServerGroup = $destinationGroup
$groupName = $sourceGroup.Name
$destinationGroup = $destinationGroup.ServerGroups[$groupName]
$copyDestinationGroupStatus = [pscustomobject]@{
SourceServer = $sourceServer.Name
DestinationServer = $destServer.Name
Name = $groupName
Type = "CMS Destination Group"
Status = $null
Notes = $null
DateTime = [Sqlcollaborative.Dbatools.Utility.DbaDateTime](Get-Date)
}
if ($null -ne $destinationGroup) {
if ($force -eq $false) {
if ($Pscmdlet.ShouldProcess($destinstance, "Checking to see if $groupName exists")) {
$copyDestinationGroupStatus.Status = "Skipped"
$copyDestinationGroupStatus.Notes = "Already exists"
$copyDestinationGroupStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
Write-Message -Level Verbose -Message "Destination group $groupName exists at destination. Use -Force to drop and migrate."
}
continue
}
if ($Pscmdlet.ShouldProcess($destinstance, "Dropping group $groupName")) {
try {
Write-Message -Level Verbose -Message "Dropping group $groupName"
$destinationGroup.Drop()
}
catch {
$copyDestinationGroupStatus.Status = "Failed"
$copyDestinationGroupStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
Stop-Function -Message "Issue dropping group" -Target $groupName -ErrorRecord $_ -Continue
}
}
}
if ($Pscmdlet.ShouldProcess($destinstance, "Creating group $groupName")) {
Write-Message -Level Verbose -Message "Creating group $($sourceGroup.Name)"
$destinationGroup = New-Object Microsoft.SqlServer.Management.RegisteredServers.ServerGroup($currentServerGroup, $sourceGroup.Name)
$destinationGroup.Create()
$copyDestinationGroupStatus.Status = "Successful"
$copyDestinationGroupStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
}
}
# Add Servers
foreach ($instance in $sourceGroup.RegisteredServers) {
$instanceName = $instance.Name
$serverName = $instance.ServerName
$copyInstanceStatus = [pscustomobject]@{
SourceServer = $sourceServer.Name
DestinationServer = $destServer.Name
Name = $instanceName
Type = "CMS Instance"
Status = $null
Notes = $null
DateTime = [Sqlcollaborative.Dbatools.Utility.DbaDateTime](Get-Date)
}
if ($serverName.ToLower() -eq $toCmStore.DomainInstanceName.ToLower()) {
if ($Pscmdlet.ShouldProcess($destinstance, "Checking to see if server is the CMS equals current server name")) {
if ($SwitchServerName) {
$serverName = $fromCmStore.DomainInstanceName
$instanceName = $fromCmStore.DomainInstanceName
Write-Message -Level Verbose -Message "SwitchServerName was used and new CMS equals current server name. $($toCmStore.DomainInstanceName.ToLower()) changed to $serverName."
}
else {
$copyInstanceStatus.Status = "Skipped"
$copyInstanceStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
Write-Message -Level Verbose -Message "$serverName is Central Management Server. Add prohibited. Skipping."
continue
}
}
}
if ($destinationGroup.RegisteredServers.Name -contains $instanceName) {
if ($force -eq $false) {
if ($Pscmdlet.ShouldProcess($destinstance, "Checking to see if $instanceName in $groupName exists")) {
$copyInstanceStatus.Status = "Skipped"
$copyInstanceStatus.Notes = "Already exists"
$copyInstanceStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
Write-Message -Level Verbose -Message "Instance $instanceName exists in group $groupName at destination. Use -Force to drop and migrate."
}
continue
}
if ($Pscmdlet.ShouldProcess($destinstance, "Dropping instance $instanceName from $groupName and recreating")) {
try {
Write-Message -Level Verbose -Message "Dropping instance $instance from $groupName"
$destinationGroup.RegisteredServers[$instanceName].Drop()
}
catch {
$copyInstanceStatus.Status = "Failed"
$copyInstanceStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
Stop-Function -Message "Issue dropping instance from group" -Target $instanceName -ErrorRecord $_ -Continue
}
}
}
if ($Pscmdlet.ShouldProcess($destinstance, "Copying $instanceName")) {
$newServer = New-Object Microsoft.SqlServer.Management.RegisteredServers.RegisteredServer($destinationGroup, $instanceName)
$newServer.ServerName = $serverName
$newServer.Description = $instance.Description
if ($serverName -ne $fromCmStore.DomainInstanceName) {
$newServer.SecureConnectionString = $instance.SecureConnectionString.ToString()
$newServer.ConnectionString = $instance.ConnectionString.ToString()
}
try {
$newServer.Create()
$copyInstanceStatus.Status = "Successful"
$copyInstanceStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
}
catch {
$copyInstanceStatus.Status = "Failed"
$copyInstanceStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
if ($_.Exception -match "same name") {
Stop-Function -Message "Could not add Switched Server instance name." -Target $instanceName -ErrorRecord $_ -Continue
}
else {
Stop-Function -Message "Failed to add $serverName" -Target $instanceName -ErrorRecord $_ -Continue
}
}
Write-Message -Level Verbose -Message "Added Server $serverName as $instanceName to $($destinationGroup.Name)"
}
}
# Add Groups
foreach ($fromSubGroup in $sourceGroup.ServerGroups) {
$fromSubGroupName = $fromSubGroup.Name
$toSubGroup = $destinationGroup.ServerGroups[$fromSubGroupName]
$copyGroupStatus = [pscustomobject]@{
SourceServer = $sourceServer.Name
DestinationServer = $destServer.Name
Name = $fromSubGroupName
Type = "CMS Group"
Status = $null
Notes = $null
DateTime = [Sqlcollaborative.Dbatools.Utility.DbaDateTime](Get-Date)
}
if ($null -ne $toSubGroup) {
if ($force -eq $false) {
if ($Pscmdlet.ShouldProcess($destinstance, "Checking to see if subgroup $fromSubGroupName exists")) {
$copyGroupStatus.Status = "Skipped"
$copyGroupStatus.Notes = "Already exists"
$copyGroupStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
Write-Message -Level Verbose -Message "Subgroup $fromSubGroupName exists at destination. Use -Force to drop and migrate."
}
continue
}
if ($Pscmdlet.ShouldProcess($destinstance, "Dropping subgroup $fromSubGroupName recreating")) {
try {
Write-Message -Level Verbose -Message "Dropping subgroup $fromSubGroupName"
$toSubGroup.Drop()
}
catch {
$copyGroupStatus.Status = "Failed"
$copyGroupStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
Stop-Function -Message "Issue dropping subgroup" -Target $toSubGroup -ErrorRecord $_ -Continue
}
}
}
if ($Pscmdlet.ShouldProcess($destinstance, "Creating group $($fromSubGroup.Name)")) {
Write-Message -Level Verbose -Message "Creating group $($fromSubGroup.Name)"
$toSubGroup = New-Object Microsoft.SqlServer.Management.RegisteredServers.ServerGroup($destinationGroup, $fromSubGroup.Name)
$toSubGroup.create()
$copyGroupStatus.Status = "Successful"
$copyGroupStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
}
Invoke-ParseServerGroup -sourceGroup $fromSubGroup -destinationgroup $toSubGroup -SwitchServerName $SwitchServerName
}
}
try {
Write-Message -Level Verbose -Message "Connecting to $Source"
$sourceServer = Connect-SqlInstance -SqlInstance $Source -SqlCredential $SourceSqlCredential -MinimumVersion 10
$fromCmStore = Get-DbaCmsRegServerStore -SqlInstance $sourceServer
}
catch {
Stop-Function -Message "Failure" -Category ConnectionError -ErrorRecord $_ -Target $Source
return
}
}
process {
if (Test-FunctionInterrupt) { return }
foreach ($destinstance in $Destination) {
try {
Write-Message -Level Verbose -Message "Connecting to $destinstance"
$destServer = Connect-SqlInstance -SqlInstance $destinstance -SqlCredential $DestinationSqlCredential -MinimumVersion 10
}
catch {
Stop-Function -Message "Failure" -Category ConnectionError -ErrorRecord $_ -Target $destinstance -Continue
}
$toCmStore = Get-DbaCmsRegServerStore -SqlInstance $destServer
$stores = $fromCmStore.DatabaseEngineServerGroup
if ($CMSGroup) {
$stores = @();
foreach ($groupName in $CMSGroup) {
$stores += $fromCmStore.DatabaseEngineServerGroup.ServerGroups[$groupName]
}
}
foreach ($store in $stores) {
Invoke-ParseServerGroup -sourceGroup $store -destinationgroup $toCmStore.DatabaseEngineServerGroup -SwitchServerName $SwitchServerName
}
}
}
end {
Test-DbaDeprecation -DeprecatedOn "1.0.0" -EnableException:$false -Alias Copy-SqlCentralManagementServer
}
}
function Copy-DbaCredential {
<#
.SYNOPSIS
Copy-DbaCredential migrates SQL Server Credentials from one SQL Server to another while maintaining Credential passwords.
.DESCRIPTION
By using password decryption techniques provided by Antti Rantasaari (NetSPI, 2014), this script migrates SQL Server Credentials from one server to another while maintaining username and password.
Credit: https://blog.netspi.com/decrypting-mssql-database-link-server-passwords/
Website: https://dbatools.io
Copyright: (C) Chrissy LeMaire, [email protected]
License: MIT https://opensource.org/licenses/MIT
.PARAMETER Source
Source SQL Server. You must have sysadmin access and server version must be SQL Server version 2000 or higher.
.PARAMETER SourceSqlCredential
Login to the target instance using alternative credentials. Windows and SQL Authentication supported. Accepts credential objects (Get-Credential)
.PARAMETER Destination
Destination SQL Server. You must have sysadmin access and the server must be SQL Server 2000 or higher.
.PARAMETER DestinationSqlCredential
Login to the target instance using alternative credentials. Windows and SQL Authentication supported. Accepts credential objects (Get-Credential)
.PARAMETER Credential
This command requires access to the Windows OS via PowerShell remoting. Use this credential to connect to Windows using alternative credentials.
.PARAMETER Name
Only include specific names
Note: if spaces exist in the credential name, you will have to type "" or '' around it.
.PARAMETER ExcludeName
Excluded credential names
.PARAMETER Identity
Only include specific identities
Note: if spaces exist in the credential identity, you will have to type "" or '' around it.
.PARAMETER ExcludeIdentity
Excluded identities
.PARAMETER Force
If this switch is enabled, the Credential will be dropped and recreated if it already exists on Destination.
.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.
.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.
.NOTES
Tags: WSMan, Migration
Author: Chrissy LeMaire (@cl), netnerds.net
Requires:
- PowerShell Version 3.0, SQL Server SMO,
- Administrator access on Windows
- sysadmin access on SQL Server.
- DAC access enabled for local (default)
Limitations: Hasn't been tested thoroughly. Works on Win8.1 and SQL Server 2012 & 2014 so far.
Website: https://dbatools.io
Copyright: (C) Chrissy LeMaire, [email protected]
License: MIT https://opensource.org/licenses/MIT
.LINK
https://dbatools.io/Copy-DbaCredential
.EXAMPLE
Copy-DbaCredential -Source sqlserver2014a -Destination sqlcluster
Copies all SQL Server Credentials on sqlserver2014a to sqlcluster. If Credentials exist on destination, they will be skipped.
.EXAMPLE
Copy-DbaCredential -Source sqlserver2014a -Destination sqlcluster -Name "PowerShell Proxy Account" -Force
Copies over one SQL Server Credential (PowerShell Proxy Account) from sqlserver to sqlcluster. If the Credential already exists on the destination, it will be dropped and recreated.
#>
[CmdletBinding(SupportsShouldProcess)]
param (
[parameter(Mandatory)]
[DbaInstanceParameter]$Source,
[PSCredential]
$SourceSqlCredential,
[PSCredential]
$Credential,
[parameter(Mandatory)]
[DbaInstanceParameter[]]$Destination,
[PSCredential]$DestinationSqlCredential,
[string[]]$Name,
[string[]]$ExcludeName,
[Alias('CredentialIdentity')]
[string[]]$Identity,
[Alias('ExcludeCredentialIdentity')]
[string[]]$ExcludeIdentity,
[switch]$Force,
[Alias('Silent')]
[switch]$EnableException
)
begin {
$null = Test-ElevationRequirement -ComputerName $Source.ComputerName
function Copy-Credential {
<#
.SYNOPSIS
Copies Credentials from one server to another using a combination of SMO's .Script() and manual password updates.
.OUTPUT
System.Data.DataTable
#>
param (
[string[]]$Credentials,
[bool]$Force
)
Write-Message -Level Verbose -Message "Collecting Credential logins and passwords on $($sourceServer.Name)"
$sourceCredentials = Get-DecryptedObject -SqlInstance $sourceServer -Type Credential
$credentialList = Get-DbaCredential -SqlInstance $sourceServer -Name $Name -ExcludeName $ExcludeName -Identity $Identity -ExcludeIdentity $ExcludeIdentity
Write-Message -Level Verbose -Message "Starting migration"
foreach ($credential in $credentialList) {
$destServer.Credentials.Refresh()
$credentialName = $credential.Name
$copyCredentialStatus = [pscustomobject]@{
SourceServer = $sourceServer.Name
DestinationServer = $destServer.Name
Type = "Credential"
Name = $credentialName
Status = $null
Notes = $null
DateTime = [DbaDateTime](Get-Date)
}
if ($null -ne $destServer.Credentials[$credentialName]) {
if (!$force) {
$copyCredentialStatus.Status = "Skipping"
$copyCredentialStatus.Notes = "Already exists"
$copyCredentialStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
Write-Message -Level Verbose -Message "$credentialName exists $($destServer.Name). Skipping."
continue
}
else {
if ($Pscmdlet.ShouldProcess($destinstance.Name, "Dropping $identity")) {
$destServer.Credentials[$credentialName].Drop()
$destServer.Credentials.Refresh()
}
}
}
Write-Message -Level Verbose -Message "Attempting to migrate $credentialName"
try {
$currentCred = $sourceCredentials | Where-Object { $_.Name -eq "[$credentialName]" }
$sqlcredentialName = $credentialName.Replace("'", "''")
$identity = $currentCred.Identity.Replace("'", "''")
$password = $currentCred.Password.Replace("'","''")
if ($Pscmdlet.ShouldProcess($destinstance.Name, "Copying $identity")) {
$destServer.Query("CREATE CREDENTIAL [$sqlcredentialName] WITH IDENTITY = N'$identity', SECRET = N'$password'")
$destServer.Credentials.Refresh()
Write-Message -Level Verbose -Message "$credentialName successfully copied"
}
$copyCredentialStatus.Status = "Successful"
$copyCredentialStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
}
catch {
$copyCredentialStatus.Status = "Failed"
$copyCredentialStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
Stop-Function -Message "Error creating credential" -Target $credentialName -ErrorRecord $_
}
}
}
try {
Write-Message -Level Verbose -Message "Connecting to $Source"
$sourceServer = Connect-SqlInstance -SqlInstance $Source -SqlCredential $SourceSqlCredential -MinimumVersion 9
}
catch {
Stop-Function -Message "Failure" -Category ConnectionError -ErrorRecord $_ -Target $instance
return
}
if ($null -ne $SourceSqlCredential.Username) {
Write-Message -Level Verbose -Message "You are using SQL credentials and this script requires Windows admin access to the $Source server. Trying anyway."
}
$sourceNetBios = Resolve-NetBiosName $sourceServer
Invoke-SmoCheck -SqlInstance $sourceServer
Write-Message -Level Verbose -Message "Checking if Remote Registry is enabled on $source"
try {
Invoke-Command2 -ComputerName $sourceNetBios -Credential $credential -ScriptBlock { Get-ItemProperty -Path "HKLM:\SOFTWARE\" }
}
catch {
Stop-Function -Message "Can't connect to registry on $source" -Target $sourceNetBios -ErrorRecord $_
return
}
}
process {
if (Test-FunctionInterrupt) { return }
foreach ($destinstance in $Destination) {
try {
Write-Message -Level Verbose -Message "Connecting to $destinstance"
$destServer = Connect-SqlInstance -SqlInstance $destinstance -SqlCredential $DestinationSqlCredential -MinimumVersion 9
}
catch {
Stop-Function -Message "Failure" -Category ConnectionError -ErrorRecord $_ -Target $destinstance -Continue
}
Invoke-SmoCheck -SqlInstance $destServer
Copy-Credential $credentials -force:$force
}
}
end {
Test-DbaDeprecation -DeprecatedOn "1.0.0" -EnableException:$false -Alias Copy-SqlCredential
}
}
function Copy-DbaCustomError {
<#
.SYNOPSIS
Copy-DbaCustomError migrates custom errors (user defined messages), by the custom error ID, from one SQL Server to another.
.DESCRIPTION
By default, all custom errors are copied. The -CustomError parameter is auto-populated for command-line completion and can be used to copy only specific custom errors.
If the custom error already exists on the destination, it will be skipped unless -Force is used. The us_english version must be created first. If you drop the us_english version, all the other languages will be dropped for that specific ID as well.
.PARAMETER Source
Source SQL Server. You must have sysadmin access and server version must be SQL Server version 2000 or higher.
.PARAMETER SourceSqlCredential
Login to the target instance using alternative credentials. Windows and SQL Authentication supported. Accepts credential objects (Get-Credential)
.PARAMETER Destination
Destination SQL Server. You must have sysadmin access and the server must be SQL Server 2000 or higher.
.PARAMETER DestinationSqlCredential
Login to the target instance using alternative credentials. Windows and SQL Authentication supported. Accepts credential objects (Get-Credential)
.PARAMETER CustomError
The custom error(s) to process. This list is auto-populated from the server. If unspecified, all custom errors will be processed.
.PARAMETER ExcludeCustomError
The custom error(s) to exclude. This list is auto-populated from the server.
.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.
.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 Force
If this switch is enabled, the custom error will be dropped and recreated if it already exists on Destination.
.NOTES
Tags: Migration, CustomError
Author: Chrissy LeMaire (@cl), netnerds.net
Requires: sysadmin access on SQL Servers
Website: https://dbatools.io
Copyright: (C) Chrissy LeMaire, [email protected]
License: MIT https://opensource.org/licenses/MIT
.LINK
https://dbatools.io/Copy-DbaCustomError
.EXAMPLE
Copy-DbaCustomError -Source sqlserver2014a -Destination sqlcluster
Copies all server custom errors from sqlserver2014a to sqlcluster using Windows credentials. If custom errors with the same name exist on sqlcluster, they will be skipped.
.EXAMPLE
Copy-DbaCustomError -Source sqlserver2014a -SourceSqlCredential $scred -Destination sqlcluster -DestinationSqlCredential $dcred -CustomError 60000 -Force
Copies only the custom error with ID number 60000 from sqlserver2014a to sqlcluster using SQL credentials for sqlserver2014a and Windows credentials for sqlcluster. If a custom error with the same name exists on sqlcluster, it will be updated because -Force was used.
.EXAMPLE
Copy-DbaCustomError -Source sqlserver2014a -Destination sqlcluster -ExcludeCustomError 60000 -Force
Copies all the custom errors found on sqlserver2014a except the custom error with ID number 60000 to sqlcluster. If a custom error with the same name exists on sqlcluster, it will be updated because -Force was used.
.EXAMPLE
Copy-DbaCustomError -Source sqlserver2014a -Destination sqlcluster -WhatIf -Force
Shows what would happen if the command were executed using force.
#>
[CmdletBinding(DefaultParameterSetName = "Default", SupportsShouldProcess = $true)]
param (
[parameter(Mandatory)]
[DbaInstanceParameter]$Source,
[PSCredential]
$SourceSqlCredential,
[parameter(Mandatory)]
[DbaInstanceParameter[]]$Destination,
[PSCredential]
$DestinationSqlCredential,
[object[]]$CustomError,
[object[]]$ExcludeCustomError,
[switch]$Force,
[Alias('Silent')]
[switch]$EnableException
)
begin {
try {
Write-Message -Level Verbose -Message "Connecting to $Source"
$sourceServer = Connect-SqlInstance -SqlInstance $Source -SqlCredential $SourceSqlCredential -MinimumVersion 9
}
catch {
Stop-Function -Message "Failure" -Category ConnectionError -ErrorRecord $_ -Target $Source
return
}
$orderedCustomErrors = @($sourceServer.UserDefinedMessages | Where-Object Language -eq "us_english")
$orderedCustomErrors += $sourceServer.UserDefinedMessages | Where-Object Language -ne "us_english"
}
process {
if (Test-FunctionInterrupt) { return }
foreach ($destinstance in $Destination) {
try {
Write-Message -Level Verbose -Message "Connecting to $destinstance"
$destServer = Connect-SqlInstance -SqlInstance $destinstance -SqlCredential $DestinationSqlCredential -MinimumVersion 9
}
catch {
Stop-Function -Message "Failure" -Category ConnectionError -ErrorRecord $_ -Target $destinstance -Continue
}
# US has to go first
$destCustomErrors = $destServer.UserDefinedMessages
foreach ($currentCustomError in $orderedCustomErrors) {
$customErrorId = $currentCustomError.ID
$language = $currentCustomError.Language.ToString()
$copyCustomErrorStatus = [pscustomobject]@{
SourceServer = $sourceServer.Name
DestinationServer = $destServer.Name
Type = "Custom error"
Name = $currentCustomError
Status = $null
Notes = $null
DateTime = [DbaDateTime](Get-Date)
}
if ($CustomError -and ($customErrorId -notin $CustomError -or $customErrorId -in $ExcludeCustomError)) {
continue
}
if ($destCustomErrors.ID -contains $customErrorId) {
if ($force -eq $false) {
$copyCustomErrorStatus.Status = "Skipped"
$copyCustomErrorStatus.Notes = "Already exists"
$copyCustomErrorStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
Write-Message -Level Verbose -Message "Custom error $customErrorId $language exists at destination. Use -Force to drop and migrate."
continue
}
else {
If ($Pscmdlet.ShouldProcess($destinstance, "Dropping custom error $customErrorId $language and recreating")) {
try {
Write-Message -Level Verbose -Message "Dropping custom error $customErrorId (drops all languages for custom error $customErrorId)"
$destServer.UserDefinedMessages[$customErrorId, $language].Drop()
}
catch {
$copyCustomErrorStatus.Status = "Failed"
$copyCustomErrorStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
Stop-Function -Message "Issue dropping custom error" -Target $customErrorId -ErrorRecord $_ -Continue
}
}
}
}
if ($Pscmdlet.ShouldProcess($destinstance, "Creating custom error $customErrorId $language")) {
try {
Write-Message -Level Verbose -Message "Copying custom error $customErrorId $language"
$sql = $currentCustomError.Script() | Out-String
Write-Message -Level Debug -Message $sql
$destServer.Query($sql)
$copyCustomErrorStatus.Status = "Successful"
$copyCustomErrorStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
}
catch {
$copyCustomErrorStatus.Status = "Failed"
$copyCustomErrorStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
Stop-Function -Message "Issue creating custom error" -Target $customErrorId -ErrorRecord $_
}
}
}
}
}
end {
Test-DbaDeprecation -DeprecatedOn "1.0.0" -EnableException:$false -Alias Copy-SqlCustomError
}
}
function Copy-DbaDatabase {
<#
.SYNOPSIS
Migrates SQL Server databases from one SQL Server to another.
.DESCRIPTION
This script provides the ability to migrate databases using detach/copy/attach or backup/restore. This script works with named instances, clusters and SQL Server Express Edition.
By default, databases will be migrated to the destination SQL Server's default data and log directories. You can override this by specifying -ReuseSourceFolderStructure. Filestreams and filegroups are also migrated. Safety is emphasized.
.PARAMETER Source
Source SQL Server.
.PARAMETER SourceSqlCredential
Login to the target instance using alternative credentials. Windows and SQL Authentication supported. Accepts credential objects (Get-Credential)
.PARAMETER Destination
Destination SQL Server. You may specify multiple servers.
Note that when using -BackupRestore with multiple servers, the backup will only be performed once and backups will be deleted at the end (if you didn't specify -NoBackupCleanup).
When using -DetachAttach with multiple servers, -Reattach must be specified.
.PARAMETER DestinationSqlCredential
Login to the target instance using alternative credentials. Windows and SQL Authentication supported. Accepts credential objects (Get-Credential)
.PARAMETER Database
Migrates only specified databases. This list is auto-populated from the server for tab completion. Multiple databases may be specified as a collection.
.PARAMETER ExcludeDatabase
Excludes specified databases when performing -AllDatabases migrations. This list is auto-populated from the Source for tab completion.
.PARAMETER AllDatabases
If this switch is enabled, all user databases will be migrated. System and support databases will not be migrated. Requires -BackupRestore or -DetachAttach.
.PARAMETER BackupRestore
If this switch is enabled, the copy-only backup and restore method will be used to migrate the database(s). This method requires that you specify -NetworkShare in a valid UNC format (\\server\share).
Backups will be immediately deleted after use unless -NoBackupCleanup is specified.
.PARAMETER NetworkShare
Specifies the network location for the backup files. The SQL Server service accounts must have read/write permission on this path.
.PARAMETER WithReplace
If this switch is enabled, the restore is executed with WITH REPLACE.
.PARAMETER NoRecovery
If this switch is enabled, the restore is executed with WITH NORECOVERY. Ideal for staging.
.PARAMETER NoBackupCleanup
If this switch is enabled, backups generated by this cmdlet will not be deleted after they are restored. The default behavior is to delete these backups.
.PARAMETER NumberFiles
Number of files to split the backup. Default is 3.
.PARAMETER DetachAttach
If this switch is enabled, the detach/copy/attach method is used to perform database migrations. No files are deleted on Source. If Destination attachment fails, the Source database will be reattached. File copies are performed over administrative shares (\\server\x$\mssql) using BITS. If a database is being mirrored, the mirror will be broken prior to migration.
.PARAMETER Reattach
If this switch is enabled, all databases are reattached to Source after DetachAttach migration.
.PARAMETER SetSourceReadOnly
If this switch is enabled, all migrated databases are set to ReadOnly on Source prior to detach/attach & backup/restore.
If -Reattach is used, databases are set to read-only after reattaching.
.PARAMETER ReuseSourceFolderStructure
If this switch is enabled, databases will be migrated to a data and log directory structure on Destination mirroring that used on Source. By default, the default data and log directories for Destination will be used when the databases are migrated.
The structure on Source will be kept exactly, so consider this if you're migrating between different versions and use part of Microsoft's default Sql structure (MSSql12.INSTANCE, etc)
To reuse Destination folder structure, use the -WithReplace switch.
.PARAMETER IncludeSupportDbs
If this switch is enabled, ReportServer, ReportServerTempDb, SSISDB, and distribution databases will be copied if they exist on Source. A log file named $SOURCE-$destinstance-$date-Sqls.csv will be written to the current directory.
Use of this switch requires -BackupRestore or -DetachAttach as well.
.PARAMETER InputObject
A collection of dbobjects from the pipeline.
.PARAMETER UseLastBackups
Use the last full, diff and logs instead of performing backups. Note that the backups must exist in a location accessible by all destination servers, such a network share.
.PARAMETER Continue
If specified, will to attempt to restore transaction log backups on top of existing database(s) in Recovering or Standby states. Only usable with -UseLastBackups
.PARAMETER NoCopyOnly
If this switch is enabled, backups will be taken without COPY_ONLY. This will break the LSN backup chain, which will interfere with the restore chain of the database.
By default this switch is disabled, so backups will be taken with COPY_ONLY. This will preserve the LSN backup chain.
For more details please refer to this MSDN article - https://msdn.microsoft.com/en-us/library/ms191495.aspx
.PARAMETER NewName
If a single database is being copied, this will be used to rename the database during the copy process. Any occurence of the original database name in the physical file names will be replaced with newname
If specified with multiple databases a warning will be raised and the copy stopped
This option is mutually exclusive of Prefix
.PARAMETER Prefix
All copied database names and physical files will be prefixed with this string
This option is mutually exclusive of NewName
.PARAMETER SetSourceOffline
If this switch is enabled, the Source database will be set to Offline after being copied.
.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.
.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 Force
If this switch is enabled, existing databases on Destination with matching names from Source will be dropped. If using -DetachReattach, mirrors will be broken and the database(s) dropped from Availability Groups.
.NOTES
Tags: Migration, Backup, Restore
Author: Chrissy LeMaire (@cl), netnerds.net
Requires: sysadmin access on SQL Servers
Limitations: Doesn't cover what it doesn't cover (replication, certificates, etc)
SQL Server 2000 databases cannot be directly migrated to SQL Server 2012 and above.
Logins within SQL Server 2012 and above logins cannot be migrated to SQL Server 2008 R2 and below.
Website: https://dbatools.io
Copyright: (C) Chrissy LeMaire, [email protected]
License: MIT https://opensource.org/licenses/MIT
.LINK
https://dbatools.io/Copy-DbaDatabase
.EXAMPLE
Copy-DbaDatabase -Source sql2014a -Destination sql2014b -Database TestDB -BackupRestore -NetworkShare \\fileshare\sql\migration
Migrates a single user database TestDB using Backup and restore from instance sql2014a to sql2014b. Backup files are stored in \\fileshare\sql\migration.
.EXAMPLE
Copy-DbaDatabase -Source sql2012 -Destination sql2014, sql2016 -DetachAttach -Reattach
Databases will be migrated from sql2012 to both sql2014 and sql2016 using the detach/copy files/attach method.The following will be performed: kick all users out of the database, detach all data/log files, move files across the network over an admin share (\\SqlSERVER\M$\MSSql...), attach file on destination server, reattach at source. If the database files (*.mdf, *.ndf, *.ldf) on *destination* exist and aren't in use, they will be overwritten.
.EXAMPLE
Copy-DbaDatabase -Source sql2014a -Destination sqlcluster, sql2016 -BackupRestore -UseLastBackups -Force
Migrates all user databases to sqlcluster and sql2016 using the last Full, Diff and Log backups from sql204a. If the databases exists on the destinations, they will be dropped prior to attach.
Note that the backups must exist in a location accessible by all destination servers, such a network share.
.EXAMPLE
Copy-DbaDatabase -Source sql2014a -Destination sqlcluster -ExcludeDatabase Northwind, pubs -IncludeSupportDbs -Force -BackupRestore -NetworkShare \\fileshare\sql\migration
Migrates all user databases except for Northwind and pubs by using backup/restore (copy-only). Backup files are stored in \\fileshare\sql\migration. If the database exists on the destination, it will be dropped prior to attach.
It also includes the support databases (ReportServer, ReportServerTempDb, distribution).
#>
[CmdletBinding(DefaultParameterSetName = "DbBackup", SupportsShouldProcess = $true)]
param (
[parameter(Mandatory = $false)]
[DbaInstanceParameter]$Source,
[PSCredential]$SourceSqlCredential,
[parameter(Mandatory)]
[DbaInstanceParameter[]]$Destination,
[PSCredential]$DestinationSqlCredential,
[Alias("Databases")]
[object[]]$Database,
[object[]]$ExcludeDatabase,
[Alias("All")]
[parameter(ParameterSetName = "DbBackup")]
[parameter(ParameterSetName = "DbAttachDetach")]
[switch]$AllDatabases,
[parameter(Mandatory, ParameterSetName = "DbBackup")]
[switch]$BackupRestore,
[parameter(ParameterSetName = "DbBackup",
HelpMessage = "Specify a valid network share in the format \\server\share that can be accessed by your account and the SQL Server service accounts for both Source and Destination.")]
[string]$NetworkShare,
[parameter(ParameterSetName = "DbBackup")]
[switch]$WithReplace,
[parameter(ParameterSetName = "DbBackup")]
[switch]$NoRecovery,
[parameter(ParameterSetName = "DbBackup")]
[switch]$NoBackupCleanup,
[parameter(ParameterSetName = "DbBackup")]
[ValidateRange(1, 64)]
[int]$NumberFiles = 3,
[parameter(Mandatory, ParameterSetName = "DbAttachDetach")]
[switch]$DetachAttach,
[parameter(ParameterSetName = "DbAttachDetach")]
[switch]$Reattach,
[parameter(ParameterSetName = "DbBackup")]
[parameter(ParameterSetName = "DbAttachDetach")]
[switch]$SetSourceReadOnly,
[Alias("ReuseFolderStructure")]
[parameter(ParameterSetName = "DbBackup")]
[parameter(ParameterSetName = "DbAttachDetach")]
[switch]$ReuseSourceFolderStructure,
[parameter(ParameterSetName = "DbBackup")]
[parameter(ParameterSetName = "DbAttachDetach")]
[switch]$IncludeSupportDbs,
[parameter(ParameterSetName = "DbBackup")]
[switch]$UseLastBackups,
[parameter(ParameterSetName = "DbBackup")]
[switch]$Continue,
[parameter(ValueFromPipeline)]
[Microsoft.SqlServer.Management.Smo.Database[]]$InputObject,
[switch]$NoCopyOnly,
[switch]$SetSourceOffline,
[string]$NewName,
[string]$Prefix,
[switch]$Force,
[Alias('Silent')]
[switch]$EnableException
)
begin {
$CopyOnly = -not $NoCopyOnly
if ($BackupRestore -and (-not $NetworkShare -and -not $UseLastBackups)) {
Stop-Function -Message "When using -BackupRestore, you must specify -NetworkShare or -UseLastBackups"
return
}
if ($NetworkShare -and $UseLastBackups) {
Stop-Function -Message "-NetworkShare cannot be used with -UseLastBackups because the backup path is determined by the paths in the last backups"
return
}
if ($DetachAttach -and -not $Reattach -and $Destination.Count -gt 1) {
Stop-Function -Message "When using -DetachAttach with multiple servers, you must specify -Reattach to reattach database at source"
return
}
if ($Continue -and -not $UseLastBackups) {
Stop-Function -Message "-Continue cannot be used without -UseLastBackups"
return
}
if ($null -ne $NewName -or $null -ne $Prefix){
$ReplaceDbNameInFile = $true
}
else {
$ReplaceDbNameInFile = $false
}
function Join-Path {
<#
An internal command that does not require the local path to exist
Boo, this does not work, but keeping it for future ref.
#>
[CmdletBinding()]
param (
[string]$Path,
[string]$ChildPath
)
process {
[IO.Path]::Combine($Path, $ChildPath)
}
}
function Join-AdminUnc {
<#
.SYNOPSIS
Internal function. Parses a path to make it an admin UNC.
#>
[CmdletBinding()]
param (
[Parameter(Mandatory)]
[ValidateNotNullOrEmpty()]
[string]$servername,
[Parameter(Mandatory)]
[ValidateNotNullOrEmpty()]
[string]$filepath
)
if ($script:sameserver) { return $filepath }
if (-not $filepath) { return }
if ($filepath.StartsWith("\\")) { return $filepath }
$servername = $servername.Split("\")[0]
if ($filepath.length -gt 0 -and $filepath -ne [System.DbNull]::Value) {
$newpath = Join-Path "\\$servername\" $filepath.replace(':', '$')
return $newpath
}
else { return }
}
function Get-SqlFileStructure {
$dbcollection = @{ };
$databaseProgressbar = 0
foreach ($db in $databaseList) {
Write-Progress -Id 1 -Activity "Processing database file structure" -PercentComplete ($databaseProgressbar / $dbCount * 100) -Status "Processing $databaseProgressbar of $dbCount."
$dbName = $db.Name
Write-Message -Level Verbose -Message $dbName
$databaseProgressbar++
$dbStatus = $db.status.toString()
if ($dbStatus.StartsWith("Normal") -eq $false) { continue }
$destinstancefiles = @{ }; $sourcefiles = @{ }
$where = "Filetype <> 'LOG' and Filetype <> 'FULLTEXT'"
$datarows = $dbFileTable.Tables.Select("dbname = '$dbName' and $where")
# Data Files
foreach ($file in $datarows) {
# Destination File Structure
$d = @{ }
if ($ReuseSourceFolderStructure) {
$d.physical = $file.filename
}
elseif ($WithReplace) {
$name = $file.Name
$destfile = $remoteDbFileTable.Tables[0].Select("dbname = '$dbName' and name = '$name'")
$d.physical = $destfile.filename
if ($null -eq $d.physical) {
$directory = Get-SqlDefaultPaths $destServer data
$fileName = Split-Path $file.filename -Leaf
$d.physical = "$directory\$fileName"
}
}
else {
$directory = Get-SqlDefaultPaths $destServer data
$fileName = Split-Path $file.filename -Leaf
$d.physical = "$directory\$fileName"
}
$d.logical = $file.Name
$d.remotefilename = Join-AdminUNC $destNetBios $d.physical
$destinstancefiles.add($file.Name, $d)
# Source File Structure
$s = @{ }
$s.logical = $file.Name
$s.physical = $file.filename
$s.remotefilename = Join-AdminUNC $sourceNetBios $s.physical
$sourcefiles.add($file.Name, $s)
}
# Add support for Full Text Catalogs in SQL Server 2005 and below
if ($sourceServer.VersionMajor -lt 10) {
try {
$fttable = $null = $sourceServer.Databases[$dbName].ExecuteWithResults('sp_help_fulltext_catalogs')
$allrows = $fttable.Tables[0].rows
}
catch {
# Nothing, it's just not enabled
}
foreach ($ftc in $allrows) {
# Destination File Structure
$d = @{ }
$pre = "sysft_"
$name = $ftc.Name
$physical = $ftc.Path # RootPath
$logical = "$pre$name"
if ($ReuseSourceFolderStructure) {
$d.physical = $physical
}
else {
$directory = Get-SqlDefaultPaths $destServer data
if ($destServer.VersionMajor -lt 10) { $directory = "$directory\FTDATA" }
$fileName = Split-Path($physical) -leaf
$d.physical = "$directory\$fileName"
}
$d.logical = $logical
$d.remotefilename = Join-AdminUNC $destNetBios $d.physical
$destinstancefiles.add($logical, $d)
# Source File Structure
$s = @{ }
$pre = "sysft_"
$name = $ftc.Name
$physical = $ftc.Path # RootPath
$logical = "$pre$name"
$s.logical = $logical
$s.physical = $physical
$s.remotefilename = Join-AdminUNC $sourceNetBios $s.physical
$sourcefiles.add($logical, $s)
}
}
$where = "Filetype = 'LOG'"
$datarows = $dbFileTable.Tables[0].Select("dbname = '$dbName' and $where")
# Log Files
foreach ($file in $datarows) {
$d = @{ }
if ($ReuseSourceFolderStructure) {
$d.physical = $file.filename
}
elseif ($WithReplace) {
$name = $file.Name
$destfile = $remoteDbFileTable.Tables[0].Select("dbname = '$dbName' and name = '$name'")
$d.physical = $destfile.filename
if ($null -eq $d.physical) {
$directory = Get-SqlDefaultPaths $destServer data
$fileName = Split-Path $file.filename -Leaf
$d.physical = "$directory\$fileName"
}
}
else {
$directory = Get-SqlDefaultPaths $destServer log
$fileName = Split-Path $file.filename -Leaf
$d.physical = "$directory\$fileName"
}
$d.logical = $file.Name
$d.remotefilename = Join-AdminUNC $destNetBios $d.physical
$destinstancefiles.add($file.Name, $d)
$s = @{ }
$s.logical = $file.Name
$s.physical = $file.filename
$s.remotefilename = Join-AdminUNC $sourceNetBios $s.physical
$sourcefiles.add($file.Name, $s)
}
$location = @{ }
$location.add("Destination", $destinstancefiles)
$location.add("Source", $sourcefiles)
$dbcollection.Add($($db.Name), $location)
}
$fileStructure = [pscustomobject]@{ "databases" = $dbcollection }
Write-Progress -id 1 -Activity "Processing database file structure" -Status "Completed" -Completed
return $fileStructure
}
function Dismount-SqlDatabase {
[CmdletBinding()]
param (
[object]$server,
[string]$dbName
)
$currentdb = $server.databases[$dbName]
if ($currentdb.IsMirroringEnabled) {
try {
Write-Message -Level Verbose -Message "Breaking mirror for $dbName"
$currentdb.ChangeMirroringState([Microsoft.SqlServer.Management.Smo.MirroringOption]::Off)
$currentdb.Alter()
$currentdb.Refresh()
Write-Message -Level Verbose -Message "Could not break mirror for $dbName. Skipping."
}
catch {
Stop-Function -Message "Issue breaking mirror." -Target $dbName -ErrorRecord $_
return $false
}
}
if ($currentdb.AvailabilityGroupName.Length -gt 0) {
$agName = $currentdb.AvailabilityGroupName
Write-Message -Level Verbose -Message "Attempting remove from Availability Group $agName."
try {
$server.AvailabilityGroups[$currentdb.AvailabilityGroupName].AvailabilityDatabases[$dbName].Drop()
Write-Message -Level Verbose -Message "Successfully removed $dbName from detach from $agName on $($server.Name)."
}
catch {
Stop-Function -Message "Could not remove $dbName from $agName on $($server.Name)." -Target $dbName -ErrorRecord $_
return $false
}
}
Write-Message -Level Verbose -Message "Attempting detach from $dbName from $source."
####### Using Sql to detach does not modify the $currentdb collection #######
$server.KillAllProcesses($dbName)
try {
$sql = "ALTER DATABASE [$dbName] SET SINGLE_USER WITH ROLLBACK IMMEDIATE"
Write-Message -Level Verbose -Message $sql
$null = $server.Query($sql)
Write-Message -Level Verbose -Message "Successfully set $dbName to single-user from $source."
}
catch {
Stop-Function -Message "Issue setting database to single-user." -Target $dbName -ErrorRecord $_
}
try {
$sql = "EXEC master.dbo.sp_detach_db N'$dbName'"
Write-Message -Level Verbose -Message $sql
$null = $server.Query($sql)
Write-Message -Level Verbose -Message "Successfully detached $dbName from $source."
return $true
}
catch {
Stop-Function -Message "Issue detaching database." -Target $dbName -ErrorRecord $_
return $false
}
}
function Mount-SqlDatabase {
[CmdletBinding()]
param (
[object]$server,
[string]$dbName,
[object]$fileStructure,
[string]$dbOwner
)
if ($null -eq $server.Logins.Item($dbOwner)) {
try {
$dbOwner = ($destServer.logins | Where-Object { $_.id -eq 1 }).Name
}
catch {
$dbOwner = "sa"
}
}
try {
$null = $server.AttachDatabase($dbName, $fileStructure, $dbOwner, [Microsoft.SqlServer.Management.Smo.AttachOptions]::None)
return $true
}
catch {
Stop-Function -Message "Issue mounting database." -ErrorRecord $_
return $false
}
}
function Start-SqlFileTransfer {
<#
SYNOPSIS
Internal function. Uses BITS to transfer detached files (.mdf, .ndf, .ldf, and filegroups) to
another server over admin UNC paths. Locations of data files are kept in the
custom object generated by Get-SqlFileStructure
#>
param (
[object]$fileStructure,
[string]$dbName
)
$filestructure
$copydb = $fileStructure.databases[$dbName]
$dbsource = $copydb.source
$dbdestination = $copydb.destination
foreach ($file in $dbsource.keys) {
$remotefilename = $dbdestination[$file].remotefilename
$from = $dbsource[$file].remotefilename
try {
if (Test-Path $from -pathtype container) {
$null = New-Item -ItemType Directory -Path $remotefilename -Force
Start-BitsTransfer -Source "$from\*.*" -Destination $remotefilename
$directories = (Get-ChildItem -recurse $from | Where-Object { $_.PsIsContainer }).FullName
foreach ($directory in $directories) {
$newdirectory = $directory.replace($from, $remotefilename)
$null = New-Item -ItemType Directory -Path $newdirectory -Force
Start-BitsTransfer -Source "$directory\*.*" -Destination $newdirectory
}
}
else {
Write-Message -Level Verbose -Message "Copying $from for $dbName."
Start-BitsTransfer -Source $from -Destination $remotefilename
}
}
catch {
try {
# Sometimes BITS trips out temporarily on cloned drives.
Start-BitsTransfer -Source $from -Destination $remotefilename
}
catch {
Write-Message -Level Verbose -Message "Start-BitsTransfer did not succeed. Now attempting with Copy-Item - no progress bar will be shown."
try {
Copy-Item -Path $from -Destination $remotefilename -ErrorAction Stop
$remotefilename
}
catch {
Write-Message -Level Verbose -Message "Access denied. This can happen for a number of reasons including issues with cloned disks."
Stop-Function -Message "Alternatively, you may need to run PowerShell as Administrator, especially when running on localhost." -Target $from -ErrorRecord $_
return
}
}
}
}
return $true
}
function Start-SqlDetachAttach {
<#
.SYNOPSIS
Internal function. Performs checks, then executes Dismount-SqlDatabase on a database, copies its files to the new server, then performs Mount-SqlDatabase. $sourceServer and $destServer are SMO server objects.
$fileStructure is a custom object generated by Get-SqlFileStructure
#>
[CmdletBinding()]
param (
[object]$sourceServer,
[object]$destServer,
[object]$fileStructure,
[string]$dbName
)
$destfilestructure = New-Object System.Collections.Specialized.StringCollection
$sourceFileStructure = New-Object System.Collections.Specialized.StringCollection
$dbOwner = $sourceServer.databases[$dbName].owner
$destDbName = $fileStructure.databases[$dbName].destinationDbName
if ($null -eq $dbOwner) {
try {
$dbOwner = ($destServer.logins | Where-Object { $_.id -eq 1 }).Name
}
catch {
$dbOwner = "sa"
}
}
foreach ($file in $fileStructure.databases[$dbName].destination.values) { $null = $destfilestructure.add($file.physical) }
foreach ($file in $fileStructure.databases[$dbName].source.values) { $null = $sourceFileStructure.add($file.physical) }
$detachresult = Dismount-SqlDatabase $sourceServer $dbName
if ($detachresult) {
$transfer = Start-SqlFileTransfer $fileStructure $dbName
if ($transfer -eq $false) { Write-Warning "Could not copy files."; return "Could not copy files." }
$attachresult = Mount-SqlDatabase $destServer $destDbName $destfilestructure $dbOwner
if ($attachresult -eq $true) {
# add to added dbs because ATTACH was successful
Write-Message -Level Verbose -Message "Successfully attached $dbName to $destinstance."
return $true
}
else {
# add to failed because ATTACH was unsuccessful
Write-Message -Level Verbose -Message "Could not attach $dbName."
return "Could not attach database."
}
}
else {
# add to failed because DETACH was unsuccessful
Write-Message -Level Verbose -Message "Could not detach $dbName."
return "Could not detach database."
}
}
$backupCollection = @()
}
process {
if (Test-FunctionInterrupt) { return }
# testing twice for whatif reasons
if ($BackupRestore -and (-not $NetworkShare -and -not $UseLastBackups)) {
Stop-Function -Message "When using -BackupRestore, you must specify -NetworkShare or -UseLastBackups"
return
}
if ($NetworkShare -and $UseLastBackups) {
Stop-Function -Message "-NetworkShare cannot be used with -UseLastBackups because the backup path is determined by the paths in the last backups"
return
}
if ($DetachAttach -and -not $Reattach -and $Destination.Count -gt 1) {
Stop-Function -Message "When using -DetachAttach with multiple servers, you must specify -Reattach to reattach database at source"
return
}
if (($AllDatabases -or $IncludeSupportDbs -or $Database) -and !$DetachAttach -and !$BackupRestore) {
Stop-Function -Message "You must specify -DetachAttach or -BackupRestore when migrating databases."
return
}
if (-not $AllDatabases -and -not $IncludeSupportDbs -and -not $Database -and -not $InputObject) {
Stop-Function -Message "You must specify a -AllDatabases or -Database to continue."
return
}
if ((Test-Bound 'NewName') -and (Test-Bound 'Prefix')){
Stop-Function -Message "NewName and Prefix are exclusive options, cannot specify both"
return
}
if ($InputObject) {
$Source = $InputObject[0].Parent
$Database = $InputObject.Name
}
if ($Database -contains "master" -or $Database -contains "msdb" -or $Database -contains "tempdb") {
Stop-Function -Message "Migrating system databases is not currently supported." -Continue
}
try {
Write-Message -Level Verbose -Message "Connecting to $Source"
$sourceServer = Connect-SqlInstance -SqlInstance $Source -SqlCredential $SourceSqlCredential
}
catch {
Stop-Function -Message "Failure" -Category ConnectionError -ErrorRecord $_ -Target $Source
return
}
Invoke-SmoCheck -SqlInstance $sourceServer
$sourceNetBios = $sourceServer.ComputerName
Write-Message -Level Verbose -Message "Ensuring user databases exist (counting databases)."
$dbTotal = $sourceServer.Databases.Count
if ($dbTotal -le 4) {
Stop-Function -Message "No user databases to migrate. Quitting."
return
}
foreach ($destinstance in $Destination) {
try {
Write-Message -Level Verbose -Message "Connecting to $destinstance"
$destServer = Connect-SqlInstance -SqlInstance $destinstance -SqlCredential $DestinationSqlCredential
}
catch {
Stop-Function -Message "Failure" -Category ConnectionError -ErrorRecord $_ -Target $destinstance -Continue
}
if ($sourceServer.ComputerName -eq $destServer.ComputerName) {
$script:sameserver = $true
}
else {
$script:sameserver = $false
}
if ($script:sameserver -and $DetachAttach) {
if (-not (Test-ElevationRequirement -ComputerName $sourceServer)) { return }
}
$destVersionLower = $destServer.VersionMajor -lt $sourceServer.VersionMajor
$destVersionMinorLow = ($destServer.VersionMajor -eq 10 -and $sourceServer.VersionMajor -eq 10) -and ($destServer.VersionMinor -lt $sourceServer.VersionMinor)
if ($destVersionLower -or $destVersionMinorLow) {
Stop-Function -Message "Error: copy database cannot be made from newer $($sourceServer.VersionString) to older $($destServer.VersionString) SQL Server version."
return
}
if ($DetachAttach) {
if ($sourceServer.ComputerName -eq $env:COMPUTERNAME -or $destServer.ComputerName -eq $env:COMPUTERNAME) {
if (-not ([Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole] "Administrator")) {
Write-Message -Level Verbose -Message "When running DetachAttach locally on the console, it's possible you'll need to Run As Administrator. Trying anyway."
}
}
}
if ($NetworkShare) {
if ($(Test-DbaPath -SqlInstance $sourceServer -Path $NetworkShare) -eq $false) {
Write-Message -Level Verbose -Message "$Source may not be able to access $NetworkShare. Trying anyway."
}
if ($(Test-DbaPath -SqlInstance $destServer -Path $NetworkShare) -eq $false) {
Write-Message -Level Verbose -Message "$destinstance may not be able to access $NetworkShare. Trying anyway."
}
if ($NetworkShare.StartsWith('\\')) {
try {
$shareServer = ($NetworkShare -split "\\")[2]
$hostEntry = ([Net.Dns]::GetHostEntry($shareServer)).HostName -split "\."
if ($shareServer -ne $hostEntry[0]) {
Write-Message -Level Verbose -Message "Using CNAME records for the network share may present an issue if an SPN has not been created. Trying anyway. If it doesn't work, use a different (A record) hostname."
}
}
catch {
Stop-Function -Message "Error validating unc path: $_"
return
}
}
}
$destNetBios = $destserver.ComputerName
Write-Message -Level Verbose -Message "Performing SMO version check."
Invoke-SmoCheck -SqlInstance $destServer
Write-Message -Level Verbose -Message "Checking to ensure the source isn't the same as the destination."
if ($source -eq $destinstance) {
Stop-Function -Message "Source and Destination SQL Servers instances are the same. Quitting." -Continue
}
if ($NetworkShare.Length -gt 0) {
Write-Message -Level Verbose -Message "Checking to ensure network path is valid."
if (!($NetworkShare.StartsWith("\\")) -and !$script:sameserver) {
Stop-Function -Message "Network share must be a valid UNC path (\\server\share)." -Continue
}
if (-not $script:sameserver) {
try {
if ((Test-Path $NetworkShare -ErrorAction Stop)) {
Write-Message -Level Verbose -Message "$NetworkShare share can be accessed."
}
}
catch {
Write-Message -Level Verbose -Message "$NetworkShare share cannot be accessed. Still trying anyway, in case the SQL Server service accounts have access."
}
}
}
Write-Message -Level Verbose -Message "Checking to ensure server is not SQL Server 7 or below."
if ($sourceServer.VersionMajor -lt 8 -and $destServer.VersionMajor -lt 8) {
Stop-Function -Message "This script can only be run on SQL Server 2000 and above. Quitting." -Continue
}
Write-Message -Level Verbose -Message "Checking to ensure detach/attach is not attempted on SQL Server 2000."
if ($destServer.VersionMajor -lt 9 -and $DetachAttach) {
Stop-Function -Message "Detach/Attach not supported when destination SQL Server is version 2000. Quitting." -Target $destServer -Continue
}
Write-Message -Level Verbose -Message "Checking to ensure SQL Server 2000 migration isn't directly attempted to SQL Server 2012."
if ($sourceServer.VersionMajor -lt 9 -and $destServer.VersionMajor -gt 10) {
Stop-Function -Message "SQL Server 2000 databases cannot be migrated to SQL Server versions 2012 and above. Quitting." -Target $destServer -Continue
}
Write-Message -Level Verbose -Message "Warning if migration from 2005 to 2012 and above and attach/detach is used."
if ($sourceServer.VersionMajor -eq 9 -and $destServer.VersionMajor -gt 9 -and !$BackupRestore -and !$Force -and $DetachAttach) {
Stop-Function -Message "Backup and restore is the safest method for migrating from SQL Server 2005 to other SQL Server versions. Please use the -BackupRestore switch or override this requirement by specifying -Force." -Continue
}
if ($sourceServer.Collation -ne $destServer.Collation) {
Write-Message -Level Verbose -Message "Warning on different collation."
Write-Message -Level Verbose -Message "Collation on $Source, $($sourceServer.Collation) differs from the $destinstance, $($destServer.Collation)."
}
Write-Message -Level Verbose -Message "Ensuring destination server version is equal to or greater than source."
if ($sourceServer.VersionMajor -ge $destServer.VersionMajor) {
if ($sourceServer.VersionMinor -gt $destServer.VersionMinor) {
Stop-Function -Message "Source SQL Server version build must be <= destination SQL Server for database migration." -Continue
}
}
# SMO's filestreamlevel is sometimes null
$sql = "select coalesce(SERVERPROPERTY('FilestreamConfiguredLevel'),0) as fs"
$sourceFilestream = $sourceServer.ConnectionContext.ExecuteScalar($sql)
$destFilestream = $destServer.ConnectionContext.ExecuteScalar($sql)
if ($sourceFilestream -gt 0 -and $destFilestream -eq 0) {
$fsWarning = $true
}
Write-Message -Level Verbose -Message "Writing warning about filestream being enabled."
if ($fsWarning) {
Write-Message -Level Verbose -Message "FILESTREAM enabled on $source but not $destinstance. Databases that use FILESTREAM will be skipped."
}
if ($DetachAttach -eq $true) {
Write-Message -Level Verbose -Message "Checking access to remote directories."
$remoteSourcePath = Join-AdminUNC $sourceNetBios (Get-SqlDefaultPaths -SqlInstance $sourceServer -filetype data)
if ((Test-Path $remoteSourcePath) -ne $true -and $DetachAttach) {
Write-Message -Level Warning -Message "Can't access remote Sql directories on $source which is required to perform detach/copy/attach."
Write-Message -Level Warning -Message "You can manually try accessing $remoteSourcePath to diagnose any issues."
Stop-Function -Message "Halting database migration"
return
}
$remoteDestPath = Join-AdminUNC $destNetBios (Get-SqlDefaultPaths -SqlInstance $destServer -filetype data)
If ((Test-Path $remoteDestPath) -ne $true -and $DetachAttach) {
Write-Message -Level Warning -Message "Can't access remote Sql directories on $destinstance which is required to perform detach/copy/attach."
Write-Message -Level Warning -Message "You can manually try accessing $remoteDestPath to diagnose any issues."
Stop-Function -Message "Halting database migration" -Continue
}
}
if (($Database -or $ExcludeDatabase -or $IncludeSupportDbs) -and (!$DetachAttach -and !$BackupRestore)) {
Stop-Function -Message "You did not select a migration method. Please use -BackupRestore or -DetachAttach."
return
}
if ((!$Database -and !$AllDatabases -and !$IncludeSupportDbs) -and ($DetachAttach -or $BackupRestore)) {
Stop-Function -Message "You did not select any databases to migrate. Please use -AllDatabases or -Database or -IncludeSupportDbs."
return
}
Write-Message -Level Verbose -Message "Building database list."
$databaseList = New-Object System.Collections.ArrayList
$SupportDBs = "ReportServer", "ReportServerTempDB", "distribution"
foreach ($currentdb in ($sourceServer.Databases | Where-Object IsAccessible)) {
$dbName = $currentdb.Name
$dbOwner = $currentdb.Owner
if ($currentdb.Id -le 4) { continue }
if ($Database -and $Database -notcontains $dbName) { continue }
if ($IncludeSupportDBs -eq $false -and $SupportDBs -contains $dbName) { continue }
if ($IncludeSupportDBs -eq $true -and $SupportDBs -notcontains $dbName) {
if ($AllDatabases -eq $false -and $Database.length -eq 0) { continue }
}
$null = $databaseList.Add($currentdb)
}
Write-Message -Level Verbose -Message "Performing count."
$dbCount = $databaseList.Count
if ((Test-Bound 'NewName') -and $dbCount -gt 1){
Stop-Function -Message "Cannot use NewName when copying multiple databases"
return
}
Write-Message -Level Verbose -Message "Building file structure inventory for $dbCount databases."
if ($sourceServer.VersionMajor -eq 8) {
$sql = "select DB_NAME (dbid) as dbname, name, filename, CASE WHEN groupid = 0 THEN 'LOG' ELSE 'ROWS' END as filetype from sysaltfiles"
}
else {
$sql = "SELECT db.Name AS dbname, type_desc AS FileType, mf.Name, Physical_Name AS filename FROM sys.master_files mf INNER JOIN sys.databases db ON db.database_id = mf.database_id"
}
$dbFileTable = $sourceServer.Databases['master'].ExecuteWithResults($sql)
if ($destServer.VersionMajor -eq 8) {
$sql = "select DB_NAME (dbid) as dbname, name, filename, CASE WHEN groupid = 0 THEN 'LOG' ELSE 'ROWS' END as filetype from sysaltfiles"
}
else {
$sql = "SELECT db.Name AS dbname, type_desc AS FileType, mf.Name, Physical_Name AS filename FROM sys.master_files mf INNER JOIN sys.databases db ON db.database_id = mf.database_id"
}
$remoteDbFileTable = $destServer.Databases['master'].ExecuteWithResults($sql)
$fileStructure = Get-SqlFileStructure -sourceserver $sourceServer -destserver $destServer -databaselist $databaseList -ReuseSourceFolderStructure $ReuseSourceFolderStructure
$elapsed = [System.Diagnostics.Stopwatch]::StartNew()
$started = Get-Date
$script:TimeNow = (Get-Date -UFormat "%m%d%Y%H%M%S")
if ($AllDatabases -or $ExcludeDatabase.length -gt 0 -or $IncludeSupportDbs -or $Database.length -gt 0) {
foreach ($currentdb in $databaseList) {
$dbName = $currentdb.Name
$dbOwner = $currentdb.Owner
$destinationDbName = $dbName
if ((Test-Bound "NewName")){
Write-Message -Level Verbose -Message "NewName specified, copying $dbname as $NewName"
$destinationDbName = $NewName
$replaceInFile = $True
}
if ($(Test-Bound "Prefix")){
$destinationDbName = $prefix+$destinationDbName
Write-Message -Level Verbose -Message "Prefix supplied, copying $dbname as $destinationDbName"
}
$filestructure.databases[$dbname].Add('destinationDbName',$destinationDbName)
ForEach ($key in $filestructure.databases[$dbname].Destination.Keys){
$splitFileName = Split-Path $fileStructure.databases[$dbname].Destination[$key].remotefilename -Leaf
$SplitPath = Split-Path $fileStructure.databases[$dbname].Destination[$key].remotefilename
if ($replaceInFile){
$splitFileName = $splitFileName.replace($dbname, $destinationDbName)
}
$splitFileName = $prefix+$splitFileName
$filestructure.databases[$dbname].Destination.$key.remotefilename = Join-Path $SplitPath $splitFileName
$splitFileName = Split-Path $filestructure.databases[$dbname].Destination[$key].physical -Leaf
$SplitPath = Split-Path $fileStructure.databases[$dbname].Destination[$key].physical
if ($replaceInFile){
$splitFileName = $splitFileName.replace($dbname, $destinationDbName)
}
$splitFileName = $prefix+$splitFileName
$filestructure.databases[$dbname].Destination.$key.physical = Join-Path $SplitPath $splitFileName
}
$copyDatabaseStatus = [pscustomobject]@{
SourceServer = $sourceServer.Name
DestinationServer = $destServer.Name
Name = $dbName
DestinationDatabase = $DestinationDbname
Type = "Database"
Status = $null
Notes = $null
DateTime = [DbaDateTime](Get-Date)
}
Write-Message -Level Verbose -Message "`n######### Database: $dbName #########"
$dbStart = Get-Date
if ($ExcludeDatabase -contains $dbName) {
Write-Message -Level Verbose -Message "$dbName excluded. Skipping."
continue
}
Write-Message -Level Verbose -Message "Checking for accessibility."
if ($currentdb.IsAccessible -eq $false) {
if ($Pscmdlet.ShouldProcess($destinstance, "Skipping $dbName. Database is inaccessible.")) {
Write-Message -Level Verbose -Message "Skipping $dbName. Database is inaccessible."
$copyDatabaseStatus.Status = "Skipped"
$copyDatabaseStatus.Notes = "Database is not accessible"
$copyDatabaseStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
}
continue
}
if ($fsWarning) {
$fsRows = $dbFileTable.Tables[0].Select("dbname = '$dbName' and FileType = 'FileStream'")
if ($fsRows.Count -gt 0) {
if ($Pscmdlet.ShouldProcess($destinstance, "Skipping $dbName (contains FILESTREAM).")) {
Write-Message -Level Verbose -Message "Skipping $dbName (contains FILESTREAM)."
$copyDatabaseStatus.Status = "Skipped"
$copyDatabaseStatus.Notes = "Contains FILESTREAM"
$copyDatabaseStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
}
continue
}
}
if ($ReuseSourceFolderStructure) {
$fgRows = $dbFileTable.Tables[0].Select("dbname = '$dbName' and FileType = 'ROWS'")[0]
$remotePath = Split-Path $fgRows.Filename
if (!(Test-DbaPath -SqlInstance $destServer -Path $remotePath)) {
if ($Pscmdlet.ShouldProcess($destinstance, "$remotePath does not exist on $destinstance and ReuseSourceFolderStructure was specified")) {
# Stop-Function -Message "Cannot resolve $remotePath on $source. `n`nYou have specified ReuseSourceFolderStructure and exact folder structure does not exist. Halting script."
$copyDatabaseStatus.Status = "Failed"
$copyDatabaseStatus.Notes = "$remotePath does not exist on $destinstance and ReuseSourceFolderStructure was specified" #"Can't resolve $remotePath"
$copyDatabaseStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
}
continue
}
}
Write-Message -Level Verbose -Message "Checking Availability Group status."
if ($currentdb.AvailabilityGroupName.Length -gt 0 -and !$force -and $DetachAttach) {
$agName = $currentdb.AvailabilityGroupName
Write-Message -Level Verbose -Message "Database is part of an Availability Group ($agName). Use -Force to drop from $agName and migrate. Alternatively, you can use the safer backup/restore method."
continue
}
$dbStatus = $currentdb.Status.ToString()
if ($dbStatus.StartsWith("Normal") -eq $false) {
if ($Pscmdlet.ShouldProcess($destinstance, "$dbName is not in a Normal state. Skipping.")) {
Write-Message -Level Verbose -Message "$dbName is not in a Normal state. Skipping."
$copyDatabaseStatus.Status = "Skipped"
$copyDatabaseStatus.Notes = "Not in normal state"
$copyDatabaseStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
}
continue
}
if ($currentdb.ReplicationOptions -ne "None" -and $DetachAttach -eq $true) {
if ($Pscmdlet.ShouldProcess($destinstance, "$dbName is part of replication. Skipping.")) {
Write-Message -Level Verbose -Message "$dbName is part of replication. Skipping."
$copyDatabaseStatus.Status = "Skipped"
$copyDatabaseStatus.Notes = "Part of replication"
$copyDatabaseStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
}
continue
}
if ($currentdb.IsMirroringEnabled -and !$force -and $DetachAttach) {
if ($Pscmdlet.ShouldProcess($destinstance, "Database is being mirrored. Use -Force to break mirror and migrate. Alternatively, you can use the safer backup/restore method.")) {
Write-Message -Level Verbose -Message "Database is being mirrored. Use -Force to break mirror and migrate. Alternatively, you can use the safer backup/restore method."
$copyDatabaseStatus.Status = "Skipped"
$copyDatabaseStatus.Notes = "Database is mirrored"
$copyDatabaseStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
}
continue
}
if (($null -ne $destServer.Databases[$DestinationdbName]) -and !$force -and !$WithReplace) {
if ($Pscmdlet.ShouldProcess($destinstance, "$DestinationdbName exists at destination. Use -Force to drop and migrate. Aborting routine for this database.")) {
Write-Message -Level Verbose -Message "$DestinationdbName exists at destination. Use -Force to drop and migrate. Aborting routine for this database."
$copyDatabaseStatus.Status = "Skipped"
$copyDatabaseStatus.Notes = "Already exists"
$copyDatabaseStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
}
continue
}
elseif ($null -ne $destServer.Databases[$DestinationdbName] -and $force) {
if ($Pscmdlet.ShouldProcess($destinstance, "DROP DATABASE $DestinationdbName")) {
Write-Message -Level Verbose -Message "$DestinationdbName already exists. -Force was specified. Dropping $DestinationdbName on $destinstance."
$removeresult = Remove-DbaDatabase -SqlInstance $destserver -Database $DestinationdbName -Confirm:$false
$dropResult = $removeresult.Status -eq 'Dropped'
if ($dropResult -eq $false) {
Write-Message -Level Verbose -Message "Database could not be dropped. Aborting routine for this database."
$copyDatabaseStatus.Status = "Failed"
$copyDatabaseStatus.Notes = "Could not drop database"
$copyDatabaseStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
continue
}
}
}
if ($force) {
$WithReplace = $true
}
Write-Message -Level Verbose -Message "Started: $dbStart."
if ($sourceServer.VersionMajor -ge 9) {
$sourceDbOwnerChaining = $sourceServer.Databases[$dbName].DatabaseOwnershipChaining
$sourceDbTrustworthy = $sourceServer.Databases[$dbName].Trustworthy
$sourceDbBrokerEnabled = $sourceServer.Databases[$dbName].BrokerEnabled
}
$sourceDbReadOnly = $sourceServer.Databases[$dbName].ReadOnly
if ($SetSourceReadOnly) {
If ($Pscmdlet.ShouldProcess($source, "Set $dbName to read-only")) {
Write-Message -Level Verbose -Message "Setting database to read-only."
$result = Update-SqldbReadOnly -SqlInstance $sourceServer -dbname $dbName -readonly:$true
if ($result -eq $false) {
Write-Message -Level Verbose -Message "Couldn't set database to read-only. Aborting routine for this database."
continue
}
}
}
if ($BackupRestore) {
if ($UseLastBackups) {
$whatifmsg = "Gathering last backup information for $dbName from $Source and restoring"
}
else {
$whatifmsg = "Backup $dbName from $source and restoring"
}
If ($Pscmdlet.ShouldProcess($destinstance, $whatifmsg)) {
if ($UseLastBackups) {
$backupTmpResult = Get-DbaBackupHistory -SqlInstance $sourceServer -Database $dbName -IncludeCopyOnly -Last
if (-not $backupTmpResult) {
$copyDatabaseStatus.Type = "Database (BackupRestore)"
$copyDatabaseStatus.Status = "Failed"
$copyDatabaseStatus.Notes = "No backups for $dbName on $source"
$copyDatabaseStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
continue
}
}
else {
$backupTmpResult = $backupCollection | Where-Object Database -eq $dbName
if (-not $backupTmpResult) {
$backupTmpResult = Backup-DbaDatabase -SqlInstance $sourceServer -Database $dbName -BackupDirectory $NetworkShare -FileCount $numberfiles -CopyOnly:$CopyOnly
}
if ($backupTmpResult) {
$backupCollection += $backupTmpResult
}
$backupResult = $BackupTmpResult.BackupComplete
if (-not $backupResult) {
$serviceAccount = $sourceServer.ServiceAccount
Write-Message -Level Verbose -Message "Backup Failed. Does SQL Server account $serviceAccount have access to $($NetworkShare)? Aborting routine for this database."
$copyDatabaseStatus.Status = "Failed"
$copyDatabaseStatus.Notes = "Backup failed. Verify service account access to $NetworkShare."
$copyDatabaseStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
continue
}
}
Write-Message -Level Verbose -Message "Reuse = $ReuseSourceFolderStructure."
try {
$msg = $null
$restoreResultTmp = $backupTmpResult | Restore-DbaDatabase -SqlInstance $destServer -DatabaseName $DestinationdbName -ReuseSourceFolderStructure:$ReuseSourceFolderStructure -NoRecovery:$NoRecovery -TrustDbBackupHistory -WithReplace:$WithReplace -Continue:$Continue -EnableException -ReplaceDbNameInFile
}
catch {
$msg = $_.Exception.InnerException.InnerException.InnerException.InnerException.Message
Stop-Function -Message "Failure attempting to restore $dbName to $destinstance" -Exception $_.Exception.InnerException.InnerException.InnerException.InnerException
}
$restoreResult = $restoreResultTmp.RestoreComplete
if ($restoreResult -eq $true) {
Write-Message -Level Verbose -Message "Successfully restored $dbName to $destinstance."
$copyDatabaseStatus.Status = "Successful"
$copyDatabaseStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
}
else {
if ($ReuseSourceFolderStructure) {
Write-Message -Level Verbose -Message "Failed to restore $dbName to $destinstance. You specified -ReuseSourceFolderStructure. Does the exact same destination directory structure exist?"
Write-Message -Level Verbose -Message "Aborting routine for this database."
$copyDatabaseStatus.Status = "Failed"
$copyDatabaseStatus.Notes = "Failed to restore. ReuseSourceFolderStructure was specified, verify same directory structure exist on destination."
$copyDatabaseStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
continue
}
else {
Write-Message -Level Verbose -Message "Failed to restore $dbName to $destinstance. Aborting routine for this database."
$copyDatabaseStatus.Status = "Failed"
if (-not $msg) {
$msg = "Failed to restore database"
}
$copyDatabaseStatus.Notes = $msg
$copyDatabaseStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
continue
}
}
if (-not $NoBackupCleanUp -and $Destination.Count -eq 1) {
foreach ($backupFile in ($backupTmpResult.BackupPath)) {
try {
if (Test-Path $backupFile -ErrorAction Stop) {
Write-Message -Level Verbose -Message "Deleting $backupFile."
Remove-Item $backupFile -ErrorAction Stop
}
}
catch {
try {
Write-Message -Level Verbose -Message "Trying alternate SQL method to delete $backupFile."
$sql = "EXEC master.sys.xp_delete_file 0, '$backupFile'"
Write-Message -Level Debug -Message $sql
$null = $sourceServer.Query($sql)
}
catch {
Write-Message -Level Verbose -Message "Cannot delete backup file $backupFile."
# Set NoBackupCleanup so that there's a warning at the end
$NoBackupCleanup = $true
}
}
}
}
}
$dbFinish = Get-Date
if ($NoRecovery -eq $false) {
# needed because the newly restored database doesn't show up
$destServer.Databases.Refresh()
$dbOwner = $sourceServer.Databases[$dbName].Owner
if ($null -eq $dbOwner -or $destServer.Logins.Name -notcontains $dbOwner) {
$dbOwner = Get-SaLoginName -SqlInstance $destServer
}
Write-Message -Level Verbose -Message "Updating database owner to $dbOwner."
$OwnerResult = Set-DbaDbOwner -SqlInstance $destServer -Database $dbName -TargetLogin $dbOwner -EnableException
if ($OwnerResult.Length -eq 0) {
Write-Message -Level Verbose -Message "Failed to update database owner."
}
}
}
if ($DetachAttach) {
$copyDatabaseStatus.Type = "Database (DetachAttach)"
$sourceFileStructure = New-Object System.Collections.Specialized.StringCollection
foreach ($file in $fileStructure.Databases[$dbName].Source.Values) {
$null = $sourceFileStructure.Add($file.Physical)
}
$dbOwner = $sourceServer.Databases[$dbName].Owner
if ($null -eq $dbOwner -or $destServer.Logins.Name -notcontains $dbOwner) {
$dbOwner = Get-SaLoginName -SqlInstance $destServer
}
if ($Pscmdlet.ShouldProcess($destinstance, "Detach $dbName from $source and attach, then update dbowner")) {
$migrationResult = Start-SqlDetachAttach $sourceServer $destServer $fileStructure $dbName
$dbFinish = Get-Date
if ($reattach -eq $true) {
$sourceServer.Databases.Refresh()
$destServer.Databases.Refresh()
$result = Mount-SqlDatabase $sourceServer $dbName $sourceFileStructure $dbOwner
if ($result -eq $true) {
$sourceServer.Databases[$dbName].DatabaseOwnershipChaining = $sourceDbOwnerChaining
$sourceServer.Databases[$dbName].Trustworthy = $sourceDbTrustworthy
$sourceServer.Databases[$dbName].BrokerEnabled = $sourceDbBrokerEnabled
$sourceServer.Databases[$dbName].Alter()
if ($SetSourceReadOnly) {
$null = Update-SqldbReadOnly -SqlInstance $sourceServer -dbname $dbName -readonly $true
}
else {
$null = Update-SqldbReadOnly -SqlInstance $sourceServer -dbname $dbName -readonly $sourceDbReadOnly
}
Write-Message -Level Verbose -Message "Successfully reattached $dbName to $source."
}
else {
Write-Message -Level Verbose -Message "Could not reattach $dbName to $source."
$copyDatabaseStatus.Status = "Failed"
$copyDatabaseStatus.Notes = "Could not reattach database to $source"
$copyDatabaseStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
}
}
if ($migrationResult -eq $true) {
Write-Message -Level Verbose -Message "Successfully attached $dbName to $destinstance."
$copyDatabaseStatus.Status = "Successful"
$copyDatabaseStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
}
else {
Write-Message -Level Verbose -Message "Failed to attach $dbName to $destinstance. Aborting routine for this database."
$copyDatabaseStatus.Status = "Failed"
$copyDatabaseStatus.Notes = "Failed to attach database to destination"
$copyDatabaseStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
continue
}
}
}
$destServer.Databases.Refresh()
# restore potentially lost settings
if ($destServer.VersionMajor -ge 9 -and $NoRecovery -eq $false) {
if ($sourceDbOwnerChaining -ne $destServer.Databases[$DestinationdbName].DatabaseOwnershipChaining) {
if ($Pscmdlet.ShouldProcess($destinstance, "Updating DatabaseOwnershipChaining on $DestinationdbName")) {
try {
$destServer.Databases[$DestinationdbName].DatabaseOwnershipChaining = $sourceDbOwnerChaining
$destServer.Databases[$DestinationdbName].Alter()
Write-Message -Level Verbose -Message "Successfully updated DatabaseOwnershipChaining for $sourceDbOwnerChaining on $DestinationdbName on $destinstance."
}
catch {
$copyDatabaseStatus.Status = "Successful - failed to apply DatabaseOwnershipChaining."
$copyDatabaseStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
Stop-Function -Message "Failed to update DatabaseOwnershipChaining for $sourceDbOwnerChaining on $DestinationdbName on $destinstance." -Target $destinstance -ErrorRecord $_ -Continue
}
}
}
if ($sourceDbTrustworthy -ne $destServer.Databases[$DestinationdbName].Trustworthy) {
if ($Pscmdlet.ShouldProcess($destinstance, "Updating Trustworthy on $DestinationdbName")) {
try {
$destServer.Databases[$DestinationdbName].Trustworthy = $sourceDbTrustworthy
$destServer.Databases[$DestinationdbName].Alter()
Write-Message -Level Verbose -Message "Successfully updated Trustworthy to $sourceDbTrustworthy for $DestinationdbName on $destinstance"
}
catch {
$copyDatabaseStatus.Status = "Successful - failed to apply Trustworthy"
$copyDatabaseStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
Stop-Function -Message "Failed to update Trustworthy to $sourceDbTrustworthy for $DestinationdbName on $destinstance." -Target $destinstance -ErrorRecord $_ -Continue
}
}
}
if ($sourceDbBrokerEnabled -ne $destServer.Databases[$DestinationdbName].BrokerEnabled) {
if ($Pscmdlet.ShouldProcess($destinstance, "Updating BrokerEnabled on $dbName")) {
try {
$destServer.Databases[$DestinationdbName].BrokerEnabled = $sourceDbBrokerEnabled
$destServer.Databases[$DestinationdbName].Alter()
Write-Message -Level Verbose -Message "Successfully updated BrokerEnabled to $sourceDbBrokerEnabled for $DestinationdbName on $destinstance."
}
catch {
$copyDatabaseStatus.Status = "Successful - failed to apply BrokerEnabled"
$copyDatabaseStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
Stop-Function -Message "Failed to update BrokerEnabled to $sourceDbBrokerEnabled for $DestinationdbName on $destinstance." -Target $destinstance -ErrorRecord $_ -Continue
}
}
}
}
if ($sourceDbReadOnly -ne $destServer.Databases[$DestinationdbName].ReadOnly -and $NoRecovery -eq $false) {
if ($Pscmdlet.ShouldProcess($destinstance, "Updating ReadOnly status on $DestinationdbName")) {
$update = Update-SqldbReadOnly -SqlInstance $destServer -dbname $DestinationdbName -readonly $sourceDbReadOnly
if ($update -eq $true) {
Write-Message -Level Verbose -Message "Successfully updated readonly status on $DestinationdbName."
}
else {
$copyDatabaseStatus.Status = "Successful - failed to apply ReadOnly."
$copyDatabaseStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
Stop-Function -Message "Failed to update ReadOnly status on $DestinationdbName." -Target $destinstance -ErrorRecord $_ -Continue
}
}
}
if ($SetSourceOffline -and $sourceServer.databases[$DestinationdbName].status -notlike '*offline*') {
if ($Pscmdlet.ShouldProcess($destinstance, "Setting $DestinationdbName offline on $source")) {
Stop-DbaProcess -SqlInstance $sourceServer -Database $DestinationdbName
Set-DbaDbState -SqlInstance $sourceServer -SqlCredential $SourceSqlCredential -database $DestinationdbName -Offline
}
}
$dbTotalTime = $dbFinish - $dbStart
$dbTotalTime = ($dbTotalTime.ToString().Split(".")[0])
Write-Message -Level Verbose -Message "Finished: $dbFinish."
Write-Message -Level Verbose -Message "Elapsed time: $dbTotalTime."
} # end db by db processing
}
}
}
end {
if (Test-FunctionInterrupt) { return }
if (-not $NoBackupCleanUp -and $Destination.Count -gt 1) {
foreach ($backupFile in ($backupCollection.BackupPath)) {
try {
if (Test-Path $backupFile -ErrorAction Stop) {
Write-Message -Level Verbose -Message "Deleting $backupFile."
Remove-Item $backupFile -ErrorAction Stop
}
}
catch {
try {
Write-Message -Level Verbose -Message "Trying alternate SQL method to delete $backupFile."
$sql = "EXEC master.sys.xp_delete_file 0, '$backupFile'"
Write-Message -Level Debug -Message $sql
$null = $sourceServer.Query($sql)
}
catch {
Write-Message -Level Verbose -Message "Cannot delete backup file $backupFile."
}
}
}
}
if (Test-FunctionInterrupt) { return }
if ($null -ne $elapsed) {
$totalTime = ($elapsed.Elapsed.toString().Split(".")[0])
Write-Message -Level Verbose -Message "`nDatabase migration finished"
Write-Message -Level Verbose -Message "Migration started: $started"
Write-Message -Level Verbose -Message "Migration completed: $(Get-Date)"
Write-Message -Level Verbose -Message "Total Elapsed time: $totalTime"
if ($NetworkShare.length -gt 0 -and $NoBackupCleanup) {
Write-Message -Level Verbose -Message "Backups still exist at $NetworkShare."
}
}
else {
Write-Message -Level Verbose -Message "No work was done, as we stopped during setup phase"
}
Test-DbaDeprecation -DeprecatedOn "1.0.0" -EnableException:$false -Alias Copy-SqlDatabase
}
}
function Copy-DbaDataCollector {
<#
.SYNOPSIS
Migrates user SQL Data Collector collection sets. SQL Data Collector configuration is on the agenda, but it's hard.
.DESCRIPTION
By default, all data collector objects are migrated. If the object already exists on the destination, it will be skipped unless -Force is used.
The -CollectionSet parameter is auto-populated for command-line completion and can be used to copy only specific objects.
.PARAMETER Source
Source SQL Server. You must have sysadmin access and server version must be SQL Server version 2008 or higher.
.PARAMETER SourceSqlCredential
Login to the target instance using alternative credentials. Windows and SQL Authentication supported. Accepts credential objects (Get-Credential)
.PARAMETER Destination
Destination Sql Server. You must have sysadmin access and server version must be SQL Server version 2008 or higher.
.PARAMETER DestinationSqlCredential
Login to the target instance using alternative credentials. Windows and SQL Authentication supported. Accepts credential objects (Get-Credential)
.PARAMETER CollectionSet
The collection set(s) to process - this list is auto-populated from the server. If unspecified, all collection sets will be processed.
.PARAMETER ExcludeCollectionSet
The collection set(s) to exclude - this list is auto-populated from the server
.PARAMETER NoServerReconfig
Upcoming parameter to enable server reconfiguration
.PARAMETER WhatIf
Shows what would happen if the command were to run. No actions are actually performed.
.PARAMETER Confirm
Prompts you for confirmation before executing any changing operations within the command.
.PARAMETER Force
If collection sets exists on destination server, it will be dropped and recreated.
.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.
.NOTES
Tags: Migration,DataCollection
Author: Chrissy LeMaire (@cl), netnerds.net
Requires: sysadmin access on SQL Servers
Website: https://dbatools.io
Copyright: (C) Chrissy LeMaire, [email protected]
License: MIT https://opensource.org/licenses/MIT
.LINK
https://dbatools.io/Copy-DbaDataCollector
.EXAMPLE
Copy-DbaDataCollector -Source sqlserver2014a -Destination sqlcluster
Copies all Data Collector Objects and Configurations from sqlserver2014a to sqlcluster, using Windows credentials.
.EXAMPLE
Copy-DbaDataCollector -Source sqlserver2014a -Destination sqlcluster -SourceSqlCredential $cred
Copies all Data Collector Objects and Configurations from sqlserver2014a to sqlcluster, using SQL credentials for sqlserver2014a and Windows credentials for sqlcluster.
.EXAMPLE
Copy-DbaDataCollector -Source sqlserver2014a -Destination sqlcluster -WhatIf
Shows what would happen if the command were executed.
.EXAMPLE
Copy-DbaDataCollector -Source sqlserver2014a -Destination sqlcluster -CollectionSet 'Server Activity', 'Table Usage Analysis'
Copies two Collection Sets, Server Activity and Table Usage Analysis, from sqlserver2014a to sqlcluster.
#>
[CmdletBinding(DefaultParameterSetName = "Default", SupportsShouldProcess = $true)]
param (
[parameter(Mandatory)]
[DbaInstanceParameter]$Source,
[PSCredential]
$SourceSqlCredential,
[parameter(Mandatory)]
[DbaInstanceParameter[]]$Destination,
[PSCredential]
$DestinationSqlCredential,
[object[]]$CollectionSet,
[object[]]$ExcludeCollectionSet,
[switch]$NoServerReconfig,
[switch]$Force,
[Alias('Silent')]
[switch]$EnableException
)
begin {
try {
Write-Message -Level Verbose -Message "Connecting to $Source"
$sourceServer = Connect-SqlInstance -SqlInstance $Source -SqlCredential $SourceSqlCredential -MinimumVersion 10
}
catch {
Stop-Function -Message "Failure" -Category ConnectionError -ErrorRecord $_ -Target $Source
return
}
$sourceSqlConn = $sourceServer.ConnectionContext.SqlConnectionObject
$sourceSqlStoreConnection = New-Object Microsoft.SqlServer.Management.Sdk.Sfc.SqlStoreConnection $sourceSqlConn
$sourceStore = New-Object Microsoft.SqlServer.Management.Collector.CollectorConfigStore $sourceSqlStoreConnection
$configDb = $sourceStore.ScriptAlter().GetScript() | Out-String
$configDb = $configDb -replace [Regex]::Escape("'$source'"), "'$destReplace'"
}
process {
if (Test-FunctionInterrupt) { return }
foreach ($destinstance in $Destination) {
try {
Write-Message -Level Verbose -Message "Connecting to $destinstance"
$destServer = Connect-SqlInstance -SqlInstance $destinstance -SqlCredential $DestinationSqlCredential -MinimumVersion 10
}
catch {
Stop-Function -Message "Failure" -Category ConnectionError -ErrorRecord $_ -Target $destinstance -Continue
}
if ($NoServerReconfig -eq $false) {
if ($Pscmdlet.ShouldProcess($destinstance, "Server reconfiguration not yet supported. Only Collection Set migration will be migrated at this time.")) {
Write-Message -Level Verbose -Message "Server reconfiguration not yet supported. Only Collection Set migration will be migrated at this time."
$NoServerReconfig = $true
<# for future use when this support is added #>
$copyServerConfigStatus = [pscustomobject]@{
SourceServer = $sourceServer.Name
DestinationServer = $destServer.Name
Name = $userName
Type = "Data Collection Server Config"
Status = "Skipped"
Notes = "Not supported at this time"
DateTime = [DbaDateTime](Get-Date)
}
$copyServerConfigStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
}
}
$destSqlConn = $destServer.ConnectionContext.SqlConnectionObject
$destSqlStoreConnection = New-Object Microsoft.SqlServer.Management.Sdk.Sfc.SqlStoreConnection $destSqlConn
$destStore = New-Object Microsoft.SqlServer.Management.Collector.CollectorConfigStore $destSqlStoreConnection
if (!$NoServerReconfig) {
if ($Pscmdlet.ShouldProcess($destinstance, "Attempting to modify Data Collector configuration")) {
try {
$sql = "Unknown at this time"
$destServer.Query($sql)
$destStore.Alter()
}
catch {
$copyServerConfigStatus.Status = "Failed"
$copyServerConfigStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
Stop-Function -Message "Issue modifying Data Collector configuration" -Target $destServer -ErrorRecord $_
}
}
}
if ($destStore.Enabled -eq $false) {
Write-Message -Level Verbose -Message "The Data Collector must be setup initially for Collection Sets to be migrated. Setup the Data Collector and try again."
continue
}
$storeCollectionSets = $sourceStore.CollectionSets | Where-Object { $_.IsSystem -eq $false }
if ($CollectionSet) {
$storeCollectionSets = $storeCollectionSets | Where-Object Name -In $CollectionSet
}
if ($ExcludeCollectionSet) {
$storeCollectionSets = $storeCollectionSets | Where-Object Name -NotIn $ExcludeCollectionSet
}
Write-Message -Level Verbose -Message "Migrating collection sets"
foreach ($set in $storeCollectionSets) {
$collectionName = $set.Name
$copyCollectionSetStatus = [pscustomobject]@{
SourceServer = $sourceServer.Name
DestinationServer = $destServer.Name
Name = $collectionName
Type = "Collection Set"
Status = $null
Notes = $null
DateTime = [DbaDateTime](Get-Date)
}
if ($null -ne $destStore.CollectionSets[$collectionName]) {
if ($force -eq $false) {
if ($Pscmdlet.ShouldProcess($destinstance, "Collection Set '$collectionName' was skipped because it already exists on $destinstance. Use -Force to drop and recreate")) {
Write-Message -Level Verbose -Message "Collection Set '$collectionName' was skipped because it already exists on $destinstance. Use -Force to drop and recreate"
$copyCollectionSetStatus.Status = "Skipped"
$copyCollectionSetStatus.Notes = "Already exists"
$copyCollectionSetStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
}
continue
}
else {
if ($Pscmdlet.ShouldProcess($destinstance, "Attempting to drop $collectionName")) {
Write-Message -Level Verbose -Message "Collection Set '$collectionName' exists on $destinstance"
Write-Message -Level Verbose -Message "Force specified. Dropping $collectionName."
try {
$destStore.CollectionSets[$collectionName].Drop()
}
catch {
$copyCollectionSetStatus.Status = "Failed to drop on destination"
$copyCollectionSetStatus.Notes = (Get-ErrorMessage -Record $_)
$copyCollectionSetStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
Stop-Function -Message "Issue dropping collection" -Target $collectionName -ErrorRecord $_ -Continue
}
}
}
}
if ($Pscmdlet.ShouldProcess($destinstance, "Migrating collection set $collectionName")) {
try {
$sql = $set.ScriptCreate().GetScript() | Out-String
$sql = $sql -replace [Regex]::Escape("'$source'"), "'$destinstance'"
Write-Message -Level Debug -Message $sql
Write-Message -Level Verbose -Message "Migrating collection set $collectionName"
$destServer.Query($sql)
$copyCollectionSetStatus.Status = "Successful"
$copyCollectionSetStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
}
catch {
$copyCollectionSetStatus.Status = "Failed to create collection"
$copyCollectionSetStatus.Notes = (Get-ErrorMessage -Record $_)
Stop-Function -Message "Issue creating collection set" -Target $collectionName -ErrorRecord $_
}
try {
if ($set.IsRunning) {
Write-Message -Level Verbose -Message "Starting collection set $collectionName"
$destStore.CollectionSets.Refresh()
$destStore.CollectionSets[$collectionName].Start()
}
$copyCollectionSetStatus.Status = "Successful started Collection"
$copyCollectionSetStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
}
catch {
$copyCollectionSetStatus.Status = "Failed to start collection"
$copyCollectionSetStatus.Notes = (Get-ErrorMessage -Record $_)
$copyCollectionSetStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
Stop-Function -Message "Issue starting collection set" -Target $collectionName -ErrorRecord $_
}
}
}
}
}
end {
Test-DbaDeprecation -DeprecatedOn "1.0.0" -EnableException:$false -Alias Copy-SqlDataCollector
Test-DbaDeprecation -DeprecatedOn "1.0.0" -EnableException:$false -Alias Copy-DbaSqlDataCollector
if (Test-FunctionInterrupt) { return }
}
}
function Copy-DbaDbAssembly {
<#
.SYNOPSIS
Copy-DbaDbAssembly migrates assemblies from one SQL Server to another.
.DESCRIPTION
By default, all assemblies are copied.
If the assembly already exists on the destination, it will be skipped unless -Force is used.
This script does not yet copy dependencies or dependent objects.
.PARAMETER Source
Source SQL Server. You must have sysadmin access and server version must be SQL Server version 2000 or higher.
.PARAMETER SourceSqlCredential
Login to the target instance using alternative credentials. Windows and SQL Authentication supported. Accepts credential objects (Get-Credential)
.PARAMETER Destination
Destination SQL Server. You must have sysadmin access and the server must be SQL Server 2000 or higher.
.PARAMETER DestinationSqlCredential
Login to the target instance using alternative credentials. Windows and SQL Authentication supported. Accepts credential objects (Get-Credential)
.PARAMETER Assembly
The assembly(ies) to process. This list is auto-populated from the server. If unspecified, all assemblies will be processed.
.PARAMETER ExcludeAssembly
The assembly(ies) to exclude. This list is auto-populated from the server.
.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.
.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 Force
If this switch is enabled, existing assemblies on Destination with matching names from Source will be dropped.
.NOTES
Tags: Migration, Assembly
Author: Chrissy LeMaire (@cl), netnerds.net
Requires: sysadmin access on SQL Servers
Website: https://dbatools.io
Copyright: (C) Chrissy LeMaire, [email protected]
License: MIT https://opensource.org/licenses/MIT
.LINK
http://dbatools.io/Get-SqlDatabaseAssembly
.EXAMPLE
Copy-DbaDbAssembly -Source sqlserver2014a -Destination sqlcluster
Copies all assemblies from sqlserver2014a to sqlcluster using Windows credentials. If assemblies with the same name exist on sqlcluster, they will be skipped.
.EXAMPLE
Copy-DbaDbAssembly -Source sqlserver2014a -Destination sqlcluster -Assembly dbname.assemblyname, dbname3.anotherassembly -SourceSqlCredential $cred -Force
Copies two assemblies, the dbname.assemblyname and dbname3.anotherassembly from sqlserver2014a to sqlcluster using SQL credentials for sqlserver2014a and Windows credentials for sqlcluster. If an assembly with the same name exists on sqlcluster, it will be dropped and recreated because -Force was used.
In this example, anotherassembly will be copied to the dbname3 database on the server sqlcluster.
.EXAMPLE
Copy-DbaDbAssembly -Source sqlserver2014a -Destination sqlcluster -WhatIf -Force
Shows what would happen if the command were executed using force.
#>
[CmdletBinding(DefaultParameterSetName = "Default", SupportsShouldProcess = $true)]
param (
[parameter(Mandatory)]
[DbaInstanceParameter]$Source,
[PSCredential]$SourceSqlCredential,
[parameter(Mandatory)]
[DbaInstanceParameter[]]$Destination,
[PSCredential]$DestinationSqlCredential,
[object[]]$Assembly,
[object[]]$ExcludeAssembly,
[switch]$Force,
[Alias('Silent')]
[switch]$EnableException
)
begin {
try {
Write-Message -Level Verbose -Message "Connecting to $Source"
$sourceServer = Connect-SqlInstance -SqlInstance $Source -SqlCredential $SourceSqlCredential -MinimumVersion 9
}
catch {
Stop-Function -Message "Failure" -Category ConnectionError -ErrorRecord $_ -Target $Source
return
}
$sourceAssemblies = @()
foreach ($database in ($sourceServer.Databases | Where-Object IsAccessible)) {
Write-Message -Level Verbose -Message "Processing $database on source"
try {
# a bug here requires a try/catch
$userAssemblies = $database.Assemblies | Where-Object IsSystemObject -eq $false
foreach ($assembly in $userAssemblies) {
$sourceAssemblies += $assembly
}
}
catch { }
}
}
process {
if (Test-FunctionInterrupt) { return }
foreach ($destinstance in $Destination) {
try {
Write-Message -Level Verbose -Message "Connecting to $destinstance"
$destServer = Connect-SqlInstance -SqlInstance $destinstance -SqlCredential $DestinationSqlCredential -MinimumVersion 9
}
catch {
Stop-Function -Message "Failure" -Category ConnectionError -ErrorRecord $_ -Target $destinstance -Continue
}
$destAssemblies = @()
foreach ($database in $destServer.Databases) {
Write-Message -Level VeryVerbose -Message "Processing $database on destination"
try {
# a bug here requires a try/catch
$userAssemblies = $database.Assemblies | Where-Object IsSystemObject -eq $false
foreach ($assembly in $userAssemblies) {
$destAssemblies += $assembly
}
}
catch { }
}
foreach ($currentAssembly in $sourceAssemblies) {
$assemblyName = $currentAssembly.Name
$dbName = $currentAssembly.Parent.Name
$destDb = $destServer.Databases[$dbName]
Write-Message -Level VeryVerbose -Message "Processing $assemblyName on $dbname"
$copyDbAssemblyStatus = [pscustomobject]@{
SourceServer = $sourceServer.Name
SourceDatabase = $dbName
DestinationServer = $destServer.Name
DestinationDatabase = $destDb
type = "Database Assembly"
Name = $assemblyName
Status = $null
Notes = $null
DateTime = [Sqlcollaborative.Dbatools.Utility.DbaDateTime](Get-Date)
}
if (!$destDb) {
$copyDbAssemblyStatus.Status = "Skipped"
$copyDbAssemblyStatus.Notes = "Destination database does not exist"
$copyDbAssemblyStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
Write-Message -Level Verbose -Message "Destination database $dbName does not exist. Skipping $assemblyName.";
continue
}
if ((Test-Bound -ParameterName Assembly) -and $Assembly -notcontains "$dbName.$assemblyName" -or $ExcludeAssembly -contains "$dbName.$assemblyName") {
continue
}
if ($currentAssembly.AssemblySecurityLevel -eq "External" -and -not $destDb.Trustworthy) {
if ($Pscmdlet.ShouldProcess($destinstance, "Setting $dbName to External")) {
Write-Message -Level Verbose -Message "Setting $dbName Security Level to External on $destinstance."
$sql = "ALTER DATABASE $dbName SET TRUSTWORTHY ON"
try {
Write-Message -Level Debug -Message $sql
$destServer.Query($sql)
}
catch {
$copyDbAssemblyStatus.Status = "Failed"
$copyDbAssemblyStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
Stop-Function -Message "Issue setting security level." -Target $destDb -ErrorRecord $_
}
}
}
if ($destServer.Databases[$dbName].Assemblies.Name -contains $currentAssembly.name) {
if ($force -eq $false) {
$copyDbAssemblyStatus.Status = "Skipped"
$copyDbAssemblyStatus.Notes = "Already exists"
$copyDbAssemblyStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
Write-Message -Level Verbose -Message "Assembly $assemblyName exists at destination in the $dbName database. Use -Force to drop and migrate."
continue
}
else {
if ($Pscmdlet.ShouldProcess($destinstance, "Dropping assembly $assemblyName and recreating")) {
try {
Write-Message -Level Verbose -Message "Dropping assembly $assemblyName."
Write-Message -Level Verbose -Message "This won't work if there are dependencies."
$destServer.Databases[$dbName].Assemblies[$assemblyName].Drop()
Write-Message -Level Verbose -Message "Copying assembly $assemblyName."
$sql = $currentAssembly.Script()
Write-Message -Level Debug -Message $sql
$destServer.Query($sql, $dbName)
}
catch {
$copyDbAssemblyStatus.Status = "Failed"
$copyDbAssemblyStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
Stop-Function -Message "Issue dropping assembly." -Target $assemblyName -ErrorRecord $_ -Continue
}
}
}
}
if ($Pscmdlet.ShouldProcess($destinstance, "Creating assembly $assemblyName")) {
try {
Write-Message -Level Verbose -Message "Copying assembly $assemblyName from database."
$sql = $currentAssembly.Script()
Write-Message -Level Debug -Message $sql
$destServer.Query($sql, $dbName)
$copyDbAssemblyStatus.Status = "Successful"
$copyDbAssemblyStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
}
catch {
$copyDbAssemblyStatus.Status = "Failed"
$copyDbAssemblyStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
Stop-Function -Message "Issue creating assembly." -Target $assemblyName -ErrorRecord $_
}
}
}
}
}
end {
Test-DbaDeprecation -DeprecatedOn "1.0.0" -EnableException:$false -Alias Copy-SqlDatabaseAssembly
Test-DbaDeprecation -DeprecatedOn "1.0.0" -EnableException:$false -Alias Copy-DbaDatabaseAssembly
}
}
function Copy-DbaDbMail {
<#
.SYNOPSIS
Migrates Mail Profiles, Accounts, Mail Servers and Mail Server Configs from one SQL Server to another.
.DESCRIPTION
By default, all mail configurations for Profiles, Accounts, Mail Servers and Configs are copied.
.PARAMETER Source
Source SQL Server. You must have sysadmin access and server version must be SQL Server version 2000 or higher.
.PARAMETER SourceSqlCredential
Login to the target instance using alternative credentials. Windows and SQL Authentication supported. Accepts credential objects (Get-Credential)
.PARAMETER Destination
Destination SQL Server. You must have sysadmin access and the server must be SQL Server 2000 or higher.
.PARAMETER DestinationSqlCredential
Login to the target instance using alternative credentials. Windows and SQL Authentication supported. Accepts credential objects (Get-Credential)
.PARAMETER Type
Specifies the object type to migrate. Valid options are "Job", "Alert" and "Operator". When Type is specified, all categories from the selected type will be migrated.
.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.
.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 Force
If this switch is enabled, existing objects on Destination with matching names from Source will be dropped.
.NOTES
Tags: Migration, Mail
Author: Chrissy LeMaire (@cl), netnerds.net
Requires: sysadmin access on SQL Servers
Website: https://dbatools.io
Copyright: (C) Chrissy LeMaire, [email protected]
License: MIT https://opensource.org/licenses/MIT
.LINK
https://dbatools.io/Copy-DbaDbMail
.EXAMPLE
Copy-DbaDbMail -Source sqlserver2014a -Destination sqlcluster
Copies all database mail objects from sqlserver2014a to sqlcluster using Windows credentials. If database mail objects with the same name exist on sqlcluster, they will be skipped.
.EXAMPLE
Copy-DbaDbMail -Source sqlserver2014a -Destination sqlcluster -SourceSqlCredential $cred
Copies all database mail objects from sqlserver2014a to sqlcluster using SQL credentials for sqlserver2014a and Windows credentials for sqlcluster.
.EXAMPLE
Copy-DbaDbMail -Source sqlserver2014a -Destination sqlcluster -WhatIf
Shows what would happen if the command were executed.
.EXAMPLE
Copy-DbaDbMail -Source sqlserver2014a -Destination sqlcluster -EnableException
Performs execution of function, and will throw a terminating exception if something breaks
#>
[cmdletbinding(DefaultParameterSetName = "Default", SupportsShouldProcess = $true)]
param (
[parameter(Mandatory)]
[DbaInstanceParameter]$Source,
[parameter(Mandatory)]
[DbaInstanceParameter[]]$Destination,
[Parameter(ParameterSetName = 'SpecificTypes')]
[ValidateSet('ConfigurationValues', 'Profiles', 'Accounts', 'mailServers')]
[string[]]$Type,
[PSCredential]$SourceSqlCredential,
[PSCredential]$DestinationSqlCredential,
[switch]$Force,
[Alias('Silent')]
[switch]$EnableException
)
begin {
function Copy-DbaDbMailConfig {
[cmdletbinding(SupportsShouldProcess)]
param ()
Write-Message -Message "Migrating mail server configuration values." -Level Verbose
$copyMailConfigStatus = [pscustomobject]@{
SourceServer = $sourceServer.Name
DestinationServer = $destServer.Name
Name = "Server Configuration"
Type = "Mail Configuration"
Status = $null
Notes = $null
DateTime = [Sqlcollaborative.Dbatools.Utility.DbaDateTime](Get-Date)
}
if ($pscmdlet.ShouldProcess($destinstance, "Migrating all mail server configuration values.")) {
try {
$sql = $mail.ConfigurationValues.Script() | Out-String
$sql = $sql -replace [Regex]::Escape("'$source'"), "'$destinstance'"
Write-Message -Message $sql -Level Debug
$destServer.Query($sql) | Out-Null
$mail.ConfigurationValues.Refresh()
$copyMailConfigStatus.Status = "Successful"
}
catch {
$copyMailConfigStatus.Status = "Failed"
$copyMailConfigStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
Stop-Function -Message "Unable to migrate mail configuration." -Category InvalidOperation -InnerErrorRecord $_ -Target $destServer
}
$copyMailConfigStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
}
}
function Copy-DbaDatabaseAccount {
[cmdletbinding(SupportsShouldProcess)]
$sourceAccounts = $sourceServer.Mail.Accounts
$destAccounts = $destServer.Mail.Accounts
Write-Message -Message "Migrating accounts." -Level Verbose
foreach ($account in $sourceAccounts) {
$accountName = $account.name
$copyMailAccountStatus = [pscustomobject]@{
SourceServer = $sourceServer.Name
DestinationServer = $destServer.Name
Name = $accountName
Type = "Mail Account"
Status = $null
Notes = $null
DateTime = [Sqlcollaborative.Dbatools.Utility.DbaDateTime](Get-Date)
}
if ($accounts.count -gt 0 -and $accounts -notcontains $accountName) {
continue
}
if ($destAccounts.name -contains $accountName) {
if ($force -eq $false) {
If ($pscmdlet.ShouldProcess($destinstance, "Account $accountName exists at destination. Use -Force to drop and migrate.")) {
$copyMailAccountStatus.Status = "Skipped"
$copyMailAccountStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
Write-Message -Message "Account $accountName exists at destination. Use -Force to drop and migrate." -Level Verbose
}
continue
}
If ($pscmdlet.ShouldProcess($destinstance, "Dropping account $accountName and recreating.")) {
try {
Write-Message -Message "Dropping account $accountName." -Level Verbose
$destServer.Mail.Accounts[$accountName].Drop()
$destServer.Mail.Accounts.Refresh()
}
catch {
$copyMailAccountStatus.Status = "Failed"
$copyMailAccountStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
Stop-Function -Message "Issue dropping account." -Target $accountName -Category InvalidOperation -InnerErrorRecord $_ -Continue
}
}
}
if ($pscmdlet.ShouldProcess($destinstance, "Migrating account $accountName.")) {
try {
Write-Message -Message "Copying mail account $accountName." -Level Verbose
$sql = $account.Script() | Out-String
$sql = $sql -replace [Regex]::Escape("'$source'"), "'$destinstance'"
Write-Message -Message $sql -Level Debug
$destServer.Query($sql) | Out-Null
$copyMailAccountStatus.Status = "Successful"
}
catch {
$copyMailAccountStatus.Status = "Failed"
$copyMailAccountStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
Stop-Function -Message "Issue copying mail account." -Target $accountName -Category InvalidOperation -InnerErrorRecord $_
}
$copyMailAccountStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
}
}
}
function Copy-DbaDbMailProfile {
$sourceProfiles = $sourceServer.Mail.Profiles
$destProfiles = $destServer.Mail.Profiles
Write-Message -Message "Migrating mail profiles." -Level Verbose
foreach ($profile in $sourceProfiles) {
$profileName = $profile.name
$copyMailProfileStatus = [pscustomobject]@{
SourceServer = $sourceServer.Name
DestinationServer = $destServer.Name
Name = $profileName
Type = "Mail Profile"
Status = $null
Notes = $null
DateTime = [Sqlcollaborative.Dbatools.Utility.DbaDateTime](Get-Date)
}
if ($profiles.count -gt 0 -and $profiles -notcontains $profileName) {
continue
}
if ($destProfiles.name -contains $profileName) {
if ($force -eq $false) {
If ($pscmdlet.ShouldProcess($destinstance, "Profile $profileName exists at destination. Use -Force to drop and migrate.")) {
$copyMailProfileStatus.Status = "Skipped"
$copyMailProfileStatus.Notes = "Already exists"
$copyMailProfileStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
Write-Message -Message "Profile $profileName exists at destination. Use -Force to drop and migrate." -Level Verbose
}
continue
}
If ($pscmdlet.ShouldProcess($destinstance, "Dropping profile $profileName and recreating.")) {
try {
Write-Message -Message "Dropping profile $profileName." -Level Verbose
$destServer.Mail.Profiles[$profileName].Drop()
$destServer.Mail.Profiles.Refresh()
}
catch {
$copyMailProfileStatus.Status = "Failed"
$copyMailProfileStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
Stop-Function -Message "Issue dropping profile." -Target $profileName -Category InvalidOperation -InnerErrorRecord $_ -Continue
}
}
}
if ($pscmdlet.ShouldProcess($destinstance, "Migrating mail profile $profileName.")) {
try {
Write-Message -Message "Copying mail profile $profileName." -Level Verbose
$sql = $profile.Script() | Out-String
$sql = $sql -replace [Regex]::Escape("'$source'"), "'$destinstance'"
Write-Message -Message $sql -Level Debug
$destServer.Query($sql) | Out-Null
$destServer.Mail.Profiles.Refresh()
$copyMailProfileStatus.Status = "Successful"
}
catch {
$copyMailProfileStatus.Status = "Failed"
$copyMailProfileStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
Stop-Function -Message "Issue copying mail profile." -Target $profileName -Category InvalidOperation -InnerErrorRecord $_
}
$copyMailProfileStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
}
}
}
function Copy-DbaDbMailServer {
[cmdletbinding(SupportsShouldProcess)]
$sourceMailServers = $sourceServer.Mail.Accounts.MailServers
$destMailServers = $destServer.Mail.Accounts.MailServers
Write-Message -Message "Migrating mail servers." -Level Verbose
foreach ($mailServer in $sourceMailServers) {
$mailServerName = $mailServer.name
$copyMailServerStatus = [pscustomobject]@{
SourceServer = $sourceServer.Name
DestinationServer = $destServer.Name
Name = $mailServerName
Type = "Mail Server"
Status = $null
Notes = $null
DateTime = [Sqlcollaborative.Dbatools.Utility.DbaDateTime](Get-Date)
}
if ($mailServers.count -gt 0 -and $mailServers -notcontains $mailServerName) {
continue
}
if ($destMailServers.name -contains $mailServerName) {
if ($force -eq $false) {
if ($pscmdlet.ShouldProcess($destinstance, "Mail server $mailServerName exists at destination. Use -Force to drop and migrate.")) {
$copyMailServerStatus.Status = "Skipped"
$copyMailServerStatus.Notes = "Already exists"
$copyMailServerStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
Write-Message -Message "Mail server $mailServerName exists at destination. Use -Force to drop and migrate." -Level Verbose
}
continue
}
If ($pscmdlet.ShouldProcess($destinstance, "Dropping mail server $mailServerName and recreating.")) {
try {
Write-Message -Message "Dropping mail server $mailServerName." -Level Verbose
$destServer.Mail.Accounts.MailServers[$mailServerName].Drop()
}
catch {
$copyMailServerStatus.Status = "Failed"
$copyMailServerStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
Stop-Function -Message "Issue dropping mail server." -Target $mailServerName -Category InvalidOperation -InnerErrorRecord $_ -Continue
}
}
}
if ($pscmdlet.ShouldProcess($destinstance, "Migrating account mail server $mailServerName.")) {
try {
Write-Message -Message "Copying mail server $mailServerName." -Level Verbose
$sql = $mailServer.Script() | Out-String
$sql = $sql -replace [Regex]::Escape("'$source'"), "'$destinstance'"
Write-Message -Message $sql -Level Debug
$destServer.Query($sql) | Out-Null
$copyMailServerStatus.Status = "Successful"
}
catch {
$copyMailServerStatus.Status = "Failed"
$copyMailServerStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
Stop-Function -Message "Issue copying mail server" -Target $mailServerName -Category InvalidOperation -InnerErrorRecord $_
}
$copyMailServerStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
}
}
}
try {
Write-Message -Level Verbose -Message "Connecting to $Source"
$sourceServer = Connect-SqlInstance -SqlInstance $Source -SqlCredential $SourceSqlCredential -MinimumVersion 9
}
catch {
Stop-Function -Message "Failure" -Category ConnectionError -ErrorRecord $_ -Target $Source
return
}
$mail = $sourceServer.mail
}
process {
if (Test-FunctionInterrupt) { return }
foreach ($destinstance in $Destination) {
try {
Write-Message -Level Verbose -Message "Connecting to $destinstance"
$destServer = Connect-SqlInstance -SqlInstance $destinstance -SqlCredential $DestinationSqlCredential -MinimumVersion 9
}
catch {
Stop-Function -Message "Failure" -Category ConnectionError -ErrorRecord $_ -Target $destinstance -Continue
}
if ($type.Count -gt 0) {
switch ($type) {
"ConfigurationValues" {
Copy-DbaDbMailConfig
$destServer.Mail.ConfigurationValues.Refresh()
}
"Profiles" {
Copy-DbaDbMailProfile
$destServer.Mail.Profiles.Refresh()
}
"Accounts" {
Copy-DbaDatabaseAccount
$destServer.Mail.Accounts.Refresh()
}
"mailServers" {
Copy-DbaDbMailServer
}
}
continue
}
if (($profiles.count + $accounts.count + $mailServers.count) -gt 0) {
if ($profiles.count -gt 0) {
Copy-DbaDbMailProfile -Profiles $profiles
$destServer.Mail.Profiles.Refresh()
}
if ($accounts.count -gt 0) {
Copy-DbaDatabaseAccount -Accounts $accounts
$destServer.Mail.Accounts.Refresh()
}
if ($mailServers.count -gt 0) {
Copy-DbaDbMailServer -mailServers $mailServers
}
continue
}
Copy-DbaDbMailConfig
$destServer.Mail.ConfigurationValues.Refresh()
Copy-DbaDatabaseAccount
$destServer.Mail.Accounts.Refresh()
Copy-DbaDbMailProfile
$destServer.Mail.Profiles.Refresh()
Copy-DbaDbMailServer
$copyMailConfigStatus
$copyMailAccountStatus
$copyMailProfileStatus
$copyMailServerStatus
$enableDBMailStatus
<# ToDo: Use Get/Set-DbaSpConfigure once the dynamic parameters are replaced. #>
if (($sourceDbMailEnabled -eq 1) -and ($destDbMailEnabled -eq 0)) {
if ($pscmdlet.ShouldProcess($destinstance, "Enabling Database Mail")) {
$sourceDbMailEnabled = ($sourceServer.Configuration.DatabaseMailEnabled).ConfigValue
Write-Message -Message "$sourceServer DBMail configuration value: $sourceDbMailEnabled." -Level Verbose
$destDbMailEnabled = ($destServer.Configuration.DatabaseMailEnabled).ConfigValue
Write-Message -Message "$destServer DBMail configuration value: $destDbMailEnabled." -Level Verbose
$enableDBMailStatus = [pscustomobject]@{
SourceServer = $sourceServer.name
DestinationServer = $destServer.name
Name = "Enabled on Destination"
Type = "Mail Configuration"
Status = if ($destDbMailEnabled -eq 1) { "Enabled" } else { $null }
DateTime = [Sqlcollaborative.Dbatools.Utility.DbaDateTime](Get-Date)
}
try {
Write-Message -Message "Enabling Database Mail on $destServer." -Level Verbose
$destServer.Configuration.DatabaseMailEnabled.ConfigValue = 1
$destServer.Alter()
$enableDBMailStatus.Status = "Successful"
}
catch {
$enableDBMailStatus.Status = "Failed"
$enableDBMailStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
Stop-Function -Message "Cannot enable Database Mail." -Category InvalidOperation -ErrorRecord $_ -Target $destServer
}
$enableDBMailStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
}
}
}
}
end {
Test-DbaDeprecation -DeprecatedOn "1.0.0" -EnableException:$false -Alias Copy-SqlDatabaseMail
Test-DbaDeprecation -DeprecatedOn "1.0.0" -EnableException:$false -Alias Copy-DbaDatabaseMail
}
}
#ValidationTags#Messaging,FlowControl,Pipeline,CodeStyle#
function Copy-DbaDbTableData {
<#
.SYNOPSIS
Copies data between SQL Server tables.
.DESCRIPTION
Copies data between SQL Server tables using SQL Bulk Copy.
The same can be achieved also doing
$sourcetable = Invoke-SqlCmd2 -ServerInstance instance1 ... -As DataTable
Write-DbaDataTable -SqlInstance ... -InputObject $sourcetable
but it will force buffering the contents on the table in memory (high RAM usage for large tables).
With this function, a streaming copy will be done in the most speedy and least resource-intensive way.
.PARAMETER SqlInstance
Source SQL Server.You must have sysadmin access and server version must be SQL Server version 2000 or greater.
.PARAMETER SqlCredential
Login to the target instance using alternative credentials. Windows and SQL Authentication supported. Accepts credential objects (Get-Credential)
.PARAMETER Destination
Destination Sql Server. You must have sysadmin access and server version must be SQL Server version 2000 or greater.
.PARAMETER DestinationSqlCredential
Login to the target instance using alternative credentials. Windows and SQL Authentication supported. Accepts credential objects (Get-Credential)
.PARAMETER Database
The database to copy the table from.
.PARAMETER DestinationDatabase
The database to copy the table to. If not specified, it is assumed to be the same of Database
.PARAMETER Table
Define a specific table you would like to use as source. You can specify up to three-part name like db.sch.tbl.
If the object has special characters please wrap them in square brackets [ ].
This dbo.First.Table will try to find table named 'Table' on schema 'First' and database 'dbo'.
The correct way to find table named 'First.Table' on schema 'dbo' is passing dbo.[First.Table]
.PARAMETER DestinationTable
The table you want to use as destination. If not specified, it is assumed to be the same of Table
.PARAMETER Query
If you want to copy only a portion, specify the query (but please, select all the columns, or nasty things will happen)
.PARAMETER BatchSize
The BatchSize for the import defaults to 5000.
.PARAMETER NotifyAfter
Sets the option to show the notification after so many rows of import
.PARAMETER NoTableLock
If this switch is enabled, a table lock (TABLOCK) will not be placed on the destination table. By default, this operation will lock the destination table while running.
.PARAMETER CheckConstraints
If this switch is enabled, the SqlBulkCopy option to process check constraints will be enabled.
Per Microsoft "Check constraints while data is being inserted. By default, constraints are not checked."
.PARAMETER FireTriggers
If this switch is enabled, the SqlBulkCopy option to fire insert triggers will be enabled.
Per Microsoft "When specified, cause the server to fire the insert triggers for the rows being inserted into the Database."
.PARAMETER KeepIdentity
If this switch is enabled, the SqlBulkCopy option to preserve source identity values will be enabled.
Per Microsoft "Preserve source identity values. When not specified, identity values are assigned by the destination."
.PARAMETER KeepNulls
If this switch is enabled, the SqlBulkCopy option to preserve NULL values will be enabled.
Per Microsoft "Preserve null values in the destination table regardless of the settings for default values. When not specified, null values are replaced by default values where applicable."
.PARAMETER Truncate
If this switch is enabled, the destination table will be truncated after prompting for confirmation.
.PARAMETER BulkCopyTimeOut
Value in seconds for the BulkCopy operations timeout. The default is 30 seconds.
.PARAMETER InputObject
Enables piping of Table objects from Get-DbaDbTable
.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.
.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.
.NOTES
Tags: Migration
Author: niphlod (Simone Bizzotto)
Website: https://dbatools.io
Copyright: (C) Chrissy LeMaire, [email protected]
License: MIT https://opensource.org/licenses/MIT
.LINK
https://dbatools.io/Copy-DbaDbTableData
.EXAMPLE
Copy-DbaDbTableData -SqlInstance sql1 -Destination sql2 -Database dbatools_from -Table test_table
Copies all the data from sql1 to sql2, using the database dbatools_from.
.EXAMPLE
Copy-DbaDbTableData -SqlInstance sql1 -Destination sql2 -Database dbatools_from -DestinationDatabase dbatools_dest -Table test_table
Copies all the data from sql1 to sql2, using the database dbatools_from as source and dbatools_dest as destination
.EXAMPLE
Get-DbaDbTable -SqlInstance sql1 -Database tempdb -Table tb1, tb2 | Copy-DbaDbTableData -DestinationTable tb3
Copies all data from tables tb1 and tb2 in tempdb on sql1 to tb3 in tempdb on sql1
.EXAMPLE
Get-DbaDbTable -SqlInstance sql1 -Database tempdb -Table tb1, tb2 | Copy-DbaDbTableData -Destination sql2
Copies data from tbl1 in tempdb on sql1 to tbl1 in tempdb on sql2
then
Copies data from tbl2 in tempdb on sql1 to tbl2 in tempdb on sql2
.EXAMPLE
Copy-DbaDbTableData -SqlInstance sql1 -Destination sql2 -Database dbatools_from -Table test_table
Copies all the data from sql1 to sql2, using the database dbatools_from.
.EXAMPLE
Copy-DbaDbTableData -SqlInstance sql1 -Destination sql2 -Database dbatools_from -Table test_table -KeepIdentity -Truncate
Copies all the data from sql1 to sql2, using the database dbatools_from, keeping identity columns and truncating the destination
.EXAMPLE
Copy-DbaDbTableData -SqlInstance sql1 -Destination sql2 -Database dbatools_from -Table test_table -KeepIdentity -Truncate
Copies all the data from sql1 to sql2, using the database dbatools_from, keeping identity columns and truncating the destination
#>
[CmdletBinding(DefaultParameterSetName = "Default", SupportsShouldProcess = $true)]
param (
[Alias("ServerInstance", "SqlServer", "Source")]
[DbaInstanceParameter]$SqlInstance,
[PSCredential]$SqlCredential,
[DbaInstanceParameter]$Destination,
[PSCredential]$DestinationSqlCredential,
[string]$Database,
[string]$DestinationDatabase,
[string[]]$Table,
[string]$Query,
[int]$BatchSize = 50000,
[int]$NotifyAfter = 5000,
[string]$DestinationTable,
[switch]$NoTableLock,
[switch]$CheckConstraints,
[switch]$FireTriggers,
[switch]$KeepIdentity,
[switch]$KeepNulls,
[switch]$Truncate,
[int]$bulkCopyTimeOut = 5000,
[Parameter(ValueFromPipeline)]
[Microsoft.SqlServer.Management.Smo.Table[]]$InputObject,
[switch]$EnableException
)
begin {
# Getting the total rows copied is a challenge. Use SqlBulkCopyExtension.
# http://stackoverflow.com/questions/1188384/sqlbulkcopy-row-count-when-complete
$sourcecode = 'namespace System.Data.SqlClient {
using Reflection;
public static class SqlBulkCopyExtension
{
const String _rowsCopiedFieldName = "_rowsCopied";
static FieldInfo _rowsCopiedField = null;
public static int RowsCopiedCount(this SqlBulkCopy bulkCopy)
{
if (_rowsCopiedField == null) _rowsCopiedField = typeof(SqlBulkCopy).GetField(_rowsCopiedFieldName, BindingFlags.NonPublic | BindingFlags.GetField | BindingFlags.Instance);
return (int)_rowsCopiedField.GetValue(bulkCopy);
}
}
}'
Add-Type -ReferencedAssemblies System.Data.dll -TypeDefinition $sourcecode -ErrorAction SilentlyContinue
$bulkCopyOptions = 0
$options = "TableLock", "CheckConstraints", "FireTriggers", "KeepIdentity", "KeepNulls", "Default"
foreach ($option in $options) {
$optionValue = Get-Variable $option -ValueOnly -ErrorAction SilentlyContinue
if ($option -eq "TableLock" -and (!$NoTableLock)) {
$optionValue = $true
}
if ($optionValue -eq $true) {
$bulkCopyOptions += $([Data.SqlClient.SqlBulkCopyOptions]::$option).value__
}
}
}
process {
if ((Test-Bound -Not -ParameterName Table, SqlInstance) -and (Test-Bound -Not -ParameterName InputObject)) {
Stop-Function -Message "You must pipe in a table or specify SqlInstance, Database and Table."
return
}
if ($SqlInstance) {
if ((Test-Bound -Not -ParameterName Database)) {
Stop-Function -Message "Database is required when passing a SqlInstance" -Target $Table
return
}
try {
$server = Connect-SqlInstance -SqlInstance $SqlInstance -SqlCredential $SqlCredential
}
catch {
Stop-Function -Message "Failure" -Category ConnectionError -ErrorRecord $_ -Target $SqlInstance
return
}
if ($Database -notin $server.Databases.Name) {
Stop-Function -Message "Database $Database doesn't exist on $server"
return
}
try {
$InputObject += Get-DbaDbTable -SqlInstance $server -Table $Table -Database $Database -EnableException -Verbose:$false
}
catch {
Stop-Function -Message "Unable to determine source table : $Table"
return
}
}
foreach ($sqltable in $InputObject) {
$Database = $sqltable.Parent.Name
$server = $sqltable.Parent.Parent
if ((Test-Bound -Not -ParameterName DestinationDatabase)) {
$DestinationDatabase = $Database
}
if ((Test-Bound -Not -ParameterName DestinationTable)) {
$DestinationTable = $sqltable.Name
}
if ((Test-Bound -Not -ParameterName Destination)) {
$destServer = $server
}
else {
try {
$destServer = Connect-SqlInstance -SqlInstance $Destination -SqlCredential $DestinationSqlCredential
}
catch {
Stop-Function -Message "Failure" -Category ConnectionError -ErrorRecord $_ -Target $Destination
return
}
}
if ($DestinationDatabase -notin $destServer.Databases.Name) {
Stop-Function -Message "Database $DestinationDatabase doesn't exist on $destServer"
return
}
try {
$desttable = Get-DbaDbTable -SqlInstance $destServer -Table $DestinationTable -Database $DestinationDatabase -EnableException -Verbose:$false | Select-Object -First 1
}
catch {
Stop-Function -Message "Unable to determine destination table: $DestinationTable"
return
}
if (-not $desttable) {
Stop-Function -Message "$DestinationTable does not exist on destination"
return
}
$connstring = $destServer.ConnectionContext.ConnectionString
$fqtnfrom = "$($server.Databases[$Database]).$sqltable"
$fqtndest = "$($destServer.Databases[$DestinationDatabase]).$desttable"
if (Test-Bound -ParameterName Query -Not) {
$Query = "SELECT * FROM $fqtnfrom"
}
try {
if ($Truncate -eq $true) {
if ($Pscmdlet.ShouldProcess($destServer, "Truncating table $fqtndest")) {
$null = $destServer.Databases[$DestinationDatabase].ExecuteNonQuery("TRUNCATE TABLE $fqtndest")
}
}
$cmd = $server.ConnectionContext.SqlConnectionObject.CreateCommand()
$cmd.CommandText = $Query
if ($server.ConnectionContext.IsOpen -eq $false) {
$server.ConnectionContext.SqlConnectionObject.Open()
}
$bulkCopy = New-Object Data.SqlClient.SqlBulkCopy("$connstring;Database=$DestinationDatabase", $bulkCopyOptions)
$bulkCopy.DestinationTableName = $fqtndest
$bulkCopy.EnableStreaming = $true
$bulkCopy.BatchSize = $BatchSize
$bulkCopy.NotifyAfter = $NotifyAfter
$bulkCopy.BulkCopyTimeOut = $BulkCopyTimeOut
$elapsed = [System.Diagnostics.Stopwatch]::StartNew()
# Add RowCount output
$bulkCopy.Add_SqlRowsCopied({
$RowsPerSec = [math]::Round($args[1].RowsCopied / $elapsed.ElapsedMilliseconds * 1000.0, 1)
Write-Progress -id 1 -activity "Inserting rows" -Status ([System.String]::Format("{0} rows ({1} rows/sec)", $args[1].RowsCopied, $RowsPerSec))
})
if ($Pscmdlet.ShouldProcess($destServer, "Writing rows to $fqtndest")) {
$reader = $cmd.ExecuteReader()
$bulkCopy.WriteToServer($reader)
$RowsTotal = [System.Data.SqlClient.SqlBulkCopyExtension]::RowsCopiedCount($bulkCopy)
$TotalTime = [math]::Round($elapsed.Elapsed.TotalSeconds, 1)
Write-Message -Level Verbose -Message "$RowsTotal rows inserted in $TotalTime sec"
if ($rowCount -is [int]) {
Write-Progress -id 1 -activity "Inserting rows" -status "Complete" -Completed
}
}
$bulkCopy.Close()
$bulkCopy.Dispose()
$reader.Close()
[pscustomobject]@{
SourceInstance = $server.Name
SourceDatabase = $Database
SourceTable = $sqltable.Name
DestinationInstance = $destServer.name
DestinationDatabase = $DestinationDatabase
DestinationTable = $desttable.Name
RowsCopied = $rowstotal
Elapsed = [prettytimespan]$elapsed.Elapsed
}
}
catch {
Stop-Function -Message "Something went wrong" -ErrorRecord $_ -Target $server -continue
}
}
}
end {
Test-DbaDeprecation -DeprecatedOn "1.0.0" -EnableException:$false -Alias Copy-DbaTableData
}
}
function Copy-DbaEndpoint {
<#
.SYNOPSIS
Copy-DbaEndpoint migrates server endpoints from one SQL Server to another.
.DESCRIPTION
By default, all endpoints are copied.
If the endpoint already exists on the destination, it will be skipped unless -Force is used.
.PARAMETER Source
Source SQL Server. You must have sysadmin access and server version must be SQL Server version 2000 or higher.
.PARAMETER SourceSqlCredential
Login to the target instance using alternative credentials. Windows and SQL Authentication supported. Accepts credential objects (Get-Credential)
.PARAMETER Destination
Destination SQL Server. You must have sysadmin access and the server must be SQL Server 2000 or higher.
.PARAMETER DestinationSqlCredential
Login to the target instance using alternative credentials. Windows and SQL Authentication supported. Accepts credential objects (Get-Credential)
.PARAMETER Endpoint
The endpoint(s) to process. This list is auto-populated from the server. If unspecified, all endpoints will be processed.
.PARAMETER ExcludeEndpoint
The endpoint(s) to exclude. This list is auto-populated from the server.
.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.
.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 Force
If this switch is enabled, existing endpoints on Destination with matching names from Source will be dropped.
.NOTES
Tags: Migration, Endpoint
Author: Chrissy LeMaire (@cl), netnerds.net
Requires: sysadmin access on SQL Servers
Website: https://dbatools.io
Copyright: (C) Chrissy LeMaire, [email protected]
License: MIT https://opensource.org/licenses/MIT
.LINK
https://dbatools.io/Copy-DbaEndpoint
.EXAMPLE
Copy-DbaEndpoint -Source sqlserver2014a -Destination sqlcluster
Copies all server endpoints from sqlserver2014a to sqlcluster, using Windows credentials. If endpoints with the same name exist on sqlcluster, they will be skipped.
.EXAMPLE
Copy-DbaEndpoint -Source sqlserver2014a -SourceSqlCredential $cred -Destination sqlcluster -Endpoint tg_noDbDrop -Force
Copies only the tg_noDbDrop endpoint from sqlserver2014a to sqlcluster using SQL credentials for sqlserver2014a and Windows credentials for sqlcluster. If an endpoint with the same name exists on sqlcluster, it will be dropped and recreated because -Force was used.
.EXAMPLE
Copy-DbaEndpoint -Source sqlserver2014a -Destination sqlcluster -WhatIf -Force
Shows what would happen if the command were executed using force.
#>
[CmdletBinding(DefaultParameterSetName = "Default", SupportsShouldProcess = $true)]
param (
[parameter(Mandatory)]
[DbaInstanceParameter]$Source,
[PSCredential]
$SourceSqlCredential,
[parameter(Mandatory)]
[DbaInstanceParameter[]]$Destination,
[PSCredential]
$DestinationSqlCredential,
[object[]]$Endpoint,
[object[]]$ExcludeEndpoint,
[switch]$Force,
[Alias('Silent')]
[switch]$EnableException
)
begin {
try {
Write-Message -Level Verbose -Message "Connecting to $Source"
$sourceServer = Connect-SqlInstance -SqlInstance $Source -SqlCredential $SourceSqlCredential -MinimumVersion 9
}
catch {
Stop-Function -Message "Failure" -Category ConnectionError -ErrorRecord $_ -Target $Source
return
}
$serverEndpoints = $sourceServer.Endpoints | Where-Object IsSystemObject -eq $false
}
process {
if (Test-FunctionInterrupt) { return }
foreach ($destinstance in $Destination) {
try {
Write-Message -Level Verbose -Message "Connecting to $destinstance"
$destServer = Connect-SqlInstance -SqlInstance $destinstance -SqlCredential $DestinationSqlCredential -MinimumVersion 9
}
catch {
Stop-Function -Message "Failure" -Category ConnectionError -ErrorRecord $_ -Target $destinstance -Continue
}
$destEndpoints = $destServer.Endpoints
foreach ($currentEndpoint in $serverEndpoints) {
$endpointName = $currentEndpoint.Name
$copyEndpointStatus = [pscustomobject]@{
SourceServer = $sourceServer.Name
DestinationServer = $destServer.Name
Name = $endpointName
Type = "Endpoint"
Status = $null
Notes = $null
DateTime = [DbaDateTime](Get-Date)
}
if ($Endpoint -and $Endpoint -notcontains $endpointName -or $ExcludeEndpoint -contains $endpointName) {
continue
}
if ($destEndpoints.Name -contains $endpointName) {
if ($force -eq $false) {
$copyEndpointStatus.Status = "Skipped"
$copyEndpointStatus.Notes = "Already exists"
$copyEndpointStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
Write-Message -Level Verbose -Message "Server endpoint $endpointName exists at destination. Use -Force to drop and migrate."
continue
}
else {
if ($Pscmdlet.ShouldProcess($destinstance, "Dropping server endpoint $endpointName and recreating.")) {
try {
Write-Message -Level Verbose -Message "Dropping server endpoint $endpointName."
$destServer.Endpoints[$endpointName].Drop()
}
catch {
$copyEndpointStatus.Status = "Failed"
$copyEndpointStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
Stop-Function -Message "Issue dropping server endpoint." -Target $endpointName -ErrorRecord $_ -Continue
}
}
}
}
if ($Pscmdlet.ShouldProcess($destinstance, "Creating server endpoint $endpointName.")) {
try {
Write-Message -Level Verbose -Message "Copying server endpoint $endpointName."
$destServer.Query($currentEndpoint.Script()) | Out-Null
$copyEndpointStatus.Status = "Successful"
$copyEndpointStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
}
catch {
$copyEndpointStatus.Status = "Failed"
$copyEndpointStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
Stop-Function -Message "Issue creating server endpoint." -Target $endpointName -ErrorRecord $_
}
}
}
}
}
end {
Test-DbaDeprecation -DeprecatedOn "1.0.0" -EnableException:$false -Alias Copy-SqlEndpoint
}
}
function Copy-DbaExtendedEvent {
<#
.SYNOPSIS
Migrates SQL Extended Event Sessions except the two default sessions, AlwaysOn_health and system_health.
.DESCRIPTION
Migrates SQL Extended Event Sessions except the two default sessions, AlwaysOn_health and system_health.
By default, all non-system Extended Events are migrated.
.PARAMETER Source
Source SQL Server. You must have sysadmin access and server version must be SQL Server version 2000 or higher.
.PARAMETER SourceSqlCredential
Login to the target instance using alternative credentials. Windows and SQL Authentication supported. Accepts credential objects (Get-Credential)
.PARAMETER Destination
Destination SQL Server. You must have sysadmin access and the server must be SQL Server 2000 or higher.
.PARAMETER DestinationSqlCredential
Login to the target instance using alternative credentials. Windows and SQL Authentication supported. Accepts credential objects (Get-Credential)
.PARAMETER XeSession
The Extended Event Session(s) to process. This list is auto-populated from the server. If unspecified, all Extended Event Sessions will be processed.
.PARAMETER ExcludeXeSession
The Extended Event Session(s) to exclude. This list is auto-populated from the server.
.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.
.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 Force
If this switch is enabled, existing Extended Events sessions on Destination with matching names from Source will be dropped.
.NOTES
Tags: Migration, ExtendedEvent, XEvent
Author: Chrissy LeMaire (@cl), netnerds.net
Requires: sysadmin access on SQL Servers
Website: https://dbatools.io
Copyright: (C) Chrissy LeMaire, [email protected]
License: MIT https://opensource.org/licenses/MIT
.LINK
https://dbatools.io/Copy-DbaExtendedEvent
.EXAMPLE
Copy-DbaExtendedEvent -Source sqlserver2014a -Destination sqlcluster
Copies all Extended Event sessions from sqlserver2014a to sqlcluster using Windows credentials.
.EXAMPLE
Copy-DbaExtendedEvent -Source sqlserver2014a -Destination sqlcluster -SourceSqlCredential $cred
Copies all Extended Event sessions from sqlserver2014a to sqlcluster using SQL credentials for sqlserver2014a and Windows credentials for sqlcluster.
.EXAMPLE
Copy-DbaExtendedEvent -Source sqlserver2014a -Destination sqlcluster -WhatIf
Shows what would happen if the command were executed.
.EXAMPLE
Copy-DbaExtendedEvent -Source sqlserver2014a -Destination sqlcluster -XeSession CheckQueries, MonitorUserDefinedException
Copies only the Extended Events named CheckQueries and MonitorUserDefinedException from sqlserver2014a to sqlcluster.
#>
[CmdletBinding(DefaultParameterSetName = "Default", SupportsShouldProcess = $true)]
param (
[parameter(Mandatory)]
[DbaInstanceParameter]$Source,
[parameter(Mandatory)]
[DbaInstanceParameter[]]$Destination,
[PSCredential]
$SourceSqlCredential,
[PSCredential]
$DestinationSqlCredential,
[object[]]$XeSession,
[object[]]$ExcludeXeSession,
[switch]$Force,
[Alias('Silent')]
[switch]$EnableException
)
begin {
try {
Write-Message -Level Verbose -Message "Connecting to $Source"
$sourceServer = Connect-SqlInstance -SqlInstance $Source -SqlCredential $SourceSqlCredential -MinimumVersion 10
}
catch {
Stop-Function -Message "Failure" -Category ConnectionError -ErrorRecord $_ -Target $Source
return
}
$sourceSqlConn = $sourceServer.ConnectionContext.SqlConnectionObject
$sourceSqlStoreConnection = New-Object Microsoft.SqlServer.Management.Sdk.Sfc.SqlStoreConnection $sourceSqlConn
$sourceStore = New-Object Microsoft.SqlServer.Management.XEvent.XEStore $sourceSqlStoreConnection
$storeSessions = $sourceStore.Sessions | Where-Object { $_.Name -notin 'AlwaysOn_health', 'system_health' }
if ($XeSession) {
$storeSessions = $storeSessions | Where-Object Name -In $XeSession
}
if ($ExcludeXeSession) {
$storeSessions = $storeSessions | Where-Object Name -NotIn $ExcludeXeSession
}
}
process {
if (Test-FunctionInterrupt) { return }
foreach ($destinstance in $Destination) {
try {
Write-Message -Level Verbose -Message "Connecting to $destinstance"
$destServer = Connect-SqlInstance -SqlInstance $destinstance -SqlCredential $DestinationSqlCredential -MinimumVersion 10
}
catch {
Stop-Function -Message "Failure" -Category ConnectionError -ErrorRecord $_ -Target $destinstance -Continue
}
$destSqlConn = $destServer.ConnectionContext.SqlConnectionObject
$destSqlStoreConnection = New-Object Microsoft.SqlServer.Management.Sdk.Sfc.SqlStoreConnection $destSqlConn
$destStore = New-Object Microsoft.SqlServer.Management.XEvent.XEStore $destSqlStoreConnection
Write-Message -Level Verbose -Message "Migrating sessions."
foreach ($session in $storeSessions) {
$sessionName = $session.Name
$copyXeSessionStatus = [pscustomobject]@{
SourceServer = $sourceServer.Name
DestinationServer = $destServer.Name
Name = $sessionName
Type = "Extended Event"
Status = $null
Notes = $null
DateTime = [DbaDateTime](Get-Date)
}
if ($null -ne $destStore.Sessions[$sessionName]) {
if ($force -eq $false) {
if ($Pscmdlet.ShouldProcess($destinstance, "Extended Event Session '$sessionName' was skipped because it already exists on $destinstance.")) {
$copyXeSessionStatus.Status = "Skipped"
$copyXeSessionStatus.Notes = "Already exists"
$copyXeSessionStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
Write-Message -Level Verbose -Message "Extended Event Session '$sessionName' was skipped because it already exists on $destinstance."
Write-Message -Level Verbose -Message "Use -Force to drop and recreate."
}
continue
}
else {
if ($Pscmdlet.ShouldProcess($destinstance, "Attempting to drop $sessionName")) {
Write-Message -Level Verbose -Message "Extended Event Session '$sessionName' exists on $destinstance."
Write-Message -Level Verbose -Message "Force specified. Dropping $sessionName."
try {
$destStore.Sessions[$sessionName].Drop()
}
catch {
$copyXeSessionStatus.Status = "Failed"
$copyXeSessionStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
Stop-Function -Message "Unable to drop session. Moving on." -Target $sessionName -ErrorRecord $_ -Continue
}
}
}
}
if ($Pscmdlet.ShouldProcess($destinstance, "Migrating session $sessionName")) {
try {
$sql = $session.ScriptCreate().GetScript() | Out-String
Write-Message -Level Debug -Message $sql
Write-Message -Level Verbose -Message "Migrating session $sessionName."
$null = $destServer.Query($sql)
if ($session.IsRunning -eq $true) {
$destStore.Sessions.Refresh()
$destStore.Sessions[$sessionName].Start()
}
$copyXeSessionStatus.Status = "Successful"
$copyXeSessionStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
}
catch {
$copyXeSessionStatus.Status = "Failed"
$copyXeSessionStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
Stop-Function -Message "Unable to create session." -Target $sessionName -ErrorRecord $_
}
}
}
}
}
end {
Test-DbaDeprecation -DeprecatedOn "1.0.0" -EnableException:$false -Alias Copy-SqlExtendedEvent
}
}
function Copy-DbaLinkedServer {
<#
.SYNOPSIS
Copy-DbaLinkedServer migrates Linked Servers from one SQL Server to another. Linked Server logins and passwords are migrated as well.
.DESCRIPTION
By using password decryption techniques provided by Antti Rantasaari (NetSPI, 2014), this script migrates SQL Server Linked Servers from one server to another, while maintaining username and password.
Credit: https://blog.netspi.com/decrypting-mssql-database-link-server-passwords/
Website: https://dbatools.io
Copyright: (C) Chrissy LeMaire, [email protected]
License: MIT https://opensource.org/licenses/MIT
.PARAMETER Source
Source SQL Server (2005 and above). You must have sysadmin access to both SQL Server and Windows.
.PARAMETER SourceSqlCredential
Login to the target instance using alternative credentials. Windows and SQL Authentication supported. Accepts credential objects (Get-Credential)
.PARAMETER Destination
Destination SQL Server (2005 and above). You must have sysadmin access to both SQL Server and Windows.
.PARAMETER DestinationSqlCredential
Login to the target instance using alternative credentials. Windows and SQL Authentication supported. Accepts credential objects (Get-Credential)
.PARAMETER LinkedServer
The linked server(s) to process - this list is auto-populated from the server. If unspecified, all linked servers will be processed.
.PARAMETER ExcludeLinkedServer
The linked server(s) to exclude - this list is auto-populated from the server
.PARAMETER UpgradeSqlClient
Upgrade any SqlClient Linked Server to the current Version
.PARAMETER WhatIf
Shows what would happen if the command were to run. No actions are actually performed.
.PARAMETER Confirm
Prompts you for confirmation before executing any changing operations within the command.
.PARAMETER Force
By default, if a Linked Server exists on the source and destination, the Linked Server is not copied over. Specifying -force will drop and recreate the Linked Server on the Destination server.
.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.
.NOTES
Tags: WSMan, Migration, LinkedServer
Author: Chrissy LeMaire (@cl), netnerds.net
Requires: sysadmin access on SQL Servers, Remote Registry & Remote Administration enabled and accessible on source server.
Limitations: Hasn't been tested thoroughly. Works on Win8.1 and SQL Server 2012 & 2014 so far.
This just copies the SQL portion. It does not copy files (ie. a local SQLite database, or Microsoft Access DB), nor does it configure ODBC entries.
.LINK
https://dbatools.io/Copy-DbaLinkedServer
.EXAMPLE
Copy-DbaLinkedServer -Source sqlserver2014a -Destination sqlcluster
Copies all SQL Server Linked Servers on sqlserver2014a to sqlcluster. If Linked Server exists on destination, it will be skipped.
.EXAMPLE
Copy-DbaLinkedServer -Source sqlserver2014a -Destination sqlcluster -LinkedServer SQL2K5,SQL2k -Force
Copies over two SQL Server Linked Servers (SQL2K and SQL2K2) from sqlserver to sqlcluster. If the credential already exists on the destination, it will be dropped.
#>
[CmdletBinding(DefaultParameterSetName = "Default", SupportsShouldProcess = $true)]
Param (
[parameter(Mandatory)]
[DbaInstanceParameter]$Source,
[PSCredential]$SourceSqlCredential,
[parameter(Mandatory)]
[DbaInstanceParameter[]]$Destination,
[PSCredential]$DestinationSqlCredential,
[object[]]$LinkedServer,
[object[]]$ExcludeLinkedServer,
[switch]$UpgradeSqlClient,
[switch]$Force,
[Alias('Silent')]
[switch]$EnableException
)
begin {
$null = Test-ElevationRequirement -ComputerName $Source.ComputerName
function Copy-DbaLinkedServers {
param (
[string[]]$LinkedServer,
[bool]$force
)
Write-Message -Level Verbose -Message "Collecting Linked Server logins and passwords on $($sourceServer.Name)."
$sourcelogins = Get-DecryptedObject -SqlInstance $sourceServer -Type LinkedServer
$serverlist = $sourceServer.LinkedServers
if ($LinkedServer) {
$serverlist = $serverlist | Where-Object Name -In $LinkedServer
}
if ($ExcludeLinkedServer) {
$serverList = $serverlist | Where-Object Name -NotIn $ExcludeLinkedServer
}
foreach ($currentLinkedServer in $serverlist) {
$provider = $currentLinkedServer.ProviderName
try {
$destServer.LinkedServers.Refresh()
$destServer.LinkedServers.LinkedServerLogins.Refresh()
}
catch { }
$linkedServerName = $currentLinkedServer.Name
$copyLinkedServer = [pscustomobject]@{
SourceServer = $sourceServer.Name
DestinationServer = $destServer.Name
Name = $linkedServerName
Type = "Linked Server"
Status = $null
Notes = $provider
DateTime = [DbaDateTime](Get-Date)
}
# This does a check to warn of missing OleDbProviderSettings but should only be checked on SQL on Windows
if ($destServer.Settings.OleDbProviderSettings.Name.Length -ne 0) {
if (!$destServer.Settings.OleDbProviderSettings.Name -contains $provider -and !$provider.StartsWith("SQLN")) {
$copyLinkedServer.Status = "Skipped"
$copyLinkedServer.Notes = "Missing provider"
$copyLinkedServer | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
Write-Message -Level Verbose -Message "$($destServer.Name) does not support the $provider provider. Skipping $linkedServerName."
continue
}
}
if ($null -ne $destServer.LinkedServers[$linkedServerName]) {
if (!$force) {
if ($Pscmdlet.ShouldProcess($destinstance, "$linkedServerName exists $($destServer.Name). Skipping.")) {
$copyLinkedServer.Status = "Skipped"
$copyLinkedServer | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
Write-Message -Level Verbose -Message "$linkedServerName exists $($destServer.Name). Skipping."
}
continue
}
else {
if ($Pscmdlet.ShouldProcess($destinstance, "Dropping $linkedServerName")) {
if ($currentLinkedServer.Name -eq 'repl_distributor') {
Write-Message -Level Verbose -Message "repl_distributor cannot be dropped. Not going to try."
continue
}
$destServer.LinkedServers[$linkedServerName].Drop($true)
$destServer.LinkedServers.refresh()
}
}
}
Write-Message -Level Verbose -Message "Attempting to migrate: $linkedServerName."
If ($Pscmdlet.ShouldProcess($destinstance, "Migrating $linkedServerName")) {
try {
$sql = $currentLinkedServer.Script() | Out-String
Write-Message -Level Debug -Message $sql
if ($UpgradeSqlClient -and $sql -match "sqlncli") {
$destProviders = $destServer.Settings.OleDbProviderSettings | Where-Object { $_.Name -like 'SQLNCLI*' }
$newProvider = $destProviders | Sort-Object Name -Descending | Select-Object -First 1 -ExpandProperty Name
Write-Message -Level Verbose -Message "Changing sqlncli to $newProvider"
$sql = $sql -replace ("sqlncli[0-9]+", $newProvider)
}
$destServer.Query($sql)
$destServer.LinkedServers.Refresh()
Write-Message -Level Verbose -Message "$linkedServerName successfully copied."
$copyLinkedServer.Status = "Successful"
$copyLinkedServer | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
}
catch {
$copyLinkedServer.Status = "Failed"
$copyLinkedServer | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
Stop-Function -Message "Issue adding linked server $destServer." -Target $linkedServerName -InnerErrorRecord $_
$skiplogins = $true
}
}
if ($skiplogins -ne $true) {
$destlogins = $destServer.LinkedServers[$linkedServerName].LinkedServerLogins
$lslogins = $sourcelogins | Where-Object { $_.Name -eq $linkedServerName }
foreach ($login in $lslogins) {
if ($Pscmdlet.ShouldProcess($destinstance, "Migrating $($login.Login)")) {
$currentlogin = $destlogins | Where-Object { $_.RemoteUser -eq $login.Identity }
$copyLinkedServer.Type = $login.Identity
if ($currentlogin.RemoteUser.length -ne 0) {
try {
$currentlogin.SetRemotePassword($login.Password)
$currentlogin.Alter()
$copyLinkedServer.Status = "Successful"
$copyLinkedServer | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
}
catch {
$copyLinkedServer.Status = "Failed"
$copyLinkedServer | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
Stop-Function -Message "Failed to copy login." -Target $login -InnerErrorRecord $_
}
}
}
}
}
}
}
if ($null -ne $SourceSqlCredential.Username) {
Write-Message -Level Verbose -Message "You are using a SQL Credential. Note that this script requires Windows Administrator access on the source server. Attempting with $($SourceSqlCredential.Username)."
}
try {
Write-Message -Level Verbose -Message "Connecting to $Source"
$sourceServer = Connect-SqlInstance -SqlInstance $Source -SqlCredential $SourceSqlCredential
return
}
catch {
Stop-Function -Message "Failure" -Category ConnectionError -ErrorRecord $_ -Target $Source
return
}
if (!(Test-SqlSa -SqlInstance $sourceServer -SqlCredential $SourceSqlCredential)) {
Stop-Function -Message "Not a sysadmin on $source. Quitting." -Target $sourceServer
return
}
Write-Message -Level Verbose -Message "Getting NetBios name for $source."
$sourceNetBios = Resolve-NetBiosName $sourceserver
Write-Message -Level Verbose -Message "Checking if Remote Registry is enabled on $source."
try {
Invoke-Command2 -Raw -Credential $Credential -ComputerName $sourceNetBios -ScriptBlock { Get-ItemProperty -Path "HKLM:\SOFTWARE\" } -ErrorAction Stop
}
catch {
Stop-Function -Message "Can't connect to registry on $source." -Target $sourceNetBios -ErrorRecord $_
return
}
}
process {
if (Test-FunctionInterrupt) { return }
foreach ($destinstance in $Destination) {
try {
Write-Message -Level Verbose -Message "Connecting to $destinstance"
$destServer = Connect-SqlInstance -SqlInstance $destinstance -SqlCredential $DestinationSqlCredential
}
catch {
Stop-Function -Message "Failure" -Category ConnectionError -ErrorRecord $_ -Target $destinstance -Continue
}
if (!(Test-SqlSa -SqlInstance $destServer -SqlCredential $DestinationSqlCredential)) {
Stop-Function -Message "Not a sysadmin on $destinstance" -Target $destServer -Continue
}
# Magic happens here
Copy-DbaLinkedServers $LinkedServer -Force:$force
}
}
end {
Test-DbaDeprecation -DeprecatedOn "1.0.0" -EnableException:$false -Alias Copy-SqlLinkedServer
}
}
function Copy-DbaLogin {
<#
.SYNOPSIS
Migrates logins from source to destination SQL Servers. Supports SQL Server versions 2000 and newer.
.DESCRIPTION
SQL Server 2000: Migrates logins with SIDs, passwords, server roles and database roles.
SQL Server 2005 & newer: Migrates logins with SIDs, passwords, defaultdb, server roles & securables, database permissions & securables, login attributes (enforce password policy, expiration, etc.)
The login hash algorithm changed in SQL Server 2012, and is not backwards compatible with previous SQL Server versions. This means that while SQL Server 2000 logins can be migrated to SQL Server 2012, logins created in SQL Server 2012 can only be migrated to SQL Server 2012 and above.
.PARAMETER Source
Source SQL Server. You must have sysadmin access and server version must be SQL Server version 2000 or higher.
.PARAMETER SourceSqlCredential
Login to the target instance using alternative credentials. Windows and SQL Authentication supported. Accepts credential objects (Get-Credential)
.PARAMETER Destination
Destination SQL Server. You must have sysadmin access and the server must be SQL Server 2000 or higher.
.PARAMETER DestinationSqlCredential
Login to the target instance using alternative credentials. Windows and SQL Authentication supported. Accepts credential objects (Get-Credential)
.PARAMETER Login
The login(s) to process. Options for this list are auto-populated from the server. If unspecified, all logins will be processed.
.PARAMETER ExcludeLogin
The login(s) to exclude. Options for this list are auto-populated from the server.
.PARAMETER ExcludeSystemLogin
If this switch is enabled, NT SERVICE accounts will be skipped.
.PARAMETER SyncOnly
If this switch is enabled, only SQL Server login permissions, roles, etc. will be synced. Logins and users will not be added or dropped. If a matching Login does not exist on the destination, the Login will be skipped.
Credential removal is not currently supported for this parameter.
.PARAMETER SyncSaName
If this switch is enabled, the name of the sa account will be synced between Source and Destination
.PARAMETER OutFile
Calls Export-SqlLogin and exports all logins to a T-SQL formatted file. This does not perform a copy, so no destination is required.
.PARAMETER InputObject
Takes the parameters required from a Login object that has been piped into the command
.PARAMETER LoginRenameHashtable
Pass a hash table into this parameter to be passed into Rename-DbaLogin to update the Login and mappings after the Login is completed.
.PARAMETER KillActiveConnection
If this switch and -Force are enabled, all active connections and sessions on Destination will be killed.
A login cannot be dropped when it has active connections on the instance.
.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.
.PARAMETER Force
If this switch is enabled, the Login(s) will be dropped and recreated on Destination. Logins that own Agent jobs cannot be dropped at this time.
.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.
.NOTES
Tags: Migration, Login
Author: Chrissy LeMaire (@cl), netnerds.net
Requires: sysadmin access on SQL Servers
Website: https://dbatools.io
Copyright: (C) Chrissy LeMaire, [email protected]
License: MIT https://opensource.org/licenses/MIT
.LINK
https://dbatools.io/Copy-DbaLogin
.EXAMPLE
Copy-DbaLogin -Source sqlserver2014a -Destination sqlcluster -Force
Copies all logins from Source Destination. If a SQL Login on Source exists on the Destination, the Login on Destination will be dropped and recreated.
If active connections are found for a login, the copy of that Login will fail as it cannot be dropped.
.EXAMPLE
Copy-DbaLogin -Source sqlserver2014a -Destination sqlcluster -Force -KillActiveConnection
Copies all logins from Source Destination. If a SQL Login on Source exists on the Destination, the Login on Destination will be dropped and recreated.
If any active connections are found they will be killed.
.EXAMPLE
Copy-DbaLogin -Source sqlserver2014a -Destination sqlcluster -Exclude realcajun -SourceSqlCredential $scred -DestinationSqlCredential $dcred
Copies all Logins from Source to Destination except for realcajun using SQL Authentication to connect to both instances.
If a Login already exists on the destination, it will not be migrated.
.EXAMPLE
Copy-DbaLogin -Source sqlserver2014a -Destination sqlcluster -Login realcajun, netnerds -force
Copies ONLY Logins netnerds and realcajun. If Login realcajun or netnerds exists on Destination, the existing Login(s) will be dropped and recreated.
.EXAMPLE
Copy-DbaLogin -Source sqlserver2014a -Destination sqlcluster -SyncOnly
Syncs only SQL Server login permissions, roles, etc. Does not add or drop logins or users.
If a matching Login does not exist on Destination, the Login will be skipped.
.EXAMPLE
Copy-DbaLogin -LoginRenameHashtable @{ "OldUser" ="newlogin" } -Source $Sql01 -Destination Localhost -SourceSqlCredential $sqlcred
Copies OldUser and then renames it to newlogin.
.EXAMPLE
Get-DbaLogin -SqlInstance sql2016 | Out-GridView -Passthru | Copy-DbaLogin -Destination sql2017
Displays all available logins on sql2016 in a grid view, then copies all selected logins to sql2017.
#>
[CmdletBinding(DefaultParameterSetName = "Default", SupportsShouldProcess)]
Param (
[parameter(ParameterSetName = "SqlInstance", Mandatory)]
[DbaInstanceParameter]$Source,
[PSCredential]$SourceSqlCredential,
[parameter(Mandatory)]
[DbaInstanceParameter[]]$Destination,
[PSCredential]$DestinationSqlCredential,
[object[]]$Login,
[object[]]$ExcludeLogin,
[switch]$ExcludeSystemLogin,
[switch]$SyncOnly,
[parameter(ParameterSetName = "Live")]
[parameter(ParameterSetName = "SqlInstance")]
[switch]$SyncSaName,
[parameter(ParameterSetName = "File", Mandatory)]
[string]$OutFile,
[parameter(ParameterSetName = "InputObject", ValueFromPipeline)]
[object]$InputObject,
[hashtable]$LoginRenameHashtable,
[switch]$KillActiveConnection,
[switch]$Force,
[Alias('Silent')]
[switch]$EnableException
)
begin {
function Copy-Login {
foreach ($sourceLogin in $sourceServer.Logins) {
$userName = $sourceLogin.name
$copyLoginStatus = [pscustomobject]@{
SourceServer = $sourceServer.Name
DestinationServer = $destServer.Name
Type = "Login - $($sourceLogin.LoginType)"
Name = $userName
DestinationLogin = $userName
SourceLogin = $userName
Status = $null
Notes = $null
DateTime = [DbaDateTime](Get-Date)
}
if ($Login -and $Login -notcontains $userName -or $ExcludeLogin -contains $userName) { continue }
if ($sourceLogin.id -eq 1) { continue }
if ($userName.StartsWith("##") -or $userName -eq 'sa') {
Write-Message -Level Verbose -Message "Skipping $userName."
continue
}
$serverName = Resolve-NetBiosName $sourceServer
$currentLogin = $sourceServer.ConnectionContext.truelogin
if ($currentLogin -eq $userName -and $force) {
if ($Pscmdlet.ShouldProcess("console", "Stating $userName is skipped because it is performing the migration.")) {
Write-Message -Level Verbose -Message "Cannot drop login performing the migration. Skipping."
$copyLoginStatus.Status = "Skipped"
$copyLoginStatus.Notes = "Current login"
$copyLoginStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
}
continue
}
if (($destServer.LoginMode -ne [Microsoft.SqlServer.Management.Smo.ServerLoginMode]::Mixed) -and ($sourceLogin.LoginType -eq [Microsoft.SqlServer.Management.Smo.LoginType]::SqlLogin)) {
Write-Message -Level Verbose -Message "$Destination does not have Mixed Mode enabled. [$userName] is an SQL Login. Enable mixed mode authentication after the migration completes to use this type of login."
}
$userBase = ($userName.Split("\")[0]).ToLower()
if ($serverName -eq $userBase -or $userName.StartsWith("NT ")) {
if ($sourceServer.ComputerName -ne $destServer.ComputerName) {
if ($Pscmdlet.ShouldProcess("console", "Stating $userName was skipped because it is a local machine name.")) {
Write-Message -Level Verbose -Message "$userName was skipped because it is a local machine name."
$copyLoginStatus.Status = "Skipped"
$copyLoginStatus.Notes = "Local machine name"
$copyLoginStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
}
continue
}
else {
if ($ExcludeSystemLogin) {
if ($Pscmdlet.ShouldProcess("console", "$userName was skipped because ExcludeSystemLogin was specified.")) {
Write-Message -Level Verbose -Message "$userName was skipped because ExcludeSystemLogin was specified."
$copyLoginStatus.Status = "Skipped"
$copyLoginStatus.Notes = "System login"
$copyLoginStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
}
continue
}
if ($Pscmdlet.ShouldProcess("console", "Stating local login $userName since the source and destination server reside on the same machine.")) {
Write-Message -Level Verbose -Message "Copying local login $userName since the source and destination server reside on the same machine."
}
}
}
if ($null -ne $destServer.Logins.Item($userName) -and !$force) {
if ($Pscmdlet.ShouldProcess("console", "Stating $userName is skipped because it exists at destination.")) {
Write-Message -Level Verbose -Message "$userName already exists in destination. Use -Force to drop and recreate."
$copyLoginStatus.Status = "Skipped"
$copyLoginStatus.Notes = "Already exists"
$copyLoginStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
}
continue
}
if ($null -ne $destServer.Logins.Item($userName) -and $force) {
if ($userName -eq $destServer.ServiceAccount) {
if ($Pscmdlet.ShouldProcess("console", "$userName is the destination service account. Skipping drop.")) {
Write-Message -Level Verbose -Message "$userName is the destination service account. Skipping drop."
$copyLoginStatus.Status = "Skipped"
$copyLoginStatus.Notes = "Destination service account"
$copyLoginStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
}
continue
}
if ($Pscmdlet.ShouldProcess($destinstance, "Dropping $userName")) {
# Kill connections, delete user
Write-Message -Level Verbose -Message "Attempting to migrate $userName"
Write-Message -Level Verbose -Message "Force was specified. Attempting to drop $userName on $destinstance."
try {
$ownedDbs = $destServer.Databases | Where-Object Owner -eq $userName
foreach ($ownedDb in $ownedDbs) {
Write-Message -Level Verbose -Message "Changing database owner for $($ownedDb.name) from $userName to sa."
$ownedDb.SetOwner('sa')
$ownedDb.Alter()
}
$ownedJobs = $destServer.JobServer.Jobs | Where-Object OwnerLoginName -eq $userName
foreach ($ownedJob in $ownedJobs) {
Write-Message -Level Verbose -Message "Changing job owner for $($ownedJob.name) from $userName to sa."
$ownedJob.Set_OwnerLoginName('sa')
$ownedJob.Alter()
}
$activeConnections = $destServer.EnumProcesses() | Where-Object Login -eq $userName
if ($activeConnections -and $KillActiveConnection) {
if (!$destServer.Logins.Item($userName).IsDisabled) {
$disabled = $true
$destServer.Logins.Item($userName).Disable()
}
$activeConnections | ForEach-Object { $destServer.KillProcess($_.Spid) }
Write-Message -Level Verbose -Message "-KillActiveConnection was provided. There are $($activeConnections.Count) active connections killed."
# just in case the kill didn't work, it'll leave behind a disabled account
if ($disabled) { $destServer.Logins.Item($userName).Enable() }
}
elseif ($activeConnections) {
Write-Message -Level Verbose -Message "There are $($activeConnections.Count) active connections found for the login $userName. Utilize -KillActiveConnection with -Force to kill the connections."
}
$destServer.Logins.Item($userName).Drop()
Write-Message -Level Verbose -Message "Successfully dropped $userName on $destinstance."
}
catch {
$copyLoginStatus.Status = "Failed"
$copyLoginStatus.Notes = (Get-ErrorMessage -Record $_).Message
$copyLoginStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
Stop-Function -Message "Could not drop $userName." -Category InvalidOperation -ErrorRecord $_ -Target $destServer -Continue 3>$null
}
}
}
if ($Pscmdlet.ShouldProcess($destinstance, "Adding SQL login $userName")) {
Write-Message -Level Verbose -Message "Attempting to add $userName to $destinstance."
$destLogin = New-Object Microsoft.SqlServer.Management.Smo.Login($destServer, $userName)
Write-Message -Level Verbose -Message "Setting $userName SID to source username SID."
$destLogin.Set_Sid($sourceLogin.Get_Sid())
$defaultDb = $sourceLogin.DefaultDatabase
Write-Message -Level Verbose -Message "Setting login language to $($sourceLogin.Language)."
$destLogin.Language = $sourceLogin.Language
if ($null -eq $destServer.databases[$defaultDb]) {
# we end up here when the default database on source doesn't exist on dest
# if source login is a sysadmin, then set the default database to master
# if not, set it to tempdb (see #303)
$OrigdefaultDb = $defaultDb
try { $sourcesysadmins = $sourceServer.roles['sysadmin'].EnumMemberNames() }
catch { $sourcesysadmins = $sourceServer.roles['sysadmin'].EnumServerRoleMembers() }
if ($sourcesysadmins -contains $userName) {
$defaultDb = "master"
}
else {
$defaultDb = "tempdb"
}
Write-Message -Level Verbose -Message "$OrigdefaultDb does not exist on destination. Setting defaultdb to $defaultDb."
}
Write-Message -Level Verbose -Message "Set $userName defaultdb to $defaultDb."
$destLogin.DefaultDatabase = $defaultDb
$checkexpiration = "ON"; $checkpolicy = "ON"
if ($sourceLogin.PasswordPolicyEnforced -eq $false) { $checkpolicy = "OFF" }
if (!$sourceLogin.PasswordExpirationEnabled) { $checkexpiration = "OFF" }
$destLogin.PasswordPolicyEnforced = $sourceLogin.PasswordPolicyEnforced
$destLogin.PasswordExpirationEnabled = $sourceLogin.PasswordExpirationEnabled
# Attempt to add SQL Login User
if ($sourceLogin.LoginType -eq "SqlLogin") {
$destLogin.LoginType = "SqlLogin"
$sourceLoginname = $sourceLogin.name
switch ($sourceServer.versionMajor) {
0 { $sql = "SELECT CONVERT(VARBINARY(256),password) as hashedpass FROM master.dbo.syslogins WHERE loginname='$sourceLoginname'" }
8 { $sql = "SELECT CONVERT(VARBINARY(256),password) as hashedpass FROM dbo.syslogins WHERE name='$sourceLoginname'" }
9 { $sql = "SELECT CONVERT(VARBINARY(256),password_hash) as hashedpass FROM sys.sql_logins where name='$sourceLoginname'" }
default {
$sql = "SELECT CAST(CONVERT(VARCHAR(256), CAST(LOGINPROPERTY(name,'PasswordHash')
AS VARBINARY(256)), 1) AS NVARCHAR(max)) AS hashedpass FROM sys.server_principals
WHERE principal_id = $($sourceLogin.id)"
}
}
try {
$hashedPass = $sourceServer.ConnectionContext.ExecuteScalar($sql)
}
catch {
$hashedPassDt = $sourceServer.Databases['master'].ExecuteWithResults($sql)
$hashedPass = $hashedPassDt.Tables[0].Rows[0].Item(0)
}
if ($hashedPass.GetType().Name -ne "String") {
$passString = "0x"; $hashedPass | ForEach-Object { $passString += ("{0:X}" -f $_).PadLeft(2, "0") }
$hashedPass = $passString
}
try {
$destLogin.Create($hashedPass, [Microsoft.SqlServer.Management.Smo.LoginCreateOptions]::IsHashed)
$destLogin.Refresh()
Write-Message -Level Verbose -Message "Successfully added $userName to $destinstance."
$copyLoginStatus.Status = "Successful"
$copyLoginStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
}
catch {
try {
$sid = "0x"; $sourceLogin.sid | ForEach-Object { $sid += ("{0:X}" -f $_).PadLeft(2, "0") }
$sql = "CREATE LOGIN [$userName] WITH PASSWORD = $hashedPass HASHED, SID = $sid,
DEFAULT_DATABASE = [$defaultDb], CHECK_POLICY = $checkpolicy,
CHECK_EXPIRATION = $checkexpiration, DEFAULT_LANGUAGE = [$($sourceLogin.Language)]"
$null = $destServer.Query($sql)
$destLogin = $destServer.logins[$userName]
Write-Message -Level Verbose -Message "Successfully added $userName to $destinstance."
$copyLoginStatus.Status = "Successful"
$copyLoginStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
}
catch {
$copyLoginStatus.Status = "Failed"
$copyLoginStatus.Notes = (Get-ErrorMessage -Record $_).Message
$copyLoginStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
Stop-Function -Message "Failed to add $userName to $destinstance." -Category InvalidOperation -ErrorRecord $_ -Target $destServer -Continue 3>$null
}
}
}
# Attempt to add Windows User
elseif ($sourceLogin.LoginType -eq "WindowsUser" -or $sourceLogin.LoginType -eq "WindowsGroup") {
Write-Message -Level Verbose -Message "Adding as login type $($sourceLogin.LoginType)"
$destLogin.LoginType = $sourceLogin.LoginType
Write-Message -Level Verbose -Message "Setting language as $($sourceLogin.Language)"
$destLogin.Language = $sourceLogin.Language
try {
$destLogin.Create()
$destLogin.Refresh()
Write-Message -Level Verbose -Message "Successfully added $userName to $destinstance."
$copyLoginStatus.Status = "Successful"
$copyLoginStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
}
catch {
$copyLoginStatus.Status = "Failed"
$copyLoginStatus.Notes = (Get-ErrorMessage -Record $_).Message
$copyLoginStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
Stop-Function -Message "Failed to add $userName to $destinstance" -Category InvalidOperation -ErrorRecord $_ -Target $destServer -Continue 3>$null
}
}
# This script does not currently support certificate mapped or asymmetric key users.
else {
Write-Message -Level Verbose -Message "$($sourceLogin.LoginType) logins not supported. $($sourceLogin.name) skipped."
$copyLoginStatus.Status = "Skipped"
$copyLoginStatus.Notes = "$($sourceLogin.LoginType) not supported"
$copyLoginStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
continue
}
if ($sourceLogin.IsDisabled) {
try {
$destLogin.Disable()
}
catch {
$copyLoginStatus.Status = "Successful - but could not disable on destination"
$copyLoginStatus.Notes = (Get-ErrorMessage -Record $_).Message
$copyLoginStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
Stop-Function -Message "$userName disabled on source, could not be disabled on $destinstance." -Category InvalidOperation -ErrorRecord $_ -Target $destServer 3>$null
}
}
if ($sourceLogin.DenyWindowsLogin) {
try {
$destLogin.DenyWindowsLogin = $true
}
catch {
$copyLoginStatus.Status = "Successful - but could not deny login on destination"
$copyLoginStatus.Notes = (Get-ErrorMessage -Record $_).Message
$copyLoginStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
Stop-Function -Message "$userName denied login on source, could not be denied login on $destinstance." -Category InvalidOperation -ErrorRecord $_ -Target $destServer 3>$null
}
}
}
if ($Pscmdlet.ShouldProcess($destinstance, "Updating SQL login $userName permissions")) {
Update-SqlPermissions -sourceserver $sourceServer -sourcelogin $sourceLogin -destserver $destServer -destlogin $destLogin
}
if ($LoginRenameHashtable.Keys -contains $userName) {
$NewLogin = $LoginRenameHashtable[$userName]
if ($Pscmdlet.ShouldProcess($destinstance, "Renaming SQL Login $userName to $NewLogin")) {
try {
Rename-DbaLogin -SqlInstance $destServer -Login $userName -NewLogin $NewLogin
$copyLoginStatus.DestinationLogin = $NewLogin
$copyLoginStatus.Status = "Successful"
$copyLoginStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
}
catch {
$copyLoginStatus.DestinationLogin = $NewLogin
$copyLoginStatus.Status = "Failed to rename"
$copyLoginStatus.Notes = (Get-ErrorMessage -Record $_).Message
$copyLoginStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
Stop-Function -Message "Issue renaming $userName to $NewLogin" -Category InvalidOperation -ErrorRecord $_ -Target $destServer 3>$null
}
}
}
}
}
}
process {
if (Test-FunctionInterrupt) { return }
if ($InputObject) {
$Source = $InputObject[0].Parent.Name
$Sourceserver = $InputObject[0].Parent
$Login = $InputObject.Name
}
else {
try {
Write-Message -Level Verbose -Message "Connecting to $Source"
$sourceServer = Connect-SqlInstance -SqlInstance $Source -SqlCredential $SourceSqlCredential
}
catch {
Stop-Function -Message "Failure" -Category ConnectionError -ErrorRecord $_ -Target $Source
return
}
}
$sourceVersionMajor = $sourceServer.VersionMajor
if ($OutFile) {
Export-DbaLogin -SqlInstance $sourceServer -FilePath $OutFile -Login $Login -ExcludeLogin $ExcludeLogin
continue
}
foreach ($destinstance in $Destination) {
try {
Write-Message -Level Verbose -Message "Connecting to $destinstance"
$destServer = Connect-SqlInstance -SqlInstance $destinstance -SqlCredential $DestinationSqlCredential
}
catch {
Stop-Function -Message "Failure" -Category ConnectionError -ErrorRecord $_ -Target $destinstance -Continue
}
$destVersionMajor = $destServer.VersionMajor
if ($sourceVersionMajor -gt 10 -and $destVersionMajor -lt 11) {
Stop-Function -Message "Login migration from version $sourceVersionMajor to $destVersionMajor is not supported." -Category InvalidOperation -ErrorRecord $_ -Target $sourceServer
}
if ($sourceVersionMajor -lt 8 -or $destVersionMajor -lt 8) {
Stop-Function -Message "SQL Server 7 and below are not supported." -Category InvalidOperation -ErrorRecord $_ -Target $sourceServer
}
if ($SyncOnly) {
if ($Pscmdlet.ShouldProcess($destinstance, "Syncing $Login permissions")) {
Sync-DbaLoginPermission -Source $sourceServer -Destination $destServer -Login $Login -ExcludeLogin $ExcludeLogin
continue
}
}
Write-Message -Level Verbose -Message "Attempting Login Migration."
Copy-Login -sourceserver $sourceServer -destserver $destServer -Login $Login -Exclude $ExcludeLogin
if ($SyncSaName) {
$sa = $sourceServer.Logins | Where-Object id -eq 1
$destSa = $destServer.Logins | Where-Object id -eq 1
$saName = $sa.Name
if ($saName -ne $destSa.name) {
Write-Message -Level Verbose -Message "Changing sa username to match source ($saName)."
if ($Pscmdlet.ShouldProcess($destinstance, "Changing sa username to match source ($saName)")) {
$destSa.Rename($saName)
$destSa.Alter()
}
}
}
}
}
end {
Test-DbaDeprecation -DeprecatedOn "1.0.0" -EnableException:$false -Alias Copy-SqlLogin
}
}
function Copy-DbaPolicyManagement {
<#
.SYNOPSIS
Migrates SQL Policy Based Management Objects, including both policies and conditions.
.DESCRIPTION
By default, all policies and conditions are copied. If an object already exist on the destination, it will be skipped unless -Force is used.
The -Policy and -Condition parameters are auto-populated for command-line completion and can be used to copy only specific objects.
.PARAMETER Source
Source SQL Server.You must have sysadmin access and server version must be SQL Server version 2008 or higher.
.PARAMETER SourceSqlCredential
Login to the target instance using alternative credentials. Windows and SQL Authentication supported. Accepts credential objects (Get-Credential)
.PARAMETER Destination
Destination Sql Server. You must have sysadmin access and server version must be SQL Server version 2008 or higher.
.PARAMETER DestinationSqlCredential
Login to the target instance using alternative credentials. Windows and SQL Authentication supported. Accepts credential objects (Get-Credential)
.PARAMETER Policy
The policy(ies) to process - this list is auto-populated from the server. If unspecified, all policies will be processed.
.PARAMETER ExcludePolicy
The policy(ies) to exclude - this list is auto-populated from the server
.PARAMETER Condition
The condition(s) to process - this list is auto-populated from the server. If unspecified, all conditions will be processed.
.PARAMETER ExcludeCondition
The condition(s) to exclude - this list is auto-populated from the server
.PARAMETER Force
If policies exists on destination server, it will be dropped and recreated.
.PARAMETER WhatIf
Shows what would happen if the command were to run. No actions are actually performed.
.PARAMETER Confirm
Prompts you for confirmation before executing any changing operations within the command.
.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.
.NOTES
Tags: Migration
Author: Chrissy LeMaire (@cl), netnerds.net
Requires: sysadmin access on SQL Servers
Website: https://dbatools.io
Copyright: (C) Chrissy LeMaire, [email protected]
License: MIT https://opensource.org/licenses/MIT
.LINK
https://dbatools.io/Copy-DbaPolicyManagement
.EXAMPLE
Copy-DbaPolicyManagement -Source sqlserver2014a -Destination sqlcluster
Copies all policies and conditions from sqlserver2014a to sqlcluster, using Windows credentials.
.EXAMPLE
Copy-DbaPolicyManagement -Source sqlserver2014a -Destination sqlcluster -SourceSqlCredential $cred
Copies all policies and conditions from sqlserver2014a to sqlcluster, using SQL credentials for sqlserver2014a and Windows credentials for sqlcluster.
.EXAMPLE
Copy-DbaPolicyManagement -Source sqlserver2014a -Destination sqlcluster -WhatIf
Shows what would happen if the command were executed.
.EXAMPLE
Copy-DbaPolicyManagement -Source sqlserver2014a -Destination sqlcluster -Policy 'xp_cmdshell must be disabled'
Copies only one policy, 'xp_cmdshell must be disabled' from sqlserver2014a to sqlcluster. No conditions are migrated.
#>
[CmdletBinding(DefaultParameterSetName = "Default", SupportsShouldProcess = $true)]
param (
[parameter(Mandatory)]
[DbaInstanceParameter]$Source,
[PSCredential]
$SourceSqlCredential,
[parameter(Mandatory)]
[DbaInstanceParameter[]]$Destination,
[PSCredential]
$DestinationSqlCredential,
[object[]]$Policy,
[object[]]$ExcludePolicy,
[object[]]$Condition,
[object[]]$ExcludeCondition,
[switch]$Force,
[Alias('Silent')]
[switch]$EnableException
)
begin {
try {
Write-Message -Level Verbose -Message "Connecting to $Source"
$sourceServer = Connect-SqlInstance -SqlInstance $Source -SqlCredential $SourceSqlCredential -MinimumVersion 10
}
catch {
Stop-Function -Message "Failure" -Category ConnectionError -ErrorRecord $_ -Target $Source
return
}
$sourceSqlConn = $sourceServer.ConnectionContext.SqlConnectionObject
$sourceSqlStoreConnection = New-Object Microsoft.SqlServer.Management.Sdk.Sfc.SqlStoreConnection $sourceSqlConn
$sourceStore = New-Object Microsoft.SqlServer.Management.DMF.PolicyStore $sourceSqlStoreConnection
$storePolicies = $sourceStore.Policies | Where-Object { $_.IsSystemObject -eq $false }
$storeConditions = $sourceStore.Conditions | Where-Object { $_.IsSystemObject -eq $false }
}
process {
if (Test-FunctionInterrupt) { return }
foreach ($destinstance in $Destination) {
try {
Write-Message -Level Verbose -Message "Connecting to $destinstance"
$destServer = Connect-SqlInstance -SqlInstance $destinstance -SqlCredential $DestinationSqlCredential -MinimumVersion 10
}
catch {
Stop-Function -Message "Failure" -Category ConnectionError -ErrorRecord $_ -Target $destinstance -Continue
}
$destSqlConn = $destServer.ConnectionContext.SqlConnectionObject
$destSqlStoreConnection = New-Object Microsoft.SqlServer.Management.Sdk.Sfc.SqlStoreConnection $destSqlConn
$destStore = New-Object Microsoft.SqlServer.Management.DMF.PolicyStore $destSqlStoreConnection
if ($Policy) {
$storePolicies = $storePolicies | Where-Object Name -In $Policy
}
if ($ExcludePolicy) {
$storePolicies = $storePolicies | Where-Object Name -NotIn $ExcludePolicy
}
if ($Condition) {
$storeConditions = $storeConditions | Where-Object Name -In $Condition
}
if ($ExcludeCondition) {
$storeConditions = $storeConditions | Where-Object Name -NotIn $ExcludeCondition
}
if ($Policy -and $Condition) {
$storeConditions = $null
$storePolicies = $null
}
<#
Conditions
#>
Write-Message -Level Verbose -Message "Migrating conditions"
foreach ($condition in $storeConditions) {
$conditionName = $condition.Name
$copyConditionStatus = [pscustomobject]@{
SourceServer = $sourceServer.Name
DestinationServer = $destServer.Name
Name = $conditionName
Type = "Policy Condition"
Status = $null
Notes = $null
DateTime = [DbaDateTime](Get-Date)
}
if ($null -ne $destStore.Conditions[$conditionName]) {
if ($force -eq $false) {
Write-Message -Level Verbose -Message "condition '$conditionName' was skipped because it already exists on $destinstance. Use -Force to drop and recreate"
$copyConditionStatus.Status = "Skipped"
$copyConditionStatus.Notes = "Already exists"
$copyConditionStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
continue
}
else {
if ($Pscmdlet.ShouldProcess($destinstance, "Attempting to drop $conditionName")) {
Write-Message -Level Verbose -Message "Condition '$conditionName' exists on $destinstance. Force specified. Dropping $conditionName."
try {
$dependentPolicies = $destStore.Conditions[$conditionName].EnumDependentPolicies()
foreach ($dependent in $dependentPolicies) {
$dependent.Drop()
$destStore.Conditions.Refresh()
}
$destStore.Conditions[$conditionName].Drop()
}
catch {
$copyConditionStatus.Status = "Failed"
$copyConditionStatus.Notes = (Get-ErrorMessage -Record $_).Message
$copyConditionStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
Stop-Function -Message "Issue dropping condition on $destinstance" -Target $conditionName -ErrorRecord $_ -Continue
}
}
}
}
if ($Pscmdlet.ShouldProcess($destinstance, "Migrating condition $conditionName")) {
try {
$sql = $condition.ScriptCreate().GetScript() | Out-String
Write-Message -Level Debug -Message $sql
Write-Message -Level Verbose -Message "Copying condition $conditionName"
$null = $destServer.Query($sql)
$destStore.Conditions.Refresh()
$copyConditionStatus.Status = "Successful"
$copyConditionStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
}
catch {
$copyConditionStatus.Status = "Failed"
$copyConditionStatus.Notes = (Get-ErrorMessage -Record $_).Message
$copyConditionStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
Stop-Function -Message "Issue creating condition on $destinstance" -Target $conditionName -ErrorRecord $_
}
}
}
<#
Policies
#>
Write-Message -Level Verbose -Message "Migrating policies"
foreach ($policy in $storePolicies) {
$policyName = $policy.Name
$copyPolicyStatus = [pscustomobject]@{
SourceServer = $sourceServer.Name
DestinationServer = $destServer.Name
Name = $policyName
Type = "Policy"
Status = $null
Notes = $null
DateTime = [DbaDateTime](Get-Date)
}
if ($null -ne $destStore.Policies[$policyName]) {
if ($force -eq $false) {
Write-Message -Level Verbose -Message "Policy '$policyName' was skipped because it already exists on $destinstance. Use -Force to drop and recreate"
$copyPolicyStatus.Status = "Skipped"
$copyPolicyStatus.Notes = "Already exists"
$copyPolicyStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
continue
}
else {
if ($Pscmdlet.ShouldProcess($destinstance, "Attempting to drop $policyName")) {
Write-Message -Level Verbose -Message "Policy '$policyName' exists on $destinstance. Force specified. Dropping $policyName."
try {
$destStore.Policies[$policyName].Drop()
$destStore.Policies.refresh()
}
catch {
$copyPolicyStatus.Status = "Failed"
$copyPolicyStatus.Notes = (Get-ErrorMessage -Record $_).Message
$copyPolicyStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
Stop-Function -Message "Issue dropping policy on $destinstance" -Target $policyName -ErrorRecord $_ -Continue
}
}
}
}
if ($Pscmdlet.ShouldProcess($destinstance, "Migrating policy $policyName")) {
try {
$destStore.Conditions.Refresh()
$destStore.Policies.Refresh()
$sql = $policy.ScriptCreateWithDependencies().GetScript() | Out-String
Write-Message -Level Debug -Message $sql
Write-Message -Level Verbose -Message "Copying policy $policyName"
$null = $destServer.Query($sql)
$copyPolicyStatus.Status = "Successful"
$copyPolicyStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
}
catch {
$copyPolicyStatus.Status = "Failed"
$copyPolicyStatus.Notes = (Get-ErrorMessage -Record $_).Message
$copyPolicyStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
# This is usually because of a duplicate dependent from above. Just skip for now.
Stop-Function -Message "Issue creating policy on $destinstance" -Target $policyName -ErrorRecord $_ -Continue
}
}
}
}
}
end {
Test-DbaDeprecation -DeprecatedOn "1.0.0" -EnableException:$false -Alias Copy-SqlPolicyManagement
Test-DbaDeprecation -DeprecatedOn "1.0.0" -EnableException:$false -Alias Copy-DbaSqlPolicyManagement
}
}
function Copy-DbaQueryStoreConfig {
<#
.SYNOPSIS
Copies the configuration of a Query Store enabled database and sets the copied configuration on other databases.
.DESCRIPTION
Copies the configuration of a Query Store enabled database and sets the copied configuration on other databases.
.PARAMETER Source
Source SQL Server. You must have sysadmin access and server version must be SQL Server version 2016 or higher.
.PARAMETER SourceSqlCredential
Login to the target instance using alternative credentials. Windows and SQL Authentication supported. Accepts credential objects (Get-Credential)
.PARAMETER SourceDatabase
Specifies the database to copy the Query Store configuration from.
.PARAMETER Destination
Destination SQL Server. You must have sysadmin access and the server must be SQL Server 2016 or higher.
.PARAMETER DestinationSqlCredential
Login to the target instance using alternative credentials. Windows and SQL Authentication supported. Accepts credential objects (Get-Credential)
.PARAMETER DestinationDatabase
Specifies a list of databases that will receive a copy of the Query Store configuration of the SourceDatabase.
.PARAMETER Exclude
Specifies a list of databases which will NOT receive a copy of the Query Store configuration.
.PARAMETER AllDatabases
If this switch is enabled, the Query Store configuration will be copied to all databases on the destination instance.
.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.
.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.
.NOTES
Author: Enrico van de Laar ( @evdlaar )
Tags: QueryStore
Website: https://dbatools.io
Copyright: (C) Chrissy LeMaire, [email protected]
License: MIT https://opensource.org/licenses/MIT
.LINK
https://dbatools.io/Copy-QueryStoreConfig
.EXAMPLE
Copy-DbaQueryStoreConfig -Source ServerA\SQL -SourceDatabase AdventureWorks -Destination ServerB\SQL -AllDatabases
Copy the Query Store configuration of the AdventureWorks database in the ServerA\SQL instance and apply it on all user databases in the ServerB\SQL Instance.
.EXAMPLE
Copy-DbaQueryStoreConfig -Source ServerA\SQL -SourceDatabase AdventureWorks -Destination ServerB\SQL -DestinationDatabase WorldWideTraders
Copy the Query Store configuration of the AdventureWorks database in the ServerA\SQL instance and apply it to the WorldWideTraders database in the ServerB\SQL Instance.
#>
[CmdletBinding(SupportsShouldProcess = $true)]
param (
[parameter(Mandatory, ValueFromPipeline)]
[DbaInstanceParameter]$Source,
[PSCredential]$SourceSqlCredential,
[parameter(Mandatory, ValueFromPipeline)]
[object]$SourceDatabase,
[parameter(Mandatory, ValueFromPipeline)]
[DbaInstanceParameter[]]$Destination,
[PSCredential]$DestinationSqlCredential,
[object[]]$DestinationDatabase,
[object[]]$Exclude,
[switch]$AllDatabases,
[Alias('Silent')]
[switch]$EnableException
)
begin {
Write-Message -Message "Connecting to source: $Source." -Level Verbose
try {
$sourceServer = Connect-SqlInstance -SqlInstance $Source -SqlCredential $SourceSqlCredential
}
catch {
Stop-Function -Message "Can't connect to $Source." -ErrorRecord $_ -Target $Source
return
}
}
process {
if (Test-FunctionInterrupt) { return }
foreach ($destinstance in $Destination) {
# Grab the Query Store configuration from the SourceDatabase through the Get-DbaQueryStoreConfig function
$SourceQSConfig = Get-DbaDbQueryStoreOption -SqlInstance $sourceServer -Database $SourceDatabase
if (!$DestinationDatabase -and !$Exclude -and !$AllDatabases) {
Stop-Function -Message "You must specify databases to execute against using either -DestinationDatabase, -Exclude or -AllDatabases." -Continue
}
foreach ($destinationServer in $destinstance) {
try {
Write-Message -Level Verbose -Message "Connecting to $destinstance"
$destServer = Connect-SqlInstance -SqlInstance $destinstance -SqlCredential $DestinationSqlCredential
}
catch {
Stop-Function -Message "Failure" -Category ConnectionError -ErrorRecord $_ -Target $destinstance -Continue
}
# We have to exclude all the system databases since they cannot have the Query Store feature enabled
$dbs = Get-DbaDatabase -SqlInstance $destServer -ExcludeAllSystemDb
if ($DestinationDatabase.count -gt 0) {
$dbs = $dbs | Where-Object { $DestinationDatabase -contains $_.Name }
}
if ($Exclude.count -gt 0) {
$dbs = $dbs | Where-Object { $exclude -notcontains $_.Name }
}
if ($dbs.count -eq 0) {
Stop-Function -Message "No matching databases found. Check the spelling and try again." -Continue
}
foreach ($db in $dbs) {
# skipping the database if the source and destination are the same instance
if (($sourceServer.Name -eq $destinationServer) -and ($SourceDatabase -eq $db.Name)) {
continue
}
Write-Message -Message "Processing destination database: $db on $destinationServer." -Level Verbose
$copyQueryStoreStatus = [pscustomobject]@{
SourceServer = $sourceServer.name
SourceDatabase = $SourceDatabase
DestinationServer = $destinationServer
Name = $db.name
Type = "QueryStore Configuration"
Status = $null
DateTime = [Sqlcollaborative.Dbatools.Utility.DbaDateTime](Get-Date)
}
if ($db.IsAccessible -eq $false) {
$copyQueryStoreStatus.Status = "Skipped"
Stop-Function -Message "The database $db on server $destinationServer is not accessible. Skipping database." -Continue
}
Write-Message -Message "Executing Set-DbaQueryStoreConfig." -Level Verbose
# Set the Query Store configuration through the Set-DbaQueryStoreConfig function
try {
$null = Set-DbaDbQueryStoreOption -SqlInstance $destinationServer -SqlCredential $DestinationSqlCredential `
-Database $db.name `
-State $SourceQSConfig.ActualState `
-FlushInterval $SourceQSConfig.FlushInterval `
-CollectionInterval $SourceQSConfig.CollectionInterval `
-MaxSize $SourceQSConfig.MaxSize `
-CaptureMode $SourceQSConfig.CaptureMode `
-CleanupMode $SourceQSConfig.CleanupMode `
-StaleQueryThreshold $SourceQSConfig.StaleQueryThreshold
$copyQueryStoreStatus.Status = "Successful"
}
catch {
$copyQueryStoreStatus.Status = "Failed"
Stop-Function -Message "Issue setting Query Store on $db." -Target $db -ErrorRecord $_ -Continue
}
$copyQueryStoreStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
}
}
}
}
}
function Copy-DbaResourceGovernor {
<#
.SYNOPSIS
Migrates Resource Pools
.DESCRIPTION
By default, all non-system resource pools are migrated. If the pool already exists on the destination, it will be skipped unless -Force is used.
The -ResourcePool parameter is auto-populated for command-line completion and can be used to copy only specific objects.
.PARAMETER Source
Source SQL Server. You must have sysadmin access and server version must be SQL Server version 2008 or higher.
.PARAMETER SourceSqlCredential
Login to the target instance using alternative credentials. Windows and SQL Authentication supported. Accepts credential objects (Get-Credential)
.PARAMETER Destination
Destination SQL Server. You must have sysadmin access and the server must be SQL Server 2008 or higher.
.PARAMETER DestinationSqlCredential
Login to the target instance using alternative credentials. Windows and SQL Authentication supported. Accepts credential objects (Get-Credential)
.PARAMETER ResourcePool
Specifies the resource pool(s) to process. Options for this list are auto-populated from the server. If unspecified, all resource pools will be processed.
.PARAMETER ExcludeResourcePool
Specifies the resource pool(s) to exclude. Options for this list are auto-populated from the server
.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.
.PARAMETER Force
If this switch is enabled, the policies will be dropped and recreated on Destination.
.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.
.NOTES
Tags: Migration, ResourceGovernor
Author: Chrissy LeMaire (@cl), netnerds.net
Requires: sysadmin access on SQL Servers
Website: https://dbatools.io
Copyright: (C) Chrissy LeMaire, [email protected]
License: MIT https://opensource.org/licenses/MIT
.LINK
https://dbatools.io/Copy-DbaResourceGovernor
.EXAMPLE
Copy-DbaResourceGovernor -Source sqlserver2014a -Destination sqlcluster
Copies all all non-system resource pools from sqlserver2014a to sqlcluster using Windows credentials to connect to the SQL Server instances..
.EXAMPLE
Copy-DbaResourceGovernor -Source sqlserver2014a -Destination sqlcluster -SourceSqlCredential $cred
Copies all all non-system resource pools from sqlserver2014a to sqlcluster using SQL credentials to connect to sqlserver2014a and Windows credentials to connect to sqlcluster.
.EXAMPLE
Copy-DbaResourceGovernor -Source sqlserver2014a -Destination sqlcluster -WhatIf
Shows what would happen if the command were executed.
#>
[CmdletBinding(DefaultParameterSetName = "Default", SupportsShouldProcess = $true)]
param (
[parameter(Mandatory)]
[DbaInstanceParameter]$Source,
[PSCredential]$SourceSqlCredential,
[parameter(Mandatory)]
[DbaInstanceParameter[]]$Destination,
[PSCredential]$DestinationSqlCredential,
[object[]]$ResourcePool,
[object[]]$ExcludeResourcePool,
[switch]$Force,
[Alias('Silent')]
[switch]$EnableException
)
process {
try {
Write-Message -Level Verbose -Message "Connecting to $Source"
$sourceServer = Connect-SqlInstance -SqlInstance $Source -SqlCredential $SourceSqlCredential -MinimumVersion 10
}
catch {
Stop-Function -Message "Failure" -Category ConnectionError -ErrorRecord $_ -Target $Source
return
}
$sourceClassifierFunction = Get-DbaRgClassifierFunction -SqlInstance $sourceServer
foreach ($destinstance in $Destination) {
try {
Write-Message -Level Verbose -Message "Connecting to $destinstance"
$destServer = Connect-SqlInstance -SqlInstance $destinstance -SqlCredential $DestinationSqlCredential -MinimumVersion 10
}
catch {
Stop-Function -Message "Failure" -Category ConnectionError -ErrorRecord $_ -Target $destinstance -Continue
}
$destClassifierFunction = Get-DbaRgClassifierFunction -SqlInstance $destServer
$copyResourceGovSetting = [pscustomobject]@{
SourceServer = $sourceServer.Name
DestinationServer = $destServer.Name
Type = "Resource Governor Settings"
Name = "All Settings"
Status = $null
Notes = $null
DateTime = [DbaDateTime](Get-Date)
}
$copyResourceGovClassifierFunc = [pscustomobject]@{
SourceServer = $sourceServer.Name
DestinationServer = $destServer.Name
Type = "Resource Governor Settings"
Name = "Classifier Function"
Status = $null
Notes = $null
DateTime = [DbaDateTime](Get-Date)
}
if ($Pscmdlet.ShouldProcess($destinstance, "Updating Resource Governor settings")) {
if ($destServer.Edition -notmatch 'Enterprise' -and $destServer.Edition -notmatch 'Datacenter' -and $destServer.Edition -notmatch 'Developer') {
Write-Message -Level Verbose -Message "The resource governor is not available in this edition of SQL Server. You can manipulate resource governor metadata but you will not be able to apply resource governor configuration. Only Enterprise edition of SQL Server supports resource governor."
}
else {
try {
Write-Message -Level Verbose -Message "Managing classifier function."
if (!$sourceClassifierFunction) {
$copyResourceGovClassifierFunc.Status = "Skipped"
$copyResourceGovClassifierFunc.Notes = $null
$copyResourceGovClassifierFunc | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
}
else {
$fullyQualifiedFunctionName = $sourceClassifierFunction.Schema + "." + $sourceClassifierFunction.Name
if (!$destClassifierFunction) {
$destServer = Connect-SqlInstance -SqlInstance $destinstance -SqlCredential $DestinationSqlCredential
$destFunction = $destServer.Databases["master"].UserDefinedFunctions[$sourceClassifierFunction.Name]
if ($destFunction) {
Write-Message -Level Verbose -Message "Dropping the function with the source classifier function name."
$destFunction.Drop()
}
Write-Message -Level Verbose -Message "Creating function."
$destServer.Query($sourceClassifierFunction.Script())
$sql = "ALTER RESOURCE GOVERNOR WITH (CLASSIFIER_FUNCTION = $fullyQualifiedFunctionName);"
Write-Message -Level Debug -Message $sql
Write-Message -Level Verbose -Message "Mapping Resource Governor classifier function."
$destServer.Query($sql)
$copyResourceGovClassifierFunc.Status = "Successful"
$copyResourceGovClassifierFunc.Notes = "The new classifier function has been created"
$copyResourceGovClassifierFunc | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
}
else {
if ($Force -eq $false) {
$copyResourceGovClassifierFunc.Status = "Skipped"
$copyResourceGovClassifierFunc.Notes = "A classifier function already exists"
$copyResourceGovClassifierFunc | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
}
else {
$sql = "ALTER RESOURCE GOVERNOR WITH (CLASSIFIER_FUNCTION = NULL);"
Write-Message -Level Debug -Message $sql
Write-Message -Level Verbose -Message "Disabling the Resource Governor."
$destServer.Query($sql)
$sql = "ALTER RESOURCE GOVERNOR RECONFIGURE;"
Write-Message -Level Debug -Message $sql
Write-Message -Level Verbose -Message "Reconfiguring Resource Governor."
$destServer.Query($sql)
Write-Message -Level Verbose -Message "Dropping the destination classifier function."
$destServer = Connect-SqlInstance -SqlInstance $destinstance -SqlCredential $DestinationSqlCredential
$destFunction = $destServer.Databases["master"].UserDefinedFunctions[$sourceClassifierFunction.Name]
$destClassifierFunction.Drop()
Write-Message -Level Verbose -Message "Re-creating the Resource Governor classifier function."
$destServer.Query($sourceClassifierFunction.Script())
$sql = "ALTER RESOURCE GOVERNOR WITH (CLASSIFIER_FUNCTION = $fullyQualifiedFunctionName);"
Write-Message -Level Debug -Message $sql
Write-Message -Level Verbose -Message "Mapping Resource Governor classifier function."
$destServer.Query($sql)
$copyResourceGovClassifierFunc.Status = "Successful"
$copyResourceGovClassifierFunc.Notes = "The old classifier function has been overwritten."
$copyResourceGovClassifierFunc | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
}
}
}
}
catch {
$copyResourceGovSetting.Status = "Failed"
$copyResourceGovSetting.Notes = (Get-ErrorMessage -Record $_)
$copyResourceGovSetting | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
Stop-Function -Message "Not able to update settings." -Target $destServer -ErrorRecord $_
}
}
}
# Pools
if ($ResourcePool) {
$pools = $sourceServer.ResourceGovernor.ResourcePools | Where-Object Name -In $ResourcePool
}
elseif ($ExcludeResourcePool) {
$pool = $sourceServer.ResourceGovernor.ResourcePools | Where-Object Name -NotIn $ExcludeResourcePool
}
else {
$pools = $sourceServer.ResourceGovernor.ResourcePools | Where-Object { $_.Name -notin "internal", "default" }
}
Write-Message -Level Verbose -Message "Migrating pools."
foreach ($pool in $pools) {
$poolName = $pool.Name
$copyResourceGovPool = [pscustomobject]@{
SourceServer = $sourceServer.Name
DestinationServer = $destServer.Name
Type = "Resource Governor Pool"
Name = $poolName
Status = $null
Notes = $null
DateTime = [DbaDateTime](Get-Date)
}
if ($null -ne $destServer.ResourceGovernor.ResourcePools[$poolName]) {
if ($force -eq $false) {
Write-Message -Level Verbose -Message "Pool '$poolName' was skipped because it already exists on $destinstance. Use -Force to drop and recreate."
$copyResourceGovPool.Status = "Skipped"
$copyResourceGovPool.Notes = "Already exists"
$copyResourceGovPool | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
continue
}
else {
if ($Pscmdlet.ShouldProcess($destinstance, "Attempting to drop $poolName")) {
Write-Message -Level Verbose -Message "Pool '$poolName' exists on $destinstance."
Write-Message -Level Verbose -Message "Force specified. Dropping $poolName."
try {
$destServer = Connect-SqlInstance -SqlInstance $destinstance -SqlCredential $DestinationSqlCredential
$destPool = $destServer.ResourceGovernor.ResourcePools[$poolName]
$workloadGroups = $destPool.WorkloadGroups
foreach ($workloadGroup in $workloadGroups) {
$workloadGroup.Drop()
}
$destPool.Drop()
$destServer.ResourceGovernor.Alter()
}
catch {
$copyResourceGovPool.Status = "Failed to drop from Destination"
$copyResourceGovPool.Notes = (Get-ErrorMessage -Record $_)
$copyResourceGovPool | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
Stop-Function -Message "Unable to drop: $_ Moving on." -Target $destPool -ErrorRecord $_ -Continue
}
}
}
}
if ($Pscmdlet.ShouldProcess($destinstance, "Migrating pool $poolName")) {
try {
$sql = $pool.Script() | Out-String
Write-Message -Level Debug -Message $sql
Write-Message -Level Verbose -Message "Copying pool $poolName."
$destServer.Query($sql)
$copyResourceGovPool.Status = "Successful"
$copyResourceGovPool | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
$workloadGroups = $pool.WorkloadGroups
foreach ($workloadGroup in $workloadGroups) {
$workgroupName = $workloadGroup.Name
$copyResourceGovWorkGroup = [pscustomobject]@{
SourceServer = $sourceServer.Name
DestinationServer = $destServer.Name
Type = "Resource Governor Pool Workgroup"
Name = $workgroupName
Status = $null
Notes = $null
DateTime = [DbaDateTime](Get-Date)
}
$sql = $workloadGroup.Script() | Out-String
Write-Message -Level Debug -Message $sql
Write-Message -Level Verbose -Message "Copying $workgroupName."
$destServer.Query($sql)
$copyResourceGovWorkGroup.Status = "Successful"
$copyResourceGovWorkGroup | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
}
}
catch {
if ($copyResourceGovWorkGroup) {
$copyResourceGovWorkGroup.Status = "Failed"
$copyResourceGovWorkGroup.Notes = (Get-ErrorMessage -Record $_)
$copyResourceGovWorkGroup | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
}
Stop-Function -Message "Unable to migrate pool." -Target $pool -ErrorRecord $_
}
}
}
if ($Pscmdlet.ShouldProcess($destinstance, "Reconfiguring")) {
if ($destServer.Edition -notmatch 'Enterprise' -and $destServer.Edition -notmatch 'Datacenter' -and $destServer.Edition -notmatch 'Developer') {
Write-Message -Level Verbose -Message "The resource governor is not available in this edition of SQL Server. You can manipulate resource governor metadata but you will not be able to apply resource governor configuration. Only Enterprise edition of SQL Server supports resource governor."
}
else {
Write-Message -Level Verbose -Message "Reconfiguring Resource Governor."
try {
if (!$sourceServer.ResourceGovernor.Enabled) {
$sql = "ALTER RESOURCE GOVERNOR DISABLE"
$destServer.Query($sql)
}
else {
$sql = "ALTER RESOURCE GOVERNOR RECONFIGURE"
$destServer.Query($sql)
}
}
catch {
$altermsg = $_.Exception
}
$copyResourceGovReconfig = [pscustomobject]@{
SourceServer = $sourceServer.Name
DestinationServer = $destServer.Name
Type = "Reconfigure Resource Governor"
Name = "Reconfigure Resource Governor"
Status = "Successful"
Notes = $altermsg
DateTime = [DbaDateTime](Get-Date)
}
$copyResourceGovReconfig | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
}
}
}
}
end {
Test-DbaDeprecation -DeprecatedOn "1.0.0" -EnableException:$false -Alias Copy-SqlResourceGovernor
}
}
function Copy-DbaServerAudit {
<#
.SYNOPSIS
Copy-DbaServerAudit migrates server audits from one SQL Server to another.
.DESCRIPTION
By default, all audits are copied. The -Audit parameter is auto-populated for command-line completion and can be used to copy only specific audits.
If the audit already exists on the destination, it will be skipped unless -Force is used.
.PARAMETER Source
Source SQL Server. You must have sysadmin access and server version must be SQL Server version 2000 or higher.
.PARAMETER SourceSqlCredential
Login to the target instance using alternative credentials. Windows and SQL Authentication supported. Accepts credential objects (Get-Credential)
.PARAMETER Destination
Destination SQL Server. You must have sysadmin access and the server must be SQL Server 2000 or higher.
.PARAMETER DestinationSqlCredential
Login to the target instance using alternative credentials. Windows and SQL Authentication supported. Accepts credential objects (Get-Credential)
.PARAMETER Audit
The audit(s) to process. Options for this list are auto-populated from the server. If unspecified, all audits will be processed.
.PARAMETER ExcludeAudit
The audit(s) to exclude. Options for this list are auto-populated from the server.
.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.
.PARAMETER Force
If this switch is enabled, the audits will be dropped and recreated on Destination.
.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.
.NOTES
Tags: Migration
Author: Chrissy LeMaire (@cl), netnerds.net
Requires: sysadmin access on SQL Servers
Website: https://dbatools.io
Copyright: (C) Chrissy LeMaire, [email protected]
License: MIT https://opensource.org/licenses/MIT
.LINK
https://dbatools.io/Copy-DbaServerAudit
.EXAMPLE
Copy-DbaServerAudit -Source sqlserver2014a -Destination sqlcluster
Copies all server audits from sqlserver2014a to sqlcluster, using Windows credentials. If audits with the same name exist on sqlcluster, they will be skipped.
.EXAMPLE
Copy-DbaServerAudit -Source sqlserver2014a -Destination sqlcluster -Audit tg_noDbDrop -SourceSqlCredential $cred -Force
Copies a single audit, the tg_noDbDrop audit from sqlserver2014a to sqlcluster, using SQL credentials for sqlserver2014a and Windows credentials for sqlcluster. If an audit with the same name exists on sqlcluster, it will be dropped and recreated because -Force was used.
.EXAMPLE
Copy-DbaServerAudit -Source sqlserver2014a -Destination sqlcluster -WhatIf -Force
Shows what would happen if the command were executed using force.
#>
[CmdletBinding(DefaultParameterSetName = "Default", SupportsShouldProcess = $true)]
param (
[parameter(Mandatory)]
[DbaInstanceParameter]$Source,
[PSCredential]
$SourceSqlCredential,
[parameter(Mandatory)]
[DbaInstanceParameter[]]$Destination,
[PSCredential]
$DestinationSqlCredential,
[object[]]$Audit,
[object[]]$ExcludeAudit,
[switch]$Force,
[Alias('Silent')]
[switch]$EnableException
)
begin {
try {
Write-Message -Level Verbose -Message "Connecting to $Source"
$sourceServer = Connect-SqlInstance -SqlInstance $Source -SqlCredential $SourceSqlCredential -MinimumVersion 10
}
catch {
Stop-Function -Message "Failure" -Category ConnectionError -ErrorRecord $_ -Target $Source
return
}
$serverAudits = $sourceServer.Audits
}
process {
if (Test-FunctionInterrupt) { return }
foreach ($destinstance in $Destination) {
try {
Write-Message -Level Verbose -Message "Connecting to $destinstance"
$destServer = Connect-SqlInstance -SqlInstance $destinstance -SqlCredential $DestinationSqlCredential -MinimumVersion 10
}
catch {
Stop-Function -Message "Failure" -Category ConnectionError -ErrorRecord $_ -Target $destinstance -Continue
}
$destAudits = $destServer.Audits
foreach ($currentAudit in $serverAudits) {
$auditName = $currentAudit.Name
$copyAuditStatus = [pscustomobject]@{
SourceServer = $sourceServer.Name
DestinationServer = $destServer.Name
Name = $auditName
Type = "Server Audit"
Status = $null
Notes = $null
DateTime = [DbaDateTime](Get-Date)
}
if ($Audit -and $auditName -notin $Audit -or $auditName -in $ExcludeAudit) {
continue
}
$sql = $currentAudit.Script() | Out-String
if ($destAudits.Name -contains $auditName) {
if ($force -eq $false) {
if ($Pscmdlet.ShouldProcess($destinstance, "Server audit $auditName exists at destination. Use -Force to drop and migrate.")) {
$copyAuditStatus.Status = "Skipped"
$copyAuditStatus.Notes = "Already exists"
Write-Message -Level Verbose -Message "Server audit $auditName exists at destination. Use -Force to drop and migrate."
}
continue
}
else {
if ($Pscmdlet.ShouldProcess($destinstance, "Dropping server audit $auditName")) {
try {
Write-Message -Level Verbose -Message "Dropping server audit $auditName."
foreach ($spec in $destServer.ServerAuditSpecifications) {
if ($auditSpecification.Auditname -eq $auditName) {
$auditSpecification.Drop()
}
}
$destServer.audits[$auditName].Disable()
$destServer.audits[$auditName].Alter()
$destServer.audits[$auditName].Drop()
}
catch {
$copyAuditStatus.Status = "Failed"
$copyAuditStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
Stop-Function -Message "Issue dropping audit from destination." -Target $auditName -ErrorRecord $_
}
}
}
}
if ($null -ne ($currentAudit.Filepath) -and -not (Test-DbaPath -SqlInstance $destServer -Path $currentAudit.Filepath)) {
if ($Force -eq $false) {
if ($Pscmdlet.ShouldProcess($destinstance, "$($currentAudit.Filepath) does not exist on $destinstance. Skipping $auditName. Specify -Force to create the directory.")) {
$copyAuditStatus.Status = "Skipped"
$copyAuditStatus.Notes = "$($currentAudit.Filepath) does not exist on $destinstance. Skipping $auditName. Specify -Force to create the directory."
$copyAuditStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
}
continue
}
else {
Write-Message -Level Verbose -Message "Force specified. Creating directory."
$destNetBios = Resolve-NetBiosName $destServer
$path = Join-AdminUnc $destNetBios $currentAudit.Filepath
$root = $currentAudit.Filepath.Substring(0, 3)
$rootUnc = Join-AdminUnc $destNetBios $root
if ((Test-Path $rootUnc) -eq $true) {
if ($Pscmdlet.ShouldProcess($destinstance, "Creating directory $($currentAudit.Filepath)")) {
try {
$null = New-DbaDirectory -SqlInstance $destServer -Path $currentAudit.Filepath -EnableException
}
catch {
Write-Message -Level Warning -Message "Couldn't create directory $($currentAudit.Filepath). Using default data directory."
$datadir = Get-SqlDefaultPaths $destServer data
$sql = $sql.Replace($currentAudit.FilePath, $datadir)
}
}
}
else {
$datadir = Get-SqlDefaultPaths $destServer data
$sql = $sql.Replace($currentAudit.FilePath, $datadir)
}
}
}
if ($Pscmdlet.ShouldProcess($destinstance, "Creating server audit $auditName")) {
try {
Write-Message -Level Verbose -Message "File path $($currentAudit.Filepath) exists on $destinstance."
Write-Message -Level Verbose -Message "Copying server audit $auditName."
$destServer.Query($sql)
$copyAuditStatus.Status = "Successful"
$copyAuditStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
}
catch {
$copyAuditStatus.Status = "Failed"
$copyAuditStatus.Notes = (Get-ErrorMessage -Record $_)
$copyAuditStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
Stop-Function -Message "Issue creating audit." -Target $auditName -ErrorRecord $_
}
}
}
}
}
end {
Test-DbaDeprecation -DeprecatedOn "1.0.0" -EnableException:$false -Alias Copy-SqlAudit
}
}
function Copy-DbaServerAuditSpecification {
<#
.SYNOPSIS
Copy-DbaServerAuditSpecification migrates server audit specifications from one SQL Server to another.
.DESCRIPTION
By default, all audits are copied. The -AuditSpecification parameter is auto-populated for command-line completion and can be used to copy only specific audits.
If the audit specification already exists on the destination, it will be skipped unless -Force is used.
.PARAMETER Source
Source SQL Server. You must have sysadmin access and server version must be SQL Server version 2000 or higher.
.PARAMETER SourceSqlCredential
Login to the target instance using alternative credentials. Windows and SQL Authentication supported. Accepts credential objects (Get-Credential)
.PARAMETER Destination
Destination SQL Server. You must have sysadmin access and the server must be SQL Server 2000 or higher.
.PARAMETER DestinationSqlCredential
Login to the target instance using alternative credentials. Windows and SQL Authentication supported. Accepts credential objects (Get-Credential)
.PARAMETER AuditSpecification
The Server Audit Specification(s) to process. Options for this list are auto-populated from the server. If unspecified, all Server Audit Specifications will be processed.
.PARAMETER ExcludeAuditSpecification
The Server Audit Specification(s) to exclude. Options for this list are auto-populated from the server
.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.
.PARAMETER Force
If this switch is enabled, the Audits Specifications will be dropped and recreated on Destination.
.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.
.NOTES
Tags: Migration,ServerAudit,AuditSpecification
Author: Chrissy LeMaire (@cl), netnerds.net
Requires: sysadmin access on SQL Servers
Website: https://dbatools.io
Copyright: (C) Chrissy LeMaire, [email protected]
License: MIT https://opensource.org/licenses/MIT
.LINK
https://dbatools.io/Copy-DbaServerAuditSpecification
.EXAMPLE
Copy-DbaServerAuditSpecification -Source sqlserver2014a -Destination sqlcluster
Copies all server audits from sqlserver2014a to sqlcluster using Windows credentials to connect. If audits with the same name exist on sqlcluster, they will be skipped.
.EXAMPLE
Copy-DbaServerAuditSpecification -Source sqlserver2014a -Destination sqlcluster -ServerAuditSpecification tg_noDbDrop -SourceSqlCredential $cred -Force
Copies a single audit, the tg_noDbDrop audit from sqlserver2014a to sqlcluster using SQL credentials to connect to sqlserver2014a and Windows credentials to connect to sqlcluster. If an audit specification with the same name exists on sqlcluster, it will be dropped and recreated because -Force was used.
.EXAMPLE
Copy-DbaServerAuditSpecification -Source sqlserver2014a -Destination sqlcluster -WhatIf -Force
Shows what would happen if the command were executed using force.
#>
[CmdletBinding(DefaultParameterSetName = "Default", SupportsShouldProcess = $true)]
param (
[parameter(Mandatory)]
[DbaInstanceParameter]$Source,
[PSCredential]$SourceSqlCredential,
[parameter(Mandatory)]
[DbaInstanceParameter[]]$Destination,
[PSCredential]$DestinationSqlCredential,
[object[]]$AuditSpecification,
[object[]]$ExcludeAuditSpecification,
[switch]$Force,
[Alias('Silent')]
[switch]$EnableException
)
begin {
try {
Write-Message -Level Verbose -Message "Connecting to $Source"
$sourceServer = Connect-SqlInstance -SqlInstance $Source -SqlCredential $SourceSqlCredential -MinimumVersion 10
}
catch {
Stop-Function -Message "Failure" -Category ConnectionError -ErrorRecord $_ -Target $Source
return
}
if (!(Test-SqlSa -SqlInstance $sourceServer -SqlCredential $SourceSqlCredential)) {
Stop-Function -Message "Not a sysadmin on $source. Quitting."
return
}
$AuditSpecifications = $sourceServer.ServerAuditSpecifications
}
process {
if (Test-FunctionInterrupt) { return }
foreach ($destinstance in $Destination) {
try {
Write-Message -Level Verbose -Message "Connecting to $destinstance"
$destServer = Connect-SqlInstance -SqlInstance $destinstance -SqlCredential $DestinationSqlCredential -MinimumVersion 10
}
catch {
Stop-Function -Message "Failure" -Category ConnectionError -ErrorRecord $_ -Target $destinstance -Continue
}
if (!(Test-SqlSa -SqlInstance $destServer -SqlCredential $DestinationSqlCredential)) {
Stop-Function -Message "Not a sysadmin on $destinstance. Quitting."
return
}
if ($destServer.VersionMajor -lt $sourceServer.VersionMajor) {
Stop-Function -Message "Migration from version $($destServer.VersionMajor) to version $($sourceServer.VersionMajor) is not supported."
return
}
$destAudits = $destServer.ServerAuditSpecifications
foreach ($auditSpec in $AuditSpecifications) {
$auditSpecName = $auditSpec.Name
$copyAuditSpecStatus = [pscustomobject]@{
SourceServer = $sourceServer.Name
DestinationServer = $destServer.Name
Type = "Server Audit Specification"
Name = $auditSpecName
Status = $null
Notes = $null
DateTime = [DbaDateTime](Get-Date)
}
if ($AuditSpecification -and $auditSpecName -notin $AuditSpecification -or $auditSpecName -in $ExcludeAuditSpecification) {
continue
}
$destServer.Audits.Refresh()
if ($destServer.Audits.Name -notcontains $auditSpec.AuditName) {
if ($Pscmdlet.ShouldProcess($destinstance, "Audit $($auditSpec.AuditName) does not exist on $destinstance. Skipping $auditSpecName.")) {
$copyAuditSpecStatus.Status = "Skipped"
$copyAuditSpecStatus.Notes = "Audit $($auditSpec.AuditName) does not exist on $destinstance. Skipping $auditSpecName."
Write-Message -Level Warning -Message "Audit $($auditSpec.AuditName) does not exist on $destinstance. Skipping $auditSpecName."
$copyAuditSpecStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
}
continue
}
if ($destAudits.name -contains $auditSpecName) {
if ($force -eq $false) {
Write-Message -Level Verbose -Message "Server audit $auditSpecName exists at destination. Use -Force to drop and migrate."
$copyAuditSpecStatus.Status = "Skipped"
$copyAuditSpecStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
continue
}
else {
if ($Pscmdlet.ShouldProcess($destinstance, "Dropping server audit $auditSpecName and recreating")) {
try {
Write-Message -Level Verbose -Message "Dropping server audit $auditSpecName"
$destServer.ServerAuditSpecifications[$auditSpecName].Drop()
}
catch {
$copyAuditSpecStatus.Status = "Failed"
$copyAuditSpecStatus.Notes = (Get-ErrorMessage -Record $_)
$copyAuditSpecStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
Stop-Function -Message "Issue dropping audit spec" -Target $auditSpecName -ErrorRecord $_ -Continue
}
}
}
}
if ($Pscmdlet.ShouldProcess($destinstance, "Creating server audit $auditSpecName")) {
try {
Write-Message -Level Verbose -Message "Copying server audit $auditSpecName"
$sql = $auditSpec.Script() | Out-String
Write-Message -Level Debug -Message $sql
$destServer.Query($sql)
$copyAuditSpecStatus.Status = "Successful"
$copyAuditSpecStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
}
catch {
$copyAuditSpecStatus.Status = "Failed"
$copyAuditSpecStatus.Notes = (Get-ErrorMessage -Record $_)
$copyAuditSpecStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
Stop-Function -Message "Issue creating audit spec on destination" -Target $auditSpecName -ErrorRecord $_
}
}
}
}
}
end {
Test-DbaDeprecation -DeprecatedOn "1.0.0" -EnableException:$false -Alias Copy-SqlAuditSpecification
}
}
function Copy-DbaServerTrigger {
<#
.SYNOPSIS
Copy-DbaServerTrigger migrates server triggers from one SQL Server to another.
.DESCRIPTION
By default, all triggers are copied. The -ServerTrigger parameter is auto-populated for command-line completion and can be used to copy only specific triggers.
If the trigger already exists on the destination, it will be skipped unless -Force is used.
.PARAMETER Source
Source SQL Server.You must have sysadmin access and server version must be SQL Server version 2000 or greater.
.PARAMETER SourceSqlCredential
Login to the target instance using alternative credentials. Windows and SQL Authentication supported. Accepts credential objects (Get-Credential)
.PARAMETER Destination
Destination Sql Server. You must have sysadmin access and server version must be SQL Server version 2000 or greater.
.PARAMETER DestinationSqlCredential
Login to the target instance using alternative credentials. Windows and SQL Authentication supported. Accepts credential objects (Get-Credential)
.PARAMETER ServerTrigger
The Server Trigger(s) to process - this list is auto-populated from the server. If unspecified, all Server Triggers will be processed.
.PARAMETER ExcludeServerTrigger
The Server Trigger(s) to exclude - this list is auto-populated from the server
.PARAMETER WhatIf
Shows what would happen if the command were to run. No actions are actually performed.
.PARAMETER Confirm
Prompts you for confirmation before executing any changing operations within the command.
.PARAMETER Force
Drops and recreates the Trigger if it exists
.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.
.NOTES
Tags: Migration
Author: Chrissy LeMaire (@cl), netnerds.net
Requires: sysadmin access on SQL Servers
Website: https://dbatools.io
Copyright: (C) Chrissy LeMaire, [email protected]
License: MIT https://opensource.org/licenses/MIT
.LINK
https://dbatools.io/Copy-DbaServerTrigger
.EXAMPLE
Copy-DbaServerTrigger -Source sqlserver2014a -Destination sqlcluster
Copies all server triggers from sqlserver2014a to sqlcluster, using Windows credentials. If triggers with the same name exist on sqlcluster, they will be skipped.
.EXAMPLE
Copy-DbaServerTrigger -Source sqlserver2014a -Destination sqlcluster -ServerTrigger tg_noDbDrop -SourceSqlCredential $cred -Force
Copies a single trigger, the tg_noDbDrop trigger from sqlserver2014a to sqlcluster, using SQL credentials for sqlserver2014a and Windows credentials for sqlcluster. If a trigger with the same name exists on sqlcluster, it will be dropped and recreated because -Force was used.
.EXAMPLE
Copy-DbaServerTrigger -Source sqlserver2014a -Destination sqlcluster -WhatIf -Force
Shows what would happen if the command were executed using force.
#>
[CmdletBinding(DefaultParameterSetName = "Default", SupportsShouldProcess = $true)]
param (
[parameter(Mandatory)]
[DbaInstanceParameter]$Source,
[PSCredential]
$SourceSqlCredential,
[parameter(Mandatory)]
[DbaInstanceParameter[]]$Destination,
[PSCredential]
$DestinationSqlCredential,
[object[]]$ServerTrigger,
[object[]]$ExcludeServerTrigger,
[switch]$Force,
[Alias('Silent')]
[switch]$EnableException
)
begin {
try {
Write-Message -Level Verbose -Message "Connecting to $Source"
$sourceServer = Connect-SqlInstance -SqlInstance $Source -SqlCredential $SourceSqlCredential -MinimumVersion 9
}
catch {
Stop-Function -Message "Failure" -Category ConnectionError -ErrorRecord $_ -Target $Source
return
}
$serverTriggers = $sourceServer.Triggers
}
process {
if (Test-FunctionInterrupt) { return }
foreach ($destinstance in $Destination) {
try {
Write-Message -Level Verbose -Message "Connecting to $destinstance"
$destServer = Connect-SqlInstance -SqlInstance $destinstance -SqlCredential $DestinationSqlCredential -MinimumVersion 9
}
catch {
Stop-Function -Message "Failure" -Category ConnectionError -ErrorRecord $_ -Target $destinstance -Continue
}
if ($destServer.VersionMajor -lt $sourceServer.VersionMajor) {
Stop-Function -Message "Migration from version $($destServer.VersionMajor) to version $($sourceServer.VersionMajor) is not supported."
return
}
$destTriggers = $destServer.Triggers
foreach ($trigger in $serverTriggers) {
$triggerName = $trigger.Name
$copyTriggerStatus = [pscustomobject]@{
SourceServer = $sourceServer.Name
DestinationServer = $destServer.Name
Name = $triggerName
Type = "Server Trigger"
Status = $null
Notes = $null
DateTime = [DbaDateTime](Get-Date)
}
if ($ServerTrigger -and $triggerName -notin $ServerTrigger -or $triggerName -in $ExcludeServerTrigger) {
continue
}
if ($destTriggers.Name -contains $triggerName) {
if ($force -eq $false) {
Write-Message -Level Verbose -Message "Server trigger $triggerName exists at destination. Use -Force to drop and migrate."
$copyTriggerStatus.Status = "Skipped"
$copyTriggerStatus.Status = "Already exists"
$copyTriggerStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
continue
}
else {
if ($Pscmdlet.ShouldProcess($destinstance, "Dropping server trigger $triggerName and recreating")) {
try {
Write-Message -Level Verbose -Message "Dropping server trigger $triggerName"
$destServer.Triggers[$triggerName].Drop()
}
catch {
$copyTriggerStatus.Status = "Failed"
$copyTriggerStatus.Notes = (Get-ErrorMessage -Record $_)
$copyTriggerStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
Stop-Function -Message "Issue dropping trigger on destination" -Target $triggerName -ErrorRecord $_ -Continue
}
}
}
}
if ($Pscmdlet.ShouldProcess($destinstance, "Creating server trigger $triggerName")) {
try {
Write-Message -Level Verbose -Message "Copying server trigger $triggerName"
$sql = $trigger.Script() | Out-String
$sql = $sql -replace "CREATE TRIGGER", "`nGO`nCREATE TRIGGER"
$sql = $sql -replace "ENABLE TRIGGER", "`nGO`nENABLE TRIGGER"
Write-Message -Level Debug -Message $sql
foreach ($query in ($sql -split '\nGO\b')) {
$destServer.Query($query) | Out-Null
}
$copyTriggerStatus.Status = "Successful"
$copyTriggerStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
}
catch {
$copyTriggerStatus.Status = "Failed"
$copyTriggerStatus.Notes = (Get-ErrorMessage -Record $_)
$copyTriggerStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
Stop-Function -Message "Issue creating trigger on destination" -Target $triggerName -ErrorRecord $_
}
}
}
}
}
end {
Test-DbaDeprecation -DeprecatedOn "1.0.0" -EnableException:$false -Alias Copy-SqlServerTrigger
}
}
function Copy-DbaSpConfigure {
<#
.SYNOPSIS
Copy-DbaSpConfigure migrates configuration values from one SQL Server to another.
.DESCRIPTION
By default, all configuration values are copied. The -ConfigName parameter is auto-populated for command-line completion and can be used to copy only specific configs.
.PARAMETER Source
Source SQL Server. You must have sysadmin access and server version must be SQL Server version 2000 or higher.
.PARAMETER SourceSqlCredential
Login to the target instance using alternative credentials. Windows and SQL Authentication supported. Accepts credential objects (Get-Credential)
.PARAMETER Destination
Destination SQL Server. You must have sysadmin access and the server must be SQL Server 2000 or higher.
.PARAMETER DestinationSqlCredential
Login to the target instance using alternative credentials. Windows and SQL Authentication supported. Accepts credential objects (Get-Credential)
.PARAMETER ConfigName
Specifies the configuration setting to process. Options for this list are auto-populated from the server. If unspecified, all ConfigNames will be processed.
.PARAMETER ExcludeConfigName
Specifies the configuration settings to exclude. Options for this list are auto-populated from the server.
.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.
.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.
.NOTES
Tags: Migration, Configure, SpConfigure
Author: Chrissy LeMaire (@cl), netnerds.net
Requires: sysadmin access on SQL Servers
Website: https://dbatools.io
Copyright: (C) Chrissy LeMaire, [email protected]
License: MIT https://opensource.org/licenses/MIT
.LINK
https://dbatools.io/Copy-DbaSpConfigure
.EXAMPLE
Copy-DbaSpConfigure -Source sqlserver2014a -Destination sqlcluster
Copies all sp_configure settings from sqlserver2014a to sqlcluster
.EXAMPLE
Copy-DbaSpConfigure -Source sqlserver2014a -Destination sqlcluster -ConfigName DefaultBackupCompression, IsSqlClrEnabled -SourceSqlCredential $cred -Force
Copies the values for IsSqlClrEnabled and DefaultBackupCompression from sqlserver2014a to sqlcluster using SQL credentials to authenticate to sqlserver2014a and Windows credentials to authenticate to sqlcluster.
.EXAMPLE
Copy-DbaSpConfigure -Source sqlserver2014a -Destination sqlcluster -ExcludeConfigName DefaultBackupCompression, IsSqlClrEnabled
Copies all configs except for IsSqlClrEnabled and DefaultBackupCompression, from sqlserver2014a to sqlcluster.
.EXAMPLE
Copy-DbaSpConfigure -Source sqlserver2014a -Destination sqlcluster -WhatIf
Shows what would happen if the command were executed.
#>
[CmdletBinding(DefaultParameterSetName = "Default", SupportsShouldProcess = $true)]
param (
[parameter(Mandatory)]
[DbaInstanceParameter]$Source,
[PSCredential]$SourceSqlCredential,
[parameter(Mandatory)]
[DbaInstanceParameter[]]$Destination,
[PSCredential]$DestinationSqlCredential,
[object[]]$ConfigName,
[object[]]$ExcludeConfigName,
[Alias('Silent')]
[switch]$EnableException
)
begin {
try {
Write-Message -Level Verbose -Message "Connecting to $Source"
$sourceServer = Connect-SqlInstance -SqlInstance $Source -SqlCredential $SourceSqlCredential
$sourceProps = Get-DbaSpConfigure -SqlInstance $sourceServer
}
catch {
Stop-Function -Message "Failure" -Category ConnectionError -ErrorRecord $_ -Target $Source
return
}
}
process {
if (Test-FunctionInterrupt) { return }
foreach ($destinstance in $Destination) {
try {
Write-Message -Level Verbose -Message "Connecting to $destinstance"
$destServer = Connect-SqlInstance -SqlInstance $destinstance -SqlCredential $DestinationSqlCredential
$destProps = Get-DbaSpConfigure -SqlInstance $destServer
}
catch {
Stop-Function -Message "Failure" -Category ConnectionError -ErrorRecord $_ -Target $destinstance -Continue
}
foreach ($sourceProp in $sourceProps) {
$displayName = $sourceProp.DisplayName
$sConfigName = $sourceProp.ConfigName
$sConfiguredValue = $sourceProp.ConfiguredValue
$requiresRestart = $sourceProp.IsDynamic
$copySpConfigStatus = [pscustomobject]@{
SourceServer = $sourceServer.Name
DestinationServer = $destServer.Name
Name = $sConfigName
Type = "Configuration Value"
Status = $null
Notes = $null
DateTime = [DbaDateTime](Get-Date)
}
if ($ConfigName -and $sConfigName -notin $ConfigName -or $sConfigName -in $ExcludeConfigName) {
continue
}
$destProp = $destProps | Where-Object ConfigName -eq $sConfigName
if (!$destProp) {
Write-Message -Level Verbose -Message "Configuration $sConfigName ('$displayName') does not exist on the destination instance."
$copySpConfigStatus.Status = "Skipped"
$copySpConfigStatus.Notes = "Configuration does not exist on destination"
$copySpConfigStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
continue
}
if ($Pscmdlet.ShouldProcess($destinstance, "Updating $sConfigName [$displayName]")) {
try {
$destOldConfigValue = $destProp.ConfiguredValue
if ($sConfiguredValue -ne $destOldConfigValue) {
$result = Set-DbaSpConfigure -SqlInstance $destServer -Name $sConfigName -Value $sConfiguredValue -EnableException -WarningAction SilentlyContinue
if ($result) {
Write-Message -Level Verbose -Message "Updated $($destProp.ConfigName) ($($destProp.DisplayName)) from $destOldConfigValue to $sConfiguredValue."
}
}
if ($requiresRestart -eq $false) {
Write-Message -Level Verbose -Message "Configuration option $sConfigName ($displayName) requires restart."
$copySpConfigStatus.Notes = "Requires restart"
}
$copySpConfigStatus.Status = "Successful"
$copySpConfigStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
}
catch {
if ($_.Exception -match 'the same as the') {
$copySpConfigStatus.Status = "Successful"
}
else {
$copySpConfigStatus.Status = "Failed"
$copySpConfigStatus.Notes = (Get-ErrorMessage -Record $_)
}
$copySpConfigStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
Stop-Function -Message "Could not set $($destProp.ConfigName) to $sConfiguredValue." -Target $sConfigName -ErrorRecord $_
}
}
}
}
}
end {
Test-DbaDeprecation -DeprecatedOn "1.0.0" -EnableException:$false -Alias Copy-SqlSpConfigure
}
}
#ValidationTags#Messaging#
function Copy-DbaSsisCatalog {
<#
.SYNOPSIS
Copy-DbaSsisCatalog migrates Folders, SSIS projects, and environments from one SQL Server to another.
.DESCRIPTION
By default, all folders, projects, and environments are copied. The -Project parameter can be specified to copy only one project, if desired.
The parameters get more granular from the Folder level. For example, specifying -Folder will only deploy projects/environments from within that folder.
.PARAMETER Source
Source SQL Server. You must have sysadmin access and server version must be SQL Server version 2012 or higher.
.PARAMETER SourceSqlCredential
Login to the target instance using alternative credentials. Windows and SQL Authentication supported. Accepts credential objects (Get-Credential)
.PARAMETER Destination
Destination SQL Server. You must have sysadmin access and the server must be SQL Server 2012 or higher.
.PARAMETER DestinationSqlCredential
Login to the target instance using alternative credentials. Windows and SQL Authentication supported. Accepts credential objects (Get-Credential)
.PARAMETER Force
If this switch is enabled, the SSIS Catalog will be dropped and recreated on Destination if it already exists.
.PARAMETER Project
Specifies a source Project name.
.PARAMETER Folder
Specifies a source folder name.
.PARAMETER Environment
Specifies an environment to copy.
.PARAMETER EnableSqlClr
If this switch is enabled and Destination does not have the SQL CLR configuration option enabled, user prompts for enabling it on Destination will be skipped. SQL CLR is required for SSISDB.
.PARAMETER CreateCatalogPassword
Specifies a secure string to use in creating an SSISDB catalog on Destination. If this is specified, prompts for the password will be skipped.
.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.
.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.
.NOTES
Tags: Migration, SSIS
Author: Phil Schwartz (philschwartz.me, @pschwartzzz)
dbatools PowerShell module (https://dbatools.io, [email protected])
Copyright (C) 2016 Chrissy LeMaire
License: MIT https://opensource.org/licenses/MIT
.LINK
https://dbatools.io/Copy-DbaSsisCatalog
.EXAMPLE
Copy-DbaSsisCatalog -Source sqlserver2014a -Destination sqlcluster
Copies all folders, environments and SSIS Projects from sqlserver2014a to sqlcluster, using Windows credentials to authenticate to both instances. If folders with the same name exist on the destination they will be skipped, but projects will be redeployed.
.EXAMPLE
Copy-DbaSsisCatalog -Source sqlserver2014a -Destination sqlcluster -Project Archive_Tables -SourceSqlCredential $cred -Force
Copies a single Project, the Archive_Tables Project, from sqlserver2014a to sqlcluster using SQL credentials to authenticate to sqlserver2014a and Windows credentials to authenticate to sqlcluster. If a Project with the same name exists on sqlcluster, it will be deleted and recreated because -Force was used.
.EXAMPLE
Copy-DbaSsisCatalog -Source sqlserver2014a -Destination sqlcluster -WhatIf -Force
Shows what would happen if the command were executed using force.
.EXAMPLE
$SecurePW = Read-Host "Enter password" -AsSecureString
Copy-DbaSsisCatalog -Source sqlserver2014a -Destination sqlcluster -CreateCatalogPassword $SecurePW
Deploy entire SSIS catalog to an instance without a destination catalog. User prompts for creating the catalog on Destination will be bypassed.
#>
[CmdletBinding(DefaultParameterSetName = "Default", SupportsShouldProcess = $true)]
param (
[parameter(Mandatory)]
[DbaInstanceParameter]$Source,
[parameter(Mandatory)]
[DbaInstanceParameter[]]$Destination,
[PSCredential]$SourceSqlCredential,
[PSCredential]$DestinationSqlCredential,
[String]$Project,
[String]$Folder,
[String]$Environment,
[System.Security.SecureString]$CreateCatalogPassword,
[Switch]$EnableSqlClr,
[Switch]$Force,
[Alias('Silent')]
[switch]$EnableException
)
<# Developer note: The throw calls must stay in this command #>
begin {
function Get-RemoteIntegrationService {
param (
[Object]$Computer
)
$result = Get-DbaService -ComputerName $Computer -Type SSIS
if ($result) {
$running = $false
foreach ($service in $result) {
if (!$service.State -eq "Running") {
Write-Message -Level Warning -Message "Service $($service.DisplayName) was found on the destination, but is currently not running."
}
else {
Write-Message -Level Verbose -Message "Service $($service.DisplayName) was found running on the destination."
$running = $true
}
}
}
else {
throw "No Integration Services service was found on the destination, please ensure the feature is installed and running."
}
}
function Invoke-ProjectDeployment {
param (
[String]$Project,
[String]$Folder
)
$sqlConn = New-Object System.Data.SqlClient.SqlConnection
$sqlConn.ConnectionString = $sourceConnection.ConnectionContext.ConnectionString
if ($sqlConn.State -eq "Closed") {
$sqlConn.Open()
}
try {
Write-Message -Level Verbose -Message "Deploying project $Project from folder $Folder."
$cmd = New-Object System.Data.SqlClient.SqlCommand
$cmd.CommandType = "StoredProcedure"
$cmd.connection = $sqlConn
$cmd.CommandText = "SSISDB.Catalog.get_project"
$cmd.Parameters.Add("@folder_name", $Folder) | out-null;
$cmd.Parameters.Add("@project_name", $Project) | out-null;
[byte[]]$results = $cmd.ExecuteScalar();
if ($null -ne $results) {
$destFolder = $destinationFolders | Where-Object { $_.Name -eq $Folder }
$deployedProject = $destFolder.DeployProject($Project, $results)
if ($deployedProject.Status -ne "Success") {
Stop-Function -Message "An error occurred deploying project $Project." -Target $Project -Continue
}
}
else {
Stop-Function -Message "Failed deploying $Project from folder $Folder." -Target $Project -Continue
}
}
catch {
Stop-Function -Message "Failed to deploy project." -Target $Project -ErrorRecord $_
}
finally {
if ($sqlConn.State -eq "Open") {
$sqlConn.Close()
}
}
}
function New-CatalogFolder {
param (
[String]$Folder,
[String]$Description,
[Switch]$Force
)
if ($Force) {
$remove = $destinationFolders | Where-Object { $_.Name -eq $Folder }
$envs = $remove.Environments.Name
foreach ($e in $envs) {
$remove.Environments[$e].Drop()
}
$projs = $remove.Projects.Name
foreach ($p in $projs) {
$remove.Projects[$p].Drop()
}
$remove.Drop()
$destinationCatalog.Alter()
$destinationCatalog.Refresh()
}
Write-Message -Level Verbose -Message "Creating folder $Folder."
$destFolder = New-Object "$ISNamespace.CatalogFolder" ($destinationCatalog, $Folder, $Description)
$destFolder.Create()
$destFolder.Alter()
$destFolder.Refresh()
}
function New-FolderEnvironment {
param (
[String]$Folder,
[String]$Environment,
[Switch]$Force
)
$envDestFolder = $destinationFolders | Where-Object { $_.Name -eq $Folder }
if ($force) {
$envDestFolder.Environments[$Environment].Drop()
$envDestFolder.Alter()
$envDestFolder.Refresh()
}
$srcEnv = ($sourceFolders | Where-Object { $_.Name -eq $Folder }).Environments[$Environment]
$targetEnv = New-Object "$ISNamespace.EnvironmentInfo" ($envDestFolder, $srcEnv.Name, $srcEnv.Description)
foreach ($var in $srcEnv.Variables) {
if ($var.Value.ToString() -eq "") {
$finalValue = ""
}
else {
$finalValue = $var.Value
}
$targetEnv.Variables.Add($var.Name, $var.Type, $finalValue, $var.Sensitive, $var.Description)
}
Write-Message -Level Verbose -Message "Creating environment $Environment."
$targetEnv.Create()
$targetEnv.Alter()
$targetEnv.Refresh()
}
function New-SSISDBCatalog {
param (
[System.Security.SecureString]$Password
)
if (!$Password) {
Write-Message -Level Verbose -Message "SSISDB Catalog requires a password."
$pass1 = Read-Host "Enter a password" -AsSecureString
$plainTextPass1 = [Runtime.InteropServices.Marshal]::PtrToStringAuto([Runtime.InteropServices.Marshal]::SecureStringToBSTR($pass1))
$pass2 = Read-Host "Re-enter password" -AsSecureString
$plainTextPass2 = [Runtime.InteropServices.Marshal]::PtrToStringAuto([Runtime.InteropServices.Marshal]::SecureStringToBSTR($pass2))
if ($plainTextPass1 -ne $plainTextPass2) {
throw "Validation error, passwords entered do not match."
}
$plainTextPass = $plainTextPass1
}
else {
$plainTextPass = [Runtime.InteropServices.Marshal]::PtrToStringAuto([Runtime.InteropServices.Marshal]::SecureStringToBSTR($Password))
}
$catalog = New-Object "$ISNamespace.Catalog" ($destinationSSIS, "SSISDB", $plainTextPass)
$catalog.Create()
$catalog.Refresh()
}
$ISNamespace = "Microsoft.SqlServer.Management.IntegrationServices"
try {
Write-Message -Level Verbose -Message "Connecting to $Source"
$sourceServer = Connect-SqlInstance -SqlInstance $Source -SqlCredential $SourceSqlCredential -MinimumVersion 11
}
catch {
Stop-Function -Message "Failure" -Category ConnectionError -ErrorRecord $_ -Target $Source
return
}
try {
Write-Message -Level Verbose -Message "Connecting to $Source integration services."
$sourceSSIS = New-Object "$ISNamespace.IntegrationServices" $sourceConnection
}
catch {
Stop-Function -Message "There was an error connecting to the source integration services." -Target $sourceConnection -ErrorRecord $_
return
}
$sourceCatalog = $sourceSSIS.Catalogs | Where-Object { $_.Name -eq "SSISDB" }
if (!$sourceCatalog) {
Stop-Function -Message "The source SSISDB catalog on $Source does not exist."
return
}
$sourceFolders = $sourceCatalog.Folders
}
process {
if (Test-FunctionInterrupt) { return }
foreach ($destinstance in $Destination) {
try {
Write-Message -Level Verbose -Message "Connecting to $destinstance"
$destinationConnection = Connect-SqlInstance -SqlInstance $destinstance -SqlCredential $DestinationSqlCredential -MinimumVersion 1
}
catch {
Stop-Function -Message "Failure" -Category ConnectionError -ErrorRecord $_ -Target $destinstance -Continue
}
try {
Get-RemoteIntegrationService -Computer $destinstance
}
catch {
Stop-Function -Message "An error occurred when checking the destination for Integration Services. Is Integration Services installed?" -Target $destinstance -ErrorRecord $_
}
try {
Write-Message -Level Verbose -Message "Connecting to $destinstance integration services."
$destinationSSIS = New-Object "$ISNamespace.IntegrationServices" $destinationConnection
}
catch {
Stop-Function -Message "There was an error connecting to the destination integration services." -Target $destinationCon -ErrorRecord $_
}
$destinationCatalog = $destinationSSIS.Catalogs | Where-Object { $_.Name -eq "SSISDB" }
$destinationFolders = $destinationCatalog.Folders
if (!$destinationCatalog) {
if (!$destinationConnection.Configuration.IsSqlClrEnabled.ConfigValue) {
if ($Pscmdlet.ShouldProcess($destinstance, "Enabling SQL CLR configuration option.")) {
if (!$EnableSqlClr) {
$message = "The destination does not have SQL CLR configuration option enabled (required by SSISDB), would you like to enable it?"
$yes = New-Object System.Management.Automation.Host.ChoiceDescription "&Yes", "Enable SQL CLR on $destinstance."
$no = New-Object System.Management.Automation.Host.ChoiceDescription "&No", "Exit."
$options = [System.Management.Automation.Host.ChoiceDescription[]]($yes, $no)
$result = $host.ui.PromptForChoice($null, $message, $options, 0)
switch ($result) {
0 {
continue
}
1 {
return
}
}
}
Write-Message -Level Verbose -Message "Enabling SQL CLR configuration option at the destination."
if ($destinationConnection.Configuration.ShowAdvancedOptions.ConfigValue -eq $false) {
$destinationConnection.Configuration.ShowAdvancedOptions.ConfigValue = $true
$changeback = $true
}
$destinationConnection.Configuration.IsSqlClrEnabled.ConfigValue = $true
if ($changeback -eq $true) {
$destinationConnection.Configuration.ShowAdvancedOptions.ConfigValue = $false
}
$destinationConnection.Configuration.Alter()
}
}
else {
Write-Message -Level Verbose -Message "SQL CLR configuration option is already enabled at the destination."
}
if ($Pscmdlet.ShouldProcess($destinstance, "Create destination SSISDB Catalog")) {
if (!$CreateCatalogPassword) {
$message = "The destination SSISDB catalog does not exist, would you like to create one?"
$yes = New-Object System.Management.Automation.Host.ChoiceDescription "&Yes", "Create an SSISDB catalog on $destinstance."
$no = New-Object System.Management.Automation.Host.ChoiceDescription "&No", "Exit."
$options = [System.Management.Automation.Host.ChoiceDescription[]]($yes, $no)
$result = $host.ui.PromptForChoice($null, $message, $options, 0)
switch ($result) {
0 {
New-SSISDBCatalog
}
1 {
return
}
}
}
else {
New-SSISDBCatalog -Password $CreateCatalogPassword
}
$destinationSSIS.Refresh()
$destinationCatalog = $destinationSSIS.Catalogs | Where-Object { $_.Name -eq "SSISDB" }
$destinationFolders = $destinationCatalog.Folders
}
else {
throw "The destination SSISDB catalog does not exist."
}
}
if ($folder) {
if ($sourceFolders.Name -contains $folder) {
$srcFolder = $sourceFolders | Where-Object { $_.Name -eq $folder }
if ($destinationFolders.Name -contains $folder) {
if (!$force) {
Write-Message -Level Warning -Message "Integration services catalog folder $folder exists at destination. Use -Force to drop and recreate."
}
else {
if ($Pscmdlet.ShouldProcess($destinstance, "Dropping folder $folder and recreating")) {
try {
New-CatalogFolder -Folder $srcFolder.Name -Description $srcFolder.Description -Force
}
catch {
Stop-Function -Message "Issue dropping folder" -Target $folder -ErrorRecord $_
}
}
}
}
else {
if ($Pscmdlet.ShouldProcess($destinstance, "Creating folder $folder")) {
try {
New-CatalogFolder -Folder $srcFolder.Name -Description $srcFolder.Description
}
catch {
Stop-Function -Message "Issue creating folder" -Target $folder -ErrorRecord $_
}
}
}
}
else {
throw "The source folder provided does not exist in the source Integration Services catalog."
}
}
else {
foreach ($srcFolder in $sourceFolders) {
if ($destinationFolders.Name -notcontains $srcFolder.Name) {
if ($Pscmdlet.ShouldProcess($destinstance, "Creating folder $($srcFolder.Name)")) {
try {
New-CatalogFolder -Folder $srcFolder.Name -Description $srcFolder.Description
}
catch {
Stop-Function -Message "Issue creating folder" -Target $srcFolder -ErrorRecord $_ -Continue
}
}
}
else {
if (!$force) {
Write-Message -Level Warning -Message "Integration services catalog folder $($srcFolder.Name) exists at destination. Use -Force to drop and recreate."
continue
}
else {
if ($Pscmdlet.ShouldProcess($destinstance, "Dropping folder $($srcFolder.Name) and recreating")) {
try {
New-CatalogFolder -Folder $srcFolder.Name -Description $srcFolder.Description -Force
}
catch {
Stop-Function -Message "Issue dropping folder" -Target $srcFolder -ErrorRecord $_
}
}
}
}
}
}
# Refresh folders for project and environment deployment
if ($Pscmdlet.ShouldProcess($destinstance, "Refresh folders for project deployment")) {
try {
$destinationFolders.Alter()
}
catch {
# Sometimes it says Alter() doesn't exist
}
$destinationFolders.Refresh()
}
if ($folder) {
$sourceFolders = $sourceFolders | Where-Object { $_.Name -eq $folder }
if (!$sourceFolders) {
throw "The source folder $folder does not exist in the source Integration Services catalog."
}
}
if ($project) {
$folderDeploy = $sourceFolders | Where-Object { $_.Projects.Name -eq $project }
if (!$folderDeploy) {
throw "The project $project cannot be found in the source Integration Services catalog."
}
else {
foreach ($f in $folderDeploy) {
if ($Pscmdlet.ShouldProcess($destinstance, "Deploying project $project from folder $($f.Name)")) {
try {
Invoke-ProjectDeployment -Folder $f.Name -Project $project
}
catch {
Stop-Function -Message "Issue deploying project" -Target $project -ErrorRecord $_
}
}
}
}
}
else {
foreach ($curFolder in $sourceFolders) {
foreach ($proj in $curFolder.Projects) {
if ($Pscmdlet.ShouldProcess($destinstance, "Deploying project $($proj.Name) from folder $($curFolder.Name)")) {
try {
Invoke-ProjectDeployment -Project $proj.Name -Folder $curFolder.Name
}
catch {
Stop-Function -Message "Issue deploying project" -Target $proj -ErrorRecord $_
}
}
}
}
}
if ($environment) {
$folderDeploy = $sourceFolders | Where-Object { $_.Environments.Name -eq $environment }
if (!$folderDeploy) {
throw "The environment $environment cannot be found in the source Integration Services catalog."
}
else {
foreach ($f in $folderDeploy) {
if ($destinationFolders[$f.Name].Environments.Name -notcontains $environment) {
if ($Pscmdlet.ShouldProcess($destinstance, "Deploying environment $environment from folder $($f.Name)")) {
try {
New-FolderEnvironment -Folder $f.Name -Environment $environment
}
catch {
Stop-Function -Message "Issue deploying environment" -Target $environment -ErrorRecord $_
}
}
}
else {
if (!$force) {
Write-Message -Level Warning -Message "Integration services catalog environment $environment exists in folder $($f.Name) at destination. Use -Force to drop and recreate."
}
else {
If ($Pscmdlet.ShouldProcess($destinstance, "Dropping existing environment $environment and deploying environment $environment from folder $($f.Name)")) {
try {
New-FolderEnvironment -Folder $f.Name -Environment $environment -Force
}
catch {
Stop-Function -Message "Issue dropping existing environment" -Target $environment -ErrorRecord $_
}
}
}
}
}
}
}
else {
foreach ($curFolder in $sourceFolders) {
foreach ($env in $curFolder.Environments) {
if ($destinationFolders[$curFolder.Name].Environments.Name -notcontains $env.Name) {
if ($Pscmdlet.ShouldProcess($destinstance, "Deploying environment $($env.Name) from folder $($curFolder.Name)")) {
try {
New-FolderEnvironment -Environment $env.Name -Folder $curFolder.Name
}
catch {
Stop-Function -Message "Issue deploying environment" -Target $env -ErrorRecord $_
}
}
}
else {
if (!$force) {
Write-Message -Level Warning -Message "Integration services catalog environment $($env.Name) exists in folder $($curFolder.Name) at destination. Use -Force to drop and recreate."
continue
}
else {
if ($Pscmdlet.ShouldProcess($destinstance, "Deploying environment $($env.Name) from folder $($curFolder.Name)")) {
try {
New-FolderEnvironment -Environment $env.Name -Folder $curFolder.Name -Force
}
catch {
Stop-Function -Message "Issue deploying environment" -Target $env -ErrorRecord $_
}
}
}
}
}
}
}
}
}
end {
Test-DbaDeprecation -DeprecatedOn "1.0.0" -EnableException:$false -Alias Copy-SqlSsisCatalog
}
}
function Copy-DbaSysDbUserObject {
<#
.SYNOPSIS
Imports all user objects found in source SQL Server's master, msdb and model databases to the destination.
.DESCRIPTION
Imports all user objects found in source SQL Server's master, msdb and model databases to the destination. This is useful because many DBAs store backup/maintenance procs/tables/triggers/etc (among other things) in master or msdb.
It is also useful for migrating objects within the model database.
.PARAMETER Source
Source SQL Server. You must have sysadmin access and server version must be SQL Server version 2000 or higher.
.PARAMETER SourceSqlCredential
Login to the target instance using alternative credentials. Windows and SQL Authentication supported. Accepts credential objects (Get-Credential)
.PARAMETER Destination
Destination SQL Server. You must have sysadmin access and the server must be SQL Server 2000 or higher.
.PARAMETER DestinationSqlCredential
Login to the target instance using alternative credentials. Windows and SQL Authentication supported. Accepts credential objects (Get-Credential)
.PARAMETER Classic
Perform the migration the old way
.PARAMETER Force
Drop destination objects first. Has no effect if you use Classic. This doesn't work really well, honestly.
.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.
.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.
.NOTES
Tags: Migration, SystemDatabase, UserObject
Author: Chrissy LeMaire (@cl), netnerds.net
Website: https://dbatools.io
Copyright: (C) Chrissy LeMaire, [email protected]
License: MIT https://opensource.org/licenses/MIT
.LINK
https://dbatools.io/Copy-DbaSysDbUserObject
.EXAMPLE
Copy-DbaSysDbUserObject $sourceServer $destserver
Copies user objects from source to destination
#>
[CmdletBinding(SupportsShouldProcess = $true)]
param (
[Parameter(Mandatory)]
[ValidateNotNullOrEmpty()]
[DbaInstanceParameter]$Source,
[PSCredential]$SourceSqlCredential,
[Parameter(Mandatory)]
[ValidateNotNullOrEmpty()]
[DbaInstanceParameter[]]$Destination,
[PSCredential]$DestinationSqlCredential,
[switch]$Force,
[switch]$Classic,
[Alias('Silent')]
[switch]$EnableException
)
begin {
function get-sqltypename ($type) {
switch ($type) {
"VIEW" { "view" }
"SQL_TABLE_VALUED_FUNCTION" { "User table valued fsunction" }
"DEFAULT_CONSTRAINT" { "User default constraint" }
"SQL_STORED_PROCEDURE" { "User stored procedure" }
"RULE" { "User rule" }
"SQL_INLINE_TABLE_VALUED_FUNCTION" { "User inline table valued function" }
"SQL_TRIGGER" { "User server trigger" }
"SQL_SCALAR_FUNCTION" { "User scalar function" }
default { $type }
}
}
}
process {
try {
Write-Message -Level Verbose -Message "Connecting to $Source"
$sourceServer = Connect-SqlInstance -SqlInstance $Source -SqlCredential $SourceSqlCredential
}
catch {
Stop-Function -Message "Failure" -Category ConnectionError -ErrorRecord $_ -Target $Source
return
}
if (!(Test-SqlSa -SqlInstance $sourceServer -SqlCredential $SourceSqlCredential)) {
Stop-Function -Message "Not a sysadmin on $source. Quitting."
return
}
if (Test-FunctionInterrupt) { return }
foreach ($destinstance in $Destination) {
try {
Write-Message -Level Verbose -Message "Connecting to $destinstance"
$destServer = Connect-SqlInstance -SqlInstance $destinstance -SqlCredential $DestinationSqlCredential
}
catch {
Stop-Function -Message "Failure" -Category ConnectionError -ErrorRecord $_ -Target $destinstance -Continue
}
if (!(Test-SqlSa -SqlInstance $destServer -SqlCredential $DestinationSqlCredential)) {
Stop-Function -Message "Not a sysadmin on $destinstance" -Continue
}
$systemDbs = "master", "model", "msdb"
if (-not $Classic) {
foreach ($systemDb in $systemDbs) {
$smodb = $sourceServer.databases[$systemDb]
$destdb = $destserver.databases[$systemDb]
$tables = $smodb.Tables | Where-Object IsSystemObject -ne $true
$schemas = $smodb.Schemas | Where-Object IsSystemObject -ne $true
foreach ($schema in $schemas) {
$copyobject = [pscustomobject]@{
SourceServer = $sourceServer.Name
DestinationServer = $destServer.Name
Name = $schema
Type = "User schema in $systemDb"
Status = $null
Notes = $null
DateTime = [Sqlcollaborative.Dbatools.Utility.DbaDateTime](Get-Date)
}
$destschema = $destdb.Schemas | Where-Object Name -eq $schema.Name
$schmadoit = $true
if ($destschema) {
if (-not $force) {
$copyobject.Status = "Skipped"
$copyobject.Notes = "$schema exists on destination"
$schmadoit = $false
}
else {
if ($PSCmdlet.ShouldProcess($destServer, "Dropping schema $schema in $systemDb")) {
try {
Write-Message -Level Verbose -Message "Force specified. Dropping $schema in $destdb on $destinstance"
$destschema.Drop()
}
catch {
$schmadoit = $false
$copyobject.Status = "Failed"
$copyobject.Notes = $_.Exception.InnerException.InnerException.InnerException.Message
}
}
}
}
if ($schmadoit) {
$transfer = New-Object Microsoft.SqlServer.Management.Smo.Transfer $smodb
$null = $transfer.CopyAllObjects = $false
$null = $transfer.Options.WithDependencies = $true
$null = $transfer.ObjectList.Add($schema)
$sql = $transfer.ScriptTransfer()
if ($PSCmdlet.ShouldProcess($destServer, "Attempting to add schema $($schema.Name) to $systemDb")) {
try {
Write-Message -Level Debug -Message "$sql"
$null = $destServer.Query($sql, $systemDb)
$copyobject.Status = "Successful"
$copyobject.Notes = "May have also created dependencies"
}
catch {
$copyobject.Status = "Failed"
$copyobject.Notes = (Get-ErrorMessage -Record $_)
}
}
}
$copyobject | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
}
foreach ($table in $tables) {
$copyobject = [pscustomobject]@{
SourceServer = $sourceServer.Name
DestinationServer = $destServer.Name
Name = $table
Type = "User table in $systemDb"
Status = $null
Notes = $null
DateTime = [Sqlcollaborative.Dbatools.Utility.DbaDateTime](Get-Date)
}
$desttable = $destdb.Tables.Item($table.Name, $table.Schema)
$doit = $true
if ($desttable) {
if (-not $force) {
$copyobject.Status = "Skipped"
$copyobject.Notes = "$table exists on destination"
$doit = $false
}
else {
if ($PSCmdlet.ShouldProcess($destServer, "Dropping table $table in $systemDb")) {
try {
Write-Message -Level Verbose -Message "Force specified. Dropping $table in $destdb on $destinstance"
$desttable.Drop()
}
catch {
$doit = $false
$copyobject.Status = "Failed"
$copyobject.Notes = $_.Exception.InnerException.InnerException.InnerException.Message
}
}
}
}
if ($doit) {
$transfer = New-Object Microsoft.SqlServer.Management.Smo.Transfer $smodb
$null = $transfer.CopyAllObjects = $false
$null = $transfer.Options.WithDependencies = $true
$null = $transfer.ObjectList.Add($table)
$sql = $transfer.ScriptTransfer()
if ($PSCmdlet.ShouldProcess($destServer, "Attempting to add table $table to $systemDb")) {
try {
Write-Message -Level Debug -Message "$sql"
$null = $destServer.Query($sql, $systemDb)
$copyobject.Status = "Successful"
$copyobject.Notes = "May have also created dependencies"
}
catch {
$copyobject.Status = "Failed"
$copyobject.Notes = (Get-ErrorMessage -Record $_)
}
}
}
$copyobject | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
}
$userobjects = Get-DbaModule -SqlInstance $sourceserver -Database $systemDb -NoSystemObjects | Sort-Object Type
Write-Message -Level Verbose -Message "Copying from $systemDb"
foreach ($userobject in $userobjects) {
$name = "[$($userobject.SchemaName)].[$($userobject.Name)]"
$db = $userobject.Database
$type = get-sqltypename $userobject.Type
$sql = $userobject.Definition
$schema = $userobject.SchemaName
$copyobject = [pscustomobject]@{
SourceServer = $sourceServer.Name
DestinationServer = $destServer.Name
Name = $name
Type = "$type in $systemDb"
Status = $null
Notes = $null
DateTime = [Sqlcollaborative.Dbatools.Utility.DbaDateTime](Get-Date)
}
Write-Message -Level Debug -Message $sql
try {
Write-Message -Level Verbose -Message "Searching for $name in $db on $destinstance"
$result = Get-DbaModule -SqlInstance $destServer -NoSystemObjects -Database $db |
Where-Object { $psitem.Name -eq $userobject.Name -and $psitem.Type -eq $userobject.Type }
if ($result) {
Write-Message -Level Verbose -Message "Found $name in $db on $destinstance"
if (-not $Force) {
$copyobject.Status = "Skipped"
$copyobject.Notes = "$name exists on destination"
}
else {
$smobject = switch ($userobject.Type) {
"VIEW" { $smodb.Views.Item($userobject.Name, $userobject.SchemaName) }
"SQL_STORED_PROCEDURE" { $smodb.StoredProcedures.Item($userobject.Name, $userobject.SchemaName) }
"RULE" { $smodb.Rules.Item($userobject.Name, $userobject.SchemaName) }
"SQL_TRIGGER" { $smodb.Triggers.Item($userobject.Name, $userobject.SchemaName) }
"SQL_TABLE_VALUED_FUNCTION" { $smodb.UserDefinedFunctions.Item($name) }
"SQL_INLINE_TABLE_VALUED_FUNCTION" { $smodb.UserDefinedFunctions.Item($name) }
"SQL_SCALAR_FUNCTION" { $smodb.UserDefinedFunctions.Item($name) }
}
if ($smobject) {
Write-Message -Level Verbose -Message "Force specified. Dropping $smobject on $destdb on $destinstance using SMO"
$transfer = New-Object Microsoft.SqlServer.Management.Smo.Transfer $smodb
$null = $transfer.CopyAllObjects = $false
$null = $transfer.Options.WithDependencies = $true
$null = $transfer.ObjectList.Add($smobject)
$null = $transfer.Options.ScriptDrops = $true
$dropsql = $transfer.ScriptTransfer()
Write-Message -Level Debug -Message "$dropsql"
if ($PSCmdlet.ShouldProcess($destServer, "Attempting to drop $type $name from $systemDb")) {
$null = $destdb.Query("$dropsql")
}
}
else {
if ($PSCmdlet.ShouldProcess($destServer, "Attempting to drop $type $name from $systemDb using T-SQL")) {
$null = $destdb.Query("DROP FUNCTION $($userobject.name)")
}
}
if ($PSCmdlet.ShouldProcess($destServer, "Attempting to add $type $name to $systemDb")) {
$null = $destdb.Query("$sql")
$copyobject.Status = "Successful"
}
}
}
else {
if ($PSCmdlet.ShouldProcess($destServer, "Attempting to add $type $name to $systemDb")) {
$null = $destdb.Query("$sql")
$copyobject.Status = "Successful"
}
}
}
catch {
try {
$smobject = switch ($userobject.Type) {
"VIEW" { $smodb.Views.Item($userobject.Name, $userobject.SchemaName) }
"SQL_STORED_PROCEDURE" { $smodb.StoredProcedures.Item($userobject.Name, $userobject.SchemaName) }
"RULE" { $smodb.Rules.Item($userobject.Name, $userobject.SchemaName) }
"SQL_TRIGGER" { $smodb.Triggers.Item($userobject.Name, $userobject.SchemaName) }
}
if ($smobject) {
$transfer = New-Object Microsoft.SqlServer.Management.Smo.Transfer $smodb
$null = $transfer.CopyAllObjects = $false
$null = $transfer.Options.WithDependencies = $true
$null = $transfer.ObjectList.Add($smobject)
$sql = $transfer.ScriptTransfer()
Write-Message -Level Debug -Message "$sql"
Write-Message -Level Verbose -Message "Adding $smoobject on $destdb on $destinstance"
if ($PSCmdlet.ShouldProcess($destServer, "Attempting to add $type $name to $systemDb")) {
$null = $destdb.Query("$sql")
}
$copyobject.Status = "Successful"
$copyobject.Notes = "May have also installed dependencies"
}
else {
$copyobject.Status = "Failed"
$copyobject.Notes = (Get-ErrorMessage -Record $_)
}
}
catch {
$copyobject.Status = "Failed"
$copyobject.Notes = (Get-ErrorMessage -Record $_)
}
}
$copyobject | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
}
}
}
else {
foreach ($systemDb in $systemDbs) {
$sysdb = $sourceServer.databases[$systemDb]
$transfer = New-Object Microsoft.SqlServer.Management.Smo.Transfer $sysdb
$transfer.CopyAllObjects = $false
$transfer.CopyAllDatabaseTriggers = $true
$transfer.CopyAllDefaults = $true
$transfer.CopyAllRoles = $true
$transfer.CopyAllRules = $true
$transfer.CopyAllSchemas = $true
$transfer.CopyAllSequences = $true
$transfer.CopyAllSqlAssemblies = $true
$transfer.CopyAllSynonyms = $true
$transfer.CopyAllTables = $true
$transfer.CopyAllViews = $true
$transfer.CopyAllStoredProcedures = $true
$transfer.CopyAllUserDefinedAggregates = $true
$transfer.CopyAllUserDefinedDataTypes = $true
$transfer.CopyAllUserDefinedTableTypes = $true
$transfer.CopyAllUserDefinedTypes = $true
$transfer.CopyAllUserDefinedFunctions = $true
$transfer.CopyAllUsers = $true
$transfer.PreserveDbo = $true
$transfer.Options.AllowSystemObjects = $false
$transfer.Options.ContinueScriptingOnError = $true
$transfer.Options.IncludeDatabaseRoleMemberships = $true
$transfer.Options.Indexes = $true
$transfer.Options.Permissions = $true
$transfer.Options.WithDependencies = $false
Write-Message -Level Output -Message "Copying from $systemDb."
try {
$sqlQueries = $transfer.ScriptTransfer()
foreach ($sql in $sqlQueries) {
Write-Message -Level Debug -Message "$sql"
if ($PSCmdlet.ShouldProcess($destServer, $sql)) {
try {
$destServer.Query($sql, $systemDb)
}
catch {
# Don't care - long story having to do with duplicate stuff
}
}
}
}
catch {
# Don't care - long story having to do with duplicate stuff
}
}
}
}
}
end {
Test-DbaDeprecation -DeprecatedOn "1.0.0" -EnableException:$false -Alias Copy-SqlSysDbUserObjects
}
}
#ValidationTags#Messaging,FlowControl,Pipeline,CodeStyle#
function Copy-DbaXESessionTemplate {
<#
.SYNOPSIS
Copies non-Microsoft templates from the dbatools template repository (\bin\xetemplates\) to $home\Documents\SQL Server Management Studio\Templates\XEventTemplates.
.DESCRIPTION
Copies non-Microsoft templates from the dbatools template repository (\bin\xetemplates\) to $home\Documents\SQL Server Management Studio\Templates\XEventTemplates.
Useful for when you want to use the SSMS GUI.
.PARAMETER Path
The path to the template directory. Defaults to the dbatools template repository (\bin\xetemplates\).
.PARAMETER Destination
Path to the Destination directory, defaults to $home\Documents\SQL Server Management Studio\Templates\XEventTemplates.
.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.
.NOTES
Tags: ExtendedEvent, XE, XEvent
Website: https://dbatools.io
Author: Chrissy LeMaire (@cl), netnerds.net
Copyright: (C) Chrissy LeMaire, [email protected]
License: MIT https://opensource.org/licenses/MIT
.LINK
https://dbatools.io/Copy-DbaXESessionTemplate
.EXAMPLE
Copy-DbaXESessionTemplate
Copies non-Microsoft templates from the dbatools template repository (\bin\xetemplates\) to $home\Documents\SQL Server Management Studio\Templates\XEventTemplates.
.EXAMPLE
Copy-DbaXESessionTemplate -Path C:\temp\xetemplates
Copies your templates from C:\temp\xetemplates to $home\Documents\SQL Server Management Studio\Templates\XEventTemplates.
#>
[CmdletBinding()]
param (
[string[]]$Path = "$script:PSModuleRoot\bin\xetemplates",
[string]$Destination = "$home\Documents\SQL Server Management Studio\Templates\XEventTemplates",
[switch]$EnableException
)
process {
if (Test-FunctionInterrupt) { return }
foreach ($destinstance in $Destination) {
if (-not (Test-Path -Path $destinstance)) {
try {
$null = New-Item -ItemType Directory -Path $destinstance -ErrorAction Stop
}
catch {
Stop-Function -Message "Failure" -ErrorRecord $_ -Target $destinstance
}
}
try {
$files = (Get-DbaXESessionTemplate -Path $Path | Where-Object Source -ne Microsoft).Path
foreach ($file in $files) {
Write-Message -Level Output -Message "Copying $($file.Name) to $destinstance."
Copy-Item -Path $file -Destination $destinstance -ErrorAction Stop
}
}
catch {
Stop-Function -Message "Failure" -ErrorRecord $_ -Target $path
}
}
}
}
#ValidationTags#Messaging,FlowControl,Pipeline,CodeStyle#
function Disable-DbaAgHadr {
<#
.SYNOPSIS
Disables the Hadr service setting on the specified SQL Server.
.DESCRIPTION
In order to build an AG a cluster has to be built and then the Hadr enabled for the SQL Server
service. This function disables that feature for the SQL Server service.
.PARAMETER SqlInstance
The SQL Server that you're connecting to.
.PARAMETER Credential
Credential object used to connect to the Windows server itself as a different user
.PARAMETER WhatIf
Shows what would happen if the command were to run. No actions are actually performed.
.PARAMETER Confirm
Prompts you for confirmation before executing any changing operations within the command.
.PARAMETER Force
Will restart SQL Server and SQL Server Agent service to apply the change.
.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.
.NOTES
Tags: Hadr, AG, AvailabilityGroup
Author: Shawn Melton (@wsmelton | http://blog.wsmelton.info)
Website: https://dbatools.io
Copyright: (C) Chrissy LeMaire, [email protected]
License: MIT https://opensource.org/licenses/MIT
.LINK
https://dbatools.io/Disable-DbaAgHadr
.EXAMPLE
Disable-DbaAgHadr -SqlInstance sql2016 -Force
Sets Hadr service to disabled for the instance sql2016, and restart the service to apply the change.
.EXAMPLE
Disable-DbaAgHadr -SqlInstance sql2012\dev1 -Force
Sets Hadr service to disabled for the instance dev1 on sq2012, and restart the service to apply the change.
#>
[CmdletBinding(SupportsShouldProcess = $true, ConfirmImpact = "High")]
param (
[parameter(Mandatory, ValueFromPipeline)]
[Alias("ServerInstance", "SqlServer")]
[DbaInstanceParameter[]]$SqlInstance,
[PSCredential]$Credential,
[switch]$Force,
[Alias('Silent')]
[switch]$EnableException
)
begin {
function GetDbaAgHadr {
[CmdletBinding()]
param (
[parameter(Mandatory, ValueFromPipeline)]
[Alias("ServerInstance", "SqlServer")]
[DbaInstanceParameter[]]$SqlInstance,
[PSCredential]$Credential,
[Alias('Silent')]
[switch]$EnableException
)
process {
foreach ($instance in $SqlInstance) {
try {
$computer = $computerName = $instance.ComputerName
$instanceName = $instance.InstanceName
Write-Message -Level Verbose -Message "Connecting to $computer"
$currentState = Invoke-ManagedComputerCommand -ComputerName $computerName -ScriptBlock { $wmi.Services[$args[0]] | Select-Object IsHadrEnabled } -ArgumentList $instanceName -Credential $Credential
}
catch {
Stop-Function -Message "Failure connecting to $computer" -Category ConnectionError -ErrorRecord $_ -Target $instance
return
}
if ($null -eq $currentState.IsHadrEnabled) {
$isenabled = $false
}
else {
$isenabled = $currentState.IsHadrEnabled
}
[PSCustomObject]@{
ComputerName = $computer
InstanceName = $instanceName
SqlInstance = $instance.FullName
IsHadrEnabled = $isenabled
}
}
}
}
}
process {
foreach ($instance in $SqlInstance) {
$computer = $computerFullName = $instance.ComputerName
$instanceName = $instance.InstanceName
if (-not (Test-ElevationRequirement -ComputerName $instance)) {
return
}
$noChange = $false
switch ($instance.InstanceName) {
'MSSQLSERVER' { $agentName = 'SQLSERVERAGENT' }
default { $agentName = "SQLAgent`$$instanceName" }
}
try {
Write-Message -Level Verbose -Message "Checking current Hadr setting for $computer"
$currentState = GetDbaAgHadr -SqlInstance $instance -Credential $Credential
}
catch {
Stop-Function -Message "Failure to pull current state of Hadr setting on $computer" -Category ConnectionError -ErrorRecord $_ -Target $instance -Continue
}
$isHadrEnabled = $currentState.IsHadrEnabled
Write-Message -Level InternalComment -Message "$instance Hadr current value: $isHadrEnabled"
# hadr results from sql wmi can be iffy, skip the check
<#
if (-not $isHadrEnabled) {
Write-Message -Level Warning -Message "Hadr is already disabled for instance: $($instance.FullName)"
$noChange = $true
continue
}
#>
$scriptblock = {
$instance = $args[0]
$sqlService = $wmi.Services | Where-Object DisplayName -eq "SQL Server ($instance)"
$sqlService.ChangeHadrServiceSetting(0)
}
if ($noChange -eq $false) {
if ($PSCmdlet.ShouldProcess($instance, "Changing Hadr from $isHadrEnabled to 0 for $instance")) {
try {
Invoke-ManagedComputerCommand -ComputerName $computerFullName -Credential $Credential -ScriptBlock $scriptblock -ArgumentList $instancename
}
catch {
Stop-Function -Continue -Message "Failure on $($instance.FullName) | This may be because AlwaysOn Availability Groups feature requires the x86(non-WOW) or x64 Enterprise Edition of SQL Server 2012 (or later version) running on Windows Server 2008 (or later version) with WSFC hotfix KB 2494036 installed."
}
}
if (Test-Bound 'Force') {
if ($PSCmdlet.ShouldProcess($instance, "Force provided, restarting Engine and Agent service for $instance on $computerFullName")) {
try {
$null = Stop-DbaService -ComputerName $computerFullName -InstanceName $instanceName -Type Agent, Engine
$null = Start-DbaService -ComputerName $computerFullName -InstanceName $instanceName -Type Agent, Engine
}
catch {
Stop-Function -Message "Issue restarting $instance" -Target $instance -Continue
}
}
}
$newState = GetDbaAgHadr -SqlInstance $instance -Credential $Credential
if (Test-Bound -Not -ParameterName Force) {
Write-Message -Level Warning -Message "You must restart the SQL Server for it to take effect."
}
[PSCustomObject]@{
ComputerName = $newState.ComputerName
InstanceName = $newState.InstanceName
SqlInstance = $newState.SqlInstance
IsHadrEnabled = $false
}
}
}
}
}
#ValidationTags#Messaging,FlowControl,Pipeline,CodeStyle#
function Disable-DbaForceNetworkEncryption {
<#
.SYNOPSIS
Disables Force Encryption for a SQL Server instance
.DESCRIPTION
Disables Force Encryption for a SQL Server instance. Note that this requires access to the Windows Server, not the SQL instance itself.
This setting is found in Configuration Manager.
.PARAMETER SqlInstance
The target SQL Server. Defaults to localhost.
.PARAMETER Credential
Allows you to login to the computer (not SQL Server instance) using alternative Windows credentials.
.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.
.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.
.EXAMPLE
Disable-DbaForceNetworkEncryption
Disables Force Encryption on the default (MSSQLSERVER) instance on localhost - requires (and checks for) RunAs admin.
.EXAMPLE
Disable-DbaForceNetworkEncryption -SqlInstance sql01\SQL2008R2SP2
Disables Force Network Encryption for the SQL2008R2SP2 on sql01. Uses Windows Credentials to both login and modify the registry.
.EXAMPLE
Disable-DbaForceNetworkEncryption -SqlInstance sql01\SQL2008R2SP2 -WhatIf
Shows what would happen if the command were executed.
.NOTES
Tags: Certificate
Author: Chrissy LeMaire (@cl), netnerds.net
Website: https://dbatools.io
Copyright: (C) Chrissy LeMaire, [email protected]
License: MIT https://opensource.org/licenses/MIT
#>
[CmdletBinding(SupportsShouldProcess, ConfirmImpact = "Low")]
param (
[Parameter(ValueFromPipeline)]
[Alias("ServerInstance", "SqlServer", "ComputerName")]
[DbaInstanceParameter[]]$SqlInstance = $env:COMPUTERNAME,
[PSCredential]$Credential,
[Alias('Silent')]
[switch]$EnableException
)
process {
foreach ($instance in $sqlinstance) {
Write-Message -Level VeryVerbose -Message "Processing $instance." -Target $instance
$null = Test-ElevationRequirement -ComputerName $instance -Continue
Write-Message -Level Verbose -Message "Resolving hostname."
$resolved = $null
$resolved = Resolve-DbaNetworkName -ComputerName $instance -Turbo
if ($null -eq $resolved) {
Stop-Function -Message "Can't resolve $instance." -Target $instance -Continue -Category InvalidArgument
}
Write-Message -Level Output -Message "Connecting to SQL WMI on $($instance.ComputerName)."
try {
$sqlwmi = Invoke-ManagedComputerCommand -ComputerName $resolved.FullComputerName -ScriptBlock { $wmi.Services } -Credential $Credential -ErrorAction Stop | Where-Object DisplayName -eq "SQL Server ($($instance.InstanceName))"
}
catch {
Stop-Function -Message "Failed to access $instance." -Target $instance -Continue -ErrorRecord $_
}
$regroot = ($sqlwmi.AdvancedProperties | Where-Object Name -eq REGROOT).Value
$vsname = ($sqlwmi.AdvancedProperties | Where-Object Name -eq VSNAME).Value
try {
$instancename = $sqlwmi.DisplayName.Replace('SQL Server (', '').Replace(')', '') # Don't clown, I don't know regex :(
}
catch {
# Probably because the instance name has been aliased or does not exist or samthin
}
$serviceaccount = $sqlwmi.ServiceAccount
if ([System.String]::IsNullOrEmpty($regroot)) {
$regroot = $sqlwmi.AdvancedProperties | Where-Object { $_ -match 'REGROOT' }
$vsname = $sqlwmi.AdvancedProperties | Where-Object { $_ -match 'VSNAME' }
if (![System.String]::IsNullOrEmpty($regroot)) {
$regroot = ($regroot -Split 'Value\=')[1]
$vsname = ($vsname -Split 'Value\=')[1]
}
else {
Stop-Function -Message "Can't find instance $vsname on $instance." -Continue -Category ObjectNotFound -Target $instance
}
}
if ([System.String]::IsNullOrEmpty($vsname)) { $vsname = $instance }
Write-Message -Level Output -Message "Regroot: $regroot" -Target $instance
Write-Message -Level Output -Message "ServiceAcct: $serviceaccount" -Target $instance
Write-Message -Level Output -Message "InstanceName: $instancename" -Target $instance
Write-Message -Level Output -Message "VSNAME: $vsname" -Target $instance
$scriptblock = {
$regpath = "Registry::HKEY_LOCAL_MACHINE\$($args[0])\MSSQLServer\SuperSocketNetLib"
$cert = (Get-ItemProperty -Path $regpath -Name Certificate).Certificate
$oldvalue = (Get-ItemProperty -Path $regpath -Name ForceEncryption).ForceEncryption
Set-ItemProperty -Path $regpath -Name ForceEncryption -Value $false
$forceencryption = (Get-ItemProperty -Path $regpath -Name ForceEncryption).ForceEncryption
[pscustomobject]@{
ComputerName = $env:COMPUTERNAME
InstanceName = $args[2]
SqlInstance = $args[1]
ForceEncryption = ($forceencryption -eq $true)
CertificateThumbprint = $cert
}
}
if ($PScmdlet.ShouldProcess("local", "Connecting to $instance to modify the ForceEncryption value in $regroot for $($instance.InstanceName)")) {
try {
Invoke-Command2 -ComputerName $resolved.FullComputerName -Credential $Credential -ArgumentList $regroot, $vsname, $instancename -ScriptBlock $scriptblock -ErrorAction Stop
Write-Message -Level Critical -Message "Force encryption was successfully set on $($resolved.FullComputerName) for the $instancename instance. You must now restart the SQL Server for changes to take effect." -Target $instance
}
catch {
Stop-Function -Message "Failed to connect to $($resolved.FullComputerName) using PowerShell remoting!" -ErrorRecord $_ -Target $instance -Continue
}
}
}
}
}
function Disable-DbaTraceFlag {
<#
.SYNOPSIS
Disable a Global Trace Flag that is currently running
.DESCRIPTION
The function will disable a Trace Flag that is currently running globally on the SQL Server instance(s) listed
.PARAMETER SqlInstance
Allows you to specify a comma separated list of servers to query.
.PARAMETER SqlCredential
Login to the target instance using alternative credentials. Windows and SQL Authentication supported. Accepts credential objects (Get-Credential)
.PARAMETER TraceFlag
Trace flag number to enable globally
.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.
.NOTES
Tags: TraceFlag
Author: Garry Bargsley (@gbargsley), http://blog.garrybargsley.com
Website: https://dbatools.io
Copyright: (C) Chrissy LeMaire, [email protected]
License: MIT https://opensource.org/licenses/MIT
.LINK
https://dbatools.io/Disable-DbaTraceFlag
.EXAMPLE
Disable-DbaTraceFlag -SqlInstance sql2016 -TraceFlag 3226
Disable the globally running trace flag 3226 on SQL Server instance sql2016
#>
[CmdletBinding()]
param (
[parameter(Position = 0, Mandatory, ValueFromPipeline)]
[Alias("ServerInstance", "SqlServer", "SqlServers")]
[DbaInstanceParameter[]]$SqlInstance,
[PSCredential]$SqlCredential,
[parameter(Mandatory)]
[int[]]$TraceFlag,
[Alias('Silent')]
[switch]$EnableException
)
process {
foreach ($instance in $SqlInstance) {
Write-Message -Level Verbose -Message "Connecting to $instance"
try {
$server = Connect-SqlInstance -SqlInstance $instance -SqlCredential $SqlCredential
}
catch {
Stop-Function -Message "Failure" -Category ConnectionError -ErrorRecord $_ -Target $instance -Continue
}
$current = Get-DbaTraceFlag -SqlInstance $server -EnableException
foreach ($tf in $TraceFlag) {
$TraceFlagInfo = [pscustomobject]@{
SourceServer = $server.ComputerName
InstanceName = $server.ServiceName
SqlInstance = $server.DomainInstanceName
TraceFlag = $tf
Status = $null
Notes = $null
DateTime = [DbaDateTime](Get-Date)
}
if ($tf -notin $current.TraceFlag) {
$TraceFlagInfo.Status = 'Skipped'
$TraceFlagInfo.Notes = "Trace Flag is not running."
$TraceFlagInfo
Write-Message -Level Warning -Message "Trace Flag $tf is not currently running on $instance"
continue
}
try {
$query = "DBCC TRACEOFF ($tf, -1)"
$server.Query($query)
}
catch {
$TraceFlagInfo.Status = "Failed"
$TraceFlagInfo.Notes = $_.Exception.Message
$TraceFlagInfo
Stop-Function -Message "Failure" -ErrorRecord $_ -Target $server -Continue
}
$TraceFlagInfo.Status = "Successful"
$TraceFlagInfo
}
}
}
}
function Dismount-DbaDatabase {
<#
.SYNOPSIS
Detach a SQL Server Database.
.DESCRIPTION
This command detaches one or more SQL Server databases. If necessary, -Force can be used to break mirrors and remove databases from availability groups prior to detaching.
.PARAMETER SqlInstance
The SQL Server instance.
.PARAMETER SqlCredential
Login to the target instance using alternative credentials. Windows and SQL Authentication supported. Accepts credential objects (Get-Credential)
.PARAMETER Database
The database(s) to detach.
.PARAMETER FileStructure
A StringCollection object value that contains a list database files. If FileStructure is not specified, BackupHistory will be used to guess the structure.
.PARAMETER InputObject
A collection of databases (such as returned by Get-DbaDatabase), to be detached.
.PARAMETER UpdateStatistics
If this switch is enabled, statistics for the database will be updated prior to detaching it.
.PARAMETER Force
If this switch is enabled and the database is part of a mirror, the mirror will be broken. If the database is part of an Availability Group, it will be removed from the AG.
.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.
.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.
.NOTES
Tags: Database
Author: Chrissy LeMaire (@cl), netnerds.net
Website: https://dbatools.io
Copyright: (C) Chrissy LeMaire, [email protected]
License: MIT https://opensource.org/licenses/MIT
.LINK
https://dbatools.io/Dismount-DbaDatabase
.EXAMPLE
Detach-DbaDatabase -SqlInstance sql2016b -Database SharePoint_Config, WSS_Logging
Detaches SharePoint_Config and WSS_Logging from sql2016b
.EXAMPLE
Get-DbaDatabase -SqlInstance sql2016b -Database 'PerformancePoint Service Application_10032db0fa0041df8f913f558a5dc0d4' | Detach-DbaDatabase -Force
Detaches 'PerformancePoint Service Application_10032db0fa0041df8f913f558a5dc0d4' from sql2016b. Since Force was specified, if the database is part of mirror, the mirror will be broken prior to detaching.
If the database is part of an Availability Group, it will first be dropped prior to detachment.
.EXAMPLE
Get-DbaDatabase -SqlInstance sql2016b -Database WSS_Logging | Detach-DbaDatabase -Force -WhatIf
Shows what would happen if the command were to execute (without actually executing the detach/break/remove commands).
#>
[CmdletBinding(SupportsShouldProcess, DefaultParameterSetName = "Default")]
Param (
[parameter(Mandatory, ParameterSetName = 'SqlInstance')]
[Alias("ServerInstance", "SqlServer")]
[DbaInstanceParameter[]]$SqlInstance,
[PSCredential]$SqlCredential,
[parameter(Mandatory, ParameterSetName = 'SqlInstance')]
[string]$Database,
[parameter(Mandatory, ParameterSetName = 'Pipeline', ValueFromPipeline)]
[Microsoft.SqlServer.Management.Smo.Database[]]$InputObject,
[Switch]$UpdateStatistics,
[switch]$Force,
[Alias('Silent')]
[switch]$EnableException
)
process {
foreach ($instance in $SqlInstance) {
try {
$server = Connect-SqlInstance -SqlInstance $instance -SqlCredential $sqlcredential
}
catch {
Stop-Function -Message "Failure" -Category ConnectionError -ErrorRecord $_ -Target $instance -Continue
}
if ($Database) {
$InputObject += $server.Databases | Where-Object Name -in $Database
}
else {
$InputObject += $server.Databases
}
if ($ExcludeDatabase) {
$InputObject = $InputObject | Where-Object Name -NotIn $ExcludeDatabase
}
}
foreach ($db in $InputObject) {
$db.Refresh()
$server = $db.Parent
if ($db.IsSystemObject) {
Stop-Function -Message "$db is a system database and cannot be detached using this method." -Target $db -Continue
}
Write-Message -Level Verbose -Message "Checking replication status."
if ($db.ReplicationOptions -ne "None") {
Stop-Function -Message "Skipping $db on $server because it is replicated." -Target $db -Continue
}
# repeat because different servers could be piped in
$snapshots = (Get-DbaDbSnapshot -SqlInstance $server).SnapshotOf
Write-Message -Level Verbose -Message "Checking for snaps"
if ($db.Name -in $snapshots) {
Write-Message -Level Warning -Message "Database $db has snapshots, you need to drop them before detaching. Skipping $db on $server."
Continue
}
Write-Message -Level Verbose -Message "Checking mirror status"
if ($db.IsMirroringEnabled -and !$Force) {
Stop-Function -Message "$db on $server is being mirrored. Use -Force to break mirror or use the safer backup/restore method." -Target $db -Continue
}
Write-Message -Level Verbose -Message "Checking Availability Group status"
if ($db.AvailabilityGroupName -and !$Force) {
$ag = $db.AvailabilityGroupName
Stop-Function -Message "$db on $server is part of an Availability Group ($ag). Use -Force to drop from $ag availability group to detach. Alternatively, you can use the safer backup/restore method." -Target $db -Continue
}
$sessions = Get-DbaProcess -SqlInstance $db.Parent -Database $db.Name
if ($sessions -and !$Force) {
Stop-Function -Message "$db on $server currently has connected users and cannot be dropped. Use -Force to kill all connections and detach the database." -Target $db -Continue
}
if ($force) {
if ($sessions) {
If ($Pscmdlet.ShouldProcess($server, "Killing $($sessions.count) sessions which are connected to $db")) {
$null = $sessions | Stop-DbaProcess -ErrorAction SilentlyContinue -WarningAction SilentlyContinue
}
}
if ($db.IsMirroringEnabled) {
If ($Pscmdlet.ShouldProcess($server, "Breaking mirror for $db on $server")) {
try {
Write-Message -Level Warning -Message "Breaking mirror for $db on $server."
$db.ChangeMirroringState([Microsoft.SqlServer.Management.Smo.MirroringOption]::Off)
$db.Alter()
$db.Refresh()
}
catch {
Stop-Function -Message "Could not break mirror for $db on $server - not detaching." -Target $db -ErrorRecord $_ -Continue
}
}
}
if ($db.AvailabilityGroupName) {
$ag = $db.AvailabilityGroupName
If ($Pscmdlet.ShouldProcess($server, "Attempting remove $db on $server from Availability Group $ag")) {
try {
$server.AvailabilityGroups[$ag].AvailabilityDatabases[$db.name].Drop()
Write-Message -Level Verbose -Message "Successfully removed $db from detach from $ag on $server."
}
catch {
if ($_.Exception.InnerException) {
$exception = $_.Exception.InnerException.ToString() -Split "System.Data.SqlClient.SqlException: "
$exception = " | $(($exception[1] -Split "at Microsoft.SqlServer.Management.Common.ConnectionManager")[0])".TrimEnd()
}
Stop-Function -Message "Could not remove $db from $ag on $server $exception." -Target $db -ErrorRecord $_ -Continue
}
}
}
$sessions = Get-DbaProcess -SqlInstance $db.Parent -Database $db.Name
if ($sessions) {
If ($Pscmdlet.ShouldProcess($server, "Killing $($sessions.count) sessions which are still connected to $db")) {
$null = $sessions | Stop-DbaProcess -ErrorAction SilentlyContinue -WarningAction SilentlyContinue
}
}
}
If ($Pscmdlet.ShouldProcess($server, "Detaching $db on $server")) {
try {
$server.DetachDatabase($db.Name, $UpdateStatistics)
[pscustomobject]@{
ComputerName = $server.ComputerName
InstanceName = $server.ServiceName
SqlInstance = $server.DomainInstanceName
Database = $db.name
DetachResult = "Success"
}
}
catch {
Stop-Function -Message "Failure" -Target $db -ErrorRecord $_ -Continue
}
}
}
}
}
#ValidationTags#Messaging,FlowControl,Pipeline,CodeStyle#
function Enable-DbaAgHadr {
<#
.SYNOPSIS
Enables the Hadr service setting on the specified SQL Server.
.DESCRIPTION
In order to build an AG a cluster has to be built and then the Hadr enabled for the SQL Server
service. This function enables that feature for the SQL Server service.
.PARAMETER SqlInstance
The SQL Server that you're connecting to.
.PARAMETER Credential
Credential object used to connect to the Windows server itself as a different user
.PARAMETER WhatIf
Shows what would happen if the command were to run. No actions are actually performed.
.PARAMETER Confirm
Prompts you for confirmation before executing any changing operations within the command.
.PARAMETER Force
Will restart SQL Server and SQL Server Agent service to apply the change.
.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.
.NOTES
Tags: Hadr, AG, AvailabilityGroup
Author: Shawn Melton (@wsmelton | http://blog.wsmelton.info)
Website: https://dbatools.io
Copyright: (C) Chrissy LeMaire, [email protected]
License: MIT https://opensource.org/licenses/MIT
.LINK
https://dbatools.io/Enable-DbaAgHadr
.EXAMPLE
Enable-DbaAgHadr -SqlInstance sql2016 -Force
Sets Hadr service to enabled for the instance sql2016, and restart the service to apply the change.
.EXAMPLE
Enable-DbaAgHadr -SqlInstance sql2012\dev1 -Force
Sets Hadr service to disabled for the instance dev1 on sq2012, and restart the service to apply the change.
#>
[CmdletBinding(SupportsShouldProcess = $true, ConfirmImpact = "High")]
param (
[parameter(Mandatory, ValueFromPipeline)]
[Alias("ServerInstance", "SqlServer")]
[DbaInstanceParameter[]]$SqlInstance,
[PSCredential]$Credential,
[switch]$Force,
[Alias('Silent')]
[switch]$EnableException
)
begin {
function GetDbaAgHadr {
<#
.SYNOPSIS
Gets the Hadr service setting on the specified SQL Server instance.
.DESCRIPTION
Gets the Hadr setting, from the service level, and returns true or false for the specified SQL Server instance.
.PARAMETER SqlInstance
The SQL Server that you're connecting to.
.PARAMETER Credential
Credential object used to connect to the Windows server itself as a different user
.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.
.NOTES
Tags: Hadr, AG, AvailabilityGroup
Author: Shawn Melton (@wsmelton | http://blog.wsmelton.info)
Website: https://dbatools.io
Copyright: (C) Chrissy LeMaire, [email protected]
License: MIT https://opensource.org/licenses/MIT
.LINK
https://dbatools.io/GetDbaAgHadr
.EXAMPLE
GetDbaAgHadr -SqlInstance sql2016
Returns a status of the Hadr setting for sql2016 SQL Server instance.
#>
[CmdletBinding()]
param (
[parameter(Mandatory, ValueFromPipeline)]
[Alias("ServerInstance", "SqlServer")]
[DbaInstanceParameter[]]$SqlInstance,
[PSCredential]$Credential,
[Alias('Silent')]
[switch]$EnableException
)
process {
foreach ($instance in $SqlInstance) {
try {
$computer = $computerName = $instance.ComputerName
$instanceName = $instance.InstanceName
Write-Message -Level Verbose -Message "Connecting to $computer"
$currentState = Invoke-ManagedComputerCommand -ComputerName $computerName -ScriptBlock { $wmi.Services[$args[0]] | Select-Object IsHadrEnabled } -ArgumentList $instanceName -Credential $Credential
}
catch {
Stop-Function -Message "Failure connecting to $computer" -Category ConnectionError -ErrorRecord $_ -Target $instance
return
}
if ($null -eq $currentState.IsHadrEnabled) {
$isenabled = $false
}
else {
$isenabled = $currentState.IsHadrEnabled
}
[PSCustomObject]@{
ComputerName = $computer
InstanceName = $instanceName
SqlInstance = $instance.FullName
IsHadrEnabled = $isenabled
}
}
}
}
}
process {
foreach ($instance in $SqlInstance) {
$computer = $computerFullName = $instance.ComputerName
$instanceName = $instance.InstanceName
if (-not (Test-ElevationRequirement -ComputerName $instance)) {
return
}
$noChange = $false
switch ($instance.InstanceName) {
'MSSQLSERVER' { $agentName = 'SQLSERVERAGENT' }
default { $agentName = "SQLAgent`$$instanceName" }
}
try {
Write-Message -Level Verbose -Message "Checking current Hadr setting for $computer"
$currentState = GetDbaAgHadr -SqlInstance $instance -Credential $Credential
}
catch {
Stop-Function -Message "Failure to pull current state of Hadr setting on $computer" -Category ConnectionError -ErrorRecord $_ -Target $instance -Continue
}
$isHadrEnabled = $currentState.IsHadrEnabled
Write-Message -Level InternalComment -Message "$instance Hadr current value: $isHadrEnabled"
# hadr results from sql wmi can be iffy, skip the check
<#
if ($isHadrEnabled) {
Write-Message -Level Warning -Message "Hadr is already enabled for instance: $($instance.FullName)"
$noChange = $true
continue
}
#>
$scriptblock = {
$instance = $args[0]
$sqlService = $wmi.Services | Where-Object DisplayName -eq "SQL Server ($instance)"
$sqlService.ChangeHadrServiceSetting(1)
}
if ($noChange -eq $false) {
if ($PSCmdlet.ShouldProcess($instance, "Changing Hadr from $isHadrEnabled to 1 for $instance")) {
try {
Invoke-ManagedComputerCommand -ComputerName $computerFullName -Credential $Credential -ScriptBlock $scriptblock -ArgumentList $instancename
}
catch {
Stop-Function -Continue -Message "Failure on $($instance.FullName) | This may be because AlwaysOn Availability Groups feature requires the x86(non-WOW) or x64 Enterprise Edition of SQL Server 2012 (or later version) running on Windows Server 2008 (or later version) with WSFC hotfix KB 2494036 installed."
}
}
}
if (Test-Bound -ParameterName Force) {
if ($PSCmdlet.ShouldProcess($instance, "Force provided, restarting Engine and Agent service for $instance on $computerFullName")) {
try {
$null = Stop-DbaService -ComputerName $computerFullName -InstanceName $instanceName -Type Agent, Engine
$null = Start-DbaService -ComputerName $computerFullName -InstanceName $instanceName -Type Agent, Engine
}
catch {
Stop-Function -Message "Issue restarting $instance" -Target $instance -Continue
}
}
}
$newState = GetDbaAgHadr -SqlInstance $instance -Credential $Credential
if (Test-Bound -Not -ParameterName Force) {
Write-Message -Level Warning -Message "You must restart the SQL Server for it to take effect."
}
[PSCustomObject]@{
ComputerName = $newState.ComputerName
InstanceName = $newState.InstanceName
SqlInstance = $newState.SqlInstance
IsHadrEnabled = $true
}
}
}
}
#ValidationTags#Messaging,FlowControl,Pipeline,CodeStyle#
function Enable-DbaForceNetworkEncryption {
<#
.SYNOPSIS
Enables Force Encryption for a SQL Server instance.
.DESCRIPTION
Enables Force Encryption for a SQL Server instance. Note that this requires access to the Windows Server, not the SQL instance itself.
This setting is found in Configuration Manager.
.PARAMETER SqlInstance
The target SQL Server.
.PARAMETER Credential
Allows you to login to the computer (not SQL Server instance) using alternative Windows credentials
.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.
.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.
.EXAMPLE
Enable-DbaForceNetworkEncryption
Enables Force Encryption on the default (MSSQLSERVER) instance on localhost. Requires (and checks for) RunAs admin.
.EXAMPLE
Enable-DbaForceNetworkEncryption -SqlInstance sql01\SQL2008R2SP2
Enables Force Network Encryption for the SQL2008R2SP2 on sql01. Uses Windows Credentials to both connect and modify the registry.
.EXAMPLE
Enable-DbaForceNetworkEncryption -SqlInstance sql01\SQL2008R2SP2 -WhatIf
Shows what would happen if the command were executed.
.NOTES
Tags: Certificate
Author: Chrissy LeMaire (@cl), netnerds.net
Website: https://dbatools.io
Copyright: (C) Chrissy LeMaire, [email protected]
License: MIT https://opensource.org/licenses/MIT
#>
[CmdletBinding(SupportsShouldProcess, ConfirmImpact = "Low", DefaultParameterSetName = 'Default')]
param (
[Parameter(ValueFromPipeline)]
[Alias("ServerInstance", "SqlServer", "ComputerName")]
[DbaInstanceParameter[]]
$SqlInstance = $env:COMPUTERNAME,
[PSCredential]$Credential,
[Alias('Silent')]
[switch]$EnableException
)
process {
foreach ($instance in $sqlinstance) {
Write-Message -Level VeryVerbose -Message "Processing $instance." -Target $instance
$null = Test-ElevationRequirement -ComputerName $instance -Continue
Write-Message -Level Verbose -Message "Resolving hostname."
$resolved = $null
$resolved = Resolve-DbaNetworkName -ComputerName $instance -Turbo
if ($null -eq $resolved) {
Stop-Function -Message "Can't resolve $instance." -Target $instance -Continue -Category InvalidArgument
}
Write-Message -Level Output -Message "Connecting to SQL WMI on $($instance.ComputerName)."
try {
$sqlwmi = Invoke-ManagedComputerCommand -ComputerName $resolved.FullComputerName -ScriptBlock { $wmi.Services } -Credential $Credential -ErrorAction Stop | Where-Object DisplayName -eq "SQL Server ($($instance.InstanceName))"
}
catch {
Stop-Function -Message "Failed to access $instance" -Target $instance -Continue -ErrorRecord $_
}
$regroot = ($sqlwmi.AdvancedProperties | Where-Object Name -eq REGROOT).Value
$vsname = ($sqlwmi.AdvancedProperties | Where-Object Name -eq VSNAME).Value
try {
$instancename = $sqlwmi.DisplayName.Replace('SQL Server (', '').Replace(')', '') # Don't clown, I don't know regex :(
}
catch {
# Probably because the instance name has been aliased or does not exist or samthin
}
$serviceaccount = $sqlwmi.ServiceAccount
if ([System.String]::IsNullOrEmpty($regroot)) {
$regroot = $sqlwmi.AdvancedProperties | Where-Object { $_ -match 'REGROOT' }
$vsname = $sqlwmi.AdvancedProperties | Where-Object { $_ -match 'VSNAME' }
if (![System.String]::IsNullOrEmpty($regroot)) {
$regroot = ($regroot -Split 'Value\=')[1]
$vsname = ($vsname -Split 'Value\=')[1]
}
else {
Stop-Function -Message "Can't find instance $vsname on $instance." -Continue -Category ObjectNotFound -Target $instance
}
}
if ([System.String]::IsNullOrEmpty($vsname)) { $vsname = $instance }
Write-Message -Level Output -Message "Regroot: $regroot" -Target $instance
Write-Message -Level Output -Message "ServiceAcct: $serviceaccount" -Target $instance
Write-Message -Level Output -Message "InstanceName: $instancename" -Target $instance
Write-Message -Level Output -Message "VSNAME: $vsname" -Target $instance
$scriptblock = {
$regpath = "Registry::HKEY_LOCAL_MACHINE\$($args[0])\MSSQLServer\SuperSocketNetLib"
$cert = (Get-ItemProperty -Path $regpath -Name Certificate).Certificate
$oldvalue = (Get-ItemProperty -Path $regpath -Name ForceEncryption).ForceEncryption
Set-ItemProperty -Path $regpath -Name ForceEncryption -Value $true
$forceencryption = (Get-ItemProperty -Path $regpath -Name ForceEncryption).ForceEncryption
[pscustomobject]@{
ComputerName = $env:COMPUTERNAME
InstanceName = $args[2]
SqlInstance = $args[1]
ForceEncryption = ($forceencryption -eq $true)
CertificateThumbprint = $cert
}
}
if ($PScmdlet.ShouldProcess("local", "Connecting to $instance to modify the ForceEncryption value in $regroot for $($instance.InstanceName)")) {
try {
Invoke-Command2 -ComputerName $resolved.FullComputerName -Credential $Credential -ArgumentList $regroot, $vsname, $instancename -ScriptBlock $scriptblock -ErrorAction Stop | Select-Object -Property * -ExcludeProperty PSComputerName, RunspaceId, PSShowComputerName
Write-Message -Level Critical -Message "Force encryption was successfully set on $($resolved.FullComputerName) for the $instancename instance. You must now restart the SQL Server for changes to take effect." -Target $instance
}
catch {
Stop-Function -Message "Failed to connect to $($resolved.FullComputerName) using PowerShell remoting!" -ErrorRecord $_ -Target $instance -Continue
}
}
}
}
}
function Enable-DbaTraceFlag {
<#
.SYNOPSIS
Enable Global Trace Flag(s)
.DESCRIPTION
The function will set one or multiple trace flags on the SQL Server instance(s) listed
.PARAMETER SqlInstance
Allows you to specify a comma separated list of servers to query.
.PARAMETER SqlCredential
Login to the target instance using alternative credentials. Windows and SQL Authentication supported. Accepts credential objects (Get-Credential)
.PARAMETER TraceFlag
Trace flag number(s) to enable globally
.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.
.NOTES
Tags: TraceFlag
Author: Garry Bargsley (@gbargsley), http://blog.garrybargsley.com
Website: https://dbatools.io
Copyright: (C) Chrissy LeMaire, [email protected]
License: MIT https://opensource.org/licenses/MIT
.LINK
https://dbatools.io/Enable-DbaTraceFlag
.EXAMPLE
Enable-DbaTraceFlag -SqlInstance sql2016 -TraceFlag 3226
Enable the trace flag 3226 on SQL Server instance sql2016
.EXAMPLE
Enable-DbaTraceFlag -SqlInstance sql2016 -TraceFlag 1117, 1118
Enable multiple trace flags on SQL Server instance sql2016
#>
[CmdletBinding()]
param (
[parameter(Position = 0, Mandatory, ValueFromPipeline)]
[Alias("ServerInstance", "SqlServer", "SqlServers")]
[DbaInstanceParameter[]]$SqlInstance,
[PSCredential]$SqlCredential,
[parameter(Mandatory)]
[int[]]$TraceFlag,
[Alias('Silent')]
[switch]$EnableException
)
process {
foreach ($instance in $SqlInstance) {
Write-Message -Level Verbose -Message "Connecting to $instance"
try {
$server = Connect-SqlInstance -SqlInstance $instance -SqlCredential $SqlCredential
}
catch {
Stop-Function -Message "Failure" -Category ConnectionError -ErrorRecord $_ -Target $instance -Continue
}
$CurrentRunningTraceFlags = Get-DbaTraceFlag -SqlInstance $server -EnableException
# We could combine all trace flags but the granularity is worth it
foreach ($tf in $TraceFlag) {
$TraceFlagInfo = [PSCustomObject]@{
SourceServer = $server.ComputerName
InstanceName = $server.ServiceName
SqlInstance = $server.DomainInstanceName
TraceFlag = $tf
Status = $null
Notes = $null
DateTime = [DbaDateTime](Get-Date)
}
if ($CurrentRunningTraceFlags.TraceFlag -contains $tf) {
$TraceFlagInfo.Status = 'Skipped'
$TraceFlagInfo.Notes = "The Trace flag is already running."
$TraceFlagInfo
Write-Message -Level Warning -Message "The Trace flag [$tf] is already running globally."
continue
}
try {
$query = "DBCC TRACEON ($tf, -1)"
$server.Query($query)
$server.Refresh()
}
catch {
$TraceFlagInfo.Status = "Failed"
$TraceFlagInfo.Notes = $_.Exception.Message
$TraceFlagInfo
Stop-Function -Message "Failure" -ErrorRecord $_ -Target $server -Continue
}
$TraceFlagInfo.Status = "Successful"
$TraceFlagInfo
}
}
}
}
function Expand-DbaTLogResponsibly {
<#
.SYNOPSIS
This command will help you to automatically grow your transaction log file in a responsible way (preventing the generation of too many VLFs).
.DESCRIPTION
As you may already know, having a transaction log file with too many Virtual Log Files (VLFs) can hurt your database performance in many ways.
Example:
Too many VLFs can cause transaction log backups to slow down and can also slow down database recovery and, in extreme cases, even impact insert/update/delete performance.
References:
http://www.sqlskills.com/blogs/kimberly/transaction-log-vlfs-too-many-or-too-few/
http://blogs.msdn.com/b/saponsqlserver/archive/2012/02/22/too-many-virtual-log-files-vlfs-can-cause-slow-database-recovery.aspx
http://www.brentozar.com/blitz/high-virtual-log-file-vlf-count/
In order to get rid of this fragmentation we need to grow the file taking the following into consideration:
- How many VLFs are created when we perform a grow operation or when an auto-grow is invoked?
Note: In SQL Server 2014 this algorithm has changed (http://www.sqlskills.com/blogs/paul/important-change-vlf-creation-algorithm-sql-server-2014/)
Attention:
We are growing in MB instead of GB because of known issue prior to SQL 2012:
More detail here:
http://www.sqlskills.com/BLOGS/PAUL/post/Bug-log-file-growth-broken-for-multiples-of-4GB.aspx
and
http://connect.microsoft.com/SqlInstance/feedback/details/481594/log-growth-not-working-properly-with-specific-growth-sizes-vlfs-also-not-created-appropriately
or
https://connect.microsoft.com/SqlInstance/feedback/details/357502/transaction-log-file-size-will-not-grow-exactly-4gb-when-filegrowth-4gb
Understanding related problems:
http://www.sqlskills.com/blogs/kimberly/transaction-log-vlfs-too-many-or-too-few/
http://blogs.msdn.com/b/saponsqlserver/archive/2012/02/22/too-many-virtual-log-files-vlfs-can-cause-slow-database-recovery.aspx
http://www.brentozar.com/blitz/high-virtual-log-file-vlf-count/
Known bug before SQL Server 2012
http://www.sqlskills.com/BLOGS/PAUL/post/Bug-log-file-growth-broken-for-multiples-of-4GB.aspx
http://connect.microsoft.com/SqlInstance/feedback/details/481594/log-growth-not-working-properly-with-specific-growth-sizes-vlfs-also-not-created-appropriately
https://connect.microsoft.com/SqlInstance/feedback/details/357502/transaction-log-file-size-will-not-grow-exactly-4gb-when-filegrowth-4gb
How it works?
The transaction log will grow in chunks until it reaches the desired size.
Example: If you have a log file with 8192MB and you say that the target size is 81920MB (80GB) it will grow in chunks of 8192MB until it reaches 81920MB. 8192 -> 16384 -> 24576 ... 73728 -> 81920
.PARAMETER SqlInstance
The target SQL Server instance.
.PARAMETER Database
The database(s) to process. Options for this list are auto-populated from the server. If unspecified, all databases will be processed.
.PARAMETER TargetLogSizeMB
Specifies the target size of the transaction log file in megabytes.
.PARAMETER IncrementSizeMB
Specifies the amount the transaction log should grow in megabytes. If this value differs from the suggested value based on your TargetLogSizeMB, you will be prompted to confirm your choice.
This value will be calculated if not specified.
.PARAMETER LogFileId
Specifies the file number(s) of additional transaction log files to grow.
If this value is not specified, only the first transaction log file will be processed.
.PARAMETER ShrinkLogFile
If this switch is enabled, your transaction log files will be shrunk.
.PARAMETER ShrinkSizeMB
Specifies the target size of the transaction log file for the shrink operation.
.PARAMETER BackupDirectory
Specifies the location of your backups. Backups must be performed to shrink the transaction log.
If this value is not specified, the SQL Server instance's default backup directory will be used.
.PARAMETER SqlCredential
Login to the target instance using alternative credentials. Windows and SQL Authentication supported. Accepts credential objects (Get-Credential)
.PARAMETER ExcludeDatabase
The database(s) to exclude. Options for this list are auto-populated from the server.
.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.
.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.
.NOTES
Tags: Storage, Backup
This script uses Get-DbaDiskSpace dbatools command to get the TLog's drive free space
Author: Claudio Silva (@ClaudioESSilva)
Requires: ALTER DATABASE permission
Limitations: Freespace cannot be validated on the directory where the log file resides in SQL Server 2005.
Website: https://dbatools.io
Copyright (C) 2016 Chrissy LeMaire
Copyright: (C) Chrissy LeMaire, [email protected]
License: MIT https://opensource.org/licenses/MIT
.LINK
https://dbatools.io/Expand-DbaTLogResponsibly
.EXAMPLE
Expand-DbaTLogResponsibly -SqlInstance sqlcluster -Database db1 -TargetLogSizeMB 50000
Grows the transaction log for database db1 on sqlcluster to 50000 MB and calculates the increment size.
.EXAMPLE
Expand-DbaTLogResponsibly -SqlInstance sqlcluster -Database db1, db2 -TargetLogSizeMB 10000 -IncrementSizeMB 200
Grows the transaction logs for databases db1 and db2 on sqlcluster to 1000MB and sets the growth increment to 200MB.
.EXAMPLE
Expand-DbaTLogResponsibly -SqlInstance sqlcluster -Database db1 -TargetLogSizeMB 10000 -LogFileId 9
Grows the transaction log file with FileId 9 of the db1 database on sqlcluster instance to 10000MB.
.EXAMPLE
Expand-DbaTLogResponsibly -SqlInstance sqlcluster -Database (Get-Content D:\DBs.txt) -TargetLogSizeMB 50000
Grows the transaction log of the databases specified in the file 'D:\DBs.txt' on sqlcluster instance to 50000MB.
.EXAMPLE
Expand-DbaTLogResponsibly -SqlInstance SqlInstance -Database db1,db2 -TargetLogSizeMB 100 -IncrementSizeMB 10 -ShrinkLogFile -ShrinkSizeMB 10 -BackupDirectory R:\MSSQL\Backup
Grows the transaction logs for databases db1 and db2 on SQL server SQLInstance to 100MB, sets the incremental growth to 10MB, shrinks the transaction log to 10MB and uses the directory R:\MSSQL\Backup for the required backups.
#>
[CmdletBinding(SupportsShouldProcess, DefaultParameterSetName = 'Default')]
param (
[parameter(Position = 1, Mandatory)]
[Alias("ServerInstance", "SqlServer")]
[DbaInstanceParameter]$SqlInstance,
[parameter(Position = 3)]
[PSCredential]$SqlCredential,
[Alias("Databases")]
[object[]]$Database,
[parameter(Position = 4)]
[object[]]$ExcludeDatabase,
[parameter(Position = 5, Mandatory)]
[int]$TargetLogSizeMB,
[parameter(Position = 6)]
[int]$IncrementSizeMB = -1,
[parameter(Position = 7)]
[int]$LogFileId = -1,
[parameter(Position = 8, ParameterSetName = 'Shrink', Mandatory)]
[switch]$ShrinkLogFile,
[parameter(Position = 9, ParameterSetName = 'Shrink', Mandatory)]
[int]$ShrinkSizeMB,
[parameter(Position = 10, ParameterSetName = 'Shrink')]
[AllowEmptyString()]
[string]$BackupDirectory,
[switch][Alias('Silent')]
$EnableException
)
begin {
Write-Message -Level Verbose -Message "Set ErrorActionPreference to Inquire."
$ErrorActionPreference = 'Inquire'
#Convert MB to KB (SMO works in KB)
Write-Message -Level Verbose -Message "Convert variables MB to KB (SMO works in KB)."
[int]$TargetLogSizeKB = $TargetLogSizeMB * 1024
[int]$LogIncrementSize = $incrementSizeMB * 1024
[int]$ShrinkSize = $ShrinkSizeMB * 1024
[int]$SuggestLogIncrementSize = 0
[bool]$LogByFileID = if ($LogFileId -eq -1) {
$false
}
else {
$true
}
#Set base information
Write-Message -Level Verbose -Message "Initialize the instance '$SqlInstance'."
$server = Connect-SqlInstance -SqlInstance $SqlInstance -SqlCredential $SqlCredential
if ($ShrinkLogFile -eq $true) {
if ($BackupDirectory.length -eq 0) {
$backupdirectory = $server.Settings.BackupDirectory
}
$pathexists = Test-DbaPath -SqlInstance $server -Path $backupdirectory
if ($pathexists -eq $false) {
Stop-Function -Message "Backup directory does not exist."
}
}
}
process {
try {
[datetime]$initialTime = Get-Date
#control the iteration number
$databaseProgressbar = 0;
Write-Message -Level Verbose -Message "Resolving NetBIOS name."
$sourcenetbios = Resolve-NetBiosName $server
$databases = $server.Databases | Where-Object IsAccessible
Write-Message -Level Verbose -Message "Number of databases found: $($databases.Count)."
if ($Database) {
$databases = $databases | Where-Object Name -In $Database
}
if ($ExcludeDatabase) {
$database = $databases | Where-Object Name -NotIn $ExcludeDatabase
}
#go through all databases
Write-Message -Level Verbose -Message "Processing...foreach database..."
foreach ($db in $databases.Name) {
Write-Message -Level Verbose -Message "Working on $db."
$databaseProgressbar += 1
#set step to reutilize on logging operations
[string]$step = "$databaseProgressbar/$($Database.Count)"
if ($server.Databases[$db]) {
Write-Progress `
-Id 1 `
-Activity "Using database: $db on Instance: '$SqlInstance'" `
-PercentComplete ($databaseProgressbar / $Database.Count * 100) `
-Status "Processing - $databaseProgressbar of $($Database.Count)"
#Validate which file will grow
if ($LogByFileID) {
$logfile = $server.Databases[$db].LogFiles.ItemById($LogFileId)
}
else {
$logfile = $server.Databases[$db].LogFiles[0]
}
$numLogfiles = $server.Databases[$db].LogFiles.Count
Write-Message -Level Verbose -Message "$step - Use log file: $logfile."
$currentSize = $logfile.Size
$currentSizeMB = $currentSize / 1024
#Get the number of VLFs
$initialVLFCount = Test-DbaDbVirtualLogFile -SqlInstance $server -Database $db
Write-Message -Level Verbose -Message "$step - Log file current size: $([System.Math]::Round($($currentSize/1024.0), 2)) MB "
[long]$requiredSpace = ($TargetLogSizeKB - $currentSize)
Write-Message -Level Verbose -Message "Verifying if sufficient space exists ($([System.Math]::Round($($requiredSpace / 1024.0), 2))MB) on the volume to perform this task."
[long]$TotalTLogFreeDiskSpaceKB = 0
Write-Message -Level Verbose -Message "Get TLog drive free space"
[object]$AllDrivesFreeDiskSpace = Get-DbaDiskSpace -ComputerName $sourcenetbios | Select-Object Name, SizeInKB
#Verify path using Split-Path on $logfile.FileName in backwards. This way we will catch the LUNs. Example: "K:\Log01" as LUN name. Need to add final backslash if not there
$DrivePath = Split-Path $logfile.FileName -parent
$DrivePath = if (!($DrivePath.EndsWith("\"))) { "$DrivePath\" }
else { $DrivePath }
Do {
if ($AllDrivesFreeDiskSpace | Where-Object { $DrivePath -eq "$($_.Name)" }) {
$TotalTLogFreeDiskSpaceKB = ($AllDrivesFreeDiskSpace | Where-Object { $DrivePath -eq $_.Name }).SizeInKB
$match = $true
break
}
else {
$match = $false
$DrivePath = Split-Path $DrivePath -parent
$DrivePath = if (!($DrivePath.EndsWith("\"))) { "$DrivePath\" }
else { $DrivePath }
}
}
while (!$match -or ([string]::IsNullOrEmpty($DrivePath)))
Write-Message -Level Verbose -Message "Total TLog Free Disk Space in MB: $([System.Math]::Round($($TotalTLogFreeDiskSpaceKB / 1024.0), 2))"
if (($TotalTLogFreeDiskSpaceKB -le 0) -or ([string]::IsNullOrEmpty($TotalTLogFreeDiskSpaceKB))) {
$title = "Choose increment value for database '$db':"
$message = "Cannot validate freespace on drive where the log file resides. Do you wish to continue? (Y/N)"
$yes = New-Object System.Management.Automation.Host.ChoiceDescription "&Yes", "Will continue"
$no = New-Object System.Management.Automation.Host.ChoiceDescription "&No", "Will exit"
$options = [System.Management.Automation.Host.ChoiceDescription[]]($yes, $no)
$result = $host.ui.PromptForChoice($title, $message, $options, 0)
#no
if ($result -eq 1) {
Write-Message -Level Warning -Message "You have cancelled the execution"
return
}
}
if ($requiredSpace -gt $TotalTLogFreeDiskSpaceKB) {
Write-Message -Level Verbose -Message "There is not enough space on volume to perform this task. `r`n" `
"Available space: $([System.Math]::Round($($TotalTLogFreeDiskSpaceKB / 1024.0), 2))MB;`r`n" `
"Required space: $([System.Math]::Round($($requiredSpace / 1024.0), 2))MB;"
return
}
else {
if ($currentSize -ige $TargetLogSizeKB -and ($ShrinkLogFile -eq $false)) {
Write-Message -Level Verbose -Message "$step - [INFO] The T-Log file '$logfile' size is already equal or greater than target size - No action required."
}
else {
Write-Message -Level Verbose -Message "$step - [OK] There is sufficient free space to perform this task."
# If SQL Server version is greater or equal to 2012
if ($server.Version.Major -ge "11") {
switch ($TargetLogSizeMB) {
{ $_ -le 64 } { $SuggestLogIncrementSize = 64 }
{ $_ -ge 64 -and $_ -lt 256 } { $SuggestLogIncrementSize = 256 }
{ $_ -ge 256 -and $_ -lt 1024 } { $SuggestLogIncrementSize = 512 }
{ $_ -ge 1024 -and $_ -lt 4096 } { $SuggestLogIncrementSize = 1024 }
{ $_ -ge 4096 -and $_ -lt 8192 } { $SuggestLogIncrementSize = 2048 }
{ $_ -ge 8192 -and $_ -lt 16384 } { $SuggestLogIncrementSize = 4096 }
{ $_ -ge 16384 } { $SuggestLogIncrementSize = 8192 }
}
}
# 2008 R2 or under
else {
switch ($TargetLogSizeMB) {
{ $_ -le 64 } { $SuggestLogIncrementSize = 64 }
{ $_ -ge 64 -and $_ -lt 256 } { $SuggestLogIncrementSize = 256 }
{ $_ -ge 256 -and $_ -lt 1024 } { $SuggestLogIncrementSize = 512 }
{ $_ -ge 1024 -and $_ -lt 4096 } { $SuggestLogIncrementSize = 1024 }
{ $_ -ge 4096 -and $_ -lt 8192 } { $SuggestLogIncrementSize = 2048 }
{ $_ -ge 8192 -and $_ -lt 16384 } { $SuggestLogIncrementSize = 4000 }
{ $_ -ge 16384 } { $SuggestLogIncrementSize = 8000 }
}
if (($IncrementSizeMB % 4096) -eq 0) {
Write-Message -Level Verbose -Message "Your instance version is below SQL 2012, remember the known BUG mentioned on HELP. `r`nUse Get-Help Expand-DbaTLogFileResponsibly to read help`r`nUse a different value for incremental size.`r`n"
return
}
}
Write-Message -Level Verbose -Message "Instance $server version: $($server.Version.Major) - Suggested TLog increment size: $($SuggestLogIncrementSize)MB"
# Shrink Log File to desired size before re-growth to desired size (You need to remove as many VLF's as possible to ensure proper growth)
$ShrinkSizeMB = $ShrinkSize / 1024
if ($ShrinkLogFile -eq $true) {
if ($server.Databases[$db].RecoveryModel -eq [Microsoft.SqlServer.Management.Smo.RecoveryModel]::Simple) {
Write-Message -Level Warning -Message "Database '$db' is in Simple RecoveryModel which does not allow log backups. Do not specify -ShrinkLogFile and -ShrinkSizeMB parameters."
Continue
}
try {
$sql = "SELECT last_log_backup_lsn FROM sys.database_recovery_status WHERE database_id = DB_ID('$db')"
$sqlResult = $server.ConnectionContext.ExecuteWithResults($sql);
if ($sqlResult.Tables[0].Rows[0]["last_log_backup_lsn"] -is [System.DBNull]) {
Write-Message -Level Warning -Message "First, you need to make a full backup before you can do Tlog backup on database '$db' (last_log_backup_lsn is null)."
Continue
}
}
catch {
Stop-Function -Message "Can't execute SQL on $server. `r`n $($_)" -Continue
}
If ($Pscmdlet.ShouldProcess($($server.name), "Backing up TLog for $db")) {
Write-Message -Level Verbose -Message "We are about to backup the Tlog for database '$db' to '$backupdirectory' and shrink the log."
Write-Message -Level Verbose -Message "Starting Size = $currentSizeMB."
$DefaultCompression = $server.Configuration.DefaultBackupCompression.ConfigValue
if ($currentSizeMB -gt $ShrinkSizeMB) {
$backupRetries = 1
Do {
try {
$percent = $null
$backup = New-Object Microsoft.SqlServer.Management.Smo.Backup
$backup.Action = [Microsoft.SqlServer.Management.Smo.BackupActionType]::Log
$backup.BackupSetDescription = "Transaction Log backup of " + $db
$backup.BackupSetName = $db + " Backup"
$backup.Database = $db
$backup.MediaDescription = "Disk"
$dt = get-date -format yyyyMMddHHmmssms
$null = $backup.Devices.AddDevice($backupdirectory + "\" + $db + "_db_" + $dt + ".trn", 'File')
if ($DefaultCompression = $true) {
$backup.CompressionOption = 1
}
else {
$backup.CompressionOption = 0
}
$null = [Microsoft.SqlServer.Management.Smo.PercentCompleteEventHandler] {
Write-Progress -id 2 -ParentId 1 -activity "Backing up $db to $server" -percentcomplete $_.Percent -status ([System.String]::Format("Progress: {0} %", $_.Percent))
}
$backup.add_PercentComplete($percent)
$backup.PercentCompleteNotification = 10
$backup.add_Complete($complete)
Write-Progress -id 2 -ParentId 1 -activity "Backing up $db to $server" -percentcomplete 0 -Status ([System.String]::Format("Progress: {0} %", 0))
$backup.SqlBackup($server)
Write-Progress -id 2 -ParentId 1 -activity "Backing up $db to $server" -status "Complete" -Completed
$logfile.Shrink($ShrinkSizeMB, [Microsoft.SqlServer.Management.SMO.ShrinkMethod]::TruncateOnly)
$logfile.Refresh()
}
catch {
Write-Progress -id 1 -activity "Backup" -status "Failed" -completed
Stop-Function -Message "Backup failed for database" -ErrorRecord $_ -Target $db -Continue
Continue
}
}
while (($logfile.Size / 1024) -gt $ShrinkSizeMB -and ++$backupRetries -lt 6)
$currentSize = $logfile.Size
Write-Message -Level Verbose -Message "TLog backup and truncate for database '$db' finished. Current TLog size after $backupRetries backups is $($currentSize/1024)MB"
}
}
}
# SMO uses values in KB
$SuggestLogIncrementSize = $SuggestLogIncrementSize * 1024
# If default, use $SuggestedLogIncrementSize
if ($IncrementSizeMB -eq -1) {
$LogIncrementSize = $SuggestLogIncrementSize
}
else {
$title = "Choose increment value for database '$db':"
$message = "The input value for increment size was $([System.Math]::Round($LogIncrementSize/1024, 0))MB. However the suggested value for increment is $($SuggestLogIncrementSize/1024)MB.`r`nDo you want to use the suggested value of $([System.Math]::Round($SuggestLogIncrementSize/1024, 0))MB insted of $([System.Math]::Round($LogIncrementSize/1024, 0))MB"
$yes = New-Object System.Management.Automation.Host.ChoiceDescription "&Yes", "Uses recomended size."
$no = New-Object System.Management.Automation.Host.ChoiceDescription "&No", "Will use parameter value."
$options = [System.Management.Automation.Host.ChoiceDescription[]]($yes, $no)
$result = $host.ui.PromptForChoice($title, $message, $options, 0)
#yes
if ($result -eq 0) {
$LogIncrementSize = $SuggestLogIncrementSize
}
}
#start growing file
If ($Pscmdlet.ShouldProcess($($server.name), "Starting log growth. Increment chunk size: $($LogIncrementSize/1024)MB for database '$db'")) {
Write-Message -Level Verbose -Message "Starting log growth. Increment chunk size: $($LogIncrementSize/1024)MB for database '$db'"
Write-Message -Level Verbose -Message "$step - While current size less than target log size."
while ($currentSize -lt $TargetLogSizeKB) {
Write-Progress `
-Id 2 `
-ParentId 1 `
-Activity "Growing file $logfile on '$db' database" `
-PercentComplete ($currentSize / $TargetLogSizeKB * 100) `
-Status "Remaining - $([System.Math]::Round($($($TargetLogSizeKB - $currentSize) / 1024.0), 2)) MB"
Write-Message -Level Verbose -Message "$step - Verifying if the log can grow or if it's already at the desired size."
if (($TargetLogSizeKB - $currentSize) -lt $LogIncrementSize) {
Write-Message -Level Verbose -Message "$step - Log size is lower than the increment size. Setting current size equals $TargetLogSizeKB."
$currentSize = $TargetLogSizeKB
}
else {
Write-Message -Level Verbose -Message "$step - Grow the $logfile file in $([System.Math]::Round($($LogIncrementSize / 1024.0), 2)) MB"
$currentSize += $LogIncrementSize
}
#When -WhatIf Switch, do not run
if ($PSCmdlet.ShouldProcess("$step - File will grow to $([System.Math]::Round($($currentSize/1024.0), 2)) MB", "This action will grow the file $logfile on database $db to $([System.Math]::Round($($currentSize/1024.0), 2)) MB .`r`nDo you wish to continue?", "Perform grow")) {
Write-Message -Level Verbose -Message "$step - Set size $logfile to $([System.Math]::Round($($currentSize/1024.0), 2)) MB"
$logfile.size = $currentSize
Write-Message -Level Verbose -Message "$step - Applying changes"
$logfile.Alter()
Write-Message -Level Verbose -Message "$step - Changes have been applied"
#Will put the info like VolumeFreeSpace up to date
$logfile.Refresh()
}
}
Write-Message -Level Verbose -Message "`r`n$step - [OK] Growth process for logfile '$logfile' on database '$db', has been finished."
Write-Message -Level Verbose -Message "$step - Grow $logfile log file on $db database finished."
}
}
} #else space available
}
#else verifying existence
else {
Write-Message -Level Verbose -Message "Database '$db' does not exist on instance '$SqlInstance'."
}
#Get the number of VLFs
$currentVLFCount = Test-DbaDbVirtualLogFile -SqlInstance $server -Database $db
[pscustomobject]@{
ComputerName = $server.ComputerName
InstanceName = $server.ServiceName
SqlInstance = $server.DomainInstanceName
Database = $db
ID = $logfile.ID
Name = $logfile.Name
LogFileCount = $numLogfiles
InitialSize = [dbasize]($currentSizeMB * 1024)
CurrentSize = [dbasize]($TargetLogSizeMB * 1024)
InitialVLFCount = $initialVLFCount.Total
CurrentVLFCount = $currentVLFCount.Total
} | Select-DefaultView -ExcludeProperty LogFileCount
} #foreach database
}
catch {
Stop-Function -Message "Logfile $logfile on database $db not processed. Error: $($_.Exception.Message). Line Number: $($_InvocationInfo.ScriptLineNumber)" -Continue
}
}
end {
Write-Message -Level Verbose -Message "Process finished $((Get-Date) - ($initialTime))"
Test-DbaDeprecation -DeprecatedOn "1.0.0" -Alias Expand-SqlTLogResponsibly
}
}
#ValidationTags#Messaging#
function Export-DbaAvailabilityGroup {
<#
.SYNOPSIS
Exports SQL Server Availability Groups to a T-SQL file.
.DESCRIPTION
Exports SQL Server Availability Groups creation scripts to a T-SQL file. This is a function that is not available in SSMS.
.PARAMETER SqlInstance
The SQL Server instance name. SQL Server 2012 and above supported.
.PARAMETER Path
The directory name where the output files will be written. A sub directory with the format 'ServerName$InstanceName' will be created. A T-SQL scripts named 'AGName.sql' will be created under this subdirectory for each scripted Availability Group.
.PARAMETER AvailabilityGroup
The Availability Group(s) to export - this list is auto-populated from the server. If unspecified, all logins will be processed.
.PARAMETER ExcludeAvailabilityGroup
The Availability Group(s) to exclude - this list is auto-populated from the server.
.PARAMETER NoClobber
Do not overwrite existing export files.
.PARAMETER SqlCredential
Login to the target instance using alternative credentials. Windows and SQL Authentication supported. Accepts credential objects (Get-Credential)
.PARAMETER WhatIf
Shows you what it'd output if you were to run the command
.PARAMETER Confirm
Confirms each step/line of output
.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.
.NOTES
Tags: Hadr, AG, AvailabilityGroup
Author: Chris Sommer (@cjsommer), cjsommer.com
dbatools PowerShell module (https://dbatools.io)
Copyright (C) 2016 Chrissy LeMaire
License: MIT https://opensource.org/licenses/MIT
.LINK
https://dbatools.io/Export-DbaAvailabilityGroup
.EXAMPLE
Export-DbaAvailabilityGroup -SqlInstance sql2012
Exports all Availability Groups from SQL server "sql2012". Output scripts are written to the Documents\SqlAgExports directory by default.
.EXAMPLE
Export-DbaAvailabilityGroup -SqlInstance sql2012 -Path C:\temp\availability_group_exports
Exports all Availability Groups from SQL server "sql2012". Output scripts are written to the C:\temp\availability_group_exports directory.
.EXAMPLE
Export-DbaAvailabilityGroup -SqlInstance sql2012 -Path 'C:\dir with spaces\availability_group_exports' -AvailabilityGroups AG1,AG2
Exports Availability Groups AG1 and AG2 from SQL server "sql2012". Output scripts are written to the C:\dir with spaces\availability_group_exports directory.
.EXAMPLE
Export-DbaAvailabilityGroup -SqlInstance sql2014 -Path C:\temp\availability_group_exports -NoClobber
Exports all Availability Groups from SQL server "sql2014". Output scripts are written to the C:\temp\availability_group_exports directory. If the export file already exists it will not be overwritten.
#>
[CmdletBinding(SupportsShouldProcess = $true)]
param (
[parameter(Mandatory, ValueFromPipeline)]
[Alias("ServerInstance", "SqlServer")]
[DbaInstanceParameter[]]$SqlInstance,
[PSCredential]$SqlCredential,
[object[]]$AvailabilityGroup,
[object[]]$ExcludeAvailabilityGroup,
[Alias("OutputLocation", "FilePath")]
[string]$Path = "$([Environment]::GetFolderPath("MyDocuments"))\SqlAgExport",
[switch]$NoClobber,
[Alias('Silent')]
[switch]$EnableException
)
process {
foreach ($instance in $SqlInstance) {
Write-Message -Level Verbose -Message "Connecting to $instance"
try {
$server = Connect-SqlInstance -SqlInstance $instance -SqlCredential $SqlCredential
}
catch {
Stop-Function -Message "Failure" -Category ConnectionError -ErrorRecord $_ -Target $instance -Continue
}
if ($server.IsHadrEnabled -eq $false) {
Stop-Function -Message "Hadr is not enabled on this instance" -Continue
}
else {
# Get all of the Availability Groups and filter if required
$ags = $server.AvailabilityGroups
}
if (Test-Bound 'AvailabilityGroup') {
$ags = $ags | Where-Object Name -In $AvailabilityGroup
}
if (Test-Bound 'ExcludeAvailabilityGroup') {
$ags = $ags | Where-Object Name -NotIn $ExcludeAvailabilityGroup
}
if ($ags) {
# Set and create the OutputLocation if it doesn't exist
$sqlinst = $instance.ToString().Replace('\', '$')
$outputLocation = "$Path\$sqlinst"
if (!(Test-Path $outputLocation -PathType Container)) {
$null = New-Item -Path $outputLocation -ItemType Directory -Force
}
# Script each Availability Group
foreach ($ag in $ags) {
$agName = $ag.Name
# Set the outfile name
if ($AppendDateToOutputFilename.IsPresent) {
$formatteddate = (Get-Date -Format 'yyyyMMdd_hhmm')
$outFile = "$outputLocation\${AGname}_${formatteddate}.sql"
}
else {
$outFile = "$outputLocation\$agName.sql"
}
# Check NoClobber and script out the AG
if ($NoClobber.IsPresent -and (Test-Path -Path $outFile -PathType Leaf)) {
Write-Message -Level Warning -Message "OutputFile $outFile already exists. Skipping due to -NoClobber parameter"
}
else {
Write-Message -Level Verbose -Message "Scripting Availability Group [$agName] on $instance to $outFile"
# Create comment block header for AG script
"/*" | Out-File -FilePath $outFile -Encoding ASCII -Force
" * Created by dbatools 'Export-DbaAvailabilityGroup' cmdlet on '$(Get-Date)'" | Out-File -FilePath $outFile -Encoding ASCII -Append
" * See https://dbatools.io/Export-DbaAvailabilityGroup for more help" | Out-File -FilePath $outFile -Encoding ASCII -Append
# Output AG and listener names
" *" | Out-File -FilePath $outFile -Encoding ASCII -Append
" * Availability Group Name: $($ag.name)" | Out-File -FilePath $outFile -Encoding ASCII -Append
$ag.AvailabilityGroupListeners | ForEach-Object { " * Listener Name: $($_.name)" } | Out-File -FilePath $outFile -Encoding ASCII -Append
# Output all replicas
" *" | Out-File -FilePath $outFile -Encoding ASCII -Append
$ag.AvailabilityReplicas | ForEach-Object { " * Replica: $($_.name)" } | Out-File -FilePath $outFile -Encoding ASCII -Append
# Output all databases
" *" | Out-File -FilePath $outFile -Encoding ASCII -Append
$ag.AvailabilityDatabases | ForEach-Object { " * Database: $($_.name)" } | Out-File -FilePath $outFile -Encoding ASCII -Append
# $ag | Select-Object -Property * | Out-File -FilePath $outFile -Encoding ASCII -Append
"*/" | Out-File -FilePath $outFile -Encoding ASCII -Append
# Script the AG
try {
$ag.Script() | Out-File -FilePath $outFile -Encoding ASCII -Append
Get-ChildItem $outFile
}
catch {
Stop-Function -ErrorRecord $_ -Message "Error scripting out the availability groups. This is likely due to a bug in SMO." -Continue
}
}
}
}
else {
Write-Message -Level Output -Message "No Availability Groups detected on $instance"
}
}
}
}
#ValidationTags#Messaging,FlowControl,Pipeline,CodeStyle#
function Export-DbaCmsRegServer {
<#
.SYNOPSIS
Exports registered servers and registered server groups to file
.DESCRIPTION
Exports registered servers and registered server groups to file
.PARAMETER SqlInstance
SQL Server name or SMO object representing the SQL Server to connect to.
.PARAMETER SqlCredential
Login to the target instance using alternative credentials. Windows and SQL Authentication supported. Accepts credential objects (Get-Credential)
.PARAMETER Group
Exports a specific group.
.PARAMETER CredentialPersistenceType
Used to specify how the login and passwords are persisted. Valid values include None, PersistLoginName and PersistLoginNameAndPassword.
.PARAMETER Path
The path to the exported file. If no path is specified, one will be created.
.PARAMETER InputObject
Enables piping from Get-DbaCmsRegServer, Get-DbaCmsRegServerGroup, CSVs and other objects.
If importing from CSV or other object, a column named ServerName is required. Optional columns include Name, Description and Group.
.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.
.NOTES
Author: Chrissy LeMaire (@cl), netnerds.net
Tags: RegisteredServer, CMS
Website: https://dbatools.io
Copyright: (C) Chrissy LeMaire, [email protected]
License: MIT https://opensource.org/licenses/MIT
.LINK
https://dbatools.io/Export-DbaCmsRegServer
.EXAMPLE
Export-DbaCmsRegServer -SqlInstance sql2008
Exports all Registered Server and Registered Server Groups on sql2008 to an automatically generated file name in the current directory
.EXAMPLE
Export-DbaCmsRegServer -SqlInstance sql2008 -Group hr\Seattle -Path C:\temp\Seattle.xml
Exports all Registered Server and Registered Server Groups with the Seattle group within the HR group on sql2008 to C:\temp\Seattle.xml
.EXAMPLE
Get-DbaCmsRegServer -SqlInstance sql2008, sql2012 | Export-DbaCmsRegServer
Exports all registered servers on sql2008 and sql2012. Warning - each one will have its own individual file. Consider piping groups.
.EXAMPLE
Get-DbaCmsRegServerGroup -SqlInstance sql2008, sql2012 | Export-DbaCmsRegServer
Exports all registered servers on sql2008 and sql2012, organized by group.
#>
[CmdletBinding()]
param (
[Alias("ServerInstance", "SqlServer")]
[DbaInstanceParameter[]]$SqlInstance,
[PSCredential]$SqlCredential,
[parameter(ValueFromPipeline)]
[object[]]$InputObject,
[string]$Path,
[ValidateSet("None", "PersistLoginName", "PersistLoginNameAndPassword")]
[string]$CredentialPersistenceType = "None",
[switch]$EnableException
)
begin {
if ((Test-Bound -ParameterName Path)) {
if ($Path -notmatch '\\') {
$Path = ".\$Path"
}
$directory = Split-Path $Path
if (-not (Test-Path $directory)) {
New-Item -Path $directory -ItemType Directory
}
}
else {
$timeNow = (Get-Date -uformat "%m%d%Y%H%M%S")
}
}
process {
foreach ($instance in $SqlInstance) {
$InputObject += Get-DbaCmsRegServerGroup -SqlInstance $instance -SqlCredential $SqlCredential -Id 1
}
foreach ($object in $InputObject) {
try {
if ($object -is [Microsoft.SqlServer.Management.RegisteredServers.RegisteredServersStore]) {
$object = Get-DbaCmsRegServerGroup -SqlInstance $object.ServerConnection.SqlConnectionObject -Id 1
}
if ($object -is [Microsoft.SqlServer.Management.RegisteredServers.RegisteredServer]) {
if ((Test-Bound -ParameterName Path -Not)) {
$servername = $object.SqlInstance.Replace('\', '$')
$regservername = $object.Name.Replace('\', '$')
$Path = "$serverName-regserver-$regservername-$timeNow.xml"
}
$object.Export($Path, $CredentialPersistenceType)
}
elseif ($object -is [Microsoft.SqlServer.Management.RegisteredServers.ServerGroup]) {
if ((Test-Bound -ParameterName Path -Not)) {
$servername = $object.SqlInstance.Replace('\', '$')
$regservergroup = $object.Name.Replace('\', '$')
$Path = "$serverName-reggroup-$regservergroup-$timeNow.xml"
}
$object.Export($Path, $CredentialPersistenceType)
}
else {
Stop-Function -Message "InputObject is not a registered server or server group" -Continue
}
Get-ChildItem $Path -ErrorAction Stop
}
catch {
Stop-Function -Message "Failure" -ErrorRecord $_
}
}
}
end {
Test-DbaDeprecation -DeprecatedOn "1.0.0" -Alias Export-DbaRegisteredServer
}
}
function Export-DbaCredential {
<#
.SYNOPSIS
Exports credentials INCLUDING PASSWORDS, unless specified otherwise, to sql file.
.DESCRIPTION
Exports credentials INCLUDING PASSWORDS, unless specified otherwise, to sql file.
Requires remote Windows access if exporting the password.
.PARAMETER SqlInstance
Source SQL Server. You must have sysadmin access and server version must be SQL Server version 2005 or higher.
.PARAMETER SqlCredential
Login to the target instance using alternative credentials. Windows and SQL Authentication supported. Accepts credential objects (Get-Credential)
.PARAMETER Credential
Login to the target OS using alternative credentials. Accepts credential objects (Get-Credential)
.PARAMETER Path
The path to the exported sql file.
.PARAMETER Identity
The credentials to export. If unspecified, all credentials will be exported.
.PARAMETER InputObject
Allow credentials to be piped in from Get-DbaCredential
.PARAMETER ExcludePassword
Exports the SQL credential without any sensitive information.
.PARAMETER InputObject
Allow credentials to be piped in from Get-DbaCredential
.PARAMETER Append
Append to Path
.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.
.NOTES
Tags: Credential
Author: Chrissy LeMaire (@cl), netnerds.net
Website: https://dbatools.io
Copyright: (C) Chrissy LeMaire, [email protected]
License: MIT https://opensource.org/licenses/MIT
.EXAMPLE
Export-DbaCredential -SqlInstance sql2017 -Path C:\temp\cred.sql
Exports credentials, including passwords, from sql2017 to the file C:\temp\cred.sql
#>
[CmdletBinding()]
param (
[Parameter(Mandatory, ValueFromPipeline)]
[Alias("ServerInstance", "SqlServer")]
[DbaInstanceParameter[]]$SqlInstance,
[string[]]$Identity,
[PSCredential]$SqlCredential,
[PSCredential]$Credential,
[string]$Path,
[switch]$ExcludePassword,
[switch]$Append,
[Microsoft.SqlServer.Management.Smo.Credential[]]$InputObject,
[switch]$EnableException
)
process {
foreach ($instance in $SqlInstance) {
try {
Write-Message -Level Verbose -Message "Connecting to $instance."
$server = Connect-SqlInstance -SqlInstance $instance -SqlCredential $sqlcredential -MinimumVersion 9
$InputObject += $server.Credentials
}
catch {
Stop-Function -Message "Failure" -Category ConnectionError -ErrorRecord $_ -Target $instance -Continue
}
if ($Identity) {
$InputObject = $InputObject | Where-Object Identity -in $Identity
}
if (!(Test-SqlSa -SqlInstance $instance -SqlCredential $sqlcredential)) {
Stop-Function -Message "Not a sysadmin on $instance. Quitting." -Target $instance -Continue
}
Write-Message -Level Verbose -Message "Getting NetBios name for $instance."
$sourceNetBios = Resolve-NetBiosName $server
Write-Message -Level Verbose -Message "Checking if Remote Registry is enabled on $instance."
try {
Invoke-Command2 -Raw -Credential $Credential -ComputerName $sourceNetBios -ScriptBlock { Get-ItemProperty -Path "HKLM:\SOFTWARE\" } -ErrorAction Stop
}
catch {
Stop-Function -Message "Can't connect to registry on $instance." -Target $sourceNetBios -ErrorRecord $_
return
}
if (-not (Test-Bound -ParameterName Path)) {
$timenow = (Get-Date -uformat "%m%d%Y%H%M%S")
$mydocs = [Environment]::GetFolderPath('MyDocuments')
$path = "$mydocs\$($server.name.replace('\', '$'))-$timenow-credential.sql"
}
$sql = @()
if ($ExcludePassword) {
Stop-Function -Message "So sorry, there's no other way around it for now. The password has to be exported in plain text."
return
}
else {
try {
$creds = Get-DecryptedObject -SqlInstance $server -Type Credential
}
catch {
Stop-Function -Continue -Message "Failure" -ErrorRecord $_
}
foreach ($currentCred in $creds) {
$name = $currentCred.Name.Replace("'", "''")
$identity = $currentCred.Identity.Replace("'", "''")
$password = $currentCred.Password.Replace("'", "''")
$sql += "CREATE CREDENTIAL $name WITH IDENTITY = N'$identity', SECRET = N'$password'"
}
}
try {
if ($Append) {
Add-Content -Path $path -Value $sql
}
else {
Set-Content -Path $path -Value $sql
}
Get-ChildItem -Path $path
}
catch {
Stop-Function -Message "Can't write to $path" -ErrorRecord $_ -Continue
}
Write-Message -Level Verbose -Message "Attempting to migrate $credentialName"
Get-ChildItem -Path $path
}
}
}
function Export-DbaDacPackage {
<#
.SYNOPSIS
Exports a dacpac from a server.
.DESCRIPTION
Using SQLPackage, export a dacpac from an instance of SQL Server.
Note - Extract from SQL Server is notoriously flaky - for example if you have three part references to external databases it will not work.
For help with the extract action parameters and properties, refer to https://msdn.microsoft.com/en-us/library/hh550080(v=vs.103).aspx
.PARAMETER SqlInstance
SQL Server name or SMO object representing the SQL Server to connect to and publish to.
.PARAMETER SqlCredential
Allows you to login to servers using alternative logins instead Integrated, accepts Credential object created by Get-Credential
.PARAMETER Path
The directory where the .dacpac files will be exported to. Defaults to documents.
.PARAMETER Database
The database(s) to process - this list is auto-populated from the server. If unspecified, all databases will be processed.
.PARAMETER ExcludeDatabase
The database(s) to exclude - this list is auto-populated from the server
.PARAMETER AllUserDatabases
Run command against all user databases
.PARAMETER ExtendedParameters
Optional parameters used to extract the DACPAC. More information can be found at
https://msdn.microsoft.com/en-us/library/hh550080.aspx
.PARAMETER ExtendedProperties
Optional properties used to extract the DACPAC. More information can be found at
https://msdn.microsoft.com/en-us/library/hh550080.aspx
.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.
.NOTES
Tags: Migration, Database, Dacpac
Author: Richie lee (@bzzzt_io)
Website: https://dbatools.io
Copyright: (C) Chrissy LeMaire, [email protected]
License: MIT https://opensource.org/licenses/MIT
.LINK
https://dbatools.io/Export-DbaDacPackage
.EXAMPLE
Export-DbaDacPackage -SqlInstance sql2016 -Database SharePoint_Config
Exports the dacpac for SharePoint_Config on sql2016 to $home\Documents\SharePoint_Config.dacpac
.EXAMPLE
$moreprops = "/p:VerifyExtraction=$true /p:CommandTimeOut=10"
Export-DbaDacPackage -SqlInstance sql2016 -Database SharePoint_Config -Path C:\temp -ExtendedProperties $moreprops
Sets the CommandTimeout to 10 then extracts the dacpac for SharePoint_Config on sql2016 to C:\temp\SharePoint_Config.dacpac then verifies extraction.
#>
[CmdletBinding()]
param
(
[parameter(Mandatory, ValueFromPipeline)]
[Alias("ServerInstance", "SqlServer")]
[DbaInstance[]]$SqlInstance,
[Alias("Credential")]
[PSCredential]$SqlCredential,
[object[]]$Database,
[object[]]$ExcludeDatabase,
[switch]$AllUserDatabases,
[string]$Path = "$home\Documents",
[string]$ExtendedParameters,
[string]$ExtendedProperties,
[switch]$EnableException
)
process {
if ((Test-Bound -Not -ParameterName Database) -and (Test-Bound -Not -ParameterName ExcludeDatabase) -and (Test-Bound -Not -ParameterName AllUserDatabases)) {
Stop-Function -Message "You must specify databases to execute against using either -Database, -ExcludeDatabase or -AllUserDatabases"
}
if (-not (Test-Path $Path)) {
Stop-Function -Message "$Path doesn't exist or access denied"
}
# Convert $Path to absolute path because relative paths are problematic when you mix PowerShell commands
# and System.IO.
# This must happen after Test-Path because Resolve-Path will throw an exception if a path does not exist.
$Path = (Resolve-Path $Path).Path
if ((Get-Item $path) -isnot [System.IO.DirectoryInfo]) {
Stop-Function -Message "Path must be a directory"
}
foreach ($instance in $sqlinstance) {
try {
Write-Message -Level Verbose -Message "Connecting to $instance."
$server = Connect-SqlInstance -SqlInstance $instance -SqlCredential $sqlcredential
}
catch {
Stop-Function -Message "Failure" -Category ConnectionError -ErrorRecord $_ -Target $instance -Continue
}
$cleaninstance = $instance.ToString().Replace('\', '-')
$dbs = $server.Databases | Where-Object { $_.IsSystemObject -eq $false -and $_.IsAccessible }
if ($Database) {
$dbs = $dbs | Where-Object Name -in $Database
if (-not $dbs.name) {
Stop-Function -Message "Database $Database does not exist on $instance" -Target $instance -Continue
}
}
if ($ExcludeDatabase) {
$dbs = $dbs | Where-Object Name -notin $ExcludeDatabase
}
foreach ($db in $dbs) {
$dbname = $db.name
$connstring = $server.ConnectionContext.ConnectionString.Replace('"', "'")
if ($connstring -notmatch 'Database=') {
$connstring = "$connstring;Database=$dbname"
}
$filename = "$Path\$cleaninstance-$dbname.dacpac"
Write-Message -Level Verbose -Message "Exporting $filename"
Write-Message -Level Verbose -Message "Using connection string $connstring"
$sqlPackageArgs = "/action:Extract /tf:""$filename"" /SourceConnectionString:""$connstring"" $ExtendedParameters $ExtendedProperties"
$resultstime = [diagnostics.stopwatch]::StartNew()
try {
$startprocess = New-Object System.Diagnostics.ProcessStartInfo
$startprocess.FileName = "$script:PSModuleRoot\bin\smo\sqlpackage.exe"
$startprocess.Arguments = $sqlPackageArgs
$startprocess.RedirectStandardError = $true
$startprocess.RedirectStandardOutput = $true
$startprocess.UseShellExecute = $false
$startprocess.CreateNoWindow = $true
$process = New-Object System.Diagnostics.Process
$process.StartInfo = $startprocess
$process.Start() | Out-Null
$stdout = $process.StandardOutput.ReadToEnd()
$stderr = $process.StandardError.ReadToEnd()
$process.WaitForExit()
Write-Message -level Verbose -Message "StandardOutput: $stdout"
[pscustomobject]@{
ComputerName = $server.ComputerName
InstanceName = $server.ServiceName
SqlInstance = $server.DomainInstanceName
Database = $dbname
Path = $filename
Elapsed = [prettytimespan]($resultstime.Elapsed)
} | Select-DefaultView -ExcludeProperty ComputerName, InstanceName
}
catch {
Stop-Function -Message "SQLPackage Failure" -ErrorRecord $_ -Continue
}
if ($process.ExitCode -ne 0) {
Stop-Function -Message "Standard output - $stderr" -Continue
}
}
}
}
end {
Test-DbaDeprecation -DeprecatedOn "1.0.0" -EnableException:$false -Alias Export-DbaDacpac
}
}
function Export-DbaDiagnosticQuery {
<#
.SYNOPSIS
Export-DbaDiagnosticQuery can convert ouput generated by Invoke-DbaDiagnosticQuery to CSV or Excel
.DESCRIPTION
The default output format of Invoke-DbaDiagnosticQuery is a custom object. It can also output to CSV and Excel.
However, CSV output can generate a lot of files and Excel output depends on the ImportExcel module by Doug Fike (https://github.com/dfinke/ImportExcel)
Export-DbaDiagnosticQuery can be used to convert from the default export type to the other available export types.
.PARAMETER InputObject
Specifies the objects to convert
.PARAMETER ConvertTo
Specifies the output type. Valid choices are Excel and CSV. CSV is the default.
.PARAMETER Path
Specifies the path to the output files.
.PARAMETER Suffix
Suffix for the filename. It's datetime by default.
.PARAMETER NoPlanExport
Use this switch to suppress exporting of .sqlplan files
.PARAMETER NoQueryExport
Use this switch to suppress exporting of .sql files
.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.
.NOTES
Tags: Query
Author: Andre Kamman (@AndreKamman), http://clouddba.io
Website: https://dbatools.io
Copyright: (C) Chrissy LeMaire, [email protected]
License: MIT https://opensource.org/licenses/MIT
.LINK
https://dbatools.io/Export-DbaDiagnosticQuery
.EXAMPLE
Invoke-DbaDiagnosticQuery -SqlInstance sql2016 | Export-DbaDiagnosticQuery -Path c:\temp
Converts output from Invoke-DbaDiagnosticQuery to multiple CSV files
.EXAMPLE
$output = Invoke-DbaDiagnosticQuery -SqlInstance sql2016
Export-DbaDiagnosticQuery -InputObject $output -ConvertTo Excel
Converts output from Invoke-DbaDiagnosticQuery to Excel worksheet(s) in the Documents folder
#>
[CmdletBinding()]
param (
[parameter(Mandatory, ValueFromPipeline)]
[object[]]$InputObject,
[ValidateSet("Excel", "Csv")]
[string]$ConvertTo = "Csv",
[System.IO.FileInfo]$Path = [Environment]::GetFolderPath("mydocuments"),
[string]$Suffix = "$(Get-Date -format 'yyyyMMddHHmmssms')",
[switch]$NoPlanExport,
[switch]$NoQueryExport,
[Alias('Silent')]
[switch]$EnableException
)
begin {
if ($ConvertTo -eq "Excel") {
try {
Import-Module ImportExcel -ErrorAction Stop
}
catch {
$message = "Failed to load module, exporting to Excel feature is not available
Install the module from: https://github.com/dfinke/ImportExcel
Valid alternative conversion format is csv"
Stop-Function -Message $message
return
}
}
if (!$(Test-Path $Path)) {
try {
New-Item $Path -ItemType Directory -ErrorAction Stop | Out-Null
Write-Message -Level Output -Message "Created directory $Path"
}
catch {
Stop-Function -Message "Failed to create directory $Path" -Continue
}
}
Function Remove-InvalidFileNameChars {
[CmdletBinding()]
param (
[Parameter(Mandatory,
Position = 0,
ValueFromPipeline,
ValueFromPipelineByPropertyName = $true)]
[String]$Name
)
$Name = $Name.Replace(" ", "-")
$invalidChars = [IO.Path]::GetInvalidFileNameChars() -join ''
$re = "[{0}]" -f [RegEx]::Escape($invalidChars)
return ($Name -replace $re)
}
}
process {
if (Test-FunctionInterrupt) { return }
foreach ($row in $InputObject) {
$result = $row.Result
$name = $row.Name
$SqlInstance = $row.SqlInstance.Replace("\", "$")
$dbname = $row.Database
$number = $row.Number
if ($null -eq $result) {
Stop-Function -Message "Result was empty for $name" -Target $result -Continue
}
$queryname = Remove-InvalidFileNameChars -Name $Name
$excelfilename = "$Path\$SqlInstance-DQ-$Suffix.xlsx"
$exceldbfilename = "$Path\$SqlInstance-DQ-$dbname-$Suffix.xlsx"
$csvdbfilename = "$Path\$SqlInstance-$dbname-DQ-$number-$queryname-$Suffix.csv"
$csvfilename = "$Path\$SqlInstance-DQ-$number-$queryname-$Suffix.csv"
$columnnameoptions = "Query Plan", "QueryPlan", "Query_Plan", "query_plan_xml"
if (($result | Get-Member | Where-Object Name -in $columnnameoptions).Count -gt 0) {
$plannr = 0
$columnname = ($result | Get-Member | Where-Object Name -In $columnnameoptions).Name
foreach ($plan in $result."$columnname") {
$plannr += 1
if ($row.DatabaseSpecific) {
$planfilename = "$Path\$SqlInstance-$dbname-DQ-$number-$queryname-$plannr-$Suffix.sqlplan"
}
else {
$planfilename = "$Path\$SqlInstance-DQ-$number-$queryname-$plannr-$Suffix.sqlplan"
}
if (!$NoPlanExport) {
Write-Message -Level Output -Message "Exporting $planfilename"
if ($plan) {$plan | Out-File -FilePath $planfilename}
}
}
$result = $result | Select-Object * -ExcludeProperty "$columnname"
}
$columnnameoptions = "Complete Query Text", "QueryText", "Query Text", "Query_Text", "query_sql_text"
if (($result | Get-Member | Where-Object Name -In $columnnameoptions ).Count -gt 0) {
$sqlnr = 0
$columnname = ($result | Get-Member | Where-Object Name -In $columnnameoptions).Name
foreach ($sql in $result."$columnname") {
$sqlnr += 1
if ($row.DatabaseSpecific) {
$sqlfilename = "$Path\$SqlInstance-$dbname-DQ-$number-$queryname-$sqlnr-$Suffix.sql"
}
else {
$sqlfilename = "$Path\$SqlInstance-DQ-$number-$queryname-$sqlnr-$Suffix.sql"
}
if (!$NoQueryExport) {
Write-Message -Level Output -Message "Exporting $sqlfilename"
if ($sql) {$sql | Out-File -FilePath $sqlfilename}
}
}
$result = $result | Select-Object * -ExcludeProperty "$columnname"
}
switch ($ConvertTo) {
"Excel" {
if ($row.DatabaseSpecific) {
Write-Message -Level Output -Message "Exporting $exceldbfilename"
$result | Export-Excel -Path $exceldbfilename -WorkSheetname $Name -AutoSize -AutoFilter -BoldTopRow -FreezeTopRow
}
else {
Write-Message -Level Output -Message "Exporting $excelfilename"
$result | Export-Excel -Path $excelfilename -WorkSheetname $Name -AutoSize -AutoFilter -BoldTopRow -FreezeTopRow
}
}
"csv" {
if ($row.DatabaseSpecific) {
Write-Message -Level Output -Message "Exporting $csvdbfilename"
$result | Export-Csv -Path $csvdbfilename -NoTypeInformation -Append
}
else {
Write-Message -Level Output -Message "Exporting $csvfilename"
$result | Export-Csv -Path $csvfilename -NoTypeInformation -Append
}
}
}
}
}
}
#ValidationTags#Messaging,FlowControl,Pipeline,CodeStyle#
function Export-DbaExecutionPlan {
<#
.SYNOPSIS
Exports execution plans to disk.
.DESCRIPTION
Exports execution plans to disk. Can pipe from Get-DbaExecutionPlan
Thanks to
https://www.simple-talk.com/sql/t-sql-programming/dmvs-for-query-plan-metadata/
and
http://www.scarydba.com/2017/02/13/export-plans-cache-sqlplan-file/
for the idea and query.
.PARAMETER SqlInstance
The SQL Server that you're connecting to.
.PARAMETER SqlCredential
Credential object used to connect to the SQL Server as a different user
.PARAMETER Database
The database(s) to process - this list is auto-populated from the server. If unspecified, all databases will be processed.
.PARAMETER ExcludeDatabase
The database(s) to exclude - this list is auto-populated from the server
.PARAMETER SinceCreation
Datetime object used to narrow the results to a date
.PARAMETER SinceLastExecution
Datetime object used to narrow the results to a date
.PARAMETER Path
The directory where all of the sqlxml files will be exported
.PARAMETER WhatIf
Shows what would happen if the command were to run. No actions are actually performed.
.PARAMETER Confirm
Prompts you for confirmation before executing any changing operations within the command.
.PARAMETER PipedObject
Internal parameter
.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.
.NOTES
Tags: Performance, ExecutionPlan
Author: Chrissy LeMaire (@cl), netnerds.net
Website: https://dbatools.io
Copyright (C) 2016 Chrissy LeMaire
License: MIT https://opensource.org/licenses/MIT
.LINK
https://dbatools.io/Export-DbaExecutionPlan
.EXAMPLE
Export-DbaExecutionPlan -SqlInstance sqlserver2014a -Path C:\Temp
Exports all execution plans for sqlserver2014a. Files saved in to C:\Temp
.EXAMPLE
Export-DbaExecutionPlan -SqlInstance sqlserver2014a -Database db1, db2 -SinceLastExecution '2016-07-01 10:47:00' -Path C:\Temp
Exports all execution plans for databases db1 and db2 on sqlserver2014a since July 1, 2016 at 10:47 AM. Files saved in to C:\Temp
.EXAMPLE
Get-DbaExecutionPlan -SqlInstance sqlserver2014a | Export-DbaExecutionPlan -Path C:\Temp
Gets all execution plans for sqlserver2014a. Using Pipeline exports them all to C:\Temp
.EXAMPLE
Get-DbaExecutionPlan -SqlInstance sqlserver2014a | Export-DbaExecutionPlan -Path C:\Temp -WhatIf
Gets all execution plans for sqlserver2014a. Then shows what would happen if the results where piped to Export-DbaExecutionPlan
#>
[cmdletbinding(SupportsShouldProcess = $true, DefaultParameterSetName = "Default")]
param (
[parameter(ParameterSetName = 'NotPiped', Mandatory)]
[Alias("ServerInstance", "SqlServer")]
[DbaInstanceParameter[]]$SqlInstance,
[parameter(ParameterSetName = 'NotPiped')]
[PSCredential]$SqlCredential,
[Alias("Databases")]
[object[]]$Database,
[object[]]$ExcludeDatabase,
[parameter(ParameterSetName = 'Piped', Mandatory)]
[parameter(ParameterSetName = 'NotPiped', Mandatory)]
[string]$Path,
[parameter(ParameterSetName = 'NotPiped')]
[datetime]$SinceCreation,
[parameter(ParameterSetName = 'NotPiped')]
[datetime]$SinceLastExecution,
[Parameter(ParameterSetName = 'Piped', Mandatory, ValueFromPipeline)]
[object[]]$PipedObject,
[Alias('Silent')]
[switch]$EnableException
)
begin {
function Export-Plan {
param(
[object]$object
)
$instanceName = $object.SqlInstance
$dbName = $object.DatabaseName
$queryPosition = $object.QueryPosition
$sqlHandle = "0x"; $object.SqlHandle | ForEach-Object { $sqlHandle += ("{0:X}" -f $_).PadLeft(2, "0") }
$sqlHandle = $sqlHandle.TrimStart('0x02000000').TrimEnd('0000000000000000000000000000000000000000')
$shortName = "$instanceName-$dbName-$queryPosition-$sqlHandle"
foreach ($queryPlan in $object.BatchQueryPlanRaw) {
$fileName = "$path\$shortName-batch.sqlplan"
try {
if ($Pscmdlet.ShouldProcess("localhost", "Writing XML file to $fileName")) {
$queryPlan.Save($fileName)
}
}
catch {
Stop-Function -Message "Skipped query plan for $fileName because it is null." -Target $fileName -ErrorRecord $_ -Continue
}
}
foreach ($statementPlan in $object.SingleStatementPlanRaw) {
$fileName = "$path\$shortName.sqlplan"
try {
if ($Pscmdlet.ShouldProcess("localhost", "Writing XML file to $fileName")) {
$statementPlan.Save($fileName)
}
}
catch {
Stop-Function -Message "Skipped statement plan for $fileName because it is null." -Target $fileName -ErrorRecord $_ -Continue
}
}
if ($Pscmdlet.ShouldProcess("console", "Showing output object")) {
Add-Member -Force -InputObject $object -MemberType NoteProperty -Name OutputFile -Value $fileName
Select-DefaultView -InputObject $object -Property ComputerName, InstanceName, SqlInstance, DatabaseName, SqlHandle, CreationTime, LastExecutionTime, OutputFile
}
}
}
process {
if (!(Test-Path $Path)) {
$null = New-Item -ItemType Directory -Path $Path
}
if ($PipedObject) {
foreach ($object in $pipedobject) {
Export-Plan $object
return
}
}
foreach ($instance in $SqlInstance) {
Write-Message -Level Verbose -Message "Connecting to $instance"
try {
$server = Connect-SqlInstance -SqlInstance $instance -SqlCredential $SqlCredential -MinimumVersion 9
}
catch {
Stop-Function -Message "Failure" -Category ConnectionError -ErrorRecord $_ -Target $instance -Continue
}
$select = "SELECT DB_NAME(deqp.dbid) as DatabaseName, OBJECT_NAME(deqp.objectid) as ObjectName,
detqp.query_plan AS SingleStatementPlan,
deqp.query_plan AS BatchQueryPlan,
ROW_NUMBER() OVER ( ORDER BY Statement_Start_offset ) AS QueryPosition,
sql_handle as SqlHandle,
plan_handle as PlanHandle,
creation_time as CreationTime,
last_execution_time as LastExecutionTime"
$from = " FROM sys.dm_exec_query_stats deqs
CROSS APPLY sys.dm_exec_text_query_plan(deqs.plan_handle,
deqs.statement_start_offset,
deqs.statement_end_offset) AS detqp
CROSS APPLY sys.dm_exec_query_plan(deqs.plan_handle) AS deqp
CROSS APPLY sys.dm_exec_sql_text(deqs.plan_handle) AS execText"
if ($ExcludeDatabase -or $Database -or $SinceCreation -or $SinceLastExecution -or $ExcludeEmptyQueryPlan -eq $true) {
$where = " WHERE "
}
$whereArray = @()
if ($Database -gt 0) {
$dbList = $Database -join "','"
$whereArray += " DB_NAME(deqp.dbid) in ('$dbList') "
}
if (Test-Bound 'SinceCreation') {
Write-Message -Level Verbose -Message "Adding creation time"
$whereArray += " creation_time >= '" + $SinceCreation.ToString("yyyy-MM-dd HH:mm:ss") + "' "
}
if (Test-Bound 'SinceLastExecution') {
Write-Message -Level Verbose -Message "Adding last execution time"
$whereArray += " last_execution_time >= '" + $SinceLastExecution.ToString("yyyy-MM-dd HH:mm:ss") + "' "
}
if (Test-Bound 'ExcludeDatabase') {
$dbList = $ExcludeDatabase -join "','"
$whereArray += " DB_NAME(deqp.dbid) not in ('$dbList') "
}
if (Test-Bound 'ExcludeEmptyQueryPlan') {
$whereArray += " detqp.query_plan is not null"
}
if ($where.Length -gt 0) {
$whereArray = $whereArray -join " and "
$where = "$where $whereArray"
}
$sql = "$select $from $where"
Write-Message -Level Debug -Message "SQL Statement: $sql"
try {
$dataTable = $server.ConnectionContext.ExecuteWithResults($sql).Tables
}
catch {
Stop-Function -Message "Issue collecting execution plans" -Target $instance -ErroRecord $_ -Continue
}
foreach ($row in ($dataTable.Rows)) {
$sqlHandle = "0x"; $row.sqlhandle | ForEach-Object { $sqlHandle += ("{0:X}" -f $_).PadLeft(2, "0") }
$planhandle = "0x"; $row.planhandle | ForEach-Object { $planhandle += ("{0:X}" -f $_).PadLeft(2, "0") }
$object = [pscustomobject]@{
ComputerName = $server.ComputerName
InstanceName = $server.ServiceName
SqlInstance = $server.DomainInstanceName
DatabaseName = $row.DatabaseName
SqlHandle = $sqlHandle
PlanHandle = $planhandle
SingleStatementPlan = $row.SingleStatementPlan
BatchQueryPlan = $row.BatchQueryPlan
QueryPosition = $row.QueryPosition
CreationTime = $row.CreationTime
LastExecutionTime = $row.LastExecutionTime
BatchQueryPlanRaw = [xml]$row.BatchQueryPlan
SingleStatementPlanRaw = [xml]$row.SingleStatementPlan
}
Export-Plan $object
}
}
}
}
function Export-DbaInstance {
<#
.SYNOPSIS
Exports SQL Server *ALL* databases, logins, database mail profiles/accounts, credentials, SQL Agent objects, linked servers,
Central Management Server objects, server configuration settings (sp_configure), user objects in systems databases,
system triggers and backup devices from one SQL Server to another.
For more granular control, please use one of the -Exclude parameters and use the other functions available within the dbatools module.
.DESCRIPTION
Export-DbaInstance consolidates most of the export scripts in dbatools into one command.
This is useful when you're looking to Export entire instances. It less flexible than using the underlying functions.
Think of it as an easy button. Unless an -Exclude is specified, it exports:
All databases.
All logins.
All database mail objects.
All credentials.
All objects within the Job Server (SQL Agent).
All linked servers.
All groups and servers within Central Management Server.
All SQL Server configuration objects (everything in sp_configure).
All user objects in system databases.
All system triggers.
All system backup devices.
All Audits.
All Endpoints.
All Extended Events.
All Policy Management objects.
All Resource Governor objects.
All Server Audit Specifications.
All Custom Errors (User Defined Messages).
All Server Roles.
All Availability Groups.
.PARAMETER SqlInstance
The target SQL Server instances
.PARAMETER SqlCredential
Login to the target instance using alternative credentials. Windows and SQL Authentication supported. Accepts credential objects (Get-Credential)
.PARAMETER Credential
Alternative Windows credentials for exporting Linked Servers and Credentials. Accepts credential objects (Get-Credential)
.PARAMETER Path
The path to the export file
.PARAMETER NetworkShare
Specifies the network location for the backup files. The SQL Server service accounts on both Source and Destination must have read/write permission to access this location.
.PARAMETER WithReplace
If this switch is used, databases are restored from backup using WITH REPLACE. This is useful if you want to stage some complex file paths.
.PARAMETER NoRecovery
If this switch is used, databases will be left in the No Recovery state to enable further backups to be added.
.PARAMETER IncludeDbMasterKey
Exports the db master key then logs into the server to copy it to the $Path
.PARAMETER Exclude
Exclude one or more objects to export
Databases
Logins
AgentServer
Credentials
LinkedServers
SpConfigure
CentralManagementServer
DatabaseMail
SysDbUserObjects
SystemTriggers
BackupDevices
Audits
Endpoints
ExtendedEvents
PolicyManagement
ResourceGovernor
ServerAuditSpecifications
CustomErrors
ServerRoles
SupportDbs
AvailabilityGroups
ReplicationSettings
.PARAMETER BatchSeparator
Batch separator for scripting output. "GO" by default.
.PARAMETER ScriptingOption
Add scripting options to scripting output for all objects except Registered Servers and Extended Events.
.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.
.NOTES
Tags: Export
Author: Chrissy LeMaire (@cl), netnerds.net
Website: https://dbatools.io
Copyright: (C) Chrissy LeMaire, [email protected]
License: MIT https://opensource.org/licenses/MIT
.LINK
https://dbatools.io/Export-DbaInstance
.EXAMPLE
Export-DbaInstance -SqlInstance sqlserver\instance
All databases, logins, job objects and sp_configure options will be exported from
sqlserver\instance to an automaticallyl generated folder name in Documents.
.EXAMPLE
Export-DbaInstance -SqlInstance sqlcluster -Exclude Databases, Logins -Path C:\dr\sqlcluster
Exports everything but logins and database restore scripts to C:\dr\sqlcluster
#>
[CmdletBinding()]
param (
[parameter(Mandatory, ValueFromPipeline)]
[Alias("ServerInstance", "SqlServer")]
[DbaInstanceParameter[]]$SqlInstance,
[PSCredential]$SqlCredential,
[PSCredential]$Credential,
[string]$Path,
[switch]$NoRecovery,
[switch]$IncludeDbMasterKey,
[ValidateSet('Databases', 'Logins', 'AgentServer', 'Credentials', 'LinkedServers', 'SpConfigure', 'CentralManagementServer', 'DatabaseMail', 'SysDbUserObjects', 'SystemTriggers', 'BackupDevices', 'Audits', 'Endpoints', 'ExtendedEvents', 'PolicyManagement', 'ResourceGovernor', 'ServerAuditSpecifications', 'CustomErrors', 'ServerRoles', 'AvailabilityGroups', 'ReplicationSettings')]
[string[]]$Exclude,
[string]$BatchSeparator = 'GO',
[Microsoft.SqlServer.Management.Smo.ScriptingOptions]$ScriptingOption,
[switch]$EnableException
)
begin {
if ((Test-Bound -ParameterName Path)) {
if (-not ((Get-Item $Path -ErrorAction Ignore) -is [System.IO.DirectoryInfo])) {
Stop-Function -Message "Path must be a directory"
}
}
if (-not $ScriptingOption) {
$ScriptingOption = New-DbaScriptingOption
}
$elapsed = [System.Diagnostics.Stopwatch]::StartNew()
$started = Get-Date
function Write-ProgressHelper {
# thanks adam!
# https://www.adamtheautomator.com/building-progress-bar-powershell-scripts/
param (
[int]$StepNumber,
[string]$Message,
[int]$TotalSteps = 21
)
Write-Progress -Activity "Performing Instance Export for $instance" -Status $Message -PercentComplete (($StepNumber / $TotalSteps) * 100)
}
$ScriptingOptions = New-Object Microsoft.SqlServer.Management.Smo.ScriptingOptions
$ScriptingOptions.ScriptBatchTerminator = $true
}
process {
if (Test-FunctionInterrupt) { return }
foreach ($instance in $SqlInstance) {
$stepCounter = $filecounter = 0
try {
Write-Message -Level Verbose -Message "Connecting to $instance."
$server = Connect-SqlInstance -SqlInstance $instance -SqlCredential $sqlcredential -MinimumVersion 10
}
catch {
Stop-Function -Message "Failure" -Category ConnectionError -ErrorRecord $_ -Target $instance -Continue
}
if (-not (Test-Bound -ParameterName Path)) {
$timenow = (Get-Date -uformat "%m%d%Y%H%M%S")
$mydocs = [Environment]::GetFolderPath('MyDocuments')
$path = "$mydocs\$($server.name.replace('\', '$'))-$timenow"
}
if (-not (Test-Path $Path)) {
try {
$null = New-Item -ItemType Directory -Path $Path -ErrorAction Stop
}
catch {
Stop-Function -Message "Failure" -ErrorRecord $_
return
}
}
if ($Exclude -notcontains 'SpConfigure') {
$fileCounter++
Write-Message -Level Verbose -Message "Exporting SQL Server Configuration"
Write-ProgressHelper -StepNumber ($stepCounter++) -Message "Exporting SQL Server Configuration"
Export-DbaSpConfigure -SqlInstance $server -Path "$Path\$fileCounter-sp_configure.sql"
if (-not (Test-Path "$Path\$fileCounter-sp_configure.sql")) {
$fileCounter--
}
}
if ($Exclude -notcontains 'CustomErrors') {
$fileCounter++
Write-Message -Level Verbose -Message "Exporting custom errors (user defined messages)"
Write-ProgressHelper -StepNumber ($stepCounter++) -Message "Exporting custom errors (user defined messages)"
$null = Get-DbaCustomError -SqlInstance $server | Export-DbaScript -Path "$Path\$fileCounter-customererrors.sql" -Append -BatchSeparator $BatchSeparator -ScriptingOptionsObject $ScriptingOption
Get-ChildItem -ErrorAction Ignore -Path "$Path\$fileCounter-customererrors.sql"
if (-not (Test-Path "$Path\$fileCounter-customererrors.sql")) {
$fileCounter--
}
}
if ($Exclude -notcontains 'ServerRoles') {
$fileCounter++
Write-Message -Level Verbose -Message "Exporting server roles"
Write-ProgressHelper -StepNumber ($stepCounter++) -Message "Exporting server roles"
$null = Get-DbaServerRole -SqlInstance $server | Export-DbaScript -Path "$Path\$fileCounter-serverroles.sql" -Append -BatchSeparator $BatchSeparator -ScriptingOptionsObject $ScriptingOption
Get-ChildItem -ErrorAction Ignore -Path "$Path\$fileCounter-serverroles.sql"
if (-not (Test-Path "$Path\$fileCounter-serverroles.sql")) {
$fileCounter--
}
}
if ($Exclude -notcontains 'Credentials') {
$fileCounter++
Write-Message -Level Verbose -Message "Exporting SQL credentials"
Write-ProgressHelper -StepNumber ($stepCounter++) -Message "Exporting SQL credentials"
$null = Export-DbaCredential -SqlInstance $server -Credential $Credential -Path "$Path\$fileCounter-credentials.sql" -Append
Get-ChildItem -ErrorAction Ignore -Path "$Path\$fileCounter-credentials.sql"
if (-not (Test-Path "$Path\$fileCounter-credentials.sql")) {
$fileCounter--
}
}
if ($Exclude -notcontains 'DatabaseMail') {
$fileCounter++
Write-Message -Level Verbose -Message "Exporting database mail"
Write-ProgressHelper -StepNumber ($stepCounter++) -Message "Exporting database mail"
$null = Get-DbaDbMailConfig -SqlInstance $server | Export-DbaScript -Path "$Path\$fileCounter-dbmail.sql" -Append -BatchSeparator $BatchSeparator -ScriptingOptionsObject $ScriptingOption
$null = Get-DbaDbMailAccount -SqlInstance $server | Export-DbaScript -Path "$Path\$fileCounter-dbmail.sql" -Append -BatchSeparator $BatchSeparator -ScriptingOptionsObject $ScriptingOption
$null = Get-DbaDbMailProfile -SqlInstance $server | Export-DbaScript -Path "$Path\$fileCounter-dbmail.sql" -Append -BatchSeparator $BatchSeparator -ScriptingOptionsObject $ScriptingOption
$null = Get-DbaDbMailServer -SqlInstance $server | Export-DbaScript -Path "$Path\$fileCounter-dbmail.sql" -Append -BatchSeparator $BatchSeparator -ScriptingOptionsObject $ScriptingOption
$null = Get-DbaDbMail -SqlInstance $server | Export-DbaScript -Path "$Path\$fileCounter-dbmail.sql" -Append -BatchSeparator $BatchSeparator -ScriptingOptionsObject $ScriptingOption
Get-ChildItem -ErrorAction Ignore -Path "$Path\$fileCounter-dbmail.sql"
if (-not (Test-Path "$Path\$fileCounter-dbmail.sql")) {
$fileCounter--
}
}
if ($Exclude -notcontains 'CentralManagementServer') {
$fileCounter++
Write-Message -Level Verbose -Message "Exporting Central Management Server"
Write-ProgressHelper -StepNumber ($stepCounter++) -Message "Exporting Central Management Server"
$null = Get-DbaCmsRegServerGroup -SqlInstance $server | Export-DbaScript -Path "$Path\$fileCounter-regserver.sql" -Append -BatchSeparator 'GO'
$null = Get-DbaCmsRegServer -SqlInstance $server | Export-DbaScript -Path "$Path\$fileCounter-regserver.sql" -Append -BatchSeparator 'GO'
Get-ChildItem -ErrorAction Ignore -Path "$Path\$fileCounter-regserver.sql"
if (-not (Test-Path "$Path\$fileCounter-regserver.sql")) {
$fileCounter--
}
}
if ($Exclude -notcontains 'BackupDevices') {
$fileCounter++
Write-Message -Level Verbose -Message "Exporting Backup Devices"
Write-ProgressHelper -StepNumber ($stepCounter++) -Message "Exporting Backup Devices"
$null = Get-DbaBackupDevice -SqlInstance $server | Export-DbaScript -Path "$Path\$fileCounter-backupdevices.sql" -Append -BatchSeparator $BatchSeparator -ScriptingOptionsObject $ScriptingOption
Get-ChildItem -ErrorAction Ignore -Path "$Path\$fileCounter-backupdevices.sql"
if (-not (Test-Path "$Path\$fileCounter-backupdevices.sql")) {
$fileCounter--
}
}
if ($Exclude -notcontains 'LinkedServers') {
$fileCounter++
Write-Message -Level Verbose -Message "Exporting linked servers"
Write-ProgressHelper -StepNumber ($stepCounter++) -Message "Exporting linked servers"
Export-DbaLinkedServer -SqlInstance $server -Path "$Path\$fileCounter-linkedservers.sql" -Credential $Credential -Append
if (-not (Test-Path "$Path\$fileCounter-linkedservers.sql")) {
$fileCounter--
}
}
if ($Exclude -notcontains 'SystemTriggers') {
$fileCounter++
Write-Message -Level Verbose -Message "Exporting System Triggers"
Write-ProgressHelper -StepNumber ($stepCounter++) -Message "Exporting System Triggers"
$null = Get-DbaServerTrigger -SqlInstance $server | Export-DbaScript -Path "$Path\$fileCounter-servertriggers.sql" -Append -BatchSeparator $BatchSeparator -ScriptingOptionsObject $ScriptingOption
$triggers = Get-Content -Path "$Path\$fileCounter-servertriggers.sql" -Raw -ErrorAction Ignore
if ($triggers) {
$triggers = $triggers.ToString() -replace 'CREATE TRIGGER', "GO`r`nCREATE TRIGGER"
$triggers = $triggers.ToString() -replace 'ENABLE TRIGGER', "GO`r`nENABLE TRIGGER"
$null = $triggers | Set-Content -Path "$Path\$fileCounter-servertriggers.sql" -Force
Get-ChildItem -ErrorAction Ignore -Path "$Path\$fileCounter-servertriggers.sql"
}
if (-not (Test-Path "$Path\$fileCounter-servertriggers.sql")) {
$fileCounter--
}
}
if ($Exclude -notcontains 'Databases') {
$fileCounter++
Write-Message -Level Verbose -Message "Exporting database restores"
Write-ProgressHelper -StepNumber ($stepCounter++) -Message "Exporting database restores"
Get-DbaBackupHistory -SqlInstance $server -Last | Restore-DbaDatabase -SqlInstance $server -NoRecovery:$NoRecovery -WithReplace -OutputScriptOnly -WarningAction SilentlyContinue | Out-File -FilePath "$Path\$fileCounter-databases.sql" -Append
Get-ChildItem -ErrorAction Ignore -Path "$Path\$fileCounter-databases.sql"
if (-not (Test-Path "$Path\$fileCounter-databases.sql")) {
$fileCounter--
}
}
if ($Exclude -notcontains 'Logins') {
$fileCounter++
Write-Message -Level Verbose -Message "Exporting logins"
Write-ProgressHelper -StepNumber ($stepCounter++) -Message "Exporting logins"
Export-DbaLogin -SqlInstance $server -Path "$Path\$fileCounter-logins.sql" -Append -WarningAction SilentlyContinue
if (-not (Test-Path "$Path\$fileCounter-logins.sql")) {
$fileCounter--
}
}
if ($Exclude -notcontains 'Audits') {
$fileCounter++
Write-Message -Level Verbose -Message "Exporting Audits"
Write-ProgressHelper -StepNumber ($stepCounter++) -Message "Exporting Audits"
$null = Get-DbaServerAudit -SqlInstance $server | Export-DbaScript -Path "$Path\$fileCounter-audits.sql" -Append -BatchSeparator $BatchSeparator -ScriptingOptionsObject $ScriptingOption
Get-ChildItem -ErrorAction Ignore -Path "$Path\$fileCounter-audits.sql"
if (-not (Test-Path "$Path\$fileCounter-audits.sql")) {
$fileCounter--
}
}
if ($Exclude -notcontains 'ServerAuditSpecifications') {
$fileCounter++
Write-Message -Level Verbose -Message "Exporting Server Audit Specifications"
Write-ProgressHelper -StepNumber ($stepCounter++) -Message "Exporting Server Audit Specifications"
$null = Get-DbaServerAuditSpecification -SqlInstance $server | Export-DbaScript -Path "$Path\$fileCounter-auditspecs.sql" -Append -BatchSeparator $BatchSeparator -ScriptingOptionsObject $ScriptingOption
Get-ChildItem -ErrorAction Ignore -Path "$Path\$fileCounter-auditspecs.sql"
if (-not (Test-Path "$Path\$fileCounter-auditspecs.sql")) {
$fileCounter--
}
}
if ($Exclude -notcontains 'Endpoints') {
$fileCounter++
Write-Message -Level Verbose -Message "Exporting Endpoints"
Write-ProgressHelper -StepNumber ($stepCounter++) -Message "Exporting Endpoints"
$null = Get-DbaEndpoint -SqlInstance $server | Where-Object IsSystemObject -eq $false | Export-DbaScript -Path "$Path\$fileCounter-endpoints.sql" -Append -BatchSeparator $BatchSeparator -ScriptingOptionsObject $ScriptingOption
Get-ChildItem -ErrorAction Ignore -Path "$Path\$fileCounter-endpoints.sql"
if (-not (Test-Path "$Path\$fileCounter-endpoints.sql")) {
$fileCounter--
}
}
if ($Exclude -notcontains 'PolicyManagement') {
$fileCounter++
Write-Message -Level Verbose -Message "Exporting Policy Management"
Write-ProgressHelper -StepNumber ($stepCounter++) -Message "Exporting Policy Management"
$null = Get-DbaPbmCondition -SqlInstance $server | Export-DbaScript -Path "$Path\$fileCounter-policymanagement.sql" -Append -BatchSeparator $BatchSeparator
$null = Get-DbaPbmObjectSet -SqlInstance $server | Export-DbaScript -Path "$Path\$fileCounter-policymanagement.sql" -Append -BatchSeparator $BatchSeparator
$null = Get-DbaPbmPolicy -SqlInstance $server | Export-DbaScript -Path "$Path\$fileCounter-policymanagement.sql" -Append -BatchSeparator $BatchSeparator
Get-ChildItem -ErrorAction Ignore -Path "$Path\$fileCounter-policymanagement.sql"
if (-not (Test-Path "$Path\$fileCounter-policymanagement.sql")) {
$fileCounter--
}
}
if ($Exclude -notcontains 'ResourceGovernor') {
$fileCounter++
Write-Message -Level Verbose -Message "Exporting Resource Governor"
Write-ProgressHelper -StepNumber ($stepCounter++) -Message "Exporting Resource Governor"
$null = Get-DbaResourceGovernor -SqlInstance $server | Export-DbaScript -Path "$Path\$fileCounter-resourcegov.sql" -Append -BatchSeparator $BatchSeparator -ScriptingOptionsObject $ScriptingOption
$null = Get-DbaRgClassifierFunction -SqlInstance $server | Export-DbaScript -Path "$Path\$fileCounter-resourcegov.sql" -Append -BatchSeparator $BatchSeparator -ScriptingOptionsObject $ScriptingOption
$null = Get-DbaRgResourcePool -SqlInstance $server | Where-Object Name -notin 'default', 'internal' | Export-DbaScript -Path "$Path\$fileCounter-resourcegov.sql" -Append -BatchSeparator $BatchSeparator -ScriptingOptionsObject $ScriptingOption
$null = Get-DbaRgWorkloadGroup -SqlInstance $server | Where-Object Name -notin 'default','internal' | Export-DbaScript -Path "$Path\$fileCounter-resourcegov.sql" -Append -BatchSeparator $BatchSeparator -ScriptingOptionsObject $ScriptingOption
$null = Add-Content -Value "ALTER RESOURCE GOVERNOR RECONFIGURE" -Path "$Path\$stepCounter-resourcegov.sql"
Get-ChildItem -ErrorAction Ignore -Path "$Path\$fileCounter-resourcegov.sql"
if (-not (Test-Path "$Path\$fileCounter-resourcegov.sql")) {
$fileCounter--
}
}
if ($Exclude -notcontains 'ExtendedEvents') {
$fileCounter++
Write-Message -Level Verbose -Message "Exporting Extended Events"
Write-ProgressHelper -StepNumber ($stepCounter++) -Message "Exporting Extended Events"
$null = Get-DbaXESession -SqlInstance $server | Export-DbaScript -Path "$Path\$fileCounter-extendedevents.sql" -Append -BatchSeparator 'GO'
Get-ChildItem -ErrorAction Ignore -Path "$Path\$fileCounter-extendedevents.sql"
if (-not (Test-Path "$Path\$fileCounter-extendedevents.sql")) {
$fileCounter--
}
}
if ($Exclude -notcontains 'AgentServer') {
$fileCounter++
Write-Message -Level Verbose -Message "Exporting job server"
Write-ProgressHelper -StepNumber ($stepCounter++) -Message "Exporting job server"
$null = Get-DbaAgentJobCategory -SqlInstance $server | Export-DbaScript -Path "$Path\$fileCounter-sqlagent.sql" -Append -BatchSeparator $BatchSeparator -ScriptingOptionsObject $ScriptingOption
$null = Get-DbaAgentOperator -SqlInstance $server | Export-DbaScript -Path "$Path\$fileCounter-sqlagent.sql" -Append -BatchSeparator $BatchSeparator -ScriptingOptionsObject $ScriptingOption
$null = Get-DbaAgentAlert -SqlInstance $server | Export-DbaScript -Path "$Path\$fileCounter-sqlagent.sql" -Append -BatchSeparator $BatchSeparator -ScriptingOptionsObject $ScriptingOption
$null = Get-DbaAgentProxy -SqlInstance $server | Export-DbaScript -Path "$Path\$fileCounter-sqlagent.sql" -Append -BatchSeparator $BatchSeparator -ScriptingOptionsObject $ScriptingOption
$null = Get-DbaAgentSchedule -SqlInstance $server | Export-DbaScript -Path "$Path\$fileCounter-sqlagent.sql" -Append -BatchSeparator $BatchSeparator -ScriptingOptionsObject $ScriptingOption
$null = Get-DbaAgentJob -SqlInstance $server | Export-DbaScript -Path "$Path\$fileCounter-sqlagent.sql" -Append -BatchSeparator $BatchSeparator -ScriptingOptionsObject $ScriptingOption
Get-ChildItem -ErrorAction Ignore -Path "$Path\$fileCounter-sqlagent.sql"
if (-not (Test-Path "$Path\$fileCounter-sqlagent.sql")) {
$fileCounter--
}
}
if ($Exclude -notcontains 'ReplicationSettings') {
$fileCounter++
Write-Message -Level Verbose -Message "Exporting replication settings"
Write-ProgressHelper -StepNumber ($stepCounter++) -Message "Exporting replication settings"
$null = Export-DbaRepServerSetting -SqlInstance $instance -SqlCredential $SqlCredential -Path "$Path\$fileCounter-replication.sql"
Get-ChildItem -ErrorAction Ignore -Path "$Path\$fileCounter-replication.sql"
if (-not (Test-Path "$Path\$fileCounter-replication.sql")) {
$fileCounter--
}
}
if ($Exclude -notcontains 'SysDbUserObjects') {
$fileCounter++
Write-Message -Level Verbose -Message "Exporting user objects in system databases (this can take a minute)."
Write-ProgressHelper -StepNumber ($stepCounter++) -Message "Exporting user objects in system databases (this can take a minute)."
$null = Get-DbaSysDbUserObjectScript -SqlInstance $server | Out-File -FilePath "$Path\$fileCounter-userobjectsinsysdbs.sql" -Append
Get-ChildItem -ErrorAction Ignore -Path "$Path\$fileCounter-userobjectsinsysdbs.sql"
if (-not (Test-Path "$Path\$fileCounter-userobjectsinsysdbs.sql")) {
$fileCounter--
}
}
if ($Exclude -notcontains 'AvailabilityGroups') {
$fileCounter++
Write-Message -Level Verbose -Message "Exporting availability group"
Write-ProgressHelper -StepNumber ($stepCounter++) -Message "Exporting availability groups"
$null = Get-DbaAvailabilityGroup -SqlInstance $server -WarningAction SilentlyContinue | Export-DbaScript -Path "$Path\$fileCounter-availabilitygroups.sql" -Append -BatchSeparator $BatchSeparator #-ScriptingOptionsObject $ScriptingOption
Get-ChildItem -ErrorAction Ignore -Path "$Path\$fileCounter-availabilitygroups.sql"
if (-not (Test-Path "$Path\$fileCounter-availabilitygroups.sql")) {
$fileCounter--
}
}
Write-Progress -Activity "Performing Instance Export for $instance" -Completed
}
}
end {
$totaltime = ($elapsed.Elapsed.toString().Split(".")[0])
Write-Message -Level Verbose -Message "SQL Server export complete."
Write-Message -Level Verbose -Message "Export started: $started"
Write-Message -Level Verbose -Message "Export completed: $(Get-Date)"
Write-Message -Level Verbose -Message "Total Elapsed time: $totaltime"
}
}
function Export-DbaLinkedServer {
<#
.SYNOPSIS
Exports linked servers INCLUDING PASSWORDS, unless specified otherwise, to sql file.
.DESCRIPTION
Exports linked servers INCLUDING PASSWORDS, unless specified otherwise, to sql file.
Requires remote Windows access if exporting the password.
.PARAMETER SqlInstance
Source SQL Server. You must have sysadmin access and server version must be SQL Server version 2005 or higher.
.PARAMETER SqlCredential
Login to the target instance using alternative linked servers. Windows and SQL Authentication supported. Accepts credential objects (Get-Credential)
.PARAMETER Credential
Login to the target OS using alternative linked servers. Accepts credential objects (Get-Credential)
.PARAMETER Path
The path to the exported sql file.
.PARAMETER LinkedServer
The linked server(s) to export. If unspecified, all linked servers will be processed.
.PARAMETER InputObject
Allow credentials to be piped in from Get-DbaLinkedServer
.PARAMETER ExcludePassword
Exports the linked server without any sensitive information.
.PARAMETER Append
Append to Path
.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.
.NOTES
Tags: LinkedServer
Author: Chrissy LeMaire (@cl), netnerds.net
Website: https://dbatools.io
Copyright: (C) Chrissy LeMaire, [email protected]
License: MIT https://opensource.org/licenses/MIT
.EXAMPLE
Export-DbaLinkedServer -SqlInstance sql2017 -Path C:\temp\ls.sql
Exports the linked servers, including passwords, from sql2017 to the file C:\temp\ls.sql
.EXAMPLE
Export-DbaLinkedServer -SqlInstance sql2017 -Path C:\temp\ls.sql -ExcludePassword
Exports the linked servers, without passwords, from sql2017 to the file C:\temp\ls.sql
#>
[CmdletBinding()]
param (
[Parameter(Mandatory, ValueFromPipeline)]
[Alias("ServerInstance", "SqlServer")]
[DbaInstanceParameter[]]$SqlInstance,
[string[]]$LinkedServer,
[PSCredential]$SqlCredential,
[PSCredential]$Credential,
[string]$Path,
[switch]$ExcludePassword,
[switch]$Append,
[Microsoft.SqlServer.Management.Smo.LinkedServer[]]$InputObject,
[switch]$EnableException
)
process {
foreach ($instance in $SqlInstance) {
try {
Write-Message -Level Verbose -Message "Connecting to $instance."
$server = Connect-SqlInstance -SqlInstance $instance -SqlCredential $sqlcredential -MinimumVersion 9
$InputObject += $server.LinkedServers
}
catch {
Stop-Function -Message "Failure" -Category ConnectionError -ErrorRecord $_ -Target $instance -Continue
}
if ($LinkedServer) {
$InputObject = $InputObject | Where-Object Name -in $LinkedServer
}
if (-not $InputObject) {
Write-Message -Level Verbose -Message "Nothing to export"
continue
}
if (!(Test-SqlSa -SqlInstance $instance -SqlCredential $sqlcredential)) {
Stop-Function -Message "Not a sysadmin on $instance. Quitting." -Target $instance -Continue
}
Write-Message -Level Verbose -Message "Getting NetBios name for $instance."
$sourceNetBios = Resolve-NetBiosName $server
Write-Message -Level Verbose -Message "Checking if Remote Registry is enabled on $instance."
try {
Invoke-Command2 -Raw -Credential $Credential -ComputerName $sourceNetBios -ScriptBlock { Get-ItemProperty -Path "HKLM:\SOFTWARE\" } -ErrorAction Stop
}
catch {
Stop-Function -Message "Can't connect to registry on $instance." -Target $sourceNetBios -ErrorRecord $_
return
}
if (-not (Test-Bound -ParameterName Path)) {
$timenow = (Get-Date -uformat "%m%d%Y%H%M%S")
$mydocs = [Environment]::GetFolderPath('MyDocuments')
$path = "$mydocs\$($server.name.replace('\', '$'))-$timenow-linkedserver.sql"
}
$sql = @()
if ($ExcludePassword) {
$sql += $InputObject.Script()
}
else {
try {
$decrypted = Get-DecryptedObject -SqlInstance $server -Type LinkedServer
}
catch {
Stop-Function -Continue -Message "Failure" -ErrorRecord $_
}
foreach ($ls in $InputObject) {
$currentls = $decrypted | Where-Object Name -eq $ls.Name
if ($currentls.Password) {
$password = $currentls.Password.Replace("'", "''")
$tempsql = $ls.Script()
$tempsql = $tempsql.Replace(' /* For security reasons the linked server remote logins password is changed with ######## */', '')
$tempsql = $tempsql.Replace("rmtpassword='########'", "rmtpassword='$password'")
$sql += $tempsql
}
else {
$sql += $ls.Script()
}
}
}
try {
if ($Append) {
Add-Content -Path $path -Value $sql
}
else {
Set-Content -Path $path -Value $sql
}
Get-ChildItem -Path $path
}
catch {
Stop-Function -Message "Can't write to $path" -ErrorRecord $_ -Continue
}
}
}
}
function Export-DbaLogin {
<#
.SYNOPSIS
Exports Windows and SQL Logins to a T-SQL file. Export includes login, SID, password, default database, default language, server permissions, server roles, db permissions, db roles.
.DESCRIPTION
Exports Windows and SQL Logins to a T-SQL file. Export includes login, SID, password, default database, default language, server permissions, server roles, db permissions, db roles.
.PARAMETER SqlInstance
The SQL Server instance name. SQL Server 2000 and above supported.
.PARAMETER SqlCredential
Login to the target instance using alternative credentials. Windows and SQL Authentication supported. Accepts credential objects (Get-Credential)
.PARAMETER Login
The login(s) to process. Options for this list are auto-populated from the server. If unspecified, all logins will be processed.
.PARAMETER ExcludeLogin
The login(s) to exclude. Options for this list are auto-populated from the server.
.PARAMETER Database
The database(s) to process. Options for this list are auto-populated from the server. If unspecified, all databases will be processed.
.PARAMETER Path
The file to write to.
.PARAMETER NoClobber
If this switch is enabled, a file already existing at the path specified by Path will not be overwritten.
.PARAMETER Append
If this switch is enabled, content will be appended to a file already existing at the path specified by Path. If the file does not exist, it will be created.
.PARAMETER NoJobs
If this switch is enabled, Agent job ownership will not be exported.
.PARAMETER NoDatabases
If this switch is enabled, mappings for databases will not be exported.
.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.
.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 ExcludeGoBatchSeparator
If specified, will NOT script the 'GO' batch separator.
.PARAMETER DestinationVersion
To say to which version the script should be generated. If not specified will use instance major version.
.NOTES
Tags: Export, Login
Author: Chrissy LeMaire (@cl), netnerds.net
Website: https://dbatools.io
Copyright: (C) Chrissy LeMaire, [email protected]
License: MIT https://opensource.org/licenses/MIT
.LINK
https://dbatools.io/Export-DbaLogin
.EXAMPLE
Export-DbaLogin -SqlInstance sql2005 -Path C:\temp\sql2005-logins.sql
Exports the logins for SQL Server "sql2005" and writes them to the file "C:\temp\sql2005-logins.sql"
.EXAMPLE
Export-DbaLogin -SqlInstance sqlserver2014a -Exclude realcajun -SqlCredential $scred -Path C:\temp\logins.sql -Append
Authenticates to sqlserver2014a using SQL Authentication. Exports all logins except for realcajun to C:\temp\logins.sql, and appends to the file if it exists. If not, the file will be created.
.EXAMPLE
Export-DbaLogin -SqlInstance sqlserver2014a -Login realcajun, netnerds -Path C:\temp\logins.sql
Exports ONLY logins netnerds and realcajun FROM sqlserver2014a to the file C:\temp\logins.sql
.EXAMPLE
Export-DbaLogin -SqlInstance sqlserver2014a -Login realcajun, netnerds -Database HR, Accounting
Exports ONLY logins netnerds and realcajun FROM sqlserver2014a with the permissions on databases HR and Accounting
.EXAMPLE
Export-DbaLogin -SqlInstance sqlserver2008 -Login realcajun, netnerds -Path C:\temp\login.sql -ExcludeGoBatchSeparator
Exports ONLY logins netnerds and realcajun FROM sqlserver2008 server, to the C:\temp\login.sql file without the 'GO' batch separator.
.EXAMPLE
Export-DbaLogin -SqlInstance sqlserver2008 -Login realcajun -Path C:\temp\users.sql -DestinationVersion SQLServer2016
Exports login realcajun from sqlsever2008 to the file C:\temp\users.sql with syntax to run on SQL Server 2016
#>
[CmdletBinding(SupportsShouldProcess = $true)]
param (
[parameter(Mandatory, ValueFromPipeline)]
[Alias("ServerInstance", "SqlServer")]
[DbaInstanceParameter]$SqlInstance,
[Alias("Credential")]
[PSCredential]
$SqlCredential,
[object[]]$Login,
[object[]]$ExcludeLogin,
[Alias("Databases")]
[object[]]$Database,
[Alias("OutFile", "FilePath", "FileName")]
[string]$Path,
[Alias("NoOverwrite")]
[switch]$NoClobber,
[switch]$Append,
[switch]$NoDatabases,
[switch]$NoJobs,
[Alias('Silent')]
[switch]$EnableException,
[switch]$ExcludeGoBatchSeparator,
[ValidateSet('SQLServer2000', 'SQLServer2005', 'SQLServer2008/2008R2', 'SQLServer2012', 'SQLServer2014', 'SQLServer2016', 'SQLServer2017')]
[string]$DestinationVersion
)
begin {
if ($Path) {
if ($Path -notlike "*\*") {
$Path = ".\$Path"
}
$directory = Split-Path $Path
$exists = Test-Path $directory
if ($exists -eq $false) {
Write-Message -Level Warning -Message "Parent directory $directory does not exist"
}
}
$outsql = @()
}
process {
if (Test-FunctionInterrupt) {
return
}
Write-Message -Level Verbose -Message "Connecting to $sqlinstance."
$server = Connect-SqlInstance -SqlInstance $SqlInstance -SqlCredential $sqlcredential
if ($NoDatabases -eq $false -or $Database) {
# if we got a database or a list of databases passed
# and we need to enumerate mappings, login.enumdatabasemappings() takes forever
# the cool thing though is that database.enumloginmappings() is fast. A lot.
# if we get a list of databases passed (or even the default list of all the databases)
# we save outself a call to enumloginmappings if there is no map at all
$DbMapping = @()
$DbsToMap = $server.Databases
if ($Database) {
$DbsToMap = $DbsToMap | Where-Object Name -in $Database
}
foreach ($db in $DbsToMap) {
if ($db.IsAccessible -eq $false) {
continue
}
$dbmap = $db.EnumLoginMappings()
foreach ($el in $dbmap) {
$DbMapping += [pscustomobject]@{
Database = $db.Name
UserName = $el.Username
LoginName = $el.LoginName
}
}
}
}
foreach ($sourceLogin in $server.Logins) {
$userName = $sourceLogin.name
if ($Login -and $Login -notcontains $userName -or $ExcludeLogin -contains $userName) {
continue
}
if ($userName.StartsWith("##") -or $userName -eq 'sa') {
Write-Message -Level Warning -Message "Skipping $userName"
continue
}
$serverName = $server
$userBase = ($userName.Split("\")[0]).ToLower()
if ($serverName -eq $userBase -or $userName.StartsWith("NT ")) {
if ($Pscmdlet.ShouldProcess("console", "Stating $userName is skipped because it is a local machine name")) {
Write-Message -Level Warning -Message "$userName is skipped because it is a local machine name"
continue
}
}
if ($Pscmdlet.ShouldProcess("Outfile", "Adding T-SQL for login $userName")) {
if ($Path) {
Write-Message -Level Verbose -Message "Exporting $userName"
}
$outsql += "`r`nUSE master`n"
# Getting some attributes
$defaultDb = $sourceLogin.DefaultDatabase
$language = $sourceLogin.Language
if ($sourceLogin.PasswordPolicyEnforced -eq $false) {
$checkPolicy = "OFF"
}
else {
$checkPolicy = "ON"
}
if (!$sourceLogin.PasswordExpirationEnabled) {
$checkExpiration = "OFF"
}
else {
$checkExpiration = "ON"
}
# Attempt to script out SQL Login
if ($sourceLogin.LoginType -eq "SqlLogin") {
$sourceLoginName = $sourceLogin.name
switch ($server.versionMajor) {
0 {
$sql = "SELECT CONVERT(VARBINARY(256),password) AS hashedpass FROM master.dbo.syslogins WHERE loginname='$sourceLoginName'"
}
8 {
$sql = "SELECT CONVERT(VARBINARY(256),password) AS hashedpass FROM dbo.syslogins WHERE name='$sourceLoginName'"
}
9 {
$sql = "SELECT CONVERT(VARBINARY(256),password_hash) as hashedpass FROM sys.sql_logins WHERE name='$sourceLoginName'"
}
default {
$sql = "SELECT CAST(CONVERT(varchar(256), CAST(LOGINPROPERTY(name,'PasswordHash') AS VARBINARY(256)), 1) AS NVARCHAR(max)) AS hashedpass FROM sys.server_principals WHERE principal_id = $($sourceLogin.id)"
}
}
try {
$hashedPass = $server.ConnectionContext.ExecuteScalar($sql)
}
catch {
$hashedPassDt = $server.Databases['master'].ExecuteWithResults($sql)
$hashedPass = $hashedPassDt.Tables[0].Rows[0].Item(0)
}
if ($hashedPass.GetType().Name -ne "String") {
$passString = "0x"; $hashedPass | ForEach-Object {
$passString += ("{0:X}" -f $_).PadLeft(2, "0")
}
$hashedPass = $passString
}
$sid = "0x"; $sourceLogin.sid | ForEach-Object {
$sid += ("{0:X}" -f $_).PadLeft(2, "0")
}
$outsql += "IF NOT EXISTS (SELECT loginname FROM master.dbo.syslogins WHERE name = '$userName') CREATE LOGIN [$userName] WITH PASSWORD = $hashedPass HASHED, SID = $sid, DEFAULT_DATABASE = [$defaultDb], CHECK_POLICY = $checkPolicy, CHECK_EXPIRATION = $checkExpiration, DEFAULT_LANGUAGE = [$language]"
}
# Attempt to script out Windows User
elseif ($sourceLogin.LoginType -eq "WindowsUser" -or $sourceLogin.LoginType -eq "WindowsGroup") {
$outsql += "IF NOT EXISTS (SELECT loginname FROM master.dbo.syslogins WHERE name = '$userName') CREATE LOGIN [$userName] FROM WINDOWS WITH DEFAULT_DATABASE = [$defaultDb], DEFAULT_LANGUAGE = [$language]"
}
# This script does not currently support certificate mapped or asymmetric key users.
else {
Write-Message -Level Warning -Message "$($sourceLogin.LoginType) logins not supported. $($sourceLogin.Name) skipped"
continue
}
if ($sourceLogin.IsDisabled) {
$outsql += "ALTER LOGIN [$userName] DISABLE"
}
if ($sourceLogin.DenyWindowsLogin) {
$outsql += "DENY CONNECT SQL TO [$userName]"
}
}
# Server Roles: sysadmin, bulklogin, etc
foreach ($role in $server.Roles) {
$roleName = $role.Name
# SMO changed over time
try {
$roleMembers = $role.EnumMemberNames()
}
catch {
$roleMembers = $role.EnumServerRoleMembers()
}
if ($roleMembers -contains $userName) {
if (($server.VersionMajor -lt 11 -and [string]::IsNullOrEmpty($destinationVersion)) -or ($DestinationVersion -in "SQLServer2000", "SQLServer2005", "SQLServer2008/2008R2")) {
$outsql += "EXEC sys.sp_addsrvrolemember @rolename=N'$roleName', @loginame=N'$userName'"
}
else {
$outsql += "ALTER SERVER ROLE [$roleName] ADD MEMBER [$userName]"
}
}
}
if ($NoJobs -eq $false) {
$ownedJobs = $server.JobServer.Jobs | Where-Object { $_.OwnerLoginName -eq $userName }
foreach ($ownedJob in $ownedJobs) {
$outsql += "`n`rUSE msdb`n"
$outsql += "EXEC msdb.dbo.sp_update_job @job_name=N'$ownedJob', @owner_login_name=N'$userName'"
}
}
if ($server.VersionMajor -ge 9) {
# These operations are only supported by SQL Server 2005 and above.
# Securables: Connect SQL, View any database, Administer Bulk Operations, etc.
$perms = $server.EnumServerPermissions($userName)
$outsql += "`n`rUSE master`n"
foreach ($perm in $perms) {
$permState = $perm.permissionstate
$permType = $perm.PermissionType
$grantor = $perm.grantor
if ($permState -eq "GrantWithGrant") {
$grantWithGrant = "WITH GRANT OPTION"
$permState = "GRANT"
}
else {
$grantWithGrant = $null
}
$outsql += "$permState $permType TO [$userName] $grantWithGrant AS [$grantor]"
}
# Credential mapping. Credential removal not currently supported for Syncs.
$loginCredentials = $server.Credentials | Where-Object { $_.Identity -eq $sourceLogin.Name }
foreach ($credential in $loginCredentials) {
$credentialName = $credential.Name
$outsql += "PRINT '$userName is associated with the $credentialName credential'"
}
}
if ($NoDatabases -eq $false) {
$dbs = $sourceLogin.EnumDatabaseMappings()
if ($Database) {
$dbs = $dbs | Where-Object { $_.DB