Downloads:
191,949
Downloads of v 0.9.772:
224
Last Update:
24 Feb 2019
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.772 | Updated: 24 Feb 2019
- 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:
191,949
Downloads of v 0.9.772:
224
Maintainer(s):
Software Author(s):
- Chrissy LeMaire
dbatools (PowerShell Module) 0.9.772
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.772 --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.772'" [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.772'"
$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.772'
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.772'
end
See docs at https://docs.chef.io/resource_chocolatey_package.html.
cChocoPackageInstaller dbatools
{
Name = "dbatools"
Version = "0.9.772"
Source = "INTERNAL REPO URL"
}
Requires cChoco DSC Resource. See docs at https://github.com/chocolatey/cChoco.
package { 'dbatools':
ensure => '0.9.772',
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 24 Feb 2019.
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
}
{
"MD024": false,
"MD022": false,
"MD032": false,
"MD013": false,
"MD001": true
}
### DO NOT EDIT THIS FILE DIRECTLY ###
function Export-DbatoolsConfig
{
<#
.SYNOPSIS
Exports configuration items to a Json file.
.DESCRIPTION
Exports configuration items to a Json file.
.PARAMETER FullName
Select the configuration objects to export by filtering by their full name.
.PARAMETER Module
Select the configuration objects to export by filtering by their module name.
.PARAMETER Name
Select the configuration objects to export by filtering by their name.
.PARAMETER Config
The configuration object(s) to export.
Returned by Get-DbatoolsConfig.
.PARAMETER ModuleName
Exports all configuration pertinent to a module to a predefined path.
Exported configuration items include all settings marked as 'ModuleExport' that have been changed from the default value.
.PARAMETER ModuleVersion
The configuration version of the module-settings to write.
.PARAMETER Scope
Which predefined path to write module specific settings to.
Only file scopes are considered.
By default it writes to the suer profile.
.PARAMETER OutPath
The path (filename included) to export to.
Will fail if the folder does not exist, will overwrite the file if it exists.
.PARAMETER SkipUnchanged
If set, configuration objects whose value was not changed from its original value will not be exported.
(Note: Settings that were updated with the same value as the original default will still be considered changed)
.PARAMETER EnableException
This parameters disables user-friendly warnings and enables the throwing of exceptions.
This is less user friendly, but allows catching exceptions in calling scripts.
.EXAMPLE
PS C:\> Get-DbatoolsConfig | Export-DbatoolsConfig -OutPath '~/export.json'
Exports all current settings to json.
.EXAMPLE
Export-DbatoolsConfig -Module message -OutPath '~/export.json' -SkipUnchanged
Exports all settings of the module 'message' that are no longer the original default values to json.
#>
[CmdletBinding(DefaultParameterSetName = 'FullName')]
Param (
[Parameter(ParameterSetName = "FullName", Position = 0, Mandatory = $true)]
[string]
$FullName,
[Parameter(ParameterSetName = "Module", Position = 0, Mandatory = $true)]
[string]
$Module,
[Parameter(ParameterSetName = "Module", Position = 1)]
[string]
$Name = "*",
[Parameter(ParameterSetName = "Config", Position = 0, Mandatory = $true, ValueFromPipeline = $true)]
[Sqlcollaborative.Dbatools.Configuration.Config[]]
$Config,
[Parameter(ParameterSetName = "ModuleName", Mandatory = $true)]
[string]
$ModuleName,
[Parameter(ParameterSetName = "ModuleName")]
[int]
$ModuleVersion = 1,
[Parameter(ParameterSetName = "ModuleName")]
[Sqlcollaborative.Dbatools.Configuration.ConfigScope]
$Scope = "FileUserShared",
[Parameter(Position = 1, Mandatory = $true, ParameterSetName = 'Config')]
[Parameter(Position = 1, Mandatory = $true, ParameterSetName = 'FullName')]
[Parameter(Position = 2, Mandatory = $true, ParameterSetName = 'Module')]
[string]
$OutPath,
[switch]
$SkipUnchanged,
[switch]
$EnableException
)
begin
{
Write-Message -Level InternalComment -Message "Bound parameters: $($PSBoundParameters.Keys -join ", ")" -Tag 'debug', 'start', 'param'
$items = @()
if (($Scope -band 15) -and ($ModuleName))
{
Stop-Function -Message "Cannot export modulecache to registry! Please pick a file scope for your export destination" -EnableException $EnableException -Category InvalidArgument -Tag 'fail', 'scope', 'registry'
return
}
}
process
{
if (Test-FunctionInterrupt) { return }
if (-not $ModuleName)
{
foreach ($item in $Config) { $items += $item }
if ($FullName) { $items = Get-DbatoolsConfig -FullName $FullName }
if ($Module) { $items = Get-DbatoolsConfig -Module $Module -Name $Name }
}
}
end
{
if (Test-FunctionInterrupt) { return }
if (-not $ModuleName)
{
try { Write-DbatoolsConfigFile -Config ($items | Where-Object { -not $SkipUnchanged -or -not $_.Unchanged } ) -Path $OutPath -Replace }
catch
{
Stop-Function -Message "Failed to export to file" -EnableException $EnableException -ErrorRecord $_ -Tag 'fail', 'export'
return
}
}
else
{
if ($Scope -band 16)
{
Write-DbatoolsConfigFile -Config (Get-DbatoolsConfig -Module $ModuleName -Force | Where-Object ModuleExport | Where-Object Unchanged -NE $true) -Path (Join-Path $script:path_FileUserLocal "$($ModuleName.ToLower())-$($ModuleVersion).json")
}
if ($Scope -band 32)
{
Write-DbatoolsConfigFile -Config (Get-DbatoolsConfig -Module $ModuleName -Force | Where-Object ModuleExport | Where-Object Unchanged -NE $true) -Path (Join-Path $script:path_FileUserShared "$($ModuleName.ToLower())-$($ModuleVersion).json")
}
if ($Scope -band 64)
{
Write-DbatoolsConfigFile -Config (Get-DbatoolsConfig -Module $ModuleName -Force | Where-Object ModuleExport | Where-Object Unchanged -NE $true) -Path (Join-Path $script:path_FileSystem "$($ModuleName.ToLower())-$($ModuleVersion).json")
}
}
}
}
function Get-DbatoolsConfig {
<#
.SYNOPSIS
Retrieves configuration elements by name.
.DESCRIPTION
Retrieves configuration elements by name.
Can be used to search the existing configuration list.
.PARAMETER FullName
Default: "*"
Search for configurations using the full name
.PARAMETER Name
Default: "*"
The name of the configuration element(s) to retrieve.
May be any string, supports wildcards.
.PARAMETER Module
Default: "*"
Search configuration by module.
.PARAMETER Force
Overrides the default behavior and also displays hidden configuration values.
.NOTES
Tags: Config, Module
Author: Friedrich Weinmann (@FredWeinmann)
Website: https://dbatools.io
Copyright: (c) 2018 by dbatools, licensed under MIT
License: MIT https://opensource.org/licenses/MIT
.EXAMPLE
PS C:\> Get-DbatoolsConfig 'Mail.To'
Retrieves the configuration element for the key "Mail.To"
.EXAMPLE
PS C:\> Get-DbatoolsConfig -Force
Retrieve all configuration elements from all modules, even hidden ones.
#>
[CmdletBinding(DefaultParameterSetName = "FullName")]
param (
[Parameter(ParameterSetName = "FullName", Position = 0)]
[string]$FullName = "*",
[Parameter(ParameterSetName = "Module", Position = 1)]
[string]$Name = "*",
[Parameter(ParameterSetName = "Module", Position = 0)]
[string]$Module = "*",
[switch]$Force
)
switch ($PSCmdlet.ParameterSetName) {
"Module" {
$Name = $Name.ToLower()
$Module = $Module.ToLower()
[Sqlcollaborative.Dbatools.Configuration.ConfigurationHost]::Configurations.Values | Where-Object { ($_.Name -like $Name) -and ($_.Module -like $Module) -and ((-not $_.Hidden) -or ($Force)) } | Sort-Object Module, Name
}
"FullName" {
[Sqlcollaborative.Dbatools.Configuration.ConfigurationHost]::Configurations.Values | Where-Object { ("$($_.Module).$($_.Name)" -like $FullName) -and ((-not $_.Hidden) -or ($Force)) } | Sort-Object Module, Name
}
}
Test-DbaDeprecation -DeprecatedOn "1.0.0" -EnableException:$false -Alias Get-DbaConfig
}
function Get-DbatoolsConfigValue {
<#
.SYNOPSIS
Returns the configuration value stored under the specified name.
.DESCRIPTION
Returns the configuration value stored under the specified name.
It requires the full name (<Module>.<Name>) and is usually only called by functions.
.PARAMETER FullName
The full name (<Module>.<Name>) of the configured value to return.
.PARAMETER Fallback
A fallback value to use, if no value was registered to a specific configuration element.
This basically is a default value that only applies on a "per call" basis, rather than a system-wide default.
.PARAMETER NotNull
By default, this function returns null if one tries to retrieve the value from either a Configuration that does not exist or a Configuration whose value was set to null.
However, sometimes it may be important that some value was returned.
By specifying this parameter, the function will throw an error if no value was found at all.
.NOTES
Tags: Config, Module
Author: Friedrich Weinmann (@FredWeinmann)
Website: https://dbatools.io
Copyright: (c) 2018 by dbatools, licensed under MIT
License: MIT https://opensource.org/licenses/MIT
.EXAMPLE
PS C:\> Get-DbatoolsConfigValue -Name 'System.MailServer'
Returns the configured value that was assigned to the key 'System.MailServer'
.EXAMPLE
PS C:\> Get-DbatoolsConfigValue -Name 'Default.CoffeeMilk' -Fallback 0
Returns the configured value for 'Default.CoffeeMilk'. If no such value is configured, it returns '0' instead.
#>
[Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSPossibleIncorrectComparisonWithNull", "")]
[CmdletBinding()]
param (
[Alias('Name')]
[Parameter(Mandatory)]
[string]$FullName,
[object]$Fallback,
[switch]$NotNull
)
Test-DbaDeprecation -DeprecatedOn "1.0.0" -EnableException:$false -Alias Get-DbaConfigValue
$FullName = $FullName.ToLower()
$temp = $null
$temp = [Sqlcollaborative.Dbatools.Configuration.ConfigurationHost]::Configurations[$FullName].Value
if ($temp -eq $null) { $temp = $Fallback }
if ($NotNull -and ($temp -eq $null)) {
Stop-Function -Message "No Configuration Value available for $Name" -EnableException $true -Category InvalidData -Target $FullName
} else {
return $temp
}
}
function Import-DbatoolsConfig
{
<#
.SYNOPSIS
Imports a json configuration file into the configuration system.
.DESCRIPTION
Imports a json configuration file into the configuration system.
.PARAMETER Path
The path to the json file to import.
.PARAMETER ModuleName
Import configuration items specific to a module from the default configuration paths.
.PARAMETER ModuleVersion
The configuration version of the module-settings to load.
.PARAMETER Scope
Where to import the module specific configuration items form.
Only file-based scopes are supported for this.
By default, all locations are queried, with user settings beating system settings.
.PARAMETER IncludeFilter
If specified, only elements with names that are similar (-like) to names in this list will be imported.
.PARAMETER ExcludeFilter
Elements that are similar (-like) to names in this list will not be imported.
.PARAMETER Peek
Rather than applying the setting, return the configuration items that would have been applied.
.PARAMETER EnableException
This parameters disables user-friendly warnings and enables the throwing of exceptions.
This is less user friendly, but allows catching exceptions in calling scripts.
.EXAMPLE
PS C:\> Import-DbatoolsConfig -Path '.\config.json'
Imports the configuration stored in '.\config.json'
.EXAMPLE
PS C:\> Import-DbatoolsConfig -ModuleName message
Imports all the module specific settings that have been persisted in any of the default file system paths.
#>
[Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSAvoidUsingEmptyCatchBlock", "")]
[CmdletBinding(DefaultParameterSetName = "Path")]
param (
[Parameter(Mandatory = $true, ValueFromPipeline = $true, ParameterSetName = "Path")]
[string[]]
$Path,
[Parameter(ParameterSetName = "ModuleName", Mandatory = $true)]
[string]
$ModuleName,
[Parameter(ParameterSetName = "ModuleName")]
[int]
$ModuleVersion = 1,
[Parameter(ParameterSetName = "ModuleName")]
[Sqlcollaborative.Dbatools.Configuration.ConfigScope]
$Scope = "FileUserLocal, FileUserShared, FileSystem",
[Parameter(ParameterSetName = "Path")]
[string[]]
$IncludeFilter,
[Parameter(ParameterSetName = "Path")]
[string[]]
$ExcludeFilter,
[Parameter(ParameterSetName = "Path")]
[switch]
$Peek,
[switch]
$EnableException
)
begin
{
Write-Message -Level InternalComment -Message "Bound parameters: $($PSBoundParameters.Keys -join ", ")" -Tag 'debug','start','param'
}
process
{
#region Explicit Path
foreach ($item in $Path)
{
try
{
if ($item -like "http*") { $data = Read-DbatoolsConfigFile -Weblink $item -ErrorAction Stop }
else
{
$pathItem = $null
try { $pathItem = Resolve-DbaPath -Path $item -SingleItem -Provider FileSystem }
catch { }
if ($pathItem) { $data = Read-DbatoolsConfigFile -Path $pathItem -ErrorAction Stop }
else { $data = Read-DbatoolsConfigFile -RawJson $item -ErrorAction Stop }
}
}
catch { Stop-Function -Message "Failed to import $item" -EnableException $EnableException -Tag 'fail', 'import' -ErrorRecord $_ -Continue -Target $item }
:element foreach ($element in $data)
{
#region Exclude Filter
foreach ($exclusion in $ExcludeFilter)
{
if ($element.FullName -like $exclusion)
{
continue element
}
}
#endregion Exclude Filter
#region Include Filter
if ($IncludeFilter)
{
$isIncluded = $false
foreach ($inclusion in $IncludeFilter)
{
if ($element.FullName -like $inclusion)
{
$isIncluded = $true
break
}
}
if (-not $isIncluded) { continue }
}
#endregion Include Filter
if ($Peek) { $element }
else
{
try
{
if (-not $element.KeepPersisted) { Set-DbatoolsConfig -FullName $element.FullName -Value $element.Value -EnableException }
else { Set-DbatoolsConfig -FullName $element.FullName -PersistedValue $element.Value -PersistedType $element.Type }
}
catch
{
Stop-Function -Message "Failed to set '$($element.FullName)'" -ErrorRecord $_ -EnableException $EnableException -Tag 'fail', 'import' -Continue -Target $item
}
}
}
}
#endregion Explicit Path
if ($ModuleName)
{
$data = Read-DbatoolsConfigPersisted -Module $ModuleName -Scope $Scope -ModuleVersion $ModuleVersion
foreach ($value in $data.Values)
{
if (-not $value.KeepPersisted) { Set-DbatoolsConfig -FullName $value.FullName -Value $value.Value -EnableException:$EnableException}
else { Set-DbatoolsConfig -FullName $value.FullName -Value ([Sqlcollaborative.Dbatools.Configuration.ConfigurationHost]::ConvertFromPersistedValue($value.Value, $value.Type)) -EnableException:$EnableException }
}
}
}
end
{
}
}
function Register-DbatoolsConfig
{
<#
.SYNOPSIS
Registers an existing configuration object in registry.
.DESCRIPTION
Registers an existing configuration object in registry.
This allows simple persisting of settings across powershell consoles.
It also can be used to generate a registry template, which can then be used to create policies.
.PARAMETER Config
The configuration object to write to registry.
Can be retrieved using Get-DbatoolsConfig.
.PARAMETER FullName
The full name of the setting to be written to registry.
.PARAMETER Module
The name of the module, whose settings should be written to registry.
.PARAMETER Name
Default: "*"
Used in conjunction with the -Module parameter to restrict the number of configuration items written to registry.
.PARAMETER Scope
Default: UserDefault
Who will be affected by this export how? Current user or all? Default setting or enforced?
Legal values: UserDefault, UserMandatory, SystemDefault, SystemMandatory
.PARAMETER EnableException
This parameters disables user-friendly warnings and enables the throwing of exceptions.
This is less user friendly, but allows catching exceptions in calling scripts.
.EXAMPLE
PS C:\> Get-DbatoolsConfig message.style.* | Register-DbatoolsConfig
Retrieves all configuration items that that start with message.style. and registers them in registry for the current user.
.EXAMPLE
PS C:\> Register-DbatoolsConfig -FullName "message.consoleoutput.disable" -Scope SystemDefault
Retrieves the configuration item "message.consoleoutput.disable" and registers it in registry as the default setting for all users on this machine.
.EXAMPLE
PS C:\> Register-DbatoolsConfig -Module Message -Scope SystemMandatory
Retrieves all configuration items of the module Message, then registers them in registry to enforce them for all users on the current system.
#>
[CmdletBinding(DefaultParameterSetName = "Default")]
Param (
[Parameter(ParameterSetName = "Default", Position = 0, ValueFromPipeline = $true)]
[Sqlcollaborative.Dbatools.Configuration.Config[]]
$Config,
[Parameter(ParameterSetName = "Default", Position = 0, ValueFromPipeline = $true)]
[string[]]
$FullName,
[Parameter(Mandatory = $true, ParameterSetName = "Name", Position = 0)]
[string]
$Module,
[Parameter(ParameterSetName = "Name", Position = 1)]
[string]
$Name = "*",
[Sqlcollaborative.Dbatools.Configuration.ConfigScope]
$Scope = "UserDefault",
[switch]
$EnableException
)
begin
{
if ($script:NoRegistry -and ($Scope -band 14))
{
Stop-Function -Message "Cannot register configurations on non-windows machines to registry. Please specify a file-based scope" -Tag 'NotSupported' -Category NotImplemented
return
}
# Linux and MAC default to local user store file
if ($script:NoRegistry -and ($Scope -eq "UserDefault"))
{
$Scope = [Sqlcollaborative.Dbatools.Configuration.ConfigScope]::FileUserLocal
}
# Linux and MAC get redirection for SystemDefault to FileSystem
if ($script:NoRegistry -and ($Scope -eq "SystemDefault"))
{
$Scope = [Sqlcollaborative.Dbatools.Configuration.ConfigScope]::FileSystem
}
$parSet = $PSCmdlet.ParameterSetName
function Write-Config
{
[CmdletBinding()]
Param (
[Sqlcollaborative.Dbatools.Configuration.Config]
$Config,
[Sqlcollaborative.Dbatools.Configuration.ConfigScope]
$Scope,
[bool]
$EnableException,
[string]
$FunctionName = (Get-PSCallStack)[0].Command
)
if (-not $Config -or ($Config.RegistryData -eq "<type not supported>"))
{
Stop-Function -Message "Invalid Input, cannot export $($Config.FullName), type not supported" -EnableException $EnableException -Category InvalidArgument -Tag "config", "fail" -Target $Config -FunctionName $FunctionName
return
}
try
{
Write-Message -Level Verbose -Message "Registering $($Config.FullName) for $Scope" -Tag "Config" -Target $Config -FunctionName $FunctionName
#region User Default
if (1 -band $Scope)
{
Ensure-RegistryPath -Path $script:path_RegistryUserDefault -ErrorAction Stop
Set-ItemProperty -Path $script:path_RegistryUserDefault -Name $Config.FullName -Value $Config.RegistryData -ErrorAction Stop
}
#endregion User Default
#region User Mandatory
if (2 -band $Scope)
{
Ensure-RegistryPath -Path $script:path_RegistryUserEnforced -ErrorAction Stop
Set-ItemProperty -Path $script:path_RegistryUserEnforced -Name $Config.FullName -Value $Config.RegistryData -ErrorAction Stop
}
#endregion User Mandatory
#region System Default
if (4 -band $Scope)
{
Ensure-RegistryPath -Path $script:path_RegistryMachineDefault -ErrorAction Stop
Set-ItemProperty -Path $script:path_RegistryMachineDefault -Name $Config.FullName -Value $Config.RegistryData -ErrorAction Stop
}
#endregion System Default
#region System Mandatory
if (8 -band $Scope)
{
Ensure-RegistryPath -Path $script:path_RegistryMachineEnforced -ErrorAction Stop
Set-ItemProperty -Path $script:path_RegistryMachineEnforced -Name $Config.FullName -Value $Config.RegistryData -ErrorAction Stop
}
#endregion System Mandatory
}
catch
{
Stop-Function -Message "Failed to export $($Config.FullName), to scope $Scope" -EnableException $EnableException -Tag "config", "fail" -Target $Config -ErrorRecord $_ -FunctionName $FunctionName
return
}
}
function Ensure-RegistryPath
{
[Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUseApprovedVerbs", "")]
[CmdletBinding()]
Param (
[string]
$Path
)
if (-not (Test-Path $Path))
{
$null = New-Item $Path -Force
}
}
# For file based persistence
$configurationItems = @()
}
process
{
if (Test-FunctionInterrupt) { return }
#region Registry Based
if ($Scope -band 15)
{
switch ($parSet)
{
"Default"
{
foreach ($item in $Config)
{
Write-Config -Config $item -Scope $Scope -EnableException $EnableException
}
foreach ($item in $FullName)
{
if ([Sqlcollaborative.Dbatools.Configuration.ConfigurationHost]::Configurations.ContainsKey($item.ToLower()))
{
Write-Config -Config ([Sqlcollaborative.Dbatools.Configuration.ConfigurationHost]::Configurations[$item.ToLower()]) -Scope $Scope -EnableException $EnableException
}
}
}
"Name"
{
foreach ($item in ([Sqlcollaborative.Dbatools.Configuration.ConfigurationHost]::Configurations.Values | Where-Object Module -EQ $Module | Where-Object Name -Like $Name))
{
Write-Config -Config $item -Scope $Scope -EnableException $EnableException
}
}
}
}
#endregion Registry Based
#region File Based
else
{
switch ($parSet)
{
"Default"
{
foreach ($item in $Config)
{
if ($configurationItems.FullName -notcontains $item.FullName) { $configurationItems += $item }
}
foreach ($item in $FullName)
{
if (($configurationItems.FullName -notcontains $item) -and ([Sqlcollaborative.Dbatools.Configuration.ConfigurationHost]::Configurations.ContainsKey($item.ToLower())))
{
$configurationItems += [Sqlcollaborative.Dbatools.Configuration.ConfigurationHost]::Configurations[$item.ToLower()]
}
}
}
"Name"
{
foreach ($item in ([Sqlcollaborative.Dbatools.Configuration.ConfigurationHost]::Configurations.Values | Where-Object Module -EQ $Module | Where-Object Name -Like $Name))
{
if ($configurationItems.FullName -notcontains $item.FullName) { $configurationItems += $item }
}
}
}
}
#endregion File Based
}
end
{
if (Test-FunctionInterrupt) { return }
#region Finish File Based Persistence
if ($Scope -band 16)
{
Write-DbatoolsConfigFile -Config $configurationItems -Path (Join-Path $script:path_FileUserLocal "psf_config.json")
}
if ($Scope -band 32)
{
Write-DbatoolsConfigFile -Config $configurationItems -Path (Join-Path $script:path_FileUserShared "psf_config.json")
}
if ($Scope -band 64)
{
Write-DbatoolsConfigFile -Config $configurationItems -Path (Join-Path $script:path_FileSystem "psf_config.json")
}
#endregion Finish File Based Persistence
}
}
function Reset-DbatoolsConfig
{
<#
.SYNOPSIS
Reverts a configuration item to its default value.
.DESCRIPTION
This command can be used to revert a configuration item to the value it was initialized with.
Generally, this amounts to reverting it to its default value.
In order for a reset to be possible, two conditions must be met:
- The setting must have been initialized.
- The setting cannot have been enforced by policy.
.PARAMETER ConfigurationItem
A configuration object as returned by Get-DbatoolsConfig.
.PARAMETER FullName
The full name of the setting to reset, offering the maximum of precision.
.PARAMETER Module
The name of the module, from which configurations should be reset.
Used in conjunction with the -Name parameter to filter a specific set of items.
.PARAMETER Name
Used in conjunction with the -Module parameter to select which settings to reset using wildcard comparison.
.PARAMETER EnableException
This parameters disables user-friendly warnings and enables the throwing of exceptions.
This is less user friendly, but allows catching exceptions in calling scripts.
.PARAMETER Confirm
If this switch is enabled, you will be prompted for confirmation before executing any operations that change state.
.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.
.EXAMPLE
PS C:\> Reset-DbatoolsConfig -Module MyModule
Resets all configuration items of the MyModule to default.
.EXAMPLE
PS C:\> Get-DbatoolsConfig | Reset-DbatoolsConfig
Resets ALL configuration items to default.
.EXAMPLE
PS C:\> Reset-DbatoolsConfig -FullName MyModule.Group.Setting1
Resets the configuration item named 'MyModule.Group.Setting1'.
#>
[CmdletBinding(DefaultParameterSetName = 'Pipeline')]
param (
[Parameter(ValueFromPipeline = $true, ParameterSetName = 'Pipeline')]
[Sqlcollaborative.Dbatools.Configuration.Config[]]
$ConfigurationItem,
[Parameter(ValueFromPipeline = $true, ParameterSetName = 'Pipeline')]
[string[]]
$FullName,
[Parameter(Mandatory = $true, ParameterSetName = 'Module')]
[string]
$Module,
[Parameter(ParameterSetName = 'Module')]
[string]
$Name = "*",
[switch]
$EnableException
)
process
{
#region By configuration Item
foreach ($item in $ConfigurationItem)
{
if ($PSCmdlet.ShouldProcess($item.FullName, 'Reset to default value'))
{
try { $item.ResetValue() }
catch { Stop-Function -Message "Failed to reset the configuration item." -ErrorRecord $_ -Continue -EnableException $EnableException }
}
}
#endregion By configuration Item
#region By FullName
foreach ($nameItem in $FullName)
{
# The configuration items themselves can be cast to string, so they need to be filtered out,
# otherwise on bind they would execute for this code-path as well.
if ($nameItem -ceq "Sqlcollaborative.Dbatools.Configuration.Config") { continue }
foreach ($item in (Get-DbatoolsConfig -FullName $nameItem))
{
if ($PSCmdlet.ShouldProcess($item.FullName, 'Reset to default value'))
{
try { $item.ResetValue() }
catch { Stop-Function -Message "Failed to reset the configuration item." -ErrorRecord $_ -Continue -EnableException $EnableException }
}
}
}
#endregion By FullName
if ($Module)
{
foreach ($item in (Get-DbatoolsConfig -Module $Module -Name $Name))
{
if ($PSCmdlet.ShouldProcess($item.FullName, 'Reset to default value'))
{
try { $item.ResetValue() }
catch { Stop-Function -Message "Failed to reset the configuration item." -ErrorRecord $_ -Continue -EnableException $EnableException }
}
}
}
}
}
function Unregister-DbatoolsConfig
{
<#
.SYNOPSIS
Removes registered configuration settings.
.DESCRIPTION
Removes registered configuration settings.
This function can be used to remove settings that have been persisted for either user or computer.
Note: This command has no effect on configuration setings currently in memory.
.PARAMETER ConfigurationItem
A configuration object as returned by Get-DbatoolsConfig.
.PARAMETER FullName
The full name of the configuration setting to purge.
.PARAMETER Module
The module, amongst which settings should be unregistered.
.PARAMETER Name
The name of the setting to unregister.
For use together with the module parameter, to limit the amount of settings that are unregistered.
.PARAMETER Scope
Settings can be set to either default or enforced, for user or the entire computer.
By default, only DefaultSettings for the user are unregistered.
Use this parameter to choose the actual scope for the command to process.
.EXAMPLE
PS C:\> Get-DbatoolsConfig | Unregister-DbatoolsConfig
Completely removes all registered configurations currently loaded in memory.
In most cases, this will mean removing all registered configurations.
.EXAMPLE
PS C:\> Unregister-DbatoolsConfig -Scope SystemDefault -FullName 'MyModule.Path.DefaultExport'
Unregisters the setting 'MyModule.Path.DefaultExport' from the list of computer-wide defaults.
Note: Changing system wide settings requires running the console with elevation.
.EXAMPLE
PS C:\> Unregister-DbatoolsConfig -Module MyModule
Unregisters all configuration settings for the module MyModule.
#>
[CmdletBinding(DefaultParameterSetName = 'Pipeline')]
param (
[Parameter(ValueFromPipeline = $true, ParameterSetName = 'Pipeline')]
[Sqlcollaborative.Dbatools.Configuration.Config[]]
$ConfigurationItem,
[Parameter(ValueFromPipeline = $true, ParameterSetName = 'Pipeline')]
[string[]]
$FullName,
[Parameter(Mandatory = $true, ParameterSetName = 'Module')]
[string]
$Module,
[Parameter(ParameterSetName = 'Module')]
[string]
$Name = "*",
[Sqlcollaborative.Dbatools.Configuration.ConfigScope]
$Scope = "UserDefault"
)
begin
{
if (($PSVersionTable.PSVersion.Major -ge 6) -and ($PSVersionTable.OS -notlike "*Windows*") -and ($Scope -band 15))
{
Stop-Function -Message "Cannot unregister configurations from registry on non-windows machines." -Tag 'NotSupported' -Category ResourceUnavailable
return
}
#region Initialize Collection
$registryProperties = @()
if ($Scope -band 1)
{
if (Test-Path $script:path_RegistryUserDefault) { $registryProperties += Get-ItemProperty -Path $script:path_RegistryUserDefault }
}
if ($Scope -band 2)
{
if (Test-Path $script:path_RegistryUserEnforced) { $registryProperties += Get-ItemProperty -Path $script:path_RegistryUserEnforced }
}
if ($Scope -band 4)
{
if (Test-Path $script:path_RegistryMachineDefault) { $registryProperties += Get-ItemProperty -Path $script:path_RegistryMachineDefault }
}
if ($Scope -band 8)
{
if (Test-Path $script:path_RegistryMachineEnforced) { $registryProperties += Get-ItemProperty -Path $script:path_RegistryMachineEnforced }
}
$pathProperties = @()
if ($Scope -band 16)
{
$fileUserLocalSettings = @()
if (Test-Path (Join-Path $script:path_FileUserLocal "psf_config.json")) { $fileUserLocalSettings = Get-Content (Join-Path $script:path_FileUserLocal "psf_config.json") -Encoding UTF8 | ConvertFrom-Json }
if ($fileUserLocalSettings)
{
$pathProperties += [pscustomobject]@{
Path = (Join-Path $script:path_FileUserLocal "psf_config.json")
Properties = $fileUserLocalSettings
Changed = $false
}
}
}
if ($Scope -band 32)
{
$fileUserSharedSettings = @()
if (Test-Path (Join-Path $script:path_FileUserShared "psf_config.json")) { $fileUserSharedSettings = Get-Content (Join-Path $script:path_FileUserShared "psf_config.json") -Encoding UTF8 | ConvertFrom-Json }
if ($fileUserSharedSettings)
{
$pathProperties += [pscustomobject]@{
Path = (Join-Path $script:path_FileUserShared "psf_config.json")
Properties = $fileUserSharedSettings
Changed = $false
}
}
}
if ($Scope -band 64)
{
$fileSystemSettings = @()
if (Test-Path (Join-Path $script:path_FileSystem "psf_config.json")) { $fileSystemSettings = Get-Content (Join-Path $script:path_FileSystem "psf_config.json") -Encoding UTF8 | ConvertFrom-Json }
if ($fileSystemSettings)
{
$pathProperties += [pscustomobject]@{
Path = (Join-Path $script:path_FileSystem "psf_config.json")
Properties = $fileSystemSettings
Changed = $false
}
}
}
#endregion Initialize Collection
$common = 'PSPath', 'PSParentPath', 'PSChildName', 'PSDrive', 'PSProvider'
}
process
{
if (Test-FunctionInterrupt) { return }
# Silently skip since no action necessary
if (-not ($pathProperties -or $registryProperties)) { return }
foreach ($item in $ConfigurationItem)
{
# Registry
foreach ($hive in ($registryProperties | Where-Object { $_.PSObject.Properties.Name -eq $item.FullName }))
{
Remove-ItemProperty -Path $hive.PSPath -Name $item.FullName
}
# Prepare file
foreach ($fileConfig in ($pathProperties | Where-Object { $_.Properties.FullName -contains $item.FullName }))
{
$fileConfig.Properties = $fileConfig.Properties | Where-Object FullName -NE $item.FullName
$fileConfig.Changed = $true
}
}
foreach ($item in $FullName)
{
# Ignore string-casted configurations
if ($item -ceq "Sqlcollaborative.Dbatools.Configuration.Config") { continue }
# Registry
foreach ($hive in ($registryProperties | Where-Object { $_.PSObject.Properties.Name -eq $item }))
{
Remove-ItemProperty -Path $hive.PSPath -Name $item
}
# Prepare file
foreach ($fileConfig in ($pathProperties | Where-Object { $_.Properties.FullName -contains $item }))
{
$fileConfig.Properties = $fileConfig.Properties | Where-Object FullName -NE $item
$fileConfig.Changed = $true
}
}
if ($Module)
{
$compoundName = "{0}.{1}" -f $Module, $Name
# Registry
foreach ($hive in ($registryProperties | Where-Object { $_.PSObject.Properties.Name -like $compoundName }))
{
foreach ($propName in $hive.PSObject.Properties.Name)
{
if ($propName -in $common) { continue }
if ($propName -like $compoundName)
{
Remove-ItemProperty -Path $hive.PSPath -Name $propName
}
}
}
# Prepare file
foreach ($fileConfig in ($pathProperties | Where-Object { $_.Properties.FullName -like $compoundName }))
{
$fileConfig.Properties = $fileConfig.Properties | Where-Object FullName -NotLike $compoundName
$fileConfig.Changed = $true
}
}
}
end
{
if (Test-FunctionInterrupt) { return }
foreach ($fileConfig in $pathProperties)
{
if (-not $fileConfig.Changed) { continue }
if ($fileConfig.Properties)
{
$fileConfig.Properties | ConvertTo-Json | Set-Content -Path $fileConfig.Path -Encoding UTF8
}
else
{
Remove-Item $fileConfig.Path
}
}
}
}
#ValidationTags#Messaging,FlowControl,Pipeline,CodeStyle#
function Add-DbaAgDatabase {
<#
.SYNOPSIS
Adds a database to an availability group on a SQL Server instance.
.DESCRIPTION
Adds a database to an availability group on a SQL Server instance.
Before joining the replica databases to the availablity group, the databases will be initialized with automatic seeding or full/log backup.
.PARAMETER SqlInstance
The target SQL Server instance or instances. Server version must be SQL Server version 2012 or higher.
This should be the primary replica.
.PARAMETER SqlCredential
Login to the SqlInstance instance using alternative credentials. Windows and SQL Authentication supported. Accepts credential objects (Get-Credential)
.PARAMETER Database
The database or databases to add.
.PARAMETER AvailabilityGroup
The availability group where the databases will be added.
.PARAMETER Secondary
Not required - the command will figure this out. But if you'd like to be explicit about replicas, this will help.
.PARAMETER SecondarySqlCredential
Login to the target instance using alternative credentials. Windows and SQL Authentication supported. Accepts credential objects (Get-Credential)
.PARAMETER InputObject
Enables piping from Get-DbaDatabase, Get-DbaDbSharePoint and more.
.PARAMETER SeedingMode
Specifies how the secondary replica will be initially seeded.
Automatic enables direct seeding. This method will seed the secondary replica over the network. This method does not require you to backup and restore a copy of the primary database on the replica.
Manual requires you to create a backup of the database on the primary replica and manually restore that backup on the secondary replica.
If not specified, the setting from the availability group replica will be used. Otherwise the setting will be updated.
.PARAMETER SharedPath
The network share where the backups will be backed up and restored from.
Each SQL Server service account must have access to this share.
NOTE: If a backup / restore is performed, the backups will be left in tact on the network share.
.PARAMETER UseLastBackup
Use the last full backup of database.
.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: AvailabilityGroup, HA, AG
Author: Chrissy LeMaire (@cl), netnerds.net
Website: https://dbatools.io
Copyright: (c) 2018 by dbatools, licensed under MIT
License: MIT https://opensource.org/licenses/MIT
.LINK
https://dbatools.io/Add-DbaAgDatabase
.EXAMPLE
PS C:\> Add-DbaAgDatabase -SqlInstance sql2017a -AvailabilityGroup ag1 -Database db1, db2 -Confirm
Adds db1 and db2 to ag1 on sql2017a. Prompts for confirmation.
.EXAMPLE
PS C:\> Get-DbaDatabase -SqlInstance sql2017a | Out-GridView -Passthru | Add-DbaAgDatabase -AvailabilityGroup ag1
Adds selected databases from sql2017a to ag1
.EXAMPLE
PS C:\> Get-DbaDbSharePoint -SqlInstance sqlcluster | Add-DbaAgDatabase -AvailabilityGroup SharePoint
Adds SharePoint databases as found in SharePoint_Config on sqlcluster to ag1 on sqlcluster
.EXAMPLE
PS C:\> Get-DbaDbSharePoint -SqlInstance sqlcluster -ConfigDatabase SharePoint_Config_2019 | Add-DbaAgDatabase -AvailabilityGroup SharePoint
Adds SharePoint databases as found in SharePoint_Config_2019 on sqlcluster to ag1 on sqlcluster
#>
[CmdletBinding(SupportsShouldProcess, ConfirmImpact = 'Low')]
param (
[DbaInstanceParameter[]]$SqlInstance,
[PSCredential]$SqlCredential,
[parameter(Mandatory)]
[string]$AvailabilityGroup,
[string[]]$Database,
[DbaInstanceParameter[]]$Secondary,
[PSCredential]$SecondarySqlCredential,
[parameter(ValueFromPipeline)]
[Microsoft.SqlServer.Management.Smo.Database[]]$InputObject,
[ValidateSet('Automatic', 'Manual')]
[string]$SeedingMode,
[string]$SharedPath,
[switch]$UseLastBackup,
[switch]$EnableException
)
process {
if ((Test-Bound -ParameterName SqlInstance)) {
if ((Test-Bound -Not -ParameterName Database) -or (Test-Bound -Not -ParameterName AvailabilityGroup)) {
Stop-Function -Message "You must specify one or more databases and one Availability Group when using the SqlInstance parameter."
return
}
}
foreach ($instance in $SqlInstance) {
$InputObject += Get-DbaDatabase -SqlInstance $instance -SqlCredential $SqlCredential -Database $Database
}
foreach ($db in $InputObject) {
$allbackups = @{
}
$Primary = $db.Parent
# check primary, should be run against primary
$ag = Get-DbaAvailabilityGroup -SqlInstance $db.Parent -AvailabilityGroup $AvailabilityGroup
if ($ag.AvailabilityDatabases.Name -contains $db.Name) {
Stop-Function -Message "$($db.Name) is already joined to $($ag.Name)" -Continue
}
if (-not $Secondary) {
$secondaryReplicas = $ag.AvailabilityReplicas | Where-Object Role -eq Secondary
} else {
$secondaryReplicas = Get-DbaAgReplica -SqlInstance $Secondary -SqlCredential $SecondarySqlCredential -AvailabilityGroup $ag.Name | Where-Object Role -eq Secondary
}
if ($SeedingMode -eq "Automatic") {
# first check
if ($Pscmdlet.ShouldProcess($Primary, "Backing up $db to NUL")) {
$null = Backup-DbaDatabase -BackupFileName NUL -SqlInstance $Primary -SqlCredential $SqlCredential -Database $db
}
}
if ($Pscmdlet.ShouldProcess($ag.Parent.Name, "Adding availability group $db to $($db.Parent.Name)")) {
try {
$agdb = New-Object Microsoft.SqlServer.Management.Smo.AvailabilityDatabase($ag, $db.Name)
# something is up with .net create(), force a stop
Invoke-Create -Object $agdb
Get-DbaAgDatabase -SqlInstance $ag.Parent -Database $db.Name
} catch {
Stop-Function -Message "Failure" -ErrorRecord $_ -Continue
}
}
foreach ($replica in $secondaryReplicas) {
$agreplica = Get-DbaAgReplica -SqlInstance $Primary -SqlCredential $SqlCredential -AvailabilityGroup $ag.name -Replica $replica.Name
if ($SeedingMode) {
$agreplica.SeedingMode = $SeedingMode
$agreplica.Alter()
}
$agreplica.Refresh()
$SeedingModeReplica = $agreplica.SeedingMode
$primarydb = Get-DbaDatabase -SqlInstance $Primary -SqlCredential $SqlCredential -Database $db.name
if ($SeedingModeReplica -ne 'Automatic') {
try {
if (-not $allbackups[$db]) {
if ($UseLastBackup) {
$allbackups[$db] = Get-DbaBackupHistory -SqlInstance $primarydb.Parent -Database $primarydb.Name -IncludeCopyOnly -Last -EnableException
} else {
$fullbackup = $primarydb | Backup-DbaDatabase -BackupDirectory $SharedPath -Type Full -EnableException
$logbackup = $primarydb | Backup-DbaDatabase -BackupDirectory $SharedPath -Type Log -EnableException
$allbackups[$db] = $fullbackup, $logbackup
}
Write-Message -Level Verbose -Message "Backups still exist on $SharedPath"
}
if ($Pscmdlet.ShouldProcess("$Secondary", "restoring full and log backups of $primarydb from $Primary")) {
# keep going to ensure output is shown even if dbs aren't added well.
$null = $allbackups[$db] | Restore-DbaDatabase -SqlInstance $replica.Parent.Parent -WithReplace -NoRecovery -TrustDbBackupHistory -EnableException
}
} catch {
Stop-Function -Message "Failure" -ErrorRecord $_ -Continue
}
}
$replicadb = Get-DbaAgDatabase -SqlInstance $replica.Parent.Parent -Database $db.Name -AvailabilityGroup $ag.Name #credential of secondary !!
if ($replicadb -and -not ($SeedingModeReplica -eq 'Automatic')) {
if ($Pscmdlet.ShouldProcess($ag.Parent.Name, "Joining availability group $db to $($db.Parent.Name)")) {
$timeout = 1
do {
try {
Write-Message -Level Verbose -Message "Trying to add $($replicadb.Name) to $($replica.Name)"
$timeout++
$replicadb.JoinAvailablityGroup()
$replicadb.Refresh()
Start-Sleep -Seconds 1
} catch {
Stop-Function -Message "Error joining database to availability group" -ErrorRecord $_ -Continue
}
} while (-not $replicadb.IsJoined -and $timeout -lt 10)
if ($replicadb.IsJoined) {
$replicadb
} else {
Stop-Function -Continue -Message "Could not join $($replicadb.Name) to $($replica.Name)"
}
}
} else {
$replicadb
}
}
}
}
}
#ValidationTags#Messaging,FlowControl,Pipeline,CodeStyle#
function Add-DbaAgListener {
<#
.SYNOPSIS
Adds a listener to an availability group on a SQL Server instance.
.DESCRIPTION
Adds a listener to an availability group on a SQL Server instance.
.PARAMETER SqlInstance
The target SQL Server instance or instances. Server version must be SQL Server version 2012 or higher.
.PARAMETER SqlCredential
Login to the SqlInstance instance using alternative credentials. Windows and SQL Authentication supported. Accepts credential objects (Get-Credential)
.PARAMETER AvailabilityGroup
The Availability Group to which a listener will be bestowed upon.
.PARAMETER IPAddress
Sets the IP address of the availability group listener.
.PARAMETER SubnetMask
Sets the subnet IP mask of the availability group listener. Defaults to 255.255.255.0.
.PARAMETER Port
Sets the port number used to communicate with the availability group. Defaults to 1433.
.PARAMETER Dhcp
Indicates whether the listener uses DHCP.
.PARAMETER Passthru
Don't create the listener, just pass thru an object that can be further customized before creation.
.PARAMETER InputObject
Enables piping from Get-DbaAvailabilityGroup
.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: AvailabilityGroup, HA, AG
Author: Chrissy LeMaire (@cl), netnerds.net
Website: https://dbatools.io
Copyright: (c) 2018 by dbatools, licensed under MIT
License: MIT https://opensource.org/licenses/MIT
.LINK
https://dbatools.io/Add-DbaAgListener
.EXAMPLE
PS C:\> Add-DbaAgListener -SqlInstance sql2017 -AvailabilityGroup SharePoint -IPAddress 10.0.20.20
Creates a listener on 10.0.20.20 port 1433 for the SharePoint availability group on sql2017.
.EXAMPLE
PS C:\> Get-DbaAvailabilityGroup -SqlInstance sql2017 -AvailabilityGroup availabilitygroup1 | Add-DbaAgListener -Dhcp
Creates a listener on port 1433 with a dynamic IP for the group1 availability group on sql2017.
#>
[CmdletBinding(SupportsShouldProcess, ConfirmImpact = 'Low')]
param (
[DbaInstanceParameter[]]$SqlInstance,
[PSCredential]$SqlCredential,
[string[]]$AvailabilityGroup,
[ipaddress]$IPAddress,
[ipaddress]$SubnetMask = "255.255.255.0",
[int]$Port = 1433,
[switch]$Dhcp,
[switch]$Passthru,
[parameter(ValueFromPipeline)]
[Microsoft.SqlServer.Management.Smo.AvailabilityGroup[]]$InputObject,
[switch]$EnableException
)
process {
if ((Test-Bound -ParameterName SqlInstance) -and (Test-Bound -Not -ParameterName AvailabilityGroup)) {
Stop-Function -Message "You must specify one or more databases and one or more Availability Groups when using the SqlInstance parameter."
return
}
if ($SqlInstance) {
$InputObject += Get-DbaAvailabilityGroup -SqlInstance $SqlInstance -SqlCredential $SqlCredential -AvailabilityGroup $AvailabilityGroup
}
foreach ($ag in $InputObject) {
if ((Test-Bound -Not -ParameterName Name)) {
$Name = $ag.Name
}
if ($Pscmdlet.ShouldProcess($ag.Parent.Name, "Adding $($IPAddress.IPAddressToString) to $($ag.Name)")) {
try {
$aglistener = New-Object Microsoft.SqlServer.Management.Smo.AvailabilityGroupListener -ArgumentList $ag, $Name
$aglistener.PortNumber = $Port
$listenerip = New-Object Microsoft.SqlServer.Management.Smo.AvailabilityGroupListenerIPAddress -ArgumentList $aglistener
if (Test-Bound -ParameterName IPAddress) {
$listenerip.IPAddress = $IPAddress.IPAddressToString
$listenerip.SubnetMask = $SubnetMask.IPAddressToString
}
$listenerip.IsDHCP = $Dhcp
$aglistener.AvailabilityGroupListenerIPAddresses.Add($listenerip)
if ($Passthru) {
return $aglistener
} else {
# something is up with .net create(), force a stop
Invoke-Create -Object $aglistener
}
} catch {
Stop-Function -Message "Failure" -ErrorRecord $_
}
Get-DbaAgListener -SqlInstance $ag.Parent -AvailabilityGroup $ag.Name -Listener $Name
}
}
}
}
#ValidationTags#Messaging,FlowControl,Pipeline,CodeStyle#
function Add-DbaAgReplica {
<#
.SYNOPSIS
Adds a replica to an availability group on a SQL Server instance.
.DESCRIPTION
Adds a replica to an availability group on a SQL Server instance.
Automatically creates a database mirroring endpoint if required.
.PARAMETER SqlInstance
The target SQL Server instance or instances. Server version must be SQL Server version 2012 or higher.
.PARAMETER SqlCredential
Login to the SqlInstance instance using alternative credentials. Windows and SQL Authentication supported. Accepts credential objects (Get-Credential)
.PARAMETER Name
The name of the replica. Defaults to the SQL Server instance name.
.PARAMETER AvailabilityMode
Sets the availability mode of the availability group replica. Options are: AsynchronousCommit and SynchronousCommit. SynchronousCommit is default.
.PARAMETER FailoverMode
Sets the failover mode of the availability group replica. Options are Automatic and Manual. Automatic is default.
.PARAMETER BackupPriority
Sets the backup priority availability group replica. Default is 50.
.PARAMETER Endpoint
By default, this command will attempt to find a DatabaseMirror endpoint. If one does not exist, it will create it.
If an endpoint must be created, the name "hadr_endpoint" will be used. If an alternative is preferred, use Endpoint.
.PARAMETER Passthru
Don't create the replica, just pass thru an object that can be further customized before creation.
.PARAMETER InputObject
Enables piping from Get-DbaAvailabilityGroup.
.PARAMETER ConnectionModeInPrimaryRole
Specifies the connection intent modes of an Availability Replica in primary role. AllowAllConnections by default.
.PARAMETER ConnectionModeInSecondaryRole
Specifies the connection modes of an Availability Replica in secondary role. AllowAllConnections by default.
.PARAMETER ReadonlyRoutingConnectionUrl
Sets the read only routing connection url for the availability replica.
.PARAMETER SeedingMode
Specifies how the secondary replica will be initially seeded.
Automatic enables direct seeding. This method will seed the secondary replica over the network. This method does not require you to backup and restore a copy of the primary database on the replica.
Manual requires you to create a backup of the database on the primary replica and manually restore that backup on the secondary replica.
.PARAMETER Certificate
Specifies that the endpoint is to authenticate the connection using the certificate specified by certificate_name to establish identity for authorization.
The far endpoint must have a certificate with the public key matching the private key of the specified certificate.
.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: AvailabilityGroup, HA, AG
Author: Chrissy LeMaire (@cl), netnerds.net
Website: https://dbatools.io
Copyright: (c) 2018 by dbatools, licensed under MIT
License: MIT https://opensource.org/licenses/MIT
.LINK
https://dbatools.io/Add-DbaAgReplica
.EXAMPLE
PS C:\> Get-DbaAvailabilityGroup -SqlInstance sql2017a -AvailabilityGroup SharePoint | Add-DbaAgReplica -SqlInstance sql2017b
Adds sql2017b to the SharePoint availability group on sql2017a
.EXAMPLE
PS C:\> Get-DbaAvailabilityGroup -SqlInstance sql2017a -AvailabilityGroup SharePoint | Add-DbaAgReplica -SqlInstance sql2017b -FailoverMode Manual
Adds sql2017b to the SharePoint availability group on sql2017a with a manual failover mode.
#>
[CmdletBinding(SupportsShouldProcess, ConfirmImpact = 'Low')]
param (
[DbaInstanceParameter[]]$SqlInstance,
[PSCredential]$SqlCredential,
[string]$Name,
[ValidateSet('AsynchronousCommit', 'SynchronousCommit')]
[string]$AvailabilityMode = "SynchronousCommit",
[ValidateSet('Automatic', 'Manual', 'External')]
[string]$FailoverMode = "Automatic",
[int]$BackupPriority = 50,
[ValidateSet('AllowAllConnections', 'AllowReadWriteConnections')]
[string]$ConnectionModeInPrimaryRole = 'AllowAllConnections',
[ValidateSet('AllowAllConnections', 'AllowNoConnections', 'AllowReadIntentConnectionsOnly')]
[string]$ConnectionModeInSecondaryRole = 'AllowAllConnections',
[ValidateSet('Automatic', 'Manual')]
[string]$SeedingMode = 'Automatic',
[string]$Endpoint,
[switch]$Passthru,
[string]$ReadonlyRoutingConnectionUrl,
[string]$Certificate,
[parameter(ValueFromPipeline, Mandatory)]
[Microsoft.SqlServer.Management.Smo.AvailabilityGroup]$InputObject,
[switch]$EnableException
)
process {
foreach ($instance in $SqlInstance) {
try {
$server = Connect-SqlInstance -SqlInstance $instance -SqlCredential $SqlCredential -MinimumVersion 11
} catch {
Stop-Function -Message "Failure" -Category ConnectionError -ErrorRecord $_ -Target $instance -Continue
}
if ($Certificate) {
$cert = Get-DbaDbCertificate -SqlInstance $server -Certificate $Certificate
if (-not $cert) {
Stop-Function -Message "Certificate $Certificate does not exist on $instance" -ErrorRecord $_ -Target $Certificate -Continue
}
}
$ep = Get-DbaEndpoint -SqlInstance $server -Type DatabaseMirroring
if (-not $ep) {
if ($Pscmdlet.ShouldProcess($server.Name, "Adding endpoint named $Endpoint to $instance")) {
if (-not $Endpoint) {
$Endpoint = "hadr_endpoint"
}
$ep = New-DbaEndpoint -SqlInstance $server -Name hadr_endpoint -Type DatabaseMirroring -EndpointEncryption Supported -EncryptionAlgorithm Aes -Certificate $Certificate
$null = $ep | Start-DbaEndpoint
}
}
if ((Test-Bound -Not -ParameterName Name)) {
$Name = $server.DomainInstanceName
}
if ($Pscmdlet.ShouldProcess($server.Name, "Creating a replica for $($InputObject.Name) named $Name")) {
try {
$replica = New-Object Microsoft.SqlServer.Management.Smo.AvailabilityReplica -ArgumentList $InputObject, $Name
$replica.EndpointUrl = $ep.Fqdn
$replica.FailoverMode = [Microsoft.SqlServer.Management.Smo.AvailabilityReplicaFailoverMode]::$FailoverMode
$replica.AvailabilityMode = [Microsoft.SqlServer.Management.Smo.AvailabilityReplicaAvailabilityMode]::$AvailabilityMode
if ($server.EngineEdition -ne "Standard") {
$replica.ConnectionModeInPrimaryRole = [Microsoft.SqlServer.Management.Smo.AvailabilityReplicaConnectionModeInPrimaryRole]::$ConnectionModeInPrimaryRole
$replica.ConnectionModeInSecondaryRole = [Microsoft.SqlServer.Management.Smo.AvailabilityReplicaConnectionModeInSecondaryRole]::$ConnectionModeInSecondaryRole
}
$replica.BackupPriority = $BackupPriority
if ($ReadonlyRoutingConnectionUrl) {
$replica.ReadonlyRoutingConnectionUrl = $ReadonlyRoutingConnectionUrl
}
if ($SeedingMode -and $server.VersionMajor -ge 13) {
$replica.SeedingMode = $SeedingMode
}
if ($Passthru) {
return $replica
}
$defaults = 'ComputerName', 'InstanceName', 'SqlInstance', 'AvailabilityGroup', 'Name', 'Role', 'RollupSynchronizationState', 'AvailabilityMode', 'BackupPriority', 'EndpointUrl', 'SessionTimeout', 'FailoverMode', 'ReadonlyRoutingList'
$InputObject.AvailabilityReplicas.Add($replica)
$agreplica = $InputObject.AvailabilityReplicas[$Name]
if ($InputObject.State -eq 'Existing') {
Invoke-Create -Object $replica
$null = Join-DbaAvailabilityGroup -SqlInstance $instance -SqlCredential $SqlCredential -AvailabilityGroup $InputObject.Name
$agreplica.Alter()
}
Add-Member -Force -InputObject $agreplica -MemberType NoteProperty -Name ComputerName -value $agreplica.Parent.ComputerName
Add-Member -Force -InputObject $agreplica -MemberType NoteProperty -Name InstanceName -value $agreplica.Parent.InstanceName
Add-Member -Force -InputObject $agreplica -MemberType NoteProperty -Name SqlInstance -value $agreplica.Parent.SqlInstance
Add-Member -Force -InputObject $agreplica -MemberType NoteProperty -Name AvailabilityGroup -value $agreplica.Parent.Name
Add-Member -Force -InputObject $agreplica -MemberType NoteProperty -Name Replica -value $agreplica.Name # backwards compat
Select-DefaultView -InputObject $agreplica -Property $defaults
} catch {
$msg = $_.Exception.InnerException.InnerException.Message
if (-not $msg) {
$msg = $_
}
Stop-Function -Message $msg -ErrorRecord $_ -Continue
}
}
}
}
}
#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
Tags: RegisteredServer, CMS
Author: Chrissy LeMaire (@cl), netnerds.net
Website: https://dbatools.io
Copyright: (c) 2018 by dbatools, licensed under MIT
License: MIT https://opensource.org/licenses/MIT
.LINK
https://dbatools.io/Add-DbaCmsRegServer
.EXAMPLE
PS C:\> 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
PS C:\> 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
PS C:\> 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
PS C:\> 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 flexibility, look into Import-DbaCmsRegServer which accepts multiple kinds of input and allows you to add reg servers and groups from different CMS.
.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 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: RegisteredServer, CMS
Author: Chrissy LeMaire (@cl), netnerds.net
Website: https://dbatools.io
Copyright: (c) 2018 by dbatools, licensed under MIT
License: MIT https://opensource.org/licenses/MIT
.LINK
https://dbatools.io/Add-DbaCmsRegServerGroup
.EXAMPLE
PS C:\> Add-DbaCmsRegServerGroup -SqlInstance sql2012 -Name HR
Creates a registered server group called HR, in the root of sql2012's CMS
.EXAMPLE
PS C:\> Add-DbaCmsRegServerGroup -SqlInstance sql2012, sql2014 -Name sub-folder -Group HR
Creates a registered server group on sql2012 and sql2014 called sub-folder within the HR group
.EXAMPLE
PS C:\> Get-DbaCmsRegServerGroup -SqlInstance sql2012, sql2014 -Group HR | Add-DbaCmsRegServerGroup -Name sub-folder
Creates a registered server group on sql2012 and sql2014 called sub-folder 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 instance or instances. Defaults to localhost.
.PARAMETER Credential
Allows you to login to $ComputerName using alternative credentials.
.PARAMETER SecurePassword
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) 2018 by dbatools, licensed under MIT
License: MIT https://opensource.org/licenses/MIT
.EXAMPLE
PS C:\> 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
PS C:\> 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 = "Low")]
param (
[Alias("ServerInstance", "SqlServer", "SqlInstance")]
[DbaInstance[]]$ComputerName = $env:COMPUTERNAME,
[PSCredential]$Credential,
[Alias("Password")]
[SecureString]$SecurePassword,
[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 oh well
$bytes = [System.IO.File]::ReadAllBytes($Path)
$Certificate = New-Object System.Security.Cryptography.X509Certificates.X509Certificate2
$Certificate.Import($bytes, $SecurePassword, [System.Security.Cryptography.X509Certificates.X509KeyStorageFlags]::DefaultKeySet)
} catch {
Stop-Function -Message "Can't import certificate." -ErrorRecord $_
return
}
}
#region Remoting Script
$scriptBlock = {
param (
$CertificateData,
[SecureString]$SecurePassword,
$Store,
$Folder
)
$cert = New-Object System.Security.Cryptography.X509Certificates.X509Certificate2
$cert.Import($CertificateData, $SecurePassword, [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, $SecurePassword)
} 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, $SecurePassword, $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-DbaDbMirrorMonitor {
<#
.SYNOPSIS
Creates a database mirroring monitor job that periodically updates the mirroring status for every mirrored database on the server instance.
.DESCRIPTION
Creates a database mirroring monitor job that periodically updates the mirroring status for every mirrored database on the server instance.
Basically executes sp_dbmmonitoraddmonitoring.
.PARAMETER SqlInstance
The target SQL Server instance
.PARAMETER SqlCredential
Login to the target instance using alternate Windows or SQL Login Authentication. Accepts credential objects (Get-Credential).
.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: Mirror, HA
Author: Chrissy LeMaire (@cl), netnerds.net
Website: https://dbatools.io
Copyright: (c) 2018 by dbatools, licensed under MIT
License: MIT https://opensource.org/licenses/MIT
.LINK
https://dbatools.io/Add-DbaDbMirrorMonitor
.EXAMPLE
PS C:\> Add-DbaDbMirrorMonitor -SqlInstance sql2008, sql2012
Creates a database mirroring monitor job that periodically updates the mirroring status for every mirrored database on sql2008 and sql2012.
#>
[CmdletBinding(SupportsShouldProcess, ConfirmImpact = 'Low')]
param (
[parameter(Mandatory, ValueFromPipeline)]
[DbaInstanceParameter[]]$SqlInstance,
[PSCredential]$SqlCredential,
[switch]$EnableException
)
process {
foreach ($instance in $SqlInstance) {
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, "add mirror monitoring")) {
try {
$server.Query("msdb.dbo.sp_dbmmonitoraddmonitoring")
[pscustomobject]@{
ComputerName = $server.ComputerName
InstanceName = $server.ServiceName
SqlInstance = $server.DomainInstanceName
MonitorStatus = "Added"
}
} catch {
Stop-Function -Message "Failure" -ErrorRecord $_ -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) 2018 by dbatools, licensed under MIT
License: MIT https://opensource.org/licenses/MIT
.LINK
https://dbatools.io/Add-DbaPfDataCollectorCounter
.EXAMPLE
PS C:\> 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
PS C:\> 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 does not 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).
Passing in NUL as the BackupFileName will backup to the NUL: device
.PARAMETER TimeStampFormat
By default the command timestamps backups using the format yyyyMMddHHmm. Using this parameter this can be overridden. The timestamp format should be defined using the Get-Date formats, illegal formats will cause an error to be thrown
.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 ReplaceInName
If this switch is set, the following list of strings will be replaced in the BackupFileName and BackupDirectory strings:
instancename - will be replaced with the instance Name
servername - will be replaced with the server name
dbname - will be replaced with the database name
timestamp - will be replaced with the timestamp (either the default, or the format provided)
backuptype - will be replaced with Full, Log or Differential as appropriate
.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 base container of an Azure Storage account to write backups to.
If specified, the only other parameters than can be used are "CopyOnly", "Type", "CompressBackup", "Checksum", "Verify", "AzureCredential", "CreateFolder".
.PARAMETER AzureCredential
The name of the credential on the SQL instance that can write to the AzureBaseUrl, only needed if using Storage access keys
If using SAS credentials, the command will look for a credential with a name matching the AzureBaseUrl
.PARAMETER NoRecovery
This is passed in to perform a tail log backup if needed
.PARAMETER BuildPath
By default this command will not attempt to create missing paths, this switch will change the behaviour so that it will
.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 cannot 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) 2018 by dbatools, licensed under MIT
License: MIT https://opensource.org/licenses/MIT
.EXAMPLE
PS C:\> 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 default backup directory.
.EXAMPLE
PS C:\> Backup-DbaDatabase -SqlInstance sql2016 -BackupDirectory C:\temp -Database AdventureWorks2014 -Type Full
Backs up AdventureWorks2014 to sql2016 C:\temp folder.
.EXAMPLE
PS C:\> 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.
.EXAMPLE
PS C:\> Backup-DbaDatabase -SqlInstance sql2016 -AzureBaseUrl https://dbatoolsaz.blob.core.windows.net/azbackups/ -Type Full
Performs a full backup of all databases on the sql2016 instance to the https://dbatoolsaz.blob.core.windows.net/azbackups/ container on Azure blog storage using the Shared Access Signature sql credential "https://dbatoolsaz.blob.core.windows.net/azbackups" registered on the sql2016 instance.
.EXAMPLE
PS C:\> Backup-Dbadatabase -SqlInstance Server1\Prod -Database db1 -BackupDirectory \\filestore\backups\servername\instancename\dbname\backuptype -Type Full -ReplaceInName
Performs a full backup of db1 into the folder \\filestore\backups\server1\prod\db1
.EXAMPLE
PS C:\> Backup-Dbadatabase -SqlInstance Server1\Prod -BackupDirectory \\filestore\backups\servername\instancename\dbname\backuptype -BackupFileName dbname-backuptype-timestamp.trn -Type Log -ReplaceInName
Performs a log backup for every database. For the database db1 this would results in backup files in \\filestore\backups\server1\prod\db1\Log\db1-log-31102018.trn
.EXAMPLE
PS C:\> Backup-DbaDatabase -SqlInstance Sql2017 -Database master -BackupFileName NUL
Performs a backup of master, but sends the output to the NUL device (ie; throws it away)
#>
[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]$ReplaceInName,
[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,
[string]$TimeStampFormat,
[switch]$IgnoreFileChecks,
[switch]$OutputScriptOnly,
[Alias('Silent')]
[switch]$EnableException
)
begin {
if (-not (Test-Bound 'TimeStampFormat')) {
Write-Message -Message 'Setting Default timestampformat' -Level Verbose
$TimeStampFormat = "yyyyMMddHHmm"
}
if ($SqlInstance.length -ne 0) {
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' -Level Verbose
$BackupDirectory = (Get-DbaDefaultPath -SqlInstance $server).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 '' -and $True -ne $ReplaceInFile) {
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) {
$AzureBaseUrl = $AzureBaseUrl.Trim("/")
if ('' -ne $AzureCredential) {
Write-Message -Message "Azure Credential name passed in, will proceed assuming it's value" -Level Verbose
$FileCount = 1
} else {
Write-Message -Message "AzureUrl and no credential, testing for SAS credential"
if (Get-DbaCredential -SqlInstance $server -Name $AzureBaseUrl) {
Write-Message -Message "Found a SAS backup credental" -Level Verbose
} else {
Stop-Function -Message "You must provide the credential name for the Azure Storage Account"
return
}
}
$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 ($db in $InputObject) {
$ProgressId = Get-Random
$failures = @()
$dbname = $db.Name
$server = $db.Parent
if ($dbname -eq "tempdb") {
Stop-Function -Message "Backing up tempdb not supported" -Continue
}
if ('Normal' -notin ($db.Status -split ',')) {
Stop-Function -Message "Database status not Normal. $dbname skipped." -Continue
}
if ($db.DatabaseSnapshotBaseName) {
Stop-Function -Message "Backing up snapshots not supported. $dbname skipped." -Continue
}
Write-Message -Level Verbose -Message "Backup database $db"
if ($null -eq $db.RecoveryModel) {
$db.RecoveryModel = $server.Databases[$db.Name].RecoveryModel
Write-Message -Level Verbose -Message "$dbname is in $($db.RecoveryModel) recovery model"
}
# Fixes one-off cases of StackOverflowException crashes, see issue 1481
$dbRecovery = $db.RecoveryModel.ToString()
if ($dbRecovery -eq 'Simple' -and $Type -eq 'Log') {
$failreason = "$db is in simple recovery mode, cannot take log backup"
$failures += $failreason
Write-Message -Level Warning -Message "$failreason"
}
$lastfull = $db.Refresh().LastBackupDate.Year
if ($Type -notin @("Database", "Full") -and $lastfull -eq 1) {
$failreason = "$db 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 = $db.Name
$Suffix = "bak"
if ($CompressBackup) {
if ($db.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'
$gbhSwitch = @{'LastDiff' = $true}
}
$Backup.NoRecovery = $false
if ($Type -eq "Log") {
Write-Message -Level VeryVerbose -Message "Creating log backup"
$Suffix = "trn"
$OutputType = 'Log'
$SMOBackupType = 'Log'
$Backup.NoRecovery = $NoRecovery
$gbhSwitch = @{'LastLog' = $true}
}
if ($Type -in 'Full', 'Database') {
Write-Message -Level VeryVerbose -Message "Creating full backup"
$SMOBackupType = "Database"
$OutputType = 'Full'
$gbhSwitch = @{'LastFull' = $true}
}
$backup.CopyOnly = $copyonly
$backup.Action = $SMOBackupType
if ('' -ne $AzureBaseUrl -and $null -ne $AzureCredential) {
$backup.CredentialName = $AzureCredential
}
Write-Message -Level Verbose -Message "Building file name"
$BackupFinalName = ''
$FinalBackupPath = @()
$timestamp = Get-Date -Format $TimeStampFormat
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 {
Write-Message -Level VeryVerbose -Message "Setting filename - $timestamp"
$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 ($True -eq $ReplaceInName) {
for ($i = 0; $i -lt $FinalBackupPath.count; $i++) {
$FinalBackupPath[$i] = $FinalBackupPath[$i] -replace ('dbname', $dbname)
$FinalBackupPath[$i] = $FinalBackupPath[$i] -replace ('instancename', $SqlInstance.InstanceName)
$FinalBackupPath[$i] = $FinalBackupPath[$i] -replace ('servername', $SqlInstance.ComputerName)
$FinalBackupPath[$i] = $FinalBackupPath[$i] -replace ('timestamp', $timestamp)
$FinalBackupPath[$i] = $FinalBackupPath[$i] -replace ('backuptype', $outputType)
}
}
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 @gbhSwitch -IncludeCopyOnly -RecoveryFork $db.RecoveryForkGuid | Sort-Object -Property End -Descending | Select-Object -First 1
}
$Verified = $false
if ($Verify) {
$verifiedresult = [PSCustomObject]@{
ComputerName = $server.ComputerName
InstanceName = $server.ServiceName
SqlInstance = $server.DomainInstanceName
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" -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
}
}
}
#ValidationTags#Messaging,FlowControl,Pipeline,CodeStyle#
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
The target SQL Server instance or instances. 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
Enables piping from Get-DbaDbCertificate
.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
Tags: Migration, Certificate
Author: Jess Pomfret (@jpomfret)
Website: https://dbatools.io
Copyright: (c) 2018 by dbatools, licensed under MIT
License: MIT https://opensource.org/licenses/MIT
.EXAMPLE
PS C:\> Backup-DbaDbCertificate -SqlInstance Server1
Exports all the certificates on the specified SQL Server to the default data path for the instance.
.EXAMPLE
PS C:\> $cred = Get-Credential sqladmin
PS C:\> 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
PS C:\> 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
PS C:\> 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
PS C:\> 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
PS C:\> 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
PS C:\> $EncryptionPassword = ConvertTo-SecureString -AsPlainText "GoodPass1234!!" -force
PS C:\> $DecryptionPassword = ConvertTo-SecureString -AsPlainText "Password4567!!" -force
PS C:\> 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
PS C:\> Backup-DbaDbCertificate -SqlInstance Server1 -Path \\Server1\Certificates
Exports all certificates on the specified SQL Server to the specified path.
.EXAMPLE
PS C:\> 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
PS C:\> Get-DbaDbCertificate -SqlInstance sql2016 | Backup-DbaDbCertificate
Exports all certificates found on sql2016 to the default data directory.
#>
[CmdletBinding(DefaultParameterSetName = "Default", SupportsShouldProcess, ConfirmImpact = 'Low')]
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,
[Security.SecureString]$EncryptionPassword,
[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 (-not $EncryptionPassword -and $DecryptionPassword) {
Stop-Function -Message "If you specify a 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 }
if ($SqlInstance) {
$InputObject += Get-DbaDbCertificate -SqlInstance $SqlInstance -SqlCredential $SqlCredential -Database $Database -ExcludeDatabase $ExcludeDatabase
}
foreach ($cert in $InputObject) {
if ($cert.Name.StartsWith("##")) {
Write-Message -Level Output -Message "Skipping system cert $cert"
} else {
export-cert $cert
}
}
}
}
#ValidationTags#Messaging,FlowControl,Pipeline,CodeStyle#
function Backup-DbaDbMasterKey {
<#
.SYNOPSIS
Backs up specified database master key.
.DESCRIPTION
Backs up specified database master key.
.PARAMETER SqlInstance
The target SQL Server instance or instances.
.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 SecurePassword
The password to encrypt the exported key. This must be a SecureString.
.PARAMETER InputObject
Database object piped in from Get-DbaDatabase
.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: Certificate, Database
Author: Chrissy LeMaire (@cl), netnerds.net
Website: https://dbatools.io
Copyright: (c) 2018 by dbatools, licensed under MIT
License: MIT https://opensource.org/licenses/MIT
.EXAMPLE
PS C:\> Backup-DbaDbMasterKey -SqlInstance server1\sql2016
```
ComputerName : SERVER1
InstanceName : SQL2016
SqlInstance : SERVER1\SQL2016
Database : master
Filename : E:\MSSQL13.SQL2016\MSSQL\Backup\server1$sql2016-master-20170614162311.key
Status : Success
```
Prompts for export password, then logs into server1\sql2016 with Windows credentials then backs up all database keys to the default backup directory.
.EXAMPLE
PS C:\> 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(SupportsShouldProcess, ConfirmImpact = 'Low')]
param (
[DbaInstanceParameter[]]$SqlInstance,
[PSCredential]$SqlCredential,
[PSCredential]$Credential,
[string[]]$Database,
[string[]]$ExcludeDatabase,
[Alias("Password")]
[Security.SecureString]$SecurePassword,
[string]$Path,
[parameter(ValueFromPipeline)]
[Microsoft.SqlServer.Management.Smo.Database[]]$InputObject,
[switch]$EnableException
)
begin {
if ($Credential) {
$SecurePassword = $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 (-not $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 (-not $SecurePassword -and -not $Credential) {
$SecurePassword = Read-Host -AsSecureString -Prompt "You must enter Service Key password for $instance"
$SecurePassword2 = Read-Host -AsSecureString -Prompt "Type the password again"
if (([System.Runtime.InteropServices.marshal]::PtrToStringAuto([System.Runtime.InteropServices.marshal]::SecureStringToBSTR($SecurePassword))) -ne ([System.Runtime.InteropServices.marshal]::PtrToStringAuto([System.Runtime.InteropServices.marshal]::SecureStringToBSTR($SecurePassword2)))) {
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"
if ($Pscmdlet.ShouldProcess($instance, "Backing up master key to $filename")) {
try {
$masterkey.Export($filename, [System.Runtime.InteropServices.marshal]::PtrToStringAuto([System.Runtime.InteropServices.marshal]::SecureStringToBSTR($SecurePassword)))
$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) 2018 by dbatools, licensed under MIT
License: MIT https://opensource.org/licenses/MIT
.LINK
https://dbatools.io/Clear-DbaConnectionPool
.EXAMPLE
PS C:\> Clear-DbaConnectionPool
Clears all local connection pools.
.EXAMPLE
PS C:\> 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-DbaLatchStatistics {
<#
.SYNOPSIS
Clears Latch Statistics
.DESCRIPTION
Reset the aggregated statistics - basically just executes DBCC SQLPERF (N'sys.dm_os_latch_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: LatchStatistic, Waits
Author: Patrick Flynn (@sqllensman)
Website: https://dbatools.io
Copyright: (c) 2018 by dbatools, licensed under MIT
License: MIT https://opensource.org/licenses/MIT
.LINK
https://dbatools.io/Clear-DbaLatchStatistics
.EXAMPLE
PS C:\> Clear-DbaLatchStatistics -SqlInstance sql2008, sqlserver2012
After confirmation, clears latch statistics on servers sql2008 and sqlserver2012
.EXAMPLE
PS C:\> Clear-DbaLatchStatistics -SqlInstance sql2008, sqlserver2012 -Confirm:$false
Clears latch statistics on servers sql2008 and sqlserver2012, without prompting
.EXAMPLE
PS C:\> 'sql2008','sqlserver2012' | Clear-DbaLatchStatistics
After confirmation, clears latch statistics on servers sql2008 and sqlserver2012
.EXAMPLE
PS C:\> $cred = Get-Credential sqladmin
PS C:\> Clear-DbaLatchStatistics -SqlInstance sql2008 -SqlCredential $cred
Connects using sqladmin credential and clears latch statistics on servers sql2008 and sqlserver2012
#>
[CmdletBinding(ConfirmImpact = 'High', SupportsShouldProcess)]
[Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUseSingularNouns", "", Justification = "Singular Noun doesn't make sense")]
param (
[parameter(Mandatory, ValueFromPipeline)]
[Alias("ServerInstance", "SqlServer", "SqlServers")]
[DbaInstance[]]$SqlInstance,
[PSCredential]$SqlCredential,
[Alias('Silent')]
[switch]$EnableException
)
process {
foreach ($instance in $SqlInstance) {
Write-Message -Level Verbose -Message "Attempting to connect 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_latch_stats")) {
try {
$server.Query("DBCC SQLPERF (N'sys.dm_os_latch_stats' , CLEAR);")
$status = "Success"
} catch {
$status = $_.Exception
}
[PSCustomObject]@{
ComputerName = $server.NetName
InstanceName = $server.ServiceName
SqlInstance = $server.DomainInstanceName
Status = $status
}
}
}
}
}
function Clear-DbaPlanCache {
<#
.SYNOPSIS
Removes ad-hoc and prepared plan caches is single use plans are over defined threshold.
.DESCRIPTION
Checks ad-hoc 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 or instances.
.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
Website: https://dbatools.io
Copyright: (c) 2018 by dbatools, licensed under MIT
License: MIT https://opensource.org/licenses/MIT
.LINK
https://dbatools.io/Clear-DbaPlanCache
.EXAMPLE
PS C:\> Clear-DbaPlanCache -SqlInstance sql2017 -Threshold 200
Logs into the SQL Server instance "sql2017" and removes plan caches if over 200 MB.
.EXAMPLE
PS C:\> 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.
.EXAMPLE
PS C:\> Find-DbaInstance -ComputerName localhost | Get-DbaPlanCache | Clear-DbaPlanCache -Threshold 200
Scans localhost for instances using the browser service, traverses all instances and gets the plan cache for each, clears them out if they are above 200 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")) {
try {
$server = Connect-SqlInstance -SqlInstance $result.SqlInstance -SqlCredential $SqlCredential
} catch {
Stop-Function -Message "Failure" -Category ConnectionError -ErrorRecord $_ -Target $instance -Continue
}
$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
The target SQL Server instance or instances.
.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, Waits
Author: Chrissy LeMaire (@cl)
Website: https://dbatools.io
Copyright: (c) 2018 by dbatools, licensed under MIT
License: MIT https://opensource.org/licenses/MIT
.LINK
https://dbatools.io/Clear-DbaWaitStatistics
.EXAMPLE
PS C:\> Clear-DbaWaitStatistics -SqlInstance sql2008, sqlserver2012
After confirmation, clears wait stats on servers sql2008 and sqlserver2012
.EXAMPLE
PS C:\> Clear-DbaWaitStatistics -SqlInstance sql2008, sqlserver2012 -Confirm:$false
Clears wait stats on servers sql2008 and sqlserver2012, without prompting
#>
[CmdletBinding(ConfirmImpact = 'High', SupportsShouldProcess)]
[Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUseSingularNouns", "", Justification = "Singular Noun doesn't make sense")]
param (
[parameter(Mandatory, ValueFromPipeline)]
[Alias("ServerInstance", "SqlServer", "SqlServers")]
[DbaInstance[]]$SqlInstance,
[PSCredential]$SqlCredential,
[Alias('Silent')]
[switch]$EnableException
)
process {
foreach ($instance in $SqlInstance) {
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
The target SQL Server instance or instances. This can be a collection and receive pipeline input to allow the function to be executed against multiple SQL Server instances.
.PARAMETER SqlCredential
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 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.
.PARAMETER DisableException
By default in most of our commands, when something goes wrong we try to catch it, interpret it and give you a friendly warning message.
This command, however, gifts you with "sea of red" exceptions, by default, because it is useful for advanced scripting.
Using this switch turns our "nice by default" feature on which makes errors into pretty warnings.
.NOTES
Tags: Connect, Connection
Author: Chrissy LeMaire (@cl), netnerds.net
Website: https://dbatools.io
Copyright: (c) 2018 by dbatools, licensed under MIT
License: MIT https://opensource.org/licenses/MIT
.LINK
https://dbatools.io/Connect-DbaInstance
.EXAMPLE
PS C:\> Connect-DbaInstance -SqlInstance sql2014
Creates an SMO Server object that connects using Windows Authentication
.EXAMPLE
PS C:\> $wincred = Get-Credential ad\sqladmin
PS C:\> Connect-DbaInstance -SqlInstance sql2014 -SqlCredential $wincred
Creates an SMO Server object that connects using alternative Windows credentials
.EXAMPLE
PS C:\> $sqlcred = Get-Credential sqladmin
PS C:\> $server = Connect-DbaInstance -SqlInstance sql2014 -SqlCredential $sqlcred
Login to sql2014 as SQL login sqladmin.
.EXAMPLE
PS C:\> $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
PS C:\> $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
PS C:\> $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
PS C:\> $server = Connect-DbaInstance sql2016 -ApplicationIntent ReadOnly
Connects with ReadOnly ApplicationIntent.
#>
[CmdletBinding()]
param (
[Parameter(Mandatory, ValueFromPipeline)]
[Alias("ServerInstance", "SqlServer")]
[DbaInstanceParameter[]]$SqlInstance,
[Alias("Credential")]
[PSCredential]$SqlCredential,
[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,
[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,
[switch]$DisableException
)
begin {
if ($DisableException) {
$EnableException = $false
} else {
$EnableException = $true
}
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) {
#region Safely convert input into instance parameters
if ($instance.GetType() -eq [Sqlcollaborative.Dbatools.Parameter.DbaInstanceParameter]) {
[DbaInstanceParameter]$ConvertedSqlInstance = $instance
if ($ConvertedSqlInstance.Type -like "SqlConnection") {
[DbaInstanceParameter]$ConvertedSqlInstance = New-Object Microsoft.SqlServer.Management.Smo.Server($ConvertedSqlInstance.InputObject)
}
} else {
[DbaInstanceParameter]$ConvertedSqlInstance = [DbaInstanceParameter]($instance | Select-Object -First 1)
if ($instance.Count -gt 1) {
Write-Message -Level Warning -EnableException $true -Message "More than on server was specified when calling Connect-SqlInstance from $((Get-PSCallStack)[1].Command)"
}
}
#endregion Safely convert input into instance parameters
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) {
if (-not $server.NetName -or $SqlInstance -match '\.') {
$parsedcomputername = $ConvertedSqlInstance.ComputerName
} else {
$parsedcomputername = $server.NetName
}
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 '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
}
if (Test-Bound -ParameterName 'ApplicationIntent') {
$server.ConnectionContext.ApplicationIntent = $ApplicationIntent
}
$connstring = $server.ConnectionContext.ConnectionString
if (Test-Bound -ParameterName 'MultiSubnetFailover') {
$connstring = "$connstring;MultiSubnetFailover=True"
}
if (Test-Bound -ParameterName 'FailoverPartner') {
$connstring = "$connstring;Failover Partner=$FailoverPartner"
}
if ($connstring -ne $server.ConnectionContext.ConnectionString) {
$server.ConnectionContext.ConnectionString = $connstring
}
try {
if ($null -ne $SqlCredential.UserName) {
$username = ($SqlCredential.UserName).TrimStart("\")
# support both ad\username and username@ad
if ($username -like "*\*" -or $username -like "*@*") {
if ($username -like "*\*") {
$domain, $login = $username.Split("\")
$authtype = "Windows Authentication with Credential"
if ($domain) {
$formatteduser = "$login@$domain"
} else {
$formatteduser = $username.Split("\")[1]
}
} else {
$formatteduser = $SqlCredential.UserName
}
$server.ConnectionContext.LoginSecure = $true
$server.ConnectionContext.ConnectAsUser = $true
$server.ConnectionContext.ConnectAsUserName = $formatteduser
$server.ConnectionContext.ConnectAsUserPassword = ($SqlCredential).GetNetworkCredential().Password
} else {
$authtype = "SQL Authentication"
$server.ConnectionContext.LoginSecure = $false
$server.ConnectionContext.set_Login($username)
$server.ConnectionContext.set_SecurePassword($SqlCredential.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.Information.Version
if ($server.ConnectionContext.IsOpen -eq $false) {
$server.ConnectionContext.Connect()
}
} else {
$server.ConnectionContext.SqlConnectionObject.Open()
}
} catch {
$originalException = $_.Exception
try {
$message = $originalException.InnerException.InnerException.ToString()
} catch {
$message = $originalException.ToString()
}
$message = ($message -Split '-->')[0]
$message = ($message -Split 'at System.Data.SqlClient')[0]
$message = ($message -Split 'at System.Data.ProviderBase')[0]
Stop-Function -Message "Can't connect to $instance" -ErrorRecord $_ -Continue
}
}
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) {
if (-not $server.NetName -or $SqlInstance -match '\.') {
$parsedcomputername = $ConvertedSqlInstance.ComputerName
} else {
$parsedcomputername = $server.NetName
}
Add-Member -InputObject $server -NotePropertyName ComputerName -NotePropertyValue $parsedcomputername -Force
}
}
$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 data table 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
Author: Chrissy LeMaire (@cl), netnerds.net
Website: https://dbatools.io/
Copyright: (c) 2018 by dbatools, licensed under MIT
License: MIT https://opensource.org/licenses/MIT
.LINK
https://dbatools.io/ConvertTo-DbaDataTable
.OUTPUTS
System.Object[]
.EXAMPLE
PS C:\> Get-Service | ConvertTo-DbaDataTable
Creates a DataTable from the output of Get-Service.
.EXAMPLE
PS C:\> ConvertTo-DbaDataTable -InputObject $csv.cheesetypes
Creates a DataTable from the CSV object $csv.cheesetypes.
.EXAMPLE
PS C:\> $dblist | ConvertTo-DbaDataTable
Creates a DataTable from the $dblist object passed in via pipeline.
.EXAMPLE
PS C:\> Get-Process | ConvertTo-DbaDataTable -TimeSpanType TotalSeconds
Creates a DataTable with the running processes and converts any TimeSpan property to TotalSeconds.
#>
[CmdletBinding()]
[Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUseOutputTypeCorrectly", "", Justification = "PSSA Rule Ignored by BOH")]
[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 don't 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 {
Stop-Function -Message "Failed to add property $($property.Name) from $object" -ErrorRecord $_ -Target $object
}
} else {
Stop-Function -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 dbatools 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)
Website: https://dbatools.io
Copyright: (c) 2018 by dbatools, licensed under MIT
License: MIT https://opensource.org/licenses/MIT
Dependency: ConvertTo-JsDate, Convert-DbaTimelineStatusColor
.LINK
https://dbatools.io/ConvertTo-DbaTimeline
.EXAMPLE
PS C:\> Get-DbaAgentJobHistory -SqlInstance sql-1 -StartDate '2018-08-13 00:00' -EndDate '2018-08-13 23:59' -ExcludeJobSteps | 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
PS C:\> 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
PS C:\> $messageParameters = @{
>> Subject = "Backup history for sql2017 and sql2016"
>> Body = Get-DbaBackupHistory -SqlInstance sql2017, sql2016 -Since '2018-08-13 00:00' | ConvertTo-DbaTimeline | Out-String
>> From = "[email protected]"
>> To = "[email protected]"
>> SmtpServer = "smtp.ad.local"
>> }
>>
PS C:\> 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()]
[Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUseOutputTypeCorrectly", "", Justification = "PSSA Rule Ignored by BOH")]
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 {
# 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 overlapping 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) 2018 by dbatools, licensed under MIT
License: MIT https://opensource.org/licenses/MIT
.EXAMPLE
PS C:\> 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
PS C:\> 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
PS C:\> 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
Website: https://dbatools.io
Copyright: (c) 2018 by dbatools, licensed under MIT
License: MIT https://opensource.org/licenses/MIT
Requires: sysadmin access on SQL Servers
.LINK
https://dbatools.io/Copy-DbaAgentAlert
.EXAMPLE
PS C:\> 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
PS C:\> 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 an alert with the same name exists on sqlcluster, it will be dropped and recreated because -Force was used.
.EXAMPLE
PS C:\> Copy-DbaAgentAlert -Source sqlserver2014a -Destination sqlcluster -WhatIf -Force
Shows what would happen if the command were executed using force.
#>
[cmdletbinding(DefaultParameterSetName = "Default", SupportsShouldProcess)]
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 {
$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 {
$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 on destination"
$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)
}
# can't 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
}
}
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) 2018 by dbatools, licensed under MIT
License: MIT https://opensource.org/licenses/MIT
.LINK
https://dbatools.io/Copy-DbaAgentJob
.EXAMPLE
PS C:\> 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
PS C:\> 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
PS C:\> Copy-DbaAgentJob -Source sqlserver2014a -Destination sqlcluster -WhatIf -Force
Shows what would happen if the command were executed using force.
.EXAMPLE
PS C:\> Get-DbaAgentJob -SqlInstance sqlserver2014a | Where-Object Category -eq "Report Server" | ForEach-Object {Copy-DbaAgentJob -Source $_.SqlInstance -Job $_.Name -Destination sqlserver2014b}
Copies all SSRS jobs (subscriptions) from AlwaysOn Primary SQL instance sqlserver2014a to AlwaysOn Secondary SQL instance sqlserver2014b
#>
[cmdletbinding(DefaultParameterSetName = "Default", SupportsShouldProcess)]
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 {
$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 {
$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 | Where-Object ProxyName).ProxyName
$missingProxy = $proxyNames | Where-Object { $destServer.JobServer.ProxyAccounts.Name -notcontains $_ }
if ($missingProxy -and $proxyNames) {
if ($Pscmdlet.ShouldProcess($destinstance, "Proxy Account(s) $missingProxy 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 $missingProxy"
$copyJobStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
Write-Message -Level Verbose -Message "Proxy Account(s) $missingProxy 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 = "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
}
}
#ValidationTags#Messaging#
function Copy-DbaAgentJobCategory {
<#
.SYNOPSIS
Copy-DbaAgentJobCategory 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
Website: https://dbatools.io
Copyright: (c) 2018 by dbatools, licensed under MIT
License: MIT https://opensource.org/licenses/MIT
Requires: sysadmin access on SQL Servers
.LINK
https://dbatools.io/Copy-DbaAgentJobCategory
.EXAMPLE
PS C:\> Copy-DbaAgentJobCategory -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
PS C:\> Copy-DbaAgentJobCategory -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 an operator category with the same name exists on sqlcluster, it will be dropped and recreated because -Force was used.
.EXAMPLE
PS C:\> Copy-DbaAgentJobCategory -Source sqlserver2014a -Destination sqlcluster -WhatIf -Force
Shows what would happen if the command were executed using force.
#>
[CmdletBinding(DefaultParameterSetName = "Default", SupportsShouldProcess)]
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 {
Test-DbaDeprecation -DeprecatedOn "1.0.0" -Alias Copy-DbaAgentCategory
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 on destination"
$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)]
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 on destination"
$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)]
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 on destination"
$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 {
$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 {
$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-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
Website: https://dbatools.io
Copyright: (c) 2018 by dbatools, licensed under MIT
License: MIT https://opensource.org/licenses/MIT
Requires: sysadmin access on SQL Servers
.LINK
https://dbatools.io/Copy-DbaAgentOperator
.EXAMPLE
PS C:\> 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
PS C:\> 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
PS C:\> Copy-DbaAgentOperator -Source sqlserver2014a -Destination sqlcluster -WhatIf -Force
Shows what would happen if the command were executed using force.
#>
[CmdletBinding(DefaultParameterSetName = "Default", SupportsShouldProcess)]
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 {
$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 {
$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 on destination"
$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-DbaAgentProxy {
<#
.SYNOPSIS
Copy-DbaAgentProxy 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
Website: https://dbatools.io
Copyright: (c) 2018 by dbatools, licensed under MIT
License: MIT https://opensource.org/licenses/MIT
Requires: sysadmin access on SQL Servers
.LINK
https://dbatools.io/Copy-DbaAgentProxy
.EXAMPLE
PS C:\> Copy-DbaAgentProxy -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
PS C:\> Copy-DbaAgentProxy -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
PS C:\> Copy-DbaAgentProxy -Source sqlserver2014a -Destination sqlcluster -WhatIf -Force
Shows what would happen if the command were executed using force.
#>
[CmdletBinding(DefaultParameterSetName = "Default", SupportsShouldProcess)]
param (
[parameter(Mandatory)]
[DbaInstanceParameter]$Source,
[PSCredential]$SourceSqlCredential,
[parameter(Mandatory)]
[DbaInstanceParameter[]]$Destination,
[PSCredential]$DestinationSqlCredential,
[string[]]$ProxyAccount,
[string[]]$ExcludeProxyAccount,
[switch]$Force,
[Alias('Silent')]
[switch]$EnableException
)
begin {
Test-DbaDeprecation -DeprecatedOn "1.0.0" -Alias Copy-DbaAgentProxyAccount
try {
$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 {
$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)
}
$credentialName = $account.CredentialName
$copyAgentProxyAccountStatus.Name = $proxyName
$copyAgentProxyAccountStatus.Type = "Credential"
# Proxy accounts rely on Credential accounts
if (-not $CredentialName) {
$copyAgentProxyAccountStatus.Status = "Skipped"
$copyAgentProxyAccountStatus.Notes = "Skipping migration of $proxyName due to misconfigured (empty) credential name"
$copyAgentProxyAccountStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
Write-Message -Level Verbose -Message "Skipping migration of $proxyName due to misconfigured (empty) credential name"
continue
}
try {
$credentialtest = $destServer.Credentials[$CredentialName]
} catch {
#here to avoid an empty catch
$null = 1
}
if ($null -eq $credentialtest) {
$copyAgentProxyAccountStatus.Status = "Skipped"
$copyAgentProxyAccountStatus.Notes = "Associated credential account, $CredentialName, does not exist on $destinstance"
$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"
continue
}
if ($destProxyAccounts.Name -contains $proxyName) {
$copyAgentProxyAccountStatus.Name = $proxyName
$copyAgentProxyAccountStatus.Type = "ProxyAccount"
if ($force -eq $false) {
$copyAgentProxyAccountStatus.Status = "Skipped"
$copyAgentProxyAccountStatus.Notes = "Already exists on destination"
$copyAgentProxyAccountStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
Stop-Function -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-DbaAgentSchedule {
<#
.SYNOPSIS
Copy-DbaAgentSchedule 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
Website: https://dbatools.io
Copyright: (c) 2018 by dbatools, licensed under MIT
License: MIT https://opensource.org/licenses/MIT
Requires: sysadmin access on SQL Servers
.LINK
https://dbatools.io/Copy-DbaAgentSchedule
.EXAMPLE
PS C:\> Copy-DbaAgentSchedule -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
PS C:\> Copy-DbaAgentSchedule -Source sqlserver2014a -Destination sqlcluster -WhatIf -Force
Shows what would happen if the command were executed using force.
#>
[CmdletBinding(DefaultParameterSetName = "Default", SupportsShouldProcess)]
param (
[parameter(Mandatory)]
[DbaInstanceParameter]$Source,
[PSCredential]
$SourceSqlCredential,
[parameter(Mandatory)]
[DbaInstanceParameter[]]$Destination,
[PSCredential]
$DestinationSqlCredential,
[switch]$Force,
[Alias('Silent')]
[switch]$EnableException
)
begin {
Test-DbaDeprecation -DeprecatedOn "1.0.0" -Alias Copy-DbaAgentSharedSchedule
try {
$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 {
$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 on destination"
$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-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
Website: https://dbatools.io
Copyright: (c) 2018 by dbatools, licensed under MIT
License: MIT https://opensource.org/licenses/MIT
Requires: sysadmin access on SQL Servers
.LINK
https://dbatools.io/Copy-DbaAgentServer
.EXAMPLE
PS C:\> 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
PS C:\> 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
PS C:\> Copy-DbaAgentServer -Source sqlserver2014a -Destination sqlcluster -WhatIf
Shows what would happen if the command were executed.
#>
[cmdletbinding(SupportsShouldProcess)]
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 {
$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 {
$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-DbaAgentJobCategory -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-DbaAgentProxy -Source $sourceServer -Destination $destServer -Force:$force
$destServer.JobServer.ProxyAccounts.Refresh()
Copy-DbaAgentSchedule -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-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
Website: https://dbatools.io
Copyright: (c) 2018 by dbatools, licensed under MIT
License: MIT https://opensource.org/licenses/MIT
Requires: sysadmin access on SQL Servers
.LINK
https://dbatools.io/Copy-DbaBackupDevice
.EXAMPLE
PS C:\> 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
PS C:\> 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
PS C:\> Copy-DbaBackupDevice -Source sqlserver2014a -Destination sqlcluster -WhatIf -Force
Shows what would happen if the command were executed using force.
#>
[CmdletBinding(DefaultParameterSetName = "Default", SupportsShouldProcess)]
param (
[parameter(Mandatory)]
[DbaInstanceParameter]$Source,
[PSCredential]$SourceSqlCredential,
[parameter(Mandatory)]
[DbaInstanceParameter[]]$Destination,
[PSCredential]$DestinationSqlCredential,
[object[]]$BackupDevice,
[switch]$Force,
[Alias('Silent')]
[switch]$EnableException
)
begin {
if (-not $script:isWindows) {
Stop-Function -Message "Copy-DbaBackupDevice does not support Linux yet though it looks doable"
return
}
try {
$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 {
$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 on destination"
$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 $destServer -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-DbaCmsRegServer {
<#
.SYNOPSIS
Migrates SQL Server Central Management groups and server instances from one SQL Server to another.
.DESCRIPTION
Copy-DbaCmsRegServer 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 Group
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 Group 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
Website: https://dbatools.io
Copyright: (c) 2018 by dbatools, licensed under MIT
License: MIT https://opensource.org/licenses/MIT
Requires: sysadmin access on SQL Servers
.LINK
https://dbatools.io/Copy-DbaCmsRegServer
.EXAMPLE
PS C:\> Copy-DbaCmsRegServer -Source sqlserver2014a -Destination sqlcluster
All groups, subgroups, and server instances are copied from sqlserver2014a CMS to sqlcluster CMS.
.EXAMPLE
PS C:\> Copy-DbaCmsRegServer -Source sqlserver2014a -Destination sqlcluster -Group Group1,Group3
Top-level groups Group1 and Group3 along with their subgroups and server instances are copied from sqlserver to sqlcluster.
.EXAMPLE
PS C:\> Copy-DbaCmsRegServer -Source sqlserver2014a -Destination sqlcluster -Group 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)]
param (
[parameter(Mandatory)]
[DbaInstanceParameter]$Source,
[PSCredential]$SourceSqlCredential,
[parameter(Mandatory)]
[DbaInstanceParameter[]]$Destination,
[PSCredential]$DestinationSqlCredential,
[Alias('CMSGroup')]
[string[]]$Group,
[switch]$SwitchServerName,
[switch]$Force,
[switch]$EnableException
)
begin {
if (-not $script:isWindows) {
Stop-Function -Message "Copy-DbaCmsRegServer does not support Linux - we're still waiting for the Core SMOs from Microsoft"
return
}
Test-DbaDeprecation -DeprecatedOn "1.0.0" -Alias Copy-DbaCentralManagementServer
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 on destination"
$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 on destination"
$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 on destination"
$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 {
$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 {
$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 ($Group) {
$stores = @();
foreach ($groupName in $Group) {
$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/
.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
Website: https://dbatools.io
Copyright: (c) 2018 by dbatools, licensed under MIT
License: MIT https://opensource.org/licenses/MIT
Requires:
- PowerShell Version 3.0
- Administrator access on Windows
- sysadmin access on SQL Server.
- DAC access enabled for local (default)
.LINK
https://dbatools.io/Copy-DbaCredential
.EXAMPLE
PS C:\> 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
PS C:\> 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 {
if (-not $script:isWindows) {
Stop-Function -Message "Copy-DbaCredential is only supported on Windows"
return
}
$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
#>
[Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSAvoidUsingPlainTextForPassword", "", Justification = "For Credentials")]
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 on destination"
$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 {
$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 {
$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
Website: https://dbatools.io
Copyright: (c) 2018 by dbatools, licensed under MIT
License: MIT https://opensource.org/licenses/MIT
Requires: sysadmin access on SQL Servers
.LINK
https://dbatools.io/Copy-DbaCustomError
.EXAMPLE
PS C:\> 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
PS C:\> 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
PS C:\> 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
PS C:\> Copy-DbaCustomError -Source sqlserver2014a -Destination sqlcluster -WhatIf -Force
Shows what would happen if the command were executed using force.
#>
[CmdletBinding(DefaultParameterSetName = "Default", SupportsShouldProcess)]
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 {
$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 {
$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 on destination"
$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.
If you are experiencing issues with Copy-DbaDatabase, please use Backup-DbaDatabase | Restore-DbaDatabase instead.
.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 -SharedPath in a valid UNC format (\\server\share).
Backups will be immediately deleted after use unless -NoBackupCleanup is specified.
.PARAMETER SharedPath
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
Enables piped input from Get-DbaDatabase
.PARAMETER UseLastBackup
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 -UseLastBackup
.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 occurrence 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.
If using -SetSourceReadonly, this will instantly roll back any open transactions that may be stopping the process.
.NOTES
Tags: Migration, Backup, Restore
Author: Chrissy LeMaire (@cl), netnerds.net
Website: https://dbatools.io
Copyright: (c) 2018 by dbatools, licensed under MIT
License: MIT https://opensource.org/licenses/MIT
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.
.LINK
https://dbatools.io/Copy-DbaDatabase
.EXAMPLE
PS C:\> Copy-DbaDatabase -Source sql2014a -Destination sql2014b -Database TestDB -BackupRestore -SharedPath \\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
PS C:\> 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
PS C:\> Copy-DbaDatabase -Source sql2014a -Destination sqlcluster, sql2016 -BackupRestore -UseLastBackup -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
PS C:\> Copy-DbaDatabase -Source sql2014a -Destination sqlcluster -ExcludeDatabase Northwind, pubs -IncludeSupportDbs -Force -BackupRestore -SharedPath \\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)]
[Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUseOutputTypeCorrectly", "", Justification = "PSSA Rule Ignored by BOH")]
param (
[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,
[Alias("NetworkShare")]
[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]$SharedPath,
[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]$UseLastBackup,
[parameter(ParameterSetName = "DbBackup")]
[switch]$Continue,
[parameter(ValueFromPipeline)]
[Microsoft.SqlServer.Management.Smo.Database[]]$InputObject,
[switch]$NoCopyOnly,
[switch]$SetSourceOffline,
[string]$NewName,
[string]$Prefix,
[switch]$Force,
[switch]$EnableException
)
begin {
Test-DbaDeprecation -DeprecatedOn 1.0.0 -Parameter NetworkShare -CustomMessage "Using the parameter NetworkShare is deprecated. This parameter will be removed in version 1.0.0 or before. Use SharedPath instead."
$CopyOnly = -not $NoCopyOnly
if ($BackupRestore -and (-not $SharedPath -and -not $UseLastBackup)) {
Stop-Function -Message "When using -BackupRestore, you must specify -SharedPath or -UseLastBackup"
return
}
if ($SharedPath -and $UseLastBackup) {
Stop-Function -Message "-SharedPath cannot be used with -UseLastBackup 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 $UseLastBackup) {
Stop-Function -Message "-Continue cannot be used without -UseLastBackup"
return
}
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 {
try {
[IO.Path]::Combine($Path, $ChildPath)
} catch {
"$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 -or (-not $script:isWindows)) {
return $filepath
}
if (-not $filepath) {
return
}
if ($filepath.StartsWith("\\")) {
return $filepath
}
$servername = $servername.Split("\")[0]
if ($filepath -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
# here to avoid an empty catch
$null = 1
}
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) {
$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
#>
[CmdletBinding(SupportsShouldProcess)]
param (
[object]$fileStructure,
[string]$dbName
)
$filestructure
$copydb = $fileStructure.databases[$dbName]
$dbsource = $copydb.source
$dbdestination = $copydb.destination
foreach ($file in $dbsource.keys) {
if ($Pscmdlet.ShouldProcess($file, "Starting Sql File Transfer")) {
$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(SupportsShouldProcess)]
param (
[object]$sourceServer,
[object]$destServer,
[object]$fileStructure,
[string]$dbName
)
if ($Pscmdlet.ShouldProcess($dbname, "Starting detaching and re-attaching from $sourceServer to $destServer")) {
$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-Message -Level Verbose -Message "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 $SharedPath -and -not $UseLastBackup)) {
Stop-Function -Message "When using -BackupRestore, you must specify -SharedPath or -UseLastBackup"
return
}
if ($SharedPath -and $UseLastBackup) {
Stop-Function -Message "-SharedPath cannot be used with -UseLastBackup 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 {
$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)."
if ($sourceserver.Databases.IsSystemObject -notcontains $false) {
Stop-Function -Message "No user databases to migrate"
return
}
foreach ($destinstance in $Destination) {
try {
$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 ($SharedPath) {
if ($(Test-DbaPath -SqlInstance $sourceServer -Path $SharedPath) -eq $false) {
Write-Message -Level Verbose -Message "$Source may not be able to access $SharedPath. Trying anyway."
}
if ($(Test-DbaPath -SqlInstance $destServer -Path $SharedPath) -eq $false) {
Write-Message -Level Verbose -Message "$destinstance may not be able to access $SharedPath. Trying anyway."
}
if ($SharedPath.StartsWith('\\')) {
try {
$shareServer = ($SharedPath -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
}
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 -or $IncludeSupportDbs -or $Database) {
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 -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 on destination"
$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."
try {
$result = Set-DbaDbState -SqlInstance $sourceServer -Database $dbName -ReadOnly -EnableException -Force:$Force
} catch {
Stop-Function -Continue -Message "Couldn't set database to read-only. Aborting routine for this database" -ErrorRecord $_
}
}
}
if ($BackupRestore) {
if ($UseLastBackup) {
$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 ($UseLastBackup) {
$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 $SharedPath -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 $($SharedPath)? Aborting routine for this database."
$copyDatabaseStatus.Status = "Failed"
$copyDatabaseStatus.Notes = "Backup failed. Verify service account access to $SharedPath."
$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 -or $sourceDbReadOnly) {
try {
$result = Set-DbaDbState -SqlInstance $sourceServer -Database $dbName -ReadOnly -EnableException
} catch {
Stop-Function -Message "Couldn't set database to read-only" -ErrorRecord $_
}
}
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
}
}
}
$NewDatabase = Get-DbaDatabase -SqlInstance $destServer -database $DestinationdbName
# restore potentially lost settings
if ($destServer.VersionMajor -ge 9 -and $NoRecovery -eq $false) {
if ($sourceDbOwnerChaining -ne $NewDatabase.DatabaseOwnershipChaining) {
if ($Pscmdlet.ShouldProcess($destinstance, "Updating DatabaseOwnershipChaining on $DestinationdbName")) {
try {
$NewDatabase.DatabaseOwnershipChaining = $sourceDbOwnerChaining
$NewDatabase.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 $NewDatabase.Trustworthy) {
if ($Pscmdlet.ShouldProcess($destinstance, "Updating Trustworthy on $DestinationdbName")) {
try {
$NewDatabase.Trustworthy = $sourceDbTrustworthy
$NewDatabase.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 $NewDatabase.BrokerEnabled) {
if ($Pscmdlet.ShouldProcess($destinstance, "Updating BrokerEnabled on $dbName")) {
try {
$NewDatabase.BrokerEnabled = $sourceDbBrokerEnabled
$NewDatabase.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 $NewDatabase.ReadOnly -and -not $NoRecovery) {
if ($Pscmdlet.ShouldProcess($destinstance, "Updating ReadOnly status on $DestinationdbName")) {
try {
if ($sourceDbReadOnly) {
$result = Set-DbaDbState -SqlInstance $destserver -Database $DestinationdbName -ReadOnly -EnableException
} else {
$result = Set-DbaDbState -SqlInstance $destserver -Database $DestinationdbName -ReadWrite -EnableException
}
} catch {
$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 ($SharedPath -and $NoBackupCleanup) {
Write-Message -Level Verbose -Message "Backups still exist at $SharedPath."
}
} 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
Website: https://dbatools.io
Copyright: (c) 2018 by dbatools, licensed under MIT
License: MIT https://opensource.org/licenses/MIT
Requires: sysadmin access on SQL Servers
.LINK
https://dbatools.io/Copy-DbaDataCollector
.EXAMPLE
PS C:\> Copy-DbaDataCollector -Source sqlserver2014a -Destination sqlcluster
Copies all Data Collector Objects and Configurations from sqlserver2014a to sqlcluster, using Windows credentials.
.EXAMPLE
PS C:\> 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
PS C:\> Copy-DbaDataCollector -Source sqlserver2014a -Destination sqlcluster -WhatIf
Shows what would happen if the command were executed.
.EXAMPLE
PS C:\> 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)]
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 {
if (-not $script:isWindows) {
Stop-Function -Message "Copy-DbaDataCollector does not support Linux - we're still waiting for the Core SMOs from Microsoft"
return
}
try {
$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 {
$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 on destination"
$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
Website: https://dbatools.io
Copyright: (c) 2018 by dbatools, licensed under MIT
License: MIT https://opensource.org/licenses/MIT
Requires: sysadmin access on SQL Servers
.LINK
http://dbatools.io/Get-SqlDatabaseAssembly
.EXAMPLE
PS C:\> 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
PS C:\> 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
PS C:\> Copy-DbaDbAssembly -Source sqlserver2014a -Destination sqlcluster -WhatIf -Force
Shows what would happen if the command were executed using force.
#>
[CmdletBinding(DefaultParameterSetName = "Default", SupportsShouldProcess)]
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 {
$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 {
#here to avoid an empty catch
$null = 1
}
}
}
process {
if (Test-FunctionInterrupt) { return }
foreach ($destinstance in $Destination) {
try {
$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 {
#here to avoid an empty catch
$null = 1
}
}
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 on destination"
$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
Website: https://dbatools.io
Copyright: (c) 2018 by dbatools, licensed under MIT
License: MIT https://opensource.org/licenses/MIT
Requires: sysadmin access on SQL Servers
.LINK
https://dbatools.io/Copy-DbaDbMail
.EXAMPLE
PS C:\> 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
PS C:\> 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
PS C:\> Copy-DbaDbMail -Source sqlserver2014a -Destination sqlcluster -WhatIf
Shows what would happen if the command were executed.
.EXAMPLE
PS C:\> Copy-DbaDbMail -Source sqlserver2014a -Destination sqlcluster -EnableException
Performs execution of function, and will throw a terminating exception if something breaks
#>
[cmdletbinding(DefaultParameterSetName = "Default", SupportsShouldProcess)]
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
$newAccountName = $accountName -replace [Regex]::Escape($source), $destinstance
Write-Message -Message "Updating account name from '$accountName' to '$newAccountName'." -Level Verbose
$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 $newAccountName) {
continue
}
if ($destAccounts.name -contains $newAccountName) {
if ($force -eq $false) {
If ($pscmdlet.ShouldProcess($destinstance, "Account '$newAccountName' exists at destination. Use -Force to drop and migrate.")) {
$copyMailAccountStatus.Status = "Skipped"
$copyMailAccountStatus.Notes = "Already exists on destination"
$copyMailAccountStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
Write-Message -Message "Account $newAccountName exists at destination. Use -Force to drop and migrate." -Level Verbose
}
continue
}
If ($pscmdlet.ShouldProcess($destinstance, "Dropping account '$newAccountName' and recreating.")) {
try {
Write-Message -Message "Dropping account '$newAccountName'." -Level Verbose
$destServer.Mail.Accounts[$newAccountName].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 "(?<=@account_name=N'[\d\w\s']*)$sourceRegEx(?=[\d\w\s']*',)", $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 $newAccountName -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
$newProfileName = $profileName -replace [Regex]::Escape($source), $destinstance
Write-Message -Message "Updating profile name from '$profileName' to '$newProfileName'." -Level Verbose
$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 $newProfileName) {
continue
}
if ($destProfiles.name -contains $newProfileName) {
if ($force -eq $false) {
If ($pscmdlet.ShouldProcess($destinstance, "Profile '$newProfileName' exists at destination. Use -Force to drop and migrate.")) {
$copyMailProfileStatus.Status = "Skipped"
$copyMailProfileStatus.Notes = "Already exists on destination"
$copyMailProfileStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
Write-Message -Message "Profile '$newProfileName' exists at destination. Use -Force to drop and migrate." -Level Verbose
}
continue
}
If ($pscmdlet.ShouldProcess($destinstance, "Dropping profile '$newProfileName' and recreating.")) {
try {
Write-Message -Message "Dropping profile '$newProfileName'." -Level Verbose
$destServer.Mail.Profiles[$newProfileName].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 $newProfileName -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 "(?<=@account_name=N'[\d\w\s']*)$sourceRegEx(?=[\d\w\s']*',)", $destinstance
$sql = $sql -replace "(?<=@profile_name=N'[\d\w\s']*)$sourceRegEx(?=[\d\w\s']*',)", $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 on destination"
$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 "(?<=@account_name=N'[\d\w\s']*)$sourceRegEx(?=[\d\w\s']*',)", $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 {
$sourceServer = Connect-SqlInstance -SqlInstance $Source -SqlCredential $SourceSqlCredential -MinimumVersion 9
} catch {
Stop-Function -Message "Failure" -Category ConnectionError -ErrorRecord $_ -Target $Source
return
}
$mail = $sourceServer.mail
$sourceRegEx = [RegEx]::Escape($source)
}
process {
if (Test-FunctionInterrupt) { return }
foreach ($destinstance in $Destination) {
try {
$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
}
}
function Copy-DbaDbQueryStoreOption {
<#
.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
Tags: QueryStore
Author: Enrico van de Laar (@evdlaar)
Website: https://dbatools.io
Copyright: (c) 2018 by dbatools, licensed under MIT
License: MIT https://opensource.org/licenses/MIT
.LINK
https://dbatools.io/Copy-QueryStoreConfig
.EXAMPLE
PS C:\> Copy-DbaDbQueryStoreOption -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
PS C:\> Copy-DbaDbQueryStoreOption -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)]
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 {
Test-DbaDeprecation -DeprecatedOn "1.0.0" -Alias Copy-DbaQueryStoreConfig
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 {
$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 -ExcludeSystem
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
if ($PSCmdlet.ShouldProcess("$db", "Copying QueryStoreConfig")) {
try {
$setDbaDbQueryStoreOptionParameters = @{
SqlInstance = $destinationServer;
SqlCredential = $DestinationSqlCredential;
Database = $db.name;
State = $SourceQSConfig.ActualState;
FlushInterval = $SourceQSConfig.DataFlushIntervalInSeconds;
CollectionInterval = $SourceQSConfig.StatisticsCollectionIntervalInMinutes;
MaxSize = $SourceQSConfig.MaxStorageSizeInMB;
CaptureMode = $SourceQSConfig.QueryCaptureMode;
CleanupMode = $SourceQSConfig.SizeBasedCleanupMode;
StaleQueryThreshold = $SourceQSConfig.StaleQueryThresholdInDays;
}
$null = Set-DbaDbQueryStoreOption @setDbaDbQueryStoreOptionParameters;
$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
}
}
}
}
}
}
#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-DbaQuery -SqlInstance 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 a 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 of a table or selected tables, specify the query.
Ensure to select all required columns. Calculated Columns or columns with default values may be excluded.
The tablename should be a full three-part name in form [Database].[Schema].[Table]
.PARAMETER AutoCreateTable
Creates the destination table if it does not already exist, based off of the "Export..." script of the source table.
.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: Simone Bizzotto (@niphlod)
Website: https://dbatools.io
Copyright: (c) 2018 by dbatools, licensed under MIT
License: MIT https://opensource.org/licenses/MIT
.LINK
https://dbatools.io/Copy-DbaDbTableData
.EXAMPLE
PS C:\> Copy-DbaDbTableData -SqlInstance sql1 -Destination sql2 -Database dbatools_from -Table dbo.test_table
Copies all the data from table dbo.test_table in database dbatools_from on sql1 to table test_table in database dbatools_from on sql2.
.EXAMPLE
PS C:\> Copy-DbaDbTableData -SqlInstance sql1 -Destination sql2 -Database dbatools_from -DestinationDatabase dbatools_dest -Table [Schema].[test table]
Copies all the data from table [Schema].[test table] in database dbatools_from on sql1 to table [Schema].[test table] in database dbatools_dest on sql2
.EXAMPLE
PS C:\> 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
PS C:\> 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
PS C:\> Copy-DbaDbTableData -SqlInstance sql1 -Destination sql2 -Database dbatools_from -Table test_table -KeepIdentity -Truncate
Copies all the data in table test_table from sql1 to sql2, using the database dbatools_from, keeping identity columns and truncating the destination
.EXAMPLE
PS C:\> $params = @{
>> SourceSqlInstance = 'sql1'
>> DestinationSqlInstance = 'sql2'
>> Database = 'dbatools_from'
>> DestinationDatabase = 'dbatools_dest'
>> Table = '[Schema].[Table]'
>> DestinationTable = '[dbo].[Table.Copy]'
>> KeepIdentity = $true
>> KeepNulls = $true
>> Truncate = $true
>> BatchSize = 10000
>> }
>>
PS C:\> Copy-DbaDbTableData @params
Copies all the data from table [Schema].[Table] in database dbatools_from on sql1 to table [dbo].[Table.Copy] in database dbatools_dest on sql2
Keeps identity columns and Nulls, truncates the destination and processes in BatchSize of 10000.
#>
[CmdletBinding(DefaultParameterSetName = "Default", SupportsShouldProcess)]
param (
[Alias("ServerInstance", "SqlServer", "Source")]
[DbaInstanceParameter]$SqlInstance,
[PSCredential]$SqlCredential,
[DbaInstanceParameter[]]$Destination,
[PSCredential]$DestinationSqlCredential,
[string]$Database,
[string]$DestinationDatabase,
[string[]]$Table,
[string]$Query,
[switch]$AutoCreateTable,
[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 Stop
if (-not $script:core) {
try {
Add-Type -ReferencedAssemblies System.Data.dll -TypeDefinition $sourcecode -ErrorAction Stop
} catch {
$null = 1
}
}
$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
}
if ((Test-Bound -Not -ParameterName Destination, DestinationDatabase, DestinationTable)) {
Stop-Function -Message "Cannot copy $Table into itself. One of destination Server, Database or Table must be specified " -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 {
foreach ($tbl in $Table) {
$dbTable = Get-DbaDbTable -SqlInstance $server -Table $tbl -Database $Database -EnableException -Verbose:$false
if ($dbTable.Count -eq 1) {
$InputObject += $dbTable
} else {
Stop-Function -Message "The table $tbl matches $($dbTable.Count) objects. Unable to determine which object to copy" -Continue
}
}
} 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 DestinationTable)) {
$DestinationTable = '[' + $sqltable.Schema + '].[' + $sqltable.Name + ']'
}
$newTableParts = Get-TableNameParts $DestinationTable
#using FQTN to determine database name
if ($newTableParts.Database) {
$DestinationDatabase = $newTableParts.Database
} elseif ((Test-Bound -Not -ParameterName DestinationDatabase)) {
$DestinationDatabase = $Database
}
if (-not $Destination) {
$Destination = $server
}
foreach ($destinationserver in $Destination) {
try {
$destServer = Connect-SqlInstance -SqlInstance $destinationserver -SqlCredential $DestinationSqlCredential
} catch {
Stop-Function -Message "Failure" -Category ConnectionError -ErrorRecord $_ -Target $destinationserver
return
}
if ($DestinationDatabase -notin $destServer.Databases.Name) {
Stop-Function -Message "Database $DestinationDatabase doesn't exist on $destServer"
return
}
$desttable = Get-DbaDbTable -SqlInstance $destServer -Table $DestinationTable -Database $DestinationDatabase -Verbose:$false | Select-Object -First 1
if (-not $desttable -and $AutoCreateTable) {
try {
$tablescript = $sqltable | Export-DbaScript -Passthru | Out-String
#replacing table name
if ($newTableParts.Table) {
$rX = "(CREATE TABLE \[$([regex]::Escape($sqltable.Schema))\]\.\[)$([regex]::Escape($sqltable.Name))(\]\()"
$tablescript = $tablescript -replace $rX, "`$1$($newTableParts.Table)`$2"
}
#replacing table schema
if ($newTableParts.Schema) {
$rX = "(CREATE TABLE \[)$([regex]::Escape($sqltable.Schema))(\]\.\[$([regex]::Escape($newTableParts.Table))\]\()"
$tablescript = $tablescript -replace $rX, "`$1$($newTableParts.Schema)`$2"
}
if ($PSCmdlet.ShouldProcess($destServer, "Creating new table: $DestinationTable")) {
Write-Message -Message "New table script: $tablescript" -Level VeryVerbose
Invoke-DbaQuery -SqlInstance $destServer -Database $DestinationDatabase -Query "$tablescript" -EnableException # add some string assurance there
#table list was updated, let's grab a fresh one
$destServer.Databases[$DestinationDatabase].Tables.Refresh()
$desttable = Get-DbaDbTable -SqlInstance $destServer -Table $DestinationTable -Database $DestinationDatabase -Verbose:$false
Write-Message -Message "New table created: $desttable" -Level Verbose
}
} catch {
Stop-Function -Message "Unable to determine destination table: $DestinationTable" -ErrorRecord $_
return
}
}
if (-not $desttable) {
Stop-Function -Message "Table $DestinationTable cannot be found in $DestinationDatabase. Use -AutoCreateTable to automatically create the table on the destination." -Continue
}
$connstring = $destServer.ConnectionContext.ConnectionString
if ($server.DatabaseEngineType -eq "SqlAzureDatabase") {
$fqtnfrom = "$sqltable"
} else {
$fqtnfrom = "$($server.Databases[$Database]).$sqltable"
}
if ($destServer.DatabaseEngineType -eq "SqlAzureDatabase") {
$fqtndest = "$desttable"
} else {
$fqtndest = "$($destServer.Databases[$DestinationDatabase]).$desttable"
}
if ($fqtndest -eq $fqtnfrom -and $server.Name -eq $destServer.Name) {
Stop-Function -Message "Cannot copy $fqtnfrom on $($server.Name) into $fqtndest on ($destServer.Name). Source and Destination must be different " -Target $Table
return
}
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")
}
}
if ($Pscmdlet.ShouldProcess($server, "Copy data from $fqtnfrom")) {
$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)
if ($script:core) {
$RowsTotal = "Unsupported in Core"
} else {
$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
SourceSchema = $sqltable.Schema
SourceTable = $sqltable.Name
DestinationInstance = $destServer.name
DestinationDatabase = $DestinationDatabase
DestinationSchema = $desttable.Schema
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
Website: https://dbatools.io
Copyright: (c) 2018 by dbatools, licensed under MIT
License: MIT https://opensource.org/licenses/MIT
Requires: sysadmin access on SQL Servers
.LINK
https://dbatools.io/Copy-DbaEndpoint
.EXAMPLE
PS C:\> 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
PS C:\> 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
PS C:\> Copy-DbaEndpoint -Source sqlserver2014a -Destination sqlcluster -WhatIf -Force
Shows what would happen if the command were executed using force.
#>
[CmdletBinding(DefaultParameterSetName = "Default", SupportsShouldProcess)]
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 {
$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 {
$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 on destination"
$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-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/
.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
Website: https://dbatools.io
Copyright: (c) 2018 by dbatools, licensed under MIT
License: MIT https://opensource.org/licenses/MIT
Requires: sysadmin access on SQL Servers
Limitations: This just copies the SQL portion. It does not copy files (i.e. a local SQLite database, or Microsoft Access DB), nor does it configure ODBC entries.
.LINK
https://dbatools.io/Copy-DbaLinkedServer
.EXAMPLE
PS C:\> 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
PS C:\> 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)]
[Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUseSingularNouns", "", Justification = "Internal functions are ignored")]
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 {
if (-not $script:isWindows) {
Stop-Function -Message "Copy-DbaCredential is only supported on Windows"
return
}
$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 {
#here to avoid an empty catch
$null = 1
}
$linkedServerName = $currentLinkedServer.Name
$linkedServerProductName = $currentLinkedServer.ProductName
$linkedServerDataSource = $currentLinkedServer.DataSource
$copyLinkedServer = [pscustomobject]@{
SourceServer = $sourceServer.Name
DestinationServer = $destServer.Name
Name = $linkedServerName
ProductName = $linkedServerProductName
DataSource = $linkedServerDataSource
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.Notes = "Already exists on destination"
$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)
if ($copyLinkedServer.ProductName -eq 'SQL Server' -and $copyLinkedServer.Name -ne $copyLinkedServer.DataSource) {
$sql2 = "EXEC sp_setnetname '$($copyLinkedServer.Name)', '$($copyLinkedServer.DataSource)'; "
$destServer.Query($sql2)
}
$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 {
$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 {
$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 ExcludeSystemLogins
If this switch is enabled, NT SERVICE accounts will be skipped.
.PARAMETER ExcludePermissionSync
Skips permission syncs
.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-DbaLogin 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
Website: https://dbatools.io
Copyright: (c) 2018 by dbatools, licensed under MIT
License: MIT https://opensource.org/licenses/MIT
Requires: sysadmin access on SQL Servers
.LINK
https://dbatools.io/Copy-DbaLogin
.EXAMPLE
PS C:\> 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
PS C:\> 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
PS C:\> Copy-DbaLogin -Source sqlserver2014a -Destination sqlcluster -ExcludeLogin 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
PS C:\> 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
PS C:\> 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
PS C:\> Copy-DbaLogin -LoginRenameHashtable @{ "PreviousUser" = "newlogin" } -Source $Sql01 -Destination Localhost -SourceSqlCredential $sqlcred
Copies PreviousUser and then renames it to newlogin.
.EXAMPLE
PS C:\> 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]$ExcludeSystemLogins,
[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,
[switch]$ExcludePermissionSync,
[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 ($ExcludeSystemLogins) {
if ($Pscmdlet.ShouldProcess("console", "$userName was skipped because ExcludeSystemLogins was specified.")) {
Write-Message -Level Verbose -Message "$userName was skipped because ExcludeSystemLogins 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 on destination"
$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 (-not $ExcludePermissionSync) {
if ($Pscmdlet.ShouldProcess($destinstance, "Updating SQL login $userName permissions")) {
Update-SqlPermission -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 {
$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 {
$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
Website: https://dbatools.io
Copyright: (c) 2018 by dbatools, licensed under MIT
License: MIT https://opensource.org/licenses/MIT
Requires: sysadmin access on SQL Servers
.LINK
https://dbatools.io/Copy-DbaPolicyManagement
.EXAMPLE
PS C:\> Copy-DbaPolicyManagement -Source sqlserver2014a -Destination sqlcluster
Copies all policies and conditions from sqlserver2014a to sqlcluster, using Windows credentials.
.EXAMPLE
PS C:\> 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
PS C:\> Copy-DbaPolicyManagement -Source sqlserver2014a -Destination sqlcluster -WhatIf
Shows what would happen if the command were executed.
.EXAMPLE
PS C:\> 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)]
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 {
if (-not $script:isWindows) {
Stop-Function -Message "Copy-DbaPolicyManagement does not support Linux - we're still waiting for the Core SMOs from Microsoft"
return
}
try {
$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 {
$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 on destination"
$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 on destination"
$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-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
Website: https://dbatools.io
Copyright: (c) 2018 by dbatools, licensed under MIT
License: MIT https://opensource.org/licenses/MIT
Requires: sysadmin access on SQL Servers
.LINK
https://dbatools.io/Copy-DbaResourceGovernor
.EXAMPLE
PS C:\> 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
PS C:\> 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
PS C:\> Copy-DbaResourceGovernor -Source sqlserver2014a -Destination sqlcluster -WhatIf
Shows what would happen if the command were executed.
#>
[CmdletBinding(DefaultParameterSetName = "Default", SupportsShouldProcess)]
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 {
$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 {
$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
$sql = "ALTER RESOURCE GOVERNOR RECONFIGURE;"
Write-Message -Level Debug -Message $sql
Write-Message -Level Verbose -Message "Reconfiguring Resource Governor."
$destServer.Query($sql)
} else {
if ($Force -eq $false) {
$copyResourceGovClassifierFunc.Status = "Skipped"
$copyResourceGovClassifierFunc.Notes = "Already exists on destination"
$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)
$sql = "ALTER RESOURCE GOVERNOR RECONFIGURE;"
Write-Message -Level Debug -Message $sql
Write-Message -Level Verbose -Message "Reconfiguring Resource Governor."
$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
$sql = "ALTER RESOURCE GOVERNOR RECONFIGURE;"
Write-Message -Level Debug -Message $sql
Write-Message -Level Verbose -Message "Reconfiguring Resource Governor."
$destServer.Query($sql)
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 on destination"
$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
$sql = "ALTER RESOURCE GOVERNOR RECONFIGURE;"
Write-Message -Level Debug -Message $sql
Write-Message -Level Verbose -Message "Reconfiguring Resource Governor."
$destServer.Query($sql)
}
}
}
}
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
$sql = "ALTER RESOURCE GOVERNOR RECONFIGURE;"
Write-Message -Level Debug -Message $sql
Write-Message -Level Verbose -Message "Reconfiguring Resource Governor."
$destServer.Query($sql)
}
} 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)
$sql = "ALTER RESOURCE GOVERNOR RECONFIGURE;"
Write-Message -Level Debug -Message $sql
Write-Message -Level Verbose -Message "Reconfiguring Resource Governor."
$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 Path
Destination file path. If not specified, the file path of the source will be used (or the default data directory).
.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
Website: https://dbatools.io
Copyright: (c) 2018 by dbatools, licensed under MIT
License: MIT https://opensource.org/licenses/MIT
Requires: sysadmin access on SQL Servers
.LINK
https://dbatools.io/Copy-DbaServerAudit
.EXAMPLE
PS C:\> 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
PS C:\> 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
PS C:\> Copy-DbaServerAudit -Source sqlserver2014a -Destination sqlcluster -WhatIf -Force
Shows what would happen if the command were executed using force.
.EXAMPLE
PS C:\> Copy-DbaServerAudit -Source sqlserver-0 -Destination sqlserver-1 -Audit audit1 -Path 'C:\audit1'
Copies audit audit1 from sqlserver-0 to sqlserver-1. The file path on sqlserver-1 will be set to 'C:\audit1'.
#>
[CmdletBinding(DefaultParameterSetName = "Default", SupportsShouldProcess)]
param (
[parameter(Mandatory)]
[DbaInstanceParameter]$Source,
[PSCredential]
$SourceSqlCredential,
[parameter(Mandatory)]
[DbaInstanceParameter[]]$Destination,
[PSCredential]
$DestinationSqlCredential,
[object[]]$Audit,
[object[]]$ExcludeAudit,
[string]$Path,
[switch]$Force,
[Alias('Silent')]
[switch]$EnableException
)
begin {
try {
$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 {
$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
}
if ($Path) {
$currentAudit.FilePath = $Path
}
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 on destination"
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
$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
$currentAudit.FilePath = $datadir
}
}
} else {
$datadir = Get-SqlDefaultPaths $destServer data
$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."
$sql = $currentAudit.Script() | Out-String
$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
Website: https://dbatools.io
Copyright: (c) 2018 by dbatools, licensed under MIT
License: MIT https://opensource.org/licenses/MIT
Requires: sysadmin access on SQL Servers
.LINK
https://dbatools.io/Copy-DbaServerAuditSpecification
.EXAMPLE
PS C:\> 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
PS C:\> Copy-DbaServerAuditSpecification -Source sqlserver2014a -Destination sqlcluster -AuditSpecification 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
PS C:\> Copy-DbaServerAuditSpecification -Source sqlserver2014a -Destination sqlcluster -WhatIf -Force
Shows what would happen if the command were executed using force.
#>
[CmdletBinding(DefaultParameterSetName = "Default", SupportsShouldProcess)]
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 {
$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 {
$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.Notes = "Already exists on destination"
$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
Website: https://dbatools.io
Copyright: (c) 2018 by dbatools, licensed under MIT
License: MIT https://opensource.org/licenses/MIT
Requires: sysadmin access on SQL Servers
.LINK
https://dbatools.io/Copy-DbaServerTrigger
.EXAMPLE
PS C:\> 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
PS C:\> 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
PS C:\> Copy-DbaServerTrigger -Source sqlserver2014a -Destination sqlcluster -WhatIf -Force
Shows what would happen if the command were executed using force.
#>
[CmdletBinding(DefaultParameterSetName = "Default", SupportsShouldProcess)]
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 {
$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 {
$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.Notes = "Already exists on destination"
$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
Website: https://dbatools.io
Copyright: (c) 2018 by dbatools, licensed under MIT
License: MIT https://opensource.org/licenses/MIT
Requires: sysadmin access on SQL Servers
.LINK
https://dbatools.io/Copy-DbaSpConfigure
.EXAMPLE
PS C:\> Copy-DbaSpConfigure -Source sqlserver2014a -Destination sqlcluster
Copies all sp_configure settings from sqlserver2014a to sqlcluster
.EXAMPLE
PS C:\> Copy-DbaSpConfigure -Source sqlserver2014a -Destination sqlcluster -ConfigName DefaultBackupCompression, IsSqlClrEnabled -SourceSqlCredential $cred
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
PS C:\> Copy-DbaSpConfigure -Source sqlserver2014a -Destination sqlcluster -ExcludeConfigName DefaultBackupCompression, IsSqlClrEnabled
Copies all configs except for IsSqlClrEnabled and DefaultBackupCompression, from sqlserver2014a to sqlcluster.
.EXAMPLE
PS C:\> Copy-DbaSpConfigure -Source sqlserver2014a -Destination sqlcluster -WhatIf
Shows what would happen if the command were executed.
#>
[CmdletBinding(DefaultParameterSetName = "Default", SupportsShouldProcess)]
param (
[parameter(Mandatory)]
[DbaInstanceParameter]$Source,
[PSCredential]$SourceSqlCredential,
[parameter(Mandatory)]
[DbaInstanceParameter[]]$Destination,
[PSCredential]$DestinationSqlCredential,
[object[]]$ConfigName,
[object[]]$ExcludeConfigName,
[Alias('Silent')]
[switch]$EnableException
)
begin {
try {
$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 {
$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) 2018 by dbatools, licensed under MIT
License: MIT https://opensource.org/licenses/MIT
.LINK
https://dbatools.io/Copy-DbaSsisCatalog
.EXAMPLE
PS C:\> 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
PS C:\> 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
PS C:\> Copy-DbaSsisCatalog -Source sqlserver2014a -Destination sqlcluster -WhatIf -Force
Shows what would happen if the command were executed using force.
.EXAMPLE
PS C:\> $SecurePW = Read-Host "Enter password" -AsSecureString
PS C:\> 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)]
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) {
#Variable marked as unused by PSScriptAnalyzer
#$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 = $sourceServer.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 {
[CmdletBinding(SupportsShouldProcess)]
param (
[String]$Folder,
[String]$Description,
[Switch]$Force
)
if ($Pscmdlet.ShouldProcess($folder, "Creating new Catalog Folder")) {
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 {
[CmdletBinding(SupportsShouldProcess)]
param (
[String]$Folder,
[String]$Environment,
[Switch]$Force
)
if ($Pscmdlet.ShouldProcess($folder, "Creating new Environment Folder")) {
$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 {
[CmdletBinding(SupportsShouldProcess)]
param (
[System.Security.SecureString]$SecurePassword
)
if ($Pscmdlet.ShouldProcess("Creating New SSISDB Catalog")) {
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()
}
}
try {
$sourceServer = Connect-SqlInstance -SqlInstance $Source -SqlCredential $SourceSqlCredential -MinimumVersion 11
} catch {
Stop-Function -Message "Failure" -Category ConnectionError -ErrorRecord $_ -Target $Source
return
}
try {
$sourceSSIS = New-Object Microsoft.SqlServer.Management.IntegrationServices.IntegrationServices $sourceServer
} catch {
Stop-Function -Message "There was an error connecting to the source integration services." -Target $sourceServer -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 {
$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.ComputerName
} catch {
Stop-Function -Message "An error occurred when checking the destination for Integration Services. Is Integration Services installed?" -Target $destinstance -ErrorRecord $_
}
try {
$destinationSSIS = New-Object Microsoft.SqlServer.Management.IntegrationServices.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 -SecurePassword $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
# here to avoid an empty catch
$null = 1
}
$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) 2018 by dbatools, licensed under MIT
License: MIT https://opensource.org/licenses/MIT
.LINK
https://dbatools.io/Copy-DbaSysDbUserObject
.EXAMPLE
PS C:\> Copy-DbaSysDbUserObject -Source $sourceServer -Destination $destserver
Copies user objects from source to destination
#>
[CmdletBinding(SupportsShouldProcess)]
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 {
$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 {
$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 = "Already 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)
if ($PSCmdlet.ShouldProcess($destServer, "Attempting to add schema $($schema.Name) to $systemDb")) {
try {
$sql = $transfer.ScriptTransfer()
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 = "Already 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)
if ($PSCmdlet.ShouldProcess($destServer, "Attempting to add table $table to $systemDb")) {
try {
$sql = $transfer.ScriptTransfer()
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 -ExcludeSystemObjects | 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 -ExcludeSystemObjects -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 = "Already 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
# here to avoid an empty catch
$null = 1
}
}
}
} catch {
# Don't care - long story having to do with duplicate stuff
# here to avoid an empty catch
$null = 1
}
}
}
}
}
end {
Test-DbaDeprecation -DeprecatedOn "1.0.0" -EnableException:$false -Alias Copy-SqlSysDbUserObjects
}
}
function Copy-DbaXESession {
<#
.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
Website: https://dbatools.io
Copyright: (c) 2018 by dbatools, licensed under MIT
License: MIT https://opensource.org/licenses/MIT
Requires: sysadmin access on SQL Servers
.LINK
https://dbatools.io/Copy-DbaXESession
.EXAMPLE
PS C:\> Copy-DbaXESession -Source sqlserver2014a -Destination sqlcluster
Copies all Extended Event sessions from sqlserver2014a to sqlcluster using Windows credentials.
.EXAMPLE
PS C:\> Copy-DbaXESession -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
PS C:\> Copy-DbaXESession -Source sqlserver2014a -Destination sqlcluster -WhatIf
Shows what would happen if the command were executed.
.EXAMPLE
PS C:\> Copy-DbaXESession -Source sqlserver2014a -Destination sqlcluster -XeSession CheckQueries, MonitorUserDefinedException
Copies only the Extended Events named CheckQueries and MonitorUserDefinedException from sqlserver2014a to sqlcluster.
#>
[CmdletBinding(DefaultParameterSetName = "Default", SupportsShouldProcess)]
param (
[parameter(Mandatory)]
[DbaInstanceParameter]$Source,
[parameter(Mandatory)]
[DbaInstanceParameter[]]$Destination,
[PSCredential]
$SourceSqlCredential,
[PSCredential]
$DestinationSqlCredential,
[object[]]$XeSession,
[object[]]$ExcludeXeSession,
[switch]$Force,
[Alias('Silent')]
[switch]$EnableException
)
begin {
Test-DbaDeprecation -DeprecatedOn "1.0.0" -Alias Copy-DbaExtendedEvent
try {
$sourceServer = Connect-SqlInstance -SqlInstance $Source -SqlCredential $SourceSqlCredential -MinimumVersion 11
} 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 {
$destServer = Connect-SqlInstance -SqlInstance $destinstance -SqlCredential $DestinationSqlCredential -MinimumVersion 11
} 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 on destination"
$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
}
}
#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
Author: Chrissy LeMaire (@cl), netnerds.net
Website: https://dbatools.io
Copyright: (c) 2018 by dbatools, licensed under MIT
License: MIT https://opensource.org/licenses/MIT
.LINK
https://dbatools.io/Copy-DbaXESessionTemplate
.EXAMPLE
PS C:\> Copy-DbaXESessionTemplate
Copies non-Microsoft templates from the dbatools template repository (\bin\xetemplates\) to $home\Documents\SQL Server Management Studio\Templates\XEventTemplates.
.EXAMPLE
PS C:\> 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 target SQL Server instance or instances.
.PARAMETER Credential
Credential object used to connect to the Windows server 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://wsmelton.github.io
Website: https://dbatools.io
Copyright: (c) 2018 by dbatools, licensed under MIT
License: MIT https://opensource.org/licenses/MIT
.LINK
https://dbatools.io/Disable-DbaAgHadr
.EXAMPLE
PS C:\> Disable-DbaAgHadr -SqlInstance sql2016
Sets Hadr service to disabled for the instance sql2016 but changes will not be applied until the next time the server restarts.
.EXAMPLE
PS C:\> Disable-DbaAgHadr -SqlInstance sql2016 -Force
Sets Hadr service to disabled for the instance sql2016, and restart the service to apply the change.
.EXAMPLE
PS C:\> 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, ConfirmImpact = "High")]
param (
[parameter(Mandatory, ValueFromPipeline)]
[DbaInstanceParameter[]]$SqlInstance,
[PSCredential]$Credential,
[switch]$Force,
[switch]$EnableException
)
process {
foreach ($instance in $SqlInstance) {
$computer = $computerFullName = $instance.ComputerName
$instanceName = $instance.InstanceName
if (-not (Test-ElevationRequirement -ComputerName $instance)) {
return
}
$noChange = $false
<#
#Variable marked as unused by PSScriptAnalyzer
switch ($instance.InstanceName) {
'MSSQLSERVER' { $agentName = 'SQLSERVERAGENT' }
default { $agentName = "SQLAgent`$$instanceName" }
}
#>
try {
Write-Message -Level Verbose -Message "Checking current Hadr setting for $computer"
$currentState = Get-WmiHadr -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 = Get-WmiHadr -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#CodeStyle,Messaging,FlowControl,Pipeline#
function Disable-DbaFilestream {
<#
.SYNOPSIS
Sets the status of FileStream on specified SQL Server instances both at the server level and the instance level
.DESCRIPTION
Connects to the specified SQL Server instances, and sets the status of the FileStream feature to the required value
To perform the action, the SQL Server instance must be restarted. By default we will prompt for confirmation for this action, this can be overridden with the -Force switch
.PARAMETER SqlInstance
The target SQL Server instance or instances. Defaults to localhost.
.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 server using alternative credentials.
.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
Restart SQL Instance after changes. Use this parameter with care as it overrides whatif.
.PARAMETER WhatIf
Shows what would happen if the command runs. The command is not run unless Force is specified.
.PARAMETER Confirm
Prompts you for confirmation before running the command.
.NOTES
Tags: Filestream
Author: Stuart Moore ( @napalmgram ) | Chrissy LeMaire ( @cl )
Website: https://dbatools.io
Copyright: (c) 2018 by dbatools, licensed under MIT
License: MIT https://opensource.org/licenses/MIT
.EXAMPLE
PS C:\> Disable-DbaFilestream -SqlInstance server1\instance2
Prompts for confirmation. Disables filestream on the service and instance levels.
.EXAMPLE
PS C:\> Disable-DbaFilestream -SqlInstance server1\instance2 -Confirm:$false
Does not prompt for confirmation. Disables filestream on the service and instance levels.
.EXAMPLE
PS C:\> Get-DbaFilestream -SqlInstance server1\instance2, server5\instance5, prod\hr | Where-Object InstanceAccessLevel -gt 0 | Disable-DbaFilestream -Force
Using this pipeline you can scan a range of SQL instances and disable filestream on only those on which it's enabled.
#>
[CmdletBinding(SupportsShouldProcess, ConfirmImpact = "High")]
param (
[Parameter(Mandatory, ValueFromPipelineByPropertyName)]
[DbaInstance[]]$SqlInstance,
[Parameter(ValueFromPipelineByPropertyName)]
[PSCredential]$SqlCredential,
[Parameter(ValueFromPipelineByPropertyName)]
[PSCredential]$Credential,
[switch]$Force,
[switch]$EnableException
)
begin {
$FileStreamLevel = $level = 0
$OutputLookup = @{
0 = 'Disabled'
1 = 'FileStream enabled for T-Sql access'
2 = 'FileStream enabled for T-Sql and IO streaming access'
3 = 'FileStream enabled for T-Sql, IO streaming, and remote clients'
}
}
process {
foreach ($instance in $SqlInstance) {
try {
$server = Connect-SqlInstance -SqlInstance $instance -SqlCredential $SqlCredential
} catch {
Stop-Function -Message "Failure connecting to $computer" -Category ConnectionError -ErrorRecord $_ -Target $instance -Continue
}
# Instance level
$filestreamstate = [int]$server.Configuration.FilestreamAccessLevel.RunningValue
if ($Force -or $PSCmdlet.ShouldProcess($instance, "Changing from '$($OutputLookup[$filestreamstate])' to '$($OutputLookup[$level])' at the instance level")) {
try {
$null = Set-DbaSpConfigure -SqlInstance $server -Name FilestreamAccessLevel -Value $level -EnableException
} catch {
Stop-Function -Message "Failure" -ErrorRecord $_ -Continue
}
# Server level
if ($server.IsClustered) {
$nodes = Get-DbaWsfcNode -ComputerName $instance -Credential $Credential
foreach ($node in $nodes.Name) {
$result = Set-FileSystemSetting -Instance $node -Credential $Credential -FilestreamLevel $FileStreamLevel
}
} else {
$result = Set-FileSystemSetting -Instance $instance -Credential $Credential -FilestreamLevel $FileStreamLevel
}
if ($Force) {
#$restart replaced with $null as it was identified as a unused variable
$null = Restart-DbaService -ComputerName $instance.ComputerName -InstanceName $server.ServiceName -Type Engine -Force
}
Get-DbaFilestream -SqlInstance $instance -SqlCredential $SqlCredential -Credential $Credential
if ($filestreamstate -ne $level -and -not $Force) {
Write-Message -Level Warning -Message "[$instance] $result"
}
}
}
}
}
#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 instance or instances. 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.
.NOTES
Tags: Certificate
Author: Chrissy LeMaire (@cl), netnerds.net
Website: https://dbatools.io
Copyright: (c) 2018 by dbatools, licensed under MIT
License: MIT https://opensource.org/licenses/MIT
.EXAMPLE
PS C:\> Disable-DbaForceNetworkEncryption
Disables Force Encryption on the default (MSSQLSERVER) instance on localhost - requires (and checks for) RunAs admin.
.EXAMPLE
PS C:\> 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
PS C:\> Disable-DbaForceNetworkEncryption -SqlInstance sql01\SQL2008R2SP2 -WhatIf
Shows what would happen if the command were executed.
#>
[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
}
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 something
# here to avoid an empty catch
$null = 1
}
$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
#Variable marked as unused by PSScriptAnalyzer
#$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
The target SQL Server instance or instances.
.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, DBCC
Author: Garry Bargsley (@gbargsley), http://blog.garrybargsley.com
Website: https://dbatools.io
Copyright: (c) 2018 by dbatools, licensed under MIT
License: MIT https://opensource.org/licenses/MIT
.LINK
https://dbatools.io/Disable-DbaTraceFlag
.EXAMPLE
PS C:\> 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) {
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 target SQL Server instance or instances.
.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) 2018 by dbatools, licensed under MIT
License: MIT https://opensource.org/licenses/MIT
.LINK
https://dbatools.io/Dismount-DbaDatabase
.EXAMPLE
PS C:\> Detach-DbaDatabase -SqlInstance sql2016b -Database SharePoint_Config, WSS_Logging
Detaches SharePoint_Config and WSS_Logging from sql2016b
.EXAMPLE
PS C:\> 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
PS C:\> 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 target SQL Server instance or instances.
.PARAMETER Credential
Credential object used to connect to the Windows server 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, HA, AG, AvailabilityGroup
Author: Shawn Melton (@wsmelton), http://wsmelton.github.io
Website: https://dbatools.io
Copyright: (c) 2018 by dbatools, licensed under MIT
License: MIT https://opensource.org/licenses/MIT
.LINK
https://dbatools.io/Enable-DbaAgHadr
.EXAMPLE
PS C:\> Enable-DbaAgHadr -SqlInstance sql2016
Sets Hadr service to enabled for the instance sql2016 but changes will not be applied until the next time the server restarts.
.EXAMPLE
PS C:\> Enable-DbaAgHadr -SqlInstance sql2016 -Force
Sets Hadr service to enabled for the instance sql2016, and restart the service to apply the change.
.EXAMPLE
PS C:\> 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, ConfirmImpact = "High")]
param (
[parameter(Mandatory, ValueFromPipeline)]
[DbaInstanceParameter[]]$SqlInstance,
[PSCredential]$Credential,
[switch]$Force,
[switch]$EnableException
)
process {
foreach ($instance in $SqlInstance) {
$computer = $computerFullName = $instance.ComputerName
$instanceName = $instance.InstanceName
if (-not (Test-ElevationRequirement -ComputerName $instance)) {
return
}
$noChange = $false
<#
#Variable marked as unused by PSScriptAnalyzer
switch ($instance.InstanceName) {
'MSSQLSERVER' { $agentName = 'SQLSERVERAGENT' }
default { $agentName = "SQLAgent`$$instanceName" }
}
#>
try {
Write-Message -Level Verbose -Message "Checking current Hadr setting for $computer"
$currentState = Get-WmiHadr -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 = Get-WmiHadr -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#CodeStyle,Messaging,FlowControl,Pipeline#
function Enable-DbaFilestream {
<#
.SYNOPSIS
Enables FileStream on specified SQL Server instances
.DESCRIPTION
Connects to the specified SQL Server instances, and Enables the FileStream feature to the required value
To perform the action, the SQL Server instance must be restarted. By default we will prompt for confirmation for this action, this can be overridden with the -Force switch
.PARAMETER SqlInstance
The target SQL Server instance or instances. Defaults to localhost.
.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 server using alternative credentials.
.PARAMETER FileStreamLevel
The level to of FileStream to be enabled:
1 or TSql - T-Sql Access Only
2 or TSqlIoStreaming - T-Sql and Win32 access enabled
3 or TSqlIoStreamingRemoteClient T-Sql, Win32 and Remote access enabled
.PARAMETER ShareName
Specifies the Windows file share name to be used for storing the FILESTREAM data.
.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
Restart SQL Instance after changes. Use this parameter with care as it overrides whatif.
.PARAMETER WhatIf
Shows what would happen if the command runs. The command is not run unless Force is specified.
.PARAMETER Confirm
Prompts you for confirmation before running the command.
.NOTES
Tags: Filestream
Author: Stuart Moore ( @napalmgram ) | Chrissy LeMaire ( @cl )
Website: https://dbatools.io
Copyright: (c) 2018 by dbatools, licensed under MIT
License: MIT https://opensource.org/licenses/MIT
.EXAMPLE
PS C:\> Enable-DbaFilestream -SqlInstance server1\instance2 -FileStreamLevel TSql
PS C:\> Enable-DbaFilestream -SqlInstance server1\instance2 -FileStreamLevel 1
These commands are functionally equivalent, both will set Filestream level on server1\instance2 to T-Sql Only
.EXAMPLE
PS C:\> Get-DbaFilestream -SqlInstance server1\instance2, server5\instance5, prod\hr | Where-Object InstanceAccessLevel -eq 0 | Enable-DbaFilestream -FileStreamLevel TSqlIoStreamingRemoteClient -Force
Using this pipeline you can scan a range of SQL instances and enable filestream on only those on which it's disabled.
#>
[CmdletBinding(SupportsShouldProcess, ConfirmImpact = "Medium")]
param (
[Parameter(Mandatory, ValueFromPipelineByPropertyName)]
[DbaInstance[]]$SqlInstance,
[Parameter(ValueFromPipelineByPropertyName)]
[PSCredential]$SqlCredential,
[Parameter(ValueFromPipelineByPropertyName)]
[PSCredential]$Credential,
[ValidateSet("TSql", "TSqlIoStreaming", "TSqlIoStreamingRemoteClient", 1, 2, 3)]
[string]$FileStreamLevel = 1,
[string]$ShareName,
[switch]$Force,
[switch]$EnableException
)
begin {
if ($FileStreamLevel -notin (1, 2, 3)) {
$FileStreamLevel = switch ($FileStreamLevel) {
"TSql" {
1
}
"TSqlIoStreaming" {
2
}
"TSqlIoStreamingRemoteClient" {
3
}
}
}
# = $finallevel removed as it was identified as a unused variable
$level = [int]$FileStreamLevel
$OutputLookup = @{
0 = 'Disabled'
1 = 'FileStream enabled for T-Sql access'
2 = 'FileStream enabled for T-Sql and IO streaming access'
3 = 'FileStream enabled for T-Sql, IO streaming, and remote clients'
}
}
process {
if ($ShareName -and $level -lt 2) {
Stop-Function -Message "Filestream must be at least level 2 when using ShareName"
return
}
foreach ($instance in $SqlInstance) {
try {
$server = Connect-SqlInstance -SqlInstance $instance -SqlCredential $SqlCredential
} catch {
Stop-Function -Message "Failure connecting to $computer" -Category ConnectionError -ErrorRecord $_ -Target $instance -Continue
}
$filestreamstate = [int]$server.Configuration.FilestreamAccessLevel.ConfigValue
if ($Force -or $PSCmdlet.ShouldProcess($instance, "Changing from '$($OutputLookup[$filestreamstate])' to '$($OutputLookup[$level])' at the instance level")) {
# Server level
if ($server.IsClustered) {
$nodes = Get-DbaWsfcNode -ComputerName $instance
foreach ($node in $nodes.Name) {
$result = Set-FileSystemSetting -Instance $node -Credential $Credential -ShareName $ShareName -FilestreamLevel $level
}
} else {
$result = Set-FileSystemSetting -Instance $instance -Credential $Credential -ShareName $ShareName -FilestreamLevel $level
}
# Instance level
if ($level -eq 3) {
$level = 2
}
try {
$null = Set-DbaSpConfigure -SqlInstance $server -Name FilestreamAccessLevel -Value $level -EnableException
} catch {
Stop-Function -Message "Failure" -ErrorRecord $_ -Continue
}
if ($Force) {
#$restart replaced with $null as it was identified as a unused variable
$null = Restart-DbaService -ComputerName $server.ComputerName -InstanceName $server.ServiceName -Type Engine -Force
}
Get-DbaFilestream -SqlInstance $instance -SqlCredential $SqlCredential -Credential $Credential
if ($filestreamstate -ne $level -and -not $Force) {
Write-Message -Level Warning -Message "[$instance] $result"
}
}
}
}
}
#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 instance or instances.
.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.
.NOTES
Tags: Certificate, Encryption
Author: Chrissy LeMaire (@cl), netnerds.net
Website: https://dbatools.io
Copyright: (c) 2018 by dbatools, licensed under MIT
License: MIT https://opensource.org/licenses/MIT
.EXAMPLE
PS C:\> Enable-DbaForceNetworkEncryption
Enables Force Encryption on the default (MSSQLSERVER) instance on localhost. Requires (and checks for) RunAs admin.
.EXAMPLE
PS C:\> 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
PS C:\> Enable-DbaForceNetworkEncryption -SqlInstance sql01\SQL2008R2SP2 -WhatIf
Shows what would happen if the command were executed.
#>
[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
}
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 something
# here to avoid an empty catch
$null = 1
}
$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
#Variable marked as unused by PSScriptAnalyzer
#$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
The target SQL Server instance or instances.
.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, DBCC
Author: Garry Bargsley (@gbargsley), http://blog.garrybargsley.com
Website: https://dbatools.io
Copyright: (c) 2018 by dbatools, licensed under MIT
License: MIT https://opensource.org/licenses/MIT
.LINK
https://dbatools.io/Enable-DbaTraceFlag
.EXAMPLE
PS C:\> Enable-DbaTraceFlag -SqlInstance sql2016 -TraceFlag 3226
Enable the trace flag 3226 on SQL Server instance sql2016
.EXAMPLE
PS C:\> 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) {
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-DbaDbLogFile {
<#
.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 or instances.
.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 TargetLogSize
Specifies the target size of the transaction log file in megabytes.
.PARAMETER IncrementSize
Specifies the amount the transaction log should grow in megabytes. If this value differs from the suggested value based on your TargetLogSize, 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 ShrinkSize
Specifies the target size of the transaction log file for the shrink operation in megabytes.
.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 ExcludeDiskSpaceValidation
If this switch is enabled, the validation for enough disk space using Get-DbaDiskSpace command will be skipped.
This can be useful when you know that you have enough space to grow your TLog but you don't have PowerShell Remoting enabled to validate it.
.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
Author: Claudio Silva (@ClaudioESSilva)
Website: https://dbatools.io
Copyright: (c) 2018 by dbatools, licensed under MIT
License: MIT https://opensource.org/licenses/MIT
Requires: ALTER DATABASE permission
Limitations: Freespace cannot be validated on the directory where the log file resides in SQL Server 2005.
This script uses Get-DbaDiskSpace dbatools command to get the TLog's drive free space
.LINK
https://dbatools.io/Expand-DbaDbLogFile
.EXAMPLE
PS C:\> Expand-DbaDbLogFile -SqlInstance sqlcluster -Database db1 -TargetLogSize 50000
Grows the transaction log for database db1 on sqlcluster to 50000 MB and calculates the increment size.
.EXAMPLE
PS C:\> Expand-DbaDbLogFile -SqlInstance sqlcluster -Database db1, db2 -TargetLogSize 10000 -IncrementSize 200
Grows the transaction logs for databases db1 and db2 on sqlcluster to 1000MB and sets the growth increment to 200MB.
.EXAMPLE
PS C:\> Expand-DbaDbLogFile -SqlInstance sqlcluster -Database db1 -TargetLogSize 10000 -LogFileId 9
Grows the transaction log file with FileId 9 of the db1 database on sqlcluster instance to 10000MB.
.EXAMPLE
PS C:\> Expand-DbaDbLogFile -SqlInstance sqlcluster -Database (Get-Content D:\DBs.txt) -TargetLogSize 50000
Grows the transaction log of the databases specified in the file 'D:\DBs.txt' on sqlcluster instance to 50000MB.
.EXAMPLE
PS C:\> Expand-DbaDbLogFile -SqlInstance SqlInstance -Database db1,db2 -TargetLogSize 100 -IncrementSize 10 -ShrinkLogFile -ShrinkSize 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)]
[Alias('TargetLogSizeMB')]
[int]$TargetLogSize,
[parameter(Position = 6)]
[Alias('IncrementSizeMB')]
[int]$IncrementSize = -1,
[parameter(Position = 7)]
[int]$LogFileId = -1,
[parameter(Position = 8, ParameterSetName = 'Shrink', Mandatory)]
[switch]$ShrinkLogFile,
[parameter(Position = 9, ParameterSetName = 'Shrink', Mandatory)]
[Alias('ShrinkSizeMB')]
[int]$ShrinkSize,
[parameter(Position = 10, ParameterSetName = 'Shrink')]
[AllowEmptyString()]
[string]$BackupDirectory,
[switch]$ExcludeDiskSpaceValidation,
[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 = $TargetLogSize * 1024
[int]$LogIncrementSize = $IncrementSize * 1024
[int]$ShrinkSizeKB = $ShrinkSize * 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) {
$databases = $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/$($Databases.Count)"
if ($server.Databases[$db]) {
Write-Progress `
-Id 1 `
-Activity "Using database: $db on Instance: '$SqlInstance'" `
-PercentComplete ($databaseProgressbar / $Databases.Count * 100) `
-Status "Processing - $databaseProgressbar of $($Databases.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)
if ($ExcludeDiskSpaceValidation -eq $false) {
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"
try {
[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))"
} catch {
#Could not validate the disk space. Will ask if we want to continue.
$TotalTLogFreeDiskSpaceKB = 0
}
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
}
} elseif ($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
}
}
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 ($TargetLogSize) {
{ $_ -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 ($TargetLogSize) {
{ $_ -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 (($IncrementSize % 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)
$ShrinkSize = $ShrinkSizeKB / 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 -ShrinkSize 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 $ShrinkSize) {
$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 -eq $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($ShrinkSize, [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 $ShrinkSize -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 ($IncrementSize -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 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 * 1024)
CurrentSize = [dbasize]($TargetLogSize * 1024 * 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
Test-DbaDeprecation -DeprecatedOn "1.0.0" -Parameter TargetLogSizeMB
Test-DbaDeprecation -DeprecatedOn "1.0.0" -Parameter IncrementSizeMB
Test-DbaDeprecation -DeprecatedOn "1.0.0" -Parameter ShrinkSizeMB
Test-DbaDeprecation -DeprecatedOn "1.0.0" -Alias Expand-DbaTLogResponsibly
}
}
#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 target SQL Server instance or instances. SQL Server 2012 and above supported.
.PARAMETER SqlCredential
Login to the target instance using alternative credentials. Windows and SQL Authentication supported. Accepts credential objects (Get-Credential)
.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 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, HA, AG, AvailabilityGroup
Author: Chris Sommer (@cjsommer), cjsommer.com
dbatools PowerShell module (https://dbatools.io)
Copyright: (c) 2018 by dbatools, licensed under MIT
License: MIT https://opensource.org/licenses/MIT
.LINK
https://dbatools.io/Export-DbaAvailabilityGroup
.EXAMPLE
PS C:\> Export-DbaAvailabilityGroup -SqlInstance sql2012
Exports all Availability Groups from SQL server "sql2012". Output scripts are written to the Documents\SqlAgExports directory by default.
.EXAMPLE
PS C:\> 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
PS C:\> Export-DbaAvailabilityGroup -SqlInstance sql2012 -Path 'C:\dir with spaces\availability_group_exports' -AvailabilityGroup 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
PS C:\> 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)]
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) {
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
The target SQL Server instance or instances.
.PARAMETER SqlCredential
Login to the target instance using alternative credentials. Windows and SQL Authentication supported. Accepts credential objects (Get-Credential)
.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
Tags: RegisteredServer, CMS
Author: Chrissy LeMaire (@cl), netnerds.net
Website: https://dbatools.io
Copyright: (c) 2018 by dbatools, licensed under MIT
License: MIT https://opensource.org/licenses/MIT
.LINK
https://dbatools.io/Export-DbaCmsRegServer
.EXAMPLE
PS C:\> 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
PS C:\> 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
PS C:\> Get-DbaCmsRegServerGroup -SqlInstance sql2008, sql2012 | Export-DbaCmsRegServer
Exports all registered servers on sql2008 and sql2012, organized by group.
#>
[CmdletBinding()]
[Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSAvoidUsingPlainTextForPassword", "", Justification = "For Parameter CredentialPersistenceType")]
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
The target SQL Server instance or instances.
.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) 2018 by dbatools, licensed under MIT
License: MIT https://opensource.org/licenses/MIT
.EXAMPLE
PS C:\> 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 {
$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
The target SQL Server instance or instances.
.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 Type
Selecting the type of the export: Dacpac (default) or Bacpac.
.PARAMETER Table
List of the tables to include into the export. Should be provided as an array of strings: dbo.Table1, Table2, Schema1.Table3.
.PARAMETER DacOption
Export options for a corresponding export type. Can be created by New-DbaDacOption -Type Dacpac | Bacpac
.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 (@richiebzzzt)
Website: https://dbatools.io
Copyright: (c) 2018 by dbatools, licensed under MIT
License: MIT https://opensource.org/licenses/MIT
.LINK
https://dbatools.io/Export-DbaDacPackage
.EXAMPLE
PS C:\> Export-DbaDacPackage -SqlInstance sql2016 -Database SharePoint_Config
Exports the dacpac for SharePoint_Config on sql2016 to $home\Documents\SharePoint_Config.dacpac
.EXAMPLE
PS C:\> $options = New-DbaDacOption -Type Dacpac -Action Export
PS C:\> $options.ExtractAllTableData = $true
PS C:\> $options.CommandTimeout = 0
PS C:\> Export-DbaDacPackage -SqlInstance sql2016 -Database DB1 -Options $options
Uses DacOption object to set the CommandTimeout to 0 then extracts the dacpac for DB1 on sql2016 to $home\Documents\DB1.dacpac including all table data.
.EXAMPLE
PS C:\> Export-DbaDacPackage -SqlInstance sql2016 -AllUserDatabases -ExcludeDatabase "DBMaintenance","DBMonitoring" C:\temp
Exports dacpac packages for all USER databases, excluding "DBMaintenance" & "DBMonitoring", on sql2016 and saves them to C:\temp
.EXAMPLE
PS C:\> $moreparams = "/OverwriteFiles:$true /Quiet:$true"
PS C:\> Export-DbaDacPackage -SqlInstance sql2016 -Database SharePoint_Config -Path C:\temp -ExtendedParameters $moreparams
Using extended parameters to over-write the files and performs the extraction in quiet mode. Uses command line instead of SMO behind the scenes.
#>
[CmdletBinding(DefaultParameterSetName = 'SMO')]
param
(
[parameter(Mandatory, ValueFromPipeline)]
[Alias("ServerInstance", "SqlServer")]
[DbaInstance[]]$SqlInstance,
[Alias("Credential")]
[PSCredential]$SqlCredential,
[object[]]$Database,
[object[]]$ExcludeDatabase,
[switch]$AllUserDatabases,
[string]$Path = "$home\Documents",
[parameter(ParameterSetName = 'SMO')]
[Alias('ExtractOptions', 'ExportOptions', 'DacExtractOptions', 'DacExportOptions', 'Options', 'Option'