Unpacking Software Livestream

Join our monthly Unpacking Software livestream to hear about the latest news, chat and opinion on packaging, software deployment and lifecycle management!

Learn More

Chocolatey Product Spotlight

Join the Chocolatey Team on our regular monthly stream where we put a spotlight on the most recent Chocolatey product releases. You'll have a chance to have your questions answered in a live Ask Me Anything format.

Learn More

Chocolatey Coding Livestream

Join us for the Chocolatey Coding Livestream, where members of our team dive into the heart of open source development by coding live on various Chocolatey projects. Tune in to witness real-time coding, ask questions, and gain insights into the world of package management. Don't miss this opportunity to engage with our team and contribute to the future of Chocolatey!

Learn More

Calling All Chocolatiers! Whipping Up Windows Automation with Chocolatey Central Management

Webinar from
Wednesday, 17 January 2024

We are delighted to announce the release of Chocolatey Central Management v0.12.0, featuring seamless Deployment Plan creation, time-saving duplications, insightful Group Details, an upgraded Dashboard, bug fixes, user interface polishing, and refined documentation. As an added bonus we'll have members of our Solutions Engineering team on-hand to dive into some interesting ways you can leverage the new features available!

Watch On-Demand
Chocolatey Community Coffee Break

Join the Chocolatey Team as we discuss all things Community, what we do, how you can get involved and answer your Chocolatey questions.

Watch The Replays
Chocolatey and Intune Overview

Webinar Replay from
Wednesday, 30 March 2022

At Chocolatey Software we strive for simple, and teaching others. Let us teach you just how simple it could be to keep your 3rd party applications updated across your devices, all with Intune!

Watch On-Demand
Chocolatey For Business. In Azure. In One Click.

Livestream from
Thursday, 9 June 2022

Join James and Josh to show you how you can get the Chocolatey For Business recommended infrastructure and workflow, created, in Azure, in around 20 minutes.

Watch On-Demand
The Future of Chocolatey CLI

Livestream from
Thursday, 04 August 2022

Join Paul and Gary to hear more about the plans for the Chocolatey CLI in the not so distant future. We'll talk about some cool new features, long term asks from Customers and Community and how you can get involved!

Watch On-Demand
Hacktoberfest Tuesdays 2022

Livestreams from
October 2022

For Hacktoberfest, Chocolatey ran a livestream every Tuesday! Re-watch Cory, James, Gary, and Rain as they share knowledge on how to contribute to open-source projects such as Chocolatey CLI.

Watch On-Demand

Downloads:

187,834

Downloads of v 0.9.395:

269

Last Update:

28 Aug 2018

Package Maintainer(s):

Software Author(s):

  • Chrissy LeMaire

Tags:

admin powershell module template dba sqlserver sql tools database

dbatools (PowerShell Module)

This is not the latest version of dbatools (PowerShell Module) available.

  • 1
  • 2
  • 3

0.9.395 | Updated: 28 Aug 2018

Downloads:

187,834

Downloads of v 0.9.395:

269

Maintainer(s):

Software Author(s):

  • Chrissy LeMaire

dbatools (PowerShell Module) 0.9.395

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

Some Checks Have Failed or Are Not Yet Complete

Not All Tests Have Passed


Validation Testing Passed


Verification Testing Passed

Details

Scan Testing Resulted in Flagged:

This package was submitted (and approved) prior to automated virus scanning integration into the package moderation processs.

We recommend clicking the "Details" link to make your own decision on installing this package.

Details
Learn More

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:

NOTE

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

  • 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

3. Copy Your Script

choco upgrade dbatools -y --source="'INTERNAL REPO URL'" --version="'0.9.395'" [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.395'" 
$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.395'
    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.395'
end

See docs at https://docs.chef.io/resource_chocolatey_package.html.


cChocoPackageInstaller dbatools
{
    Name     = "dbatools"
    Version  = "0.9.395"
    Source   = "INTERNAL REPO URL"
}

Requires cChoco DSC Resource. See docs at https://github.com/chocolatey/cChoco.


package { 'dbatools':
  ensure   => '0.9.395',
  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.

Package Approved

This package was approved as a trusted package on 29 Aug 2018.

Description

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.


tools\.skipAutoUninstaller
 
tools\chocolateyBeforeModify.ps1
$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 
        }
    }
}
tools\chocolateyInstall.ps1
$ErrorActionPreference = 'Stop'

$toolsDir   = "$(Split-Path -parent $MyInvocation.MyCommand.Definition)"
$moduleName = 'dbatools'  # this may be different from the package name and different case

if ($PSVersionTable.PSVersion.Major -lt 3) {
    throw "$moduleName) module requires a minimum of PowerShell v3."
}

# module may already be installed outside of Chocolatey
Remove-Module -Name $moduleName -Force -ErrorAction SilentlyContinue

$sourcePath = Join-Path -Path $toolsDir -ChildPath "$modulename\*"
$destPath   = Join-Path -Path $env:ProgramFiles -ChildPath "WindowsPowerShell\Modules\$moduleName"

if ($PSVersionTable.PSVersion.Major -ge 5)
{
    $manifestFile = Join-Path -Path $toolsDir -ChildPath "$moduleName\$moduleName.psd1"
    $manifest     = Test-ModuleManifest -Path $manifestFile -WarningAction Ignore -ErrorAction Stop
    $destPath     = Join-Path -Path $destPath -ChildPath $manifest.Version.ToString()
}

Write-Verbose "Creating destination directory '$destPath' for module."
New-Item -Path $destPath -ItemType Directory -Force -ErrorAction SilentlyContinue | Out-Null

Write-Verbose "Moving '$moduleName' files from '$sourcePath' to '$destPath'."
Move-Item -Path $sourcePath -Destination $destPath -Force

if ($PSVersionTable.PSVersion.Major -lt 4)
{
    $modulePaths = [Environment]::GetEnvironmentVariable('PSModulePath', 'Machine') -split ';'
    if ($modulePaths -notcontains $destPath)
    {
        Write-Verbose "Adding '$destPath' to PSModulePath."
        $newModulePath = @($destPath, $modulePaths) -join ';'

        [Environment]::SetEnvironmentVariable('PSModulePath', $newModulePath, 'Machine')
        $env:PSModulePath = $newModulePath
    }
}
tools\chocolateyUninstall.ps1
$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
}
tools\dbatools\allcommands.ps1
### DO NOT EDIT THIS FILE DIRECTLY ###
#ValidationTags#Messaging,FlowControl,Pipeline,CodeStyle#
function Add-DbaComputerCertificate {
    <#
        .SYNOPSIS
            Adds a computer certificate - useful for older systems.

        .DESCRIPTION
            Adds a computer certificate from a local or remote computer.

        .PARAMETER ComputerName
            The target SQL Server. Defaults to localhost.

        .PARAMETER Credential
            Allows you to login to $ComputerName using alternative credentials.

        .PARAMETER Password
            The password for the certificate, if it is password protected.

        .PARAMETER Certificate
            The target certificate object.

        .PARAMETER Path
            The local path to the target certificate object.

        .PARAMETER Store
            Certificate store. Default is LocalMachine.

        .PARAMETER Folder
            Certificate folder. Default is My (Personal).

        .PARAMETER EnableException
            By default, when something goes wrong we try to catch it, interpret it and give you a friendly warning message.
            This avoids overwhelming you with "sea of red" exceptions, but is inconvenient because it basically disables advanced scripting.
            Using this switch turns this "nice by default" feature off and enables you to catch exceptions with your own try/catch.

        .PARAMETER WhatIf
            If this switch is enabled, no actions are performed but informational messages will be displayed that explain what would happen if the command were to run.

        .PARAMETER Confirm
            If this switch is enabled, you will be prompted for confirmation before executing any operations that change state.

        .NOTES
            Tags: Certificate

            Website: https://dbatools.io
            Copyright: (C) Chrissy LeMaire, [email protected]
            License: MIT https://opensource.org/licenses/MIT

        .EXAMPLE
            Add-DbaComputerCertificate -ComputerName Server1 -Path C:\temp\cert.cer

            Adds the local C:\temp\cert.cer to the remote server Server1 in LocalMachine\My (Personal).

        .EXAMPLE
            Add-DbaComputerCertificate -Path C:\temp\cert.cer

            Adds the local C:\temp\cert.cer to the local computer's LocalMachine\My (Personal) certificate store.
    #>
    [CmdletBinding(SupportsShouldProcess, ConfirmImpact = "High")]
    param (
        [Alias("ServerInstance", "SqlServer", "SqlInstance")]
        [DbaInstance[]]$ComputerName = $env:COMPUTERNAME,
        [PSCredential]$Credential,
        [securestring]$Password,
        [parameter(ValueFromPipeline)]
        [System.Security.Cryptography.X509Certificates.X509Certificate2[]]$Certificate,
        [string]$Path,
        [string]$Store = "LocalMachine",
        [string]$Folder = "My",
        [Alias('Silent')]
        [switch]$EnableException
    )

    begin {

        if ($Path) {
            if (!(Test-Path -Path $Path)) {
                Stop-Function -Message "Path ($Path) does not exist." -Category InvalidArgument
                return
            }

            try {
                # This may be too much, but ¯\_(ツ)_/¯
                $bytes = [System.IO.File]::ReadAllBytes($Path)
                $Certificate = New-Object System.Security.Cryptography.X509Certificates.X509Certificate2
                $Certificate.Import($bytes, $Password, [System.Security.Cryptography.X509Certificates.X509KeyStorageFlags]::DefaultKeySet)
            }
            catch {
                Stop-Function -Message "Can't import certificate." -ErrorRecord $_
                return
            }
        }

        #region Remoting Script
        $scriptBlock = {

            param (
                $CertificateData,

                [securestring]$Password,

                $Store,

                $Folder
            )

            $cert = New-Object System.Security.Cryptography.X509Certificates.X509Certificate2
            $cert.Import($CertificateData, $Password, [System.Security.Cryptography.X509Certificates.X509KeyStorageFlags]::DefaultKeySet)
            Write-Message -Level Verbose -Message "Importing cert to $Folder\$Store"
            $tempStore = New-Object System.Security.Cryptography.X509Certificates.X509Store($Folder, $Store)
            $tempStore.Open('ReadWrite')
            $tempStore.Add($cert)
            $tempStore.Close()

            Write-Message -Level Verbose -Message "Searching Cert:\$Store\$Folder"
            Get-ChildItem "Cert:\$Store\$Folder" -Recurse | Where-Object { $_.Thumbprint -eq $cert.Thumbprint }
        }
        #endregion Remoting Script
    }
    process {
        if (Test-FunctionInterrupt) { return }

        if (-not $Certificate) {
            Stop-Function -Message "You must specify either Certificate or Path" -Category InvalidArgument
            return
        }

        foreach ($cert in $Certificate) {

            try {
                $certData = $cert.Export([System.Security.Cryptography.X509Certificates.X509ContentType]::PFX, $Password)
            }
            catch {
                Stop-Function -Message "Can't export certificate" -ErrorRecord $_ -Continue
            }

            foreach ($computer in $ComputerName) {

                if ($PScmdlet.ShouldProcess("local", "Connecting to $computer to import cert")) {
                    try {
                        Invoke-Command2 -ComputerName $computer -Credential $Credential -ArgumentList $certdata, $Password, $Store, $Folder -ScriptBlock $scriptblock -ErrorAction Stop |
                            Select-DefaultView -Property FriendlyName, DnsNameList, Thumbprint, NotBefore, NotAfter, Subject, Issuer
                    }
                    catch {
                        Stop-Function -Message "Failure" -ErrorRecord $_ -Target $computer -Continue
                    }
                }
            }
        }
    }
}
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
            Website: https://dbatools.io
            Copyright: (C) Chrissy LeMaire, [email protected]
            License: MIT https://opensource.org/licenses/MIT
    
        .LINK
            https://dbatools.io/Add-DbaPfDataCollectorCounter

        .EXAMPLE
            Add-DbaPfDataCollectorCounter -ComputerName sql2017 -CollectorSet 'System Correlation' -Collector DataCollector01  -Counter '\LogicalDisk(*)\Avg. Disk Queue Length'
    
            Adds the '\LogicalDisk(*)\Avg. Disk Queue Length' counter within the DataCollector01 collector within the System Correlation collector set on sql2017.
    
        .EXAMPLE
            Get-DbaPfDataCollector | Out-GridView -PassThru | Add-DbaPfDataCollectorCounter -Counter '\LogicalDisk(*)\Avg. Disk Queue Length' -Confirm
    
            Allows you to select which Data Collector you'd like to add the counter '\LogicalDisk(*)\Avg. Disk Queue Length' on localhost and prompts for confirmation.
    #>
    [CmdletBinding(SupportsShouldProcess, ConfirmImpact = "Low")]
    param (
        [DbaInstance[]]$ComputerName = $env:COMPUTERNAME,
        [PSCredential]$Credential,
        [Alias("DataCollectorSet")]
        [string[]]$CollectorSet,
        [Alias("DataCollector")]
        [string[]]$Collector,
        [Alias("Name")]
        [parameter(Mandatory, ValueFromPipelineByPropertyName)]
        [object[]]$Counter,
        [parameter(ValueFromPipeline)]
        [object[]]$InputObject,
        [switch]$EnableException
    )
    begin {
        $setscript = {
            $setname = $args[0]; $Addxml = $args[1]
            $set = New-Object -ComObject Pla.DataCollectorSet
            $set.SetXml($Addxml)
            $set.Commit($setname, $null, 0x0003) #add or modify.
            $set.Query($setname, $Null)
        }
    }
    process {
        if ($InputObject.Credential -and (Test-Bound -ParameterName Credential -Not)) {
            $Credential = $InputObject.Credential
        }
        
        if (($InputObject | Get-Member -MemberType NoteProperty -ErrorAction SilentlyContinue).Count -le 3 -and $InputObject.ComputerName -and $InputObject.Name) {
            # it's coming from Get-DbaPfAvailableCounter
            $ComputerName = $InputObject.ComputerName
            $Counter = $InputObject.Name
            $InputObject = $null
        }
        
        if (-not $InputObject -or ($InputObject -and (Test-Bound -ParameterName ComputerName))) {
            foreach ($computer in $ComputerName) {
                $InputObject += Get-DbaPfDataCollector -ComputerName $computer -Credential $Credential -CollectorSet $CollectorSet -Collector $Collector
            }
        }
        
        if ($InputObject) {
            if (-not $InputObject.DataCollectorObject) {
                Stop-Function -Message "InputObject is not of the right type. Please use Get-DbaPfDataCollector or Get-DbaPfAvailableCounter."
                return
            }
        }
        
        foreach ($object in $InputObject) {
            $computer = $InputObject.ComputerName
            $null = Test-ElevationRequirement -ComputerName $computer -Continue
            $setname = $InputObject.DataCollectorSet
            $collectorname = $InputObject.Name
            $xml = [xml]($InputObject.DataCollectorSetXml)
            
            foreach ($countername in $counter) {
                $node = $xml.SelectSingleNode("//Name[.='$collectorname']")
                $newitem = $xml.CreateElement('Counter')
                $null = $newitem.PsBase.InnerText = $countername
                $null = $node.ParentNode.AppendChild($newitem)
                $newitem = $xml.CreateElement('CounterDisplayName')
                $null = $newitem.PsBase.InnerText = $countername
                $null = $node.ParentNode.AppendChild($newitem)
            }
            $plainxml = $xml.OuterXml
            
            if ($Pscmdlet.ShouldProcess("$computer", "Adding $counters to $collectorname with the $setname collection set")) {
                try {
                    $results = Invoke-Command2 -ComputerName $computer -Credential $Credential -ScriptBlock $setscript -ArgumentList $setname, $plainxml -ErrorAction Stop
                    Write-Message -Level Verbose -Message " $results"
                    Get-DbaPfDataCollectorCounter -ComputerName $computer -Credential $Credential -CollectorSet $setname -Collector $collectorname -Counter $counter
                }
                catch {
                    Stop-Function -Message "Failure importing $Countername to $computer." -ErrorRecord $_ -Target $computer -Continue
                }
            }
        }
    }
}
#ValidationTags#Messaging,FlowControl,Pipeline,CodeStyle#
function Add-DbaRegisteredServer {
    <#
        .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-DbaRegisteredServer which
            accepts multiple kinds of input and allows you to add reg servers from different CMSes.

        .PARAMETER SqlInstance
            The target SQL Server instance

        .PARAMETER SqlCredential
            Login to the target instance using alternative credentials. Windows and SQL Authentication supported. Accepts credential objects (Get-Credential)

        .PARAMETER ServerName
            Server Name is the actual SQL instance name (labeled Server Name)

        .PARAMETER Name
            Name is basically the nickname in SSMS CMS interface (labeled Registered Server Name)

        .PARAMETER Description
            Adds a description for the registered server

        .PARAMETER Group
            Adds the registered server to a specific group.

        .PARAMETER InputObject
            Allows the piping of a registered server group

        .PARAMETER WhatIf
            Shows what would happen if the command were to run. No actions are actually performed.

        .PARAMETER Confirm
            Prompts you for confirmation before executing any changing operations within the command.

        .PARAMETER EnableException
            By default, when something goes wrong we try to catch it, interpret it and give you a friendly warning message.

            This avoids overwhelming you with "sea of red" exceptions, but is inconvenient because it basically disables advanced scripting.

            Using this switch turns this "nice by default" feature off and enables you to catch exceptions with your own try/catch.

        .NOTES
            Author: Chrissy LeMaire (@cl)
            Tags: RegisteredServer, CMS

            Website: https://dbatools.io
            Copyright: (C) Chrissy LeMaire, [email protected]
            License: MIT https://opensource.org/licenses/MIT

        .LINK
            https://dbatools.io/Add-DbaRegisteredServer

        .EXAMPLE
           Add-DbaRegisteredServer -SqlInstance sql2008 -ServerName sql01

           Creates a registered server on sql2008's CMS which points to the SQL Server, sql01. When scrolling in CMS, the name "sql01" will be visible.

        .EXAMPLE
           Add-DbaRegisteredServer -SqlInstance sql2008 -ServerName sql01 -Name "The 2008 Clustered Instance" -Description "HR's Dedicated SharePoint instance"

           Creates a registered server on sql2008's CMS which points to the SQL Server, sql01. When scrolling in CMS, "The 2008 Clustered Instance" will be visible.
           Clearly this is hard to explain ;)

        .EXAMPLE
           Add-DbaRegisteredServer -SqlInstance sql2008 -ServerName sql01 -Group hr\Seattle

           Creates a registered server on sql2008's CMS which points to the SQL Server, sql01. When scrolling in CMS, the name "sql01" will be visible within the Seattle group which is in the hr group.

        .EXAMPLE
           Get-DbaRegisteredServerGroup -SqlInstance sql2008 -Group hr\Seattle | Add-DbaRegisteredServer -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-DbaRegisteredServerGroup -SqlInstance $instance -SqlCredential $SqlCredential -Group $Group.Name
                }
                else {
                    $InputObject += Get-DbaRegisteredServerGroup -SqlInstance $instance -SqlCredential $SqlCredential -Group $Group
                }
            }
            else {
                $InputObject += Get-DbaRegisteredServerGroup -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-DbaRegisteredServer -SqlInstance $server -Name $Name -ServerName $ServerName
                }
                catch {
                    Stop-Function -Message "Failed to add $ServerName on $($parentserver.SqlInstance)" -ErrorRecord $_ -Continue
                }
            }
        }
    }
}
#ValidationTags#Messaging,FlowControl,Pipeline,CodeStyle#
function Add-DbaRegisteredServerGroup {
    <#
        .SYNOPSIS
            Adds registered server groups to SQL Server Central Management Server (CMS)

        .DESCRIPTION
            Adds registered server groups to SQL Server Central Management Server (CMS). If you need more flexiblity, look into Import-DbaRegisteredServer which
            accepts multiple kinds of input and allows you to add reg servers and groups from different CMSes.

        .PARAMETER SqlInstance
            The target SQL Server instance

        .PARAMETER SqlCredential
            Login to the target instance using alternative credentials. Windows and SQL Authentication supported. Accepts credential objects (Get-Credential)

        .PARAMETER Name
            The name of the registered server group

        .PARAMETER Description
            The description for the registered server group

        .PARAMETER Group
            The SQL Server Central Management Server group. If no groups are specified, the new group will be created at the root.

        .PARAMETER InputObject
            Allows results from Get-DbaRegisteredServerGroup to be piped in

        .PARAMETER IncludeRegisteredServers
            Create the registered servers within the group, too

        .PARAMETER InputObject
            Allows results from Get-DbaRegisteredServerGroup to be piped in

        .PARAMETER WhatIf
            Shows what would happen if the command were to run. No actions are actually performed.

        .PARAMETER Confirm
            Prompts you for confirmation before executing any changing operations within the command.

        .PARAMETER EnableException
            By default, when something goes wrong we try to catch it, interpret it and give you a friendly warning message.

            This avoids overwhelming you with "sea of red" exceptions, but is inconvenient because it basically disables advanced scripting.

            Using this switch turns this "nice by default" feature off and enables you to catch exceptions with your own try/catch.

        .NOTES
            Author: Chrissy LeMaire (@cl)
            Tags: RegisteredServer, CMS

            Website: https://dbatools.io
            Copyright: (C) Chrissy LeMaire, [email protected]
            License: MIT https://opensource.org/licenses/MIT

        .LINK
            https://dbatools.io/Add-DbaRegisteredServerGroup

        .EXAMPLE
            Add-DbaRegisteredServerGroup -SqlInstance sql2012 -Name HR

            Creates a registered server group called HR, in the root of sql2012's CMS

        .EXAMPLE
            Add-DbaRegisteredServerGroup -SqlInstance sql2012, sql2014 -Name subfolder -Group HR

            Creates a registered server group on sql2012 and sql2014 called subfolder within the HR group

    .EXAMPLE
            Get-DbaRegisteredServerGroup -SqlInstance sql2012, sql2014 -Group HR | Add-DbaRegisteredServerGroup -Name subfolder

            Creates a registered server group on sql2012 and sql2014 called subfolder within the HR group of each server
    #>
    [CmdletBinding(SupportsShouldProcess)]
    param (
        [Alias("ServerInstance", "SqlServer")]
        [DbaInstanceParameter[]]$SqlInstance,
        [PSCredential]$SqlCredential,
        [parameter(Mandatory)]
        [string]$Name,
        [string]$Description,
        [string]$Group,
        [parameter(ValueFromPipeline)]
        [Microsoft.SqlServer.Management.RegisteredServers.ServerGroup[]]$InputObject,
        [switch]$EnableException
    )
    process {
        if (-not $InputObject -and -not $SqlInstance) {
            Stop-Function -Message "You must either pipe in a registered server group or specify a sqlinstance"
            return
        }
        foreach ($instance in $SqlInstance) {
            if ((Test-Bound -ParameterName Group)) {
                $InputObject += Get-DbaRegisteredServerGroup -SqlInstance $instance -SqlCredential $SqlCredential -Group $Group
            }
            else {
                $InputObject += Get-DbaRegisteredServerGroup -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-DbaRegisteredServerGroup -SqlInstance $parentserver.ServerConnection.SqlConnectionObject -Group (Get-RegServerGroupReverseParse -object $newgroup)
                    $parentserver.ServerConnection.Disconnect()
                }
                catch {
                    Stop-Function -Message "Failed to add $reggroup on $server" -ErrorRecord $_ -Continue
                }
            }
        }
    }
}
function Backup-DbaDatabase {
    <#
            .SYNOPSIS
                Backup one or more SQL Sever databases from a single SQL Server SqlInstance.

            .DESCRIPTION
                Performs a backup of a specified type of 1 or more databases on a single SQL Server Instance. These backups may be Full, Differential or Transaction log backups.

            .PARAMETER SqlInstance
                The SQL Server instance hosting the databases to be backed up.

            .PARAMETER SqlCredential
                Credentials to connect to the SQL Server instance if the calling user doesn't have permission.

            .PARAMETER Database
                The database(s) to process. This list is auto-populated from the server. If unspecified, all databases will be processed.

            .PARAMETER ExcludeDatabase
                The database(s) to exclude. This list is auto-populated from the server.

            .PARAMETER BackupFileName
                The name of the file to backup to. This is only accepted for single database backups.
                If no name is specified then the backup files will be named DatabaseName_yyyyMMddHHmm (i.e. "Database1_201714022131") with the appropriate extension.

                If the same name is used repeatedly, SQL Server will add backups to the same file at an incrementing position.

                SQL Server needs permissions to write to the specified location. Path names are based on the SQL Server (C:\ is the C drive on the SQL Server, not the machine running the script).

            .PARAMETER BackupDirectory
                Path in which to place the backup files. If not specified, the backups will be placed in the default backup location for SqlInstance.
                If multiple paths are specified, the backups will be striped across these locations. This will overwrite the FileCount option.

                If the path does not exist, Sql Server will attempt to create it. Folders are created by the Sql Instance, and checks will be made for write permissions.

                File Names with be suffixed with x-of-y to enable identifying striped sets, where y is the number of files in the set and x ranges from 1 to y.

            .PARAMETER CopyOnly
                If this switch is enabled, CopyOnly backups will be taken. By default function performs a normal backup, these backups interfere with the restore chain of the database. CopyOnly backups will not interfere with the restore chain of the database.

                For more details please refer to this MSDN article - https://msdn.microsoft.com/en-us/library/ms191495.aspx

            .PARAMETER Type
                The type of SQL Server backup to perform. Accepted values are "Full", "Log", "Differential", "Diff", "Database"

            .PARAMETER FileCount
                This is the number of striped copies of the backups you wish to create.    This value is overwritten if you specify multiple Backup Directories.

            .PARAMETER CreateFolder
                If this switch is enabled, each database will be backed up into a separate folder on each of the paths specified by BackupDirectory.

            .PARAMETER CompressBackup
                If this switch is enabled, the function will try to perform a compressed backup if supported by the version and edition of SQL Server. Otherwise, this function will use the server's default setting for compression.

            .PARAMETER MaxTransferSize
                Sets the size of the unit of transfer. Values must be a multiple of 64kb.

            .PARAMETER Blocksize
                Specifies the block size to use. Must be one of 0.5KB, 1KB, 2KB, 4KB, 8KB, 16KB, 32KB or 64KB. This can be specified in bytes.
                Refer to https://msdn.microsoft.com/en-us/library/ms178615.aspx for more detail

            .PARAMETER BufferCount
                Number of I/O buffers to use to perform the operation.
                Refer to https://msdn.microsoft.com/en-us/library/ms178615.aspx for more detail

            .PARAMETER Checksum
                If this switch is enabled, the backup checksum will be calculated.

            .PARAMETER Verify
                If this switch is enabled, the backup will be verified by running a RESTORE VERIFYONLY against the SqlInstance

            .PARAMETER WithFormat
                 Formats the media as the first step of the backup operation. NOTE: This will set Initialize and SkipTapeHeader to $true.

            .PARAMETER Initialize
                 Initializes the media as part of the backup operation.

            .PARAMETER SkipTapeHeader
                 Initializes the media as part of the backup operation.

            .PARAMETER InputObject
                Internal parameter

            .PARAMETER AzureBaseUrl
                The URL to the basecontainer of an Azure storage account to write backups to.

                If specified, the only other parameters than can be used are "NoCopyOnly", "Type", "CompressBackup", "Checksum", "Verify", "AzureCredential", "CreateFolder".

            .PARAMETER AzureCredential
                The name of the credential on the SQL instance that can write to the AzureBaseUrl.

            .PARAMETER NoRecovery
                This is passed in to perform a tail log backup if needed

            .PARAMETER BuildPath
                By default this command won't attempt to create missing paths, this switch will change the behavious so that it wll

            .PARAMETER IgnoreFileChecks
                This switch stops the function from checking for the validity of paths. This can be useful if SQL Server only has read access to the backup area.
                Note, that as we can't check the path you may well end up with errors.

            .PARAMETER OutputScriptOnly
                Switch causes only the T-SQL script for the backup to be generated. Will not create any paths if they do not exist

            .PARAMETER EnableException
                By default, when something goes wrong we try to catch it, interpret it and give you a friendly warning message.
                This avoids overwhelming you with "sea of red" exceptions, but is inconvenient because it basically disables advanced scripting.
                Using this switch turns this "nice by default" feature off and enables you to catch exceptions with your own try/catch.

            .PARAMETER WhatIf
                If this switch is enabled, no actions are performed but informational messages will be displayed that explain what would happen if the command were to run.

            .PARAMETER Confirm
                If this switch is enabled, you will be prompted for confirmation before executing any operations that change state.

            .NOTES
                Tags: DisasterRecovery, Backup, Restore
                Author: Stuart Moore (@napalmgram), stuart-moore.com

                Website: https://dbatools.io
                Copyright: (C) Chrissy LeMaire, [email protected]
                License: MIT https://opensource.org/licenses/MIT

            .EXAMPLE
                Backup-DbaDatabase -SqlInstance Server1 -Database HR, Finance

                This will perform a full database backup on the databases HR and Finance on SQL Server Instance Server1 to Server1's default backup directory.

            .EXAMPLE
                Backup-DbaDatabase -SqlInstance sql2016 -BackupDirectory C:\temp -Database AdventureWorks2014 -Type Full

                Backs up AdventureWorks2014 to sql2016's C:\temp folder.

            .EXAMPLE
                Backup-DbaDatabase -SqlInstance sql2016 -AzureBaseUrl https://dbatoolsaz.blob.core.windows.net/azbackups/ -AzureCredential dbatoolscred -Type Full -CreateFolder

                Performs a full backup of all databases on the sql2016 instance to their own containers under the https://dbatoolsaz.blob.core.windows.net/azbackups/ container on Azure blog storage using the sql credential "dbatoolscred" registered on the sql2016 instance.
    #>
    [CmdletBinding(DefaultParameterSetName = "Default", SupportsShouldProcess)]
    [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSAvoidUsingPlainTextForPassword", "")] #For AzureCredential
    param (
        [parameter(ParameterSetName = "Pipe", Mandatory = $true)]
        [DbaInstanceParameter]$SqlInstance,
        [PSCredential]$SqlCredential,
        [Alias("Databases")]
        [object[]]$Database,
        [object[]]$ExcludeDatabase,
        [string[]]$BackupDirectory,
        [string]$BackupFileName,
        [switch]$CopyOnly,
        [ValidateSet('Full', 'Log', 'Differential', 'Diff', 'Database')]
        [string]$Type = 'Database',
        [parameter(ParameterSetName = "NoPipe", Mandatory = $true, ValueFromPipeline = $true)]
        [object[]]$InputObject,
        [switch]$CreateFolder,
        [int]$FileCount = 0,
        [switch]$CompressBackup,
        [switch]$Checksum,
        [switch]$Verify,
        [int]$MaxTransferSize,
        [int]$BlockSize,
        [int]$BufferCount,
        [string]$AzureBaseUrl,
        [string]$AzureCredential,
        [switch]$NoRecovery,
        [switch]$BuildPath,
        [switch]$WithFormat,
        [switch]$Initialize,
        [switch]$SkipTapeHeader,
        [switch]$IgnoreFileChecks,
        [switch]$OutputScriptOnly,
        [Alias('Silent')]
        [switch]$EnableException
    )

    begin {

        if ($SqlInstance.length -ne 0) {
            Write-Message -Level Verbose -Message "Connecting to $SqlInstance"
            try {
                $Server = Connect-SqlInstance -SqlInstance $SqlInstance -SqlCredential $SqlCredential -AzureUnsupported
            }
            catch {
                Stop-Function -Message "Cannot connect to $SqlInstance" -ErrorRecord $_
                return
            }

            if ($Database) {
                $InputObject = $server.Databases | Where-Object Name -in $Database
            }
            else {
                $InputObject = $server.Databases | Where-Object Name -ne 'tempdb'
            }

            if ($ExcludeDatabase) {
                $InputObject = $InputObject | Where-Object Name -notin $ExcludeDatabase
            }

            if ($null -eq $BackupDirectory -and $backupfileName -ne 'NUL') {
                Write-Message -Message 'No backupfolder passed in, setting it to instance default'
                $BackupDirectory = (Get-DbaDefaultPath -SqlInstance $SqlInstance).Backup
            }

            if ($BackupDirectory.Count -gt 1) {
                Write-Message -Level Verbose -Message "Multiple Backup Directories, striping"
                $Filecount = $BackupDirectory.Count
            }

            if ($InputObject.Count -gt 1 -and $BackupFileName -ne '') {
                Stop-Function -Message "1 BackupFile specified, but more than 1 database."
                return
            }

            if (($MaxTransferSize % 64kb) -ne 0 -or $MaxTransferSize -gt 4mb) {
                Stop-Function -Message "MaxTransferSize value must be a multiple of 64kb and no greater than 4MB"
                return
            }
            if ($BlockSize) {
                if ($BlockSize -notin (0.5kb, 1kb, 2kb, 4kb, 8kb, 16kb, 32kb, 64kb)) {
                    Stop-Function -Message "Block size must be one of 0.5kb,1kb,2kb,4kb,8kb,16kb,32kb,64kb"
                    return
                }
            }
            if ('' -ne $AzureBaseUrl) {
                if ($null -eq $AzureCredential) {
                    Stop-Function -Message "You must provide the credential name for the Azure Storage Account"
                    return
                }
                $AzureBaseUrl = $AzureBaseUrl.Trim("/")
                $FileCount = 1
                $BackupDirectory = $AzureBaseUrl
            }

            if ($OutputScriptOnly) {
                $IgnoreFileChecks = $true
            }
        }
    }

    process {
        if (!$SqlInstance -and !$InputObject) {
            Stop-Function -Message "You must specify a server and database or pipe some databases"
            return
        }

        Write-Message -Level Verbose -Message "$($InputObject.Count) database to backup"

        foreach ($Database in $InputObject) {
            $ProgressId = Get-Random
            $failures = @()
            $dbname = $Database.Name

            if ($dbname -eq "tempdb") {
                Stop-Function -Message "Backing up tempdb not supported" -Continue
            }

            if ('Normal' -notin ($Database.Status -split ',')) {
                Stop-Function -Message "Database status not Normal. $dbname skipped." -Continue
            }

            if ($Database.DatabaseSnapshotBaseName) {
                Stop-Function -Message "Backing up snapshots not supported. $dbname skipped." -Continue
            }

            if ($null -eq $server) { $server = $Database.Parent }

            Write-Message -Level Verbose -Message "Backup database $database"

            if ($null -eq $Database.RecoveryModel) {
                $Database.RecoveryModel = $server.Databases[$Database.Name].RecoveryModel
                Write-Message -Level Verbose -Message "$dbname is in $($Database.RecoveryModel) recovery model"
            }

            # Fixes one-off cases of StackOverflowException crashes, see issue 1481
            $dbRecovery = $Database.RecoveryModel.ToString()
            if ($dbRecovery -eq 'Simple' -and $Type -eq 'Log') {
                $failreason = "$database is in simple recovery mode, cannot take log backup"
                $failures += $failreason
                Write-Message -Level Warning -Message "$failreason"
            }

            $lastfull = $database.Refresh().LastBackupDate.Year

            if ($Type -notin @("Database", "Full") -and $lastfull -eq 1) {
                $failreason = "$database does not have an existing full backup, cannot take log or differentialbackup"
                $failures += $failreason
                Write-Message -Level Warning -Message "$failreason"
            }

            if ($CopyOnly -ne $true) {
                $CopyOnly = $false
            }

            $server.ConnectionContext.StatementTimeout = 0
            $backup = New-Object Microsoft.SqlServer.Management.Smo.Backup
            $backup.Database = $Database.Name
            $Suffix = "bak"

            if ($CompressBackup) {
                if ($server.Edition -like 'Express*' -or ($server.VersionMajor -eq 10 -and $server.VersionMinor -eq 0 -and $server.Edition -notlike '*enterprise*') -or $server.VersionMajor -lt 10) {
                    Write-Message -Level Warning -Message "Compression is not supported with this version/edition of Sql Server"
                }
                else {
                    Write-Message -Level Verbose -Message "Compression enabled"
                    $backup.CompressionOption = 1
                }
            }

            if ($Checksum) {
                $backup.Checksum = $true
            }

            if ($Type -in 'Diff', 'Differential') {
                Write-Message -Level VeryVerbose -Message "Creating differential backup"
                $SMOBackuptype = "Database"
                $backup.Incremental = $true
                $outputType = 'Differential'
            }
            $Backup.NoRecovery = $false
            if ($Type -eq "Log") {
                Write-Message -Level VeryVerbose -Message "Creating log backup"
                $Suffix = "trn"
                $OutputType = 'Log'
                $SMOBackupType = 'Log'
                $Backup.NoRecovery = $NoRecovery
            }

            if ($Type -in 'Full', 'Database') {
                Write-Message -Level VeryVerbose -Message "Creating full backup"
                $SMOBackupType = "Database"
                $OutputType = 'Full'
            }

            $backup.CopyOnly = $copyonly
            $backup.Action = $SMOBackupType
            if ('' -ne $AzureBaseUrl) {
                $backup.CredentialName = $AzureCredential
            }

            Write-Message -Level Verbose -Message "Building file name"

            $BackupFinalName = ''
            $FinalBackupPath = @()
            if ('NUL' -eq $BackupFileName) {
                $FinalBackupPath += 'NUL:'
                $IgnoreFileChecks = $true
            }
            elseif ('' -ne $BackupFileName) {
                $File = New-Object System.IO.FileInfo($BackupFileName)
                $BackupFinalName = $file.Name
                $suffix = $file.extension -Replace '^\.',''
                if ( '' -ne (Split-Path $BackupFileName)) {
                    Write-Message -Level Verbose -Message "Fully qualified path passed in"
                    $FinalBackupPath += [IO.Path]::GetFullPath($file.DirectoryName)
                }
            }
            else {
                $timestamp = (Get-Date -Format yyyyMMddHHmm)
                Write-Message -Level VeryVerbose -Message "Setting filename"
                $BackupFinalName = "$($dbname)_$timestamp.$suffix"
            }

            Write-Message -Level Verbose -Message "Building backup path"
            if ($FinalBackupPath.Count -eq 0) {
                $FinalBackupPath += $BackupDirectory
            }

            if ($BackupDirectory.Count -eq 1 -and $Filecount -gt 1) {
                for ($i = 0; $i -lt ($Filecount - 1); $i++) {
                    $FinalBackupPath += $FinalBackupPath[0]
                }
            }

            if ($AzureBaseUrl -or $AzureCredential) {
                $slash = "/"
            }
            else {
                $slash = "\"
            }
            if ($FinalBackupPath.Count -gt 1) {
                $File = New-Object System.IO.FileInfo($BackupFinalName)
                for ($i = 0; $i -lt $FinalBackupPath.Count; $i++) {
                    $FinalBackupPath[$i] = $FinalBackupPath[$i] + $slash + $($File.BaseName) + "-$($i+1)-of-$FileCount.$suffix"
                }
            }
            elseif ($FinalBackupPath[0] -ne 'NUL:') {
                $FinalBackupPath[0] = $FinalBackupPath[0] + $slash + $BackupFinalName
            }

            if ($CreateFolder -and $FinalBackupPath[0] -ne 'NUL:') {
                for ($i = 0; $i -lt $FinalBackupPath.Count; $i++) {
                    $parent = [IO.Path]::GetDirectoryName($FinalBackupPath[$i])
                    $leaf = [IO.Path]::GetFileName($FinalBackupPath[$i])
                    $FinalBackupPath[$i] = [IO.Path]::Combine($parent, $dbname, $leaf)
                }
            }

            if (-not $IgnoreFileChecks -and -not $AzureBaseUrl) {
                $parentPaths = ($FinalBackupPath | ForEach-Object { Split-Path $_ } | Select-Object -Unique)
                foreach ($parentPath in $parentPaths) {
                    if (-not (Test-DbaSqlPath -SqlInstance $server -Path $parentPath)) {
                        if (($BuildPath -eq $true) -or ($CreateFolder -eq $True)) {
                            $null = New-DbaSqlDirectory -SqlInstance $server -Path $parentPath
                        }
                        else {
                            $failreason += "SQL Server cannot check if $parentPath exists. You can try disabiling this check with -IgnoreFileChecks"
                            $failures += $failreason
                            Write-Message -Level Warning -Message "$failreason"
                        }
                    }
                }
            }


            if ('' -eq $AzureBaseUrl -and $BackupDirectory) {
                $FinalBackupPath = $FinalBackupPath | ForEach-Object { [IO.Path]::GetFullPath($_) }
            }


            $script = $null
            $backupComplete = $false

            if (!$failures) {
                $Filecount = $FinalBackupPath.Count

                foreach ($backupfile in $FinalBackupPath) {
                    $device = New-Object Microsoft.SqlServer.Management.Smo.BackupDeviceItem
                    if ('' -ne $AzureBaseUrl) {
                        $device.DeviceType = "URL"
                    }
                    else {
                        $device.DeviceType = "File"
                    }
                    
                    if ($WithFormat) {
                        Write-Message -Message "WithFormat specified. Ensuring Initialize and SkipTapeHeader are set to true." -Level Verbose
                        $Initialize = $true
                        $SkipTapeHeader = $true
                    }
                    
                    $backup.FormatMedia = $WithFormat
                    $backup.Initialize = $Initialize
                    $backup.SkipTapeHeader = $SkipTapeHeader
                    $device.Name = $backupfile
                    $backup.Devices.Add($device)
                }
                $humanBackupFile = $FinalBackupPath -Join ','
                Write-Message -Level Verbose -Message "Devices added"
                $percent = [Microsoft.SqlServer.Management.Smo.PercentCompleteEventHandler] {
                    Write-Progress -id $ProgressId -activity "Backing up database $dbname to $humanBackupFile" -percentcomplete $_.Percent -status ([System.String]::Format("Progress: {0} %", $_.Percent))
                }
                $backup.add_PercentComplete($percent)
                $backup.PercentCompleteNotification = 1
                $backup.add_Complete($complete)

                if ($MaxTransferSize) {
                    $backup.MaxTransferSize = $MaxTransferSize
                }
                if ($BufferCount) {
                    $backup.BufferCount = $BufferCount
                }
                if ($BlockSize) {
                    $backup.Blocksize = $BlockSize
                }

                Write-Progress -id $ProgressId -activity "Backing up database $dbname to $humanBackupFile" -percentcomplete 0 -status ([System.String]::Format("Progress: {0} %", 0))

                try {
                    if ($Pscmdlet.ShouldProcess($server.Name, "Backing up $dbname to $humanBackupFile")) {
                        if ($OutputScriptOnly -ne $True) {
                            $Filelist = @()
                            $FileList += $server.Databases[$dbname].FileGroups.Files | Select-Object @{ Name = "FileType"; Expression = { "D" } }, @{ Name = "Type"; Expression = { "D" } }, @{ Name = "LogicalName"; Expression = { $_.Name } }, @{ Name = "PhysicalName"; Expression = { $_.FileName } }
                            $FileList += $server.Databases[$dbname].LogFiles | Select-Object @{ Name = "FileType"; Expression = { "L" } }, @{ Name = "Type"; Expression = { "L" } }, @{ Name = "LogicalName"; Expression = { $_.Name } }, @{ Name = "PhysicalName"; Expression = { $_.FileName } }

                            $backup.SqlBackup($server)
                            $script = $backup.Script($server)
                            Write-Progress -id $ProgressId -activity "Backing up database $dbname to $backupfile" -status "Complete" -Completed
                            $BackupComplete = $true
                            if ($server.VersionMajor -eq '8') {
                                $HeaderInfo = Get-BackupAncientHistory -SqlInstance $server -Database $dbname
                            }
                            else {
                                $HeaderInfo = Get-DbaBackupHistory -SqlInstance $server -Database $dbname -Last -IncludeCopyOnly | Sort-Object -Property End -Descending | Select-Object -First 1
                            }
                            $Verified = $false
                            if ($Verify) {
                                $verifiedresult = [PSCustomObject]@{
                                    SqlInstance          = $server.name
                                    DatabaseName         = $dbname
                                    BackupComplete       = $BackupComplete
                                    BackupFilesCount     = $FinalBackupPath.Count
                                    BackupFile           = (Split-Path $FinalBackupPath -Leaf)
                                    BackupFolder         = (Split-Path $FinalBackupPath | Sort-Object -Unique)
                                    BackupPath           = ($FinalBackupPath | Sort-Object -Unique)
                                    Script               = $script
                                    Notes                = $failures -join (',')
                                    FullName             = ($FinalBackupPath | Sort-Object -Unique)
                                    FileList             = $FileList
                                    SoftwareVersionMajor = $server.VersionMajor
                                    Type                 = $outputType
                                    FirstLsn             = $HeaderInfo.FirstLsn
                                    DatabaseBackupLsn    = $HeaderInfo.DatabaseBackupLsn
                                    CheckPointLsn        = $HeaderInfo.CheckPointLsn
                                    LastLsn              = $HeaderInfo.LastLsn
                                    BackupSetId          = $HeaderInfo.BackupSetId
                                    LastRecoveryForkGUID = $HeaderInfo.LastRecoveryForkGUID
                                } | Restore-DbaDatabase -SqlInstance $server -DatabaseName DbaVerifyOnly -VerifyOnly -TrustDbBackupHistory -DestinationFilePrefix DbaVerifyOnly
                                if ($verifiedResult[0] -eq "Verify successful") {
                                    $failures += $verifiedResult[0]
                                    $Verified = $true
                                }
                                else {
                                    $failures += $verifiedResult[0]
                                    $Verified = $false
                                }
                            }
                            $HeaderInfo | Add-Member -Type NoteProperty -Name BackupComplete -Value $BackupComplete
                            $HeaderInfo | Add-Member -Type NoteProperty -Name BackupFile -Value (Split-Path $FinalBackupPath -Leaf)
                            $HeaderInfo | Add-Member -Type NoteProperty -Name BackupFilesCount -Value $FinalBackupPath.Count
                            if ($FinalBackupPath[0] -eq 'NUL:') {
                                $pathresult = "NUL:"
                            }
                            else {
                                $pathresult = (Split-Path $FinalBackupPath | Sort-Object -Unique)
                            }
                            $HeaderInfo | Add-Member -Type NoteProperty -Name BackupFolder -Value $pathresult
                            $HeaderInfo | Add-Member -Type NoteProperty -Name BackupPath -Value ($FinalBackupPath | Sort-Object -Unique)
                            $HeaderInfo | Add-Member -Type NoteProperty -Name DatabaseName -Value $dbname
                            $HeaderInfo | Add-Member -Type NoteProperty -Name Notes -Value ($failures -join (','))
                            $HeaderInfo | Add-Member -Type NoteProperty -Name Script -Value $script
                            $HeaderInfo | Add-Member -Type NoteProperty -Name Verified -Value $Verified
                        }
                        else {
                            $backup.Script($server)
                        }
                    }
                }
                catch {
                    if ($NoRecovery -and ($_.Exception.InnerException.InnerException.InnerException -like '*cannot be opened. It is in the middle of a restore.')) {
                        Write-Message -Message "Exception thrown by db going into restoring mode due to recovery" -Leve Verbose
                    }
                    else {
                        Write-Progress -id $ProgressId -activity "Backup" -status "Failed" -completed
                        Stop-Function -message "Backup Failed:  $($_.Exception.Message)" -EnableException $EnableException -ErrorRecord $_ -Continue
                        $BackupComplete = $false
                    }
                }
            }
            $OutputExclude = 'FullName', 'FileList', 'SoftwareVersionMajor'
            if ($failures.Count -eq 0) {
                $OutputExclude += ('Notes', 'FirstLsn', 'DatabaseBackupLsn', 'CheckpointLsn', 'LastLsn', 'BackupSetId', 'LastRecoveryForkGuid')
            }
            $headerinfo | Select-DefaultView -ExcludeProperty $OutputExclude
            $BackupFileName = $null
        }
    }
}
function Backup-DbaDatabaseMasterKey {
    <#
.SYNOPSIS
Backs up specified database master key.

.DESCRIPTION
Backs up specified database master key.

.PARAMETER SqlInstance
The target SQL Server instance.

.PARAMETER SqlCredential
Allows you to login to SQL Server using alternative credentials.

.PARAMETER Database
Backup master key from specific database(s).

.PARAMETER ExcludeDatabase
The database(s) to exclude - this list is auto-populated from the server.

.PARAMETER Path
The directory to export the key. If no path is specified, the default backup directory for the instance will be used.

.PARAMETER Password
The password to encrypt the exported key. This must be a SecureString.

.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

Website: https://dbatools.io
Copyright: (C) Chrissy LeMaire, [email protected]
License: MIT https://opensource.org/licenses/MIT

.EXAMPLE
Backup-DbaDatabaseMasterKey -SqlInstance server1\sql2016

Prompts for export password, then logs into server1\sql2016 with Windows credentials then backs up all database keys to the default backup directory.

ComputerName : SERVER1
InstanceName : SQL2016
SqlInstance  : SERVER1\SQL2016
Database     : master
Filename     : E:\MSSQL13.SQL2016\MSSQL\Backup\server1$sql2016-master-20170614162311.key
Status       : Success

.EXAMPLE
Backup-DbaDatabaseMasterKey -SqlInstance Server1 -Database db1 -Path \\nas\sqlbackups\keys

Logs into sql2016 with Windows credentials then backs up db1's keys to the \\nas\sqlbackups\keys directory.

#>
    [CmdletBinding()]
    param (
        [parameter(Mandatory, ValueFromPipeline)]
        [Alias("ServerInstance", "SqlServer")]
        [DbaInstanceParameter[]]$SqlInstance,
        [PSCredential]$SqlCredential,
        [object[]]$Database,
        [object[]]$ExcludeDatabase,
        [Security.SecureString]$Password,
        [string]$Path,
        [Alias('Silent')]
        [switch]$EnableException
    )

    process {
        foreach ($instance in $SqlInstance) {

            try {
                Write-Message -Level Verbose -Message "Connecting to $instance"
                $server = Connect-SqlInstance -SqlInstance $instance -SqlCredential $sqlcredential
            }
            catch {
                Stop-Function -Message "Failure" -Category ConnectionError -ErrorRecord $_ -Target $instance -Continue
            }

            $databases = $server.Databases | Where-Object IsAccessible

            if ($Database) {
                $databases = $databases | Where-Object Name -In $Database
            }
            if ($ExcludeDatabase) {
                $databases = $databases | Where-Object Name -NotIn $ExcludeDatabase
            }

            if (Test-Bound -ParameterName Path -Not) {
                $Path = $server.BackupDirectory
            }

            if (!$Path) {
                Stop-Function -Message "Path discovery failed. Please explicitly specify -Path" -Target $server -Continue
            }

            if (!(Test-DbaSqlPath -SqlInstance $server -Path $Path)) {
                Stop-Function -Message "$instance cannot access $Path" -Target $server -InnerErrorRecord $_ -Continue
            }

            foreach ($db in $databases) {

                if (!$db.IsAccessible) {
                    Write-Message -Level Warning -Message "Database $db is not accessible. Skipping."
                    continue
                }

                $masterkey = $db.MasterKey

                if (!$masterkey) {
                    Write-Message -Message "No master key exists in the $db database on $instance" -Target $db -Level Verbose
                    continue
                }

                # If you pass a password param, then you will not be prompted for each database, but it wouldn't be a good idea to build in insecurity
                if (Test-Bound -ParameterName Password -Not) {
                    $password = Read-Host -AsSecureString -Prompt "You must enter Service Key password for $instance"
                    $password2 = Read-Host -AsSecureString -Prompt "Type the password again"

                    if (([System.Runtime.InteropServices.marshal]::PtrToStringAuto([System.Runtime.InteropServices.marshal]::SecureStringToBSTR($password))) -ne ([System.Runtime.InteropServices.marshal]::PtrToStringAuto([System.Runtime.InteropServices.marshal]::SecureStringToBSTR($password2)))) {
                        Stop-Function -Message "Passwords do not match" -Continue
                    }
                }

                $time = (Get-Date -Format yyyMMddHHmmss)
                $dbname = $db.name
                $Path = $Path.TrimEnd("\")
                $fileinstance = $instance.ToString().Replace('\', '$')
                $filename = "$Path\$fileinstance-$dbname-$time.key"

                try {
                    $masterkey.export($filename, [System.Runtime.InteropServices.marshal]::PtrToStringAuto([System.Runtime.InteropServices.marshal]::SecureStringToBSTR($password)))
                    $status = "Success"
                }
                catch {
                    $status = "Failure"
                    Write-Message -Level Warning -Message "Backup failure: $($_.Exception.InnerException)"
                }

                Add-Member -Force -InputObject $masterkey -MemberType NoteProperty -Name ComputerName -value $server.ComputerName
                Add-Member -Force -InputObject $masterkey -MemberType NoteProperty -Name InstanceName -value $server.ServiceName
                Add-Member -Force -InputObject $masterkey -MemberType NoteProperty -Name SqlInstance -value $server.DomainInstanceName
                Add-Member -Force -InputObject $masterkey -MemberType NoteProperty -Name Database -value $dbname
                Add-Member -Force -InputObject $masterkey -MemberType NoteProperty -Name Filename -value $filename
                Add-Member -Force -InputObject $masterkey -MemberType NoteProperty -Name Status -value $status

                Select-DefaultView -InputObject $masterkey -Property ComputerName, InstanceName, SqlInstance, Database, 'Filename as Path', Status
            }
        }
    }
}
function Backup-DbaDbCertificate {
    <#
        .SYNOPSIS
            Exports database certificates from SQL Server using SMO.

        .DESCRIPTION
            Exports database certificates from SQL Server using SMO and outputs the .cer and .pvk files.

        .PARAMETER SqlInstance
            SQL Server name or SMO object representing the SQL Server to connect to. This can be a collection and receive pipeline input to allow the function to be executed against multiple SQL Server instances.

        .PARAMETER SqlCredential
            Login to the target instance using alternative credentials. Windows and SQL Authentication supported. Accepts credential objects (Get-Credential)

        .PARAMETER Certificate
            Exports certificate that matches the name(s).

        .PARAMETER Database
            Exports the encryptor for specific database(s).

        .PARAMETER ExcludeDatabase
            Database(s) to skip when exporting encryptors.

        .PARAMETER EncryptionPassword
            A string value that specifies the system path to encrypt the private key.

        .PARAMETER DecryptionPassword
            A string value that specifies the system path to decrypt the private key.

        .PARAMETER Path
            The path to output the files to. The path is relative to the SQL Server itself. If no path is specified, the default data directory will be used.

        .PARAMETER Suffix
            The suffix of the filename of the exported certificate.

        .PARAMETER InputObject
            Certificate object

        .PARAMETER Confirm
            If this switch is enabled, you will be prompted for confirmation before executing any operations that change state.

        .PARAMETER EnableException
            By default, when something goes wrong we try to catch it, interpret it and give you a friendly warning message.
            This avoids overwhelming you with "sea of red" exceptions, but is inconvenient because it basically disables advanced scripting.
            Using this switch turns this "nice by default" feature off and enables you to catch exceptions with your own try/catch.

        .PARAMETER WhatIf
            If this switch is enabled, no actions are performed but informational messages will be displayed that explain what would happen if the command were to run.

        .NOTES
            Author: Jess Pomfret (@jpomfret)
            Tags: Migration, Certificate

            Website: https://dbatools.io
            Copyright: (C) Chrissy LeMaire, [email protected]
            License: MIT https://opensource.org/licenses/MIT

        .EXAMPLE
            Backup-DbaDbCertificate -SqlInstance Server1
            Exports all the certificates on the specified SQL Server to the default data path for the instance.

        .EXAMPLE
            $cred = Get-Credential sqladmin
            Backup-DbaDbCertificate -SqlInstance Server1 -SqlCredential $cred

            Connects using sqladmin credential and exports all the certificates on the specified SQL Server to the default data path for the instance.

        .EXAMPLE
            Backup-DbaDbCertificate -SqlInstance Server1 -Certificate Certificate1
            Exports only the certificate named Certificate1 on the specified SQL Server to the default data path for the instance.

        .EXAMPLE
            Backup-DbaDbCertificate -SqlInstance Server1 -Database AdventureWorks
            Exports only the certificates for AdventureWorks on the specified SQL Server to the default data path for the instance.

        .EXAMPLE
            Backup-DbaDbCertificate -SqlInstance Server1 -ExcludeDatabase AdventureWorks
            Exports all certificates except those for AdventureWorks on the specified SQL Server to the default data path for the instance.

        .EXAMPLE
            Backup-DbaDbCertificate -SqlInstance Server1 -Path \\Server1\Certificates -EncryptionPassword (ConvertTo-SecureString -force -AsPlainText GoodPass1234!!)
            Exports all the certificates and private keys on the specified SQL Server.

        .EXAMPLE
            $EncryptionPassword = ConvertTo-SecureString -AsPlainText "GoodPass1234!!" -force
            $DecryptionPassword = ConvertTo-SecureString -AsPlainText "Password4567!!" -force
            Backup-DbaDbCertificate -SqlInstance Server1 -EncryptionPassword $EncryptionPassword -DecryptionPassword $DecryptionPassword
            Exports all the certificates on the specified SQL Server using the supplied DecryptionPassword, since an EncryptionPassword is specified private keys are also exported.

        .EXAMPLE
            Backup-DbaDbCertificate -SqlInstance Server1 -Path \\Server1\Certificates
            Exports all certificates on the specified SQL Server to the specified path.

        .EXAMPLE
            Backup-DbaDbCertificate -SqlInstance Server1 -Suffix DbaTools
            Exports all certificates on the specified SQL Server to the specified path, appends DbaTools to the end of the filenames.

        .EXAMPLE
            Get-DbaDbCertificate -SqlInstance sql2016 | Backup-DbaDbCertificate
            Exports all certificates found on sql2016 to the default data directory.
    #>
    [CmdletBinding(DefaultParameterSetName = "Default", SupportsShouldProcess = $true)]
    param (
        [parameter(Mandatory, ParameterSetName = "instance")]
        [Alias("ServerInstance", "SqlServer")]
        [DbaInstanceParameter[]]$SqlInstance,
        [PSCredential]$SqlCredential,
        [parameter(ParameterSetName = "instance")]
        [object[]]$Certificate,
        [parameter(ParameterSetName = "instance")]
        [object[]]$Database,
        [parameter(ParameterSetName = "instance")]
        [object[]]$ExcludeDatabase,
        [parameter(Mandatory = $false)]
        [Security.SecureString]$EncryptionPassword,
        [parameter(Mandatory = $false)]
        [Security.SecureString]$DecryptionPassword,
        [System.IO.FileInfo]$Path,
        [string]$Suffix = "$(Get-Date -format 'yyyyMMddHHmmssms')",
        [parameter(ValueFromPipeline, ParameterSetName = "collection")]
        [Microsoft.SqlServer.Management.Smo.Certificate[]]$InputObject,
        [Alias('Silent')]
        [switch]$EnableException
    )

    begin {

        if ($EncryptionPassword.Length -eq 0 -and $DecryptionPassword.Length -gt 0) {
            Stop-Function -Message "If you specify an decryption password, you must also specify an encryption password" -Target $DecryptionPassword
        }

        Test-DbaDeprecation -DeprecatedOn "1.0.0" -Alias Backup-DbaDatabaseCertificate

        function export-cert ($cert) {
            $certName = $cert.Name
            $db = $cert.Parent
            $server = $db.Parent
            $instance = $server.Name
            $actualPath = $Path

            if ($null -eq $actualPath) {
                $actualPath = Get-SqlDefaultPaths -SqlInstance $server -filetype Data
            }

            $actualPath = $actualPath.TrimEnd('\')
            $fullCertName = "$actualPath\$certName$Suffix"
            $exportPathKey = "$fullCertName.pvk"

            if (!(Test-DbaSqlPath -SqlInstance $server -Path $actualPath)) {
                Stop-Function -Message "$SqlInstance cannot access $actualPath" -Target $actualPath
            }

            if ($Pscmdlet.ShouldProcess($instance, "Exporting certificate $certName from $db on $instance to $actualPath")) {
                Write-Message -Level Verbose -Message "Exporting Certificate: $certName to $fullCertName"
                try {

                    $exportPathCert = "$fullCertName.cer"

                    # because the password shouldn't go to memory...
                    if ($EncryptionPassword.Length -gt 0 -and $DecryptionPassword.Length -gt 0) {

                        Write-Message -Level Verbose -Message "Both passwords passed in. Will export both cer and pvk."

                        $cert.export(
                            $exportPathCert,
                            $exportPathKey,
                            [System.Runtime.InteropServices.marshal]::PtrToStringAuto([System.Runtime.InteropServices.marshal]::SecureStringToBSTR($EncryptionPassword)),
                            [System.Runtime.InteropServices.marshal]::PtrToStringAuto([System.Runtime.InteropServices.marshal]::SecureStringToBSTR($DecryptionPassword))
                        )
                    }
                    elseif ($EncryptionPassword.Length -gt 0 -and $DecryptionPassword.Length -eq 0) {
                        Write-Message -Level Verbose -Message "Only encryption password passed in. Will export both cer and pvk."

                        $cert.export(
                            $exportPathCert,
                            $exportPathKey,
                            [System.Runtime.InteropServices.marshal]::PtrToStringAuto([System.Runtime.InteropServices.marshal]::SecureStringToBSTR($EncryptionPassword))
                        )
                    }
                    else {
                        Write-Message -Level Verbose -Message "No passwords passed in. Will export just cer."
                        $exportPathKey = "Password required to export key"
                        $cert.export($exportPathCert)
                    }

                    [pscustomobject]@{
                        ComputerName   = $server.ComputerName
                        InstanceName   = $server.ServiceName
                        SqlInstance    = $server.DomainInstanceName
                        Database       = $db.Name
                        Certificate    = $certName
                        Path           = $exportPathCert
                        Key            = $exportPathKey
                        ExportPath     = $exportPathCert
                        ExportKey      = $exportPathKey
                        exportPathCert = $exportPathCert
                        exportPathKey  = $exportPathKey
                        Status         = "Success"
                    } | Select-DefaultView -ExcludeProperty exportPathCert, exportPathKey, ExportPath, ExportKey
                }
                catch {

                    if ($_.Exception.InnerException) {
                        $exception = $_.Exception.InnerException.ToString() -Split "System.Data.SqlClient.SqlException: "
                        $exception = ($exception[1] -Split "at Microsoft.SqlServer.Management.Common.ConnectionManager")[0]
                    }
                    else {
                        $exception = $_.Exception
                    }
                    [pscustomobject]@{
                        ComputerName   = $server.ComputerName
                        InstanceName   = $server.ServiceName
                        SqlInstance    = $server.DomainInstanceName
                        Database       = $db.Name
                        Certificate    = $certName
                        Path           = $exportPathCert
                        Key            = $exportPathKey
                        ExportPath     = $exportPathCert
                        ExportKey      = $exportPathKey
                        exportPathCert = $exportPathCert
                        exportPathKey  = $exportPathKey
                        Status         = "Failure: $exception"
                    } | Select-DefaultView -ExcludeProperty exportPathCert, exportPathKey, ExportPath, ExportKey
                    Stop-Function -Message "$certName from $db on $instance cannot be exported." -Continue -Target $cert -ErrorRecord $_
                }
            }
        }
    }

    process {
        if (Test-FunctionInterrupt) { return }
        foreach ($instance in $SqlInstance) {
            try {
                Write-Message -Level Verbose -Message "Connecting to $instance"
                $server = Connect-SqlInstance -SqlInstance $instance -SqlCredential $SqlCredential
            }
            catch {
                Stop-Function -Message "Failure" -Category ConnectionError -ErrorRecord $_ -Target $instance -Continue
                return
            }
            $databases = Get-DbaDatabase -SqlInstance $server | Where-Object IsAccessible

            if ($Database) {
                $databases = $databases | Where-Object Name -in $Database
            }
            if ($ExcludeDatabase) {
                $databases = $databases | Where-Object Name -NotIn $ExcludeDatabase
            }
            foreach ($db in $databases.Name) {
                $DBInputObject = Get-DbaDbCertificate -SqlInstance $server -Database $db
                if ($Certificate) {
                    $InputObject += $DBInputObject | Where-Object Name -In $Certificate
                }
                else {
                    $InputObject += $DBInputObject | Where-Object Name -NotLike "##*"
                }
                if (!$InputObject) {
                    Write-Message -Level Output -Message "No certificates found to export in $db."
                    continue
                }
            }

        }

        foreach ($cert in $InputObject) {
            if ($cert.Name.StartsWith("##")) {
                Write-Message -Level Output -Message "Skipping system cert $cert"
            }
            else {
                export-cert $cert
            }
        }
    }
}
function Clear-DbaPlanCache {
    <#
        .SYNOPSIS
            Removes adhoc and prepared plan caches is single use plans are over defined threshold.

        .DESCRIPTION
            Checks ahoc and prepared plan cache for each database, if over 100 MBs removes from the cache.

            This command automates that process.

            References: https://www.sqlskills.com/blogs/kimberly/plan-cache-adhoc-workloads-and-clearing-the-single-use-plan-cache-bloat/

        .PARAMETER SqlInstance
            The target SQL Server instance.

        .PARAMETER SqlCredential
           Login to the target instance using alternative credentials. Windows and SQL Authentication supported. Accepts credential objects (Get-Credential)

        .PARAMETER Threshold
            Memory used threshold.

        .PARAMETER InputObject
            Enables results to be piped in from Get-DbaPlanCache.

        .PARAMETER WhatIf
            If this switch is enabled, no actions are performed but informational messages will be displayed that explain what would happen if the command were to run.

        .PARAMETER Confirm
            If this switch is enabled, you will be prompted for confirmation before executing any operations that change state.

        .PARAMETER EnableException
            By default, when something goes wrong we try to catch it, interpret it and give you a friendly warning message.
            This avoids overwhelming you with "sea of red" exceptions, but is inconvenient because it basically disables advanced scripting.
            Using this switch turns this "nice by default" feature off and enables you to catch exceptions with your own try/catch.

        .NOTES
            Tags: Memory
            Author: Tracy Boggiano, databasesuperhero.com

            dbatools PowerShell module (https://dbatools.io, [email protected])
            Copyright (C) 2016 Chrissy LeMaire
            License: GNU GPL v3 https://opensource.org/licenses/GPL-3.0

        .LINK
            https://dbatools.io/Clear-DbaPlanCache

        .EXAMPLE
            Clear-DbaPlanCache -SqlInstance sql2017 -Threshold 200

            Logs into the SQL Server instance "sql2017" and removes plan caches if over 200 MB.

        .EXAMPLE
            Clear-DbaPlanCache -SqlInstance sql2017 -SqlCredential (Get-Credential sqladmin)

            Logs into the SQL instance using the SQL Login 'sqladmin' and then Windows instance as 'ad\sqldba'
            and removes if Threshold over 100 MB.
    #>
    [CmdletBinding(SupportsShouldProcess)]
    Param (
        [Alias("ServerInstance", "SqlServer", "SqlServers")]
        [DbaInstanceParameter[]]$SqlInstance,
        [PSCredential]$SqlCredential,
        [int]$Threshold = 100,
        [parameter(ValueFromPipeline)]
        [object[]]$InputObject,
        [switch]$EnableException
    )
    process {
        foreach ($instance in $SqlInstance) {
            $InputObject += Get-DbaPlanCache -SqlInstance $instance -SqlCredential $SqlCredential
        }

        foreach ($result in $InputObject) {
            if ($result.MB -ge $Threshold) {
                if ($Pscmdlet.ShouldProcess($($result.SqlInstance), "Cleared SQL Plans plan cache")) {
                    $server.Query("DBCC FREESYSTEMCACHE('SQL Plans')")
                    [pscustomobject]@{
                        ComputerName = $result.ComputerName
                        InstanceName = $result.InstanceName
                        SqlInstance  = $result.SqlInstance
                        Size         = $result.Size
                        Status       = "Plan cache cleared"
                    }
                }
            }
            else {
                if ($Pscmdlet.ShouldProcess($($result.SqlInstance), "Results $($result.Size) below threshold")) {
                    [pscustomobject]@{
                        ComputerName = $result.ComputerName
                        InstanceName = $result.InstanceName
                        SqlInstance  = $result.SqlInstance
                        Size         = $result.Size
                        Status       = "Plan cache size below threshold ($Threshold) "
                    }
                    Write-Message -Level Verbose -Message "Plan cache size below threshold ($Threshold) "
                }
            }
        }
    }
}
function Clear-DbaSqlConnectionPool {
    <#
    .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

        Website: https://dbatools.io
        Copyright: (C) Chrissy LeMaire, [email protected]
        License: MIT https://opensource.org/licenses/MIT

    .LINK
        https://dbatools.io/Clear-DbaSqlConnectionPool

    .EXAMPLE
        Clear-DbaSqlConnectionPool

        Clears all local connection pools.

    .EXAMPLE
        Clear-DbaSqlConnectionPool -ComputerName workstation27

        Clears all connection pools on workstation27.
#>
    [CmdletBinding()]
    param (
        [Parameter(ValueFromPipeline = $true)]
        [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
            }
        }
    }
}
function Clear-DbaWaitStatistics {
    <#
    .SYNOPSIS
        Clears wait statistics

    .DESCRIPTION
        Reset the aggregated statistics - basically just executes DBCC SQLPERF (N'sys.dm_os_wait_stats', CLEAR)

    .PARAMETER SqlInstance
        Allows you to specify a comma separated list of servers to query.

    .PARAMETER SqlCredential
        Login to the target instance using alternative credentials. Windows and SQL Authentication supported. Accepts credential objects (Get-Credential)

    .PARAMETER WhatIf
        If this switch is enabled, no actions are performed but informational messages will be displayed that explain what would happen if the command were to run.

    .PARAMETER Confirm
        If this switch is enabled, you will be prompted for confirmation before executing any operations that change state.

    .PARAMETER EnableException
        By default, when something goes wrong we try to catch it, interpret it and give you a friendly warning message.
        This avoids overwhelming you with "sea of red" exceptions, but is inconvenient because it basically disables advanced scripting.
        Using this switch turns this "nice by default" feature off and enables you to catch exceptions with your own try/catch.

    .NOTES
        Tags: WaitStatistic
        Website: https://dbatools.io
        Copyright: (C) Chrissy LeMaire, [email protected]
        License: MIT https://opensource.org/licenses/MIT

    .LINK
        https://dbatools.io/Clear-DbaWaitStatistics

    .EXAMPLE
        Clear-DbaWaitStatistics -SqlInstance sql2008, sqlserver2012
        After confirmation, clears wait stats on servers sql2008 and sqlserver2012

    .EXAMPLE
        Clear-DbaWaitStatistics -SqlInstance sql2008, sqlserver2012 -Confirm:$false
        Clears wait stats on servers sql2008 and sqlserver2012, without prompting
    #>
    [CmdletBinding(ConfirmImpact = 'High', SupportsShouldProcess)]
    param (
        [parameter(Position = 0, Mandatory = $true, ValueFromPipeline = $True)]
        [Alias("ServerInstance", "SqlServer", "SqlServers")]
        [DbaInstance[]]$SqlInstance,
        [PSCredential]$SqlCredential,
        [Alias('Silent')]
        [switch]$EnableException
    )
    process {
        foreach ($instance in $SqlInstance) {
            Write-Message -Level Verbose -Message "Connecting to $instance"

            try {
                $server = Connect-SqlInstance -SqlInstance $instance -SqlCredential $SqlCredential -MinimumVersion 9
            }
            catch {
                Stop-Function -Message "Failure" -Category ConnectionError -ErrorRecord $_ -Target $instance -Continue
            }

            if ($Pscmdlet.ShouldProcess($instance, "Performing CLEAR of sys.dm_os_wait_stats")) {
                try {
                    $server.Query("DBCC SQLPERF (N'sys.dm_os_wait_stats', CLEAR);")
                    $status = "Success"
                }
                catch {
                    $status = $_.Exception
                }

                [PSCustomObject]@{
                    ComputerName = $server.ComputerName
                    InstanceName = $server.ServiceName
                    SqlInstance  = $server.DomainInstanceName
                    Status       = $status
                }
            }
        }
    }
}
function Connect-DbaInstance {
    <#
    .SYNOPSIS
        Creates a robust SMO SQL Server object.

    .DESCRIPTION
        This command is robust because it initializes properties that do not cause enumeration by default. It also supports both Windows and SQL Server authentication methods, and detects which to use based upon the provided credentials.

        By default, this command also sets the connection's ApplicationName property  to "dbatools PowerShell module - dbatools.io - custom connection". If you're doing anything that requires profiling, you can look for this client name.

        Alternatively, you can pass in whichever client name you'd like using the -ClientName parameter. There are a ton of other parameters for you to explore as well.

        See https://msdn.microsoft.com/en-us/library/system.data.sqlclient.sqlconnection.connectionstring.aspx
        and https://msdn.microsoft.com/en-us/library/system.data.sqlclient.sqlconnectionstringbuilder.aspx,
        and https://msdn.microsoft.com/en-us/library/system.data.sqlclient.sqlconnection.aspx

        To execute SQL commands, you can use $server.ConnectionContext.ExecuteReader($sql) or $server.Databases['master'].ExecuteNonQuery($sql)

    .PARAMETER SqlInstance
        SQL Server name or SMO object representing the SQL Server to connect to. This can be a collection and receive pipeline input to allow the function to be executed against multiple SQL Server instances.

    .PARAMETER Credential
        Credential object used to connect to the SQL Server Instance as a different user. This can be a Windows or SQL Server account. Windows users are determined by the existence of a backslash, so if you are intending to use an alternative Windows connection instead of a SQL login, ensure it contains a backslash.

    .PARAMETER Database
        The database(s) to process. This list is auto-populated from the server.

    .PARAMETER AccessToken
        Gets or sets the access token for the connection.

    .PARAMETER AppendConnectionString
        Appends to the current connection string. Note that you cannot pass authentication information using this method. Use -SqlInstance and optionally -SqlCredential to set authentication information.

    .PARAMETER ApplicationIntent
        Declares the application workload type when connecting to a server.

        Valid values are "ReadOnly" and "ReadWrite".

    .PARAMETER BatchSeparator
        A string to separate groups of SQL statements being executed. By default, this is "GO".

    .PARAMETER ClientName
        By default, this command sets the client's ApplicationName property to "dbatools PowerShell module - dbatools.io - custom connection" if you're doing anything that requires profiling, you can look for this client name. Using -ClientName allows you to set your own custom client application name.

    .PARAMETER ConnectTimeout
        The length of time (in seconds) to wait for a connection to the server before terminating the attempt and generating an error.

        Valid values are integers between 0 and 2147483647.

        When opening a connection to a Azure SQL Database, set the connection timeout to 30 seconds.

    .PARAMETER EncryptConnection
        If this switch is enabled, SQL Server uses SSL encryption for all data sent between the client and server if the server has a certificate installed.

        For more information, see Connection String Syntax. https://docs.microsoft.com/en-us/dotnet/framework/data/adonet/connection-string-syntax

        Beginning in .NET Framework 4.5, when TrustServerCertificate is false and Encrypt is true, the server name (or IP address) in a SQL Server SSL certificate must exactly match the server name (or IP address) specified in the connection string. Otherwise, the connection attempt will fail. For information about support for certificates whose subject starts with a wildcard character (*), see Accepted wildcards used by server certificates for server authentication. https://support.microsoft.com/en-us/help/258858/accepted-wildcards-used-by-server-certificates-for-server-authenticati

    .PARAMETER FailoverPartner
        The name of the failover partner server where database mirroring is configured.

        If the value of this key is "" (an empty string), then Initial Catalog must be present in the connection string, and its value must not be "".

        The server name can be 128 characters or less.

        If you specify a failover partner but the failover partner server is not configured for database mirroring and the primary server (specified with the Server keyword) is not available, then the connection will fail.

        If you specify a failover partner and the primary server is not configured for database mirroring, the connection to the primary server (specified with the Server keyword) will succeed if the primary server is available.


    .PARAMETER IsActiveDirectoryUniversalAuth
        If this switch is enabled, the connection will be configured to use Azure Active Directory authentication.

    .PARAMETER LockTimeout
        Sets the time in seconds required for the connection to time out when the current transaction is locked.

    .PARAMETER MaxPoolSize
        Sets the maximum number of connections allowed in the connection pool for this specific connection string.

    .PARAMETER MinPoolSize
        Sets the minimum number of connections allowed in the connection pool for this specific connection string.

    .PARAMETER MultipleActiveResultSets
        If this switch is enabled, an application can maintain multiple active result sets (MARS).

        If this switch is not enabled, an application must process or cancel all result sets from one batch before it can execute any other batch on that connection.

    .PARAMETER MultiSubnetFailover
        If this switch is enabled, and your application is connecting to an AlwaysOn availability group (AG) on different subnets, detection of and connection to the currently active server will be faster. For more information about SqlClient support for Always On Availability Groups, see https://docs.microsoft.com/en-us/dotnet/framework/data/adonet/sql/sqlclient-support-for-high-availability-disaster-recovery

    .PARAMETER NetworkProtocol
        Explicitly sets the network protocol used to connect to the server.

        Valid values are "TcpIp","NamedPipes","Multiprotocol","AppleTalk","BanyanVines","Via","SharedMemory" and "NWLinkIpxSpx"

    .PARAMETER NonPooledConnection
        If this switch is enabled, a non-pooled connection will be requested.

    .PARAMETER PacketSize
        Sets the size in bytes of the network packets used to communicate with an instance of SQL Server. Must match at server.

    .PARAMETER PooledConnectionLifetime
        When a connection is returned to the pool, its creation time is compared with the current time and the connection is destroyed if that time span (in seconds) exceeds the value specified by Connection Lifetime. This is useful in clustered configurations to force load balancing between a running server and a server just brought online.

        A value of zero (0) causes pooled connections to have the maximum connection timeout.

    .PARAMETER SqlExecutionModes
        The SqlExecutionModes enumeration contains values that are used to specify whether the commands sent to the referenced connection to the server are executed immediately or saved in a buffer.

        Valid values include "CaptureSql", "ExecuteAndCaptureSql" and "ExecuteSql".

    .PARAMETER StatementTimeout
        Sets the number of seconds a statement is given to run before failing with a timeout error.

    .PARAMETER TrustServerCertificate
        When this switch is enabled, the channel will be encrypted while bypassing walking the certificate chain to validate trust.

    .PARAMETER WorkstationId
        Sets the name of the workstation connecting to SQL Server.

    .PARAMETER SqlConnectionOnly
        Instead of returning a rich SMO server object, this command will only return a SqlConnection object when setting this switch.

    .NOTES
        Tags: Connect, Connection
        Website: https://dbatools.io
        Copyright: (C) Chrissy LeMaire, [email protected]
        License: MIT https://opensource.org/licenses/MIT

    .LINK
        https://dbatools.io/Connect-DbaInstance

    .EXAMPLE
        Connect-DbaInstance -SqlInstance sql2014

        Creates an SMO Server object that connects using Windows Authentication

    .EXAMPLE
        $wincred = Get-Credential ad\sqladmin
        Connect-DbaInstance -SqlInstance sql2014 -Credential $wincred

        Creates an SMO Server object that connects using alternative Windows credentials

    .EXAMPLE
        $sqlcred = Get-Credential sqladmin
        $server = Connect-DbaInstance -SqlInstance sql2014 -Credential $sqlcred

        Login to sql2014 as SQL login sqladmin.

    .EXAMPLE
        $server = Connect-DbaInstance -SqlInstance sql2014 -ClientName "my connection"

        Creates an SMO Server object that connects using Windows Authentication and uses the client name "my connection". So when you open up profiler or use extended events, you can search for "my connection".

    .EXAMPLE
        $server = Connect-DbaInstance -SqlInstance sql2014 -AppendConnectionString "Packet Size=4096;AttachDbFilename=C:\MyFolder\MyDataFile.mdf;User Instance=true;"

        Creates an SMO Server object that connects to sql2014 using Windows Authentication, then it sets the packet size (this can also be done via -PacketSize) and other connection attributes.

    .EXAMPLE
        $server = Connect-DbaInstance -SqlInstance sql2014 -NetworkProtocol TcpIp -MultiSubnetFailover

        Creates an SMO Server object that connects using Windows Authentication that uses TCP/IP and has MultiSubnetFailover enabled.

    .EXAMPLE
        $server = Connect-DbaInstance sql2016 -ApplicationIntent ReadOnly

        Connects with ReadOnly ApplicationIntent.
#>
    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $true, ValueFromPipeline = $true)]
        [Alias("ServerInstance", "SqlServer")]
        [DbaInstanceParameter[]]$SqlInstance,
        [Alias("SqlCredential")]
        [PSCredential]$Credential,
        [object[]]$Database,
        [string]$AccessToken,
        [ValidateSet('ReadOnly', 'ReadWrite')]
        [string]$ApplicationIntent,
        [string]$BatchSeparator,
        [string]$ClientName = "dbatools PowerShell module - dbatools.io - custom connection",
        [int]$ConnectTimeout = ([Sqlcollaborative.Dbatools.Connection.ConnectionHost]::SqlConnectionTimeout),
        [switch]$EncryptConnection,
        [string]$FailoverPartner,
        [switch]$IsActiveDirectoryUniversalAuth,
        [int]$LockTimeout,
        [int]$MaxPoolSize,
        [int]$MinPoolSize,
        [switch]$MultipleActiveResultSets,
        [switch]$MultiSubnetFailover,
        [ValidateSet('TcpIp', 'NamedPipes', 'Multiprotocol', 'AppleTalk', 'BanyanVines', 'Via', 'SharedMemory', 'NWLinkIpxSpx')]
        [string]$NetworkProtocol,
        [switch]$NonPooledConnection,
        [int]$PacketSize,
        [int]$PooledConnectionLifetime,
        [ValidateSet('CaptureSql', 'ExecuteAndCaptureSql', 'ExecuteSql')]
        [string]$SqlExecutionModes,
        [int]$StatementTimeout,
        [switch]$TrustServerCertificate,
        [string]$WorkstationId,
        [string]$AppendConnectionString,
        [switch]$SqlConnectionOnly
    )
    begin {
        Test-DbaDeprecation -DeprecatedOn "1.0.0" -EnableException:$false -Alias Connect-DbaSqlServer
        Test-DbaDeprecation -DeprecatedOn "1.0.0" -EnableException:$false -Alias Get-DbaInstance

        $loadedSmoVersion = [AppDomain]::CurrentDomain.GetAssemblies() | Where-Object { $_.Fullname -like "Microsoft.SqlServer.SMO,*" }

        if ($loadedSmoVersion) {
            $loadedSmoVersion = $loadedSmoVersion | ForEach-Object {
                if ($_.Location -match "__") {
                    ((Split-Path (Split-Path $_.Location) -Leaf) -split "__")[0]
                }
                else {
                    ((Get-ChildItem -Path $_.Location).VersionInfo.ProductVersion)
                }
            }
        }
        #'PrimaryFilePath' seems the culprit for slow SMO on databases
        $Fields2000_Db = 'Collation', 'CompatibilityLevel', 'CreateDate', 'ID', 'IsAccessible', 'IsFullTextEnabled', 'IsSystemObject', 'IsUpdateable', 'LastBackupDate', 'LastDifferentialBackupDate', 'LastLogBackupDate', 'Name', 'Owner', 'ReadOnly', 'RecoveryModel', 'ReplicationOptions', 'Status', 'Version'
        $Fields200x_Db = $Fields2000_Db + @('BrokerEnabled', 'DatabaseSnapshotBaseName', 'IsMirroringEnabled', 'Trustworthy')
        $Fields201x_Db = $Fields200x_Db + @('ActiveConnections', 'AvailabilityDatabaseSynchronizationState', 'AvailabilityGroupName', 'ContainmentType', 'EncryptionEnabled')

        $Fields2000_Login = 'CreateDate' , 'DateLastModified' , 'DefaultDatabase' , 'DenyWindowsLogin' , 'IsSystemObject' , 'Language' , 'LanguageAlias' , 'LoginType' , 'Name' , 'Sid' , 'WindowsLoginAccessType'
        $Fields200x_Login = $Fields2000_Login + @('AsymmetricKey', 'Certificate', 'Credential', 'ID', 'IsDisabled', 'IsLocked', 'IsPasswordExpired', 'MustChangePassword', 'PasswordExpirationEnabled', 'PasswordPolicyEnforced')
        $Fields201x_Login = $Fields200x_Login + @('PasswordHashAlgorithm')


    }
    process {
        foreach ($instance in $SqlInstance) {
            if ($instance.Type -like "Server") {
                if ($instance.InputObject.ConnectionContext.IsOpen -eq $false) {
                    $instance.InputObject.ConnectionContext.Connect()
                }
                if ($SqlConnectionOnly) { return $instance.InputObject.ConnectionContext.SqlConnectionObject }
                else { return $instance.InputObject }
            }
            if ($instance.Type -like "SqlConnection") {
                $server = New-Object Microsoft.SqlServer.Management.Smo.Server($instance.InputObject)

                if ($server.ConnectionContext.IsOpen -eq $false) {
                    $server.ConnectionContext.Connect()
                }
                if ($SqlConnectionOnly) { return $server.ConnectionContext.SqlConnectionObject }
                else {
                    if (-not $server.ComputerName) {
                        $parsedcomputername = $server.NetName
                        if (-not $parsedcomputername) {
                            $parsedcomputername = ([dbainstance]$instance).ComputerName
                        }
                        Add-Member -InputObject $server -NotePropertyName ComputerName -NotePropertyValue $parsedcomputername -Force
                    }
                    return $server
                }
            }

            if ($instance.IsConnectionString) { $server = New-Object Microsoft.SqlServer.Management.Smo.Server($instance.InputObject) }
            else { $server = New-Object Microsoft.SqlServer.Management.Smo.Server $instance.FullSmoName }

            if ($AppendConnectionString) {
                $connstring = $server.ConnectionContext.ConnectionString
                $server.ConnectionContext.ConnectionString = "$connstring;$appendconnectionstring"
                $server.ConnectionContext.Connect()
            }
            else {

                $server.ConnectionContext.ApplicationName = $ClientName

                if (Test-Bound -ParameterName 'AccessToken') { $server.ConnectionContext.AccessToken = $AccessToken }
                if (Test-Bound -ParameterName 'BatchSeparator') { $server.ConnectionContext.BatchSeparator = $BatchSeparator }
                if (Test-Bound -ParameterName 'ConnectTimeout') { $server.ConnectionContext.ConnectTimeout = $ConnectTimeout }
                if (Test-Bound -ParameterName 'Database') { $server.ConnectionContext.DatabaseName = $Database }
                if (Test-Bound -ParameterName 'EncryptConnection') { $server.ConnectionContext.EncryptConnection = $true }
                if (Test-Bound -ParameterName 'IsActiveDirectoryUniversalAuth') { $server.ConnectionContext.IsActiveDirectoryUniversalAuth = $true }
                if (Test-Bound -ParameterName 'LockTimeout') { $server.ConnectionContext.LockTimeout = $LockTimeout }
                if (Test-Bound -ParameterName 'MaxPoolSize') { $server.ConnectionContext.MaxPoolSize = $MaxPoolSize }
                if (Test-Bound -ParameterName 'MinPoolSize') { $server.ConnectionContext.MinPoolSize = $MinPoolSize }
                if (Test-Bound -ParameterName 'MultipleActiveResultSets') { $server.ConnectionContext.MultipleActiveResultSets = $true }
                if (Test-Bound -ParameterName 'NetworkProtocol') { $server.ConnectionContext.NetworkProtocol = $NetworkProtocol }
                if (Test-Bound -ParameterName 'NonPooledConnection') { $server.ConnectionContext.NonPooledConnection = $true }
                if (Test-Bound -ParameterName 'PacketSize') { $server.ConnectionContext.PacketSize = $PacketSize }
                if (Test-Bound -ParameterName 'PooledConnectionLifetime') { $server.ConnectionContext.PooledConnectionLifetime = $PooledConnectionLifetime }
                if (Test-Bound -ParameterName 'StatementTimeout') { $server.ConnectionContext.StatementTimeout = $StatementTimeout }
                if (Test-Bound -ParameterName 'SqlExecutionModes') { $server.ConnectionContext.SqlExecutionModes = $SqlExecutionModes }
                if (Test-Bound -ParameterName 'TrustServerCertificate') { $server.ConnectionContext.TrustServerCertificate = $true }
                if (Test-Bound -ParameterName 'WorkstationId') { $server.ConnectionContext.WorkstationId = $WorkstationId }

                $connstring = $server.ConnectionContext.ConnectionString
                if (Test-Bound -ParameterName 'MultiSubnetFailover') { $connstring = "$connstring;MultiSubnetFailover=True" }
                if (Test-Bound -ParameterName 'FailoverPartner') { $connstring = "$connstring;Failover Partner=$FailoverPartner" }
                if (Test-Bound -ParameterName 'ApplicationIntent') { $connstring = "$connstring;ApplicationIntent=$ApplicationIntent" }

                if ($connstring -ne $server.ConnectionContext.ConnectionString) {
                    $server.ConnectionContext.ConnectionString = $connstring
                }

                try {
                    if ($null -ne $Credential.username) {
                        $username = ($Credential.username).TrimStart("\")

                        if ($username -like "*\*") {
                            $username = $username.Split("\")[1]
                            $authtype = "Windows Authentication with Credential"
                            $server.ConnectionContext.LoginSecure = $true
                            $server.ConnectionContext.ConnectAsUser = $true
                            $server.ConnectionContext.ConnectAsUserName = $username
                            $server.ConnectionContext.ConnectAsUserPassword = ($Credential).GetNetworkCredential().Password
                        }
                        else {
                            $authtype = "SQL Authentication"
                            $server.ConnectionContext.LoginSecure = $false
                            $server.ConnectionContext.set_Login($username)
                            $server.ConnectionContext.set_SecurePassword($Credential.Password)
                        }
                    }

                    if ($NonPooled) {
                        $server.ConnectionContext.Connect()
                    }
                    elseif ($authtype -eq "Windows Authentication with Credential") {
                        # Make it connect in a natural way, hard to explain.
                        $null = $server.IsMemberOfWsfcCluster
                    }
                    else {
                        $server.ConnectionContext.SqlConnectionObject.Open()
                    }
                }
                catch {
                    $message = $_.Exception.InnerException.InnerException
                    $message = $message.ToString()
                    $message = ($message -Split '-->')[0]
                    $message = ($message -Split 'at System.Data.SqlClient')[0]
                    $message = ($message -Split 'at System.Data.ProviderBase')[0]
                    throw "Can't connect to $instance`: $message "
                }

            }

            if ($loadedSmoVersion -ge 11) {
                if ($server.VersionMajor -eq 8) {
                    # 2000
                    $initFieldsDb = New-Object System.Collections.Specialized.StringCollection
                    [void]$initFieldsDb.AddRange($Fields2000_Db)
                    $initFieldsLogin = New-Object System.Collections.Specialized.StringCollection
                    [void]$initFieldsLogin.AddRange($Fields2000_Login)
                    $server.SetDefaultInitFields([Microsoft.SqlServer.Management.Smo.Database], $initFieldsDb)
                    $server.SetDefaultInitFields([Microsoft.SqlServer.Management.Smo.Login], $initFieldsLogin)
                }

                elseif ($server.VersionMajor -eq 9 -or $server.VersionMajor -eq 10) {
                    # 2005 and 2008
                    $initFieldsDb = New-Object System.Collections.Specialized.StringCollection
                    [void]$initFieldsDb.AddRange($Fields200x_Db)
                    $initFieldsLogin = New-Object System.Collections.Specialized.StringCollection
                    [void]$initFieldsLogin.AddRange($Fields200x_Login)
                    $server.SetDefaultInitFields([Microsoft.SqlServer.Management.Smo.Database], $initFieldsDb)
                    $server.SetDefaultInitFields([Microsoft.SqlServer.Management.Smo.Login], $initFieldsLogin)
                }

                else {
                    # 2012 and above
                    $initFieldsDb = New-Object System.Collections.Specialized.StringCollection
                    [void]$initFieldsDb.AddRange($Fields201x_Db)
                    $initFieldsLogin = New-Object System.Collections.Specialized.StringCollection
                    [void]$initFieldsLogin.AddRange($Fields201x_Login)
                    $server.SetDefaultInitFields([Microsoft.SqlServer.Management.Smo.Database], $initFieldsDb)
                    $server.SetDefaultInitFields([Microsoft.SqlServer.Management.Smo.Login], $initFieldsLogin)
                }
            }

            if ($SqlConnectionOnly) {
                return $server.ConnectionContext.SqlConnectionObject
            }
            else {
                if (-not $server.ComputerName) {
                    $parsedcomputername = $server.NetName
                    if (-not $parsedcomputername) {
                        $parsedcomputername = ([dbainstance]$instance).ComputerName
                    }
                    Add-Member -InputObject $server -NotePropertyName ComputerName -NotePropertyValue $parsedcomputername -Force
                }
                return $server
            }
        }
    }
}
#ValidationTags#Messaging,FlowControl,Pipeline,CodeStyle#
function ConvertTo-DbaDataTable {
    <#
        .SYNOPSIS
            Creates a DataTable for an object.

        .DESCRIPTION
            Creates a DataTable based on an object's properties. This allows you to easily write to SQL Server tables.

            Thanks to Chad Miller, this is based on his script. https://gallery.technet.microsoft.com/scriptcenter/4208a159-a52e-4b99-83d4-8048468d29dd

            If the attempt to convert to datatable fails, try the -Raw parameter for less accurate datatype detection.

        .PARAMETER InputObject
            The object to transform into a DataTable.

        .PARAMETER TimeSpanType
            Specifies the type to convert TimeSpan objects into. Default is 'TotalMilliseconds'. Valid options are: 'Ticks', 'TotalDays', 'TotalHours', 'TotalMinutes', 'TotalSeconds', 'TotalMilliseconds', and 'String'.

        .PARAMETER SizeType
            Specifies the type to convert DbaSize objects to. Default is 'Int64'. Valid options are 'Int32', 'Int64', and 'String'.

        .PARAMETER IgnoreNull
            If this switch is enabled, objects with null values will be ignored (empty rows will be added by default).

        .PARAMETER Raw
            If this switch is enabled, the DataTable will be created with strings. No attempt will be made to parse/determine data types.

        .PARAMETER EnableException
            By default, when something goes wrong we try to catch it, interpret it and give you a friendly warning message.
            This avoids overwhelming you with "sea of red" exceptions, but is inconvenient because it basically disables advanced scripting.
            Using this switch turns this "nice by default" feature off and enables you to catch exceptions with your own try/catch.

        .NOTES
            Tags: DataTable, Table, Data
            Website: https://dbatools.io/
            Copyright: (C) 2016 Chrissy LeMaire
            License: MIT https://opensource.org/licenses/MIT

        .LINK
            https://dbatools.io/ConvertTo-DbaDataTable

        .OUTPUTS
            System.Object[]

        .EXAMPLE
            Get-Service | ConvertTo-DbaDataTable

            Creates a DataTable from the output of Get-Service.

        .EXAMPLE
            ConvertTo-DbaDataTable -InputObject $csv.cheesetypes

            Creates a DataTable from the CSV object $csv.cheesetypes.

        .EXAMPLE
            $dblist | ConvertTo-DbaDataTable

            Creates a DataTable from the $dblist object passed in via pipeline.

        .EXAMPLE
            Get-Process | ConvertTo-DbaDataTable -TimeSpanType TotalSeconds

            Creates a DataTable with the running processes and converts any TimeSpan property to TotalSeconds.
    #>
    [CmdletBinding()]
    [OutputType([System.Object[]])]
    param (
        [Parameter(Position = 0,
            Mandatory = $true,
            ValueFromPipeline = $true)]
        [AllowNull()]
        [PSObject[]]$InputObject,
        [Parameter(Position = 1)]
        [ValidateSet("Ticks",
            "TotalDays",
            "TotalHours",
            "TotalMinutes",
            "TotalSeconds",
            "TotalMilliseconds",
            "String")]
        [ValidateNotNullOrEmpty()]
        [string]$TimeSpanType = "TotalMilliseconds",
        [ValidateSet("Int64", "Int32", "String")]
        [string]$SizeType = "Int64",
        [switch]$IgnoreNull,
        [switch]$Raw,
        [Alias('Silent')]
        [switch]$EnableException
    )

    begin {
        Write-Message -Level Debug -Message "Bound parameters: $($PSBoundParameters.Keys -join ", ")"
        Write-Message -Level Debug -Message "TimeSpanType = $TimeSpanType | SizeType = $SizeType"
        Test-DbaDeprecation -DeprecatedOn 1.0.0 -Alias Out-DbaDataTable

        function Convert-Type {
            # This function will check so that the type is an accepted type which could be used when inserting into a table.
            # If a type is accepted (included in the $type array) then it will be passed on, otherwise it will first change type before passing it on.
            # Special types will have both their types converted as well as the value.
            # TimeSpan is a special type and will be converted into the $timespantype. (default: TotalMilliseconds) so that the timespan can be stored in a database further down the line.
            [CmdletBinding()]
            param (
                $type,

                $value,

                $timespantype = 'TotalMilliseconds',

                $sizetype = 'Int64'
            )

            $types = [System.Collections.ArrayList]@(
                'System.Int32',
                'System.UInt32',
                'System.Int16',
                'System.UInt16',
                'System.Int64',
                'System.UInt64',
                'System.Decimal',
                'System.Single',
                'System.Double',
                'System.Byte',
                'System.SByte',
                'System.Boolean',
                'System.DateTime',
                'System.Guid',
                'System.Char'
            )

            # The $special variable is used to mark the return value if a conversion was made on the value itself.
            # If this is set to true the original value will later be ignored when updating the DataTable.
            # And the value returned from this function will be used instead. (cannot modify existing properties)
            $special = $false
            $specialType = ""

            # Special types need to be converted in some way.
            # This attempt is to convert timespan into something that works in a table.
            # I couldn't decide on what to convert it to so the user can decide.
            # If the parameter is not used, TotalMilliseconds will be used as default.
            # Ticks are more accurate but I think milliseconds are more useful most of the time.
            if (($type -eq 'System.TimeSpan') -or ($type -eq 'Sqlcollaborative.Dbatools.Utility.DbaTimeSpan') -or ($type -eq 'Sqlcollaborative.Dbatools.Utility.DbaTimeSpanPretty')) {
                $special = $true
                if ($timespantype -eq 'String') {
                    $value = $value.ToString()
                    $type = 'System.String'
                }
                else {
                    # Let's use Int64 for all other types than string.
                    # We could match the type more closely with the timespantype but that can be added in the future if needed.
                    $value = $value.$timespantype
                    $type = 'System.Int64'
                }
                $specialType = 'Timespan'
            }
            elseif ($type -eq 'Sqlcollaborative.Dbatools.Utility.Size') {
                $special = $true
                switch ($sizetype) {
                    'Int64' {
                        $value = $value.Byte
                        $type = 'System.Int64'
                    }
                    'Int32' {
                        $value = $value.Byte
                        $type = 'System.Int32'
                    }
                    'String' {
                        $value = $value.ToString()
                        $type = 'System.String'
                    }
                }
                $specialType = 'Size'
            }
            elseif (-not ($type -in $types)) {
                # All types which are not found in the array will be converted into strings.
                # In this way we dont ignore it completely and it will be clear in the end why it looks as it does.
                $type = 'System.String'
            }

            # return a hashtable instead of an object. I like hashtables :)
            return @{ type = $type; Value = $value; Special = $special; SpecialType = $specialType }
        }

        function Convert-SpecialType {
            <#
            .SYNOPSIS
                Converts a value for a known column.

            .DESCRIPTION
                Converts a value for a known column.

            .PARAMETER Value
                The value to convert

            .PARAMETER Type
                The special type for which to convert

            .PARAMETER SizeType
                The size type defined by the user

            .PARAMETER TimeSpanType
                The timespan type defined by the user
        #>
            [CmdletBinding()]
            Param (
                $Value,
                [ValidateSet('Timespan', 'Size')] [string]$Type,
                [string]$SizeType,
                [string]$TimeSpanType
            )

            switch ($Type) {
                'Size' {
                    if ($SizeType -eq 'String') { return $Value.ToString() }
                    else { return $Value.Byte }
                }
                'Timespan' {
                    if ($TimeSpanType -eq 'String') {
                        $Value.ToString()
                    }
                    else {
                        $Value.$TimeSpanType
                    }
                }
            }
        }

        function Add-Column {
            <#
            .SYNOPSIS
                Adds a column to the datatable in progress.

            .DESCRIPTION
                Adds a column to the datatable in progress.

            .PARAMETER Property
                The property for which to add a column.

            .PARAMETER DataTable
                Autofilled. The table for which to add a column.

            .PARAMETER TimeSpanType
                Autofilled. How should timespans be handled?

            .PARAMETER SizeType
                Autofilled. How should sizes be handled?

            .PARAMETER Raw
                Autofilled. Whether the column should be string, no matter the input.
        #>
            [CmdletBinding()]
            Param (
                [System.Management.Automation.PSPropertyInfo]$Property,
                [System.Data.DataTable]$DataTable = $datatable,
                [string]$TimeSpanType = $TimeSpanType,
                [string]$SizeType = $SizeType,
                [bool]$Raw = $Raw
            )

            $type = $property.TypeNameOfValue
            try {
                if ($Property.MemberType -like 'ScriptProperty') {
                    $type = $Property.GetType().FullName
                }
            }
            catch { $type = 'System.String' }

            $converted = Convert-Type -type $type -value $property.Value -timespantype $TimeSpanType -sizetype $SizeType

            $column = New-Object System.Data.DataColumn
            $column.ColumnName = $property.Name.ToString()
            if (-not $Raw) {
                $column.DataType = [System.Type]::GetType($converted.type)
            }
            $null = $DataTable.Columns.Add($column)
            $converted
        }

        $datatable = New-Object System.Data.DataTable

        # Accelerate subsequent lookups of columns and special type columns
        $columns = @()
        $specialColumns = @()
        $specialColumnsType = @{ }

        $ShouldCreateColumns = $true
    }

    process {
        #region Handle null objects
        if ($null -eq $InputObject) {
            if (-not $IgnoreNull) {
                $datarow = $datatable.NewRow()
                $datatable.Rows.Add($datarow)
            }

            # Only ends the current process block
            return
        }
        #endregion Handle null objects


        foreach ($object in $InputObject) {
            #region Handle null objects
            if ($null -eq $object) {
                if (-not $IgnoreNull) {
                    $datarow = $datatable.NewRow()
                    $datatable.Rows.Add($datarow)
                }
                continue
            }
            #endregion Handle null objects

            #Handle rows already being System.Data.DataRow
            if ($object.GetType().FullName -eq 'System.Data.DataRow') {
                if ($ShouldCreateColumns) {
                    $datatable = $object.Table.Copy()
                    $ShouldCreateColumns = $false
                }
                continue
            }

            # The new row to insert
            $datarow = $datatable.NewRow()

            #region Process Properties
            $objectProperties = $object.PSObject.Properties
            foreach ($property in $objectProperties) {
                #region Create Columns as needed
                if ($ShouldCreateColumns) {
                    $newColumn = Add-Column -Property $property
                    $columns += $property.Name
                    if ($newColumn.Special) {
                        $specialColumns += $property.Name
                        $specialColumnsType[$property.Name] = $newColumn.SpecialType
                    }
                }
                #endregion Create Columns as needed

                # Handle null properties, as well as properties with access errors
                try {
                    $propValueLength = $property.value.length
                }
                catch {
                    $propValueLength = 0
                }

                #region Insert value into column of row
                if ($propValueLength -gt 0) {
                    # If the typename was a special typename we want to use the value returned from Convert-Type instead.
                    # We might get error if we try to change the value for $property.value if it is read-only. That's why we use $converted.value instead.
                    if ($property.Name -in $specialColumns) {
                        $datarow.Item($property.Name) = Convert-SpecialType -Value $property.value -Type $specialColumnsType[$property.Name] -SizeType $SizeType -TimeSpanType $TimeSpanType
                    }
                    else {
                        if ($property.value.ToString().length -eq 15) {
                            if ($property.value.ToString() -eq 'System.Object[]') {
                                $value = $property.value -join ", "
                            }
                            elseif ($property.value.ToString() -eq 'System.String[]') {
                                $value = $property.value -join ", "
                            }
                            else {
                                $value = $property.value
                            }
                        }
                        else {
                            $value = $property.value
                        }

                        try {
                            $datarow.Item($property.Name) = $value
                        }
                        catch {
                            if ($property.Name -notin $columns) {
                                try {
                                    $newColumn = Add-Column -Property $property
                                    $columns += $property.Name
                                    if ($newColumn.Special) {
                                        $specialColumns += $property.Name
                                        $specialColumnsType[$property.Name] = $newColumn.SpecialType
                                    }

                                    $datarow.Item($property.Name) = $newColumn.Value
                                }
                                catch {
                                    Write-Message -Level Warning -Message "Failed to add property $($property.Name) from $object" -ErrorRecord $_ -Target $object
                                }
                            }
                            else {
                                Write-Message -Level Warning -Message "Failed to add property $($property.Name) from $object" -ErrorRecord $_ -Target $object
                            }
                        }
                    }
                }
                #endregion Insert value into column of row
            }

            $datatable.Rows.Add($datarow)
            # If this is the first non-null object then the columns has just been created.
            # Set variable to false to skip creating columns from now on.
            if ($ShouldCreateColumns) {
                $ShouldCreateColumns = $false
            }
            #endregion Process Properties
        }
    }

    end {
        Write-Message -Level InternalComment -Message "Finished."
        , $datatable
    }
}
function ConvertTo-DbaTimeline {
    <#
        .SYNOPSIS
            Converts InputObject to a html timeline using Google Chart

        .DESCRIPTION
            This function accepts input as pipeline from the following psdbatools functions:
                Get-DbaAgentJobHistory
                Get-DbaBackupHistory
                (more to come...)
            And generates Bootstrap based, HTML file with Google Chart Timeline

        .PARAMETER InputObject

            Pipe input, must an output from the above functions.

        .PARAMETER EnableException
            By default, when something goes wrong we try to catch it, interpret it and give you a friendly warning message.
            This avoids overwhelming you with "sea of red" exceptions, but is inconvenient because it basically disables advanced scripting.
            Using this switch turns this "nice by default" feature off and enables you to catch exceptions with your own try/catch.

        .NOTES
            Tags: Chart
            Author: Marcin Gminski (@marcingminski)
            Dependency: ConvertTo-JsDate, Convert-DbaTimelineStatusColor

            Website: https://dbatools.io
            Copyright: (C) Chrissy LeMaire, [email protected]
-           License: MIT https://opensource.org/licenses/MIT

        .LINK
            https://dbatools.io/ConvertTo-DbaTimeline

        .EXAMPLE
            Get-DbaAgentJobHistory -SqlInstance sql-1 -StartDate '2018-08-13 00:00' -EndDate '2018-08-13 23:59' -NoJobSteps | ConvertTo-DbaTimeline | Out-File C:\temp\DbaAgentJobHistory.html -Encoding ASCII

            Creates an output file containing a pretty timeline for all of the agent job history results for sql-1 the whole day of 2018-08-13

        .EXAMPLE
            Get-DbaRegisteredServer -SqlInstance sqlcm | Get-DbaBackupHistory -Since '2018-08-13 00:00' | ConvertTo-DbaTimeline | Out-File C:\temp\DbaBackupHistory.html -Encoding ASCII

            Creates an output file containing a pretty timeline for the agent job history since 2018-08-13 for all of the registered servers on sqlcm

        .EXAMPLE
            $messageParameters = @{
                Subject = "Backup history for sql2017 and sql2016"
                Body = Get-DbaBackupHistory -SqlInstance sql2017, sql2016 -Since '2018-08-13 00:00' | ConvertTo-DbaTimeline
                From = "[email protected]"
                To = "[email protected]"
                SmtpServer = "smtp.ad.local"
            }
            Send-MailMessage @messageParameters -BodyAsHtml

            Sends an email to [email protected] with the results of Get-DbaBackupHistory. Note that viewing these reports may not be supported in all email clients.
    #>

    [CmdletBinding()]
    Param (
        [parameter(Mandatory, ValueFromPipeline)]
        [object[]]$InputObject,
        [switch]$EnableException
    )
    begin {
        $body = $servers = @()
        $begin = @"
<html>
<head>
<!-- Developed by Marcin Gminski, https://marcin.gminski.net, 2018 -->
<!-- Load jQuery required to autosize timeline -->
<script src="https://code.jquery.com/jquery-3.3.1.min.js" integrity="sha256-FgpCb/KJQlLNfOu91ta32o/NMZxltwRo8QtmkMRdAu8=" crossorigin="anonymous"></script>
<!-- Load Bootstrap -->
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js" integrity="sha384-Tc5IQib027qvyjSMfHjOMaLkfuWVxZxUPnCJA7l2mCWNIpG9mGCD8wGNIcPD7Txa" crossorigin="anonymous"></script>
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous">
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap-theme.min.css" integrity="sha384-rHyoN1iRsVXV4nD0JutlnGaslCJuC7uwjduW9SVrLvRYooPp2bWYgmgJQIXwl/Sp" crossorigin="anonymous">
<!-- Load Google Charts library -->
<script type="text/javascript" src="https://www.gstatic.com/charts/loader.js"></script>
<!-- a bit of custom styling to work with bootstrap grid -->
<style>

    html,body{height:100%;background-color:#c2c2c2;}
    .viewport {height:100%}

    .chart{
        background-color:#fff;
        text-align:left;
        padding:0;
        border:1px solid #7D7D7D;
        -webkit-box-shadow:1px 1px 3px 0 rgba(0,0,0,.45);
        -moz-box-shadow:1px 1px 3px 0 rgba(0,0,0,.45);
        box-shadow:1px 1px 3px 0 rgba(0,0,0,.45)
    }
    .badge-custom{background-color:#939}
    .container {
        height:100%;
    }
    .fill{
        width:100%;
        height:100%;
        min-height:100%;
        padding:10px;
    }
    .timeline-tooltip{
        border:1px solid #E0E0E0;
        font-family:Arial,Helvetica;
        font-size:10pt;
        padding:12px
    }
    .timeline-tooltip div{padding:6px}
    .timeline-tooltip span{font-weight:700}
</style>
    <script type="text/javascript">
    google.charts.load('43', {'packages':['timeline']});
    google.charts.setOnLoadCallback(drawChart);
    function drawChart() {
        var container = document.getElementById('Chart');
        var chart = new google.visualization.Timeline(container);
        var dataTable = new google.visualization.DataTable();
        dataTable.addColumn({type: 'string', id: 'vLabel'});
        dataTable.addColumn({type: 'string', id: 'hLabel'});
        dataTable.addColumn({type: 'string', role: 'style' });
        dataTable.addColumn({type: 'date', id: 'date_start'});
        dataTable.addColumn({type: 'date', id: 'date_end'});

        dataTable.addRows([
"@
    }

    process {
        # build html container
        $BaseObject = $InputObject.PsObject.BaseObject

        # create server list to support multiple servers
        if ($InputObject[0].SqlInstance -notin $servers) {
            $servers += $InputObject[0].SqlInstance
        }
        # This is where do column mapping.

        # Check for types - this will help support if someone assigns a variable then pipes
        # AgentJobHistory is a forced type while backuphistory is a legit type
        if ($InputObject[0].TypeName -eq 'AgentJobHistory') {
            $CallerName = "Get-DbaAgentJobHistory"
            $data = $InputObject | Select-Object @{ Name = "SqlInstance"; Expression = { $_.SqlInstance } }, @{ Name = "InstanceName"; Expression = { $_.InstanceName } }, @{ Name = "vLabel"; Expression = { $_.Job } }, @{ Name = "hLabel"; Expression = { $_.Status } }, @{ Name = "Style"; Expression = { $(Convert-DbaTimelineStatusColor($_.Status)) } }, @{ Name = "StartDate"; Expression = { $(ConvertTo-JsDate($_.StartDate)) } }, @{ Name = "EndDate"; Expression = { $(ConvertTo-JsDate($_.EndDate)) } }

        }
        elseif ($InputObject[0] -is [Sqlcollaborative.Dbatools.Database.BackupHistory]) {
            $CallerName = "Get-DbaBackupHistory"
            $data = $InputObject | Select-Object @{ Name = "SqlInstance"; Expression = { $_.SqlInstance } }, @{ Name = "InstanceName"; Expression = { $_.InstanceName } }, @{ Name = "vLabel"; Expression = { $_.Database } }, @{ Name = "hLabel"; Expression = { $_.Type } }, @{ Name = "StartDate"; Expression = { $(ConvertTo-JsDate($_.Start)) } }, @{ Name = "EndDate"; Expression = { $(ConvertTo-JsDate($_.End)) } }
        }
        else {
            # sorry to be so formal, can't help it ;)
            Stop-Function -Message "Unsupported input data. To request support for additional commands, please file an issue at dbatools.io/issues and we'll take a look"
            return
        }
        $body += "$($data | ForEach-Object{ "['$($_.vLabel)','$($_.hLabel)','$($_.Style)',$($_.StartDate), $($_.EndDate)]," })"
    }
    end {
        if (Test-FunctionInterrupt) { return }
$end = @"
]);
        var paddingHeight = 20;
        var rowHeight = dataTable.getNumberOfRows() * 41;
        var chartHeight = rowHeight + paddingHeight;
        dataTable.insertColumn(2, {type: 'string', role: 'tooltip', p: {html: true}});
        var dateFormat = new google.visualization.DateFormat({
          pattern: 'dd/MM/yy HH:mm:ss'
        });
        for (var i = 0; i < dataTable.getNumberOfRows(); i++) {
          var duration = (dataTable.getValue(i, 5).getTime() - dataTable.getValue(i, 4).getTime()) / 1000;
          var hours = parseInt( duration / 3600 ) % 24;
          var minutes = parseInt( duration / 60 ) % 60;
          var seconds = duration % 60;
          var tooltip = '<div class="timeline-tooltip"><span>' +
            dataTable.getValue(i, 1).split(",").join("<br />")  + '</span></div><div class="timeline-tooltip"><span>' +
            dataTable.getValue(i, 0) + '</span>: ' +
            dateFormat.formatValue(dataTable.getValue(i, 4)) + ' - ' +
            dateFormat.formatValue(dataTable.getValue(i, 5)) + '</div>' +
            '<div class="timeline-tooltip"><span>Duration: </span>' +
            hours + 'h ' + minutes + 'm ' + seconds + 's ';
          dataTable.setValue(i, 2, tooltip);
        }
        var options = {
            timeline: {
                rowLabelStyle: { },
                barLabelStyle: { },
            },
            hAxis: {
                format: 'dd/MM HH:mm',
            },
        }
        // Autosize chart. It would not be enough to just count rows and expand based on row height as there can be overlappig rows.
        // this will draw the chart, get the size of the underlying div and apply that size to the parent container and redraw:
        chart.draw(dataTable, options);
        // get the size of the chold div:
        var realheight= parseInt(`$("#Chart div:first-child div:first-child div:first-child div svg").attr( "height"))+70;
        // set the height:
        options.height=realheight
        // draw again:
        chart.draw(dataTable, options);
    }
</script>
</head>
<body>
    <div class="container-fluid">
    <div class="pull-left"><h3><code>$($CallerName)</code> timeline for server <code>$($servers -join ', ')</code></h3></div><div class="pull-right text-right"><img class="text-right" style="vertical-align:bottom; margin-top: 10px;" src="https://dbatools.io/wp-content/uploads/2016/05/dbatools-logo-1.png" width=150></div>
         <div class="clearfix"></div>
         <div class="col-12">
            <div class="chart" id="Chart"></div>
         </div>
         <hr>
    <p><a href="https://dbatools.io">dbatools.io</a> - the community's sql powershell module. Find us on Twitter: <a href="https://twitter.com/psdbatools">@psdbatools</a> | Chart by <a href="https://twitter.com/marcingminski">@marcingminski</a></p>
</div>
</body>
</html>
"@
        $begin, $body, $end
    }
}
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
            Website: https://dbatools.io
            Copyright: (C) Chrissy LeMaire, [email protected]
            License: MIT https://opensource.org/licenses/MIT

        .EXAMPLE
            Get-DbaTrace -SqlInstance sql2017, sql2012 | Where Id -eq 2 | ConvertTo-DbaXESession -Name 'Test'

            Converts Trace with ID 2 to a Session named Test on SQL Server instances named sql2017 and sql2012
            and creates the Session on each respective server.

       .EXAMPLE
            Get-DbaTrace -SqlInstance sql2014 | Out-GridView -PassThru | ConvertTo-DbaXESession -Name 'Test' | Start-DbaXESession

            Converts selected traces on sql2014 to sessions, creates the session, and starts it.

        .EXAMPLE
            Get-DbaTrace -SqlInstance sql2014 | Where Id -eq 1 | ConvertTo-DbaXESession -Name 'Test' -OutputScriptOnly

            Converts trace ID 1 on sql2014 to an Extended Event and outputs the resulting T-SQL.
    #>
    [CmdletBinding()]
    Param (
        [parameter(Mandatory, ValueFromPipeline)]
        [object[]]$InputObject,
        [parameter(Mandatory)]
        [string]$Name,
        [switch]$OutputScriptOnly,
        [switch]$EnableException
    )
    begin {
        $rawsql = Get-Content "$script:PSModuleRoot\bin\sp_SQLskills_ConvertTraceToEEs.sql" -Raw
    }
    process {
        foreach ($trace in $InputObject) {
            if (-not $trace.id -and -not $trace.Parent) {
                Stop-Function -Message "Input is of the wrong type. Use Get-DbaTrace." -Continue
                return
            }

            $server = $trace.Parent

            if ($server.VersionMajor -lt 11) {
                Stop-Function -Message "SQL Server version 2012+ required - $server not supported."
                return
            }

            $tempdb = $server.Databases['tempdb']
            $traceid = $trace.id

            if ((Get-DbaXESession -SqlInstance $server -Session $PSBoundParameters.Name)) {
                $oldname = $name
                $Name = "$name-$traceid"
                Write-Message -Level Output -Message "XE Session $oldname already exists on $server, trying $name."
            }

            if ((Get-DbaXESession -SqlInstance $server -Session $Name)) {
                $oldname = $name
                $Name = "$name-$(Get-Random)"
                Write-Message -Level Output -Message "XE Session $oldname already exists on $server, trying $name."
            }

            $sql = $rawsql.Replace("--TRACEID--", $traceid)
            $sql = $sql.Replace("--SESSIONNAME--", $name)

            try {
                Write-Message -Level Verbose -Message "Executing SQL in tempdb."
                $results = $tempdb.ExecuteWithResults($sql).Tables.Rows.SqlString
            }
            catch {
                Stop-Function -Message "Issue creating, dropping or executing sp_SQLskills_ConvertTraceToExtendedEvents in tempdb on $server." -Target $server -ErrorRecord $_
            }

            $results = $results -join "`r`n"

            if ($OutputScriptOnly) {
                $results
            }
            else {
                Write-Message -Level Verbose -Message "Creating XE Session $name."
                try {
                    $tempdb.ExecuteNonQuery($results)
                }
                catch {
                    Stop-Function -Message "Issue creating extended event $name on $server." -Target $server -ErrorRecord $_
                }
                Get-DbaXESession -SqlInstance $server -Session $name
            }
        }
    }
}
function Copy-DbaAgentAlert {
    <#
        .SYNOPSIS
            Copy-DbaAgentAlert migrates alerts from one SQL Server to another.

        .DESCRIPTION
            By default, all alerts are copied. The -Alert parameter is auto-populated for command-line completion and can be used to copy only specific alerts.

            If the alert already exists on the destination, it will be skipped unless -Force is used.

        .PARAMETER Source
            Source SQL Server. You must have sysadmin access and server version must be SQL Server version 2000 or higher.

        .PARAMETER SourceSqlCredential
            Login to the target instance using alternative credentials. Windows and SQL Authentication supported. Accepts credential objects (Get-Credential)

        .PARAMETER Destination
            Destination SQL Server. You must have sysadmin access and the server must be SQL Server 2000 or higher.

        .PARAMETER DestinationSqlCredential
            Login to the target instance using alternative credentials. Windows and SQL Authentication supported. Accepts credential objects (Get-Credential)

        .PARAMETER Alert
            The alert(s) to process. This list is auto-populated from the server. If unspecified, all alerts will be processed.

        .PARAMETER ExcludeAlert
            The alert(s) to exclude. This list is auto-populated from the server.

        .PARAMETER IncludeDefaults
            Copy SQL Agent defaults such as FailSafeEmailAddress, ForwardingServer, and PagerSubjectTemplate.

        .PARAMETER WhatIf
            If this switch is enabled, no actions are performed but informational messages will be displayed that explain what would happen if the command were to run.

        .PARAMETER Confirm
            If this switch is enabled, you will be prompted for confirmation before executing any operations that change state.

        .PARAMETER Force
            If this switch is enabled, the Alert will be dropped and recreated on Destination.

        .PARAMETER EnableException
            By default, when something goes wrong we try to catch it, interpret it and give you a friendly warning message.
            This avoids overwhelming you with "sea of red" exceptions, but is inconvenient because it basically disables advanced scripting.
            Using this switch turns this "nice by default" feature off and enables you to catch exceptions with your own try/catch.

        .NOTES
            Tags: Migration, Agent
            Author: Chrissy LeMaire (@cl), netnerds.net
            Requires: sysadmin access on SQL Servers

            Website: https://dbatools.io
            Copyright: (C) Chrissy LeMaire, [email protected]
            License: MIT https://opensource.org/licenses/MIT

        .LINK
            https://dbatools.io/Copy-DbaAgentAlert

        .EXAMPLE
            Copy-DbaAgentAlert -Source sqlserver2014a -Destination sqlcluster

            Copies all alerts from sqlserver2014a to sqlcluster using Windows credentials. If alerts with the same name exist on sqlcluster, they will be skipped.

        .EXAMPLE
            Copy-DbaAgentAlert -Source sqlserver2014a -Destination sqlcluster -Alert PSAlert -SourceSqlCredential $cred -Force

            Copies a only the alert named PSAlert from sqlserver2014a to sqlcluster using SQL credentials for sqlserver2014a and Windows credentials for sqlcluster. If a alert with the same name exists on sqlcluster, it will be dropped and recreated because -Force was used.

        .EXAMPLE
            Copy-DbaAgentAlert -Source sqlserver2014a -Destination sqlcluster -WhatIf -Force

            Shows what would happen if the command were executed using force.
    #>
    [cmdletbinding(DefaultParameterSetName = "Default", SupportsShouldProcess = $true)]
    param (
        [parameter(Mandatory = $true)]
        [DbaInstanceParameter]$Source,
        [PSCredential]$SourceSqlCredential,
        [parameter(Mandatory = $true)]
        [DbaInstanceParameter[]]$Destination,
        [PSCredential]$DestinationSqlCredential,
        [object[]]$Alert,
        [object[]]$ExcludeAlert,
        [switch]$IncludeDefaults,
        [switch]$Force,
        [Alias('Silent')]
        [switch]$EnableException
    )
    begin {
        try {
            Write-Message -Level Verbose -Message "Connecting to $Source"
            $sourceServer = Connect-SqlInstance -SqlInstance $Source -SqlCredential $SourceSqlCredential
            $serverAlerts = $sourceServer.JobServer.Alerts
        }
        catch {
            Stop-Function -Message "Failure" -Category ConnectionError -ErrorRecord $_ -Target $Source
            return
        }
    }
    process {
        if (Test-FunctionInterrupt) { return }
        foreach ($destinstance in $Destination) {
            try {
                Write-Message -Level Verbose -Message "Connecting to $destinstance"
                $destServer = Connect-SqlInstance -SqlInstance $destinstance -SqlCredential $DestinationSqlCredential
            }
            catch {
                Stop-Function -Message "Failure" -Category ConnectionError -ErrorRecord $_ -Target $destinstance -Continue
            }
            $destAlerts = $destServer.JobServer.Alerts
            
            if ($IncludeDefaults -eq $true) {
                if ($PSCmdlet.ShouldProcess($destinstance, "Creating Alert Defaults")) {
                    $copyAgentAlertStatus = [pscustomobject]@{
                        SourceServer = $sourceServer.Name
                        DestinationServer = $destServer.Name
                        Name         = "Alert Defaults"
                        Type         = "Alert Defaults"
                        Status       = $null
                        Notes        = $null
                        DateTime     = [Sqlcollaborative.Dbatools.Utility.DbaDateTime](Get-Date)
                    }
                    try {
                        Write-Message -Message "Creating Alert Defaults" -Level Verbose
                        $sql = $sourceServer.JobServer.AlertSystem.Script() | Out-String
                        $sql = $sql -replace [Regex]::Escape("'$source'"), "'$destinstance'"
                        
                        Write-Message -Message $sql -Level Debug
                        $null = $destServer.Query($sql)
                        
                        $copyAgentAlertStatus.Status = "Successful"
                    }
                    catch {
                        $copyAgentAlertStatus.Status = "Failed"
                        $copyAgentAlertStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
                        Stop-Function -Message "Issue creating alert defaults." -Category InvalidOperation -ErrorRecord $_ -Target $destServer -Continue
                    }
                    $copyAgentAlertStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
                }
            }
            
            foreach ($serverAlert in $serverAlerts) {
                $alertName = $serverAlert.name
                $copyAgentAlertStatus = [pscustomobject]@{
                    SourceServer = $sourceServer.Name
                    DestinationServer = $destServer.Name
                    Name         = $alertName
                    Type         = "Agent Alert"
                    Notes        = $null
                    Status       = $null
                    DateTime     = [Sqlcollaborative.Dbatools.Utility.DbaDateTime](Get-Date)
                }
                if (($Alert -and $Alert -notcontains $alertName) -or ($ExcludeAlert -and $ExcludeAlert -contains $alertName)) {
                    continue
                }
                
                if ($destAlerts.name -contains $serverAlert.name) {
                    if ($force -eq $false) {
                        if ($PSCmdlet.ShouldProcess($destinstance, "Alert [$alertName] exists at destination. Use -Force to drop and migrate.")) {
                            $copyAgentAlertStatus.Status = "Skipped"
                            $copyAgentAlertStatus.Notes = "Already exists"
                            $copyAgentAlertStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
                            Write-Message -Message "Alert [$alertName] exists at destination. Use -Force to drop and migrate." -Level Verbose
                        }
                        continue
                    }
                    
                    if ($PSCmdlet.ShouldProcess($destinstance, "Dropping alert $alertName and recreating")) {
                        try {
                            Write-Message -Message "Dropping Alert $alertName on $destServer." -Level Verbose
                            
                            $sql = "EXEC msdb.dbo.sp_delete_alert @name = N'$($alertname)';"
                            Write-Message -Message $sql -Level Debug
                            $null = $destServer.Query($sql)
                            $destAlerts.Refresh()
                        }
                        catch {
                            $copyAgentAlertStatus.Status = "Failed"
                            $copyAgentAlertStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
                            Stop-Function -Message "Issue dropping/recreating alert" -Category InvalidOperation -ErrorRecord $_ -Target $destServer -Continue
                        }
                    }
                }
                
                if ($destAlerts | Where-Object { $_.Severity -eq $serverAlert.Severity -and $_.MessageID -eq $serverAlert.MessageID -and $_.DatabaseName -eq $serverAlert.DatabaseName -and $_.EventDescriptionKeyword -eq $serverAlert.EventDescriptionKeyword }) {
                    if ($PSCmdlet.ShouldProcess($destinstance, "Checking for conflicts")) {
                        $conflictMessage = "Alert [$alertName] has already been defined to use"
                        if ($serverAlert.Severity -gt 0) { $conflictMessage += " severity $($serverAlert.Severity)" }
                        if ($serverAlert.MessageID -gt 0) { $conflictMessage += " error number $($serverAlert.MessageID)" }
                        if ($serverAlert.DatabaseName) { $conflictMessage += " on database '$($serverAlert.DatabaseName)'" }
                        if ($serverAlert.EventDescriptionKeyword) { $conflictMessage += " with error text '$($serverAlert.Severity)'" }
                        $conflictMessage += ". Skipping."
                        
                        Write-Message -Level Verbose -Message $conflictMessage
                        $copyAgentAlertStatus.Status = "Skipped"
                        $copyAgentAlertStatus.Notes = $conflictMessage
                        $copyAgentAlertStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
                    }
                    continue
                }
                if ($serverAlert.JobName -and $destServer.JobServer.Jobs.Name -NotContains $serverAlert.JobName) {
                    Write-Message -Level Verbose -Message "Alert [$alertName] has job [$($serverAlert.JobName)] configured as response. The job does not exist on destination $destServer. Skipping."
                    if ($PSCmdlet.ShouldProcess($destinstance, "Checking for conflicts")) {
                        $copyAgentAlertStatus.Status = "Skipped"
                        $copyAgentAlertStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
                    }
                    continue
                }
                
                if ($PSCmdlet.ShouldProcess($destinstance, "Creating Alert $alertName")) {
                    try {
                        Write-Message -Message "Copying Alert $alertName" -Level Verbose
                        $sql = $serverAlert.Script() | Out-String
                        $sql = $sql -replace "@job_id=N'........-....-....-....-............", "@job_id=N'00000000-0000-0000-0000-000000000000"
                        
                        Write-Message -Message $sql -Level Debug
                        $null = $destServer.Query($sql)
                        
                        $copyAgentAlertStatus.Status = "Successful"
                        $copyAgentAlertStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
                    }
                    catch {
                        $copyAgentAlertStatus.Status = "Failed"
                        $copyAgentAlertStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
                        Stop-Function -Message "Issue creating alert" -Category InvalidOperation -ErrorRecord $_ -Target $destServer -Continue
                    }
                }
                
                $destServer.JobServer.Alerts.Refresh()
                $destServer.JobServer.Jobs.Refresh()
                
                $newAlert = $destServer.JobServer.Alerts[$alertName]
                $notifications = $serverAlert.EnumNotifications()
                $jobName = $serverAlert.JobName
                
                # JobId = 00000000-0000-0000-0000-000 means the Alert does not execute/is attached to a SQL Agent Job.
                if ($serverAlert.JobId -ne '00000000-0000-0000-0000-000000000000') {
                    $copyAgentAlertStatus = [pscustomobject]@{
                        SourceServer = $sourceServer.Name
                        DestinationServer = $destServer.Name
                        Name         = $alertName
                        Type         = "Agent Alert Job Association"
                        Notes        = "Associated with $jobName"
                        Status       = $null
                        DateTime     = [Sqlcollaborative.Dbatools.Utility.DbaDateTime](Get-Date)
                    }
                    if ($PSCmdlet.ShouldProcess($destinstance, "Adding $alertName to $jobName")) {
                        try {
                        <# THERE needs to be validation within this block to see if the $jobName actually exists on the source server. #>
                            Write-Message -Message "Adding $alertName to $jobName" -Level Verbose
                            $newJob = $destServer.JobServer.Jobs[$jobName]
                            $newJobId = ($newJob.JobId) -replace " ", ""
                            $sql = $sql -replace '00000000-0000-0000-0000-000000000000', $newJobId
                            $sql = $sql -replace 'sp_add_alert', 'sp_update_alert'
                            
                            Write-Message -Message $sql -Level Debug
                            $null = $destServer.Query($sql)
                            
                            $copyAgentAlertStatus.Status = "Successful"
                            $copyAgentAlertStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
                        }
                        catch {
                            $copyAgentAlertStatus.Status = "Failed"
                            $copyAgentAlertStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
                            Stop-Function -Message "Issue adding alert to job" -Category InvalidOperation -ErrorRecord $_ -Target $destServer
                        }
                    }
                }
                
                if ($PSCmdlet.ShouldProcess($destinstance, "Moving Notifications $alertName")) {
                    try {
                        $copyAgentAlertStatus = [pscustomobject]@{
                            SourceServer = $sourceServer.Name
                            DestinationServer = $destServer.Name
                            Name         = $alertName
                            Type         = "Agent Alert Notification"
                            Notes        = $null
                            Status       = $null
                            DateTime     = [Sqlcollaborative.Dbatools.Utility.DbaDateTime](Get-Date)
                        }
                        # cant add them this way, we need to modify the existing one or give all options that are supported.
                        foreach ($notify in $notifications) {
                            $notifyCollection = @()
                            if ($notify.UseNetSend -eq $true) {
                                Write-Message -Message "Adding net send" -Level Verbose
                                $notifyCollection += "NetSend"
                            }
                            
                            if ($notify.UseEmail -eq $true) {
                                Write-Message -Message "Adding email" -Level Verbose
                                $notifyCollection += "NotifyEmail"
                            }
                            
                            if ($notify.UsePager -eq $true) {
                                Write-Message -Message "Adding pager" -Level Verbose
                                $notifyCollection += "Pager"
                            }
                            
                            $notifyMethods = $notifyCollection -join ", "
                            $newAlert.AddNotification($notify.OperatorName, [Microsoft.SqlServer.Management.Smo.Agent.NotifyMethods]$notifyMethods)
                        }
                        $copyAgentAlertStatus.Status = "Successful"
                        $copyAgentAlertStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
                    }
                    catch {
                        $copyAgentAlertStatus.Status = "Failed"
                        $copyAgentAlertStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
                        Stop-Function -Message "Issue moving notifications for the alert" -Category InvalidOperation -ErrorRecord $_ -Target $destServer
                    }
                }
            }
        }
    }
    end {
        Test-DbaDeprecation -DeprecatedOn "1.0.0" -EnableException:$false -Alias Copy-SqlAlert
    }
}
#ValidationTags#Messaging#
function Copy-DbaAgentCategory {
    <#
        .SYNOPSIS
            Copy-DbaAgentCategory migrates SQL Agent categories from one SQL Server to another. This is similar to sp_add_category.

            https://msdn.microsoft.com/en-us/library/ms181597.aspx

        .DESCRIPTION
            By default, all SQL Agent categories for Jobs, Operators and Alerts are copied.

            The -OperatorCategories parameter is auto-populated for command-line completion and can be used to copy only specific operator categories.
            The -AgentCategories parameter is auto-populated for command-line completion and can be used to copy only specific agent categories.
            The -JobCategories parameter is auto-populated for command-line completion and can be used to copy only specific job categories.

            If the category already exists on the destination, it will be skipped unless -Force is used.

        .PARAMETER Source
            Source SQL Server. You must have sysadmin access and server version must be SQL Server version 2000 or higher.

        .PARAMETER SourceSqlCredential
            Login to the target instance using alternative credentials. Windows and SQL Authentication supported. Accepts credential objects (Get-Credential)

        .PARAMETER Destination
            Destination SQL Server. You must have sysadmin access and the server must be SQL Server 2000 or higher.

        .PARAMETER DestinationSqlCredential
            Login to the target instance using alternative credentials. Windows and SQL Authentication supported. Accepts credential objects (Get-Credential)

        .PARAMETER CategoryType
            Specifies the Category Type to migrate. Valid options are "Job", "Alert" and "Operator". When CategoryType is specified, all categories from the selected type will be migrated. For granular migrations, use the three parameters below.

        .PARAMETER OperatorCategory
            This parameter is auto-populated for command-line completion and can be used to copy only specific operator categories.

        .PARAMETER AgentCategory
            This parameter is auto-populated for command-line completion and can be used to copy only specific agent categories.

        .PARAMETER JobCategory
            This parameter is auto-populated for command-line completion and can be used to copy only specific job categories.

        .PARAMETER WhatIf
            If this switch is enabled, no actions are performed but informational messages will be displayed that explain what would happen if the command were to run.

        .PARAMETER Confirm
            If this switch is enabled, you will be prompted for confirmation before executing any operations that change state.

        .PARAMETER Force
            If this switch is enabled, the Category will be dropped and recreated on Destination.

        .PARAMETER EnableException
            By default, when something goes wrong we try to catch it, interpret it and give you a friendly warning message.
            This avoids overwhelming you with "sea of red" exceptions, but is inconvenient because it basically disables advanced scripting.
            Using this switch turns this "nice by default" feature off and enables you to catch exceptions with your own try/catch.

        .NOTES
            Tags: Migration, Agent
            Author: Chrissy LeMaire (@cl), netnerds.net
            Requires: sysadmin access on SQL Servers

            Website: https://dbatools.io
            Copyright: (C) Chrissy LeMaire, [email protected]
            License: MIT https://opensource.org/licenses/MIT

        .LINK
            https://dbatools.io/Copy-DbaAgentCategory

        .EXAMPLE
            Copy-DbaAgentCategory -Source sqlserver2014a -Destination sqlcluster

            Copies all operator categories from sqlserver2014a to sqlcluster using Windows authentication. If operator categories with the same name exist on sqlcluster, they will be skipped.

        .EXAMPLE
            Copy-DbaAgentCategory -Source sqlserver2014a -Destination sqlcluster -OperatorCategory PSOperator -SourceSqlCredential $cred -Force

            Copies a single operator category, the PSOperator operator category from sqlserver2014a to sqlcluster using SQL credentials to authenticate to sqlserver2014a and Windows credentials for sqlcluster. If a operator category with the same name exists on sqlcluster, it will be dropped and recreated because -Force was used.

        .EXAMPLE
            Copy-DbaAgentCategory -Source sqlserver2014a -Destination sqlcluster -WhatIf -Force

            Shows what would happen if the command were executed using force.
    #>
    [CmdletBinding(DefaultParameterSetName = "Default", SupportsShouldprocess = $true)]
    param (
        [parameter(Mandatory = $true)]
        [DbaInstanceParameter]$Source,
        [PSCredential]$SourceSqlCredential,
        [parameter(Mandatory = $true)]
        [DbaInstanceParameter[]]$Destination,
        [PSCredential]$DestinationSqlCredential,
        [Parameter(ParameterSetName = 'SpecificAlerts')]
        [ValidateSet('Job', 'Alert', 'Operator')]
        [string[]]$CategoryType,
        [string[]]$JobCategory,
        [string[]]$AgentCategory,
        [string[]]$OperatorCategory,
        [switch]$Force,
        [Alias('Silent')]
        [switch]$EnableException
    )
    
    begin {
        function Copy-JobCategory {
            <#
                .SYNOPSIS
                    Copy-JobCategory migrates job categories from one SQL Server to another.

                .DESCRIPTION
                    By default, all job categories are copied. The -JobCategories parameter is auto-populated for command-line completion and can be used to copy only specific job categories.

                    If the associated credential for the category does not exist on the destination, it will be skipped. If the job category already exists on the destination, it will be skipped unless -Force is used.
            #>
            param (
                [string[]]$jobCategories
            )
            
            process {
                
                $serverJobCategories = $sourceServer.JobServer.JobCategories | Where-Object ID -ge 100
                $destJobCategories = $destServer.JobServer.JobCategories | Where-Object ID -ge 100
                
                foreach ($jobCategory in $serverJobCategories) {
                    $categoryName = $jobCategory.Name
                    
                    $copyJobCategoryStatus = [pscustomobject]@{
                        SourceServer = $sourceServer.Name
                        DestinationServer = $destServer.Name
                        Name         = $categoryName
                        Type         = "Agent Job Category"
                        Status       = $null
                        Notes        = $null
                        DateTime     = [Sqlcollaborative.Dbatools.Utility.DbaDateTime](Get-Date)
                    }
                    
                    if ($jobCategories.Count -gt 0 -and $jobCategories -notcontains $categoryName) {
                        continue
                    }
                    
                    if ($destJobCategories.Name -contains $jobCategory.name) {
                        if ($force -eq $false) {
                            $copyJobCategoryStatus.Status = "Skipped"
                            $copyJobCategoryStatus.Notes = "Already exists"
                            $copyJobCategoryStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
                            Write-Message -Level Verbose -Message "Job category $categoryName exists at destination. Use -Force to drop and migrate."
                            continue
                        }
                        else {
                            if ($Pscmdlet.ShouldProcess($destinstance, "Dropping job category $categoryName")) {
                                try {
                                    Write-Message -Level Verbose -Message "Dropping Job category $categoryName"
                                    $destServer.JobServer.JobCategories[$categoryName].Drop()
                                }
                                catch {
                                    $copyJobCategoryStatus.Status = "Failed"
                                    $copyJobCategoryStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
                                    Stop-Function -Message "Issue dropping job category" -Target $categoryName -ErrorRecord $_ -Continue
                                }
                            }
                        }
                    }
                    
                    if ($Pscmdlet.ShouldProcess($destinstance, "Creating Job category $categoryName")) {
                        try {
                            Write-Message -Level Verbose -Message "Copying Job category $categoryName"
                            $sql = $jobCategory.Script() | Out-String
                            Write-Message -Level Debug -Message "SQL Statement: $sql"
                            $destServer.Query($sql)
                            
                            $copyJobCategoryStatus.Status = "Successful"
                            $copyJobCategoryStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
                        }
                        catch {
                            $copyJobCategoryStatus.Status = "Failed"
                            $copyJobCategoryStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
                            Stop-Function -Message "Issue copying job category" -Target $categoryName -ErrorRecord $_
                        }
                    }
                }
            }
        }
        
        function Copy-OperatorCategory {
            <#
                .SYNOPSIS
                    Copy-OperatorCategory migrates operator categories from one SQL Server to another.

                .DESCRIPTION
                    By default, all operator categories are copied. The -OperatorCategories parameter is auto-populated for command-line completion and can be used to copy only specific operator categories.

                    If the associated credential for the category does not exist on the destination, it will be skipped. If the operator category already exists on the destination, it will be skipped unless -Force is used.
            #>
            [CmdletBinding(DefaultParameterSetName = "Default", SupportsShouldprocess = $true)]
            param (
                [string[]]$operatorCategories
            )
            process {
                $serverOperatorCategories = $sourceServer.JobServer.OperatorCategories | Where-Object ID -ge 100
                $destOperatorCategories = $destServer.JobServer.OperatorCategories | Where-Object ID -ge 100
                
                foreach ($operatorCategory in $serverOperatorCategories) {
                    $categoryName = $operatorCategory.Name
                    
                    $copyOperatorCategoryStatus = [pscustomobject]@{
                        SourceServer = $sourceServer.Name
                        DestinationServer = $destServer.Name
                        Type         = "Agent Operator Category"
                        Name         = $categoryName
                        Status       = $null
                        Notes        = $null
                        DateTime     = [Sqlcollaborative.Dbatools.Utility.DbaDateTime](Get-Date)
                    }
                    
                    if ($operatorCategories.Count -gt 0 -and $operatorCategories -notcontains $categoryName) {
                        continue
                    }
                    
                    if ($destOperatorCategories.Name -contains $operatorCategory.Name) {
                        if ($force -eq $false) {
                            $copyOperatorCategoryStatus.Status = "Skipped"
                            $copyOperatorCategoryStatus.Notes = "Already exists"
                            $copyOperatorCategoryStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
                            Write-Message -Level Verbose -Message "Operator category $categoryName exists at destination. Use -Force to drop and migrate."
                            continue
                        }
                        else {
                            if ($Pscmdlet.ShouldProcess($destinstance, "Dropping operator category $categoryName and recreating")) {
                                try {
                                    Write-Message -Level Verbose -Message "Dropping Operator category $categoryName"
                                    $destServer.JobServer.OperatorCategories[$categoryName].Drop()
                                    Write-Message -Level Verbose -Message "Copying Operator category $categoryName"
                                    $sql = $operatorCategory.Script() | Out-String
                                    Write-Message -Level Debug -Message $sql
                                    $destServer.Query($sql)
                                }
                                catch {
                                    $copyOperatorCategoryStatus.Status = "Failed"
                                    $copyOperatorCategoryStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
                                    Stop-Function -Message "Issue dropping operator category" -Target $categoryName -ErrorRecord $_
                                }
                            }
                        }
                    }
                    else {
                        if ($Pscmdlet.ShouldProcess($destinstance, "Creating Operator category $categoryName")) {
                            try {
                                Write-Message -Level Verbose -Message "Copying Operator category $categoryName"
                                $sql = $operatorCategory.Script() | Out-String
                                Write-Message -Level Debug -Message $sql
                                $destServer.Query($sql)
                                
                                $copyOperatorCategoryStatus.Status = "Successful"
                                $copyOperatorCategoryStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
                            }
                            catch {
                                $copyOperatorCategoryStatus.Status = "Failed"
                                $copyOperatorCategoryStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
                                Stop-Function -Message "Issue copying operator category" -Target $categoryName -ErrorRecord $_
                            }
                        }
                    }
                }
            }
        }
        
        function Copy-AlertCategory {
            <#
                .SYNOPSIS
                    Copy-AlertCategory migrates alert categories from one SQL Server to another.

                .DESCRIPTION
                    By default, all alert categories are copied. The -AlertCategories parameter is auto-populated for command-line completion and can be used to copy only specific alert categories.

                    If the associated credential for the category does not exist on the destination, it will be skipped. If the alert category already exists on the destination, it will be skipped unless -Force is used.
            #>
            [CmdletBinding(DefaultParameterSetName = "Default", SupportsShouldprocess = $true)]
            param (
                [string[]]$AlertCategories
            )
            
            process {
                if ($sourceServer.VersionMajor -lt 9 -or $destServer.VersionMajor -lt 9) {
                    throw "Server AlertCategories are only supported in SQL Server 2005 and above. Quitting."
                }
                
                $serverAlertCategories = $sourceServer.JobServer.AlertCategories | Where-Object ID -ge 100
                $destAlertCategories = $destServer.JobServer.AlertCategories | Where-Object ID -ge 100
                
                foreach ($alertCategory in $serverAlertCategories) {
                    $categoryName = $alertCategory.Name
                    
                    $copyAlertCategoryStatus = [pscustomobject]@{
                        SourceServer = $sourceServer.Name
                        DestinationServer = $destServer.Name
                        Type         = "Agent Alert Category"
                        Name         = $categoryName
                        Status       = $null
                        Notes        = $null
                        DateTime     = [Sqlcollaborative.Dbatools.Utility.DbaDateTime](Get-Date)
                    }
                    
                    if ($alertCategories.Length -gt 0 -and $alertCategories -notcontains $categoryName) {
                        continue
                    }
                    
                    if ($destAlertCategories.Name -contains $alertCategory.name) {
                        if ($force -eq $false) {
                            $copyAlertCategoryStatus.Status = "Skipped"
                            $copyAlertCategoryStatus.Notes = "Already exists"
                            $copyAlertCategoryStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
                            Write-Message -Level Verbose -Message "Alert category $categoryName exists at destination. Use -Force to drop and migrate."
                            continue
                        }
                        else {
                            if ($Pscmdlet.ShouldProcess($destinstance, "Dropping alert category $categoryName and recreating")) {
                                try {
                                    Write-Message -Level Verbose -Message "Dropping Alert category $categoryName"
                                    $destServer.JobServer.AlertCategories[$categoryName].Drop()
                                    Write-Message -Level Verbose -Message "Copying Alert category $categoryName"
                                    $sql = $alertcategory.Script() | Out-String
                                    Write-Message -Level Debug -Message "SQL Statement: $sql"
                                    $destServer.Query($sql)
                                }
                                catch {
                                    $copyAlertCategoryStatus.Status = "Failed"
                                    $copyAlertCategoryStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
                                    Stop-Function -Message "Issue dropping alert category" -Target $categoryName -ErrorRecord $_
                                }
                            }
                        }
                    }
                    else {
                        if ($Pscmdlet.ShouldProcess($destinstance, "Creating Alert category $categoryName")) {
                            try {
                                Write-Message -Level Verbose -Message "Copying Alert category $categoryName"
                                $sql = $alertCategory.Script() | Out-String
                                Write-Message -Level Debug -Message $sql
                                $destServer.Query($sql)
                                
                                $copyAlertCategoryStatus.Status = "Successful"
                                $copyAlertCategoryStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
                            }
                            catch {
                                $copyAlertCategoryStatus.Status = "Failed"
                                $copyAlertCategoryStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
                                Stop-Function -Message "Issue creating alert category" -Target $categoryName -ErrorRecord $_
                            }
                        }
                    }
                }
            }
        }
        
        try {
            Write-Message -Level Verbose -Message "Connecting to $Source"
            $sourceServer = Connect-SqlInstance -SqlInstance $Source -SqlCredential $SourceSqlCredential
        }
        catch {
            Stop-Function -Message "Failure" -Category ConnectionError -ErrorRecord $_ -Target $Source
            return
        }
    }
    process {
        if (Test-FunctionInterrupt) { return }
        foreach ($destinstance in $Destination) {
            try {
                Write-Message -Level Verbose -Message "Connecting to $destinstance"
                $destServer = Connect-SqlInstance -SqlInstance $destinstance -SqlCredential $DestinationSqlCredential
            }
            catch {
                Stop-Function -Message "Failure" -Category ConnectionError -ErrorRecord $_ -Target $destinstance -Continue
            }
            
            if ($CategoryType.count -gt 0) {
                
                switch ($CategoryType) {
                    "Job" {
                        Copy-JobCategory
                    }
                    
                    "Alert" {
                        Copy-AlertCategory
                    }
                    
                    "Operator" {
                        Copy-OperatorCategory
                    }
                }
                continue
            }
            
            if (($OperatorCategory.Count + $AlertCategory.Count + $jobCategory.Count) -gt 0) {
                
                if ($OperatorCategory.Count -gt 0) {
                    Copy-OperatorCategory -OperatorCategories $OperatorCategory
                }
                
                if ($AlertCategory.Count -gt 0) {
                    Copy-AlertCategory -AlertCategories $AlertCategory
                }
                
                if ($jobCategory.Count -gt 0) {
                    Copy-JobCategory -JobCategories $jobCategory
                }
                continue
            }
            Copy-OperatorCategory
            Copy-AlertCategory
            Copy-JobCategory
        }
    }
    end {
        Test-DbaDeprecation -DeprecatedOn "1.0.0" -EnableException:$false -Alias Copy-SqlAgentCategory
    }
}
function Copy-DbaAgentJob {
    <#
        .SYNOPSIS
            Copy-DbaAgentJob migrates jobs from one SQL Server to another.

        .DESCRIPTION
            By default, all jobs are copied. The -Job parameter is auto-populated for command-line completion and can be used to copy only specific jobs.

            If the job already exists on the destination, it will be skipped unless -Force is used.

        .PARAMETER Source
            Source SQL Server. You must have sysadmin access and server version must be SQL Server version 2000 or higher.

        .PARAMETER SourceSqlCredential
            Login to the target instance using alternative credentials. Windows and SQL Authentication supported. Accepts credential objects (Get-Credential)

        .PARAMETER Destination
            Destination SQL Server. You must have sysadmin access and the server must be SQL Server 2000 or higher.

        .PARAMETER DestinationSqlCredential
            Login to the target instance using alternative credentials. Windows and SQL Authentication supported. Accepts credential objects (Get-Credential)

        .PARAMETER Job
            The job(s) to process. This list is auto-populated from the server. If unspecified, all jobs will be processed.

        .PARAMETER ExcludeJob
            The job(s) to exclude. This list is auto-populated from the server.

        .PARAMETER DisableOnSource
            If this switch is enabled, the job will be disabled on the source server.

        .PARAMETER DisableOnDestination
            If this switch is enabled, the newly migrated job will be disabled on the destination server.

        .PARAMETER WhatIf
            If this switch is enabled, no actions are performed but informational messages will be displayed that explain what would happen if the command were to run.

        .PARAMETER Confirm
            If this switch is enabled, you will be prompted for confirmation before executing any operations that change state.

        .PARAMETER Force
            If this switch is enabled, the Job will be dropped and recreated on Destination.

        .PARAMETER EnableException
            By default, when something goes wrong we try to catch it, interpret it and give you a friendly warning message.
            This avoids overwhelming you with "sea of red" exceptions, but is inconvenient because it basically disables advanced scripting.
            Using this switch turns this "nice by default" feature off and enables you to catch exceptions with your own try/catch.

        .NOTES
            Tags: Migration, Agent, Job
            Author: Chrissy LeMaire (@cl), netnerds.net

            Website: https://dbatools.io
            Copyright: (C) Chrissy LeMaire, [email protected]
            License: MIT https://opensource.org/licenses/MIT

        .LINK
            https://dbatools.io/Copy-DbaAgentJob

        .EXAMPLE
            Copy-DbaAgentJob -Source sqlserver2014a -Destination sqlcluster

            Copies all jobs from sqlserver2014a to sqlcluster, using Windows credentials. If jobs with the same name exist on sqlcluster, they will be skipped.

        .EXAMPLE
            Copy-DbaAgentJob -Source sqlserver2014a -Destination sqlcluster -Job PSJob -SourceSqlCredential $cred -Force

            Copies a single job, the PSJob job from sqlserver2014a to sqlcluster, using SQL credentials for sqlserver2014a and Windows credentials for sqlcluster. If a job with the same name exists on sqlcluster, it will be dropped and recreated because -Force was used.

        .EXAMPLE
            Copy-DbaAgentJob -Source sqlserver2014a -Destination sqlcluster -WhatIf -Force

            Shows what would happen if the command were executed using force.
    #>
    [cmdletbinding(DefaultParameterSetName = "Default", SupportsShouldProcess = $true)]
    param (
        [parameter(Mandatory = $true)]
        [DbaInstanceParameter]$Source,
        [PSCredential]$SourceSqlCredential,
        [parameter(Mandatory = $true)]
        [DbaInstanceParameter[]]$Destination,
        [PSCredential]$DestinationSqlCredential,
        [object[]]$Job,
        [object[]]$ExcludeJob,
        [switch]$DisableOnSource,
        [switch]$DisableOnDestination,
        [switch]$Force,
        [Alias('Silent')]
        [switch]$EnableException
    )
    begin {
        try {
            Write-Message -Level Verbose -Message "Connecting to $Source"
            $sourceServer = Connect-SqlInstance -SqlInstance $Source -SqlCredential $SourceSqlCredential
        }
        catch {
            Stop-Function -Message "Failure" -Category ConnectionError -ErrorRecord $_ -Target $Source
            return
        }
        
        $serverJobs = $sourceServer.JobServer.Jobs
    }
    process {
        if (Test-FunctionInterrupt) { return }
        foreach ($destinstance in $Destination) {
            try {
                Write-Message -Level Verbose -Message "Connecting to $destinstance"
                $destServer = Connect-SqlInstance -SqlInstance $destinstance -SqlCredential $DestinationSqlCredential
            }
            catch {
                Stop-Function -Message "Failure" -Category ConnectionError -ErrorRecord $_ -Target $destinstance -Continue
            }
            $destJobs = $destServer.JobServer.Jobs
            
            foreach ($serverJob in $serverJobs) {
                $jobName = $serverJob.name
                $jobId = $serverJob.JobId
                
                $copyJobStatus = [pscustomobject]@{
                    SourceServer = $sourceServer.Name
                    DestinationServer = $destServer.Name
                    Name         = $jobName
                    Type         = "Agent Job"
                    Status       = $null
                    Notes        = $null
                    DateTime     = [DbaDateTime](Get-Date)
                }
                
                if ($Job -and $jobName -notin $Job -or $jobName -in $ExcludeJob) {
                    Write-Message -Level Verbose -Message "Job [$jobName] filtered. Skipping."
                    continue
                }
                Write-Message -Message "Working on job: $jobName" -Level Verbose
                $sql = "
                SELECT sp.[name] AS MaintenancePlanName
                FROM msdb.dbo.sysmaintplan_plans AS sp
                INNER JOIN msdb.dbo.sysmaintplan_subplans AS sps
                    ON sps.plan_id = sp.id
                WHERE job_id = '$($jobId)'"
                Write-Message -Message $sql -Level Debug
                
                $MaintenancePlanName = $sourceServer.Query($sql).MaintenancePlanName
                
                if ($MaintenancePlanName) {
                    if ($Pscmdlet.ShouldProcess($destinstance, "Job [$jobName] is associated with Maintenance Plan: $MaintenancePlanNam")) {
                        $copyJobStatus.Status = "Skipped"
                        $copyJobStatus.Notes = "Job is associated with maintenance plan"
                        $copyJobStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
                        Write-Message -Level Verbose -Message "Job [$jobName] is associated with Maintenance Plan: $MaintenancePlanName"
                    }
                    continue
                }
                
                $dbNames = ($serverJob.JobSteps | where-object {$_.SubSystem -ne 'ActiveScripting'}).DatabaseName | Where-Object { $_.Length -gt 0 }
                $missingDb = $dbNames | Where-Object { $destServer.Databases.Name -notcontains $_ }
                
                if ($missingDb.Count -gt 0 -and $dbNames.Count -gt 0) {
                    if ($Pscmdlet.ShouldProcess($destinstance, "Database(s) $missingDb doesn't exist on destination. Skipping job [$jobName].")) {
                        $missingDb = ($missingDb | Sort-Object | Get-Unique) -join ", "
                        $copyJobStatus.Status = "Skipped"
                        $copyJobStatus.Notes = "Job is dependent on database: $missingDb"
                        $copyJobStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
                        Write-Message -Level Verbose -Message "Database(s) $missingDb doesn't exist on destination. Skipping job [$jobName]."
                    }
                    continue
                }
                
                $missingLogin = $serverJob.OwnerLoginName | Where-Object { $destServer.Logins.Name -notcontains $_ }
                
                if ($missingLogin.Count -gt 0) {
                    if ($force -eq $false) {
                        if ($Pscmdlet.ShouldProcess($destinstance, "Login(s) $missingLogin doesn't exist on destination. Use -Force to set owner to [sa]. Skipping job [$jobName].")) {
                            $missingLogin = ($missingLogin | Sort-Object | Get-Unique) -join ", "
                            $copyJobStatus.Status = "Skipped"
                            $copyJobStatus.Notes = "Job is dependent on login $missingLogin"
                            $copyJobStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
                            Write-Message -Level Verbose -Message "Login(s) $missingLogin doesn't exist on destination. Use -Force to set owner to [sa]. Skipping job [$jobName]."
                        }
                        continue
                    }
                }
                
                $proxyNames = $serverJob.JobSteps.ProxyName | Where-Object { $_.Length -gt 0 }
                $missingProxy = $proxyNames | Where-Object { $destServer.JobServer.ProxyAccounts.Name -notcontains $_ }
                
                if ($missingProxy.Count -gt 0 -and $proxyNames.Count -gt 0) {
                    if ($Pscmdlet.ShouldProcess($destinstance, "Proxy Account(s) $($proxyNames[0]) doesn't exist on destination. Skipping job [$jobName].")) {
                        $missingProxy = ($missingProxy | Sort-Object | Get-Unique) -join ", "
                        $copyJobStatus.Status = "Skipped"
                        $copyJobStatus.Notes = "Job is dependent on proxy $($proxyNames[0])"
                        $copyJobStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
                        Write-Message -Level Verbose -Message "Proxy Account(s) $($proxyNames[0]) doesn't exist on destination. Skipping job [$jobName]."
                    }
                    continue
                }
                
                $operators = $serverJob.OperatorToEmail, $serverJob.OperatorToNetSend, $serverJob.OperatorToPage | Where-Object { $_.Length -gt 0 }
                $missingOperators = $operators | Where-Object { $destServer.JobServer.Operators.Name -notcontains $_ }
                
                if ($missingOperators.Count -gt 0 -and $operators.Count -gt 0) {
                    if ($Pscmdlet.ShouldProcess($destinstance, "Operator(s) $($missingOperator) doesn't exist on destination. Skipping job [$jobName]")) {
                        $missingOperator = ($operators | Sort-Object | Get-Unique) -join ", "
                        $copyJobStatus.Status = "Skipped"
                        $copyJobStatus.Notes = "Job is dependent on operator $missingOperator"
                        $copyJobStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
                        Write-Message -Level Verbose -Message "Operator(s) $($missingOperator) doesn't exist on destination. Skipping job [$jobName]"
                    }
                    continue
                }
                
                if ($destJobs.name -contains $serverJob.name) {
                    if ($force -eq $false) {
                        if ($Pscmdlet.ShouldProcess($destinstance, "Job $jobName exists at destination. Use -Force to drop and migrate.")) {
                            $copyJobStatus.Status = "Skipped"
                            $copyJobStatus.Notes = "Job already exists on destination"
                            $copyJobStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
                            Write-Message -Level Verbose -Message "Job $jobName exists at destination. Use -Force to drop and migrate."
                        }
                        continue
                    }
                    else {
                        if ($Pscmdlet.ShouldProcess($destinstance, "Dropping job $jobName and recreating")) {
                            try {
                                Write-Message -Message "Dropping Job $jobName" -Level Verbose
                                $destServer.JobServer.Jobs[$jobName].Drop()
                            }
                            catch {
                                $copyJobStatus.Status = "Failed"
                                $copyJobStatus.Notes = (Get-ErrorMessage -Record $_).Message
                                $copyJobStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
                                Stop-Function -Message "Issue dropping job" -Target $jobName -ErrorRecord $_ -Continue
                            }
                        }
                    }
                }
                
                if ($Pscmdlet.ShouldProcess($destinstance, "Creating Job $jobName")) {
                    try {
                        Write-Message -Message "Copying Job $jobName" -Level Verbose
                        $sql = $serverJob.Script() | Out-String
                        
                        if ($missingLogin.Count -gt 0 -and $force) {
                            $saLogin = Get-SqlSaLogin -SqlInstance $destServer
                            $sql = $sql -replace [Regex]::Escape("@owner_login_name=N'$missingLogin'"), [Regex]::Escape("@owner_login_name=N'$saLogin'")
                        }
                        
                        Write-Message -Message $sql -Level Debug
                        $destServer.Query($sql)
                        
                        $destServer.JobServer.Jobs.Refresh()
                        $destServer.JobServer.Jobs[$serverJob.name].IsEnabled = $sourceServer.JobServer.Jobs[$serverJob.name].IsEnabled
                        $destServer.JobServer.Jobs[$serverJob.name].Alter()
                    }
                    catch {
                        $copyJobStatus.Status = "Failed"
                        $copyJobStatus.Notes = (Get-ErrorMessage -Record $_)
                        $copyJobStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
                        Stop-Function -Message "Issue copying job" -Target $jobName -ErrorRecord $_ -Continue
                    }
                }
                
                if ($DisableOnDestination) {
                    if ($Pscmdlet.ShouldProcess($destinstance, "Disabling $jobName")) {
                        Write-Message -Message "Disabling $jobName on $destinstance" -Level Verbose
                        $destServer.JobServer.Jobs[$serverJob.name].IsEnabled = $False
                        $destServer.JobServer.Jobs[$serverJob.name].Alter()
                    }
                }
                
                if ($DisableOnSource) {
                    if ($Pscmdlet.ShouldProcess($source, "Disabling $jobName")) {
                        Write-Message -Message "Disabling $jobName on $source" -Level Verbose
                        $serverJob.IsEnabled = $false
                        $serverJob.Alter()
                    }
                }
                if ($Pscmdlet.ShouldProcess($destinstance, "Reporting status of migration for $jobname")) {
                    $copyJobStatus.Status = "Successful"
                    $copyJobStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
                }
            }
        }
    }
    end {
        Test-DbaDeprecation -DeprecatedOn "1.0.0" -EnableException:$false -Alias Copy-SqlJob
    }
}
function Copy-DbaAgentOperator {
    <#
        .SYNOPSIS
            Copy-DbaAgentOperator migrates operators from one SQL Server to another.

        .DESCRIPTION
            By default, all operators are copied. The -Operators parameter is auto-populated for command-line completion and can be used to copy only specific operators.

            If the associated credentials for the operator do not exist on the destination, it will be skipped. If the operator already exists on the destination, it will be skipped unless -Force is used.

        .PARAMETER Source
            Source SQL Server. You must have sysadmin access and server version must be SQL Server version 2000 or higher.

        .PARAMETER SourceSqlCredential
            Login to the target instance using alternative credentials. Windows and SQL Authentication supported. Accepts credential objects (Get-Credential)

        .PARAMETER Destination
            Destination SQL Server. You must have sysadmin access and the server must be SQL Server 2000 or higher.

        .PARAMETER DestinationSqlCredential
            Login to the target instance using alternative credentials. Windows and SQL Authentication supported. Accepts credential objects (Get-Credential)

        .PARAMETER Operator
            The operator(s) to process. This list is auto-populated from the server. If unspecified, all operators will be processed.

        .PARAMETER ExcludeOperator
            The operators(s) to exclude. This list is auto-populated from the server.

        .PARAMETER WhatIf
            If this switch is enabled, no actions are performed but informational messages will be displayed that explain what would happen if the command were to run.

        .PARAMETER Confirm
            If this switch is enabled, you will be prompted for confirmation before executing any operations that change state.

        .PARAMETER Force
            If this switch is enabled, the Operator will be dropped and recreated on Destination.

        .PARAMETER EnableException
            By default, when something goes wrong we try to catch it, interpret it and give you a friendly warning message.
            This avoids overwhelming you with "sea of red" exceptions, but is inconvenient because it basically disables advanced scripting.
            Using this switch turns this "nice by default" feature off and enables you to catch exceptions with your own try/catch.

        .NOTES
            Tags: Migration, Agent, Operator
            Author: Chrissy LeMaire (@cl), netnerds.net
            Requires: sysadmin access on SQL Servers

            Website: https://dbatools.io
            Copyright: (C) Chrissy LeMaire, [email protected]
            License: MIT https://opensource.org/licenses/MIT

        .LINK
            https://dbatools.io/Copy-DbaAgentOperator

        .EXAMPLE
            Copy-DbaAgentOperator -Source sqlserver2014a -Destination sqlcluster

            Copies all operators from sqlserver2014a to sqlcluster using Windows credentials. If operators with the same name exist on sqlcluster, they will be skipped.

        .EXAMPLE
            Copy-DbaAgentOperator -Source sqlserver2014a -Destination sqlcluster -Operator PSOperator -SourceSqlCredential $cred -Force

            Copies only the PSOperator operator from sqlserver2014a to sqlcluster using SQL credentials for sqlserver2014a and Windows credentials for sqlcluster. If an operator with the same name exists on sqlcluster, it will be dropped and recreated because -Force was used.

        .EXAMPLE
            Copy-DbaAgentOperator -Source sqlserver2014a -Destination sqlcluster -WhatIf -Force

            Shows what would happen if the command were executed using force.
    #>
    [CmdletBinding(DefaultParameterSetName = "Default", SupportsShouldProcess = $true)]
    param (
        [parameter(Mandatory = $true)]
        [DbaInstanceParameter]$Source,
        [PSCredential]
        $SourceSqlCredential,
        [parameter(Mandatory = $true)]
        [DbaInstanceParameter[]]$Destination,
        [PSCredential]
        $DestinationSqlCredential,
        [object[]]$Operator,
        [object[]]$ExcludeOperator,
        [switch]$Force,
        [Alias('Silent')]
        [switch]$EnableException
    )

    begin {
        try {
            Write-Message -Level Verbose -Message "Connecting to $Source"
            $sourceServer = Connect-SqlInstance -SqlInstance $Source -SqlCredential $SourceSqlCredential
        }
        catch {
            Stop-Function -Message "Failure" -Category ConnectionError -ErrorRecord $_ -Target $Source
            return
        }
        $serverOperator = $sourceServer.JobServer.Operators
    }
    process {
        if (Test-FunctionInterrupt) { return }
        foreach ($destinstance in $Destination) {
            try {
                Write-Message -Level Verbose -Message "Connecting to $destinstance"
                $destServer = Connect-SqlInstance -SqlInstance $destinstance -SqlCredential $DestinationSqlCredential
            }
            catch {
                Stop-Function -Message "Failure" -Category ConnectionError -ErrorRecord $_ -Target $destinstance -Continue
            }
            
            $destOperator = $destServer.JobServer.Operators
            $failsafe = $destServer.JobServer.AlertSystem | Select-Object FailSafeOperator
            foreach ($sOperator in $serverOperator) {
                $operatorName = $sOperator.Name
                
                $copyOperatorStatus = [pscustomobject]@{
                    SourceServer = $sourceServer.Name
                    DestinationServer = $destServer.Name
                    Name         = $operatorName
                    Type         = "Agent Operator"
                    Status       = $null
                    Notes        = $null
                    DateTime     = [DbaDateTime](Get-Date)
                }
                
                if ($Operator -and $Operator -notcontains $operatorName -or $ExcludeOperator -in $operatorName) {
                    continue
                }
                
                if ($destOperator.Name -contains $sOperator.Name) {
                    if ($force -eq $false) {
                        if ($Pscmdlet.ShouldProcess($destinstance, "Operator $operatorName exists at destination. Use -Force to drop and migrate.")) {
                            $copyOperatorStatus.Status = "Skipped"
                            $copyOperatorStatus.Notes = "Already exists"
                            $copyOperatorStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
                            Write-Message -Level Verbose -Message "Operator $operatorName exists at destination. Use -Force to drop and migrate."
                        }
                        continue
                    }
                    else {
                        if ($failsafe.FailSafeOperator -eq $operatorName) {
                            Write-Message -Level Verbose -Message "$operatorName is the failsafe operator. Skipping drop."
                            continue
                        }
                        
                        if ($Pscmdlet.ShouldProcess($destinstance, "Dropping operator $operatorName and recreating")) {
                            try {
                                Write-Message -Level Verbose -Message "Dropping Operator $operatorName"
                                $destServer.JobServer.Operators[$operatorName].Drop()
                            }
                            catch {
                                $copyOperatorStatus.Status = "Failed"
                                $copyOperatorStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
                                
                                Stop-Function -Message "Issue dropping operator" -Category InvalidOperation -ErrorRecord $_ -Target $destServer -Continue
                            }
                        }
                    }
                }
                
                if ($Pscmdlet.ShouldProcess($destinstance, "Creating Operator $operatorName")) {
                    try {
                        Write-Message -Level Verbose -Message "Copying Operator $operatorName"
                        $sql = $sOperator.Script() | Out-String
                        Write-Message -Level Debug -Message $sql
                        $destServer.Query($sql)
                        
                        $copyOperatorStatus.Status = "Successful"
                        $copyOperatorStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
                    }
                    catch {
                        $copyOperatorStatus.Status = "Failed"
                        $copyOperatorStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
                        Stop-Function -Message "Issue creating operator." -Category InvalidOperation -ErrorRecord $_ -Target $destServer
                    }
                }
            }
        }
    }
    end {
        Test-DbaDeprecation -DeprecatedOn "1.0.0" -EnableException:$false -Alias Copy-SqlOperator
    }
}
function Copy-DbaAgentProxyAccount {
    <#
        .SYNOPSIS
            Copy-DbaAgentProxyAccount migrates proxy accounts from one SQL Server to another.

        .DESCRIPTION
            By default, all proxy accounts are copied. The -ProxyAccounts parameter is auto-populated for command-line completion and can be used to copy only specific proxy accounts.

            If the associated credential for the account does not exist on the destination, it will be skipped. If the proxy account already exists on the destination, it will be skipped unless -Force is used.

        .PARAMETER Source
            Source SQL Server. You must have sysadmin access and server version must be SQL Server version 2000 or higher.

        .PARAMETER SourceSqlCredential
            Login to the target instance using alternative credentials. Windows and SQL Authentication supported. Accepts credential objects (Get-Credential)

        .PARAMETER Destination
            Destination SQL Server. You must have sysadmin access and the server must be SQL Server 2000 or higher.

        .PARAMETER DestinationSqlCredential
            Login to the target instance using alternative credentials. Windows and SQL Authentication supported. Accepts credential objects (Get-Credential)

        .PARAMETER ProxyAccount
            Only migrate specific proxy accounts
    
        .PARAMETER ExcludeProxyAccount
            Migrate all proxy accounts except the ones explicitly excluded
    
        .PARAMETER WhatIf
            If this switch is enabled, no actions are performed but informational messages will be displayed that explain what would happen if the command were to run.

        .PARAMETER Confirm
            If this switch is enabled, you will be prompted for confirmation before executing any operations that change state.

        .PARAMETER Force
            If this switch is enabled, the Operator will be dropped and recreated on Destination.

        .PARAMETER EnableException
            By default, when something goes wrong we try to catch it, interpret it and give you a friendly warning message.
            This avoids overwhelming you with "sea of red" exceptions, but is inconvenient because it basically disables advanced scripting.
            Using this switch turns this "nice by default" feature off and enables you to catch exceptions with your own try/catch.

        .NOTES
            Tags: Migration, Agent
            Author: Chrissy LeMaire (@cl), netnerds.net
            Requires: sysadmin access on SQL Servers

            Website: https://dbatools.io
            Copyright: (C) Chrissy LeMaire, [email protected]
            License: MIT https://opensource.org/licenses/MIT

        .LINK
            https://dbatools.io/Copy-DbaAgentProxyAccount

        .EXAMPLE
            Copy-DbaAgentProxyAccount -Source sqlserver2014a -Destination sqlcluster

            Copies all proxy accounts from sqlserver2014a to sqlcluster using Windows credentials. If proxy accounts with the same name exist on sqlcluster, they will be skipped.

        .EXAMPLE
            Copy-DbaAgentProxyAccount -Source sqlserver2014a -Destination sqlcluster -ProxyAccount PSProxy -SourceSqlCredential $cred -Force

            Copies only the PSProxy proxy account from sqlserver2014a to sqlcluster using SQL credentials for sqlserver2014a and Windows credentials for sqlcluster. If a proxy account with the same name exists on sqlcluster, it will be dropped and recreated because -Force was used.

        .EXAMPLE
            Copy-DbaAgentProxyAccount -Source sqlserver2014a -Destination sqlcluster -WhatIf -Force

            Shows what would happen if the command were executed using force.
    #>
    [CmdletBinding(DefaultParameterSetName = "Default", SupportsShouldProcess = $true)]
    param (
        [parameter(Mandatory = $true)]
        [DbaInstanceParameter]$Source,
        [PSCredential]$SourceSqlCredential,
        [parameter(Mandatory = $true)]
        [DbaInstanceParameter[]]$Destination,
        [PSCredential]$DestinationSqlCredential,
        [string[]]$ProxyAccount,
        [string[]]$ExcludeProxyAccount,
        [switch]$Force,
        [Alias('Silent')]
        [switch]$EnableException
    )
    begin {
        try {
            Write-Message -Level Verbose -Message "Connecting to $Source"
            $sourceServer = Connect-SqlInstance -SqlInstance $Source -SqlCredential $SourceSqlCredential -MinimumVersion 9
        }
        catch {
            Stop-Function -Message "Failure" -Category ConnectionError -ErrorRecord $_ -Target $Source
            return
        }
        $serverProxyAccounts = $sourceServer.JobServer.ProxyAccounts
        if ($ProxyAccount) {
            $serverProxyAccounts | Where-Object Name -in $ProxyAccount
        }
        if ($ExcludeProxyAccount) {
            $serverProxyAccounts | Where-Object Name -notin $ProxyAccount
        }
    }
    process {
        if (Test-FunctionInterrupt) { return }
        foreach ($destinstance in $Destination) {
            try {
                Write-Message -Level Verbose -Message "Connecting to $destinstance"
                $destServer = Connect-SqlInstance -SqlInstance $destinstance -SqlCredential $DestinationSqlCredential -MinimumVersion 9
            }
            catch {
                Stop-Function -Message "Failure" -Category ConnectionError -ErrorRecord $_ -Target $destinstance -Continue
            }
            
            $destProxyAccounts = $destServer.JobServer.ProxyAccounts
            
            foreach ($account in $serverProxyAccounts) {
                $proxyName = $account.Name
                
                $copyAgentProxyAccountStatus = [pscustomobject]@{
                    SourceServer = $sourceServer.Name
                    DestinationServer = $destServer.Name
                    Name         = $null
                    Type         = "Agent Proxy"
                    Status       = $null
                    Notes        = $null
                    DateTime     = [Sqlcollaborative.Dbatools.Utility.DbaDateTime](Get-Date)
                }
                
                # Proxy accounts rely on Credential accounts
                $credentialName = $account.CredentialName
                $copyAgentProxyAccountStatus.Name = $credentialName
                $copyAgentProxyAccountStatus.Type = "Credential"
                
                try {
                    $credentialtest = $destServer.Credentials[$CredentialName]
                }
                catch {
                    # don't care
                }
                
                if ($null -eq $credentialtest) {
                    $copyAgentProxyAccountStatus.Status = "Skipped"
                    $copyAgentProxyAccountStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
                    Write-Message -Level Verbose -Message "Associated credential account, $CredentialName, does not exist on $destinstance. Skipping migration of $proxyName."
                    continue
                }
                
                if ($destProxyAccounts.Name -contains $proxyName) {
                    $copyAgentProxyAccountStatus.Name = $proxyName
                    $copyAgentProxyAccountStatus.Type = "ProxyAccount"
                    
                    if ($force -eq $false) {
                        $copyAgentProxyAccountStatus.Status = "Skipped"
                        $copyAgentProxyAccountStatus
                        Write-Message -Level Verbose -Message "Server proxy account $proxyName exists at destination. Use -Force to drop and migrate."
                        continue
                    }
                    else {
                        if ($Pscmdlet.ShouldProcess($destinstance, "Dropping server proxy account $proxyName and recreating")) {
                            try {
                                Write-Message -Level Verbose -Message "Dropping server proxy account $proxyName"
                                $destServer.JobServer.ProxyAccounts[$proxyName].Drop()
                            }
                            catch {
                                $copyAgentProxyAccountStatus.Status = "Failed"
                                $copyAgentProxyAccountStatus.Notes = "Could not drop"
                                $copyAgentProxyAccountStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
                                Stop-Function -Message "Issue dropping proxy account" -Target $proxyName -ErrorRecord $_ -Continue
                            }
                        }
                    }
                }
                
                if ($Pscmdlet.ShouldProcess($destinstance, "Creating server proxy account $proxyName")) {
                    $copyAgentProxyAccountStatus.Name = $proxyName
                    $copyAgentProxyAccountStatus.Type = "ProxyAccount"
                    
                    try {
                        Write-Message -Level Verbose -Message "Copying server proxy account $proxyName"
                        $sql = $account.Script() | Out-String
                        Write-Message -Level Debug -Message $sql
                        $destServer.Query($sql)
                        
                        # Will fixing this misspelled status cause problems downstream?
                        $copyAgentProxyAccountStatus.Status = "Successful"
                        $copyAgentProxyAccountStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
                    }
                    catch {
                        $exceptionstring = $_.Exception.InnerException.ToString()
                        if ($exceptionstring -match 'subsystem') {
                            $copyAgentProxyAccountStatus.Status = "Skipping"
                            $copyAgentProxyAccountStatus.Notes = "Failure"
                            $copyAgentProxyAccountStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
                            
                            Write-Message -Level Verbose -Message "One or more subsystems do not exist on the destination server. Skipping that part."
                        }
                        else {
                            $copyAgentProxyAccountStatus.Status = "Failed"
                            $copyAgentProxyAccountStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
                            
                            Stop-Function -Message "Issue creating proxy account" -Target $proxyName -ErrorRecord $_
                        }
                    }
                }
            }
        }
    }
    end {
        Test-DbaDeprecation -DeprecatedOn "1.0.0" -EnableException:$false -Alias Copy-SqlProxyAccount
    }
}
function Copy-DbaAgentSharedSchedule {
    <#
        .SYNOPSIS
            Copy-DbaAgentSharedSchedule migrates shared job schedules from one SQL Server to another.

        .DESCRIPTION
            All shared job schedules are copied.

            If the associated credential for the account does not exist on the destination, it will be skipped. If the shared job schedule already exists on the destination, it will be skipped unless -Force is used.

        .PARAMETER Source
            Source SQL Server. You must have sysadmin access and server version must be SQL Server version 2000 or higher.

        .PARAMETER SourceSqlCredential
            Login to the target instance using alternative credentials. Windows and SQL Authentication supported. Accepts credential objects (Get-Credential)

        .PARAMETER Destination
            Destination SQL Server. You must have sysadmin access and the server must be SQL Server 2000 or higher.

        .PARAMETER DestinationSqlCredential
            Login to the target instance using alternative credentials. Windows and SQL Authentication supported. Accepts credential objects (Get-Credential)

        .PARAMETER WhatIf
            If this switch is enabled, no actions are performed but informational messages will be displayed that explain what would happen if the command were to run.

        .PARAMETER Confirm
            If this switch is enabled, you will be prompted for confirmation before executing any operations that change state.

        .PARAMETER Force
            If this switch is enabled, the Operator will be dropped and recreated on Destination.

        .PARAMETER EnableException
            By default, when something goes wrong we try to catch it, interpret it and give you a friendly warning message.
            This avoids overwhelming you with "sea of red" exceptions, but is inconvenient because it basically disables advanced scripting.
            Using this switch turns this "nice by default" feature off and enables you to catch exceptions with your own try/catch.

        .NOTES
            Tags: Migration, Agent
            Author: Chrissy LeMaire (@cl), netnerds.net
            Requires: sysadmin access on SQL Servers

            Website: https://dbatools.io
            Copyright: (C) Chrissy LeMaire, [email protected]
            License: MIT https://opensource.org/licenses/MIT

        .LINK
            https://dbatools.io/Copy-DbaAgentSharedSchedule

        .EXAMPLE
            Copy-DbaAgentSharedSchedule -Source sqlserver2014a -Destination sqlcluster

            Copies all shared job schedules from sqlserver2014a to sqlcluster using Windows credentials. If shared job schedules with the same name exist on sqlcluster, they will be skipped.

        .EXAMPLE
            Copy-DbaAgentSharedSchedule -Source sqlserver2014a -Destination sqlcluster -WhatIf -Force

            Shows what would happen if the command were executed using force.
    #>
    [CmdletBinding(DefaultParameterSetName = "Default", SupportsShouldProcess = $true)]
    param (
        [parameter(Mandatory = $true)]
        [DbaInstanceParameter]$Source,
        [PSCredential]
        $SourceSqlCredential,
        [parameter(Mandatory = $true)]
        [DbaInstanceParameter[]]$Destination,
        [PSCredential]
        $DestinationSqlCredential,
        [switch]$Force,
        [Alias('Silent')]
        [switch]$EnableException
    )
    begin {
        try {
            Write-Message -Level Verbose -Message "Connecting to $Source"
            $sourceServer = Connect-SqlInstance -SqlInstance $Source -SqlCredential $SourceSqlCredential -MinimumVersion 9
        }
        catch {
            Stop-Function -Message "Failure" -Category ConnectionError -ErrorRecord $_ -Target $Source
            return
        }
        $serverSchedules = $sourceServer.JobServer.SharedSchedules
    }
    process {
        if (Test-FunctionInterrupt) { return }
        foreach ($destinstance in $Destination) {
            try {
                Write-Message -Level Verbose -Message "Connecting to $destinstance"
                $destServer = Connect-SqlInstance -SqlInstance $destinstance -SqlCredential $DestinationSqlCredential -MinimumVersion 9
            }
            catch {
                Stop-Function -Message "Failure" -Category ConnectionError -ErrorRecord $_ -Target $destinstance -Continue
            }
            
            $destSchedules = $destServer.JobServer.SharedSchedules
            foreach ($schedule in $serverSchedules) {
                $scheduleName = $schedule.Name
                $copySharedScheduleStatus = [pscustomobject]@{
                    SourceServer = $sourceServer.Name
                    DestinationServer = $destServer.Name
                    Type         = "Agent Schedule"
                    Name         = $scheduleName
                    Status       = $null
                    Notes        = $null
                    DateTime     = [Sqlcollaborative.Dbatools.Utility.DbaDateTime](Get-Date)
                }
                
                if ($schedules.Length -gt 0 -and $schedules -notcontains $scheduleName) {
                    continue
                }
                
                if ($destSchedules.Name -contains $scheduleName) {
                    if ($force -eq $false) {
                        if ($Pscmdlet.ShouldProcess($destinstance, "Shared job schedule $scheduleName exists at destination. Use -Force to drop and migrate.")) {
                            $copySharedScheduleStatus.Status = "Skipped"
                            $copySharedScheduleStatus.Notes = "Already exists"
                            $copySharedScheduleStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
                            Write-Message -Level Verbose -Message "Shared job schedule $scheduleName exists at destination. Use -Force to drop and migrate."
                            continue
                        }
                    }
                    else {
                        if ($Pscmdlet.ShouldProcess($destinstance, "Schedule [$scheduleName] has associated jobs. Skipping.")) {
                            if ($destServer.JobServer.Jobs.JobSchedules.Name -contains $scheduleName) {
                                $copySharedScheduleStatus.Status = "Skipped"
                                $copySharedScheduleStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
                                Write-Message -Level Verbose -Message "Schedule [$scheduleName] has associated jobs. Skipping."
                            }
                            continue
                        }
                        else {
                            if ($Pscmdlet.ShouldProcess($destinstance, "Dropping schedule $scheduleName and recreating")) {
                                try {
                                    Write-Message -Level Verbose -Message "Dropping schedule $scheduleName"
                                    $destServer.JobServer.SharedSchedules[$scheduleName].Drop()
                                }
                                catch {
                                    $copySharedScheduleStatus.Status = "Failed"
                                    $copySharedScheduleStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
                                    Stop-Function -Message "Issue dropping schedule" -Target $scheduleName -ErrorRecord $_ -Continue
                                }
                            }
                        }
                    }
                }
                
                if ($Pscmdlet.ShouldProcess($destinstance, "Creating schedule $scheduleName")) {
                    try {
                        Write-Message -Level Verbose -Message "Copying schedule $scheduleName"
                        $sql = $schedule.Script() | Out-String
                        
                        Write-Message -Level Debug -Message $sql
                        $destServer.Query($sql)
                        
                        $copySharedScheduleStatus.Status = "Successful"
                        $copySharedScheduleStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
                    }
                    catch {
                        $copySharedScheduleStatus.Status = "Failed"
                        $copySharedScheduleStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
                        Stop-Function -Message "Issue creating schedule" -Target $scheduleName -ErrorRecord $_ -Continue
                    }
                }
            }
        }
    }
    end {
        Test-DbaDeprecation -DeprecatedOn "1.0.0" -EnableException:$false -Alias Copy-SqlSharedSchedule
    }
}
function Copy-DbaBackupDevice {
    <#
        .SYNOPSIS
            Copies backup devices one by one. Copies both SQL code and the backup file itself.

        .DESCRIPTION
            Backups are migrated using Admin shares. If the destination directory does not exist, SQL Server's default backup directory will be used.

            If a backup device with same name exists on destination, it will not be dropped and recreated unless -Force is used.

        .PARAMETER Source
            Source SQL Server. You must have sysadmin access and server version must be SQL Server version 2000 or higher.

        .PARAMETER SourceSqlCredential
            Login to the target instance using alternative credentials. Windows and SQL Authentication supported. Accepts credential objects (Get-Credential)

        .PARAMETER Destination
            Destination SQL Server. You must have sysadmin access and the server must be SQL Server 2000 or higher.

        .PARAMETER DestinationSqlCredential
            Login to the target instance using alternative credentials. Windows and SQL Authentication supported. Accepts credential objects (Get-Credential)

        .PARAMETER BackupDevice
            BackupDevice to be copied. Auto-populated list of devices. If not provided all BackupDevice(s) will be copied.

        .PARAMETER Force
            If this switch is enabled, backup device(s) will be dropped and recreated if they already exists on destination.

        .PARAMETER WhatIf
            If this switch is enabled, no actions are performed but informational messages will be displayed that explain what would happen if the command were to run.

        .PARAMETER Confirm
            If this switch is enabled, you will be prompted for confirmation before executing any operations that change state.

        .PARAMETER EnableException
            By default, when something goes wrong we try to catch it, interpret it and give you a friendly warning message.
            This avoids overwhelming you with "sea of red" exceptions, but is inconvenient because it basically disables advanced scripting.
            Using this switch turns this "nice by default" feature off and enables you to catch exceptions with your own try/catch.

        .NOTES
            Tags: Migration, Backup
            Author: Chrissy LeMaire (@cl), netnerds.net
            Requires: sysadmin access on SQL Servers

            Website: https://dbatools.io
            Copyright: (C) Chrissy LeMaire, [email protected]
            License: MIT https://opensource.org/licenses/MIT

        .LINK
            https://dbatools.io/Copy-DbaBackupDevice

        .EXAMPLE
            Copy-DbaBackupDevice -Source sqlserver2014a -Destination sqlcluster

            Copies all server backup devices from sqlserver2014a to sqlcluster using Windows credentials. If backup devices with the same name exist on sqlcluster, they will be skipped.

        .EXAMPLE
            Copy-DbaBackupDevice -Source sqlserver2014a -Destination sqlcluster -BackupDevice backup01 -SourceSqlCredential $cred -Force

            Copies only the backup device named backup01 from sqlserver2014a to sqlcluster using SQL credentials for sqlserver2014a    and Windows credentials for sqlcluster. If a backup device with the same name exists on sqlcluster, it will be dropped and recreated because -Force was used.

        .EXAMPLE
            Copy-DbaBackupDevice -Source sqlserver2014a -Destination sqlcluster -WhatIf -Force

            Shows what would happen if the command were executed using force.
    #>
    [CmdletBinding(DefaultParameterSetName = "Default", SupportsShouldProcess = $true)]
    param (
        [parameter(Mandatory = $true)]
        [DbaInstanceParameter]$Source,
        [PSCredential]$SourceSqlCredential,
        [parameter(Mandatory = $true)]
        [DbaInstanceParameter[]]$Destination,
        [PSCredential]$DestinationSqlCredential,
        [object[]]$BackupDevice,
        [switch]$Force,
        [Alias('Silent')]
        [switch]$EnableException
    )
    begin {
        try {
            Write-Message -Level Verbose -Message "Connecting to $Source"
            $sourceServer = Connect-SqlInstance -SqlInstance $Source -SqlCredential $SourceSqlCredential
        }
        catch {
            Stop-Function -Message "Failure" -Category ConnectionError -ErrorRecord $_ -Target $Source
            return
        }
        $serverBackupDevices = $sourceServer.BackupDevices
        $sourceNetBios = $Source.ComputerName
    }
    process {
        if (Test-FunctionInterrupt) { return }
        foreach ($destinstance in $Destination) {
            try {
                Write-Message -Level Verbose -Message "Connecting to $destinstance"
                $destServer = Connect-SqlInstance -SqlInstance $destinstance -SqlCredential $DestinationSqlCredential
            }
            catch {
                Stop-Function -Message "Failure" -Category ConnectionError -ErrorRecord $_ -Target $destinstance -Continue
            }
            $destBackupDevices = $destServer.BackupDevices
            $destNetBios = $destinstance.ComputerName
            
            foreach ($currentBackupDevice in $serverBackupDevices) {
                $deviceName = $currentBackupDevice.Name
                
                $copyBackupDeviceStatus = [pscustomobject]@{
                    SourceServer = $sourceServer.Name
                    DestinationServer = $destServer.Name
                    Name         = $deviceName
                    Type         = "Backup Device"
                    Status       = $null
                    Notes        = $null
                    DateTime     = [Sqlcollaborative.Dbatools.Utility.DbaDateTime](Get-Date)
                }
                
                if ($BackupDevice -and $BackupDevice -notcontains $deviceName) {
                    continue
                }
                
                if ($destBackupDevices.Name -contains $deviceName) {
                    if ($force -eq $false) {
                        $copyBackupDeviceStatus.Status = "Skipped"
                        $copyBackupDeviceStatus.Notes = "Already exists"
                        $copyBackupDeviceStatus
                        
                        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-DbaSqlPath -SqlInstance $destinstance -Path $path) -eq $false) {
                    $backupDirectory = $destServer.BackupDirectory
                    $destPath = Join-AdminUnc $destNetBios $backupDirectory
                    
                    if ($Pscmdlet.ShouldProcess($destinstance, "Updating create code to use new path")) {
                        Write-Message -Level Verbose -Message "$path doesn't exist on $destinstance"
                        Write-Message -Level Verbose -Message "Using default backup directory $backupDirectory"
                        
                        try {
                            Write-Message -Level Verbose -Message "Updating $deviceName to use $backupDirectory"
                            $sql = $sql -replace [Regex]::Escape($path), $backupDirectory
                        }
                        catch {
                            $copyBackupDeviceStatus.Status = "Failed"
                            $copyBackupDeviceStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
                            
                            Stop-Function -Message "Issue updating script of backup device with new path" -Target $deviceName -ErrorRecord $_ -Continue
                        }
                    }
                }
                
                if ($Pscmdlet.ShouldProcess($destinstance, "Copying $sourcepath to $destPath using BITSTransfer")) {
                    try {
                        Start-BitsTransfer -Source $sourcepath -Destination $destPath -ErrorAction Stop
                        Write-Message -Level Verbose -Message "Backup device $deviceName successfully copied"
                    }
                    catch {
                        $copyBackupDeviceStatus.Status = "Failed"
                        $copyBackupDeviceStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
                        
                        Stop-Function -Message "Issue copying backup device to destination" -Target $deviceName -ErrorRecord $_ -Continue
                    }
                }
                
                if ($Pscmdlet.ShouldProcess($destinstance, "Adding backup device $deviceName")) {
                    Write-Message -Level Verbose -Message "Adding backup device $deviceName on $destinstance"
                    try {
                        $destServer.Query($sql)
                        $destServer.BackupDevices.Refresh()
                        
                        $copyBackupDeviceStatus.Status = "Successful"
                        $copyBackupDeviceStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
                    }
                    catch {
                        $copyBackupDeviceStatus.Status = "Failed"
                        $copyBackupDeviceStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
                        
                        Stop-Function -Message "Issue adding backup device" -Target $deviceName -ErrorRecord $_ -Continue
                    }
                }
            }
        }
    }
    end {
        Test-DbaDeprecation -DeprecatedOn "1.0.0" -EnableException:$false -Alias Copy-SqlBackupDevice
    }
}
function Copy-DbaCentralManagementServer {
    <#
        .SYNOPSIS
            Migrates SQL Server Central Management groups and server instances from one SQL Server to another.

        .DESCRIPTION
            Copy-DbaCentralManagementServer copies all groups, subgroups, and server instances from one SQL Server to another.

        .PARAMETER Source
            Source SQL Server. You must have sysadmin access and server version must be SQL Server version 2000 or higher.

        .PARAMETER SourceSqlCredential
            Login to the target instance using alternative credentials. Windows and SQL Authentication supported. Accepts credential objects (Get-Credential)

        .PARAMETER Destination
            Destination SQL Server. You must have sysadmin access and the server must be SQL Server 2000 or higher.

        .PARAMETER DestinationSqlCredential
            Login to the target instance using alternative credentials. Windows and SQL Authentication supported. Accepts credential objects (Get-Credential)

        .PARAMETER CMSGroup
            This is an auto-populated array that contains your Central Management Server top-level groups on Source. You can specify one, many or none.

            If CMSGroup is not specified, all groups in your Central Management Server will be copied.

        .PARAMETER SwitchServerName
            If this switch is enabled, all instance names will be changed from Source to Destination.

            Central Management Server does not allow you to add a shared registered server with the same name as the Configuration Server.

        .PARAMETER Force
            If this switch is enabled, group(s) will be dropped and recreated if they already exists on destination.

        .PARAMETER WhatIf
            If this switch is enabled, no actions are performed but informational messages will be displayed that explain what would happen if the command were to run.

        .PARAMETER Confirm
            If this switch is enabled, you will be prompted for confirmation before executing any operations that change state.

        .PARAMETER EnableException
            By default, when something goes wrong we try to catch it, interpret it and give you a friendly warning message.
            This avoids overwhelming you with "sea of red" exceptions, but is inconvenient because it basically disables advanced scripting.
            Using this switch turns this "nice by default" feature off and enables you to catch exceptions with your own try/catch.

        .NOTES
            Tags: Migration
            Author: Chrissy LeMaire (@cl), netnerds.net
            Requires: sysadmin access on SQL Servers

            Website: https://dbatools.io
            Copyright: (C) Chrissy LeMaire, [email protected]
            License: MIT https://opensource.org/licenses/MIT

        .LINK
            https://dbatools.io/Copy-DbaCentralManagementServer

        .EXAMPLE
            Copy-DbaCentralManagementServer -Source sqlserver2014a -Destination sqlcluster

            All groups, subgroups, and server instances are copied from sqlserver's Central Management Server to sqlcluster's Central Management Server.

        .EXAMPLE
            Copy-DbaCentralManagementServer -Source sqlserver2014a -Destination sqlcluster -ServerGroup Group1,Group3

            Top-level groups Group1 and Group3 along with their subgroups and server instances are copied from sqlserver to sqlcluster.

        .EXAMPLE
            Copy-DbaCentralManagementServer -Source sqlserver2014a -Destination sqlcluster -ServerGroup Group1,Group3 -SwitchServerName -SourceSqlCredential $SourceSqlCredential -DestinationSqlCredential $DestinationSqlCredential

            Top-level groups Group1 and Group3 along with their subgroups and server instances are copied from sqlserver to sqlcluster. When adding sql instances to sqlcluster, if the server name of the migrating instance is "sqlcluster", it will be switched to "sqlserver".

            If SwitchServerName is not specified, "sqlcluster" will be skipped.
    #>
    [CmdletBinding(DefaultParameterSetName = "Default", SupportsShouldProcess = $true)]
    Param (
        [parameter(Mandatory = $true)]
        [DbaInstanceParameter]$Source,
        [PSCredential]$SourceSqlCredential,
        [parameter(Mandatory = $true)]
        [DbaInstanceParameter[]]$Destination,
        [PSCredential]$DestinationSqlCredential,
        [object[]]$CMSGroup,
        [switch]$SwitchServerName,
        [switch]$Force,
        [Alias('Silent')]
        [switch]$EnableException
    )
    begin {
        function Invoke-ParseServerGroup {
            [cmdletbinding()]
            param (
                $sourceGroup,
                $destinationGroup,
                $SwitchServerName
            )
            if ($destinationGroup.Name -eq "DatabaseEngineServerGroup" -and $sourceGroup.Name -ne "DatabaseEngineServerGroup") {
                $currentServerGroup = $destinationGroup
                $groupName = $sourceGroup.Name
                $destinationGroup = $destinationGroup.ServerGroups[$groupName]
                
                $copyDestinationGroupStatus = [pscustomobject]@{
                    SourceServer = $sourceServer.Name
                    DestinationServer = $destServer.Name
                    Name         = $groupName
                    Type         = "CMS Destination Group"
                    Status       = $null
                    Notes        = $null
                    DateTime     = [Sqlcollaborative.Dbatools.Utility.DbaDateTime](Get-Date)
                }
                
                if ($null -ne $destinationGroup) {
                    
                    if ($force -eq $false) {
                        if ($Pscmdlet.ShouldProcess($destinstance, "Checking to see if $groupName exists")) {
                            $copyDestinationGroupStatus.Status = "Skipped"
                            $copyDestinationGroupStatus.Notes = "Already exists"
                            $copyDestinationGroupStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
                            Write-Message -Level Verbose -Message "Destination group $groupName exists at destination. Use -Force to drop and migrate."
                        }
                        continue
                    }
                    if ($Pscmdlet.ShouldProcess($destinstance, "Dropping group $groupName")) {
                        try {
                            Write-Message -Level Verbose -Message "Dropping group $groupName"
                            $destinationGroup.Drop()
                        }
                        catch {
                            $copyDestinationGroupStatus.Status = "Failed"
                            $copyDestinationGroupStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
                            
                            Stop-Function -Message "Issue dropping group" -Target $groupName -ErrorRecord $_ -Continue
                        }
                    }
                }
                
                if ($Pscmdlet.ShouldProcess($destinstance, "Creating group $groupName")) {
                    Write-Message -Level Verbose -Message "Creating group $($sourceGroup.Name)"
                    $destinationGroup = New-Object Microsoft.SqlServer.Management.RegisteredServers.ServerGroup($currentServerGroup, $sourceGroup.Name)
                    $destinationGroup.Create()
                    
                    $copyDestinationGroupStatus.Status = "Successful"
                    $copyDestinationGroupStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
                }
            }
            
            # Add Servers
            foreach ($instance in $sourceGroup.RegisteredServers) {
                $instanceName = $instance.Name
                $serverName = $instance.ServerName
                
                $copyInstanceStatus = [pscustomobject]@{
                    SourceServer = $sourceServer.Name
                    DestinationServer = $destServer.Name
                    Name         = $instanceName
                    Type         = "CMS Instance"
                    Status       = $null
                    Notes        = $null
                    DateTime     = [Sqlcollaborative.Dbatools.Utility.DbaDateTime](Get-Date)
                }
                
                if ($serverName.ToLower() -eq $toCmStore.DomainInstanceName.ToLower()) {
                    if ($Pscmdlet.ShouldProcess($destinstance, "Checking to see if server is the CMS equals current server name")) {
                        if ($SwitchServerName) {
                            $serverName = $fromCmStore.DomainInstanceName
                            $instanceName = $fromCmStore.DomainInstanceName
                            Write-Message -Level Verbose -Message "SwitchServerName was used and new CMS equals current server name. $($toCmStore.DomainInstanceName.ToLower()) changed to $serverName."
                        }
                        else {
                            $copyInstanceStatus.Status = "Skipped"
                            $copyInstanceStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
                            
                            Write-Message -Level Verbose -Message "$serverName is Central Management Server. Add prohibited. Skipping."
                            continue
                        }
                    }
                }
                
                if ($destinationGroup.RegisteredServers.Name -contains $instanceName) {
                    
                    if ($force -eq $false) {
                        if ($Pscmdlet.ShouldProcess($destinstance, "Checking to see if $instanceName in $groupName exists")) {
                            $copyInstanceStatus.Status = "Skipped"
                            $copyInstanceStatus.Notes = "Already exists"
                            $copyInstanceStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
                            
                            Write-Message -Level Verbose -Message "Instance $instanceName exists in group $groupName at destination. Use -Force to drop and migrate."
                        }
                        continue
                    }
                    
                    if ($Pscmdlet.ShouldProcess($destinstance, "Dropping instance $instanceName from $groupName and recreating")) {
                        try {
                            Write-Message -Level Verbose -Message "Dropping instance $instance from $groupName"
                            $destinationGroup.RegisteredServers[$instanceName].Drop()
                        }
                        catch {
                            $copyInstanceStatus.Status = "Failed"
                            $copyInstanceStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
                            
                            Stop-Function -Message "Issue dropping instance from group" -Target $instanceName -ErrorRecord $_ -Continue
                        }
                    }
                }
                
                if ($Pscmdlet.ShouldProcess($destinstance, "Copying $instanceName")) {
                    $newServer = New-Object Microsoft.SqlServer.Management.RegisteredServers.RegisteredServer($destinationGroup, $instanceName)
                    $newServer.ServerName = $serverName
                    $newServer.Description = $instance.Description
                    
                    if ($serverName -ne $fromCmStore.DomainInstanceName) {
                        $newServer.SecureConnectionString = $instance.SecureConnectionString.ToString()
                        $newServer.ConnectionString = $instance.ConnectionString.ToString()
                    }
                    
                    try {
                        $newServer.Create()
                        
                        $copyInstanceStatus.Status = "Successful"
                        $copyInstanceStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
                    }
                    catch {
                        $copyInstanceStatus.Status = "Failed"
                        $copyInstanceStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
                        if ($_.Exception -match "same name") {
                            Stop-Function -Message "Could not add Switched Server instance name." -Target $instanceName -ErrorRecord $_ -Continue
                        }
                        else {
                            Stop-Function -Message "Failed to add $serverName" -Target $instanceName -ErrorRecord $_ -Continue
                        }
                    }
                    Write-Message -Level Verbose -Message "Added Server $serverName as $instanceName to $($destinationGroup.Name)"
                }
            }
            
            # Add Groups
            foreach ($fromSubGroup in $sourceGroup.ServerGroups) {
                $fromSubGroupName = $fromSubGroup.Name
                $toSubGroup = $destinationGroup.ServerGroups[$fromSubGroupName]
                
                $copyGroupStatus = [pscustomobject]@{
                    SourceServer = $sourceServer.Name
                    DestinationServer = $destServer.Name
                    Name         = $fromSubGroupName
                    Type         = "CMS Group"
                    Status       = $null
                    Notes        = $null
                    DateTime     = [Sqlcollaborative.Dbatools.Utility.DbaDateTime](Get-Date)
                }
                
                if ($null -ne $toSubGroup) {
                    if ($force -eq $false) {
                        if ($Pscmdlet.ShouldProcess($destinstance, "Checking to see if subgroup $fromSubGroupName exists")) {
                            $copyGroupStatus.Status = "Skipped"
                            $copyGroupStatus.Notes = "Already exists"
                            $copyGroupStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
                            
                            Write-Message -Level Verbose -Message "Subgroup $fromSubGroupName exists at destination. Use -Force to drop and migrate."
                        }
                        continue
                    }
                    
                    if ($Pscmdlet.ShouldProcess($destinstance, "Dropping subgroup $fromSubGroupName recreating")) {
                        try {
                            Write-Message -Level Verbose -Message "Dropping subgroup $fromSubGroupName"
                            $toSubGroup.Drop()
                        }
                        catch {
                            $copyGroupStatus.Status = "Failed"
                            $copyGroupStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
                            
                            Stop-Function -Message "Issue dropping subgroup" -Target $toSubGroup -ErrorRecord $_ -Continue
                        }
                    }
                }
                
                if ($Pscmdlet.ShouldProcess($destinstance, "Creating group $($fromSubGroup.Name)")) {
                    Write-Message -Level Verbose -Message "Creating group $($fromSubGroup.Name)"
                    $toSubGroup = New-Object Microsoft.SqlServer.Management.RegisteredServers.ServerGroup($destinationGroup, $fromSubGroup.Name)
                    $toSubGroup.create()
                    
                    $copyGroupStatus.Status = "Successful"
                    $copyGroupStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
                }
                
                Invoke-ParseServerGroup -sourceGroup $fromSubGroup -destinationgroup $toSubGroup -SwitchServerName $SwitchServerName
            }
        }
        
        try {
            Write-Message -Level Verbose -Message "Connecting to $Source"
            $sourceServer = Connect-SqlInstance -SqlInstance $Source -SqlCredential $SourceSqlCredential -MinimumVersion 10
            $fromCmStore = Get-DbaRegisteredServerStore -SqlInstance $sourceServer
        }
        catch {
            Stop-Function -Message "Failure" -Category ConnectionError -ErrorRecord $_ -Target $Source
            return
        }
    }
    
    process {
        if (Test-FunctionInterrupt) { return }
        foreach ($destinstance in $Destination) {
            try {
                Write-Message -Level Verbose -Message "Connecting to $destinstance"
                $destServer = Connect-SqlInstance -SqlInstance $destinstance -SqlCredential $DestinationSqlCredential -MinimumVersion 10
            }
            catch {
                Stop-Function -Message "Failure" -Category ConnectionError -ErrorRecord $_ -Target $destinstance -Continue
            }
            $toCmStore = Get-DbaRegisteredServerStore -SqlInstance $destServer
            
            $stores = $fromCmStore.DatabaseEngineServerGroup
            if ($CMSGroup) {
                $stores = @();
                foreach ($groupName in $CMSGroup) {
                    $stores += $fromCmStore.DatabaseEngineServerGroup.ServerGroups[$groupName]
                }
            }
            
            foreach ($store in $stores) {
                Invoke-ParseServerGroup -sourceGroup $store -destinationgroup $toCmStore.DatabaseEngineServerGroup -SwitchServerName $SwitchServerName
            }
        }
    }
    end {
        Test-DbaDeprecation -DeprecatedOn "1.0.0" -EnableException:$false -Alias Copy-SqlCentralManagementServer
    }
}
function Copy-DbaCredential {
    <#
        .SYNOPSIS
            Copy-DbaCredential migrates SQL Server Credentials from one SQL Server to another while maintaining Credential passwords.

        .DESCRIPTION
            By using password decryption techniques provided by Antti Rantasaari (NetSPI, 2014), this script migrates SQL Server Credentials from one server to another while maintaining username and password.

            Credit: https://blog.netspi.com/decrypting-mssql-database-link-server-passwords/
            License: BSD 3-Clause http://opensource.org/licenses/BSD-3-Clause

        .PARAMETER Source
            Source SQL Server. You must have sysadmin access and server version must be SQL Server version 2000 or higher.

        .PARAMETER SourceSqlCredential
            Login to the target instance using alternative credentials. Windows and SQL Authentication supported. Accepts credential objects (Get-Credential)

        .PARAMETER Destination
            Destination SQL Server. You must have sysadmin access and the server must be SQL Server 2000 or higher.

        .PARAMETER DestinationSqlCredential
            Login to the target instance using alternative credentials. Windows and SQL Authentication supported. Accepts credential objects (Get-Credential)

        .PARAMETER Credential
             This command requires access to the Windows OS via PowerShell remoting. Use this credential to connect to Windows using alternative credentials.

        .PARAMETER Name
            Only include specific names
            Note: if spaces exist in the credential name, you will have to type "" or '' around it.

        .PARAMETER ExcludeName
            Excluded credential names
    
        .PARAMETER Identity
            Only include specific identities
            Note: if spaces exist in the credential identity, you will have to type "" or '' around it.

        .PARAMETER ExcludeIdentity
            Excluded identities

        .PARAMETER Force
            If this switch is enabled, the Credential will be dropped and recreated if it already exists on Destination.

        .PARAMETER WhatIf
            If this switch is enabled, no actions are performed but informational messages will be displayed that explain what would happen if the command were to run.

        .PARAMETER Confirm
            If this switch is enabled, you will be prompted for confirmation before executing any operations that change state.

        .PARAMETER EnableException
            By default, when something goes wrong we try to catch it, interpret it and give you a friendly warning message.
            This avoids overwhelming you with "sea of red" exceptions, but is inconvenient because it basically disables advanced scripting.
            Using this switch turns this "nice by default" feature off and enables you to catch exceptions with your own try/catch.

        .NOTES
            Tags: WSMan, Migration
            Author: Chrissy LeMaire (@cl), netnerds.net
            Requires:
                - PowerShell Version 3.0, SQL Server SMO,
                - Administrator access on Windows
                - sysadmin access on SQL Server.
                - DAC access enabled for local (default)
            Limitations: Hasn't been tested thoroughly. Works on Win8.1 and SQL Server 2012 & 2014 so far.

            Website: https://dbatools.io
            Copyright: (C) Chrissy LeMaire, [email protected]
            License: MIT https://opensource.org/licenses/MIT

        .LINK
            https://dbatools.io/Copy-DbaCredential

        .EXAMPLE
            Copy-DbaCredential -Source sqlserver2014a -Destination sqlcluster

            Copies all SQL Server Credentials on sqlserver2014a to sqlcluster. If Credentials exist on destination, they will be skipped.

        .EXAMPLE
            Copy-DbaCredential -Source sqlserver2014a -Destination sqlcluster -Name "PowerShell Proxy Account" -Force

            Copies over one SQL Server Credential (PowerShell Proxy Account) from sqlserver to sqlcluster. If the Credential already exists on the destination, it will be dropped and recreated.
    #>
    [CmdletBinding(SupportsShouldProcess)]
    param (
        [parameter(Mandatory)]
        [DbaInstanceParameter]$Source,
        [PSCredential]
        $SourceSqlCredential,
        [PSCredential]
        $Credential,
        [parameter(Mandatory)]
        [DbaInstanceParameter[]]$Destination,
        [PSCredential]$DestinationSqlCredential,
        [string[]]$Name,
        [string[]]$ExcludeName,
        [Alias('CredentialIdentity')]
        [string[]]$Identity,
        [Alias('ExcludeCredentialIdentity')]
        [string[]]$ExcludeIdentity,
        [switch]$Force,
        [Alias('Silent')]
        [switch]$EnableException
    )

    begin {
        $null = Test-ElevationRequirement -ComputerName $Source.ComputerName

        function Copy-Credential {
            <#
                .SYNOPSIS
                    Copies Credentials from one server to another using a combination of SMO's .Script() and manual password updates.

                .OUTPUT
                    System.Data.DataTable
            #>
            param (
                [string[]]$Credentials,
                [bool]$Force
            )

            Write-Message -Level Verbose -Message "Collecting Credential logins and passwords on $($sourceServer.Name)"
            $sourceCredentials = Get-DecryptedObject -SqlInstance $sourceServer -Type Credential
            $credentialList = Get-DbaCredential -SqlInstance $sourceServer -Name $Name -ExcludeName $ExcludeName -Identity $Identity -ExcludeIdentity $ExcludeIdentity
            
            Write-Message -Level Verbose -Message "Starting migration"
            foreach ($credential in $credentialList) {
                $destServer.Credentials.Refresh()
                $credentialName = $credential.Name

                $copyCredentialStatus = [pscustomobject]@{
                    SourceServer      = $sourceServer.Name
                    DestinationServer = $destServer.Name
                    Type              = "Credential"
                    Name              = $credentialName
                    Status            = $null
                    Notes             = $null
                    DateTime          = [DbaDateTime](Get-Date)
                }

                if ($null -ne $destServer.Credentials[$credentialName]) {
                    if (!$force) {
                        $copyCredentialStatus.Status = "Skipping"
                        $copyCredentialStatus.Notes = "Already exists"
                        $copyCredentialStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject

                        Write-Message -Level Verbose -Message "$credentialName exists $($destServer.Name). Skipping."
                        continue
                    }
                    else {
                        if ($Pscmdlet.ShouldProcess($destinstance.Name, "Dropping $identity")) {
                            $destServer.Credentials[$credentialName].Drop()
                            $destServer.Credentials.Refresh()
                        }
                    }
                }
                
                Write-Message -Level Verbose -Message "Attempting to migrate $credentialName"
                try {
                    $currentCred = $sourceCredentials | Where-Object { $_.Name -eq "[$credentialName]" }
                    $sqlcredentialName = $credentialName.Replace("'", "''")
                    $identity = $currentCred.Identity.Replace("'", "''")
                    $password = $currentCred.Password.Replace("'","''")
                    if ($Pscmdlet.ShouldProcess($destinstance.Name, "Copying $identity")) {
                        $destServer.Query("CREATE CREDENTIAL [$sqlcredentialName] WITH IDENTITY = N'$identity', SECRET = N'$password'")
                        $destServer.Credentials.Refresh()
                        Write-Message -Level Verbose -Message "$credentialName successfully copied"
                    }

                    $copyCredentialStatus.Status = "Successful"
                    $copyCredentialStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
                }
                catch {
                    $copyCredentialStatus.Status = "Failed"
                    $copyCredentialStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject

                    Stop-Function -Message "Error creating credential" -Target $credentialName -ErrorRecord $_
                }
            }
        }
        
        try {
            Write-Message -Level Verbose -Message "Connecting to $Source"
            $sourceServer = Connect-SqlInstance -SqlInstance $Source -SqlCredential $SourceSqlCredential -MinimumVersion 9
        }
        catch {
            Stop-Function -Message "Failure" -Category ConnectionError -ErrorRecord $_ -Target $instance
            return
        }
        
        if ($null -ne $SourceSqlCredential.Username) {
            Write-Message -Level Verbose -Message "You are using SQL credentials and this script requires Windows admin access to the $Source server. Trying anyway."
        }
        
        $sourceNetBios = Resolve-NetBiosName $sourceServer
        
        Invoke-SmoCheck -SqlInstance $sourceServer
        
        Write-Message -Level Verbose -Message "Checking if Remote Registry is enabled on $source"
        try {
            Invoke-Command2 -ComputerName $sourceNetBios -Credential $credential -ScriptBlock { Get-ItemProperty -Path "HKLM:\SOFTWARE\" }
        }
        catch {
            Stop-Function -Message "Can't connect to registry on $source" -Target $sourceNetBios -ErrorRecord $_
            return
        }
    }
    process {
        if (Test-FunctionInterrupt) { return }
        
        foreach ($destinstance in $Destination) {
            try {
                Write-Message -Level Verbose -Message "Connecting to $destinstance"
                $destServer = Connect-SqlInstance -SqlInstance $destinstance -SqlCredential $DestinationSqlCredential -MinimumVersion 9
            }
            catch {
                Stop-Function -Message "Failure" -Category ConnectionError -ErrorRecord $_ -Target $destinstance -Continue
            }
            Invoke-SmoCheck -SqlInstance $destServer
            
            Copy-Credential $credentials -force:$force
        }
    }
    end {
        Test-DbaDeprecation -DeprecatedOn "1.0.0" -EnableException:$false -Alias Copy-SqlCredential
    }
}
function Copy-DbaCustomError {
    <#
        .SYNOPSIS
            Copy-DbaCustomError migrates custom errors (user defined messages), by the custom error ID, from one SQL Server to another.

        .DESCRIPTION
            By default, all custom errors are copied. The -CustomError parameter is auto-populated for command-line completion and can be used to copy only specific custom errors.

            If the custom error already exists on the destination, it will be skipped unless -Force is used. The us_english version must be created first. If you drop the us_english version, all the other languages will be dropped for that specific ID as well.

        .PARAMETER Source
            Source SQL Server. You must have sysadmin access and server version must be SQL Server version 2000 or higher.

        .PARAMETER SourceSqlCredential
            Login to the target instance using alternative credentials. Windows and SQL Authentication supported. Accepts credential objects (Get-Credential)

        .PARAMETER Destination
            Destination SQL Server. You must have sysadmin access and the server must be SQL Server 2000 or higher.

        .PARAMETER DestinationSqlCredential
            Login to the target instance using alternative credentials. Windows and SQL Authentication supported. Accepts credential objects (Get-Credential)

        .PARAMETER CustomError
            The custom error(s) to process. This list is auto-populated from the server. If unspecified, all custom errors will be processed.

        .PARAMETER ExcludeCustomError
            The custom error(s) to exclude. This list is auto-populated from the server.

        .PARAMETER WhatIf
            If this switch is enabled, no actions are performed but informational messages will be displayed that explain what would happen if the command were to run.

        .PARAMETER Confirm
            If this switch is enabled, you will be prompted for confirmation before executing any operations that change state.

        .PARAMETER EnableException
            By default, when something goes wrong we try to catch it, interpret it and give you a friendly warning message.
            This avoids overwhelming you with "sea of red" exceptions, but is inconvenient because it basically disables advanced scripting.
            Using this switch turns this "nice by default" feature off and enables you to catch exceptions with your own try/catch.

        .PARAMETER Force
            If this switch is enabled, the custom error will be dropped and recreated if it already exists on Destination.

        .NOTES
            Tags: Migration, CustomError
            Author: Chrissy LeMaire (@cl), netnerds.net
            Requires: sysadmin access on SQL Servers

            Website: https://dbatools.io
            Copyright: (C) Chrissy LeMaire, [email protected]
            License: MIT https://opensource.org/licenses/MIT

        .LINK
            https://dbatools.io/Copy-DbaCustomError

        .EXAMPLE
            Copy-DbaCustomError -Source sqlserver2014a -Destination sqlcluster

            Copies all server custom errors from sqlserver2014a to sqlcluster using Windows credentials. If custom errors with the same name exist on sqlcluster, they will be skipped.

        .EXAMPLE
            Copy-DbaCustomError -Source sqlserver2014a -SourceSqlCredential $scred -Destination sqlcluster -DestinationSqlCredential $dcred -CustomError 60000 -Force

            Copies only the custom error with ID number 60000 from sqlserver2014a to sqlcluster using SQL credentials for sqlserver2014a and Windows credentials for sqlcluster. If a custom error with the same name exists on sqlcluster, it will be updated because -Force was used.

        .EXAMPLE
            Copy-DbaCustomError -Source sqlserver2014a -Destination sqlcluster -ExcludeCustomError 60000 -Force

            Copies all the custom errors found on sqlserver2014a except the custom error with ID number 60000 to sqlcluster. If a custom error with the same name exists on sqlcluster, it will be updated because -Force was used.

        .EXAMPLE
            Copy-DbaCustomError -Source sqlserver2014a -Destination sqlcluster -WhatIf -Force

            Shows what would happen if the command were executed using force.
    #>
    [CmdletBinding(DefaultParameterSetName = "Default", SupportsShouldProcess = $true)]
    param (
        [parameter(Mandatory = $true)]
        [DbaInstanceParameter]$Source,
        [PSCredential]
        $SourceSqlCredential,
        [parameter(Mandatory = $true)]
        [DbaInstanceParameter[]]$Destination,
        [PSCredential]
        $DestinationSqlCredential,
        [object[]]$CustomError,
        [object[]]$ExcludeCustomError,
        [switch]$Force,
        [Alias('Silent')]
        [switch]$EnableException
    )

    begin {
        try {
            Write-Message -Level Verbose -Message "Connecting to $Source"
            $sourceServer = Connect-SqlInstance -SqlInstance $Source -SqlCredential $SourceSqlCredential -MinimumVersion 9
        }
        catch {
            Stop-Function -Message "Failure" -Category ConnectionError -ErrorRecord $_ -Target $Source
            return
        }
        $orderedCustomErrors = @($sourceServer.UserDefinedMessages | Where-Object Language -eq "us_english")
        $orderedCustomErrors += $sourceServer.UserDefinedMessages | Where-Object Language -ne "us_english"
    }
    process {
        if (Test-FunctionInterrupt) { return }
        foreach ($destinstance in $Destination) {
            try {
                Write-Message -Level Verbose -Message "Connecting to $destinstance"
                $destServer = Connect-SqlInstance -SqlInstance $destinstance -SqlCredential $DestinationSqlCredential -MinimumVersion 9
            }
            catch {
                Stop-Function -Message "Failure" -Category ConnectionError -ErrorRecord $_ -Target $destinstance -Continue
            }
            # US has to go first
            $destCustomErrors = $destServer.UserDefinedMessages
            
            foreach ($currentCustomError in $orderedCustomErrors) {
                $customErrorId = $currentCustomError.ID
                $language = $currentCustomError.Language.ToString()
                
                $copyCustomErrorStatus = [pscustomobject]@{
                    SourceServer = $sourceServer.Name
                    DestinationServer = $destServer.Name
                    Type         = "Custom error"
                    Name         = $currentCustomError
                    Status       = $null
                    Notes        = $null
                    DateTime     = [DbaDateTime](Get-Date)
                }
                
                if ($CustomError -and ($customErrorId -notin $CustomError -or $customErrorId -in $ExcludeCustomError)) {
                    continue
                }
                
                if ($destCustomErrors.ID -contains $customErrorId) {
                    if ($force -eq $false) {
                        $copyCustomErrorStatus.Status = "Skipped"
                        $copyCustomErrorStatus.Notes = "Already exists"
                        $copyCustomErrorStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
                        
                        Write-Message -Level Verbose -Message "Custom error $customErrorId $language exists at destination. Use -Force to drop and migrate."
                        continue
                    }
                    else {
                        If ($Pscmdlet.ShouldProcess($destinstance, "Dropping custom error $customErrorId $language and recreating")) {
                            try {
                                Write-Message -Level Verbose -Message "Dropping custom error $customErrorId (drops all languages for custom error $customErrorId)"
                                $destServer.UserDefinedMessages[$customErrorId, $language].Drop()
                            }
                            catch {
                                $copyCustomErrorStatus.Status = "Failed"
                                $copyCustomErrorStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
                                
                                Stop-Function -Message "Issue dropping custom error" -Target $customErrorId -ErrorRecord $_ -Continue
                            }
                        }
                    }
                }
                
                if ($Pscmdlet.ShouldProcess($destinstance, "Creating custom error $customErrorId $language")) {
                    try {
                        Write-Message -Level Verbose -Message "Copying custom error $customErrorId $language"
                        $sql = $currentCustomError.Script() | Out-String
                        Write-Message -Level Debug -Message $sql
                        $destServer.Query($sql)
                        
                        $copyCustomErrorStatus.Status = "Successful"
                        $copyCustomErrorStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
                    }
                    catch {
                        $copyCustomErrorStatus.Status = "Failed"
                        $copyCustomErrorStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
                        
                        Stop-Function -Message "Issue creating custom error" -Target $customErrorId -ErrorRecord $_
                    }
                }
            }
        }
    }
    end {
        Test-DbaDeprecation -DeprecatedOn "1.0.0" -EnableException:$false -Alias Copy-SqlCustomError
    }
}
function Copy-DbaDatabase {
    <#
        .SYNOPSIS
            Migrates SQL Server databases from one SQL Server to another.

        .DESCRIPTION
            This script provides the ability to migrate databases using detach/copy/attach or backup/restore. This script works with named instances, clusters and SQL Server Express Edition.

            By default, databases will be migrated to the destination SQL Server's default data and log directories. You can override this by specifying -ReuseSourceFolderStructure. Filestreams and filegroups are also migrated. Safety is emphasized.

        .PARAMETER Source
            Source SQL Server.

        .PARAMETER SourceSqlCredential
            Login to the target instance using alternative credentials. Windows and SQL Authentication supported. Accepts credential objects (Get-Credential)

        .PARAMETER Destination
            Destination SQL Server. You may specify multiple servers.

            Note that when using -BackupRestore with multiple servers, the backup will only be performed once and backups will be deleted at the end (if you didn't specify -NoBackupCleanup).

            When using -DetachAttach with multiple servers, -Reattach must be specified.

        .PARAMETER DestinationSqlCredential
            Login to the target instance using alternative credentials. Windows and SQL Authentication supported. Accepts credential objects (Get-Credential)

        .PARAMETER Database
            Migrates only specified databases. This list is auto-populated from the server for tab completion. Multiple databases may be specified as a collection.

        .PARAMETER ExcludeDatabase
            Excludes specified databases when performing -AllDatabases migrations. This list is auto-populated from the Source for tab completion.

        .PARAMETER AllDatabases
            If this switch is enabled, all user databases will be migrated. System and support databases will not be migrated. Requires -BackupRestore or -DetachAttach.

        .PARAMETER BackupRestore
            If this switch is enabled, the copy-only backup and restore method will be used to migrate the database(s). This method requires that you specify -NetworkShare in a valid UNC format (\\server\share).

            Backups will be immediately deleted after use unless -NoBackupCleanup is specified.

        .PARAMETER NetworkShare
            Specifies the network location for the backup files. The SQL Server service accounts must have read/write permission on this path.

        .PARAMETER WithReplace
            If this switch is enabled, the restore is executed with WITH REPLACE.

        .PARAMETER NoRecovery
            If this switch is enabled, the restore is executed with WITH NORECOVERY. Ideal for staging.

        .PARAMETER NoBackupCleanup
            If this switch is enabled, backups generated by this cmdlet will not be deleted after they are restored. The default behavior is to delete these backups.

        .PARAMETER NumberFiles
            Number of files to split the backup. Default is 3.

        .PARAMETER DetachAttach
            If this switch is enabled, the detach/copy/attach method is used to perform database migrations. No files are deleted on Source. If Destination attachment fails, the Source database will be reattached. File copies are performed over administrative shares (\\server\x$\mssql) using BITS. If a database is being mirrored, the mirror will be broken prior to migration.

        .PARAMETER Reattach
            If this switch is enabled, all databases are reattached to Source after DetachAttach migration.

        .PARAMETER SetSourceReadOnly
            If this switch is enabled, all migrated databases are set to ReadOnly on Source prior to detach/attach & backup/restore.

            If -Reattach is used, databases are set to read-only after reattaching.

        .PARAMETER ReuseSourceFolderStructure
            If this switch is enabled, databases will be migrated to a data and log directory structure on Destination mirroring that used on Source. By default, the default data and log directories for Destination will be used when the databases are migrated.

            The structure on Source  will be kept exactly, so consider this if you're migrating between different versions and use part of Microsoft's default Sql structure (MSSql12.INSTANCE, etc)

            To reuse Destination folder structure, use the  -WithReplace switch.

        .PARAMETER IncludeSupportDbs
            If this switch is enabled, ReportServer, ReportServerTempDb, SSISDB, and distribution databases will be copied if they exist on Source. A log file named $SOURCE-$destinstance-$date-Sqls.csv will be written to the current directory.

            Use of this switch requires -BackupRestore or -DetachAttach as well.

        .PARAMETER InputObject
            A collection of dbobjects from the pipeline.

        .PARAMETER UseLastBackups
            Use the last full, diff and logs instead of performing backups. Note that the backups must exist in a location accessible by all destination servers, such a network share.

        .PARAMETER Continue
            If specified, will to attempt to restore transaction log backups on top of existing database(s) in Recovering or Standby states. Only usable with -UseLastBackups

        .PARAMETER NoCopyOnly
             If this switch is enabled, backups will be taken without COPY_ONLY. This will break the LSN backup chain, which will interfere with the restore chain of the database.

            By default this switch is disabled, so backups will be taken with COPY_ONLY. This will preserve the LSN backup chain.

            For more details please refer to this MSDN article - https://msdn.microsoft.com/en-us/library/ms191495.aspx

        .PARAMETER SetSourceOffline
            If this switch is enabled, the Source database will be set to Offline after being copied.

        .PARAMETER WhatIf
            If this switch is enabled, no actions are performed but informational messages will be displayed that explain what would happen if the command were to run.

        .PARAMETER Confirm
            If this switch is enabled, you will be prompted for confirmation before executing any operations that change state.

        .PARAMETER EnableException
            By default, when something goes wrong we try to catch it, interpret it and give you a friendly warning message.
            This avoids overwhelming you with "sea of red" exceptions, but is inconvenient because it basically disables advanced scripting.
            Using this switch turns this "nice by default" feature off and enables you to catch exceptions with your own try/catch.

        .PARAMETER Force
            If this switch is enabled, existing databases on Destination with matching names from Source will be dropped. If using -DetachReattach, mirrors will be broken and the database(s) dropped from Availability Groups.

        .NOTES
            Tags: Migration, Backup, Restore
            Author: Chrissy LeMaire (@cl), netnerds.net

            Requires: sysadmin access on SQL Servers
            Limitations: Doesn't cover what it doesn't cover (replication, certificates, etc)

            SQL Server 2000 databases cannot be directly migrated to SQL Server 2012 and above.
            Logins within SQL Server 2012 and above logins cannot be migrated to SQL Server 2008 R2 and below.

            Website: https://dbatools.io
            Copyright: (C) Chrissy LeMaire, [email protected]
            License: MIT https://opensource.org/licenses/MIT

        .LINK
            https://dbatools.io/Copy-DbaDatabase

        .EXAMPLE
            Copy-DbaDatabase -Source sql2014a -Destination sql2014b -Database TestDB -BackupRestore -NetworkShare \\fileshare\sql\migration

            Migrates a single user database TestDB using Backup and restore from instance sql2014a to sql2014b. Backup files are stored in \\fileshare\sql\migration.

        .EXAMPLE
            Copy-DbaDatabase -Source sql2012 -Destination sql2014, sql2016 -DetachAttach -Reattach

            Databases will be migrated from sql2012 to both sql2014 and sql2016 using the detach/copy files/attach method.The following will be performed: kick all users out of the database, detach all data/log files, move files across the network over an admin share (\\SqlSERVER\M$\MSSql...), attach file on destination server, reattach at source. If the database files (*.mdf, *.ndf, *.ldf) on *destination* exist and aren't in use, they will be overwritten.

        .EXAMPLE
            Copy-DbaDatabase -Source sql2014a -Destination sqlcluster, sql2016 -BackupRestore -UseLastBackups -Force

            Migrates all user databases to sqlcluster and sql2016 using the last Full, Diff and Log backups from sql204a. If the databases exists on the destinations, they will be dropped prior to attach.

            Note that the backups must exist in a location accessible by all destination servers, such a network share.

        .EXAMPLE
            Copy-DbaDatabase -Source sql2014a -Destination sqlcluster -ExcludeDatabase Northwind, pubs -IncludeSupportDbs -Force -BackupRestore -NetworkShare \\fileshare\sql\migration

            Migrates all user databases except for Northwind and pubs by using backup/restore (copy-only). Backup files are stored in \\fileshare\sql\migration. If the database exists on the destination, it will be dropped prior to attach.

            It also includes the support databases (ReportServer, ReportServerTempDb, distribution).
    #>
    [CmdletBinding(DefaultParameterSetName = "DbBackup", SupportsShouldProcess = $true)]
    param (
        [parameter(Mandatory = $false)]
        [DbaInstanceParameter]$Source,
        [PSCredential]$SourceSqlCredential,
        [parameter(Mandatory = $true)]
        [DbaInstanceParameter[]]$Destination,
        [PSCredential]$DestinationSqlCredential,
        [Alias("Databases")]
        [object[]]$Database,
        [object[]]$ExcludeDatabase,
        [Alias("All")]
        [parameter(ParameterSetName = "DbBackup")]
        [parameter(ParameterSetName = "DbAttachDetach")]
        [switch]$AllDatabases,
        [parameter(Mandatory = $true, ParameterSetName = "DbBackup")]
        [switch]$BackupRestore,
        [parameter(ParameterSetName = "DbBackup",
                   HelpMessage = "Specify a valid network share in the format \\server\share that can be accessed by your account and the SQL Server service accounts for both Source and Destination.")]
        [string]$NetworkShare,
        [parameter(ParameterSetName = "DbBackup")]
        [switch]$WithReplace,
        [parameter(ParameterSetName = "DbBackup")]
        [switch]$NoRecovery,
        [parameter(ParameterSetName = "DbBackup")]
        [switch]$NoBackupCleanup,
        [parameter(ParameterSetName = "DbBackup")]
        [ValidateRange(1, 64)]
        [int]$NumberFiles = 3,
        [parameter(Mandatory = $true, ParameterSetName = "DbAttachDetach")]
        [switch]$DetachAttach,
        [parameter(ParameterSetName = "DbAttachDetach")]
        [switch]$Reattach,
        [parameter(ParameterSetName = "DbBackup")]
        [parameter(ParameterSetName = "DbAttachDetach")]
        [switch]$SetSourceReadOnly,
        [Alias("ReuseFolderStructure")]
        [parameter(ParameterSetName = "DbBackup")]
        [parameter(ParameterSetName = "DbAttachDetach")]
        [switch]$ReuseSourceFolderStructure,
        [parameter(ParameterSetName = "DbBackup")]
        [parameter(ParameterSetName = "DbAttachDetach")]
        [switch]$IncludeSupportDbs,
        [parameter(ParameterSetName = "DbBackup")]
        [switch]$UseLastBackups,
        [parameter(ParameterSetName = "DbBackup")]
        [switch]$Continue,
        [parameter(ValueFromPipeline)]
        [Microsoft.SqlServer.Management.Smo.Database[]]$InputObject,
        [switch]$NoCopyOnly,
        [switch]$SetSourceOffline,
        [switch]$Force,
        [Alias('Silent')]
        [switch]$EnableException
    )
    begin {
        $CopyOnly = -not $NoCopyOnly

        if ($BackupRestore -and (-not $NetworkShare -and -not $UseLastBackups)) {
            Stop-Function -Message "When using -BackupRestore, you must specify -NetworkShare or -UseLastBackups"
            return
        }
        if ($NetworkShare -and $UseLastBackups) {
            Stop-Function -Message "-NetworkShare cannot be used with -UseLastBackups because the backup path is determined by the paths in the last backups"
            return
        }
        if ($DetachAttach -and -not $Reattach -and $Destination.Count -gt 1) {
            Stop-Function -Message "When using -DetachAttach with multiple servers, you must specify -Reattach to reattach database at source"
            return
        }
        if ($Continue -and -not $UseLastBackups) {
            Stop-Function -Message "-Continue cannot be used without -UseLastBackups"
            return
        }
        function Join-AdminUnc {
            <#
        .SYNOPSIS
        Internal function. Parses a path to make it an admin UNC.
        #>
            [CmdletBinding()]
            param (
                [Parameter(Mandatory = $true)]
                [ValidateNotNullOrEmpty()]
                [string]$servername,
                [Parameter(Mandatory = $true)]
                [ValidateNotNullOrEmpty()]
                [string]$filepath

            )

            if ($script:sameserver) { return $filepath }
            if (-not $filepath) { return }
            if ($filepath.StartsWith("\\")) { return $filepath }

            $servername = $servername.Split("\")[0]

            if ($filepath.length -gt 0 -and $filepath -ne [System.DbNull]::Value) {
                $newpath = Join-Path "\\$servername\" $filepath.replace(':', '$')
                return $newpath
            }
            else { return }
        }

        function Get-SqlFileStructure {
            $dbcollection = @{ };
            $databaseProgressbar = 0

            foreach ($db in $databaseList) {
                Write-Progress -Id 1 -Activity "Processing database file structure" -PercentComplete ($databaseProgressbar / $dbCount * 100) -Status "Processing $databaseProgressbar of $dbCount."
                $dbName = $db.Name
                Write-Message -Level Verbose -Message $dbName

                $databaseProgressbar++
                $dbStatus = $db.status.toString()
                if ($dbStatus.StartsWith("Normal") -eq $false) { continue }
                $destinstancefiles = @{ }; $sourcefiles = @{ }

                $where = "Filetype <> 'LOG' and Filetype <> 'FULLTEXT'"

                $datarows = $dbFileTable.Tables.Select("dbname = '$dbName' and $where")

                # Data Files
                foreach ($file in $datarows) {
                    # Destination File Structure
                    $d = @{ }
                    if ($ReuseSourceFolderStructure) {
                        $d.physical = $file.filename
                    }
                    elseif ($WithReplace) {
                        $name = $file.Name
                        $destfile = $remoteDbFileTable.Tables[0].Select("dbname = '$dbName' and name = '$name'")
                        $d.physical = $destfile.filename

                        if ($null -eq $d.physical) {
                            $directory = Get-SqlDefaultPaths $destServer data
                            $fileName = Split-Path $file.filename -Leaf
                            $d.physical = "$directory\$fileName"
                        }
                    }
                    else {
                        $directory = Get-SqlDefaultPaths $destServer data
                        $fileName = Split-Path $file.filename -Leaf
                        $d.physical = "$directory\$fileName"
                    }
                    $d.logical = $file.Name

                    $d.remotefilename = Join-AdminUNC $destNetBios $d.physical
                    $destinstancefiles.add($file.Name, $d)

                    # Source File Structure
                    $s = @{ }
                    $s.logical = $file.Name
                    $s.physical = $file.filename
                    $s.remotefilename = Join-AdminUNC $sourceNetBios $s.physical
                    $sourcefiles.add($file.Name, $s)
                }

                # Add support for Full Text Catalogs in SQL Server 2005 and below
                if ($sourceServer.VersionMajor -lt 10) {
                    try {
                        $fttable = $null = $sourceServer.Databases[$dbName].ExecuteWithResults('sp_help_fulltext_catalogs')
                        $allrows = $fttable.Tables[0].rows
                    }
                    catch {
                        # Nothing, it's just not enabled
                    }

                    foreach ($ftc in $allrows) {
                        # Destination File Structure
                        $d = @{ }
                        $pre = "sysft_"
                        $name = $ftc.Name
                        $physical = $ftc.Path # RootPath
                        $logical = "$pre$name"
                        if ($ReuseSourceFolderStructure) {
                            $d.physical = $physical
                        }
                        else {
                            $directory = Get-SqlDefaultPaths $destServer data
                            if ($destServer.VersionMajor -lt 10) { $directory = "$directory\FTDATA" }
                            $fileName = Split-Path($physical) -leaf
                            $d.physical = "$directory\$fileName"
                        }
                        $d.logical = $logical
                        $d.remotefilename = Join-AdminUNC $destNetBios $d.physical
                        $destinstancefiles.add($logical, $d)

                        # Source File Structure
                        $s = @{ }
                        $pre = "sysft_"
                        $name = $ftc.Name
                        $physical = $ftc.Path # RootPath
                        $logical = "$pre$name"

                        $s.logical = $logical
                        $s.physical = $physical
                        $s.remotefilename = Join-AdminUNC $sourceNetBios $s.physical
                        $sourcefiles.add($logical, $s)
                    }
                }

                $where = "Filetype = 'LOG'"
                $datarows = $dbFileTable.Tables[0].Select("dbname = '$dbName' and $where")

                # Log Files
                foreach ($file in $datarows) {
                    $d = @{ }
                    if ($ReuseSourceFolderStructure) {
                        $d.physical = $file.filename
                    }
                    elseif ($WithReplace) {
                        $name = $file.Name
                        $destfile = $remoteDbFileTable.Tables[0].Select("dbname = '$dbName' and name = '$name'")
                        $d.physical = $destfile.filename

                        if ($null -eq $d.physical) {
                            $directory = Get-SqlDefaultPaths $destServer data
                            $fileName = Split-Path $file.filename -Leaf
                            $d.physical = "$directory\$fileName"
                        }
                    }
                    else {
                        $directory = Get-SqlDefaultPaths $destServer log
                        $fileName = Split-Path $file.filename -Leaf
                        $d.physical = "$directory\$fileName"
                    }
                    $d.logical = $file.Name
                    $d.remotefilename = Join-AdminUNC $destNetBios $d.physical
                    $destinstancefiles.add($file.Name, $d)

                    $s = @{ }
                    $s.logical = $file.Name
                    $s.physical = $file.filename
                    $s.remotefilename = Join-AdminUNC $sourceNetBios $s.physical
                    $sourcefiles.add($file.Name, $s)
                }

                $location = @{ }
                $location.add("Destination", $destinstancefiles)
                $location.add("Source", $sourcefiles)
                $dbcollection.Add($($db.Name), $location)
            }

            $fileStructure = [pscustomobject]@{ "databases" = $dbcollection }
            Write-Progress -id 1 -Activity "Processing database file structure" -Status "Completed" -Completed
            return $fileStructure
        }

        function Dismount-SqlDatabase {
            [CmdletBinding()]
            param (
                [object]$server,
                [string]$dbName
            )

            $currentdb = $server.databases[$dbName]
            if ($currentdb.IsMirroringEnabled) {
                try {
                    Write-Message -Level Verbose -Message "Breaking mirror for $dbName"
                    $currentdb.ChangeMirroringState([Microsoft.SqlServer.Management.Smo.MirroringOption]::Off)
                    $currentdb.Alter()
                    $currentdb.Refresh()
                    Write-Message -Level Verbose -Message "Could not break mirror for $dbName. Skipping."
                }
                catch {
                    Stop-Function -Message "Issue breaking mirror." -Target $dbName -ErrorRecord $_
                    return $false
                }
            }

            if ($currentdb.AvailabilityGroupName.Length -gt 0) {
                $agName = $currentdb.AvailabilityGroupName
                Write-Message -Level Verbose -Message "Attempting remove from Availability Group $agName."
                try {
                    $server.AvailabilityGroups[$currentdb.AvailabilityGroupName].AvailabilityDatabases[$dbName].Drop()
                    Write-Message -Level Verbose -Message "Successfully removed $dbName from  detach from $agName on $($server.Name)."
                }
                catch {
                    Stop-Function -Message "Could not remove $dbName from $agName on $($server.Name)." -Target $dbName -ErrorRecord $_
                    return $false
                }
            }

            Write-Message -Level Verbose -Message "Attempting detach from $dbName from $source."

            ####### Using Sql to detach does not modify the $currentdb collection #######

            $server.KillAllProcesses($dbName)

            try {
                $sql = "ALTER DATABASE [$dbName] SET SINGLE_USER WITH ROLLBACK IMMEDIATE"
                Write-Message -Level Verbose -Message $sql
                $null = $server.Query($sql)
                Write-Message -Level Verbose -Message "Successfully set $dbName to single-user from $source."
            }
            catch {
                Stop-Function -Message "Issue setting database to single-user." -Target $dbName -ErrorRecord $_
            }

            try {
                $sql = "EXEC master.dbo.sp_detach_db N'$dbName'"
                Write-Message -Level Verbose -Message $sql
                $null = $server.Query($sql)
                Write-Message -Level Verbose -Message "Successfully detached $dbName from $source."
                return $true
            }
            catch {
                Stop-Function -Message "Issue detaching database." -Target $dbName -ErrorRecord $_
                return $false
            }
        }

        function Mount-SqlDatabase {
            [CmdletBinding()]
            param (
                [object]$server,
                [string]$dbName,
                [object]$fileStructure,
                [string]$dbOwner
            )

            if ($null -eq $server.Logins.Item($dbOwner)) {
                try {
                    $dbOwner = ($destServer.logins | Where-Object { $_.id -eq 1 }).Name
                }
                catch {
                    $dbOwner = "sa"
                }
            }
            try {
                $null = $server.AttachDatabase($dbName, $fileStructure, $dbOwner, [Microsoft.SqlServer.Management.Smo.AttachOptions]::None)
                return $true
            }
            catch {
                Stop-Function -Message "Issue mounting database." -ErrorRecord $_
                return $false
            }
        }

        function Start-SqlFileTransfer {
            <#

            SYNOPSIS
            Internal function. Uses BITS to transfer detached files (.mdf, .ndf, .ldf, and filegroups) to
            another server over admin UNC paths. Locations of data files are kept in the
            custom object generated by Get-SqlFileStructure

            #>
            param (
                [object]$fileStructure,
                [string]$dbName
            )

            $copydb = $fileStructure.databases[$dbName]
            $dbsource = $copydb.source
            $dbdestination = $copydb.destination

            foreach ($file in $dbsource.keys) {
                $remotefilename = $dbdestination[$file].remotefilename
                $from = $dbsource[$file].remotefilename
                try {
                    if (Test-Path $from -pathtype container) {
                        $null = New-Item -ItemType Directory -Path $remotefilename -Force
                        Start-BitsTransfer -Source "$from\*.*" -Destination $remotefilename

                        $directories = (Get-ChildItem -recurse $from | Where-Object { $_.PsIsContainer }).FullName
                        foreach ($directory in $directories) {
                            $newdirectory = $directory.replace($from, $remotefilename)
                            $null = New-Item -ItemType Directory -Path $newdirectory -Force
                            Start-BitsTransfer -Source "$directory\*.*" -Destination $newdirectory
                        }
                    }
                    else {
                        Write-Message -Level Verbose -Message "Copying $from for $dbName."
                        Start-BitsTransfer -Source $from -Destination $remotefilename
                    }
                }
                catch {
                    try {
                        # Sometimes BITS trips out temporarily on cloned drives.
                        Start-BitsTransfer -Source $from -Destination $remotefilename
                    }
                    catch {
                        Write-Message -Level Verbose -Message "Start-BitsTransfer did not succeed. Now attempting with Copy-Item - no progress bar will be shown."
                        try {
                            Copy-Item -Path $from -Destination $remotefilename -ErrorAction Stop
                        }
                        catch {
                            Write-Message -Level Verbose -Message "Access denied. This can happen for a number of reasons including issues with cloned disks."
                            Stop-Function -Message "Alternatively, you may need to run PowerShell as Administrator, especially when running on localhost." -Target $from -ErrorRecord $_
                            return
                        }
                    }
                }
            }
            return $true
        }

        function Start-SqlDetachAttach {
            <#

            .SYNOPSIS
            Internal function. Performs checks, then executes Dismount-SqlDatabase on a database, copies its files to the new server,    then performs Mount-SqlDatabase. $sourceServer and $destServer are SMO server objects.

            $fileStructure is a custom object generated by Get-SqlFileStructure

            #>
            [CmdletBinding()]
            param (
                [object]$sourceServer,
                [object]$destServer,
                [object]$fileStructure,
                [string]$dbName
            )

            $destfilestructure = New-Object System.Collections.Specialized.StringCollection
            $sourceFileStructure = New-Object System.Collections.Specialized.StringCollection
            $dbOwner = $sourceServer.databases[$dbName].owner

            if ($null -eq $dbOwner) {
                try {
                    $dbOwner = ($destServer.logins | Where-Object { $_.id -eq 1 }).Name
                }
                catch {
                    $dbOwner = "sa"
                }
            }

            foreach ($file in $fileStructure.databases[$dbName].destination.values) { $null = $destfilestructure.add($file.physical) }
            foreach ($file in $fileStructure.databases[$dbName].source.values) { $null = $sourceFileStructure.add($file.physical) }

            $detachresult = Dismount-SqlDatabase $sourceServer $dbName

            if ($detachresult) {
                $transfer = Start-SqlFileTransfer $fileStructure $dbName
                if ($transfer -eq $false) { Write-Warning "Could not copy files."; return "Could not copy files." }
                $attachresult = Mount-SqlDatabase $destServer $dbName $destfilestructure $dbOwner

                if ($attachresult -eq $true) {
                    # add to added dbs because ATTACH was successful
                    Write-Message -Level Verbose -Message "Successfully attached $dbName to $destinstance."
                    return $true
                }
                else {
                    # add to failed because ATTACH was unsuccessful
                    Write-Message -Level Verbose -Message "Could not attach $dbName."
                    return "Could not attach database."
                }
            }
            else {
                # add to failed because DETACH was unsuccessful
                Write-Message -Level Verbose -Message "Could not detach $dbName."
                return "Could not detach database."
            }
        }
        $backupCollection = @()
    }
    process {
        if (Test-FunctionInterrupt) { return }
        
        # testing twice for whatif reasons
        if ($BackupRestore -and (-not $NetworkShare -and -not $UseLastBackups)) {
            Stop-Function -Message "When using -BackupRestore, you must specify -NetworkShare or -UseLastBackups"
            return
        }
        if ($NetworkShare -and $UseLastBackups) {
            Stop-Function -Message "-NetworkShare cannot be used with -UseLastBackups because the backup path is determined by the paths in the last backups"
            return
        }
        if ($DetachAttach -and -not $Reattach -and $Destination.Count -gt 1) {
            Stop-Function -Message "When using -DetachAttach with multiple servers, you must specify -Reattach to reattach database at source"
            return
        }
        if (($AllDatabases -or $IncludeSupportDbs -or $Database) -and !$DetachAttach -and !$BackupRestore) {
            Stop-Function -Message "You must specify -DetachAttach or -BackupRestore when migrating databases."
            return
        }

        if (-not $AllDatabases -and -not $IncludeSupportDbs -and -not $Database -and -not $InputObject) {
            Stop-Function -Message "You must specify a -AllDatabases or -Database to continue."
            return
        }

        if ($InputObject) {
            $Source = $InputObject[0].Parent
            $Database = $InputObject.Name
        }


        if ($Database -contains "master" -or $Database -contains "msdb" -or $Database -contains "tempdb") {
            Stop-Function -Message "Migrating system databases is not currently supported." -Continue
        }

        try {
            Write-Message -Level Verbose -Message "Connecting to $Source"
            $sourceServer = Connect-SqlInstance -SqlInstance $Source -SqlCredential $SourceSqlCredential
        }
        catch {
            Stop-Function -Message "Failure" -Category ConnectionError -ErrorRecord $_ -Target $Source
            return
        }

        Invoke-SmoCheck -SqlInstance $sourceServer
        $sourceNetBios = $sourceServer.ComputerName

        Write-Message -Level Verbose -Message "Ensuring user databases exist (counting databases)."
        $dbTotal = $sourceServer.Databases.Count

        if ($dbTotal -le 4) {
            Stop-Function -Message "No user databases to migrate. Quitting."
            return
        }

        foreach ($destinstance in $Destination) {
            try {
                Write-Message -Level Verbose -Message "Connecting to $destinstance"
                $destServer = Connect-SqlInstance -SqlInstance $destinstance -SqlCredential $DestinationSqlCredential
            }
            catch {
                Stop-Function -Message "Failure" -Category ConnectionError -ErrorRecord $_ -Target $destinstance -Continue
            }

            if ($sourceServer.ComputerName -eq $destServer.ComputerName) {
                $script:sameserver = $true
            }
            else {
                $script:sameserver = $false
            }

            if ($script:sameserver -and $DetachAttach) {
                if (-not (Test-ElevationRequirement -ComputerName $sourceServer)) { return }
            }

            $destVersionLower = $destServer.VersionMajor -lt $sourceServer.VersionMajor
            $destVersionMinorLow = ($destServer.VersionMajor -eq 10 -and $sourceServer.VersionMajor -eq 10) -and ($destServer.VersionMinor -lt $sourceServer.VersionMinor)

            if ($destVersionLower -or $destVersionMinorLow) {
                Stop-Function -Message "Error: copy database cannot be made from newer $($sourceServer.VersionString) to older $($destServer.VersionString) SQL Server version."
                return
            }

            if ($DetachAttach) {
                if ($sourceServer.ComputerName -eq $env:COMPUTERNAME -or $destServer.ComputerName -eq $env:COMPUTERNAME) {
                    if (-not ([Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole] "Administrator")) {
                        Write-Message -Level Verbose -Message "When running DetachAttach locally on the console, it's possible you'll need to Run As Administrator. Trying anyway."
                    }
                }
            }

            if ($NetworkShare) {
                if ($(Test-DbaSqlPath -SqlInstance $sourceServer -Path $NetworkShare) -eq $false) {
                    Write-Message -Level Verbose -Message "$Source may not be able to access $NetworkShare. Trying anyway."
                }

                if ($(Test-DbaSqlPath -SqlInstance $destServer -Path $NetworkShare) -eq $false) {
                    Write-Message -Level Verbose -Message "$destinstance may not be able to access $NetworkShare. Trying anyway."
                }

                if ($NetworkShare.StartsWith('\\')) {
                    try {
                        $shareServer = ($NetworkShare -split "\\")[2]
                        $hostEntry = ([Net.Dns]::GetHostEntry($shareServer)).HostName -split "\."

                        if ($shareServer -ne $hostEntry[0]) {
                            Write-Message -Level Verbose -Message "Using CNAME records for the network share may present an issue if an SPN has not been created. Trying anyway. If it doesn't work, use a different (A record) hostname."
                        }
                    }
                    catch {
                        Stop-Function -Message "Error validating unc path: $_"
                        return
                    }
                }
            }

            $destNetBios = $destserver.ComputerName

            Write-Message -Level Verbose -Message "Performing SMO version check."
            Invoke-SmoCheck -SqlInstance $destServer

            Write-Message -Level Verbose -Message "Checking to ensure the source isn't the same as the destination."
            if ($source -eq $destinstance) {
                Stop-Function -Message "Source and Destination SQL Servers instances are the same. Quitting." -Continue
            }

            if ($NetworkShare.Length -gt 0) {
                Write-Message -Level Verbose -Message "Checking to ensure network path is valid."
                if (!($NetworkShare.StartsWith("\\")) -and !$script:sameserver) {
                    Stop-Function -Message "Network share must be a valid UNC path (\\server\share)." -Continue
                }

                if (-not $script:sameserver) {
                    try {
                        if ((Test-Path $NetworkShare -ErrorAction Stop)) {
                            Write-Message -Level Verbose -Message "$NetworkShare share can be accessed."
                        }
                    }
                    catch {
                        Write-Message -Level Verbose -Message "$NetworkShare share cannot be accessed. Still trying anyway, in case the SQL Server service accounts have access."
                    }
                }
            }

            Write-Message -Level Verbose -Message "Checking to ensure server is not SQL Server 7 or below."
            if ($sourceServer.VersionMajor -lt 8 -and $destServer.VersionMajor -lt 8) {
                Stop-Function -Message "This script can only be run on SQL Server 2000 and above. Quitting." -Continue
            }

            Write-Message -Level Verbose -Message "Checking to ensure detach/attach is not attempted on SQL Server 2000."
            if ($destServer.VersionMajor -lt 9 -and $DetachAttach) {
                Stop-Function -Message "Detach/Attach not supported when destination SQL Server is version 2000. Quitting." -Target $destServer -Continue
            }

            Write-Message -Level Verbose -Message "Checking to ensure SQL Server 2000 migration isn't directly attempted to SQL Server 2012."
            if ($sourceServer.VersionMajor -lt 9 -and $destServer.VersionMajor -gt 10) {
                Stop-Function -Message "SQL Server 2000 databases cannot be migrated to SQL Server versions 2012 and above. Quitting." -Target $destServer -Continue
            }

            Write-Message -Level Verbose -Message "Warning if migration from 2005 to 2012 and above and attach/detach is used."
            if ($sourceServer.VersionMajor -eq 9 -and $destServer.VersionMajor -gt 9 -and !$BackupRestore -and !$Force -and $DetachAttach) {
                Stop-Function -Message "Backup and restore is the safest method for migrating from SQL Server 2005 to other SQL Server versions. Please use the -BackupRestore switch or override this requirement by specifying -Force." -Continue
            }

            if ($sourceServer.Collation -ne $destServer.Collation) {
                Write-Message -Level Verbose -Message "Warning on different collation."
                Write-Message -Level Verbose -Message "Collation on $Source, $($sourceServer.Collation) differs from the $destinstance, $($destServer.Collation)."
            }

            Write-Message -Level Verbose -Message "Ensuring destination server version is equal to or greater than source."
            if ($sourceServer.VersionMajor -ge $destServer.VersionMajor) {
                if ($sourceServer.VersionMinor -gt $destServer.VersionMinor) {
                    Stop-Function -Message "Source SQL Server version build must be <= destination SQL Server for database migration." -Continue
                }
            }

            # SMO's filestreamlevel is sometimes null
            $sql = "select coalesce(SERVERPROPERTY('FilestreamConfiguredLevel'),0) as fs"
            $sourceFilestream = $sourceServer.ConnectionContext.ExecuteScalar($sql)
            $destFilestream = $destServer.ConnectionContext.ExecuteScalar($sql)
            if ($sourceFilestream -gt 0 -and $destFilestream -eq 0) {
                $fsWarning = $true
            }

            Write-Message -Level Verbose -Message "Writing warning about filestream being enabled."
            if ($fsWarning) {
                Write-Message -Level Verbose -Message "FILESTREAM enabled on $source but not $destinstance. Databases that use FILESTREAM will be skipped."
            }

            if ($DetachAttach -eq $true) {
                Write-Message -Level Verbose -Message "Checking access to remote directories."
                $remoteSourcePath = Join-AdminUNC $sourceNetBios (Get-SqlDefaultPaths -SqlInstance $sourceServer -filetype data)

                if ((Test-Path $remoteSourcePath) -ne $true -and $DetachAttach) {
                    Write-Message -Level Warning -Message "Can't access remote Sql directories on $source which is required to perform detach/copy/attach."
                    Write-Message -Level Warning -Message "You can manually try accessing $remoteSourcePath to diagnose any issues."
                    Stop-Function -Message "Halting database migration"
                    return
                }

                $remoteDestPath = Join-AdminUNC $destNetBios (Get-SqlDefaultPaths -SqlInstance $destServer -filetype data)
                If ((Test-Path $remoteDestPath) -ne $true -and $DetachAttach) {
                    Write-Message -Level Warning -Message "Can't access remote Sql directories on $destinstance which is required to perform detach/copy/attach."
                    Write-Message -Level Warning -Message "You can manually try accessing $remoteDestPath to diagnose any issues."
                    Stop-Function -Message "Halting database migration" -Continue
                }
            }

            if (($Database -or $ExcludeDatabase -or $IncludeSupportDbs) -and (!$DetachAttach -and !$BackupRestore)) {
                Stop-Function -Message "You did not select a migration method. Please use -BackupRestore or -DetachAttach."
                return
            }

            if ((!$Database -and !$AllDatabases -and !$IncludeSupportDbs) -and ($DetachAttach -or $BackupRestore)) {
                Stop-Function -Message "You did not select any databases to migrate. Please use -AllDatabases or -Database or -IncludeSupportDbs."
                return
            }

            Write-Message -Level Verbose -Message "Building database list."
            $databaseList = New-Object System.Collections.ArrayList
            $SupportDBs = "ReportServer", "ReportServerTempDB", "distribution"
            foreach ($currentdb in ($sourceServer.Databases | Where-Object IsAccessible)) {
                $dbName = $currentdb.Name
                $dbOwner = $currentdb.Owner

                if ($currentdb.Id -le 4) { continue }
                if ($Database -and $Database -notcontains $dbName) { continue }
                if ($IncludeSupportDBs -eq $false -and $SupportDBs -contains $dbName) { continue }
                if ($IncludeSupportDBs -eq $true -and $SupportDBs -notcontains $dbName) {
                    if ($AllDatabases -eq $false -and $Database.length -eq 0) { continue }
                }
                $null = $databaseList.Add($currentdb)
            }

            Write-Message -Level Verbose -Message "Performing count."
            $dbCount = $databaseList.Count

            Write-Message -Level Verbose -Message "Building file structure inventory for $dbCount databases."

            if ($sourceServer.VersionMajor -eq 8) {
                $sql = "select DB_NAME (dbid) as dbname, name, filename, CASE WHEN groupid = 0 THEN 'LOG' ELSE 'ROWS' END as filetype from sysaltfiles"
            }
            else {
                $sql = "SELECT db.Name AS dbname, type_desc AS FileType, mf.Name, Physical_Name AS filename FROM sys.master_files mf INNER JOIN  sys.databases db ON db.database_id = mf.database_id"
            }

            $dbFileTable = $sourceServer.Databases['master'].ExecuteWithResults($sql)

            if ($destServer.VersionMajor -eq 8) {
                $sql = "select DB_NAME (dbid) as dbname, name, filename, CASE WHEN groupid = 0 THEN 'LOG' ELSE 'ROWS' END as filetype from sysaltfiles"
            }
            else {
                $sql = "SELECT db.Name AS dbname, type_desc AS FileType, mf.Name, Physical_Name AS filename FROM sys.master_files mf INNER JOIN  sys.databases db ON db.database_id = mf.database_id"
            }

            $remoteDbFileTable = $destServer.Databases['master'].ExecuteWithResults($sql)

            $fileStructure = Get-SqlFileStructure -sourceserver $sourceServer -destserver $destServer -databaselist $databaseList -ReuseSourceFolderStructure $ReuseSourceFolderStructure

            $elapsed = [System.Diagnostics.Stopwatch]::StartNew()
            $started = Get-Date
            $script:TimeNow = (Get-Date -UFormat "%m%d%Y%H%M%S")

            if ($AllDatabases -or $ExcludeDatabase.length -gt 0 -or $IncludeSupportDbs -or $Database.length -gt 0) {
                foreach ($currentdb in $databaseList) {
                    $dbName = $currentdb.Name
                    $dbOwner = $currentdb.Owner

                    $copyDatabaseStatus = [pscustomobject]@{
                        SourceServer = $sourceServer.Name
                        DestinationServer = $destServer.Name
                        Name         = $dbName
                        DestinationDatabase = $dbname
                        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-DbaSqlPath -SqlInstance $destServer -Path $remotePath)) {
                            if ($Pscmdlet.ShouldProcess($destinstance, "$remotePath does not exist on $destinstance and ReuseSourceFolderStructure was specified")) {
                                # Stop-Function -Message "Cannot resolve $remotePath on $source. `n`nYou have specified ReuseSourceFolderStructure and exact folder structure does not exist. Halting script."
                                $copyDatabaseStatus.Status = "Failed"
                                $copyDatabaseStatus.Notes = "$remotePath does not exist on $destinstance and ReuseSourceFolderStructure was specified" #"Can't resolve $remotePath"
                                $copyDatabaseStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
                            }
                            continue
                        }
                    }

                    Write-Message -Level Verbose -Message "Checking Availability Group status."
                    if ($currentdb.AvailabilityGroupName.Length -gt 0 -and !$force -and $DetachAttach) {
                        $agName = $currentdb.AvailabilityGroupName
                        Write-Message -Level Verbose -Message "Database is part of an Availability Group ($agName). Use -Force to drop from $agName and migrate. Alternatively, you can use the safer backup/restore method."
                        continue
                    }

                    $dbStatus = $currentdb.Status.ToString()
                    
                    if ($dbStatus.StartsWith("Normal") -eq $false) {
                        if ($Pscmdlet.ShouldProcess($destinstance, "$dbName is not in a Normal state. Skipping.")) {
                            Write-Message -Level Verbose -Message "$dbName is not in a Normal state. Skipping."
                            
                            $copyDatabaseStatus.Status = "Skipped"
                            $copyDatabaseStatus.Notes = "Not in normal state"
                            $copyDatabaseStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
                        }
                        continue
                    }
                    
                    if ($currentdb.ReplicationOptions -ne "None" -and $DetachAttach -eq $true) {
                        if ($Pscmdlet.ShouldProcess($destinstance, "$dbName is part of replication. Skipping.")) {
                            Write-Message -Level Verbose -Message "$dbName is part of replication. Skipping."
                            
                            $copyDatabaseStatus.Status = "Skipped"
                            $copyDatabaseStatus.Notes = "Part of replication"
                            $copyDatabaseStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
                        }
                        continue
                    }
                    
                    if ($currentdb.IsMirroringEnabled -and !$force -and $DetachAttach) {
                        if ($Pscmdlet.ShouldProcess($destinstance, "Database is being mirrored. Use -Force to break mirror and migrate. Alternatively, you can use the safer backup/restore method.")) {
                            Write-Message -Level Verbose -Message "Database is being mirrored. Use -Force to break mirror and migrate. Alternatively, you can use the safer backup/restore method."
                            
                            $copyDatabaseStatus.Status = "Skipped"
                            $copyDatabaseStatus.Notes = "Database is mirrored"
                            $copyDatabaseStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
                        }
                        
                        continue
                    }
                    
                    if (($null -ne $destServer.Databases[$dbName]) -and !$force -and !$WithReplace) {
                        if ($Pscmdlet.ShouldProcess($destinstance, "$dbname exists at destination. Use -Force to drop and migrate. Aborting routine for this database.")) {
                            Write-Message -Level Verbose -Message "$dbname exists at destination. Use -Force to drop and migrate. Aborting routine for this database."
                            
                            $copyDatabaseStatus.Status = "Skipped"
                            $copyDatabaseStatus.Notes = "Already exists"
                            $copyDatabaseStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
                        }
                        continue
                    }
                    elseif ($null -ne $destServer.Databases[$dbName] -and $force) {
                        if ($Pscmdlet.ShouldProcess($destinstance, "DROP DATABASE $dbName")) {
                            Write-Message -Level Verbose -Message "$dbName already exists. -Force was specified. Dropping $dbName on $destinstance."
                            $removeresult = Remove-DbaDatabase -SqlInstance $destserver -Database $dbname -Confirm:$false
                            $dropResult = $removeresult.Status -eq 'Dropped'

                            if ($dropResult -eq $false) {
                                Write-Message -Level Verbose -Message "Database could not be dropped. Aborting routine for this database."

                                $copyDatabaseStatus.Status = "Failed"
                                $copyDatabaseStatus.Notes = "Could not drop database"
                                $copyDatabaseStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
                                continue
                            }
                        }
                    }

                    if ($force) {
                        $WithReplace = $true
                    }

                    Write-Message -Level Verbose -Message "Started: $dbStart."

                    if ($sourceServer.VersionMajor -ge 9) {
                        $sourceDbOwnerChaining = $sourceServer.Databases[$dbName].DatabaseOwnershipChaining
                        $sourceDbTrustworthy = $sourceServer.Databases[$dbName].Trustworthy
                        $sourceDbBrokerEnabled = $sourceServer.Databases[$dbName].BrokerEnabled
                    }

                    $sourceDbReadOnly = $sourceServer.Databases[$dbName].ReadOnly

                    if ($SetSourceReadOnly) {
                        If ($Pscmdlet.ShouldProcess($source, "Set $dbName to read-only")) {
                            Write-Message -Level Verbose -Message "Setting database to read-only."
                            $result = Update-SqldbReadOnly -SqlInstance $sourceServer -dbname $dbName -readonly:$true

                            if ($result -eq $false) {
                                Write-Message -Level Verbose -Message "Couldn't set database to read-only. Aborting routine for this database."
                                continue
                            }
                        }
                    }

                    if ($BackupRestore) {
                        if ($UseLastBackups) {
                            $whatifmsg = "Gathering last backup information for $dbName from $Source and restoring"
                        }
                        else {
                            $whatifmsg = "Backup $dbName from $source and restoring"
                        }
                        If ($Pscmdlet.ShouldProcess($destinstance, $whatifmsg)) {
                            if ($UseLastBackups) {
                                $backupTmpResult = Get-DbaBackupHistory -SqlInstance $sourceServer -Database $dbName -IncludeCopyOnly -Last
                                if (-not $backupTmpResult) {
                                    $copyDatabaseStatus.Type = "Database (BackupRestore)"
                                    $copyDatabaseStatus.Status = "Failed"
                                    $copyDatabaseStatus.Notes = "No backups for $dbName on $source"
                                    $copyDatabaseStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
                                    continue
                                }
                            }
                            else {
                                $backupTmpResult = $backupCollection | Where-Object Database -eq $dbName
                                if (-not $backupTmpResult) {
                                    $backupTmpResult = Backup-DbaDatabase -SqlInstance $sourceServer -Database $dbName -BackupDirectory $NetworkShare -FileCount $numberfiles -CopyOnly:$CopyOnly
                                }
                                if ($backupTmpResult) {
                                    $backupCollection += $backupTmpResult
                                }
                                $backupResult = $BackupTmpResult.BackupComplete
                                if (-not $backupResult) {
                                    $serviceAccount = $sourceServer.ServiceAccount
                                    Write-Message -Level Verbose -Message "Backup Failed. Does SQL Server account $serviceAccount have access to $($NetworkShare)? Aborting routine for this database."

                                    $copyDatabaseStatus.Status = "Failed"
                                    $copyDatabaseStatus.Notes = "Backup failed. Verify service account access to $NetworkShare."
                                    $copyDatabaseStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
                                    continue
                                }
                            }
                            Write-Message -Level Verbose -Message "Reuse = $ReuseSourceFolderStructure."
                            try {
                                $msg = $null
                                $restoreResultTmp = $backupTmpResult | Restore-DbaDatabase -SqlInstance $destServer -DatabaseName $dbName -ReuseSourceFolderStructure:$ReuseSourceFolderStructure -NoRecovery:$NoRecovery -TrustDbBackupHistory -WithReplace:$WithReplace -Continue:$Continue -EnableException
                            }
                            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-DbaDatabaseOwner -SqlInstance $destServer -Database $dbName -TargetLogin $dbOwner -EnableException
                            if ($OwnerResult.Length -eq 0) {
                                Write-Message -Level Verbose -Message "Failed to update database owner."
                            }
                        }
                    }

                    if ($DetachAttach) {
                        $copyDatabaseStatus.Type = "Database (DetachAttach)"

                        $sourceFileStructure = New-Object System.Collections.Specialized.StringCollection
                        foreach ($file in $fileStructure.Databases[$dbName].Source.Values) {
                            $null = $sourceFileStructure.Add($file.Physical)
                        }

                        $dbOwner = $sourceServer.Databases[$dbName].Owner

                        if ($null -eq $dbOwner -or $destServer.Logins.Name -notcontains $dbOwner) {
                            $dbOwner = Get-SaLoginName -SqlInstance $destServer
                        }

                        if ($Pscmdlet.ShouldProcess($destinstance, "Detach $dbName from $source and attach, then update dbowner")) {
                            $migrationResult = Start-SqlDetachAttach $sourceServer $destServer $fileStructure $dbName

                            $dbFinish = Get-Date

                            if ($reattach -eq $true) {
                                $sourceServer.Databases.Refresh()
                                $destServer.Databases.Refresh()
                                $result = Mount-SqlDatabase $sourceServer $dbName $sourceFileStructure $dbOwner

                                if ($result -eq $true) {
                                    $sourceServer.Databases[$dbName].DatabaseOwnershipChaining = $sourceDbOwnerChaining
                                    $sourceServer.Databases[$dbName].Trustworthy = $sourceDbTrustworthy
                                    $sourceServer.Databases[$dbName].BrokerEnabled = $sourceDbBrokerEnabled
                                    $sourceServer.Databases[$dbName].Alter()

                                    if ($SetSourceReadOnly) {
                                        $null = Update-SqldbReadOnly -SqlInstance $sourceServer -dbname $dbName -readonly $true
                                    }
                                    else {
                                        $null = Update-SqldbReadOnly -SqlInstance $sourceServer -dbname $dbName -readonly $sourceDbReadOnly
                                    }

                                    Write-Message -Level Verbose -Message "Successfully reattached $dbName to $source."
                                }
                                else {
                                    Write-Message -Level Verbose -Message "Could not reattach $dbName to $source."
                                    $copyDatabaseStatus.Status = "Failed"
                                    $copyDatabaseStatus.Notes = "Could not reattach database to $source"
                                    $copyDatabaseStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
                                }
                            }

                            if ($migrationResult -eq $true) {
                                Write-Message -Level Verbose -Message "Successfully attached $dbName to $destinstance."
                                $copyDatabaseStatus.Status = "Successful"
                                $copyDatabaseStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
                            }
                            else {
                                Write-Message -Level Verbose -Message "Failed to attach $dbName to $destinstance. Aborting routine for this database."

                                $copyDatabaseStatus.Status = "Failed"
                                $copyDatabaseStatus.Notes = "Failed to attach database to destination"
                                $copyDatabaseStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject

                                continue
                            }
                        }
                    }
                    $destServer.Databases.Refresh()

                    # restore potentially lost settings
                    if ($destServer.VersionMajor -ge 9 -and $NoRecovery -eq $false) {
                        if ($sourceDbOwnerChaining -ne $destServer.Databases[$dbName].DatabaseOwnershipChaining) {
                            if ($Pscmdlet.ShouldProcess($destinstance, "Updating DatabaseOwnershipChaining on $dbName")) {
                                try {
                                    $destServer.Databases[$dbName].DatabaseOwnershipChaining = $sourceDbOwnerChaining
                                    $destServer.Databases[$dbName].Alter()
                                    Write-Message -Level Verbose -Message "Successfully updated DatabaseOwnershipChaining for $sourceDbOwnerChaining on $dbName 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 $dbName on $destinstance." -Target $destinstance -ErrorRecord $_ -Continue
                                }
                            }
                        }

                        if ($sourceDbTrustworthy -ne $destServer.Databases[$dbName].Trustworthy) {
                            if ($Pscmdlet.ShouldProcess($destinstance, "Updating Trustworthy on $dbName")) {
                                try {
                                    $destServer.Databases[$dbName].Trustworthy = $sourceDbTrustworthy
                                    $destServer.Databases[$dbName].Alter()
                                    Write-Message -Level Verbose -Message "Successfully updated Trustworthy to $sourceDbTrustworthy for $dbName 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 $dbName on $destinstance." -Target $destinstance -ErrorRecord $_ -Continue
                                }
                            }
                        }

                        if ($sourceDbBrokerEnabled -ne $destServer.Databases[$dbName].BrokerEnabled) {
                            if ($Pscmdlet.ShouldProcess($destinstance, "Updating BrokerEnabled on $dbName")) {
                                try {
                                    $destServer.Databases[$dbName].BrokerEnabled = $sourceDbBrokerEnabled
                                    $destServer.Databases[$dbName].Alter()
                                    Write-Message -Level Verbose -Message "Successfully updated BrokerEnabled to $sourceDbBrokerEnabled for $dbName 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 $dbName on $destinstance." -Target $destinstance -ErrorRecord $_ -Continue
                                }
                            }
                        }
                    }

                    if ($sourceDbReadOnly -ne $destServer.Databases[$dbName].ReadOnly -and $NoRecovery -eq $false) {
                        if ($Pscmdlet.ShouldProcess($destinstance, "Updating ReadOnly status on $dbName")) {
                            $update = Update-SqldbReadOnly -SqlInstance $destServer -dbname $dbName -readonly $sourceDbReadOnly
                            if ($update -eq $true) {
                                Write-Message -Level Verbose -Message "Successfully updated readonly status on $dbName."
                            }
                            else {
                                $copyDatabaseStatus.Status = "Successful - failed to apply ReadOnly."
                                $copyDatabaseStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
                                Stop-Function -Message "Failed to update ReadOnly status on $dbName." -Target $destinstance -ErrorRecord $_ -Continue
                            }
                        }
                    }

                    if ($SetSourceOffline -and $sourceServer.databases[$dbName].status -notlike '*offline*') {
                        if ($Pscmdlet.ShouldProcess($destinstance, "Setting $dbName offline on $source")) {
                            Stop-DbaProcess -SqlInstance $sourceServer -Database $dbName
                            Set-DbaDatabaseState -SqlInstance $sourceServer -SqlCredential $SourceSqlCredential -database $dbName -Offline
                        }
                    }

                    $dbTotalTime = $dbFinish - $dbStart
                    $dbTotalTime = ($dbTotalTime.ToString().Split(".")[0])

                    Write-Message -Level Verbose -Message "Finished: $dbFinish."
                    Write-Message -Level Verbose -Message "Elapsed time: $dbTotalTime."

                } # end db by db processing
            }
        }
    }
    end {
        if (Test-FunctionInterrupt) { return }
        if (-not $NoBackupCleanUp -and $Destination.Count -gt 1) {
            foreach ($backupFile in ($backupCollection.BackupPath)) {
                try {
                    if (Test-Path $backupFile -ErrorAction Stop) {
                        Write-Message -Level Verbose -Message "Deleting $backupFile."
                        Remove-Item $backupFile -ErrorAction Stop
                    }
                }
                catch {
                    try {
                        Write-Message -Level Verbose -Message "Trying alternate SQL method to delete $backupFile."
                        $sql = "EXEC master.sys.xp_delete_file 0, '$backupFile'"
                        Write-Message -Level Debug -Message $sql
                        $null = $sourceServer.Query($sql)
                    }
                    catch {
                        Write-Message -Level Verbose -Message "Cannot delete backup file $backupFile."
                    }
                }
            }
        }
        if (Test-FunctionInterrupt) { return }
        if ($null -ne $elapsed) {
            $totalTime = ($elapsed.Elapsed.toString().Split(".")[0])

            Write-Message -Level Verbose -Message "`nDatabase migration finished"
            Write-Message -Level Verbose -Message "Migration started: $started"
            Write-Message -Level Verbose -Message "Migration completed: $(Get-Date)"
            Write-Message -Level Verbose -Message "Total Elapsed time: $totalTime"

            if ($NetworkShare.length -gt 0 -and $NoBackupCleanup) {
                Write-Message -Level Verbose -Message "Backups still exist at $NetworkShare."
            }
        }
        else {
            Write-Message -Level Verbose -Message "No work was done, as we stopped during setup phase"
        }
        Test-DbaDeprecation -DeprecatedOn "1.0.0" -EnableException:$false -Alias Copy-SqlDatabase
    }
}
function Copy-DbaDatabaseAssembly {
    <#
        .SYNOPSIS
            Copy-DbaDatabaseAssembly migrates assemblies from one SQL Server to another.

        .DESCRIPTION
            By default, all assemblies are copied.

            If the assembly already exists on the destination, it will be skipped unless -Force is used.

            This script does not yet copy dependencies or dependent objects.

        .PARAMETER Source
            Source SQL Server. You must have sysadmin access and server version must be SQL Server version 2000 or higher.

        .PARAMETER SourceSqlCredential
            Login to the target instance using alternative credentials. Windows and SQL Authentication supported. Accepts credential objects (Get-Credential)

        .PARAMETER Destination
            Destination SQL Server. You must have sysadmin access and the server must be SQL Server 2000 or higher.

        .PARAMETER DestinationSqlCredential
            Login to the target instance using alternative credentials. Windows and SQL Authentication supported. Accepts credential objects (Get-Credential)

        .PARAMETER Assembly
            The assembly(ies) to process. This list is auto-populated from the server. If unspecified, all assemblies will be processed.

        .PARAMETER ExcludeAssembly
            The assembly(ies) to exclude. This list is auto-populated from the server.

        .PARAMETER WhatIf
            If this switch is enabled, no actions are performed but informational messages will be displayed that explain what would happen if the command were to run.

        .PARAMETER Confirm
            If this switch is enabled, you will be prompted for confirmation before executing any operations that change state.

        .PARAMETER EnableException
            By default, when something goes wrong we try to catch it, interpret it and give you a friendly warning message.
            This avoids overwhelming you with "sea of red" exceptions, but is inconvenient because it basically disables advanced scripting.
            Using this switch turns this "nice by default" feature off and enables you to catch exceptions with your own try/catch.

        .PARAMETER Force
            If this switch is enabled, existing assemblies on Destination with matching names from Source will be dropped.

        .NOTES
            Tags: Migration, Assembly
            Author: Chrissy LeMaire (@cl), netnerds.net
            Requires: sysadmin access on SQL Servers

            Website: https://dbatools.io
            Copyright: (C) Chrissy LeMaire, [email protected]
            License: MIT https://opensource.org/licenses/MIT

        .LINK
            http://dbatools.io/Get-SqlDatabaseAssembly

        .EXAMPLE
            Copy-DbaDatabaseAssembly -Source sqlserver2014a -Destination sqlcluster

            Copies all assemblies from sqlserver2014a to sqlcluster using Windows credentials. If assemblies with the same name exist on sqlcluster, they will be skipped.

        .EXAMPLE
            Copy-DbaDatabaseAssembly -Source sqlserver2014a -Destination sqlcluster -Assembly dbname.assemblyname, dbname3.anotherassembly -SourceSqlCredential $cred -Force

            Copies two assemblies, the dbname.assemblyname and dbname3.anotherassembly from sqlserver2014a to sqlcluster using SQL credentials for sqlserver2014a and Windows credentials for sqlcluster. If an assembly with the same name exists on sqlcluster, it will be dropped and recreated because -Force was used.

            In this example, anotherassembly will be copied to the dbname3 database on the server sqlcluster.

        .EXAMPLE
            Copy-DbaThing -Source sqlserver2014a -Destination sqlcluster -WhatIf -Force

            Shows what would happen if the command were executed using force.
    #>
    [CmdletBinding(DefaultParameterSetName = "Default", SupportsShouldProcess = $true)]
    param (
        [parameter(Mandatory = $true)]
        [DbaInstanceParameter]$Source,
        [PSCredential]$SourceSqlCredential,
        [parameter(Mandatory = $true)]
        [DbaInstanceParameter[]]$Destination,
        [PSCredential]$DestinationSqlCredential,
        [object[]]$Assembly,
        [object[]]$ExcludeAssembly,
        [switch]$Force,
        [Alias('Silent')]
        [switch]$EnableException
    )
    begin {
        try {
            Write-Message -Level Verbose -Message "Connecting to $Source"
            $sourceServer = Connect-SqlInstance -SqlInstance $Source -SqlCredential $SourceSqlCredential -MinimumVersion 9
        }
        catch {
            Stop-Function -Message "Failure" -Category ConnectionError -ErrorRecord $_ -Target $Source
            return
        }
        $sourceAssemblies = @()
        foreach ($database in ($sourceServer.Databases | Where-Object IsAccessible)) {
            Write-Message -Level Verbose -Message "Processing $database on source"
            
            try {
                # a bug here requires a try/catch
                $userAssemblies = $database.Assemblies | Where-Object IsSystemObject -eq $false
                foreach ($assembly in $userAssemblies) {
                    $sourceAssemblies += $assembly
                }
            }
            catch { }
        }
    }
    process {
        if (Test-FunctionInterrupt) { return }
        foreach ($destinstance in $Destination) {
            try {
                Write-Message -Level Verbose -Message "Connecting to $destinstance"
                $destServer = Connect-SqlInstance -SqlInstance $destinstance -SqlCredential $DestinationSqlCredential -MinimumVersion 9
            }
            catch {
                Stop-Function -Message "Failure" -Category ConnectionError -ErrorRecord $_ -Target $destinstance -Continue
            }
            
            $destAssemblies = @()
            foreach ($database in $destServer.Databases) {
                Write-Message -Level VeryVerbose -Message "Processing $database on destination"
                try {
                    # a bug here requires a try/catch
                    $userAssemblies = $database.Assemblies | Where-Object IsSystemObject -eq $false
                    foreach ($assembly in $userAssemblies) {
                        $destAssemblies += $assembly
                    }
                }
                catch { }
            }
            foreach ($currentAssembly in $sourceAssemblies) {
                $assemblyName = $currentAssembly.Name
                $dbName = $currentAssembly.Parent.Name
                $destDb = $destServer.Databases[$dbName]
                Write-Message -Level VeryVerbose -Message "Processing $assemblyName on $dbname"
                $copyDbAssemblyStatus = [pscustomobject]@{
                    SourceServer = $sourceServer.Name
                    SourceDatabase = $dbName
                    DestinationServer = $destServer.Name
                    DestinationDatabase = $destDb
                    type         = "Database Assembly"
                    Name         = $assemblyName
                    Status       = $null
                    Notes        = $null
                    DateTime     = [Sqlcollaborative.Dbatools.Utility.DbaDateTime](Get-Date)
                }
                
                
                if (!$destDb) {
                    $copyDbAssemblyStatus.Status = "Skipped"
                    $copyDbAssemblyStatus.Notes = "Destination database does not exist"
                    $copyDbAssemblyStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
                    
                    Write-Message -Level Verbose -Message "Destination database $dbName does not exist. Skipping $assemblyName.";
                    continue
                }
                
                if ((Test-Bound -ParameterName Assembly) -and $Assembly -notcontains "$dbName.$assemblyName" -or $ExcludeAssembly -contains "$dbName.$assemblyName") {
                    continue
                }
                
                if ($currentAssembly.AssemblySecurityLevel -eq "External" -and -not $destDb.Trustworthy) {
                    if ($Pscmdlet.ShouldProcess($destinstance, "Setting $dbName to External")) {
                        Write-Message -Level Verbose -Message "Setting $dbName Security Level to External on $destinstance."
                        $sql = "ALTER DATABASE $dbName SET TRUSTWORTHY ON"
                        try {
                            Write-Message -Level Debug -Message $sql
                            $destServer.Query($sql)
                        }
                        catch {
                            $copyDbAssemblyStatus.Status = "Failed"
                            $copyDbAssemblyStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
                            
                            Stop-Function -Message "Issue setting security level." -Target $destDb -ErrorRecord $_
                        }
                    }
                }
                
                if ($destServer.Databases[$dbName].Assemblies.Name -contains $currentAssembly.name) {
                    if ($force -eq $false) {
                        $copyDbAssemblyStatus.Status = "Skipped"
                        $copyDbAssemblyStatus.Notes = "Already exists"
                        $copyDbAssemblyStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
                        
                        Write-Message -Level Verbose -Message "Assembly $assemblyName exists at destination in the $dbName database. Use -Force to drop and migrate."
                        continue
                    }
                    else {
                        if ($Pscmdlet.ShouldProcess($destinstance, "Dropping assembly $assemblyName and recreating")) {
                            try {
                                Write-Message -Level Verbose -Message "Dropping assembly $assemblyName."
                                Write-Message -Level Verbose -Message "This won't work if there are dependencies."
                                $destServer.Databases[$dbName].Assemblies[$assemblyName].Drop()
                                Write-Message -Level Verbose -Message "Copying assembly $assemblyName."
                                $sql = $currentAssembly.Script()
                                Write-Message -Level Debug -Message $sql
                                $destServer.Query($sql, $dbName)
                            }
                            catch {
                                $copyDbAssemblyStatus.Status = "Failed"
                                $copyDbAssemblyStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
                                
                                Stop-Function -Message "Issue dropping assembly." -Target $assemblyName -ErrorRecord $_ -Continue
                            }
                        }
                    }
                }
                
                if ($Pscmdlet.ShouldProcess($destinstance, "Creating assembly $assemblyName")) {
                    try {
                        Write-Message -Level Verbose -Message "Copying assembly $assemblyName from database."
                        $sql = $currentAssembly.Script()
                        Write-Message -Level Debug -Message $sql
                        $destServer.Query($sql, $dbName)
                        
                        $copyDbAssemblyStatus.Status = "Successful"
                        $copyDbAssemblyStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
                        
                    }
                    catch {
                        $copyDbAssemblyStatus.Status = "Failed"
                        $copyDbAssemblyStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
                        
                        Stop-Function -Message "Issue creating assembly." -Target $assemblyName -ErrorRecord $_
                    }
                }
            }
        }
    }
    end {
        Test-DbaDeprecation -DeprecatedOn "1.0.0" -EnableException:$false -Alias Copy-SqlDatabaseAssembly
    }
}
function Copy-DbaDatabaseMail {
    <#
    .SYNOPSIS
        Migrates Mail Profiles, Accounts, Mail Servers and Mail Server Configs from one SQL Server to another.

    .DESCRIPTION
        By default, all mail configurations for Profiles, Accounts, Mail Servers and Configs are copied.

    .PARAMETER Source
        Source SQL Server. You must have sysadmin access and server version must be SQL Server version 2000 or higher.

    .PARAMETER SourceSqlCredential
        Login to the target instance using alternative credentials. Windows and SQL Authentication supported. Accepts credential objects (Get-Credential)

    .PARAMETER Destination
        Destination SQL Server. You must have sysadmin access and the server must be SQL Server 2000 or higher.

    .PARAMETER DestinationSqlCredential
        Login to the target instance using alternative credentials. Windows and SQL Authentication supported. Accepts credential objects (Get-Credential)

    .PARAMETER Type
        Specifies the object type to migrate. Valid options are "Job", "Alert" and "Operator". When Type is specified, all categories from the selected type will be migrated.

    .PARAMETER WhatIf
        If this switch is enabled, no actions are performed but informational messages will be displayed that explain what would happen if the command were to run.

    .PARAMETER Confirm
        If this switch is enabled, you will be prompted for confirmation before executing any operations that change state.

    .PARAMETER EnableException
        By default, when something goes wrong we try to catch it, interpret it and give you a friendly warning message.
        This avoids overwhelming you with "sea of red" exceptions, but is inconvenient because it basically disables advanced scripting.
        Using this switch turns this "nice by default" feature off and enables you to catch exceptions with your own try/catch.

    .PARAMETER Force
        If this switch is enabled, existing objects on Destination with matching names from Source will be dropped.

    .NOTES
        Tags: Migration, Mail
        Author: Chrissy LeMaire (@cl), netnerds.net
        Requires: sysadmin access on SQL Servers

        Website: https://dbatools.io
        Copyright: (C) Chrissy LeMaire, [email protected]
        License: MIT https://opensource.org/licenses/MIT

    .LINK
        https://dbatools.io/Copy-DbaDatabaseMail

    .EXAMPLE
        Copy-DbaDatabaseMail -Source sqlserver2014a -Destination sqlcluster

        Copies all database mail objects from sqlserver2014a to sqlcluster using Windows credentials. If database mail objects with the same name exist on sqlcluster, they will be skipped.

    .EXAMPLE
        Copy-DbaDatabaseMail -Source sqlserver2014a -Destination sqlcluster -SourceSqlCredential $cred

        Copies all database mail objects from sqlserver2014a to sqlcluster using SQL credentials for sqlserver2014a and Windows credentials for sqlcluster.

    .EXAMPLE
        Copy-DbaDatabaseMail -Source sqlserver2014a -Destination sqlcluster -WhatIf

        Shows what would happen if the command were executed.

    .EXAMPLE
        Copy-DbaDatabaseMail -Source sqlserver2014a -Destination sqlcluster -EnableException

        Performs execution of function, and will throw a terminating exception if something breaks
    #>
    [cmdletbinding(DefaultParameterSetName = "Default", SupportsShouldProcess = $true)]
    param (
        [parameter(Mandatory = $true)]
        [DbaInstanceParameter]$Source,
        [parameter(Mandatory = $true)]
        [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-DbaDatabaseMailConfig {
            [cmdletbinding(SupportsShouldProcess)]
            param ()

            Write-Message -Message "Migrating mail server configuration values." -Level Verbose
            $copyMailConfigStatus = [pscustomobject]@{
                SourceServer      = $sourceServer.Name
                DestinationServer = $destServer.Name
                Name              = "Server Configuration"
                Type              = "Mail Configuration"
                Status            = $null
                Notes             = $null
                DateTime          = [Sqlcollaborative.Dbatools.Utility.DbaDateTime](Get-Date)
            }
            if ($pscmdlet.ShouldProcess($destinstance, "Migrating all mail server configuration values.")) {
                try {
                    $sql = $mail.ConfigurationValues.Script() | Out-String
                    $sql = $sql -replace [Regex]::Escape("'$source'"), "'$destinstance'"
                    Write-Message -Message $sql -Level Debug
                    $destServer.Query($sql) | Out-Null
                    $mail.ConfigurationValues.Refresh()
                    $copyMailConfigStatus.Status = "Successful"
                }
                catch {
                    $copyMailConfigStatus.Status = "Failed"
                    $copyMailConfigStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
                    Stop-Function -Message "Unable to migrate mail configuration." -Category InvalidOperation -InnerErrorRecord $_ -Target $destServer
                }
                $copyMailConfigStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
            }
        }
        
        function Copy-DbaDatabaseAccount {
            [cmdletbinding(SupportsShouldProcess)]
            $sourceAccounts = $sourceServer.Mail.Accounts
            $destAccounts = $destServer.Mail.Accounts

            Write-Message -Message "Migrating accounts." -Level Verbose
            foreach ($account in $sourceAccounts) {
                $accountName = $account.name
                $copyMailAccountStatus = [pscustomobject]@{
                    SourceServer      = $sourceServer.Name
                    DestinationServer = $destServer.Name
                    Name              = $accountName
                    Type              = "Mail Account"
                    Status            = $null
                    Notes             = $null
                    DateTime          = [Sqlcollaborative.Dbatools.Utility.DbaDateTime](Get-Date)
                }

                if ($accounts.count -gt 0 -and $accounts -notcontains $accountName) {
                    continue
                }

                if ($destAccounts.name -contains $accountName) {
                    if ($force -eq $false) {
                        If ($pscmdlet.ShouldProcess($destinstance, "Account $accountName exists at destination. Use -Force to drop and migrate.")) {
                            $copyMailAccountStatus.Status = "Skipped"
                            $copyMailAccountStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
                            Write-Message -Message "Account $accountName exists at destination. Use -Force to drop and migrate." -Level Verbose
                        }
                        continue
                    }

                    If ($pscmdlet.ShouldProcess($destinstance, "Dropping account $accountName and recreating.")) {
                        try {
                            Write-Message -Message "Dropping account $accountName." -Level Verbose
                            $destServer.Mail.Accounts[$accountName].Drop()
                            $destServer.Mail.Accounts.Refresh()
                        }
                        catch {
                            $copyMailAccountStatus.Status = "Failed"
                            $copyMailAccountStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
                            Stop-Function -Message "Issue dropping account." -Target $accountName -Category InvalidOperation -InnerErrorRecord $_ -Continue
                        }
                    }
                }

                if ($pscmdlet.ShouldProcess($destinstance, "Migrating account $accountName.")) {
                    try {
                        Write-Message -Message "Copying mail account $accountName." -Level Verbose
                        $sql = $account.Script() | Out-String
                        $sql = $sql -replace [Regex]::Escape("'$source'"), "'$destinstance'"
                        Write-Message -Message $sql -Level Debug
                        $destServer.Query($sql) | Out-Null
                        $copyMailAccountStatus.Status = "Successful"
                    }
                    catch {
                        $copyMailAccountStatus.Status = "Failed"
                        $copyMailAccountStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
                        Stop-Function -Message "Issue copying mail account." -Target $accountName -Category InvalidOperation -InnerErrorRecord $_
                    }
                    $copyMailAccountStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
                }
            }
        }
        
        function Copy-DbaDatabaseMailProfile {

            $sourceProfiles = $sourceServer.Mail.Profiles
            $destProfiles = $destServer.Mail.Profiles

            Write-Message -Message "Migrating mail profiles." -Level Verbose
            foreach ($profile in $sourceProfiles) {

                $profileName = $profile.name
                $copyMailProfileStatus = [pscustomobject]@{
                    SourceServer      = $sourceServer.Name
                    DestinationServer = $destServer.Name
                    Name              = $profileName
                    Type              = "Mail Profile"
                    Status            = $null
                    Notes             = $null
                    DateTime          = [Sqlcollaborative.Dbatools.Utility.DbaDateTime](Get-Date)
                }

                if ($profiles.count -gt 0 -and $profiles -notcontains $profileName) {
                    continue
                }

                if ($destProfiles.name -contains $profileName) {
                    if ($force -eq $false) {
                        If ($pscmdlet.ShouldProcess($destinstance, "Profile $profileName exists at destination. Use -Force to drop and migrate.")) {
                            $copyMailProfileStatus.Status = "Skipped"
                            $copyMailProfileStatus.Notes = "Already exists"
                            $copyMailProfileStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
                            Write-Message -Message "Profile $profileName exists at destination. Use -Force to drop and migrate." -Level Verbose
                        }
                        continue
                    }

                    If ($pscmdlet.ShouldProcess($destinstance, "Dropping profile $profileName and recreating.")) {
                        try {
                            Write-Message -Message "Dropping profile $profileName." -Level Verbose
                            $destServer.Mail.Profiles[$profileName].Drop()
                            $destServer.Mail.Profiles.Refresh()
                        }
                        catch {
                            $copyMailProfileStatus.Status = "Failed"
                            $copyMailProfileStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
                            Stop-Function -Message "Issue dropping profile." -Target $profileName -Category InvalidOperation -InnerErrorRecord $_ -Continue
                        }
                    }
                }

                if ($pscmdlet.ShouldProcess($destinstance, "Migrating mail profile $profileName.")) {
                    try {
                        Write-Message -Message "Copying mail profile $profileName." -Level Verbose
                        $sql = $profile.Script() | Out-String
                        $sql = $sql -replace [Regex]::Escape("'$source'"), "'$destinstance'"
                        Write-Message -Message $sql -Level Debug
                        $destServer.Query($sql) | Out-Null
                        $destServer.Mail.Profiles.Refresh()
                        $copyMailProfileStatus.Status = "Successful"
                    }
                    catch {
                        $copyMailProfileStatus.Status = "Failed"
                        $copyMailProfileStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
                        Stop-Function -Message "Issue copying mail profile." -Target $profileName -Category InvalidOperation -InnerErrorRecord $_
                    }
                    $copyMailProfileStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
                }
            }
        }
        
        function Copy-DbaDatabaseMailServer {
            [cmdletbinding(SupportsShouldProcess)]
            $sourceMailServers = $sourceServer.Mail.Accounts.MailServers
            $destMailServers = $destServer.Mail.Accounts.MailServers

            Write-Message -Message "Migrating mail servers." -Level Verbose
            foreach ($mailServer in $sourceMailServers) {
                $mailServerName = $mailServer.name
                $copyMailServerStatus = [pscustomobject]@{
                    SourceServer      = $sourceServer.Name
                    DestinationServer = $destServer.Name
                    Name              = $mailServerName
                    Type              = "Mail Server"
                    Status            = $null
                    Notes             = $null
                    DateTime          = [Sqlcollaborative.Dbatools.Utility.DbaDateTime](Get-Date)
                }
                if ($mailServers.count -gt 0 -and $mailServers -notcontains $mailServerName) {
                    continue
                }

                if ($destMailServers.name -contains $mailServerName) {
                    if ($force -eq $false) {
                        if ($pscmdlet.ShouldProcess($destinstance, "Mail server $mailServerName exists at destination. Use -Force to drop and migrate.")) {
                            $copyMailServerStatus.Status = "Skipped"
                            $copyMailServerStatus.Notes = "Already exists"
                            $copyMailServerStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
                            Write-Message -Message "Mail server $mailServerName exists at destination. Use -Force to drop and migrate." -Level Verbose
                        }
                        continue
                    }

                    If ($pscmdlet.ShouldProcess($destinstance, "Dropping mail server $mailServerName and recreating.")) {
                        try {
                            Write-Message -Message "Dropping mail server $mailServerName." -Level Verbose
                            $destServer.Mail.Accounts.MailServers[$mailServerName].Drop()
                        }
                        catch {
                            $copyMailServerStatus.Status = "Failed"
                            $copyMailServerStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
                            Stop-Function -Message "Issue dropping mail server." -Target $mailServerName -Category InvalidOperation -InnerErrorRecord $_ -Continue
                        }
                    }
                }

                if ($pscmdlet.ShouldProcess($destinstance, "Migrating account mail server $mailServerName.")) {
                    try {
                        Write-Message -Message "Copying mail server $mailServerName." -Level Verbose
                        $sql = $mailServer.Script() | Out-String
                        $sql = $sql -replace [Regex]::Escape("'$source'"), "'$destinstance'"
                        Write-Message -Message $sql -Level Debug
                        $destServer.Query($sql) | Out-Null
                        $copyMailServerStatus.Status = "Successful"
                    }
                    catch {
                        $copyMailServerStatus.Status = "Failed"
                        $copyMailServerStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
                        Stop-Function -Message "Issue copying mail server" -Target $mailServerName -Category InvalidOperation -InnerErrorRecord $_
                    }
                    $copyMailServerStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
                }
            }
        }
        
        try {
            Write-Message -Level Verbose -Message "Connecting to $Source"
            $sourceServer = Connect-SqlInstance -SqlInstance $Source -SqlCredential $SourceSqlCredential -MinimumVersion 9
        }
        catch {
            Stop-Function -Message "Failure" -Category ConnectionError -ErrorRecord $_ -Target $Source
            return
        }
        $mail = $sourceServer.mail
    }
    process {
        if (Test-FunctionInterrupt) { return }
        foreach ($destinstance in $Destination) {
            try {
                Write-Message -Level Verbose -Message "Connecting to $destinstance"
                $destServer = Connect-SqlInstance -SqlInstance $destinstance -SqlCredential $DestinationSqlCredential -MinimumVersion 9
            }
            catch {
                Stop-Function -Message "Failure" -Category ConnectionError -ErrorRecord $_ -Target $destinstance -Continue
            }
            
            if ($type.Count -gt 0) {
                
                switch ($type) {
                    "ConfigurationValues" {
                        Copy-DbaDatabaseMailConfig
                        $destServer.Mail.ConfigurationValues.Refresh()
                    }
                    
                    "Profiles" {
                        Copy-DbaDatabaseMailProfile
                        $destServer.Mail.Profiles.Refresh()
                    }
                    
                    "Accounts" {
                        Copy-DbaDatabaseAccount
                        $destServer.Mail.Accounts.Refresh()
                    }
                    
                    "mailServers" {
                        Copy-DbaDatabaseMailServer
                    }
                }
                
                continue
            }
            
            if (($profiles.count + $accounts.count + $mailServers.count) -gt 0) {
                
                if ($profiles.count -gt 0) {
                    Copy-DbaDatabaseMailProfile -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-DbaDatabaseMailServer -mailServers $mailServers
                }
                
                continue
            }
            
            Copy-DbaDatabaseMailConfig
            $destServer.Mail.ConfigurationValues.Refresh()
            Copy-DbaDatabaseAccount
            $destServer.Mail.Accounts.Refresh()
            Copy-DbaDatabaseMailProfile
            $destServer.Mail.Profiles.Refresh()
            Copy-DbaDatabaseMailServer
            $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
    }
}
function Copy-DbaEndpoint {
    <#
        .SYNOPSIS
            Copy-DbaEndpoint migrates server endpoints from one SQL Server to another.

        .DESCRIPTION
            By default, all endpoints are copied.

            If the endpoint already exists on the destination, it will be skipped unless -Force is used.

        .PARAMETER Source
            Source SQL Server. You must have sysadmin access and server version must be SQL Server version 2000 or higher.

        .PARAMETER SourceSqlCredential
            Login to the target instance using alternative credentials. Windows and SQL Authentication supported. Accepts credential objects (Get-Credential)

        .PARAMETER Destination
            Destination SQL Server. You must have sysadmin access and the server must be SQL Server 2000 or higher.

        .PARAMETER DestinationSqlCredential
            Login to the target instance using alternative credentials. Windows and SQL Authentication supported. Accepts credential objects (Get-Credential)

        .PARAMETER Endpoint
            The endpoint(s) to process. This list is auto-populated from the server. If unspecified, all endpoints will be processed.

        .PARAMETER ExcludeEndpoint
            The endpoint(s) to exclude. This list is auto-populated from the server.

        .PARAMETER WhatIf
            If this switch is enabled, no actions are performed but informational messages will be displayed that explain what would happen if the command were to run.

        .PARAMETER Confirm
            If this switch is enabled, you will be prompted for confirmation before executing any operations that change state.

        .PARAMETER EnableException
            By default, when something goes wrong we try to catch it, interpret it and give you a friendly warning message.
            This avoids overwhelming you with "sea of red" exceptions, but is inconvenient because it basically disables advanced scripting.
            Using this switch turns this "nice by default" feature off and enables you to catch exceptions with your own try/catch.

        .PARAMETER Force
            If this switch is enabled, existing endpoints on Destination with matching names from Source will be dropped.

        .NOTES
            Tags: Migration, Endpoint
            Author: Chrissy LeMaire (@cl), netnerds.net
            Requires: sysadmin access on SQL Servers

            Website: https://dbatools.io
            Copyright: (C) Chrissy LeMaire, [email protected]
            License: MIT https://opensource.org/licenses/MIT

        .LINK
            https://dbatools.io/Copy-DbaEndpoint

        .EXAMPLE
            Copy-DbaEndpoint -Source sqlserver2014a -Destination sqlcluster

            Copies all server endpoints from sqlserver2014a to sqlcluster, using Windows credentials. If endpoints with the same name exist on sqlcluster, they will be skipped.

        .EXAMPLE
            Copy-DbaEndpoint -Source sqlserver2014a -SourceSqlCredential $cred -Destination sqlcluster -Endpoint tg_noDbDrop -Force

            Copies only the tg_noDbDrop endpoint from sqlserver2014a to sqlcluster using SQL credentials for sqlserver2014a and Windows credentials for sqlcluster. If an endpoint with the same name exists on sqlcluster, it will be dropped and recreated because -Force was used.

        .EXAMPLE
            Copy-DbaEndpoint -Source sqlserver2014a -Destination sqlcluster -WhatIf -Force

            Shows what would happen if the command were executed using force.
    #>
    [CmdletBinding(DefaultParameterSetName = "Default", SupportsShouldProcess = $true)]
    param (
        [parameter(Mandatory = $true)]
        [DbaInstanceParameter]$Source,
        [PSCredential]
        $SourceSqlCredential,
        [parameter(Mandatory = $true)]
        [DbaInstanceParameter[]]$Destination,
        [PSCredential]
        $DestinationSqlCredential,
        [object[]]$Endpoint,
        [object[]]$ExcludeEndpoint,
        [switch]$Force,
        [Alias('Silent')]
        [switch]$EnableException
    )

    begin {
        try {
            Write-Message -Level Verbose -Message "Connecting to $Source"
            $sourceServer = Connect-SqlInstance -SqlInstance $Source -SqlCredential $SourceSqlCredential -MinimumVersion 9
        }
        catch {
            Stop-Function -Message "Failure" -Category ConnectionError -ErrorRecord $_ -Target $Source
            return
        }
        $serverEndpoints = $sourceServer.Endpoints | Where-Object IsSystemObject -eq $false
    }
    process {
        if (Test-FunctionInterrupt) { return }
        foreach ($destinstance in $Destination) {
            try {
                Write-Message -Level Verbose -Message "Connecting to $destinstance"
                $destServer = Connect-SqlInstance -SqlInstance $destinstance -SqlCredential $DestinationSqlCredential -MinimumVersion 9
            }
            catch {
                Stop-Function -Message "Failure" -Category ConnectionError -ErrorRecord $_ -Target $destinstance -Continue
            }
            $destEndpoints = $destServer.Endpoints
            
            foreach ($currentEndpoint in $serverEndpoints) {
                $endpointName = $currentEndpoint.Name
                
                $copyEndpointStatus = [pscustomobject]@{
                    SourceServer = $sourceServer.Name
                    DestinationServer = $destServer.Name
                    Name         = $endpointName
                    Type         = "Endpoint"
                    Status       = $null
                    Notes        = $null
                    DateTime     = [DbaDateTime](Get-Date)
                }
                
                if ($Endpoint -and $Endpoint -notcontains $endpointName -or $ExcludeEndpoint -contains $endpointName) {
                    continue
                }
                
                if ($destEndpoints.Name -contains $endpointName) {
                    if ($force -eq $false) {
                        $copyEndpointStatus.Status = "Skipped"
                        $copyEndpointStatus.Notes = "Already exists"
                        $copyEndpointStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
                        
                        Write-Message -Level Verbose -Message "Server endpoint $endpointName exists at destination. Use -Force to drop and migrate."
                        continue
                    }
                    else {
                        if ($Pscmdlet.ShouldProcess($destinstance, "Dropping server endpoint $endpointName and recreating.")) {
                            try {
                                Write-Message -Level Verbose -Message "Dropping server endpoint $endpointName."
                                $destServer.Endpoints[$endpointName].Drop()
                            }
                            catch {
                                $copyEndpointStatus.Status = "Failed"
                                $copyEndpointStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
                                
                                Stop-Function -Message "Issue dropping server endpoint." -Target $endpointName -ErrorRecord $_ -Continue
                            }
                        }
                    }
                }
                
                if ($Pscmdlet.ShouldProcess($destinstance, "Creating server endpoint $endpointName.")) {
                    try {
                        Write-Message -Level Verbose -Message "Copying server endpoint $endpointName."
                        $destServer.Query($currentEndpoint.Script()) | Out-Null
                        
                        $copyEndpointStatus.Status = "Successful"
                        $copyEndpointStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
                    }
                    catch {
                        $copyEndpointStatus.Status = "Failed"
                        $copyEndpointStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
                        
                        Stop-Function -Message "Issue creating server endpoint." -Target $endpointName -ErrorRecord $_
                    }
                }
            }
        }
    }
    end {
        Test-DbaDeprecation -DeprecatedOn "1.0.0" -EnableException:$false -Alias Copy-SqlEndpoint
    }
}
function Copy-DbaExtendedEvent {
    <#
        .SYNOPSIS
            Migrates SQL Extended Event Sessions except the two default sessions, AlwaysOn_health and system_health.

        .DESCRIPTION
            Migrates SQL Extended Event Sessions except the two default sessions, AlwaysOn_health and system_health.

            By default, all non-system Extended Events are migrated.

        .PARAMETER Source
            Source SQL Server. You must have sysadmin access and server version must be SQL Server version 2000 or higher.

        .PARAMETER SourceSqlCredential
            Login to the target instance using alternative credentials. Windows and SQL Authentication supported. Accepts credential objects (Get-Credential)

        .PARAMETER Destination
            Destination SQL Server. You must have sysadmin access and the server must be SQL Server 2000 or higher.

        .PARAMETER DestinationSqlCredential
            Login to the target instance using alternative credentials. Windows and SQL Authentication supported. Accepts credential objects (Get-Credential)

        .PARAMETER XeSession
            The Extended Event Session(s) to process. This list is auto-populated from the server. If unspecified, all Extended Event Sessions will be processed.

        .PARAMETER ExcludeXeSession
            The Extended Event Session(s) to exclude. This list is auto-populated from the server.

        .PARAMETER WhatIf
            If this switch is enabled, no actions are performed but informational messages will be displayed that explain what would happen if the command were to run.

        .PARAMETER Confirm
            If this switch is enabled, you will be prompted for confirmation before executing any operations that change state.

        .PARAMETER EnableException
            By default, when something goes wrong we try to catch it, interpret it and give you a friendly warning message.
            This avoids overwhelming you with "sea of red" exceptions, but is inconvenient because it basically disables advanced scripting.
            Using this switch turns this "nice by default" feature off and enables you to catch exceptions with your own try/catch.

        .PARAMETER Force
            If this switch is enabled, existing Extended Events sessions on Destination with matching names from Source will be dropped.

        .NOTES
            Tags: Migration, ExtendedEvent, XEvent
            Author: Chrissy LeMaire (@cl), netnerds.net
            Requires: sysadmin access on SQL Servers

            Website: https://dbatools.io
            Copyright: (C) Chrissy LeMaire, [email protected]
            License: MIT https://opensource.org/licenses/MIT

        .LINK
            https://dbatools.io/Copy-DbaExtendedEvent

        .EXAMPLE
            Copy-DbaExtendedEvent -Source sqlserver2014a -Destination sqlcluster

            Copies all Extended Event sessions from sqlserver2014a to sqlcluster using Windows credentials.

        .EXAMPLE
            Copy-DbaExtendedEvent -Source sqlserver2014a -Destination sqlcluster -SourceSqlCredential $cred

            Copies all Extended Event sessions from sqlserver2014a to sqlcluster using SQL credentials for sqlserver2014a and Windows credentials for sqlcluster.

        .EXAMPLE
            Copy-DbaExtendedEvent -Source sqlserver2014a -Destination sqlcluster -WhatIf

            Shows what would happen if the command were executed.

        .EXAMPLE
            Copy-DbaExtendedEvent -Source sqlserver2014a -Destination sqlcluster -XeSession CheckQueries, MonitorUserDefinedException

            Copies only the Extended Events named CheckQueries and MonitorUserDefinedException from sqlserver2014a to sqlcluster.
    #>
    [CmdletBinding(DefaultParameterSetName = "Default", SupportsShouldProcess = $true)]
    param (
        [parameter(Mandatory = $true)]
        [DbaInstanceParameter]$Source,
        [parameter(Mandatory = $true)]
        [DbaInstanceParameter[]]$Destination,
        [PSCredential]
        $SourceSqlCredential,
        [PSCredential]
        $DestinationSqlCredential,
        [object[]]$XeSession,
        [object[]]$ExcludeXeSession,
        [switch]$Force,
        [Alias('Silent')]
        [switch]$EnableException
    )
    begin {
        try {
            Write-Message -Level Verbose -Message "Connecting to $Source"
            $sourceServer = Connect-SqlInstance -SqlInstance $Source -SqlCredential $SourceSqlCredential -MinimumVersion 10
        }
        catch {
            Stop-Function -Message "Failure" -Category ConnectionError -ErrorRecord $_ -Target $Source
            return
        }
        $sourceSqlConn = $sourceServer.ConnectionContext.SqlConnectionObject
        $sourceSqlStoreConnection = New-Object Microsoft.SqlServer.Management.Sdk.Sfc.SqlStoreConnection $sourceSqlConn
        $sourceStore = New-Object  Microsoft.SqlServer.Management.XEvent.XEStore $sourceSqlStoreConnection
        $storeSessions = $sourceStore.Sessions | Where-Object { $_.Name -notin 'AlwaysOn_health', 'system_health' }
        if ($XeSession) {
            $storeSessions = $storeSessions | Where-Object Name -In $XeSession
        }
        if ($ExcludeXeSession) {
            $storeSessions = $storeSessions | Where-Object Name -NotIn $ExcludeXeSession
        }
    }
    process {
        if (Test-FunctionInterrupt) { return }
        foreach ($destinstance in $Destination) {
            try {
                Write-Message -Level Verbose -Message "Connecting to $destinstance"
                $destServer = Connect-SqlInstance -SqlInstance $destinstance -SqlCredential $DestinationSqlCredential -MinimumVersion 10
            }
            catch {
                Stop-Function -Message "Failure" -Category ConnectionError -ErrorRecord $_ -Target $destinstance -Continue
            }
            
            $destSqlConn = $destServer.ConnectionContext.SqlConnectionObject
            $destSqlStoreConnection = New-Object Microsoft.SqlServer.Management.Sdk.Sfc.SqlStoreConnection $destSqlConn
            $destStore = New-Object  Microsoft.SqlServer.Management.XEvent.XEStore $destSqlStoreConnection
            
            Write-Message -Level Verbose -Message "Migrating sessions."
            foreach ($session in $storeSessions) {
                $sessionName = $session.Name
                
                $copyXeSessionStatus = [pscustomobject]@{
                    SourceServer = $sourceServer.Name
                    DestinationServer = $destServer.Name
                    Name         = $sessionName
                    Type         = "Extended Event"
                    Status       = $null
                    Notes        = $null
                    DateTime     = [DbaDateTime](Get-Date)
                }
                
                if ($null -ne $destStore.Sessions[$sessionName]) {
                    if ($force -eq $false) {
                        if ($Pscmdlet.ShouldProcess($destinstance, "Extended Event Session '$sessionName' was skipped because it already exists on $destinstance.")) {
                            $copyXeSessionStatus.Status = "Skipped"
                            $copyXeSessionStatus.Notes = "Already exists"
                            $copyXeSessionStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
                            
                            Write-Message -Level Verbose -Message "Extended Event Session '$sessionName' was skipped because it already exists on $destinstance."
                            Write-Message -Level Verbose -Message "Use -Force to drop and recreate."
                        }
                        continue
                    }
                    else {
                        if ($Pscmdlet.ShouldProcess($destinstance, "Attempting to drop $sessionName")) {
                            Write-Message -Level Verbose -Message "Extended Event Session '$sessionName' exists on $destinstance."
                            Write-Message -Level Verbose -Message "Force specified. Dropping $sessionName."
                            
                            try {
                                $destStore.Sessions[$sessionName].Drop()
                            }
                            catch {
                                $copyXeSessionStatus.Status = "Failed"
                                $copyXeSessionStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
                                
                                Stop-Function -Message "Unable to drop session. Moving on." -Target $sessionName -ErrorRecord $_ -Continue
                            }
                        }
                    }
                }
                
                if ($Pscmdlet.ShouldProcess($destinstance, "Migrating session $sessionName")) {
                    try {
                        $sql = $session.ScriptCreate().GetScript() | Out-String
                        
                        Write-Message -Level Debug -Message $sql
                        Write-Message -Level Verbose -Message "Migrating session $sessionName."
                        $null = $destServer.Query($sql)
                        
                        if ($session.IsRunning -eq $true) {
                            $destStore.Sessions.Refresh()
                            $destStore.Sessions[$sessionName].Start()
                        }
                        
                        $copyXeSessionStatus.Status = "Successful"
                        $copyXeSessionStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
                    }
                    catch {
                        $copyXeSessionStatus.Status = "Failed"
                        $copyXeSessionStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
                        
                        Stop-Function -Message "Unable to create session." -Target $sessionName -ErrorRecord $_
                    }
                }
            }
        }
    }
    end {
        Test-DbaDeprecation -DeprecatedOn "1.0.0" -EnableException:$false -Alias Copy-SqlExtendedEvent
    }
}
function Copy-DbaLinkedServer {
    <#
        .SYNOPSIS
            Copy-DbaLinkedServer migrates Linked Servers from one SQL Server to another. Linked Server logins and passwords are migrated as well.

        .DESCRIPTION
            By using password decryption techniques provided by Antti Rantasaari (NetSPI, 2014), this script migrates SQL Server Linked Servers from one server to another, while maintaining username and password.

            Credit: https://blog.netspi.com/decrypting-mssql-database-link-server-passwords/
            License: BSD 3-Clause http://opensource.org/licenses/BSD-3-Clause

        .PARAMETER Source
            Source SQL Server (2005 and above). You must have sysadmin access to both SQL Server and Windows.

        .PARAMETER SourceSqlCredential
            Login to the target instance using alternative credentials. Windows and SQL Authentication supported. Accepts credential objects (Get-Credential)

        .PARAMETER Destination
            Destination SQL Server (2005 and above). You must have sysadmin access to both SQL Server and Windows.

        .PARAMETER DestinationSqlCredential
            Login to the target instance using alternative credentials. Windows and SQL Authentication supported. Accepts credential objects (Get-Credential)

        .PARAMETER LinkedServer
            The linked server(s) to process - this list is auto-populated from the server. If unspecified, all linked servers will be processed.

        .PARAMETER ExcludeLinkedServer
            The linked server(s) to exclude - this list is auto-populated from the server

        .PARAMETER UpgradeSqlClient
            Upgrade any SqlClient Linked Server to the current Version

        .PARAMETER WhatIf
            Shows what would happen if the command were to run. No actions are actually performed.

        .PARAMETER Confirm
            Prompts you for confirmation before executing any changing operations within the command.

        .PARAMETER Force
            By default, if a Linked Server exists on the source and destination, the Linked Server is not copied over. Specifying -force will drop and recreate the Linked Server on the Destination server.

        .PARAMETER EnableException
            By default, when something goes wrong we try to catch it, interpret it and give you a friendly warning message.
            This avoids overwhelming you with "sea of red" exceptions, but is inconvenient because it basically disables advanced scripting.
            Using this switch turns this "nice by default" feature off and enables you to catch exceptions with your own try/catch.

        .NOTES
            Tags: WSMan, Migration, LinkedServer
            Author: Chrissy LeMaire (@cl), netnerds.net
            Requires: sysadmin access on SQL Servers, Remote Registry & Remote Administration enabled and accessible on source server.

            Limitations: Hasn't been tested thoroughly. Works on Win8.1 and SQL Server 2012 & 2014 so far.
            This just copies the SQL portion. It does not copy files (ie. a local SQLite database, or Microsoft Access DB), nor does it configure ODBC entries.

        .LINK
            https://dbatools.io/Copy-DbaLinkedServer

        .EXAMPLE
            Copy-DbaLinkedServer -Source sqlserver2014a -Destination sqlcluster

            Description
            Copies all SQL Server Linked Servers on sqlserver2014a to sqlcluster. If Linked Server exists on destination, it will be skipped.

        .EXAMPLE
            Copy-DbaLinkedServer -Source sqlserver2014a -Destination sqlcluster -LinkedServer SQL2K5,SQL2k -Force

            Description
            Copies over two SQL Server Linked Servers (SQL2K and SQL2K2) from sqlserver to sqlcluster. If the credential already exists on the destination, it will be dropped.
    #>
    [CmdletBinding(DefaultParameterSetName = "Default", SupportsShouldProcess = $true)]
    Param (
        [parameter(Mandatory = $true)]
        [DbaInstanceParameter]$Source,
        [PSCredential]$SourceSqlCredential,
        [parameter(Mandatory = $true)]
        [DbaInstanceParameter[]]$Destination,
        [PSCredential]$DestinationSqlCredential,
        [object[]]$LinkedServer,
        [object[]]$ExcludeLinkedServer,
        [switch]$UpgradeSqlClient,
        [switch]$Force,
        [Alias('Silent')]
        [switch]$EnableException
    )
    begin {
        $null = Test-ElevationRequirement -ComputerName $Source.ComputerName
        function Copy-DbaLinkedServers {
            param (
                [string[]]$LinkedServer,
                [bool]$force
            )

            Write-Message -Level Verbose -Message "Collecting Linked Server logins and passwords on $($sourceServer.Name)."
            $sourcelogins = Get-DecryptedObject -SqlInstance $sourceServer -Type LinkedServer

            $serverlist = $sourceServer.LinkedServers

            if ($LinkedServer) {
                $serverlist = $serverlist | Where-Object Name -In $LinkedServer
            }
            if ($ExcludeLinkedServer) {
                $serverList = $serverlist | Where-Object Name -NotIn $ExcludeLinkedServer
            }

            foreach ($currentLinkedServer in $serverlist) {
                $provider = $currentLinkedServer.ProviderName
                try {
                    $destServer.LinkedServers.Refresh()
                    $destServer.LinkedServers.LinkedServerLogins.Refresh()
                }
                catch { }

                $linkedServerName = $currentLinkedServer.Name

                $copyLinkedServer = [pscustomobject]@{
                    SourceServer      = $sourceServer.Name
                    DestinationServer = $destServer.Name
                    Name              = $linkedServerName
                    Type              = "Linked Server"
                    Status            = $null
                    Notes             = $provider
                    DateTime          = [DbaDateTime](Get-Date)
                }

                # This does a check to warn of missing OleDbProviderSettings but should only be checked on SQL on Windows
                if ($destServer.Settings.OleDbProviderSettings.Name.Length -ne 0) {
                    if (!$destServer.Settings.OleDbProviderSettings.Name -contains $provider -and !$provider.StartsWith("SQLN")) {
                        $copyLinkedServer.Status = "Skipped"
                        $copyLinkedServer.Notes = "Missing provider"
                        $copyLinkedServer | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject

                        Write-Message -Level Verbose -Message "$($destServer.Name) does not support the $provider provider. Skipping $linkedServerName."
                        continue
                    }
                }

                if ($null -ne $destServer.LinkedServers[$linkedServerName]) {
                    if (!$force) {
                        if ($Pscmdlet.ShouldProcess($destinstance, "$linkedServerName exists $($destServer.Name). Skipping.")) {
                            $copyLinkedServer.Status = "Skipped"
                            $copyLinkedServer | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
                            
                            Write-Message -Level Verbose -Message "$linkedServerName exists $($destServer.Name). Skipping."
                        }
                        continue
                    }
                    else {
                        if ($Pscmdlet.ShouldProcess($destinstance, "Dropping $linkedServerName")) {
                            if ($currentLinkedServer.Name -eq 'repl_distributor') {
                                Write-Message -Level Verbose -Message "repl_distributor cannot be dropped. Not going to try."
                                continue
                            }

                            $destServer.LinkedServers[$linkedServerName].Drop($true)
                            $destServer.LinkedServers.refresh()
                        }
                    }
                }

                Write-Message -Level Verbose -Message "Attempting to migrate: $linkedServerName."
                If ($Pscmdlet.ShouldProcess($destinstance, "Migrating $linkedServerName")) {
                    try {
                        $sql = $currentLinkedServer.Script() | Out-String
                        Write-Message -Level Debug -Message $sql

                        if ($UpgradeSqlClient -and $sql -match "sqlncli") {
                            $destProviders = $destServer.Settings.OleDbProviderSettings | Where-Object { $_.Name -like 'SQLNCLI*' }
                            $newProvider = $destProviders | Sort-Object Name -Descending | Select-Object -First 1 -ExpandProperty Name

                            Write-Message -Level Verbose -Message "Changing sqlncli to $newProvider"
                            $sql = $sql -replace ("sqlncli[0-9]+", $newProvider)
                        }

                        $destServer.Query($sql)
                        $destServer.LinkedServers.Refresh()
                        Write-Message -Level Verbose -Message "$linkedServerName successfully copied."

                        $copyLinkedServer.Status = "Successful"
                        $copyLinkedServer | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
                    }
                    catch {
                        $copyLinkedServer.Status = "Failed"
                        $copyLinkedServer | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject

                        Stop-Function -Message "Issue adding linked server $destServer." -Target $linkedServerName -InnerErrorRecord $_
                        $skiplogins = $true
                    }
                }

                if ($skiplogins -ne $true) {
                    $destlogins = $destServer.LinkedServers[$linkedServerName].LinkedServerLogins
                    $lslogins = $sourcelogins | Where-Object { $_.Name -eq $linkedServerName }

                    foreach ($login in $lslogins) {
                        if ($Pscmdlet.ShouldProcess($destinstance, "Migrating $($login.Login)")) {
                            $currentlogin = $destlogins | Where-Object { $_.RemoteUser -eq $login.Identity }

                            $copyLinkedServer.Type = $login.Identity

                            if ($currentlogin.RemoteUser.length -ne 0) {
                                try {
                                    $currentlogin.SetRemotePassword($login.Password)
                                    $currentlogin.Alter()

                                    $copyLinkedServer.Status = "Successful"
                                    $copyLinkedServer | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
                                }
                                catch {
                                    $copyLinkedServer.Status = "Failed"
                                    $copyLinkedServer | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject

                                    Stop-Function -Message "Failed to copy login." -Target $login -InnerErrorRecord $_
                                }
                            }
                        }
                    }
                }
            }
        }
        
        if ($null -ne $SourceSqlCredential.Username) {
            Write-Message -Level Verbose -Message "You are using a SQL Credential. Note that this script requires Windows Administrator access on the source server. Attempting with $($SourceSqlCredential.Username)."
        }
        try {
            Write-Message -Level Verbose -Message "Connecting to $Source"
            $sourceServer = Connect-SqlInstance -SqlInstance $Source -SqlCredential $SourceSqlCredential
            return
        }
        catch {
            Stop-Function -Message "Failure" -Category ConnectionError -ErrorRecord $_ -Target $Source
            return
        }
        if (!(Test-SqlSa -SqlInstance $sourceServer -SqlCredential $SourceSqlCredential)) {
            Stop-Function -Message "Not a sysadmin on $source. Quitting." -Target $sourceServer
            return
        }
        Write-Message -Level Verbose -Message "Getting NetBios name for $source."
        $sourceNetBios = Resolve-NetBiosName $sourceserver
        
        Write-Message -Level Verbose -Message "Checking if Remote Registry is enabled on $source."
        try {
            Invoke-Command2 -Raw -Credential $Credential -ComputerName $sourceNetBios -ScriptBlock { Get-ItemProperty -Path "HKLM:\SOFTWARE\" } -ErrorAction Stop
        }
        catch {
            Stop-Function -Message "Can't connect to registry on $source." -Target $sourceNetBios -ErrorRecord $_
            return
        }
    }
    process {
        if (Test-FunctionInterrupt) { return }
        
        foreach ($destinstance in $Destination) {
            try {
                Write-Message -Level Verbose -Message "Connecting to $destinstance"
                $destServer = Connect-SqlInstance -SqlInstance $destinstance -SqlCredential $DestinationSqlCredential
            }
            catch {
                Stop-Function -Message "Failure" -Category ConnectionError -ErrorRecord $_ -Target $destinstance -Continue
            }
            if (!(Test-SqlSa -SqlInstance $destServer -SqlCredential $DestinationSqlCredential)) {
                Stop-Function -Message "Not a sysadmin on $destinstance" -Target $destServer -Continue
            }
            
            # Magic happens here
            Copy-DbaLinkedServers $LinkedServer -Force:$force
        }
    }
    end {
        Test-DbaDeprecation -DeprecatedOn "1.0.0" -EnableException:$false -Alias Copy-SqlLinkedServer
    }
}
function Copy-DbaLogin {
    <#
        .SYNOPSIS
            Migrates logins from source to destination SQL Servers. Supports SQL Server versions 2000 and newer.

        .DESCRIPTION
            SQL Server 2000: Migrates logins with SIDs, passwords, server roles and database roles.

            SQL Server 2005 & newer: Migrates logins with SIDs, passwords, defaultdb, server roles & securables, database permissions & securables, login attributes (enforce password policy, expiration, etc.)

            The login hash algorithm changed in SQL Server 2012, and is not backwards compatible with previous SQL Server versions. This means that while SQL Server 2000 logins can be migrated to SQL Server 2012, logins created in SQL Server 2012 can only be migrated to SQL Server 2012 and above.

        .PARAMETER Source
            Source SQL Server. You must have sysadmin access and server version must be SQL Server version 2000 or higher.

        .PARAMETER SourceSqlCredential
            Login to the target instance using alternative credentials. Windows and SQL Authentication supported. Accepts credential objects (Get-Credential)

        .PARAMETER Destination
            Destination SQL Server. You must have sysadmin access and the server must be SQL Server 2000 or higher.

        .PARAMETER DestinationSqlCredential
            Login to the target instance using alternative credentials. Windows and SQL Authentication supported. Accepts credential objects (Get-Credential)

        .PARAMETER Login
            The login(s) to process. Options for this list are auto-populated from the server. If unspecified, all logins will be processed.

        .PARAMETER ExcludeLogin
            The login(s) to exclude. Options for this list are auto-populated from the server.

        .PARAMETER ExcludeSystemLogin
            If this switch is enabled, NT SERVICE accounts will be skipped.

        .PARAMETER SyncOnly
            If this switch is enabled, only SQL Server login permissions, roles, etc. will be synced. Logins and users will not be added or dropped.  If a matching Login does not exist on the destination, the Login will be skipped.
            Credential removal is not currently supported for this parameter.

        .PARAMETER SyncSaName
            If this switch is enabled, the name of the sa account will be synced between Source and Destination

        .PARAMETER OutFile
            Calls Export-SqlLogin and exports all logins to a T-SQL formatted file. This does not perform a copy, so no destination is required.

        .PARAMETER InputObject
            Takes the parameters required from a Login object that has been piped into the command

        .PARAMETER LoginRenameHashtable
            Pass a hash table into this parameter to be passed into Rename-DbaLogin to update the Login and mappings after the Login is completed.

        .PARAMETER KillActiveConnection
            If this switch and -Force are enabled, all active connections and sessions on Destination will be killed.

            A login cannot be dropped when it has active connections on the instance.

        .PARAMETER WhatIf
            If this switch is enabled, no actions are performed but informational messages will be displayed that explain what would happen if the command were to run.

        .PARAMETER Confirm
            If this switch is enabled, you will be prompted for confirmation before executing any operations that change state.

        .PARAMETER Force
            If this switch is enabled, the Login(s) will be dropped and recreated on Destination. Logins that own Agent jobs cannot be dropped at this time.

        .PARAMETER EnableException
            By default, when something goes wrong we try to catch it, interpret it and give you a friendly warning message.
            This avoids overwhelming you with "sea of red" exceptions, but is inconvenient because it basically disables advanced scripting.
            Using this switch turns this "nice by default" feature off and enables you to catch exceptions with your own try/catch.

        .NOTES
            Tags: Migration, Login
            Author: Chrissy LeMaire (@cl), netnerds.net
            Requires: sysadmin access on SQL Servers

            Website: https://dbatools.io
            Copyright: (C) Chrissy LeMaire, [email protected]
            License: MIT https://opensource.org/licenses/MIT

        .LINK
            https://dbatools.io/Copy-DbaLogin

        .EXAMPLE
            Copy-DbaLogin -Source sqlserver2014a -Destination sqlcluster -Force

            Copies all logins from Source Destination. If a SQL Login on Source exists on the Destination, the Login on Destination will be dropped and recreated.

            If active connections are found for a login, the copy of that Login will fail as it cannot be dropped.

        .EXAMPLE
            Copy-DbaLogin -Source sqlserver2014a -Destination sqlcluster -Force -KillActiveConnection

            Copies all logins from Source Destination. If a SQL Login on Source exists on the Destination, the Login on Destination will be dropped and recreated.

            If any active connections are found they will be killed.

        .EXAMPLE
            Copy-DbaLogin -Source sqlserver2014a -Destination sqlcluster -Exclude realcajun -SourceSqlCredential $scred -DestinationSqlCredential $dcred

            Copies all Logins from Source to Destination except for realcajun using SQL Authentication to connect to both instances.

            If a Login already exists on the destination, it will not be migrated.

        .EXAMPLE
            Copy-DbaLogin -Source sqlserver2014a -Destination sqlcluster -Login realcajun, netnerds -force

            Copies ONLY Logins netnerds and realcajun. If Login realcajun or netnerds exists on Destination, the existing Login(s) will be dropped and recreated.

        .EXAMPLE
            Copy-DbaLogin -Source sqlserver2014a -Destination sqlcluster -SyncOnly

            Syncs only SQL Server login permissions, roles, etc. Does not add or drop logins or users.

            If a matching Login does not exist on Destination, the Login will be skipped.

        .EXAMPLE
            Copy-DbaLogin -LoginRenameHashtable @{ "OldUser" ="newlogin" } -Source $Sql01 -Destination Localhost -SourceSqlCredential $sqlcred

            Copies OldUser and then renames it to newlogin.

        .EXAMPLE
            Get-DbaLogin -SqlInstance sql2016 | Out-GridView -Passthru | Copy-DbaLogin -Destination sql2017

            Displays all available logins on sql2016 in a grid view, then copies all selected logins to sql2017.
    #>

    [CmdletBinding(DefaultParameterSetName = "Default", SupportsShouldProcess)]
    Param (
        [parameter(ParameterSetName = "SqlInstance", Mandatory)]
        [DbaInstanceParameter]$Source,
        [PSCredential]$SourceSqlCredential,
        [parameter(Mandatory = $true)]
        [DbaInstanceParameter[]]$Destination,
        [PSCredential]$DestinationSqlCredential,
        [object[]]$Login,
        [object[]]$ExcludeLogin,
        [switch]$ExcludeSystemLogin,
        [switch]$SyncOnly,
        [parameter(ParameterSetName = "Live")]
        [parameter(ParameterSetName = "SqlInstance")]
        [switch]$SyncSaName,
        [parameter(ParameterSetName = "File", Mandatory)]
        [string]$OutFile,
        [parameter(ParameterSetName = "InputObject", ValueFromPipeline)]
        [object]$InputObject,
        [hashtable]$LoginRenameHashtable,
        [switch]$KillActiveConnection,
        [switch]$Force,
        [Alias('Silent')]
        [switch]$EnableException
    )

    begin {
        function Copy-Login {
            foreach ($sourceLogin in $sourceServer.Logins) {
                $userName = $sourceLogin.name
                
                $copyLoginStatus = [pscustomobject]@{
                    SourceServer = $sourceServer.Name
                    DestinationServer = $destServer.Name
                    Type         = "Login - $($sourceLogin.LoginType)"
                    Name         = $userName
                    DestinationLogin = $userName
                    SourceLogin  = $userName
                    Status       = $null
                    Notes        = $null
                    DateTime     = [DbaDateTime](Get-Date)
                }
                
                if ($Login -and $Login -notcontains $userName -or $ExcludeLogin -contains $userName) { continue }

                if ($sourceLogin.id -eq 1) { continue }

                if ($userName.StartsWith("##") -or $userName -eq 'sa') {
                    Write-Message -Level Verbose -Message "Skipping $userName."
                    continue
                }

                $serverName = Resolve-NetBiosName $sourceServer

                $currentLogin = $sourceServer.ConnectionContext.truelogin

                if ($currentLogin -eq $userName -and $force) {
                    if ($Pscmdlet.ShouldProcess("console", "Stating $userName is skipped because it is performing the migration.")) {
                        Write-Message -Level Verbose -Message "Cannot drop login performing the migration. Skipping."
                        $copyLoginStatus.Status = "Skipped"
                        $copyLoginStatus.Notes = "Current login"
                        $copyLoginStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
                    }
                    continue
                }
                
                if (($destServer.LoginMode -ne [Microsoft.SqlServer.Management.Smo.ServerLoginMode]::Mixed) -and ($sourceLogin.LoginType -eq [Microsoft.SqlServer.Management.Smo.LoginType]::SqlLogin)) {
                    Write-Message -Level Verbose -Message "$Destination does not have Mixed Mode enabled. [$userName] is an SQL Login. Enable mixed mode authentication after the migration completes to use this type of login."
                }

                $userBase = ($userName.Split("\")[0]).ToLower()

                if ($serverName -eq $userBase -or $userName.StartsWith("NT ")) {
                    if ($sourceServer.ComputerName -ne $destServer.ComputerName) {
                        if ($Pscmdlet.ShouldProcess("console", "Stating $userName was skipped because it is a local machine name.")) {
                            Write-Message -Level Verbose -Message "$userName was skipped because it is a local machine name."
                            $copyLoginStatus.Status = "Skipped"
                            $copyLoginStatus.Notes = "Local machine name"
                            $copyLoginStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
                        }
                        continue
                    }
                    else {
                        if ($ExcludeSystemLogin) {
                            if ($Pscmdlet.ShouldProcess("console", "$userName was skipped because ExcludeSystemLogin was specified.")) {
                                Write-Message -Level Verbose -Message "$userName was skipped because ExcludeSystemLogin was specified."
                                
                                $copyLoginStatus.Status = "Skipped"
                                $copyLoginStatus.Notes = "System login"
                                $copyLoginStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
                            }
                            continue
                        }
                        
                        if ($Pscmdlet.ShouldProcess("console", "Stating local login $userName since the source and destination server reside on the same machine.")) {
                            Write-Message -Level Verbose -Message "Copying local login $userName since the source and destination server reside on the same machine."
                        }
                    }
                }
                
                if ($null -ne $destServer.Logins.Item($userName) -and !$force) {
                    if ($Pscmdlet.ShouldProcess("console", "Stating $userName is skipped because it exists at destination.")) {
                        Write-Message -Level Verbose -Message "$userName already exists in destination. Use -Force to drop and recreate."
                        $copyLoginStatus.Status = "Skipped"
                        $copyLoginStatus.Notes = "Already exists"
                        $copyLoginStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
                    }
                    continue
                }
                
                if ($null -ne $destServer.Logins.Item($userName) -and $force) {
                    if ($userName -eq $destServer.ServiceAccount) {
                        if ($Pscmdlet.ShouldProcess("console", "$userName is the destination service account. Skipping drop.")) {
                            Write-Message -Level Verbose -Message "$userName is the destination service account. Skipping drop."
                            
                            $copyLoginStatus.Status = "Skipped"
                            $copyLoginStatus.Notes = "Destination service account"
                            $copyLoginStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
                        }
                        continue
                    }
                    
                    if ($Pscmdlet.ShouldProcess($destinstance, "Dropping $userName")) {

                        # Kill connections, delete user
                        Write-Message -Level Verbose -Message "Attempting to migrate $userName"
                        Write-Message -Level Verbose -Message "Force was specified. Attempting to drop $userName on $destinstance."

                        try {
                            $ownedDbs = $destServer.Databases | Where-Object Owner -eq $userName

                            foreach ($ownedDb in $ownedDbs) {
                                Write-Message -Level Verbose -Message "Changing database owner for $($ownedDb.name) from $userName to sa."
                                $ownedDb.SetOwner('sa')
                                $ownedDb.Alter()
                            }

                            $ownedJobs = $destServer.JobServer.Jobs | Where-Object OwnerLoginName -eq $userName

                            foreach ($ownedJob in $ownedJobs) {
                                Write-Message -Level Verbose -Message "Changing job owner for $($ownedJob.name) from $userName to sa."
                                $ownedJob.Set_OwnerLoginName('sa')
                                $ownedJob.Alter()
                            }

                            $activeConnections = $destServer.EnumProcesses() | Where-Object Login -eq $userName

                            if ($activeConnections -and $KillActiveConnection) {
                                if (!$destServer.Logins.Item($userName).IsDisabled) {
                                    $disabled = $true
                                    $destServer.Logins.Item($userName).Disable()
                                }

                                $activeConnections | ForEach-Object { $destServer.KillProcess($_.Spid) }
                                Write-Message -Level Verbose -Message "-KillActiveConnection was provided. There are $($activeConnections.Count) active connections killed."
                                # just in case the kill didn't work, it'll leave behind a disabled account
                                if ($disabled) { $destServer.Logins.Item($userName).Enable() }
                            }
                            elseif ($activeConnections) {
                                Write-Message -Level Verbose -Message "There are $($activeConnections.Count) active connections found for the login $userName. Utilize -KillActiveConnection with -Force to kill the connections."
                            }
                            $destServer.Logins.Item($userName).Drop()

                            Write-Message -Level Verbose -Message "Successfully dropped $userName on $destinstance."
                        }
                        catch {
                            $copyLoginStatus.Status = "Failed"
                            $copyLoginStatus.Notes = (Get-ErrorMessage -Record $_).Message
                            $copyLoginStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject

                            Stop-Function -Message "Could not drop $userName." -Category InvalidOperation -ErrorRecord $_ -Target $destServer -Continue 3>$null
                        }
                    }
                }

                if ($Pscmdlet.ShouldProcess($destinstance, "Adding SQL login $userName")) {

                    Write-Message -Level Verbose -Message "Attempting to add $userName to $destinstance."
                    $destLogin = New-Object Microsoft.SqlServer.Management.Smo.Login($destServer, $userName)

                    Write-Message -Level Verbose -Message "Setting $userName SID to source username SID."
                    $destLogin.Set_Sid($sourceLogin.Get_Sid())

                    $defaultDb = $sourceLogin.DefaultDatabase

                    Write-Message -Level Verbose -Message "Setting login language to $($sourceLogin.Language)."
                    $destLogin.Language = $sourceLogin.Language

                    if ($null -eq $destServer.databases[$defaultDb]) {
                        # we end up here when the default database on source doesn't exist on dest
                        # if source login is a sysadmin, then set the default database to master
                        # if not, set it to tempdb (see #303)
                        $OrigdefaultDb = $defaultDb
                        try { $sourcesysadmins = $sourceServer.roles['sysadmin'].EnumMemberNames() }
                        catch { $sourcesysadmins = $sourceServer.roles['sysadmin'].EnumServerRoleMembers() }
                        if ($sourcesysadmins -contains $userName) {
                            $defaultDb = "master"
                        }
                        else {
                            $defaultDb = "tempdb"
                        }
                        Write-Message -Level Verbose -Message "$OrigdefaultDb does not exist on destination. Setting defaultdb to $defaultDb."
                    }

                    Write-Message -Level Verbose -Message "Set $userName defaultdb to $defaultDb."
                    $destLogin.DefaultDatabase = $defaultDb

                    $checkexpiration = "ON"; $checkpolicy = "ON"

                    if ($sourceLogin.PasswordPolicyEnforced -eq $false) { $checkpolicy = "OFF" }

                    if (!$sourceLogin.PasswordExpirationEnabled) { $checkexpiration = "OFF" }

                    $destLogin.PasswordPolicyEnforced = $sourceLogin.PasswordPolicyEnforced
                    $destLogin.PasswordExpirationEnabled = $sourceLogin.PasswordExpirationEnabled

                    # Attempt to add SQL Login User
                    if ($sourceLogin.LoginType -eq "SqlLogin") {
                        $destLogin.LoginType = "SqlLogin"
                        $sourceLoginname = $sourceLogin.name

                        switch ($sourceServer.versionMajor) {
                            0 { $sql = "SELECT CONVERT(VARBINARY(256),password) as hashedpass FROM master.dbo.syslogins WHERE loginname='$sourceLoginname'" }
                            8 { $sql = "SELECT CONVERT(VARBINARY(256),password) as hashedpass FROM dbo.syslogins WHERE name='$sourceLoginname'" }
                            9 { $sql = "SELECT CONVERT(VARBINARY(256),password_hash) as hashedpass FROM sys.sql_logins where name='$sourceLoginname'" }
                            default {
                                $sql = "SELECT CAST(CONVERT(VARCHAR(256), CAST(LOGINPROPERTY(name,'PasswordHash')
                        AS VARBINARY(256)), 1) AS NVARCHAR(max)) AS hashedpass FROM sys.server_principals
                        WHERE principal_id = $($sourceLogin.id)"
                            }
                        }

                        try {
                            $hashedPass = $sourceServer.ConnectionContext.ExecuteScalar($sql)
                        }
                        catch {
                            $hashedPassDt = $sourceServer.Databases['master'].ExecuteWithResults($sql)
                            $hashedPass = $hashedPassDt.Tables[0].Rows[0].Item(0)
                        }

                        if ($hashedPass.GetType().Name -ne "String") {
                            $passString = "0x"; $hashedPass | ForEach-Object { $passString += ("{0:X}" -f $_).PadLeft(2, "0") }
                            $hashedPass = $passString
                        }

                        try {
                            $destLogin.Create($hashedPass, [Microsoft.SqlServer.Management.Smo.LoginCreateOptions]::IsHashed)
                            $destLogin.Refresh()
                            Write-Message -Level Verbose -Message "Successfully added $userName to $destinstance."

                            $copyLoginStatus.Status = "Successful"
                            $copyLoginStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
                        }
                        catch {
                            try {
                                $sid = "0x"; $sourceLogin.sid | ForEach-Object { $sid += ("{0:X}" -f $_).PadLeft(2, "0") }
                                $sql = "CREATE LOGIN [$userName] WITH PASSWORD = $hashedPass HASHED, SID = $sid,
                                                DEFAULT_DATABASE = [$defaultDb], CHECK_POLICY = $checkpolicy,
                                                CHECK_EXPIRATION = $checkexpiration, DEFAULT_LANGUAGE = [$($sourceLogin.Language)]"

                                $null = $destServer.Query($sql)

                                $destLogin = $destServer.logins[$userName]
                                Write-Message -Level Verbose -Message "Successfully added $userName to $destinstance."

                                $copyLoginStatus.Status = "Successful"
                                $copyLoginStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject

                            }
                            catch {
                                $copyLoginStatus.Status = "Failed"
                                $copyLoginStatus.Notes = (Get-ErrorMessage -Record $_).Message
                                $copyLoginStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject

                                Stop-Function -Message "Failed to add $userName to $destinstance." -Category InvalidOperation -ErrorRecord $_ -Target $destServer -Continue 3>$null
                            }
                        }
                    }
                    # Attempt to add Windows User
                    elseif ($sourceLogin.LoginType -eq "WindowsUser" -or $sourceLogin.LoginType -eq "WindowsGroup") {
                        Write-Message -Level Verbose -Message "Adding as login type $($sourceLogin.LoginType)"
                        $destLogin.LoginType = $sourceLogin.LoginType

                        Write-Message -Level Verbose -Message "Setting language as $($sourceLogin.Language)"
                        $destLogin.Language = $sourceLogin.Language

                        try {
                            $destLogin.Create()
                            $destLogin.Refresh()
                            Write-Message -Level Verbose -Message "Successfully added $userName to $destinstance."

                            $copyLoginStatus.Status = "Successful"
                            $copyLoginStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject

                        }
                        catch {
                            $copyLoginStatus.Status = "Failed"
                            $copyLoginStatus.Notes = (Get-ErrorMessage -Record $_).Message
                            $copyLoginStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject

                            Stop-Function -Message "Failed to add $userName to $destinstance" -Category InvalidOperation -ErrorRecord $_ -Target $destServer -Continue 3>$null
                        }
                    }
                    # This script does not currently support certificate mapped or asymmetric key users.
                    else {
                        Write-Message -Level Verbose -Message "$($sourceLogin.LoginType) logins not supported. $($sourceLogin.name) skipped."

                        $copyLoginStatus.Status = "Skipped"
                        $copyLoginStatus.Notes = "$($sourceLogin.LoginType) not supported"
                        $copyLoginStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject

                        continue
                    }

                    if ($sourceLogin.IsDisabled) {
                        try {
                            $destLogin.Disable()
                        }
                        catch {
                            $copyLoginStatus.Status = "Successful - but could not disable on destination"
                            $copyLoginStatus.Notes = (Get-ErrorMessage -Record $_).Message
                            $copyLoginStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject

                            Stop-Function -Message "$userName disabled on source, could not be disabled on $destinstance." -Category InvalidOperation -ErrorRecord $_ -Target $destServer  3>$null
                        }
                    }
                    if ($sourceLogin.DenyWindowsLogin) {
                        try {
                            $destLogin.DenyWindowsLogin = $true
                        }
                        catch {
                            $copyLoginStatus.Status = "Successful - but could not deny login on destination"
                            $copyLoginStatus.Notes = (Get-ErrorMessage -Record $_).Message
                            $copyLoginStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject

                            Stop-Function -Message "$userName denied login on source, could not be denied login on $destinstance." -Category InvalidOperation -ErrorRecord $_ -Target $destServer 3>$null
                        }
                    }
                }
                if ($Pscmdlet.ShouldProcess($destinstance, "Updating SQL login $userName permissions")) {
                    Update-SqlPermissions -sourceserver $sourceServer -sourcelogin $sourceLogin -destserver $destServer -destlogin $destLogin
                }

                if ($LoginRenameHashtable.Keys -contains $userName) {
                    $NewLogin = $LoginRenameHashtable[$userName]

                    if ($Pscmdlet.ShouldProcess($destinstance, "Renaming SQL Login $userName to $NewLogin")) {
                        try {
                            Rename-DbaLogin -SqlInstance $destServer -Login $userName -NewLogin $NewLogin

                            $copyLoginStatus.DestinationLogin = $NewLogin
                            $copyLoginStatus.Status = "Successful"
                            $copyLoginStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject

                        }
                        catch {
                            $copyLoginStatus.DestinationLogin = $NewLogin
                            $copyLoginStatus.Status = "Failed to rename"
                            $copyLoginStatus.Notes = (Get-ErrorMessage -Record $_).Message
                            $copyLoginStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject

                            Stop-Function -Message "Issue renaming $userName to $NewLogin" -Category InvalidOperation -ErrorRecord $_ -Target $destServer 3>$null
                        }
                    }
                }
            }
        }
    }
    process {
        if (Test-FunctionInterrupt) { return }
        if ($InputObject) {
            $Source = $InputObject[0].Parent.Name
            $Sourceserver = $InputObject[0].Parent
            $Login = $InputObject.Name
        }
        else {
            try {
                Write-Message -Level Verbose -Message "Connecting to $Source"
                $sourceServer = Connect-SqlInstance -SqlInstance $Source -SqlCredential $SourceSqlCredential
            }
            catch {
                Stop-Function -Message "Failure" -Category ConnectionError -ErrorRecord $_ -Target $Source
                return
            }
        }
        $sourceVersionMajor = $sourceServer.VersionMajor

        if ($OutFile) {
            Export-DbaLogin -SqlInstance $sourceServer -FilePath $OutFile -Login $Login -ExcludeLogin $ExcludeLogin
            continue
        }

        foreach ($destinstance in $Destination) {
            try {
                Write-Message -Level Verbose -Message "Connecting to $destinstance"
                $destServer = Connect-SqlInstance -SqlInstance $destinstance -SqlCredential $DestinationSqlCredential
            }
            catch {
                Stop-Function -Message "Failure" -Category ConnectionError -ErrorRecord $_ -Target $destinstance -Continue
            }
            
            $destVersionMajor = $destServer.VersionMajor
            if ($sourceVersionMajor -gt 10 -and $destVersionMajor -lt 11) {
                Stop-Function -Message "Login migration from version $sourceVersionMajor to $destVersionMajor is not supported." -Category InvalidOperation -ErrorRecord $_ -Target $sourceServer
            }

            if ($sourceVersionMajor -lt 8 -or $destVersionMajor -lt 8) {
                Stop-Function -Message "SQL Server 7 and below are not supported." -Category InvalidOperation -ErrorRecord $_ -Target $sourceServer
            }
            
            if ($SyncOnly) {
                if ($Pscmdlet.ShouldProcess($destinstance, "Syncing $Login permissions")) {
                    Sync-DbaLoginPermission -Source $sourceServer -Destination $destServer -Login $Login -ExcludeLogin $ExcludeLogin
                    continue
                }
            }
            
            Write-Message -Level Verbose -Message "Attempting Login Migration."
            Copy-Login -sourceserver $sourceServer -destserver $destServer -Login $Login -Exclude $ExcludeLogin

            if ($SyncSaName) {
                $sa = $sourceServer.Logins | Where-Object id -eq 1
                $destSa = $destServer.Logins | Where-Object id -eq 1
                $saName = $sa.Name
                if ($saName -ne $destSa.name) {
                    Write-Message -Level Verbose -Message "Changing sa username to match source ($saName)."
                    if ($Pscmdlet.ShouldProcess($destinstance, "Changing sa username to match source ($saName)")) {
                        $destSa.Rename($saName)
                        $destSa.Alter()
                    }
                }
            }
        }
    }
    end {
        Test-DbaDeprecation -DeprecatedOn "1.0.0" -EnableException:$false -Alias Copy-SqlLogin
    }
}
function Copy-DbaQueryStoreConfig {
    <#
        .SYNOPSIS
            Copies the configuration of a Query Store enabled database and sets the copied configuration on other databases.

        .DESCRIPTION
            Copies the configuration of a Query Store enabled database and sets the copied configuration on other databases.

        .PARAMETER Source
            Source SQL Server. You must have sysadmin access and server version must be SQL Server version 2016 or higher.

        .PARAMETER SourceSqlCredential
            Login to the target instance using alternative credentials. Windows and SQL Authentication supported. Accepts credential objects (Get-Credential)

        .PARAMETER SourceDatabase
            Specifies the database to copy the Query Store configuration from.

        .PARAMETER Destination
            Destination SQL Server. You must have sysadmin access and the server must be SQL Server 2016 or higher.

        .PARAMETER DestinationSqlCredential
            Login to the target instance using alternative credentials. Windows and SQL Authentication supported. Accepts credential objects (Get-Credential)

        .PARAMETER DestinationDatabase
            Specifies a list of databases that will receive a copy of the Query Store configuration of the SourceDatabase.

        .PARAMETER Exclude
            Specifies a list of databases which will NOT receive a copy of the Query Store configuration.

        .PARAMETER AllDatabases
            If this switch is enabled, the Query Store configuration will be copied to all databases on the destination instance.

        .PARAMETER WhatIf
            If this switch is enabled, no actions are performed but informational messages will be displayed that explain what would happen if the command were to run.

        .PARAMETER Confirm
            If this switch is enabled, you will be prompted for confirmation before executing any operations that change state.

        .PARAMETER EnableException
            By default, when something goes wrong we try to catch it, interpret it and give you a friendly warning message.
            This avoids overwhelming you with "sea of red" exceptions, but is inconvenient because it basically disables advanced scripting.
            Using this switch turns this "nice by default" feature off and enables you to catch exceptions with your own try/catch.

        .NOTES
            Author: Enrico van de Laar ( @evdlaar )
            Tags: QueryStore

            Website: https://dbatools.io
            Copyright: (C) Chrissy LeMaire, [email protected]
            License: MIT https://opensource.org/licenses/MIT

        .LINK
            https://dbatools.io/Copy-QueryStoreConfig

        .EXAMPLE
            Copy-DbaQueryStoreConfig -Source ServerA\SQL -SourceDatabase AdventureWorks -Destination ServerB\SQL -AllDatabases

            Copy the Query Store configuration of the AdventureWorks database in the ServerA\SQL instance and apply it on all user databases in the ServerB\SQL Instance.

        .EXAMPLE
            Copy-DbaQueryStoreConfig -Source ServerA\SQL -SourceDatabase AdventureWorks -Destination ServerB\SQL -DestinationDatabase WorldWideTraders

            Copy the Query Store configuration of the AdventureWorks database in the ServerA\SQL instance and apply it to the WorldWideTraders database in the ServerB\SQL Instance.
    #>
    [CmdletBinding(SupportsShouldProcess = $true)]
    param (
        [parameter(Mandatory = $true, ValueFromPipeline = $true)]
        [DbaInstanceParameter]$Source,
        [PSCredential]$SourceSqlCredential,
        [parameter(Mandatory = $true, ValueFromPipeline = $true)]
        [object]$SourceDatabase,
        [parameter(Mandatory = $true, ValueFromPipeline = $true)]
        [DbaInstanceParameter[]]$Destination,
        [PSCredential]$DestinationSqlCredential,
        [object[]]$DestinationDatabase,
        [object[]]$Exclude,
        [switch]$AllDatabases,
        [Alias('Silent')]
        [switch]$EnableException
    )

    begin {
        Write-Message -Message "Connecting to source: $Source." -Level Verbose
        try {
            $sourceServer = Connect-SqlInstance -SqlInstance $Source -SqlCredential $SourceSqlCredential
        }
        catch {
            Stop-Function -Message "Can't connect to $Source." -ErrorRecord $_ -Target $Source
            return
        }
    }
    
    process {
        if (Test-FunctionInterrupt) { return }
        foreach ($destinstance in $Destination) {
            # Grab the Query Store configuration from the SourceDatabase through the Get-DbaQueryStoreConfig function
            $SourceQSConfig = Get-DbaDbQueryStoreOptions -SqlInstance $sourceServer -Database $SourceDatabase
            
            if (!$DestinationDatabase -and !$Exclude -and !$AllDatabases) {
                Stop-Function -Message "You must specify databases to execute against using either -DestinationDatabase, -Exclude or -AllDatabases." -Continue
            }
            
            foreach ($destinationServer in $destinstance) {
                
                try {
                    Write-Message -Level Verbose -Message "Connecting to $destinstance"
                    $destServer = Connect-SqlInstance -SqlInstance $destinstance -SqlCredential $DestinationSqlCredential
                }
                catch {
                    Stop-Function -Message "Failure" -Category ConnectionError -ErrorRecord $_ -Target $destinstance -Continue
                }
                
                # We have to exclude all the system databases since they cannot have the Query Store feature enabled
                $dbs = Get-DbaDatabase -SqlInstance $destServer -ExcludeAllSystemDb
                
                if ($DestinationDatabase.count -gt 0) {
                    $dbs = $dbs | Where-Object { $DestinationDatabase -contains $_.Name }
                }
                
                if ($Exclude.count -gt 0) {
                    $dbs = $dbs | Where-Object { $exclude -notcontains $_.Name }
                }
                
                if ($dbs.count -eq 0) {
                    Stop-Function -Message "No matching databases found. Check the spelling and try again." -Continue
                }
                
                foreach ($db in $dbs) {
                    # skipping the database if the source and destination are the same instance
                    if (($sourceServer.Name -eq $destinationServer) -and ($SourceDatabase -eq $db.Name)) {
                        continue
                    }
                    Write-Message -Message "Processing destination database: $db on $destinationServer." -Level Verbose
                    $copyQueryStoreStatus = [pscustomobject]@{
                        SourceServer = $sourceServer.name
                        SourceDatabase = $SourceDatabase
                        DestinationServer = $destinationServer
                        Name         = $db.name
                        Type         = "QueryStore Configuration"
                        Status       = $null
                        DateTime     = [Sqlcollaborative.Dbatools.Utility.DbaDateTime](Get-Date)
                    }
                    
                    if ($db.IsAccessible -eq $false) {
                        $copyQueryStoreStatus.Status = "Skipped"
                        Stop-Function -Message "The database $db on server $destinationServer is not accessible. Skipping database." -Continue
                    }
                    
                    Write-Message -Message "Executing Set-DbaQueryStoreConfig." -Level Verbose
                    # Set the Query Store configuration through the Set-DbaQueryStoreConfig function
                    try {
                        $null = Set-DbaDbQueryStoreOptions -SqlInstance $destinationServer -SqlCredential $DestinationSqlCredential `
                                                        -Database $db.name `
                                                        -State $SourceQSConfig.ActualState `
                                                        -FlushInterval $SourceQSConfig.FlushInterval `
                                                        -CollectionInterval $SourceQSConfig.CollectionInterval `
                                                        -MaxSize $SourceQSConfig.MaxSize `
                                                        -CaptureMode $SourceQSConfig.CaptureMode `
                                                        -CleanupMode $SourceQSConfig.CleanupMode `
                                                        -StaleQueryThreshold $SourceQSConfig.StaleQueryThreshold
                        $copyQueryStoreStatus.Status = "Successful"
                    }
                    catch {
                        $copyQueryStoreStatus.Status = "Failed"
                        Stop-Function -Message "Issue setting Query Store on $db." -Target $db -ErrorRecord $_ -Continue
                    }
                    $copyQueryStoreStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
                }
            }
        }
    }
}
function Copy-DbaResourceGovernor {
    <#
        .SYNOPSIS
            Migrates Resource Pools

        .DESCRIPTION
            By default, all non-system resource pools are migrated. If the pool already exists on the destination, it will be skipped unless -Force is used.

            The -ResourcePool parameter is auto-populated for command-line completion and can be used to copy only specific objects.

        .PARAMETER Source
            Source SQL Server. You must have sysadmin access and server version must be SQL Server version 2008 or higher.

        .PARAMETER SourceSqlCredential
            Login to the target instance using alternative credentials. Windows and SQL Authentication supported. Accepts credential objects (Get-Credential)

        .PARAMETER Destination
            Destination SQL Server. You must have sysadmin access and the server must be SQL Server 2008 or higher.

        .PARAMETER DestinationSqlCredential
            Login to the target instance using alternative credentials. Windows and SQL Authentication supported. Accepts credential objects (Get-Credential)

        .PARAMETER ResourcePool
            Specifies the resource pool(s) to process. Options for this list are auto-populated from the server. If unspecified, all resource pools will be processed.

        .PARAMETER ExcludeResourcePool
            Specifies the resource pool(s) to exclude. Options for this list are auto-populated from the server

        .PARAMETER WhatIf
            If this switch is enabled, no actions are performed but informational messages will be displayed that explain what would happen if the command were to run.

        .PARAMETER Confirm
            If this switch is enabled, you will be prompted for confirmation before executing any operations that change state.

        .PARAMETER Force
            If this switch is enabled, the policies will be dropped and recreated on Destination.

        .PARAMETER EnableException
            By default, when something goes wrong we try to catch it, interpret it and give you a friendly warning message.
            This avoids overwhelming you with "sea of red" exceptions, but is inconvenient because it basically disables advanced scripting.
            Using this switch turns this "nice by default" feature off and enables you to catch exceptions with your own try/catch.

        .NOTES
            Tags: Migration, ResourceGovernor
            Author: Chrissy LeMaire (@cl), netnerds.net
            Requires: sysadmin access on SQL Servers

            Website: https://dbatools.io
            Copyright: (C) Chrissy LeMaire, [email protected]
            License: MIT https://opensource.org/licenses/MIT

        .LINK
            https://dbatools.io/Copy-DbaResourceGovernor

        .EXAMPLE
            Copy-DbaResourceGovernor -Source sqlserver2014a -Destination sqlcluster

            Copies all extended event policies from sqlserver2014a to sqlcluster using Windows credentials to connect to the SQL Server instances..

        .EXAMPLE
            Copy-DbaResourceGovernor -Source sqlserver2014a -Destination sqlcluster -SourceSqlCredential $cred

            Copies all extended event policies from sqlserver2014a to sqlcluster using SQL credentials to connect to sqlserver2014a and Windows credentials to connect to sqlcluster.

        .EXAMPLE
            Copy-DbaResourceGovernor -Source sqlserver2014a -Destination sqlcluster -WhatIf

            Shows what would happen if the command were executed.
    #>
    [CmdletBinding(DefaultParameterSetName = "Default", SupportsShouldProcess = $true)]
    param (
        [parameter(Mandatory = $true)]
        [DbaInstanceParameter]$Source,
        [PSCredential]$SourceSqlCredential,
        [parameter(Mandatory = $true)]
        [DbaInstanceParameter[]]$Destination,
        [PSCredential]$DestinationSqlCredential,
        [object[]]$ResourcePool,
        [object[]]$ExcludeResourcePool,
        [switch]$Force,
        [Alias('Silent')]
        [switch]$EnableException
    )
    process {
        try {
            Write-Message -Level Verbose -Message "Connecting to $Source"
            $sourceServer = Connect-SqlInstance -SqlInstance $Source -SqlCredential $SourceSqlCredential -MinimumVersion 10
        }
        catch {
            Stop-Function -Message "Failure" -Category ConnectionError -ErrorRecord $_ -Target $Source
            return
        }
        $sourceClassifierFunction = Get-DbaResourceGovernorClassifierFunction -SqlInstance $sourceServer
        
       foreach ($destinstance in $Destination) {
            try {
                Write-Message -Level Verbose -Message "Connecting to $destinstance"
                $destServer = Connect-SqlInstance -SqlInstance $destinstance -SqlCredential $DestinationSqlCredential -MinimumVersion 10
            }
            catch {
                Stop-Function -Message "Failure" -Category ConnectionError -ErrorRecord $_ -Target $destinstance -Continue
            }
            $destClassifierFunction = Get-DbaResourceGovernorClassifierFunction -SqlInstance $destServer
            
            $copyResourceGovSetting = [pscustomobject]@{
                SourceServer = $sourceServer.Name
                DestinationServer = $destServer.Name
                Type         = "Resource Governor Settings"
                Name         = "All Settings"
                Status       = $null
                Notes        = $null
                DateTime     = [DbaDateTime](Get-Date)
            }
            
            $copyResourceGovClassifierFunc = [pscustomobject]@{
                SourceServer = $sourceServer.Name
                DestinationServer = $destServer.Name
                Type         = "Resource Governor Settings"
                Name         = "Classifier Function"
                Status       = $null
                Notes        = $null
                DateTime     = [DbaDateTime](Get-Date)
            }
            
            if ($Pscmdlet.ShouldProcess($destinstance, "Updating Resource Governor settings")) {
                if ($destServer.Edition -notmatch 'Enterprise' -and $destServer.Edition -notmatch 'Datacenter' -and $destServer.Edition -notmatch 'Developer') {
                    Write-Message -Level Verbose -Message "The resource governor is not available in this edition of SQL Server. You can manipulate resource governor metadata but you will not be able to apply resource governor configuration. Only Enterprise edition of SQL Server supports resource governor."
                }
                else {
                    try {
                        Write-Message -Level Verbose -Message "Managing classifier function."
                        if (!$sourceClassifierFunction) {
                            $copyResourceGovClassifierFunc.Status = "Skipped"
                            $copyResourceGovClassifierFunc.Notes = $null
                            $copyResourceGovClassifierFunc | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
                        }
                        else {
                            $fullyQualifiedFunctionName = $sourceClassifierFunction.Schema + "." + $sourceClassifierFunction.Name
                            
                            if (!$destClassifierFunction) {
                                $destServer = Connect-SqlInstance -SqlInstance $destinstance -SqlCredential $DestinationSqlCredential
                                $destFunction = $destServer.Databases["master"].UserDefinedFunctions[$sourceClassifierFunction.Name]
                                if ($destFunction) {
                                    Write-Message -Level Verbose -Message "Dropping the function with the source classifier function name."
                                    $destFunction.Drop()
                                }
                                
                                Write-Message -Level Verbose -Message "Creating function."
                                $destServer.Query($sourceClassifierFunction.Script())
                                
                                $sql = "ALTER RESOURCE GOVERNOR WITH (CLASSIFIER_FUNCTION = $fullyQualifiedFunctionName);"
                                Write-Message -Level Debug -Message $sql
                                Write-Message -Level Verbose -Message "Mapping Resource Governor classifier function."
                                $destServer.Query($sql)
                                
                                $copyResourceGovClassifierFunc.Status = "Successful"
                                $copyResourceGovClassifierFunc.Notes = "The new classifier function has been created"
                                $copyResourceGovClassifierFunc | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
                            }
                            else {
                                if ($Force -eq $false) {
                                    $copyResourceGovClassifierFunc.Status = "Skipped"
                                    $copyResourceGovClassifierFunc.Notes = "A classifier function already exists"
                                    $copyResourceGovClassifierFunc | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
                                }
                                else {
                                    
                                    $sql = "ALTER RESOURCE GOVERNOR WITH (CLASSIFIER_FUNCTION = NULL);"
                                    Write-Message -Level Debug -Message $sql
                                    Write-Message -Level Verbose -Message "Disabling the Resource Governor."
                                    $destServer.Query($sql)
                                    
                                    $sql = "ALTER RESOURCE GOVERNOR RECONFIGURE;"
                                    Write-Message -Level Debug -Message $sql
                                    Write-Message -Level Verbose -Message "Reconfiguring Resource Governor."
                                    $destServer.Query($sql)
                                    
                                    Write-Message -Level Verbose -Message "Dropping the destination classifier function."
                                    $destServer = Connect-SqlInstance -SqlInstance $destinstance -SqlCredential $DestinationSqlCredential
                                    $destFunction = $destServer.Databases["master"].UserDefinedFunctions[$sourceClassifierFunction.Name]
                                    $destClassifierFunction.Drop()
                                    
                                    Write-Message -Level Verbose -Message "Re-creating the Resource Governor classifier function."
                                    $destServer.Query($sourceClassifierFunction.Script())
                                    
                                    $sql = "ALTER RESOURCE GOVERNOR WITH (CLASSIFIER_FUNCTION = $fullyQualifiedFunctionName);"
                                    Write-Message -Level Debug -Message $sql
                                    Write-Message -Level Verbose -Message "Mapping Resource Governor classifier function."
                                    $destServer.Query($sql)
                                    
                                    $copyResourceGovClassifierFunc.Status = "Successful"
                                    $copyResourceGovClassifierFunc.Notes = "The old classifier function has been overwritten."
                                    $copyResourceGovClassifierFunc | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
                                }
                            }
                        }
                    }
                    catch {
                        $copyResourceGovSetting.Status = "Failed"
                        $copyResourceGovSetting.Notes = (Get-ErrorMessage -Record $_)
                        $copyResourceGovSetting | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
                        
                        Stop-Function -Message "Not able to update settings." -Target $destServer -ErrorRecord $_
                    }
                }
            }
            
            # Pools
            if ($ResourcePool) {
                $pools = $sourceServer.ResourceGovernor.ResourcePools | Where-Object Name -In $ResourcePool
            }
            elseif ($ExcludeResourcePool) {
                $pool = $sourceServer.ResourceGovernor.ResourcePools | Where-Object Name -NotIn $ExcludeResourcePool
            }
            else {
                $pools = $sourceServer.ResourceGovernor.ResourcePools | Where-Object { $_.Name -notin "internal", "default" }
            }
            
            Write-Message -Level Verbose -Message "Migrating pools."
            foreach ($pool in $pools) {
                $poolName = $pool.Name
                
                $copyResourceGovPool = [pscustomobject]@{
                    SourceServer = $sourceServer.Name
                    DestinationServer = $destServer.Name
                    Type         = "Resource Governor Pool"
                    Name         = $poolName
                    Status       = $null
                    Notes        = $null
                    DateTime     = [DbaDateTime](Get-Date)
                }
                
                if ($null -ne $destServer.ResourceGovernor.ResourcePools[$poolName]) {
                    if ($force -eq $false) {
                        Write-Message -Level Verbose -Message "Pool '$poolName' was skipped because it already exists on $destinstance. Use -Force to drop and recreate."
                        
                        $copyResourceGovPool.Status = "Skipped"
                        $copyResourceGovPool.Notes = "Already exists"
                        $copyResourceGovPool | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
                        continue
                    }
                    else {
                        if ($Pscmdlet.ShouldProcess($destinstance, "Attempting to drop $poolName")) {
                            Write-Message -Level Verbose -Message "Pool '$poolName' exists on $destinstance."
                            Write-Message -Level Verbose -Message "Force specified. Dropping $poolName."
                            
                            try {
                                $destServer = Connect-SqlInstance -SqlInstance $destinstance -SqlCredential $DestinationSqlCredential
                                $destPool = $destServer.ResourceGovernor.ResourcePools[$poolName]
                                $workloadGroups = $destPool.WorkloadGroups
                                foreach ($workloadGroup in $workloadGroups) {
                                    $workloadGroup.Drop()
                                }
                                $destPool.Drop()
                                $destServer.ResourceGovernor.Alter()
                            }
                            catch {
                                $copyResourceGovPool.Status = "Failed to drop from Destination"
                                $copyResourceGovPool.Notes = (Get-ErrorMessage -Record $_)
                                $copyResourceGovPool | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
                                
                                Stop-Function -Message "Unable to drop: $_ Moving on." -Target $destPool -ErrorRecord $_ -Continue
                            }
                        }
                    }
                }
                
                if ($Pscmdlet.ShouldProcess($destinstance, "Migrating pool $poolName")) {
                    try {
                        $sql = $pool.Script() | Out-String
                        Write-Message -Level Debug -Message $sql
                        Write-Message -Level Verbose -Message "Copying pool $poolName."
                        $destServer.Query($sql)
                        
                        $copyResourceGovPool.Status = "Successful"
                        $copyResourceGovPool | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
                        
                        $workloadGroups = $pool.WorkloadGroups
                        foreach ($workloadGroup in $workloadGroups) {
                            $workgroupName = $workloadGroup.Name
                            
                            $copyResourceGovWorkGroup = [pscustomobject]@{
                                SourceServer = $sourceServer.Name
                                DestinationServer = $destServer.Name
                                Type         = "Resource Governor Pool Workgroup"
                                Name         = $workgroupName
                                Status       = $null
                                Notes        = $null
                                DateTime     = [DbaDateTime](Get-Date)
                            }
                            
                            $sql = $workloadGroup.Script() | Out-String
                            Write-Message -Level Debug -Message $sql
                            Write-Message -Level Verbose -Message "Copying $workgroupName."
                            $destServer.Query($sql)
                            
                            $copyResourceGovWorkGroup.Status = "Successful"
                            $copyResourceGovWorkGroup | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
                        }
                    }
                    catch {
                        if ($copyResourceGovWorkGroup) {
                            $copyResourceGovWorkGroup.Status = "Failed"
                            $copyResourceGovWorkGroup.Notes = (Get-ErrorMessage -Record $_)
                            $copyResourceGovWorkGroup | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
                        }
                        Stop-Function -Message "Unable to migrate pool." -Target $pool -ErrorRecord $_
                    }
                }
            }
            
            if ($Pscmdlet.ShouldProcess($destinstance, "Reconfiguring")) {
                if ($destServer.Edition -notmatch 'Enterprise' -and $destServer.Edition -notmatch 'Datacenter' -and $destServer.Edition -notmatch 'Developer') {
                    Write-Message -Level Verbose -Message "The resource governor is not available in this edition of SQL Server. You can manipulate resource governor metadata but you will not be able to apply resource governor configuration. Only Enterprise edition of SQL Server supports resource governor."
                }
                else {
                    
                    Write-Message -Level Verbose -Message "Reconfiguring Resource Governor."
                    try {
                        if (!$sourceServer.ResourceGovernor.Enabled) {
                            $sql = "ALTER RESOURCE GOVERNOR DISABLE"
                            $destServer.Query($sql)
                        }
                        else {
                            $sql = "ALTER RESOURCE GOVERNOR RECONFIGURE"
                            $destServer.Query($sql)
                        }
                    }
                    catch {
                        $altermsg = $_.Exception
                    }
                    
                    
                    $copyResourceGovReconfig = [pscustomobject]@{
                        SourceServer = $sourceServer.Name
                        DestinationServer = $destServer.Name
                        Type         = "Reconfigure Resource Governor"
                        Name         = "Reconfigure Resource Governor"
                        Status       = "Successful"
                        Notes        = $altermsg
                        DateTime     = [DbaDateTime](Get-Date)
                    }
                    $copyResourceGovReconfig | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
                }
            }
        }
    }
    end {
        Test-DbaDeprecation -DeprecatedOn "1.0.0" -EnableException:$false -Alias Copy-SqlResourceGovernor
    }
}
function Copy-DbaServerAudit {
    <#
        .SYNOPSIS
            Copy-DbaServerAudit migrates server audits from one SQL Server to another.

        .DESCRIPTION
            By default, all audits are copied. The -Audit parameter is auto-populated for command-line completion and can be used to copy only specific audits.

            If the audit already exists on the destination, it will be skipped unless -Force is used.

        .PARAMETER Source
            Source SQL Server. You must have sysadmin access and server version must be SQL Server version 2000 or higher.

        .PARAMETER SourceSqlCredential
            Login to the target instance using alternative credentials. Windows and SQL Authentication supported. Accepts credential objects (Get-Credential)

        .PARAMETER Destination
            Destination SQL Server. You must have sysadmin access and the server must be SQL Server 2000 or higher.

        .PARAMETER DestinationSqlCredential
            Login to the target instance using alternative credentials. Windows and SQL Authentication supported. Accepts credential objects (Get-Credential)

        .PARAMETER Audit
            The audit(s) to process. Options for this list are auto-populated from the server. If unspecified, all audits will be processed.

        .PARAMETER ExcludeAudit
            The audit(s) to exclude. Options for this list are auto-populated from the server.

        .PARAMETER WhatIf
            If this switch is enabled, no actions are performed but informational messages will be displayed that explain what would happen if the command were to run.

        .PARAMETER Confirm
            If this switch is enabled, you will be prompted for confirmation before executing any operations that change state.

        .PARAMETER Force
            If this switch is enabled, the audits will be dropped and recreated on Destination.

        .PARAMETER EnableException
            By default, when something goes wrong we try to catch it, interpret it and give you a friendly warning message.
            This avoids overwhelming you with "sea of red" exceptions, but is inconvenient because it basically disables advanced scripting.
            Using this switch turns this "nice by default" feature off and enables you to catch exceptions with your own try/catch.

        .NOTES
            Tags: Migration
            Author: Chrissy LeMaire (@cl), netnerds.net
            Requires: sysadmin access on SQL Servers

            Website: https://dbatools.io
            Copyright: (C) Chrissy LeMaire, [email protected]
            License: MIT https://opensource.org/licenses/MIT

        .LINK
            https://dbatools.io/Copy-DbaServerAudit

        .EXAMPLE
            Copy-DbaServerAudit -Source sqlserver2014a -Destination sqlcluster

            Copies all server audits from sqlserver2014a to sqlcluster, using Windows credentials. If audits with the same name exist on sqlcluster, they will be skipped.

        .EXAMPLE
            Copy-DbaServerAudit -Source sqlserver2014a -Destination sqlcluster -Audit tg_noDbDrop -SourceSqlCredential $cred -Force

            Copies a single audit, the tg_noDbDrop audit from sqlserver2014a to sqlcluster, using SQL credentials for sqlserver2014a and Windows credentials for sqlcluster. If an audit with the same name exists on sqlcluster, it will be dropped and recreated because -Force was used.

        .EXAMPLE
            Copy-DbaServerAudit -Source sqlserver2014a -Destination sqlcluster -WhatIf -Force

            Shows what would happen if the command were executed using force.
    #>
    [CmdletBinding(DefaultParameterSetName = "Default", SupportsShouldProcess = $true)]
    param (
        [parameter(Mandatory = $true)]
        [DbaInstanceParameter]$Source,
        [PSCredential]
        $SourceSqlCredential,
        [parameter(Mandatory = $true)]
        [DbaInstanceParameter[]]$Destination,
        [PSCredential]
        $DestinationSqlCredential,
        [object[]]$Audit,
        [object[]]$ExcludeAudit,
        [switch]$Force,
        [Alias('Silent')]
        [switch]$EnableException
    )

    begin {
        
        try {
            Write-Message -Level Verbose -Message "Connecting to $Source"
            $sourceServer = Connect-SqlInstance -SqlInstance $Source -SqlCredential $SourceSqlCredential -MinimumVersion 10
        }
        catch {
            Stop-Function -Message "Failure" -Category ConnectionError -ErrorRecord $_ -Target $Source
            return
        }
        $serverAudits = $sourceServer.Audits
    }
    process {
        if (Test-FunctionInterrupt) { return }
        foreach ($destinstance in $Destination) {
            
            try {
                Write-Message -Level Verbose -Message "Connecting to $destinstance"
                $destServer = Connect-SqlInstance -SqlInstance $destinstance -SqlCredential $DestinationSqlCredential -MinimumVersion 10
            }
            catch {
                Stop-Function -Message "Failure" -Category ConnectionError -ErrorRecord $_ -Target $destinstance -Continue
            }
            $destAudits = $destServer.Audits
            foreach ($currentAudit in $serverAudits) {
                $auditName = $currentAudit.Name
                
                $copyAuditStatus = [pscustomobject]@{
                    SourceServer = $sourceServer.Name
                    DestinationServer = $destServer.Name
                    Name         = $auditName
                    Type         = "Server Audit"
                    Status       = $null
                    Notes        = $null
                    DateTime     = [DbaDateTime](Get-Date)
                }
                
                if ($Audit -and $auditName -notin $Audit -or $auditName -in $ExcludeAudit) {
                    continue
                }
                
                $sql = $currentAudit.Script() | Out-String
                
                if ($destAudits.Name -contains $auditName) {
                    if ($force -eq $false) {
                        if ($Pscmdlet.ShouldProcess($destinstance, "Server audit $auditName exists at destination. Use -Force to drop and migrate.")) {
                            $copyAuditStatus.Status = "Skipped"
                            $copyAuditStatus.Notes = "Already exists"
                            Write-Message -Level Verbose -Message "Server audit $auditName exists at destination. Use -Force to drop and migrate."
                        }
                        continue
                    }
                    else {
                        if ($Pscmdlet.ShouldProcess($destinstance, "Dropping server audit $auditName")) {
                            try {
                                Write-Message -Level Verbose -Message "Dropping server audit $auditName."
                                foreach ($spec in $destServer.ServerAuditSpecifications) {
                                    if ($auditSpecification.Auditname -eq $auditName) {
                                        $auditSpecification.Drop()
                                    }
                                }
                                
                                $destServer.audits[$auditName].Disable()
                                $destServer.audits[$auditName].Alter()
                                $destServer.audits[$auditName].Drop()
                            }
                            catch {
                                $copyAuditStatus.Status = "Failed"
                                $copyAuditStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
                                
                                Stop-Function -Message "Issue dropping audit from destination." -Target $auditName -ErrorRecord $_
                            }
                        }
                    }
                }
                
                if ($null -ne ($currentAudit.Filepath) -and -not (Test-DbaSqlPath -SqlInstance $destServer -Path $currentAudit.Filepath)) {
                    if ($Force -eq $false) {
                        if ($Pscmdlet.ShouldProcess($destinstance, "$($currentAudit.Filepath) does not exist on $destinstance. Skipping $auditName. Specify -Force to create the directory.")) {
                            $copyAuditStatus.Status = "Skipped"
                            $copyAuditStatus.Notes = "$($currentAudit.Filepath) does not exist on $destinstance. Skipping $auditName. Specify -Force to create the directory."
                            $copyAuditStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
                        }
                        continue
                    }
                    else {
                        Write-Message -Level Verbose -Message "Force specified. Creating directory."
                        
                        $destNetBios = Resolve-NetBiosName $destServer
                        $path = Join-AdminUnc $destNetBios $currentAudit.Filepath
                        $root = $currentAudit.Filepath.Substring(0, 3)
                        $rootUnc = Join-AdminUnc $destNetBios $root
                        
                        if ((Test-Path $rootUnc) -eq $true) {
                            if ($Pscmdlet.ShouldProcess($destinstance, "Creating directory $($currentAudit.Filepath)")) {
                                try {
                                    $null = New-DbaSqlDirectory -SqlInstance $destServer -Path $currentAudit.Filepath -EnableException
                                }
                                catch {
                                    Write-Message -Level Warning -Message "Couldn't create directory $($currentAudit.Filepath). Using default data directory."
                                    $datadir = Get-SqlDefaultPaths $destServer data
                                    $sql = $sql.Replace($currentAudit.FilePath, $datadir)
                                }
                            }
                        }
                        else {
                            $datadir = Get-SqlDefaultPaths $destServer data
                            $sql = $sql.Replace($currentAudit.FilePath, $datadir)
                        }
                    }
                }
                if ($Pscmdlet.ShouldProcess($destinstance, "Creating server audit $auditName")) {
                    try {
                        Write-Message -Level Verbose -Message "File path $($currentAudit.Filepath) exists on $destinstance."
                        Write-Message -Level Verbose -Message "Copying server audit $auditName."
                        $destServer.Query($sql)
                        
                        $copyAuditStatus.Status = "Successful"
                        $copyAuditStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
                    }
                    catch {
                        $copyAuditStatus.Status = "Failed"
                        $copyAuditStatus.Notes = (Get-ErrorMessage -Record $_)
                        $copyAuditStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
                        
                        Stop-Function -Message "Issue creating audit." -Target $auditName -ErrorRecord $_
                    }
                }
            }
        }
    }
    end {
        Test-DbaDeprecation -DeprecatedOn "1.0.0" -EnableException:$false -Alias Copy-SqlAudit
    }
}
function Copy-DbaServerAuditSpecification {
    <#
        .SYNOPSIS
            Copy-DbaServerAuditSpecification migrates server audit specifications from one SQL Server to another.

        .DESCRIPTION
            By default, all audits are copied. The -AuditSpecification parameter is auto-populated for command-line completion and can be used to copy only specific audits.

            If the audit specification already exists on the destination, it will be skipped unless -Force is used.

        .PARAMETER Source
            Source SQL Server. You must have sysadmin access and server version must be SQL Server version 2000 or higher.

        .PARAMETER SourceSqlCredential
            Login to the target instance using alternative credentials. Windows and SQL Authentication supported. Accepts credential objects (Get-Credential)

        .PARAMETER Destination
            Destination SQL Server. You must have sysadmin access and the server must be SQL Server 2000 or higher.

        .PARAMETER DestinationSqlCredential
            Login to the target instance using alternative credentials. Windows and SQL Authentication supported. Accepts credential objects (Get-Credential)

        .PARAMETER AuditSpecification
            The Server Audit Specification(s) to process. Options for this list are auto-populated from the server. If unspecified, all Server Audit Specifications will be processed.

        .PARAMETER ExcludeAuditSpecification
            The Server Audit Specification(s) to exclude. Options for this list are auto-populated from the server

        .PARAMETER WhatIf
            If this switch is enabled, no actions are performed but informational messages will be displayed that explain what would happen if the command were to run.

        .PARAMETER Confirm
            If this switch is enabled, you will be prompted for confirmation before executing any operations that change state.

        .PARAMETER Force
            If this switch is enabled, the Audits Specifications will be dropped and recreated on Destination.

        .PARAMETER EnableException
            By default, when something goes wrong we try to catch it, interpret it and give you a friendly warning message.
            This avoids overwhelming you with "sea of red" exceptions, but is inconvenient because it basically disables advanced scripting.
            Using this switch turns this "nice by default" feature off and enables you to catch exceptions with your own try/catch.

        .NOTES
            Tags: Migration,ServerAudit,AuditSpecification
            Author: Chrissy LeMaire (@cl), netnerds.net
            Requires: sysadmin access on SQL Servers

            Website: https://dbatools.io
            Copyright: (C) Chrissy LeMaire, [email protected]
            License: MIT https://opensource.org/licenses/MIT

        .LINK
            https://dbatools.io/Copy-DbaServerAuditSpecification

        .EXAMPLE
            Copy-DbaServerAuditSpecification -Source sqlserver2014a -Destination sqlcluster

            Copies all server audits from sqlserver2014a to sqlcluster using Windows credentials to connect. If audits with the same name exist on sqlcluster, they will be skipped.

        .EXAMPLE
            Copy-DbaServerAuditSpecification -Source sqlserver2014a -Destination sqlcluster -ServerAuditSpecification tg_noDbDrop -SourceSqlCredential $cred -Force

            Copies a single audit, the tg_noDbDrop audit from sqlserver2014a to sqlcluster using SQL credentials to connect to sqlserver2014a and Windows credentials to connect to sqlcluster. If an audit specification with the same name exists on sqlcluster, it will be dropped and recreated because -Force was used.

        .EXAMPLE
            Copy-DbaServerAuditSpecification -Source sqlserver2014a -Destination sqlcluster -WhatIf -Force

            Shows what would happen if the command were executed using force.
    #>
    [CmdletBinding(DefaultParameterSetName = "Default", SupportsShouldProcess = $true)]
    param (
        [parameter(Mandatory = $true)]
        [DbaInstanceParameter]$Source,
        [PSCredential]$SourceSqlCredential,
        [parameter(Mandatory = $true)]
        [DbaInstanceParameter[]]$Destination,
        [PSCredential]$DestinationSqlCredential,
        [object[]]$AuditSpecification,
        [object[]]$ExcludeAuditSpecification,
        [switch]$Force,
        [Alias('Silent')]
        [switch]$EnableException
    )

    begin {
        try {
            Write-Message -Level Verbose -Message "Connecting to $Source"
            $sourceServer = Connect-SqlInstance -SqlInstance $Source -SqlCredential $SourceSqlCredential -MinimumVersion 10
        }
        catch {
            Stop-Function -Message "Failure" -Category ConnectionError -ErrorRecord $_ -Target $Source
            return
        }
        
        if (!(Test-SqlSa -SqlInstance $sourceServer -SqlCredential $SourceSqlCredential)) {
            Stop-Function -Message "Not a sysadmin on $source. Quitting."
            return
        }
        
        $AuditSpecifications = $sourceServer.ServerAuditSpecifications
    }
    process {
        if (Test-FunctionInterrupt) { return }
        foreach ($destinstance in $Destination) {
            try {
                Write-Message -Level Verbose -Message "Connecting to $destinstance"
                $destServer = Connect-SqlInstance -SqlInstance $destinstance -SqlCredential $DestinationSqlCredential -MinimumVersion 10
            }
            catch {
                Stop-Function -Message "Failure" -Category ConnectionError -ErrorRecord $_ -Target $destinstance -Continue
            }
            
            if (!(Test-SqlSa -SqlInstance $destServer -SqlCredential $DestinationSqlCredential)) {
                Stop-Function -Message "Not a sysadmin on $destinstance. Quitting."
                return
            }
            
            if ($destServer.VersionMajor -lt $sourceServer.VersionMajor) {
                Stop-Function -Message "Migration from version $($destServer.VersionMajor) to version $($sourceServer.VersionMajor) is not supported."
                return
            }
            $destAudits = $destServer.ServerAuditSpecifications
            foreach ($auditSpec in $AuditSpecifications) {
                $auditSpecName = $auditSpec.Name
                
                $copyAuditSpecStatus = [pscustomobject]@{
                    SourceServer = $sourceServer.Name
                    DestinationServer = $destServer.Name
                    Type         = "Server Audit Specification"
                    Name         = $auditSpecName
                    Status       = $null
                    Notes        = $null
                    DateTime     = [DbaDateTime](Get-Date)
                }
                
                if ($AuditSpecification -and $auditSpecName -notin $AuditSpecification -or $auditSpecName -in $ExcludeAuditSpecification) {
                    continue
                }
                
                $destServer.Audits.Refresh()
                if ($destServer.Audits.Name -notcontains $auditSpec.AuditName) {
                    if ($Pscmdlet.ShouldProcess($destinstance, "Audit $($auditSpec.AuditName) does not exist on $destinstance. Skipping $auditSpecName.")) {
                        $copyAuditSpecStatus.Status = "Skipped"
                        $copyAuditSpecStatus.Notes = "Audit $($auditSpec.AuditName) does not exist on $destinstance. Skipping $auditSpecName."
                        Write-Message -Level Warning -Message "Audit $($auditSpec.AuditName) does not exist on $destinstance. Skipping $auditSpecName."
                        $copyAuditSpecStatus
                    }
                    continue
                }
                
                if ($destAudits.name -contains $auditSpecName) {
                    if ($force -eq $false) {
                        Write-Message -Level Verbose -Message "Server audit $auditSpecName exists at destination. Use -Force to drop and migrate."
                        
                        $copyAuditSpecStatus.Status = "Skipped"
                        $copyAuditSpecStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
                        continue
                    }
                    else {
                        if ($Pscmdlet.ShouldProcess($destinstance, "Dropping server audit $auditSpecName and recreating")) {
                            try {
                                Write-Message -Level Verbose -Message "Dropping server audit $auditSpecName"
                                $destServer.ServerAuditSpecifications[$auditSpecName].Drop()
                            }
                            catch {
                                $copyAuditSpecStatus.Status = "Failed"
                                $copyAuditSpecStatus.Notes = (Get-ErrorMessage -Record $_)
                                $copyAuditSpecStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
                                
                                Stop-Function -Message "Issue dropping audit spec" -Target $auditSpecName -ErrorRecord $_ -Continue
                            }
                        }
                    }
                }
                if ($Pscmdlet.ShouldProcess($destinstance, "Creating server audit $auditSpecName")) {
                    try {
                        Write-Message -Level Verbose -Message "Copying server audit $auditSpecName"
                        $sql = $auditSpec.Script() | Out-String
                        Write-Message -Level Debug -Message $sql
                        $destServer.Query($sql)
                        
                        $copyAuditSpecStatus.Status = "Successful"
                        $copyAuditSpecStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
                    }
                    catch {
                        $copyAuditSpecStatus.Status = "Failed"
                        $copyAuditSpecStatus.Notes = (Get-ErrorMessage -Record $_)
                        $copyAuditSpecStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
                        
                        Stop-Function -Message "Issue creating audit spec on destination" -Target $auditSpecName -ErrorRecord $_
                    }
                }
            }
        }
    }
    end {
        Test-DbaDeprecation -DeprecatedOn "1.0.0" -EnableException:$false -Alias Copy-SqlAuditSpecification
    }
}
function Copy-DbaServerTrigger {
    <#
        .SYNOPSIS
            Copy-DbaServerTrigger migrates server triggers from one SQL Server to another.

        .DESCRIPTION
            By default, all triggers are copied. The -ServerTrigger parameter is auto-populated for command-line completion and can be used to copy only specific triggers.

            If the trigger already exists on the destination, it will be skipped unless -Force is used.

        .PARAMETER Source
            Source SQL Server.You must have sysadmin access and server version must be SQL Server version 2000 or greater.

        .PARAMETER SourceSqlCredential
            Login to the target instance using alternative credentials. Windows and SQL Authentication supported. Accepts credential objects (Get-Credential)

        .PARAMETER Destination
            Destination Sql Server. You must have sysadmin access and server version must be SQL Server version 2000 or greater.

        .PARAMETER DestinationSqlCredential
            Login to the target instance using alternative credentials. Windows and SQL Authentication supported. Accepts credential objects (Get-Credential)

        .PARAMETER ServerTrigger
            The Server Trigger(s) to process - this list is auto-populated from the server. If unspecified, all Server Triggers will be processed.

        .PARAMETER ExcludeServerTrigger
            The Server Trigger(s) to exclude - this list is auto-populated from the server

        .PARAMETER WhatIf
            Shows what would happen if the command were to run. No actions are actually performed.

        .PARAMETER Confirm
            Prompts you for confirmation before executing any changing operations within the command.

        .PARAMETER Force
            Drops and recreates the Trigger if it exists

        .PARAMETER EnableException
            By default, when something goes wrong we try to catch it, interpret it and give you a friendly warning message.
            This avoids overwhelming you with "sea of red" exceptions, but is inconvenient because it basically disables advanced scripting.
            Using this switch turns this "nice by default" feature off and enables you to catch exceptions with your own try/catch.

        .NOTES
            Tags: Migration
            Author: Chrissy LeMaire (@cl), netnerds.net
            Requires: sysadmin access on SQL Servers

            Website: https://dbatools.io
            Copyright: (C) Chrissy LeMaire, [email protected]
            License: MIT https://opensource.org/licenses/MIT

        .LINK
            https://dbatools.io/Copy-DbaServerTrigger

        .EXAMPLE
            Copy-DbaServerTrigger -Source sqlserver2014a -Destination sqlcluster

            Copies all server triggers from sqlserver2014a to sqlcluster, using Windows credentials. If triggers with the same name exist on sqlcluster, they will be skipped.

        .EXAMPLE
            Copy-DbaServerTrigger -Source sqlserver2014a -Destination sqlcluster -ServerTrigger tg_noDbDrop -SourceSqlCredential $cred -Force

            Copies a single trigger, the tg_noDbDrop trigger from sqlserver2014a to sqlcluster, using SQL credentials for sqlserver2014a and Windows credentials for sqlcluster. If a trigger with the same name exists on sqlcluster, it will be dropped and recreated because -Force was used.

        .EXAMPLE
            Copy-DbaServerTrigger -Source sqlserver2014a -Destination sqlcluster -WhatIf -Force

            Shows what would happen if the command were executed using force.
    #>
    [CmdletBinding(DefaultParameterSetName = "Default", SupportsShouldProcess = $true)]
    param (
        [parameter(Mandatory = $true)]
        [DbaInstanceParameter]$Source,
        [PSCredential]
        $SourceSqlCredential,
        [parameter(Mandatory = $true)]
        [DbaInstanceParameter[]]$Destination,
        [PSCredential]
        $DestinationSqlCredential,
        [object[]]$ServerTrigger,
        [object[]]$ExcludeServerTrigger,
        [switch]$Force,
        [Alias('Silent')]
        [switch]$EnableException
    )

    begin {
        try {
            Write-Message -Level Verbose -Message "Connecting to $Source"
            $sourceServer = Connect-SqlInstance -SqlInstance $Source -SqlCredential $SourceSqlCredential -MinimumVersion 9
        }
        catch {
            Stop-Function -Message "Failure" -Category ConnectionError -ErrorRecord $_ -Target $Source
            return
        }
        $serverTriggers = $sourceServer.Triggers
    }
    process {
        if (Test-FunctionInterrupt) { return }
        foreach ($destinstance in $Destination) {
            try {
                Write-Message -Level Verbose -Message "Connecting to $destinstance"
                $destServer = Connect-SqlInstance -SqlInstance $destinstance -SqlCredential $DestinationSqlCredential -MinimumVersion 9
            }
            catch {
                Stop-Function -Message "Failure" -Category ConnectionError -ErrorRecord $_ -Target $destinstance -Continue
            }
            if ($destServer.VersionMajor -lt $sourceServer.VersionMajor) {
                Stop-Function -Message "Migration from version $($destServer.VersionMajor) to version $($sourceServer.VersionMajor) is not supported."
                return
            }
            $destTriggers = $destServer.Triggers
            
            foreach ($trigger in $serverTriggers) {
                $triggerName = $trigger.Name
                
                $copyTriggerStatus = [pscustomobject]@{
                    SourceServer = $sourceServer.Name
                    DestinationServer = $destServer.Name
                    Name         = $triggerName
                    Type         = "Server Trigger"
                    Status       = $null
                    Notes        = $null
                    DateTime     = [DbaDateTime](Get-Date)
                }
                
                if ($ServerTrigger -and $triggerName -notin $ServerTrigger -or $triggerName -in $ExcludeServerTrigger) {
                    continue
                }
                
                if ($destTriggers.Name -contains $triggerName) {
                    if ($force -eq $false) {
                        Write-Message -Level Verbose -Message "Server trigger $triggerName exists at destination. Use -Force to drop and migrate."
                        
                        $copyTriggerStatus.Status = "Skipped"
                        $copyTriggerStatus.Status = "Already exists"
                        $copyTriggerStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
                        continue
                    }
                    else {
                        if ($Pscmdlet.ShouldProcess($destinstance, "Dropping server trigger $triggerName and recreating")) {
                            try {
                                Write-Message -Level Verbose -Message "Dropping server trigger $triggerName"
                                $destServer.Triggers[$triggerName].Drop()
                            }
                            catch {
                                $copyTriggerStatus.Status = "Failed"
                                $copyTriggerStatus.Notes = (Get-ErrorMessage -Record $_)
                                $copyTriggerStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
                                
                                Stop-Function -Message "Issue dropping trigger on destination" -Target $triggerName -ErrorRecord $_ -Continue
                            }
                        }
                    }
                }
                
                if ($Pscmdlet.ShouldProcess($destinstance, "Creating server trigger $triggerName")) {
                    try {
                        Write-Message -Level Verbose -Message "Copying server trigger $triggerName"
                        $sql = $trigger.Script() | Out-String
                        $sql = $sql -replace "CREATE TRIGGER", "`nGO`nCREATE TRIGGER"
                        $sql = $sql -replace "ENABLE TRIGGER", "`nGO`nENABLE TRIGGER"
                        Write-Message -Level Debug -Message $sql
                        
                        foreach ($query in ($sql -split '\nGO\b')) {
                            $destServer.Query($query) | Out-Null
                        }
                        
                        $copyTriggerStatus.Status = "Successful"
                        $copyTriggerStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
                    }
                    catch {
                        $copyTriggerStatus.Status = "Failed"
                        $copyTriggerStatus.Notes = (Get-ErrorMessage -Record $_)
                        $copyTriggerStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
                        
                        Stop-Function -Message "Issue creating trigger on destination" -Target $triggerName -ErrorRecord $_
                    }
                }
            }
        }
    }
    end {
        Test-DbaDeprecation -DeprecatedOn "1.0.0" -EnableException:$false -Alias Copy-SqlServerTrigger
    }
}
function Copy-DbaSpConfigure {
    <#
        .SYNOPSIS
            Copy-DbaSpConfigure migrates configuration values from one SQL Server to another.

        .DESCRIPTION
            By default, all configuration values are copied. The -ConfigName parameter is auto-populated for command-line completion and can be used to copy only specific configs.

        .PARAMETER Source
            Source SQL Server. You must have sysadmin access and server version must be SQL Server version 2000 or higher.

        .PARAMETER SourceSqlCredential
            Login to the target instance using alternative credentials. Windows and SQL Authentication supported. Accepts credential objects (Get-Credential)

        .PARAMETER Destination
            Destination SQL Server. You must have sysadmin access and the server must be SQL Server 2000 or higher.

        .PARAMETER DestinationSqlCredential
            Login to the target instance using alternative credentials. Windows and SQL Authentication supported. Accepts credential objects (Get-Credential)

        .PARAMETER ConfigName
            Specifies the configuration setting to process. Options for this list are auto-populated from the server. If unspecified, all ConfigNames will be processed.

        .PARAMETER ExcludeConfigName
            Specifies the configuration settings to exclude. Options for this list are auto-populated from the server.

        .PARAMETER WhatIf
            If this switch is enabled, no actions are performed but informational messages will be displayed that explain what would happen if the command were to run.

        .PARAMETER Confirm
            If this switch is enabled, you will be prompted for confirmation before executing any operations that change state.

        .PARAMETER EnableException
            By default, when something goes wrong we try to catch it, interpret it and give you a friendly warning message.
            This avoids overwhelming you with "sea of red" exceptions, but is inconvenient because it basically disables advanced scripting.
            Using this switch turns this "nice by default" feature off and enables you to catch exceptions with your own try/catch.

        .NOTES
            Tags: Migration, Configure, SpConfigure
            Author: Chrissy LeMaire (@cl), netnerds.net
            Requires: sysadmin access on SQL Servers

            Website: https://dbatools.io
            Copyright: (C) Chrissy LeMaire, [email protected]
            License: MIT https://opensource.org/licenses/MIT

        .LINK
            https://dbatools.io/Copy-DbaSpConfigure

        .EXAMPLE
            Copy-DbaSpConfigure -Source sqlserver2014a -Destination sqlcluster

            Copies all sp_configure settings from sqlserver2014a to sqlcluster

        .EXAMPLE
            Copy-DbaSpConfigure -Source sqlserver2014a -Destination sqlcluster -ConfigName DefaultBackupCompression, IsSqlClrEnabled -SourceSqlCredential $cred -Force

            Copies the values for IsSqlClrEnabled and DefaultBackupCompression from sqlserver2014a to sqlcluster using SQL credentials to authenticate to sqlserver2014a and Windows credentials to authenticate to sqlcluster.

        .EXAMPLE
            Copy-DbaSpConfigure -Source sqlserver2014a -Destination sqlcluster -ExcludeConfigName DefaultBackupCompression, IsSqlClrEnabled

            Copies all configs except for IsSqlClrEnabled and DefaultBackupCompression, from sqlserver2014a to sqlcluster.

        .EXAMPLE
            Copy-DbaSpConfigure -Source sqlserver2014a -Destination sqlcluster -WhatIf

            Shows what would happen if the command were executed.
    #>
    [CmdletBinding(DefaultParameterSetName = "Default", SupportsShouldProcess = $true)]
    param (
        [parameter(Mandatory = $true)]
        [DbaInstanceParameter]$Source,
        [PSCredential]$SourceSqlCredential,
        [parameter(Mandatory = $true)]
        [DbaInstanceParameter[]]$Destination,
        [PSCredential]$DestinationSqlCredential,
        [object[]]$ConfigName,
        [object[]]$ExcludeConfigName,
        [Alias('Silent')]
        [switch]$EnableException
    )
    begin {
        try {
            Write-Message -Level Verbose -Message "Connecting to $Source"
            $sourceServer = Connect-SqlInstance -SqlInstance $Source -SqlCredential $SourceSqlCredential
            $sourceProps = Get-DbaSpConfigure -SqlInstance $sourceServer
        }
        catch {
            Stop-Function -Message "Failure" -Category ConnectionError -ErrorRecord $_ -Target $Source
            return
        }
    }
    process {
        if (Test-FunctionInterrupt) { return }
        foreach ($destinstance in $Destination) {
            try {
                Write-Message -Level Verbose -Message "Connecting to $destinstance"
                $destServer = Connect-SqlInstance -SqlInstance $destinstance -SqlCredential $DestinationSqlCredential
                $destProps = Get-DbaSpConfigure -SqlInstance $destServer
            }
            catch {
                Stop-Function -Message "Failure" -Category ConnectionError -ErrorRecord $_ -Target $destinstance -Continue
            }
            
            foreach ($sourceProp in $sourceProps) {
                $displayName = $sourceProp.DisplayName
                $sConfigName = $sourceProp.ConfigName
                $sConfiguredValue = $sourceProp.ConfiguredValue
                $requiresRestart = $sourceProp.IsDynamic
                
                $copySpConfigStatus = [pscustomobject]@{
                    SourceServer = $sourceServer.Name
                    DestinationServer = $destServer.Name
                    Name         = $sConfigName
                    Type         = "Configuration Value"
                    Status       = $null
                    Notes        = $null
                    DateTime     = [DbaDateTime](Get-Date)
                }
                
                if ($ConfigName -and $sConfigName -notin $ConfigName -or $sConfigName -in $ExcludeConfigName) {
                    continue
                }
                
                $destProp = $destProps | Where-Object ConfigName -eq $sConfigName
                if (!$destProp) {
                    Write-Message -Level Verbose -Message "Configuration $sConfigName ('$displayName') does not exist on the destination instance."
                    
                    $copySpConfigStatus.Status = "Skipped"
                    $copySpConfigStatus.Notes = "Configuration does not exist on destination"
                    $copySpConfigStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
                    
                    continue
                }
                
                if ($Pscmdlet.ShouldProcess($destinstance, "Updating $sConfigName [$displayName]")) {
                    try {
                        $destOldConfigValue = $destProp.ConfiguredValue
                        
                        if ($sConfiguredValue -ne $destOldConfigValue) {
                            $result = Set-DbaSpConfigure -SqlInstance $destServer -Name $sConfigName -Value $sConfiguredValue -EnableException -WarningAction SilentlyContinue
                            if ($result) {
                                Write-Message -Level Verbose -Message "Updated $($destProp.ConfigName) ($($destProp.DisplayName)) from $destOldConfigValue to $sConfiguredValue."
                            }
                        }
                        if ($requiresRestart -eq $false) {
                            Write-Message -Level Verbose -Message "Configuration option $sConfigName ($displayName) requires restart."
                            $copySpConfigStatus.Notes = "Requires restart"
                        }
                        $copySpConfigStatus.Status = "Successful"
                        $copySpConfigStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
                    }
                    catch {
                        if ($_.Exception -match 'the same as the') {
                            $copySpConfigStatus.Status = "Successful"
                        }
                        else {
                            $copySpConfigStatus.Status = "Failed"
                            $copySpConfigStatus.Notes = (Get-ErrorMessage -Record $_)
                        }
                        $copySpConfigStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
                        
                        Stop-Function -Message "Could not set $($destProp.ConfigName) to $sConfiguredValue." -Target $sConfigName -ErrorRecord $_
                    }
                }
            }
        }
    }
    end {
        Test-DbaDeprecation -DeprecatedOn "1.0.0" -EnableException:$false -Alias Copy-SqlSpConfigure
    }
}
function Copy-DbaSqlDataCollector {
    <#
        .SYNOPSIS
            Migrates user SQL Data Collector collection sets. SQL Data Collector configuration is on the agenda, but it's hard.

        .DESCRIPTION
            By default, all data collector objects are migrated. If the object already exists on the destination, it will be skipped unless -Force is used.

            The -CollectionSet parameter is auto-populated for command-line completion and can be used to copy only specific objects.

        .PARAMETER Source
            Source SQL Server. You must have sysadmin access and server version must be SQL Server version 2008 or higher.

        .PARAMETER SourceSqlCredential
            Login to the target instance using alternative credentials. Windows and SQL Authentication supported. Accepts credential objects (Get-Credential)

        .PARAMETER Destination
            Destination Sql Server. You must have sysadmin access and server version must be SQL Server version 2008 or higher.

        .PARAMETER DestinationSqlCredential
            Login to the target instance using alternative credentials. Windows and SQL Authentication supported. Accepts credential objects (Get-Credential)

        .PARAMETER CollectionSet
            The collection set(s) to process - this list is auto-populated from the server. If unspecified, all collection sets will be processed.

        .PARAMETER ExcludeCollectionSet
            The collection set(s) to exclude - this list is auto-populated from the server

        .PARAMETER NoServerReconfig
            Upcoming parameter to enable server reconfiguration

        .PARAMETER WhatIf
            Shows what would happen if the command were to run. No actions are actually performed.

        .PARAMETER Confirm
            Prompts you for confirmation before executing any changing operations within the command.

        .PARAMETER Force
            If collection sets exists on destination server, it will be dropped and recreated.

        .PARAMETER EnableException
            By default, when something goes wrong we try to catch it, interpret it and give you a friendly warning message.
            This avoids overwhelming you with "sea of red" exceptions, but is inconvenient because it basically disables advanced scripting.
            Using this switch turns this "nice by default" feature off and enables you to catch exceptions with your own try/catch.

        .NOTES
            Tags: Migration,DataCollection
            Author: Chrissy LeMaire (@cl), netnerds.net
            Requires: sysadmin access on SQL Servers

            Website: https://dbatools.io
            Copyright: (C) Chrissy LeMaire, [email protected]
            License: MIT https://opensource.org/licenses/MIT

        .LINK
            https://dbatools.io/Copy-DbaSqlDataCollector

        .EXAMPLE
            Copy-DbaSqlDataCollector -Source sqlserver2014a -Destination sqlcluster

            Copies all Data Collector Objects and Configurations from sqlserver2014a to sqlcluster, using Windows credentials.

        .EXAMPLE
            Copy-DbaSqlDataCollector -Source sqlserver2014a -Destination sqlcluster -SourceSqlCredential $cred

            Copies all Data Collector Objects and Configurations from sqlserver2014a to sqlcluster, using SQL credentials for sqlserver2014a and Windows credentials for sqlcluster.

        .EXAMPLE
            Copy-DbaSqlDataCollector -Source sqlserver2014a -Destination sqlcluster -WhatIf

            Shows what would happen if the command were executed.

        .EXAMPLE
            Copy-DbaSqlDataCollector -Source sqlserver2014a -Destination sqlcluster -CollectionSet 'Server Activity', 'Table Usage Analysis'

            Copies two Collection Sets, Server Activity and Table Usage Analysis, from sqlserver2014a to sqlcluster.
    #>
    [CmdletBinding(DefaultParameterSetName = "Default", SupportsShouldProcess = $true)]
    param (
        [parameter(Mandatory = $true)]
        [DbaInstanceParameter]$Source,
        [PSCredential]
        $SourceSqlCredential,
        [parameter(Mandatory = $true)]
        [DbaInstanceParameter[]]$Destination,
        [PSCredential]
        $DestinationSqlCredential,
        [object[]]$CollectionSet,
        [object[]]$ExcludeCollectionSet,
        [switch]$NoServerReconfig,
        [switch]$Force,
        [Alias('Silent')]
        [switch]$EnableException
    )
    begin {
        try {
            Write-Message -Level Verbose -Message "Connecting to $Source"
            $sourceServer = Connect-SqlInstance -SqlInstance $Source -SqlCredential $SourceSqlCredential -MinimumVersion 10
        }
        catch {
            Stop-Function -Message "Failure" -Category ConnectionError -ErrorRecord $_ -Target $Source
            return
        }
        $sourceSqlConn = $sourceServer.ConnectionContext.SqlConnectionObject
        $sourceSqlStoreConnection = New-Object Microsoft.SqlServer.Management.Sdk.Sfc.SqlStoreConnection $sourceSqlConn
        $sourceStore = New-Object Microsoft.SqlServer.Management.Collector.CollectorConfigStore $sourceSqlStoreConnection
        $configDb = $sourceStore.ScriptAlter().GetScript() | Out-String
        $configDb = $configDb -replace [Regex]::Escape("'$source'"), "'$destReplace'"
    }
    process {
        if (Test-FunctionInterrupt) { return }
        foreach ($destinstance in $Destination) {
            
            try {
                Write-Message -Level Verbose -Message "Connecting to $destinstance"
                $destServer = Connect-SqlInstance -SqlInstance $destinstance -SqlCredential $DestinationSqlCredential -MinimumVersion 10
            }
            catch {
                Stop-Function -Message "Failure" -Category ConnectionError -ErrorRecord $_ -Target $destinstance -Continue
            }
            if ($NoServerReconfig -eq $false) {
                if ($Pscmdlet.ShouldProcess($destinstance, "Server reconfiguration not yet supported. Only Collection Set migration will be migrated at this time.")) {
                    Write-Message -Level Verbose -Message "Server reconfiguration not yet supported. Only Collection Set migration will be migrated at this time."
                    $NoServerReconfig = $true
                    
            <# for future use when this support is added #>
                    $copyServerConfigStatus = [pscustomobject]@{
                        SourceServer = $sourceServer.Name
                        DestinationServer = $destServer.Name
                        Name         = $userName
                        Type         = "Data Collection Server Config"
                        Status       = "Skipped"
                        Notes        = "Not supported at this time"
                        DateTime     = [DbaDateTime](Get-Date)
                    }
                    $copyServerConfigStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
                }
            }
            $destSqlConn = $destServer.ConnectionContext.SqlConnectionObject
            $destSqlStoreConnection = New-Object Microsoft.SqlServer.Management.Sdk.Sfc.SqlStoreConnection $destSqlConn
            $destStore = New-Object Microsoft.SqlServer.Management.Collector.CollectorConfigStore $destSqlStoreConnection
            
            if (!$NoServerReconfig) {
                if ($Pscmdlet.ShouldProcess($destinstance, "Attempting to modify Data Collector configuration")) {
                    try {
                        $sql = "Unknown at this time"
                        $destServer.Query($sql)
                        $destStore.Alter()
                    }
                    catch {
                        $copyServerConfigStatus.Status = "Failed"
                        $copyServerConfigStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
                        Stop-Function -Message "Issue modifying Data Collector configuration" -Target $destServer -ErrorRecord $_
                    }
                }
            }
            
            if ($destStore.Enabled -eq $false) {
                Write-Message -Level Verbose -Message "The Data Collector must be setup initially for Collection Sets to be migrated. Setup the Data Collector and try again."
                continue
            }
            
            $storeCollectionSets = $sourceStore.CollectionSets | Where-Object { $_.IsSystem -eq $false }
            if ($CollectionSet) {
                $storeCollectionSets = $storeCollectionSets | Where-Object Name -In $CollectionSet
            }
            if ($ExcludeCollectionSet) {
                $storeCollectionSets = $storeCollectionSets | Where-Object Name -NotIn $ExcludeCollectionSet
            }
            
            Write-Message -Level Verbose -Message "Migrating collection sets"
            foreach ($set in $storeCollectionSets) {
                $collectionName = $set.Name
                
                $copyCollectionSetStatus = [pscustomobject]@{
                    SourceServer = $sourceServer.Name
                    DestinationServer = $destServer.Name
                    Name         = $collectionName
                    Type         = "Collection Set"
                    Status       = $null
                    Notes        = $null
                    DateTime     = [DbaDateTime](Get-Date)
                }
                
                if ($null -ne $destStore.CollectionSets[$collectionName]) {
                    if ($force -eq $false) {
                        if ($Pscmdlet.ShouldProcess($destinstance, "Collection Set '$collectionName' was skipped because it already exists on $destinstance. Use -Force to drop and recreate")) {
                            Write-Message -Level Verbose -Message "Collection Set '$collectionName' was skipped because it already exists on $destinstance. Use -Force to drop and recreate"
                            
                            $copyCollectionSetStatus.Status = "Skipped"
                            $copyCollectionSetStatus.Notes = "Already exists"
                            $copyCollectionSetStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
                        }
                        continue
                    }
                    else {
                        if ($Pscmdlet.ShouldProcess($destinstance, "Attempting to drop $collectionName")) {
                            Write-Message -Level Verbose -Message "Collection Set '$collectionName' exists on $destinstance"
                            Write-Message -Level Verbose -Message "Force specified. Dropping $collectionName."
                            
                            try {
                                $destStore.CollectionSets[$collectionName].Drop()
                            }
                            catch {
                                $copyCollectionSetStatus.Status = "Failed to drop on destination"
                                $copyCollectionSetStatus.Notes = (Get-ErrorMessage -Record $_)
                                $copyCollectionSetStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
                                Stop-Function -Message "Issue dropping collection" -Target $collectionName -ErrorRecord $_ -Continue
                            }
                        }
                    }
                }
                
                if ($Pscmdlet.ShouldProcess($destinstance, "Migrating collection set $collectionName")) {
                    try {
                        $sql = $set.ScriptCreate().GetScript() | Out-String
                        $sql = $sql -replace [Regex]::Escape("'$source'"), "'$destinstance'"
                        Write-Message -Level Debug -Message $sql
                        Write-Message -Level Verbose -Message "Migrating collection set $collectionName"
                        $destServer.Query($sql)
                        
                        $copyCollectionSetStatus.Status = "Successful"
                        $copyCollectionSetStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
                    }
                    catch {
                        $copyCollectionSetStatus.Status = "Failed to create collection"
                        $copyCollectionSetStatus.Notes = (Get-ErrorMessage -Record $_)
                        
                        Stop-Function -Message "Issue creating collection set" -Target $collectionName -ErrorRecord $_
                    }
                    
                    try {
                        if ($set.IsRunning) {
                            Write-Message -Level Verbose -Message "Starting collection set $collectionName"
                            $destStore.CollectionSets.Refresh()
                            $destStore.CollectionSets[$collectionName].Start()
                        }
                        
                        $copyCollectionSetStatus.Status = "Successful started Collection"
                        $copyCollectionSetStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
                    }
                    catch {
                        $copyCollectionSetStatus.Status = "Failed to start collection"
                        $copyCollectionSetStatus.Notes = (Get-ErrorMessage -Record $_)
                        $copyCollectionSetStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
                        
                        Stop-Function -Message "Issue starting collection set" -Target $collectionName -ErrorRecord $_
                    }
                }
            }
        }
    }
    end {
        if (Test-FunctionInterrupt) { return }
        Test-DbaDeprecation -DeprecatedOn "1.0.0" -EnableException:$false -Alias Copy-SqlDataCollector
    }
}
function Copy-DbaSqlPolicyManagement {
    <#
        .SYNOPSIS
            Migrates SQL Policy Based Management Objects, including both policies and conditions.

        .DESCRIPTION
            By default, all policies and conditions are copied. If an object already exist on the destination, it will be skipped unless -Force is used.

            The -Policy and -Condition parameters are auto-populated for command-line completion and can be used to copy only specific objects.

        .PARAMETER Source
            Source SQL Server.You must have sysadmin access and server version must be SQL Server version 2008 or higher.

        .PARAMETER SourceSqlCredential
            Login to the target instance using alternative credentials. Windows and SQL Authentication supported. Accepts credential objects (Get-Credential)

        .PARAMETER Destination
            Destination Sql Server. You must have sysadmin access and server version must be SQL Server version 2008 or higher.

        .PARAMETER DestinationSqlCredential
            Login to the target instance using alternative credentials. Windows and SQL Authentication supported. Accepts credential objects (Get-Credential)

        .PARAMETER Policy
            The policy(ies) to process - this list is auto-populated from the server. If unspecified, all policies will be processed.

        .PARAMETER ExcludePolicy
            The policy(ies) to exclude - this list is auto-populated from the server

        .PARAMETER Condition
            The condition(s) to process - this list is auto-populated from the server. If unspecified, all conditions will be processed.

        .PARAMETER ExcludeCondition
            The condition(s) to exclude - this list is auto-populated from the server

        .PARAMETER Force
            If policies exists on destination server, it will be dropped and recreated.

        .PARAMETER WhatIf
            Shows what would happen if the command were to run. No actions are actually performed.

        .PARAMETER Confirm
            Prompts you for confirmation before executing any changing operations within the command.

        .PARAMETER EnableException
            By default, when something goes wrong we try to catch it, interpret it and give you a friendly warning message.
            This avoids overwhelming you with "sea of red" exceptions, but is inconvenient because it basically disables advanced scripting.
            Using this switch turns this "nice by default" feature off and enables you to catch exceptions with your own try/catch.

        .NOTES
            Tags: Migration
            Author: Chrissy LeMaire (@cl), netnerds.net
            Requires: sysadmin access on SQL Servers

            Website: https://dbatools.io
            Copyright: (C) Chrissy LeMaire, [email protected]
            License: MIT https://opensource.org/licenses/MIT

        .LINK
            https://dbatools.io/Copy-DbaSqlPolicyManagement

        .EXAMPLE
            Copy-DbaSqlPolicyManagement -Source sqlserver2014a -Destination sqlcluster

            Copies all policies and conditions from sqlserver2014a to sqlcluster, using Windows credentials.

        .EXAMPLE
            Copy-DbaSqlPolicyManagement -Source sqlserver2014a -Destination sqlcluster -SourceSqlCredential $cred

            Copies all policies and conditions from sqlserver2014a to sqlcluster, using SQL credentials for sqlserver2014a and Windows credentials for sqlcluster.

        .EXAMPLE
            Copy-DbaSqlPolicyManagement -Source sqlserver2014a -Destination sqlcluster -WhatIf

            Shows what would happen if the command were executed.

        .EXAMPLE
            Copy-DbaSqlPolicyManagement -Source sqlserver2014a -Destination sqlcluster -Policy 'xp_cmdshell must be disabled'

            Copies only one policy, 'xp_cmdshell must be disabled' from sqlserver2014a to sqlcluster. No conditions are migrated.
    #>
    [CmdletBinding(DefaultParameterSetName = "Default", SupportsShouldProcess = $true)]
    param (
        [parameter(Mandatory = $true)]
        [DbaInstanceParameter]$Source,
        [PSCredential]
        $SourceSqlCredential,
        [parameter(Mandatory = $true)]
        [DbaInstanceParameter[]]$Destination,
        [PSCredential]
        $DestinationSqlCredential,
        [object[]]$Policy,
        [object[]]$ExcludePolicy,
        [object[]]$Condition,
        [object[]]$ExcludeCondition,
        [switch]$Force,
        [Alias('Silent')]
        [switch]$EnableException
    )

    begin {
        try {
            Write-Message -Level Verbose -Message "Connecting to $Source"
            $sourceServer = Connect-SqlInstance -SqlInstance $Source -SqlCredential $SourceSqlCredential -MinimumVersion 10
        }
        catch {
            Stop-Function -Message "Failure" -Category ConnectionError -ErrorRecord $_ -Target $Source
            return
        }
        $sourceSqlConn = $sourceServer.ConnectionContext.SqlConnectionObject
        $sourceSqlStoreConnection = New-Object Microsoft.SqlServer.Management.Sdk.Sfc.SqlStoreConnection $sourceSqlConn
        $sourceStore = New-Object  Microsoft.SqlServer.Management.DMF.PolicyStore $sourceSqlStoreConnection
        $storePolicies = $sourceStore.Policies | Where-Object { $_.IsSystemObject -eq $false }
        $storeConditions = $sourceStore.Conditions | Where-Object { $_.IsSystemObject -eq $false }
    }
    process {
        if (Test-FunctionInterrupt) { return }
        foreach ($destinstance in $Destination) {
            try {
                Write-Message -Level Verbose -Message "Connecting to $destinstance"
                $destServer = Connect-SqlInstance -SqlInstance $destinstance -SqlCredential $DestinationSqlCredential -MinimumVersion 10
            }
            catch {
                Stop-Function -Message "Failure" -Category ConnectionError -ErrorRecord $_ -Target $destinstance -Continue
            }
            $destSqlConn = $destServer.ConnectionContext.SqlConnectionObject
            $destSqlStoreConnection = New-Object Microsoft.SqlServer.Management.Sdk.Sfc.SqlStoreConnection $destSqlConn
            $destStore = New-Object  Microsoft.SqlServer.Management.DMF.PolicyStore $destSqlStoreConnection
            
            if ($Policy) {
                $storePolicies = $storePolicies | Where-Object Name -In $Policy
            }
            if ($ExcludePolicy) {
                $storePolicies = $storePolicies | Where-Object Name -NotIn $ExcludePolicy
            }
            if ($Condition) {
                $storeConditions = $storeConditions | Where-Object Name -In $Condition
            }
            if ($ExcludeCondition) {
                $storeConditions = $storeConditions | Where-Object Name -NotIn $ExcludeCondition
            }
            
            if ($Policy -and $Condition) {
                $storeConditions = $null
                $storePolicies = $null
            }
            
        <#
                        Conditions
        #>
            
            Write-Message -Level Verbose -Message "Migrating conditions"
            foreach ($condition in $storeConditions) {
                $conditionName = $condition.Name
                
                $copyConditionStatus = [pscustomobject]@{
                    SourceServer = $sourceServer.Name
                    DestinationServer = $destServer.Name
                    Name         = $conditionName
                    Type         = "Policy Condition"
                    Status       = $null
                    Notes        = $null
                    DateTime     = [DbaDateTime](Get-Date)
                }
                
                if ($null -ne $destStore.Conditions[$conditionName]) {
                    if ($force -eq $false) {
                        Write-Message -Level Verbose -Message "condition '$conditionName' was skipped because it already exists on $destinstance. Use -Force to drop and recreate"
                        
                        $copyConditionStatus.Status = "Skipped"
                        $copyConditionStatus.Notes = "Already exists"
                        $copyConditionStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
                        continue
                    }
                    else {
                        if ($Pscmdlet.ShouldProcess($destinstance, "Attempting to drop $conditionName")) {
                            Write-Message -Level Verbose -Message "Condition '$conditionName' exists on $destinstance. Force specified. Dropping $conditionName."
                            
                            try {
                                $dependentPolicies = $destStore.Conditions[$conditionName].EnumDependentPolicies()
                                foreach ($dependent in $dependentPolicies) {
                                    $dependent.Drop()
                                    $destStore.Conditions.Refresh()
                                }
                                $destStore.Conditions[$conditionName].Drop()
                            }
                            catch {
                                $copyConditionStatus.Status = "Failed"
                                $copyConditionStatus.Notes = (Get-ErrorMessage -Record $_).Message
                                $copyConditionStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
                                Stop-Function -Message "Issue dropping condition on $destinstance" -Target $conditionName -ErrorRecord $_ -Continue
                            }
                        }
                    }
                }
                
                if ($Pscmdlet.ShouldProcess($destinstance, "Migrating condition $conditionName")) {
                    try {
                        $sql = $condition.ScriptCreate().GetScript() | Out-String
                        Write-Message -Level Debug -Message $sql
                        Write-Message -Level Verbose -Message "Copying condition $conditionName"
                        $null = $destServer.Query($sql)
                        $destStore.Conditions.Refresh()
                        
                        $copyConditionStatus.Status = "Successful"
                        $copyConditionStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
                    }
                    catch {
                        $copyConditionStatus.Status = "Failed"
                        $copyConditionStatus.Notes = (Get-ErrorMessage -Record $_).Message
                        $copyConditionStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
                        
                        Stop-Function -Message "Issue creating condition on $destinstance" -Target $conditionName -ErrorRecord $_
                    }
                }
            }
            
        <#
                        Policies
        #>
            
            Write-Message -Level Verbose -Message "Migrating policies"
            foreach ($policy in $storePolicies) {
                $policyName = $policy.Name
                
                $copyPolicyStatus = [pscustomobject]@{
                    SourceServer = $sourceServer.Name
                    DestinationServer = $destServer.Name
                    Name         = $policyName
                    Type         = "Policy"
                    Status       = $null
                    Notes        = $null
                    DateTime     = [DbaDateTime](Get-Date)
                }
                
                if ($null -ne $destStore.Policies[$policyName]) {
                    if ($force -eq $false) {
                        Write-Message -Level Verbose -Message "Policy '$policyName' was skipped because it already exists on $destinstance. Use -Force to drop and recreate"
                        
                        $copyPolicyStatus.Status = "Skipped"
                        $copyPolicyStatus.Notes = "Already exists"
                        $copyPolicyStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
                        continue
                    }
                    else {
                        if ($Pscmdlet.ShouldProcess($destinstance, "Attempting to drop $policyName")) {
                            Write-Message -Level Verbose -Message "Policy '$policyName' exists on $destinstance. Force specified. Dropping $policyName."
                            
                            try {
                                $destStore.Policies[$policyName].Drop()
                                $destStore.Policies.refresh()
                            }
                            catch {
                                $copyPolicyStatus.Status = "Failed"
                                $copyPolicyStatus.Notes = (Get-ErrorMessage -Record $_).Message
                                $copyPolicyStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
                                
                                Stop-Function -Message "Issue dropping policy on $destinstance" -Target $policyName -ErrorRecord $_ -Continue
                            }
                        }
                    }
                }
                
                if ($Pscmdlet.ShouldProcess($destinstance, "Migrating policy $policyName")) {
                    try {
                        $destStore.Conditions.Refresh()
                        $destStore.Policies.Refresh()
                        $sql = $policy.ScriptCreateWithDependencies().GetScript() | Out-String
                        Write-Message -Level Debug -Message $sql
                        Write-Message -Level Verbose -Message "Copying policy $policyName"
                        $null = $destServer.Query($sql)
                        
                        $copyPolicyStatus.Status = "Successful"
                        $copyPolicyStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
                    }
                    catch {
                        $copyPolicyStatus.Status = "Failed"
                        $copyPolicyStatus.Notes = (Get-ErrorMessage -Record $_).Message
                        $copyPolicyStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
                        
                        # This is usually because of a duplicate dependent from above. Just skip for now.
                        Stop-Function -Message "Issue creating policy on $destinstance" -Target $policyName -ErrorRecord $_ -Continue
                    }
                }
            }
        }
    }
    end {
        Test-DbaDeprecation -DeprecatedOn "1.0.0" -EnableException:$false -Alias Copy-SqlPolicyManagement
    }
}
function Copy-DbaSqlServerAgent {
    <#
        .SYNOPSIS
            Copy SQL Server Agent from one server to another.

        .DESCRIPTION
            A wrapper function that calls the associated Copy command for each of the object types seen in SSMS under SQL Server Agent. This also copies all of the the SQL Agent properties (job history max rows, DBMail profile name, etc.).

            You must have sysadmin access and server version must be SQL Server version 2000 or greater.

        .PARAMETER Source
            Source SQL Server. You must have sysadmin access and server version must be SQL Server version 2000 or higher.

        .PARAMETER SourceSqlCredential
            Login to the target instance using alternative credentials. Windows and SQL Authentication supported. Accepts credential objects (Get-Credential)

        .PARAMETER Destination
            Destination SQL Server. You must have sysadmin access and the server must be SQL Server 2000 or higher.

        .PARAMETER DestinationSqlCredential
            Login to the target instance using alternative credentials. Windows and SQL Authentication supported. Accepts credential objects (Get-Credential)

        .PARAMETER DisableJobsOnDestination
            If this switch is enabled, the jobs will be disabled on Destination after copying.

        .PARAMETER DisableJobsOnSource
            If this switch is enabled, the jobs will be disabled on Source after copying.

        .PARAMETER WhatIf
            If this switch is enabled, no actions are performed but informational messages will be displayed that explain what would happen if the command were to run.

        .PARAMETER Confirm
            If this switch is enabled, you will be prompted for confirmation before executing any operations that change state.

        .PARAMETER EnableException
            By default, when something goes wrong we try to catch it, interpret it and give you a friendly warning message.
            This avoids overwhelming you with "sea of red" exceptions, but is inconvenient because it basically disables advanced scripting.
            Using this switch turns this "nice by default" feature off and enables you to catch exceptions with your own try/catch.

        .PARAMETER Force
            If this switch is enabled, existing objects on Destination with matching names from Source will be dropped, then copied.

        .NOTES
            Tags: Migration, SqlServerAgent, SqlAgent
            Author: Chrissy LeMaire (@cl), netnerds.net
            Requires: sysadmin access on SQL Servers

            Website: https://dbatools.io
            Copyright: (C) Chrissy LeMaire, [email protected]
            License: MIT https://opensource.org/licenses/MIT

        .LINK
            https://dbatools.io/Copy-DbaSqlServerAgent

        .EXAMPLE
            Copy-DbaSqlServerAgent -Source sqlserver2014a -Destination sqlcluster

            Copies all job server objects from sqlserver2014a to sqlcluster using Windows credentials for authentication. If job objects with the same name exist on sqlcluster, they will be skipped.

        .EXAMPLE
            Copy-DbaSqlServerAgent -Source sqlserver2014a -Destination sqlcluster -SourceSqlCredential $cred

            Copies all job objects from sqlserver2014a to sqlcluster using SQL credentials to authentication to sqlserver2014a and Windows credentials to authenticate to sqlcluster.

        .EXAMPLE
            Copy-DbaSqlServerAgent -Source sqlserver2014a -Destination sqlcluster -WhatIf

            Shows what would happen if the command were executed.
    #>
    [cmdletbinding(SupportsShouldProcess = $true)]
    param (
        [parameter(Mandatory = $true)]
        [DbaInstanceParameter]$Source,
        [PSCredential]$SourceSqlCredential,
        [parameter(Mandatory = $true)]
        [DbaInstanceParameter[]]$Destination,
        [PSCredential]$DestinationSqlCredential,
        [Switch]$DisableJobsOnDestination,
        [Switch]$DisableJobsOnSource,
        [switch]$Force,
        [Alias('Silent')]
        [switch]$EnableException
    )

    begin {
        try {
            Write-Message -Level Verbose -Message "Connecting to $Source"
            $sourceServer = Connect-SqlInstance -SqlInstance $Source -SqlCredential $SourceSqlCredential
        }
        catch {
            Stop-Function -Message "Failure" -Category ConnectionError -ErrorRecord $_ -Target $Source
            return
        }
        Invoke-SmoCheck -SqlInstance $sourceServer
        $sourceAgent = $sourceServer.JobServer
    }
    process {
        if (Test-FunctionInterrupt) { return }
        foreach ($destinstance in $Destination) {
            try {
                Write-Message -Level Verbose -Message "Connecting to $destinstance"
                $destServer = Connect-SqlInstance -SqlInstance $destinstance -SqlCredential $DestinationSqlCredential
            }
            catch {
                Stop-Function -Message "Failure" -Category ConnectionError -ErrorRecord $_ -Target $destinstance -Continue
            }
            Invoke-SmoCheck -SqlInstance $destServer
            # All of these support whatif inside of them
            Copy-DbaAgentCategory -Source $sourceServer -Destination $destServer -Force:$force
            
            $destServer.JobServer.JobCategories.Refresh()
            $destServer.JobServer.OperatorCategories.Refresh()
            $destServer.JobServer.AlertCategories.Refresh()
            
            Copy-DbaAgentOperator -Source $sourceServer -Destination $destServer -Force:$force
            $destServer.JobServer.Operators.Refresh()
            
            Copy-DbaAgentAlert -Source $sourceServer -Destination $destServer -Force:$force -IncludeDefaults
            $destServer.JobServer.Alerts.Refresh()
            
            Copy-DbaAgentProxyAccount -Source $sourceServer -Destination $destServer -Force:$force
            $destServer.JobServer.ProxyAccounts.Refresh()
            
            Copy-DbaAgentSharedSchedule -Source $sourceServer -Destination $destServer -Force:$force
            $destServer.JobServer.SharedSchedules.Refresh()
            
            $destServer.JobServer.Refresh()
            $destServer.Refresh()
            Copy-DbaAgentJob -Source $sourceServer -Destination $destServer -Force:$force -DisableOnDestination:$DisableJobsOnDestination -DisableOnSource:$DisableJobsOnSource
            
            # To do
        <#
            Copy-DbaAgentMasterServer -Source $sourceServer -Destination $destServer -Force:$force
            Copy-DbaAgentTargetServer -Source $sourceServer -Destination $destServer -Force:$force
            Copy-DbaAgentTargetServerGroup -Source $sourceServer -Destination $destServer -Force:$force
        #>
            
        <# Here are the properties which must be migrated separately #>
            $copyAgentPropStatus = [pscustomobject]@{
                SourceServer = $sourceServer.Name
                DestinationServer = $destServer.Name
                Name         = "Server level properties"
                Type         = "Agent Properties"
                Status       = $null
                Notes        = $null
                DateTime     = [DbaDateTime](Get-Date)
            }
            
            if ($Pscmdlet.ShouldProcess($destinstance, "Copying Agent Properties")) {
                try {
                    Write-Message -Level Verbose -Message "Copying SQL Agent Properties"
                    $sql = $sourceAgent.Script() | Out-String
                    $sql = $sql -replace [Regex]::Escape("'$source'"), "'$destinstance'"
                    $sql = $sql -replace [Regex]::Escape("@errorlog_file="), [Regex]::Escape("--@errorlog_file=")
                    $sql = $sql -replace [Regex]::Escape("@auto_start="), [Regex]::Escape("--@auto_start=")
                    Write-Message -Level Debug -Message $sql
                    $null = $destServer.Query($sql)
                    
                    $copyAgentPropStatus.Status = "Successful"
                    $copyAgentPropStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
                }
                catch {
                    $message = $_.Exception.InnerException.InnerException.InnerException.Message
                    if (-not $message) { $message = $_.Exception.Message }
                    $copyAgentPropStatus.Status = "Failed"
                    $copyAgentPropStatus.Notes = $message
                    $copyAgentPropStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
                    Stop-Function -Message $message -Target $destinstance
                }
            }
        }
    }
    end {
        Test-DbaDeprecation -DeprecatedOn "1.0.0" -EnableException:$false -Alias Copy-SqlServerAgent
    }
}
#ValidationTags#Messaging#
function Copy-DbaSsisCatalog {
    <#
        .SYNOPSIS
           Copy-DbaSsisCatalog migrates Folders, SSIS projects, and environments from one SQL Server to another.

        .DESCRIPTION
            By default, all folders, projects, and environments are copied. The -Project parameter can be specified to copy only one project, if desired.

            The parameters get more granular from the Folder level. For example, specifying -Folder will only deploy projects/environments from within that folder.

        .PARAMETER Source
            Source SQL Server. You must have sysadmin access and server version must be SQL Server version 2012 or higher.

        .PARAMETER SourceSqlCredential
            Login to the target instance using alternative credentials. Windows and SQL Authentication supported. Accepts credential objects (Get-Credential)

        .PARAMETER Destination
            Destination SQL Server. You must have sysadmin access and the server must be SQL Server 2012 or higher.

        .PARAMETER DestinationSqlCredential
            Login to the target instance using alternative credentials. Windows and SQL Authentication supported. Accepts credential objects (Get-Credential)

        .PARAMETER Force
            If this switch is enabled, the SSIS Catalog will be dropped and recreated on Destination if it already exists.

        .PARAMETER Project
            Specifies a source Project name.

        .PARAMETER Folder
            Specifies a source folder name.

        .PARAMETER Environment
            Specifies an environment to copy.

        .PARAMETER EnableSqlClr
            If this switch is enabled and Destination does not have the SQL CLR configuration option enabled, user prompts for enabling it on Destination will be skipped. SQL CLR is required for SSISDB.

        .PARAMETER CreateCatalogPassword
            Specifies a secure string to use in creating an SSISDB catalog on Destination. If this is specified, prompts for the password will be skipped.

        .PARAMETER WhatIf
            If this switch is enabled, no actions are performed but informational messages will be displayed that explain what would happen if the command were to run.

        .PARAMETER Confirm
            If this switch is enabled, you will be prompted for confirmation before executing any operations that change state.

        .PARAMETER EnableException
            By default, when something goes wrong we try to catch it, interpret it and give you a friendly warning message.
            This avoids overwhelming you with "sea of red" exceptions, but is inconvenient because it basically disables advanced scripting.
            Using this switch turns this "nice by default" feature off and enables you to catch exceptions with your own try/catch.

        .NOTES
            Tags: Migration, SSIS
            Author: Phil Schwartz (philschwartz.me, @pschwartzzz)

            dbatools PowerShell module (https://dbatools.io, [email protected])
            Copyright (C) 2016 Chrissy LeMaire
            License: MIT https://opensource.org/licenses/MIT

        .LINK
            https://dbatools.io/Copy-DbaSsisCatalog

        .EXAMPLE
            Copy-DbaSsisCatalog -Source sqlserver2014a -Destination sqlcluster

            Copies all folders, environments and SSIS Projects from sqlserver2014a to sqlcluster, using Windows credentials to authenticate to both instances. If folders with the same name exist on the destination they will be skipped, but projects will be redeployed.

        .EXAMPLE
            Copy-DbaSsisCatalog -Source sqlserver2014a -Destination sqlcluster -Project Archive_Tables -SourceSqlCredential $cred -Force

            Copies a single Project, the Archive_Tables Project, from sqlserver2014a to sqlcluster using SQL credentials to authenticate to sqlserver2014a and Windows credentials to authenticate to sqlcluster. If a Project with the same name exists on sqlcluster, it will be deleted and recreated because -Force was used.

        .EXAMPLE
            Copy-DbaSsisCatalog -Source sqlserver2014a -Destination sqlcluster -WhatIf -Force

            Shows what would happen if the command were executed using force.

        .EXAMPLE
            $SecurePW = Read-Host "Enter password" -AsSecureString
            Copy-DbaSsisCatalog -Source sqlserver2014a -Destination sqlcluster -CreateCatalogPassword $SecurePW

            Deploy entire SSIS catalog to an instance without a destination catalog. User prompts for creating the catalog on Destination will be bypassed.

    #>
    [CmdletBinding(DefaultParameterSetName = "Default", SupportsShouldProcess = $true)]
    param (
        [parameter(Mandatory = $true)]
        [DbaInstanceParameter]$Source,
        [parameter(Mandatory = $true)]
        [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-DbaSqlService -ComputerName $Computer -Type SSIS
            if ($result) {
                $running = $false
                foreach ($service in $result) {
                    if (!$service.State -eq "Running") {
                        Write-Message -Level Warning -Message "Service $($service.DisplayName) was found on the destination, but is currently not running."
                    }
                    else {
                        Write-Message -Level Verbose -Message "Service $($service.DisplayName) was found running on the destination."
                        $running = $true
                    }
                }
            }
            else {
                throw "No Integration Services service was found on the destination, please ensure the feature is installed and running."
            }
        }

        function Invoke-ProjectDeployment {
            param (
                [String]$Project,
                [String]$Folder
            )
            $sqlConn = New-Object System.Data.SqlClient.SqlConnection
            $sqlConn.ConnectionString = $sourceConnection.ConnectionContext.ConnectionString
            if ($sqlConn.State -eq "Closed") {
                $sqlConn.Open()
            }
            try {
                Write-Message -Level Verbose -Message "Deploying project $Project from folder $Folder."
                $cmd = New-Object System.Data.SqlClient.SqlCommand
                $cmd.CommandType = "StoredProcedure"
                $cmd.connection = $sqlConn
                $cmd.CommandText = "SSISDB.Catalog.get_project"
                $cmd.Parameters.Add("@folder_name", $Folder) | out-null;
                $cmd.Parameters.Add("@project_name", $Project) | out-null;
                [byte[]]$results = $cmd.ExecuteScalar();
                if ($null -ne $results) {
                    $destFolder = $destinationFolders | Where-Object { $_.Name -eq $Folder }
                    $deployedProject = $destFolder.DeployProject($Project, $results)
                    if ($deployedProject.Status -ne "Success") {
                        Stop-Function -Message "An error occurred deploying project $Project." -Target $Project -Continue
                    }
                }
                else {
                    Stop-Function -Message "Failed deploying $Project from folder $Folder." -Target $Project -Continue
                }
            }
            catch {
                Stop-Function -Message "Failed to deploy project." -Target $Project -ErrorRecord $_
            }
            finally {
                if ($sqlConn.State -eq "Open") {
                    $sqlConn.Close()
                }
            }
        }
        function New-CatalogFolder {
            param (
                [String]$Folder,
                [String]$Description,
                [Switch]$Force
            )
            if ($Force) {
                $remove = $destinationFolders | Where-Object { $_.Name -eq $Folder }
                $envs = $remove.Environments.Name
                foreach ($e in $envs) {
                    $remove.Environments[$e].Drop()
                }
                $projs = $remove.Projects.Name
                foreach ($p in $projs) {
                    $remove.Projects[$p].Drop()
                }
                $remove.Drop()
                $destinationCatalog.Alter()
                $destinationCatalog.Refresh()
            }
            Write-Message -Level Verbose -Message "Creating folder $Folder."
            $destFolder = New-Object "$ISNamespace.CatalogFolder" ($destinationCatalog, $Folder, $Description)
            $destFolder.Create()
            $destFolder.Alter()
            $destFolder.Refresh()
        }
        function New-FolderEnvironment {
            param (
                [String]$Folder,
                [String]$Environment,
                [Switch]$Force
            )
            $envDestFolder = $destinationFolders | Where-Object { $_.Name -eq $Folder }
            if ($force) {
                $envDestFolder.Environments[$Environment].Drop()
                $envDestFolder.Alter()
                $envDestFolder.Refresh()
            }
            $srcEnv = ($sourceFolders | Where-Object { $_.Name -eq $Folder }).Environments[$Environment]
            $targetEnv = New-Object "$ISNamespace.EnvironmentInfo" ($envDestFolder, $srcEnv.Name, $srcEnv.Description)
            foreach ($var in $srcEnv.Variables) {
                if ($var.Value.ToString() -eq "") {
                    $finalValue = ""
                }
                else {
                    $finalValue = $var.Value
                }
                $targetEnv.Variables.Add($var.Name, $var.Type, $finalValue, $var.Sensitive, $var.Description)
            }
            Write-Message -Level Verbose -Message "Creating environment $Environment."
            $targetEnv.Create()
            $targetEnv.Alter()
            $targetEnv.Refresh()
        }
        function New-SSISDBCatalog {
            param (
                [System.Security.SecureString]$Password
            )

            if (!$Password) {
                Write-Message -Level Verbose -Message "SSISDB Catalog requires a password."
                $pass1 = Read-Host "Enter a password" -AsSecureString
                $plainTextPass1 = [Runtime.InteropServices.Marshal]::PtrToStringAuto([Runtime.InteropServices.Marshal]::SecureStringToBSTR($pass1))
                $pass2 = Read-Host "Re-enter password" -AsSecureString
                $plainTextPass2 = [Runtime.InteropServices.Marshal]::PtrToStringAuto([Runtime.InteropServices.Marshal]::SecureStringToBSTR($pass2))
                if ($plainTextPass1 -ne $plainTextPass2) {
                    throw "Validation error, passwords entered do not match."
                }
                $plainTextPass = $plainTextPass1
            }
            else {
                $plainTextPass = [Runtime.InteropServices.Marshal]::PtrToStringAuto([Runtime.InteropServices.Marshal]::SecureStringToBSTR($Password))
            }

            $catalog = New-Object "$ISNamespace.Catalog" ($destinationSSIS, "SSISDB", $plainTextPass)
            $catalog.Create()
            $catalog.Refresh()
        }

        $ISNamespace = "Microsoft.SqlServer.Management.IntegrationServices"
        
        try {
            Write-Message -Level Verbose -Message "Connecting to $Source"
            $sourceServer = Connect-SqlInstance -SqlInstance $Source -SqlCredential $SourceSqlCredential -MinimumVersion 11
        }
        catch {
            Stop-Function -Message "Failure" -Category ConnectionError -ErrorRecord $_ -Target $Source
            return
        }
        
        try {
            Write-Message -Level Verbose -Message "Connecting to $Source integration services."
            $sourceSSIS = New-Object "$ISNamespace.IntegrationServices" $sourceConnection
        }
        catch {
            Stop-Function -Message "There was an error connecting to the source integration services." -Target $sourceConnection -ErrorRecord $_
            return
        }
        
        $sourceCatalog = $sourceSSIS.Catalogs | Where-Object { $_.Name -eq "SSISDB" }
        if (!$sourceCatalog) {
            Stop-Function -Message "The source SSISDB catalog on $Source does not exist."
            return
        }
        $sourceFolders = $sourceCatalog.Folders
    }
    process {
        if (Test-FunctionInterrupt) { return }
        foreach ($destinstance in $Destination) {
            try {
                Write-Message -Level Verbose -Message "Connecting to $destinstance"
                $destinationConnection = Connect-SqlInstance -SqlInstance $destinstance -SqlCredential $DestinationSqlCredential -MinimumVersion 1
            }
            catch {
                Stop-Function -Message "Failure" -Category ConnectionError -ErrorRecord $_ -Target $destinstance -Continue
            }
            
            try {
                Get-RemoteIntegrationService -Computer $destinstance
            }
            catch {
                Stop-Function -Message "An error occurred when checking the destination for Integration Services. Is Integration Services installed?" -Target $destinstance -ErrorRecord $_
            }
            
            try {
                Write-Message -Level Verbose -Message "Connecting to $destinstance integration services."
                $destinationSSIS = New-Object "$ISNamespace.IntegrationServices" $destinationConnection
            }
            catch {
                Stop-Function -Message "There was an error connecting to the destination integration services." -Target $destinationCon -ErrorRecord $_
            }
            
            $destinationCatalog = $destinationSSIS.Catalogs | Where-Object { $_.Name -eq "SSISDB" }
            $destinationFolders = $destinationCatalog.Folders
            
            if (!$destinationCatalog) {
                if (!$destinationConnection.Configuration.IsSqlClrEnabled.ConfigValue) {
                    if ($Pscmdlet.ShouldProcess($destinstance, "Enabling SQL CLR configuration option.")) {
                        if (!$EnableSqlClr) {
                            $message = "The destination does not have SQL CLR configuration option enabled (required by SSISDB), would you like to enable it?"
                            $yes = New-Object System.Management.Automation.Host.ChoiceDescription "&Yes", "Enable SQL CLR on $destinstance."
                            $no = New-Object System.Management.Automation.Host.ChoiceDescription "&No", "Exit."
                            $options = [System.Management.Automation.Host.ChoiceDescription[]]($yes, $no)
                            $result = $host.ui.PromptForChoice($null, $message, $options, 0)
                            switch ($result) {
                                0 {
                                    continue
                                }
                                1 {
                                    return
                                }
                            }
                        }
                        Write-Message -Level Verbose -Message "Enabling SQL CLR configuration option at the destination."
                        if ($destinationConnection.Configuration.ShowAdvancedOptions.ConfigValue -eq $false) {
                            $destinationConnection.Configuration.ShowAdvancedOptions.ConfigValue = $true
                            $changeback = $true
                        }
                        
                        $destinationConnection.Configuration.IsSqlClrEnabled.ConfigValue = $true
                        
                        if ($changeback -eq $true) {
                            $destinationConnection.Configuration.ShowAdvancedOptions.ConfigValue = $false
                        }
                        $destinationConnection.Configuration.Alter()
                    }
                }
                else {
                    Write-Message -Level Verbose -Message "SQL CLR configuration option is already enabled at the destination."
                }
                if ($Pscmdlet.ShouldProcess($destinstance, "Create destination SSISDB Catalog")) {
                    if (!$CreateCatalogPassword) {
                        $message = "The destination SSISDB catalog does not exist, would you like to create one?"
                        $yes = New-Object System.Management.Automation.Host.ChoiceDescription "&Yes", "Create an SSISDB catalog on $destinstance."
                        $no = New-Object System.Management.Automation.Host.ChoiceDescription "&No", "Exit."
                        $options = [System.Management.Automation.Host.ChoiceDescription[]]($yes, $no)
                        $result = $host.ui.PromptForChoice($null, $message, $options, 0)
                        switch ($result) {
                            0 {
                                New-SSISDBCatalog
                            }
                            1 {
                                return
                            }
                        }
                    }
                    else {
                        New-SSISDBCatalog -Password $CreateCatalogPassword
                    }
                    
                    $destinationSSIS.Refresh()
                    $destinationCatalog = $destinationSSIS.Catalogs | Where-Object { $_.Name -eq "SSISDB" }
                    $destinationFolders = $destinationCatalog.Folders
                }
                else {
                    throw "The destination SSISDB catalog does not exist."
                }
            }
            if ($folder) {
                if ($sourceFolders.Name -contains $folder) {
                    $srcFolder = $sourceFolders | Where-Object { $_.Name -eq $folder }
                    if ($destinationFolders.Name -contains $folder) {
                        if (!$force) {
                            Write-Message -Level Warning -Message "Integration services catalog folder $folder exists at destination. Use -Force to drop and recreate."
                        }
                        else {
                            if ($Pscmdlet.ShouldProcess($destinstance, "Dropping folder $folder and recreating")) {
                                try {
                                    New-CatalogFolder -Folder $srcFolder.Name -Description $srcFolder.Description -Force
                                }
                                catch {
                                    Stop-Function -Message "Issue dropping folder" -Target $folder -ErrorRecord $_
                                }
                                
                            }
                        }
                    }
                    else {
                        if ($Pscmdlet.ShouldProcess($destinstance, "Creating folder $folder")) {
                            try {
                                New-CatalogFolder -Folder $srcFolder.Name -Description $srcFolder.Description
                            }
                            catch {
                                Stop-Function -Message "Issue creating folder" -Target $folder -ErrorRecord $_
                            }
                        }
                    }
                }
                else {
                    throw "The source folder provided does not exist in the source Integration Services catalog."
                }
            }
            else {
                foreach ($srcFolder in $sourceFolders) {
                    if ($destinationFolders.Name -notcontains $srcFolder.Name) {
                        if ($Pscmdlet.ShouldProcess($destinstance, "Creating folder $($srcFolder.Name)")) {
                            try {
                                New-CatalogFolder -Folder $srcFolder.Name -Description $srcFolder.Description
                            }
                            catch {
                                Stop-Function -Message "Issue creating folder" -Target $srcFolder -ErrorRecord $_ -Continue
                            }
                        }
                    }
                    else {
                        if (!$force) {
                            Write-Message -Level Warning -Message "Integration services catalog folder $($srcFolder.Name) exists at destination. Use -Force to drop and recreate."
                            continue
                        }
                        else {
                            if ($Pscmdlet.ShouldProcess($destinstance, "Dropping folder $($srcFolder.Name) and recreating")) {
                                try {
                                    New-CatalogFolder -Folder $srcFolder.Name -Description $srcFolder.Description -Force
                                }
                                catch {
                                    Stop-Function -Message "Issue dropping folder" -Target $srcFolder -ErrorRecord $_
                                }
                            }
                        }
                    }
                }
            }
            
            # Refresh folders for project and environment deployment
            if ($Pscmdlet.ShouldProcess($destinstance, "Refresh folders for project deployment")) {
                try {
                    $destinationFolders.Alter()
                }
                catch {
                    # Sometimes it says Alter() doesn't exist
                }
                $destinationFolders.Refresh()
            }
            
            if ($folder) {
                $sourceFolders = $sourceFolders | Where-Object { $_.Name -eq $folder }
                if (!$sourceFolders) {
                    throw "The source folder $folder does not exist in the source Integration Services catalog."
                }
            }
            if ($project) {
                $folderDeploy = $sourceFolders | Where-Object { $_.Projects.Name -eq $project }
                if (!$folderDeploy) {
                    throw "The project $project cannot be found in the source Integration Services catalog."
                }
                else {
                    foreach ($f in $folderDeploy) {
                        if ($Pscmdlet.ShouldProcess($destinstance, "Deploying project $project from folder $($f.Name)")) {
                            try {
                                Invoke-ProjectDeployment -Folder $f.Name -Project $project
                            }
                            catch {
                                Stop-Function -Message "Issue deploying project" -Target $project -ErrorRecord $_
                            }
                        }
                    }
                }
            }
            else {
                foreach ($curFolder in $sourceFolders) {
                    foreach ($proj in $curFolder.Projects) {
                        if ($Pscmdlet.ShouldProcess($destinstance, "Deploying project $($proj.Name) from folder $($curFolder.Name)")) {
                            try {
                                Invoke-ProjectDeployment -Project $proj.Name -Folder $curFolder.Name
                            }
                            catch {
                                Stop-Function -Message "Issue deploying project" -Target $proj -ErrorRecord $_
                            }
                        }
                    }
                }
            }
            
            if ($environment) {
                $folderDeploy = $sourceFolders | Where-Object { $_.Environments.Name -eq $environment }
                if (!$folderDeploy) {
                    throw "The environment $environment cannot be found in the source Integration Services catalog."
                }
                else {
                    foreach ($f in $folderDeploy) {
                        if ($destinationFolders[$f.Name].Environments.Name -notcontains $environment) {
                            if ($Pscmdlet.ShouldProcess($destinstance, "Deploying environment $environment from folder $($f.Name)")) {
                                try {
                                    New-FolderEnvironment -Folder $f.Name -Environment $environment
                                }
                                catch {
                                    Stop-Function -Message "Issue deploying environment" -Target $environment -ErrorRecord $_
                                }
                            }
                        }
                        else {
                            if (!$force) {
                                Write-Message -Level Warning -Message "Integration services catalog environment $environment exists in folder $($f.Name) at destination. Use -Force to drop and recreate."
                            }
                            else {
                                If ($Pscmdlet.ShouldProcess($destinstance, "Dropping existing environment $environment and deploying environment $environment from folder $($f.Name)")) {
                                    try {
                                        New-FolderEnvironment -Folder $f.Name -Environment $environment -Force
                                    }
                                    catch {
                                        Stop-Function -Message "Issue dropping existing environment" -Target $environment -ErrorRecord $_
                                    }
                                }
                            }
                        }
                    }
                }
            }
            else {
                foreach ($curFolder in $sourceFolders) {
                    foreach ($env in $curFolder.Environments) {
                        if ($destinationFolders[$curFolder.Name].Environments.Name -notcontains $env.Name) {
                            if ($Pscmdlet.ShouldProcess($destinstance, "Deploying environment $($env.Name) from folder $($curFolder.Name)")) {
                                try {
                                    New-FolderEnvironment -Environment $env.Name -Folder $curFolder.Name
                                }
                                catch {
                                    Stop-Function -Message "Issue deploying environment" -Target $env -ErrorRecord $_
                                }
                            }
                        }
                        else {
                            if (!$force) {
                                Write-Message -Level Warning -Message "Integration services catalog environment $($env.Name) exists in folder $($curFolder.Name) at destination. Use -Force to drop and recreate."
                                continue
                            }
                            else {
                                if ($Pscmdlet.ShouldProcess($destinstance, "Deploying environment $($env.Name) from folder $($curFolder.Name)")) {
                                    try {
                                        New-FolderEnvironment -Environment $env.Name -Folder $curFolder.Name -Force
                                    }
                                    catch {
                                        Stop-Function -Message "Issue deploying environment" -Target $env -ErrorRecord $_
                                    }
                                }
                            }
                        }
                    }
                }
            }
        }
    }
    end {
        Test-DbaDeprecation -DeprecatedOn "1.0.0" -EnableException:$false -Alias Copy-SqlSsisCatalog
    }
}
function Copy-DbaSysDbUserObject {
    <#
        .SYNOPSIS
            Imports all user objects found in source SQL Server's master, msdb and model databases to the destination.

        .DESCRIPTION
            Imports all user objects found in source SQL Server's master, msdb and model databases to the destination. This is useful because many DBAs store backup/maintenance procs/tables/triggers/etc (among other things) in master or msdb.

            It is also useful for migrating objects within the model database.

        .PARAMETER Source
            Source SQL Server. You must have sysadmin access and server version must be SQL Server version 2000 or higher.

        .PARAMETER SourceSqlCredential
            Login to the target instance using alternative credentials. Windows and SQL Authentication supported. Accepts credential objects (Get-Credential)

        .PARAMETER Destination
            Destination SQL Server. You must have sysadmin access and the server must be SQL Server 2000 or higher.

        .PARAMETER DestinationSqlCredential
            Login to the target instance using alternative credentials. Windows and SQL Authentication supported. Accepts credential objects (Get-Credential)

        .PARAMETER Classic
            Perform the migration the old way
    
        .PARAMETER Force
            Drop destination objects first. Has no effect if you use Classic. This doesn't work really well, honestly.

        .PARAMETER WhatIf
            If this switch is enabled, no actions are performed but informational messages will be displayed that explain what would happen if the command were to run.

        .PARAMETER Confirm
            If this switch is enabled, you will be prompted for confirmation before executing any operations that change state.

        .PARAMETER EnableException
            By default, when something goes wrong we try to catch it, interpret it and give you a friendly warning message.
            This avoids overwhelming you with "sea of red" exceptions, but is inconvenient because it basically disables advanced scripting.
            Using this switch turns this "nice by default" feature off and enables you to catch exceptions with your own try/catch.

        .NOTES
            Tags: Migration, SystemDatabase, UserObject

            Website: https://dbatools.io
            Copyright: (C) Chrissy LeMaire, [email protected]
            License: MIT https://opensource.org/licenses/MIT

        .LINK
            https://dbatools.io/Copy-DbaSysDbUserObject

        .EXAMPLE
            Copy-DbaSysDbUserObject $sourceServer $destserver

            Copies user objects from source to destination
    #>
    [CmdletBinding(SupportsShouldProcess = $true)]
    param (
        [Parameter(Mandatory = $true)]
        [ValidateNotNullOrEmpty()]
        [DbaInstanceParameter]$Source,
        [PSCredential]$SourceSqlCredential,
        [Parameter(Mandatory = $true)]
        [ValidateNotNullOrEmpty()]
        [DbaInstanceParameter[]]$Destination,
        [PSCredential]$DestinationSqlCredential,
        [switch]$Force,
        [switch]$Classic,
        [Alias('Silent')]
        [switch]$EnableException
    )
    begin {
        function get-sqltypename ($type) {
            switch ($type) {
                "VIEW" { "view" }
                "SQL_TABLE_VALUED_FUNCTION" { "User table valued fsunction" }
                "DEFAULT_CONSTRAINT" { "User default constraint" }
                "SQL_STORED_PROCEDURE" { "User stored procedure" }
                "RULE" { "User rule" }
                "SQL_INLINE_TABLE_VALUED_FUNCTION" { "User inline table valued function" }
                "SQL_TRIGGER" { "User server trigger" }
                "SQL_SCALAR_FUNCTION" { "User scalar function" }
                default { $type }
            }
        }
    }
    process {
        try {
            Write-Message -Level Verbose -Message "Connecting to $Source"
            $sourceServer = Connect-SqlInstance -SqlInstance $Source -SqlCredential $SourceSqlCredential
        }
        catch {
            Stop-Function -Message "Failure" -Category ConnectionError -ErrorRecord $_ -Target $Source
            return
        }
        
        if (!(Test-SqlSa -SqlInstance $sourceServer -SqlCredential $SourceSqlCredential)) {
            Stop-Function -Message "Not a sysadmin on $source. Quitting."
            return
        }
        
        if (Test-FunctionInterrupt) { return }
        foreach ($destinstance in $Destination) {
            try {
                Write-Message -Level Verbose -Message "Connecting to $destinstance"
                $destServer = Connect-SqlInstance -SqlInstance $destinstance -SqlCredential $DestinationSqlCredential
            }
            catch {
                Stop-Function -Message "Failure" -Category ConnectionError -ErrorRecord $_ -Target $destinstance -Continue
            }
            
            if (!(Test-SqlSa -SqlInstance $destServer -SqlCredential $DestinationSqlCredential)) {
                Stop-Function -Message "Not a sysadmin on $destinstance" -Continue
            }
            
            $systemDbs = "master", "model", "msdb"
            
            if (-not $Classic) {
                foreach ($systemDb in $systemDbs) {
                    $smodb = $sourceServer.databases[$systemDb]
                    $destdb = $destserver.databases[$systemDb]
                    
                    $tables = $smodb.Tables | Where-Object IsSystemObject -ne $true
                    $schemas = $smodb.Schemas | Where-Object IsSystemObject -ne $true
                    
                    foreach ($schema in $schemas) {
                        $copyobject = [pscustomobject]@{
                            SourceServer = $sourceServer.Name
                            DestinationServer = $destServer.Name
                            Name         = $schema
                            Type         = "User schema in $systemDb"
                            Status       = $null
                            Notes        = $null
                            DateTime     = [Sqlcollaborative.Dbatools.Utility.DbaDateTime](Get-Date)
                        }
                        
                        $destschema = $destdb.Schemas | Where-Object Name -eq $schema.Name
                        $schmadoit = $true
                        
                        if ($destschema) {
                            if (-not $force) {
                                $copyobject.Status = "Skipped"
                                $copyobject.Notes = "$schema exists on destination"
                                $schmadoit = $false
                            }
                            else {
                                if ($PSCmdlet.ShouldProcess($destServer, "Dropping schema $schema in $systemDb")) {
                                    try {
                                        Write-Message -Level Verbose -Message "Force specified. Dropping $schema in $destdb on $destinstance"
                                        $destschema.Drop()
                                    }
                                    catch {
                                        $schmadoit = $false
                                        $copyobject.Status = "Failed"
                                        $copyobject.Notes = $_.Exception.InnerException.InnerException.InnerException.Message
                                    }
                                }
                            }
                        }
                        
                        if ($schmadoit) {
                            $transfer = New-Object Microsoft.SqlServer.Management.Smo.Transfer $smodb
                            $null = $transfer.CopyAllObjects = $false
                            $null = $transfer.Options.WithDependencies = $true
                            $null = $transfer.ObjectList.Add($schema)
                            $sql = $transfer.ScriptTransfer()
                            if ($PSCmdlet.ShouldProcess($destServer, "Attempting to add schema $($schema.Name) to $systemDb")) {
                                try {
                                    Write-Message -Level Debug -Message "$sql"
                                    $null = $destServer.Query($sql, $systemDb)
                                    $copyobject.Status = "Successful"
                                    $copyobject.Notes = "May have also created dependencies"
                                }
                                catch {
                                    $copyobject.Status = "Failed"
                                    $copyobject.Notes = (Get-ErrorMessage -Record $_)
                                }
                            }
                        }
                        
                        $copyobject | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
                    }
                    
                    foreach ($table in $tables) {
                        $copyobject = [pscustomobject]@{
                            SourceServer = $sourceServer.Name
                            DestinationServer = $destServer.Name
                            Name         = $table
                            Type         = "User table in $systemDb"
                            Status       = $null
                            Notes        = $null
                            DateTime     = [Sqlcollaborative.Dbatools.Utility.DbaDateTime](Get-Date)
                        }
                        
                        $desttable = $destdb.Tables.Item($table.Name, $table.Schema)
                        $doit = $true
                        
                        if ($desttable) {
                            if (-not $force) {
                                $copyobject.Status = "Skipped"
                                $copyobject.Notes = "$table exists on destination"
                                $doit = $false
                            }
                            else {
                                if ($PSCmdlet.ShouldProcess($destServer, "Dropping table $table in $systemDb")) {
                                    try {
                                        Write-Message -Level Verbose -Message "Force specified. Dropping $table in $destdb on $destinstance"
                                        $desttable.Drop()
                                    }
                                    catch {
                                        $doit = $false
                                        $copyobject.Status = "Failed"
                                        $copyobject.Notes = $_.Exception.InnerException.InnerException.InnerException.Message
                                    }
                                }
                            }
                        }
                        
                        if ($doit) {
                            $transfer = New-Object Microsoft.SqlServer.Management.Smo.Transfer $smodb
                            $null = $transfer.CopyAllObjects = $false
                            $null = $transfer.Options.WithDependencies = $true
                            $null = $transfer.ObjectList.Add($table)
                            $sql = $transfer.ScriptTransfer()
                            if ($PSCmdlet.ShouldProcess($destServer, "Attempting to add table $table to $systemDb")) {
                                try {
                                    Write-Message -Level Debug -Message "$sql"
                                    $null = $destServer.Query($sql, $systemDb)
                                    $copyobject.Status = "Successful"
                                    $copyobject.Notes = "May have also created dependencies"
                                }
                                catch {
                                    $copyobject.Status = "Failed"
                                    $copyobject.Notes = (Get-ErrorMessage -Record $_)
                                }
                            }
                        }
                        $copyobject | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
                    }
                    
                    $userobjects = Get-DbaSqlModule -SqlInstance $sourceserver -Database $systemDb -NoSystemObjects | Sort-Object Type
                    Write-Message -Level Verbose -Message "Copying from $systemDb"
                    foreach ($userobject in $userobjects) {
                        
                        $name = "[$($userobject.SchemaName)].[$($userobject.Name)]"
                        $db = $userobject.Database
                        $type = get-sqltypename $userobject.Type
                        $sql = $userobject.Definition
                        $schema = $userobject.SchemaName
                        
                        $copyobject = [pscustomobject]@{
                            SourceServer = $sourceServer.Name
                            DestinationServer = $destServer.Name
                            Name         = $name
                            Type         = "$type in $systemDb"
                            Status       = $null
                            Notes        = $null
                            DateTime     = [Sqlcollaborative.Dbatools.Utility.DbaDateTime](Get-Date)
                        }
                        Write-Message -Level Debug -Message $sql
                        try {
                            Write-Message -Level Verbose -Message "Searching for $name in $db on $destinstance"
                            $result = Get-DbaSqlModule -SqlInstance $destServer -NoSystemObjects -Database $db |
                            Where-Object { $psitem.Name -eq $userobject.Name -and $psitem.Type -eq $userobject.Type }
                            if ($result) {
                                Write-Message -Level Verbose -Message "Found $name in $db on $destinstance"
                                if (-not $Force) {
                                    $copyobject.Status = "Skipped"
                                    $copyobject.Notes = "$name exists on destination"
                                }
                                else {
                                    $smobject = switch ($userobject.Type) {
                                        "VIEW" { $smodb.Views.Item($userobject.Name, $userobject.SchemaName) }
                                        "SQL_STORED_PROCEDURE" { $smodb.StoredProcedures.Item($userobject.Name, $userobject.SchemaName) }
                                        "RULE" { $smodb.Rules.Item($userobject.Name, $userobject.SchemaName) }
                                        "SQL_TRIGGER" { $smodb.Triggers.Item($userobject.Name, $userobject.SchemaName) }
                                        "SQL_TABLE_VALUED_FUNCTION" { $smodb.UserDefinedFunctions.Item($name) }
                                        "SQL_INLINE_TABLE_VALUED_FUNCTION" { $smodb.UserDefinedFunctions.Item($name) }
                                        "SQL_SCALAR_FUNCTION" { $smodb.UserDefinedFunctions.Item($name) }
                                    }
                                    
                                    if ($smobject) {
                                        Write-Message -Level Verbose -Message "Force specified. Dropping $smobject on $destdb on $destinstance using SMO"
                                        $transfer = New-Object Microsoft.SqlServer.Management.Smo.Transfer $smodb
                                        $null = $transfer.CopyAllObjects = $false
                                        $null = $transfer.Options.WithDependencies = $true
                                        $null = $transfer.ObjectList.Add($smobject)
                                        $null = $transfer.Options.ScriptDrops = $true
                                        $dropsql = $transfer.ScriptTransfer()
                                        Write-Message -Level Debug -Message "$dropsql"
                                        if ($PSCmdlet.ShouldProcess($destServer, "Attempting to drop $type $name from $systemDb")) {
                                            $null = $destdb.Query("$dropsql")
                                        }
                                    }
                                    else {
                                        if ($PSCmdlet.ShouldProcess($destServer, "Attempting to drop $type $name from $systemDb using T-SQL")) {
                                            $null = $destdb.Query("DROP FUNCTION $($userobject.name)")
                                        }
                                    }
                                    if ($PSCmdlet.ShouldProcess($destServer, "Attempting to add $type $name to $systemDb")) {
                                        $null = $destdb.Query("$sql")
                                        $copyobject.Status = "Successful"
                                    }
                                }
                            }
                            else {
                                if ($PSCmdlet.ShouldProcess($destServer, "Attempting to add $type $name to $systemDb")) {
                                    $null = $destdb.Query("$sql")
                                    $copyobject.Status = "Successful"
                                }
                            }
                        }
                        catch {
                            try {
                                $smobject = switch ($userobject.Type) {
                                    "VIEW" { $smodb.Views.Item($userobject.Name, $userobject.SchemaName) }
                                    "SQL_STORED_PROCEDURE" { $smodb.StoredProcedures.Item($userobject.Name, $userobject.SchemaName) }
                                    "RULE" { $smodb.Rules.Item($userobject.Name, $userobject.SchemaName) }
                                    "SQL_TRIGGER" { $smodb.Triggers.Item($userobject.Name, $userobject.SchemaName) }
                                }
                                if ($smobject) {
                                    $transfer = New-Object Microsoft.SqlServer.Management.Smo.Transfer $smodb
                                    $null = $transfer.CopyAllObjects = $false
                                    $null = $transfer.Options.WithDependencies = $true
                                    $null = $transfer.ObjectList.Add($smobject)
                                    $sql = $transfer.ScriptTransfer()
                                    Write-Message -Level Debug -Message "$sql"
                                    Write-Message -Level Verbose -Message "Adding $smoobject on $destdb on $destinstance"
                                    if ($PSCmdlet.ShouldProcess($destServer, "Attempting to add $type $name to $systemDb")) {
                                        $null = $destdb.Query("$sql")
                                    }
                                    $copyobject.Status = "Successful"
                                    $copyobject.Notes = "May have also installed dependencies"
                                }
                                else {
                                    $copyobject.Status = "Failed"
                                    $copyobject.Notes = (Get-ErrorMessage -Record $_)
                                }
                            }
                            catch {
                                $copyobject.Status = "Failed"
                                $copyobject.Notes = (Get-ErrorMessage -Record $_)
                            }
                        }
                        $copyobject | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
                    }
                }
            }
            else {
                foreach ($systemDb in $systemDbs) {
                    $sysdb = $sourceServer.databases[$systemDb]
                    $transfer = New-Object Microsoft.SqlServer.Management.Smo.Transfer $sysdb
                    $transfer.CopyAllObjects = $false
                    $transfer.CopyAllDatabaseTriggers = $true
                    $transfer.CopyAllDefaults = $true
                    $transfer.CopyAllRoles = $true
                    $transfer.CopyAllRules = $true
                    $transfer.CopyAllSchemas = $true
                    $transfer.CopyAllSequences = $true
                    $transfer.CopyAllSqlAssemblies = $true
                    $transfer.CopyAllSynonyms = $true
                    $transfer.CopyAllTables = $true
                    $transfer.CopyAllViews = $true
                    $transfer.CopyAllStoredProcedures = $true
                    $transfer.CopyAllUserDefinedAggregates = $true
                    $transfer.CopyAllUserDefinedDataTypes = $true
                    $transfer.CopyAllUserDefinedTableTypes = $true
                    $transfer.CopyAllUserDefinedTypes = $true
                    $transfer.CopyAllUserDefinedFunctions = $true
                    $transfer.CopyAllUsers = $true
                    $transfer.PreserveDbo = $true
                    $transfer.Options.AllowSystemObjects = $false
                    $transfer.Options.ContinueScriptingOnError = $true
                    $transfer.Options.IncludeDatabaseRoleMemberships = $true
                    $transfer.Options.Indexes = $true
                    $transfer.Options.Permissions = $true
                    $transfer.Options.WithDependencies = $false
                    
                    Write-Message -Level Output -Message "Copying from $systemDb."
                    try {
                        $sqlQueries = $transfer.ScriptTransfer()
                        
                        foreach ($sql in $sqlQueries) {
                            Write-Message -Level Debug -Message "$sql"
                            if ($PSCmdlet.ShouldProcess($destServer, $sql)) {
                                try {
                                    $destServer.Query($sql, $systemDb)
                                }
                                catch {
                                    # Don't care - long story having to do with duplicate stuff
                                }
                            }
                        }
                    }
                    catch {
                        # Don't care - long story having to do with duplicate stuff
                    }
                }
            }
        }
    }
    end {
        Test-DbaDeprecation -DeprecatedOn "1.0.0" -EnableException:$false -Alias Copy-SqlSysDbUserObjects
    }
}
#ValidationTags#Messaging,FlowControl,Pipeline,CodeStyle#
function Copy-DbaTableData {
    <#
        .SYNOPSIS
            Copies data between SQL Server tables.

        .DESCRIPTION
            Copies data between SQL Server tables using SQL Bulk Copy.
            The same can be achieved also doing
                $sourcetable = Invoke-SqlCmd2 -ServerInstance instance1 ... -As DataTable
                Write-DbaDataTable -SqlInstance ... -InputObject $sourcetable
            but it will force buffering the contents on the table in memory (high RAM usage for large tables).
            With this function, a streaming copy will be done in the most speedy and least resource-intensive way.

        .PARAMETER SqlInstance
            Source SQL Server.You must have sysadmin access and server version must be SQL Server version 2000 or greater.

        .PARAMETER SqlCredential
            Login to the target instance using alternative credentials. Windows and SQL Authentication supported. Accepts credential objects (Get-Credential)

        .PARAMETER Destination
            Destination Sql Server. You must have sysadmin access and server version must be SQL Server version 2000 or greater.

        .PARAMETER DestinationSqlCredential
            Login to the target instance using alternative credentials. Windows and SQL Authentication supported. Accepts credential objects (Get-Credential)

        .PARAMETER Database
            The database to copy the table from.

        .PARAMETER DestinationDatabase
            The database to copy the table to. If not specified, it is assumed to be the same of Database

        .PARAMETER Table
            Define a specific table you would like to use as source. You can specify up to three-part name like db.sch.tbl.
            If the object has special characters please wrap them in square brackets [ ].
            This dbo.First.Table will try to find table named 'Table' on schema 'First' and database 'dbo'.
            The correct way to find table named 'First.Table' on schema 'dbo' is passing dbo.[First.Table]

        .PARAMETER DestinationTable
            The table you want to use as destination. If not specified, it is assumed to be the same of Table

        .PARAMETER Query
            If you want to copy only a portion, specify the query (but please, select all the columns, or nasty things will happen)

        .PARAMETER BatchSize
            The BatchSize for the import defaults to 5000.

        .PARAMETER NotifyAfter
            Sets the option to show the notification after so many rows of import

        .PARAMETER NoTableLock
            If this switch is enabled, a table lock (TABLOCK) will not be placed on the destination table. By default, this operation will lock the destination table while running.

        .PARAMETER CheckConstraints
            If this switch is enabled, the SqlBulkCopy option to process check constraints will be enabled.

            Per Microsoft "Check constraints while data is being inserted. By default, constraints are not checked."

        .PARAMETER FireTriggers
            If this switch is enabled, the SqlBulkCopy option to fire insert triggers will be enabled.

            Per Microsoft "When specified, cause the server to fire the insert triggers for the rows being inserted into the Database."

        .PARAMETER KeepIdentity
            If this switch is enabled, the SqlBulkCopy option to preserve source identity values will be enabled.

            Per Microsoft "Preserve source identity values. When not specified, identity values are assigned by the destination."

        .PARAMETER KeepNulls
            If this switch is enabled, the SqlBulkCopy option to preserve NULL values will be enabled.

            Per Microsoft "Preserve null values in the destination table regardless of the settings for default values. When not specified, null values are replaced by default values where applicable."

        .PARAMETER Truncate
            If this switch is enabled, the destination table will be truncated after prompting for confirmation.

        .PARAMETER BulkCopyTimeOut
            Value in seconds for the BulkCopy operations timeout. The default is 30 seconds.

        .PARAMETER InputObject
            Enables piping of Table objects from Get-DbaTable

        .PARAMETER WhatIf
            If this switch is enabled, no actions are performed but informational messages will be displayed that explain what would happen if the command were to run.

        .PARAMETER Confirm
            If this switch is enabled, you will be prompted for confirmation before executing any operations that change state.

        .PARAMETER EnableException
            By default, when something goes wrong we try to catch it, interpret it and give you a friendly warning message.
            This avoids overwhelming you with "sea of red" exceptions, but is inconvenient because it basically disables advanced scripting.
            Using this switch turns this "nice by default" feature off and enables you to catch exceptions with your own try/catch.

        .NOTES
            Tags: Migration
            Author: niphlod (Simone Bizzotto)

            Website: https://dbatools.io
            Copyright: (C) Chrissy LeMaire, [email protected]
            License: MIT https://opensource.org/licenses/MIT

        .LINK
            https://dbatools.io/Copy-DbaTableData

        .EXAMPLE
            Copy-DbaTableData -SqlInstance sql1 -Destination sql2 -Database dbatools_from -Table test_table

            Copies all the data from sql1 to sql2, using the database dbatools_from.

        .EXAMPLE
            Copy-DbaTableData -SqlInstance sql1 -Destination sql2 -Database dbatools_from -DestinationDatabase dbatools_dest -Table test_table

            Copies all the data from sql1 to sql2, using the database dbatools_from as source and dbatools_dest as destination

        .EXAMPLE
            Get-DbaTable -SqlInstance sql1 -Database tempdb -Table tb1, tb2 | Copy-DbaTableData -DestinationTable tb3

            Copies all data from tables tb1 and tb2 in tempdb on sql1 to tb3 in tempdb onsql1

        .EXAMPLE
            Get-DbaTable -SqlInstance sql1 -Database tempdb -Table tb1, tb2 | Copy-DbaTableData -Destination sql2

            Copies data from tbl1 in tempdb on sql1 to tbl1 in tempdb on sql2
            then
            Copies data from tbl2 in tempdb on sql1 to tbl2 in tempdb on sql2

        .EXAMPLE
            Copy-DbaTableData -SqlInstance sql1 -Destination sql2 -Database dbatools_from -Table test_table

            Copies all the data from sql1 to sql2, using the database dbatools_from.

        .EXAMPLE
            Copy-DbaTableData -SqlInstance sql1 -Destination sql2 -Database dbatools_from -Table test_table -KeepIdentity -Truncate

            Copies all the data from sql1 to sql2, using the database dbatools_from, keeping identity columns and truncating the destination

        .EXAMPLE
            Copy-DbaTableData -SqlInstance sql1 -Destination sql2 -Database dbatools_from -Table test_table -KeepIdentity -Truncate

            Copies all the data from sql1 to sql2, using the database dbatools_from, keeping identity columns and truncating the destination

    #>
    [CmdletBinding(DefaultParameterSetName = "Default", SupportsShouldProcess = $true)]
    param (
        [Alias("ServerInstance", "SqlServer", "Source")]
        [DbaInstanceParameter]$SqlInstance,
        [PSCredential]$SqlCredential,
        [DbaInstanceParameter]$Destination,
        [PSCredential]$DestinationSqlCredential,
        [string]$Database,
        [string]$DestinationDatabase,
        [string[]]$Table,
        [string]$Query,
        [int]$BatchSize = 50000,
        [int]$NotifyAfter = 5000,
        [string]$DestinationTable,
        [switch]$NoTableLock,
        [switch]$CheckConstraints,
        [switch]$FireTriggers,
        [switch]$KeepIdentity,
        [switch]$KeepNulls,
        [switch]$Truncate,
        [int]$bulkCopyTimeOut = 5000,
        [Parameter(ValueFromPipeline)]
        [Microsoft.SqlServer.Management.Smo.Table[]]$InputObject,
        [switch]$EnableException
    )

    begin {
        # Getting the total rows copied is a challenge. Use SqlBulkCopyExtension.
        # http://stackoverflow.com/questions/1188384/sqlbulkcopy-row-count-when-complete

        $sourcecode = 'namespace System.Data.SqlClient {
            using Reflection;

            public static class SqlBulkCopyExtension
            {
                const String _rowsCopiedFieldName = "_rowsCopied";
                static FieldInfo _rowsCopiedField = null;

                public static int RowsCopiedCount(this SqlBulkCopy bulkCopy)
                {
                    if (_rowsCopiedField == null) _rowsCopiedField = typeof(SqlBulkCopy).GetField(_rowsCopiedFieldName, BindingFlags.NonPublic | BindingFlags.GetField | BindingFlags.Instance);
                    return (int)_rowsCopiedField.GetValue(bulkCopy);
                }
            }
        }'

        Add-Type -ReferencedAssemblies System.Data.dll -TypeDefinition $sourcecode -ErrorAction SilentlyContinue
        $bulkCopyOptions = 0
        $options = "TableLock", "CheckConstraints", "FireTriggers", "KeepIdentity", "KeepNulls", "Default"

        foreach ($option in $options) {
            $optionValue = Get-Variable $option -ValueOnly -ErrorAction SilentlyContinue
            if ($option -eq "TableLock" -and (!$NoTableLock)) {
                $optionValue = $true
            }
            if ($optionValue -eq $true) {
                $bulkCopyOptions += $([Data.SqlClient.SqlBulkCopyOptions]::$option).value__
            }
        }
    }

    process {
        if ((Test-Bound -Not -ParameterName Table, SqlInstance) -and (Test-Bound -Not -ParameterName InputObject)) {
            Stop-Function -Message "You must pipe in a table or specify SqlInstance, Database and Table."
            return
        }

        if ($SqlInstance) {
            if ((Test-Bound -Not -ParameterName Database)) {
                Stop-Function -Message "Database is required when passing a SqlInstance" -Target $Table
                return
            }

            try {
                $server = Connect-SqlInstance -SqlInstance $SqlInstance -SqlCredential $SqlCredential
            }
            catch {
                Stop-Function -Message "Failure" -Category ConnectionError -ErrorRecord $_ -Target $SqlInstance
                return
            }

            if ($Database -notin $server.Databases.Name) {
                Stop-Function -Message "Database $Database doesn't exist on $server"
                return
            }

            try {
                $InputObject += Get-DbaTable -SqlInstance $server -Table $Table -Database $Database -EnableException -Verbose:$false
            }
            catch {
                Stop-Function -Message "Unable to determine source table : $Table"
                return
            }
        }

        foreach ($sqltable in $InputObject) {
            $Database = $sqltable.Parent.Name
            $server = $sqltable.Parent.Parent

            if ((Test-Bound -Not -ParameterName DestinationDatabase)) {
                $DestinationDatabase = $Database
            }

            if ((Test-Bound -Not -ParameterName DestinationTable)) {
                $DestinationTable = $sqltable.Name
            }

            if ((Test-Bound -Not -ParameterName Destination)) {
                $destServer = $server
            }
            else {
                try {
                    $destServer = Connect-SqlInstance -SqlInstance $Destination -SqlCredential $DestinationSqlCredential
                }
                catch {
                    Stop-Function -Message "Failure" -Category ConnectionError -ErrorRecord $_ -Target $Destination
                    return
                }
            }

            if ($DestinationDatabase -notin $destServer.Databases.Name) {
                Stop-Function -Message "Database $DestinationDatabase doesn't exist on $destServer"
                return
            }

            try {
                $desttable = Get-DbaTable -SqlInstance $destServer -Table $DestinationTable -Database $DestinationDatabase -EnableException -Verbose:$false | Select-Object -First 1
            }
            catch {
                Stop-Function -Message "Unable to determine destination table: $DestinationTable"
                return
            }

            if (-not $desttable) {
                Stop-Function -Message "$DestinationTable does not exist on destination"
                return
            }

            $connstring = $destServer.ConnectionContext.ConnectionString

            $fqtnfrom = "$($server.Databases[$Database]).$sqltable"
            $fqtndest = "$($destServer.Databases[$DestinationDatabase]).$desttable"

            if (Test-Bound -ParameterName Query -Not) {
                $Query = "SELECT * FROM $fqtnfrom"
            }
            try {
                if ($Truncate -eq $true) {
                    if ($Pscmdlet.ShouldProcess($destServer, "Truncating table $fqtndest")) {
                        $null = $destServer.Databases[$DestinationDatabase].ExecuteNonQuery("TRUNCATE TABLE $fqtndest")
                    }
                }
                $cmd = $server.ConnectionContext.SqlConnectionObject.CreateCommand()
                $cmd.CommandText = $Query
                if ($server.ConnectionContext.IsOpen -eq $false) {
                    $server.ConnectionContext.SqlConnectionObject.Open()
                }
                $bulkCopy = New-Object Data.SqlClient.SqlBulkCopy("$connstring;Database=$DestinationDatabase", $bulkCopyOptions)
                $bulkCopy.DestinationTableName = $fqtndest
                $bulkCopy.EnableStreaming = $true
                $bulkCopy.BatchSize = $BatchSize
                $bulkCopy.NotifyAfter = $NotifyAfter
                $bulkCopy.BulkCopyTimeOut = $BulkCopyTimeOut

                $elapsed = [System.Diagnostics.Stopwatch]::StartNew()
                # Add RowCount output
                $bulkCopy.Add_SqlRowsCopied({
                        $RowsPerSec = [math]::Round($args[1].RowsCopied / $elapsed.ElapsedMilliseconds * 1000.0, 1)
                        Write-Progress -id 1 -activity "Inserting rows" -Status ([System.String]::Format("{0} rows ({1} rows/sec)", $args[1].RowsCopied, $RowsPerSec))
                    })

                if ($Pscmdlet.ShouldProcess($destServer, "Writing rows to $fqtndest")) {
                    $reader = $cmd.ExecuteReader()
                    $bulkCopy.WriteToServer($reader)
                    $RowsTotal = [System.Data.SqlClient.SqlBulkCopyExtension]::RowsCopiedCount($bulkCopy)
                    $TotalTime = [math]::Round($elapsed.Elapsed.TotalSeconds, 1)
                    Write-Message -Level Verbose -Message "$RowsTotal rows inserted in $TotalTime sec"
                    if ($rowCount -is [int]) {
                        Write-Progress -id 1 -activity "Inserting rows" -status "Complete" -Completed
                    }
                }

                $bulkCopy.Close()
                $bulkCopy.Dispose()
                $reader.Close()

                [pscustomobject]@{
                    SourceInstance       = $server.Name
                    SourceDatabase       = $Database
                    SourceTable          = $sqltable.Name
                    DestinationInstance  = $destServer.name
                    DestinationDatabase  = $DestinationDatabase
                    DestinationTable     = $desttable.Name
                    RowsCopied           = $rowstotal
                    Elapsed              = [prettytimespan]$elapsed.Elapsed
                }
            }
            catch {
                Stop-Function -Message "Something went wrong" -ErrorRecord $_ -Target $server -continue
            }
        }
    }
}
function Copy-DbaXESessionTemplate {
    <#
        .SYNOPSIS
            Copies non-Microsoft templates from the dbatools template repository (\bin\xetemplates\) to $home\Documents\SQL Server Management Studio\Templates\XEventTemplates.

        .DESCRIPTION
            Copies non-Microsoft templates from the dbatools template repository (\bin\xetemplates\) to $home\Documents\SQL Server Management Studio\Templates\XEventTemplates.

            Useful for when you want to use the SSMS GUI.

        .PARAMETER Path
            The path to the template directory. Defaults to the dbatools template repository (\bin\xetemplates\).

        .PARAMETER Destination
            Path to the Destination directory, defaults to $home\Documents\SQL Server Management Studio\Templates\XEventTemplates.

        .PARAMETER EnableException
            By default, when something goes wrong we try to catch it, interpret it and give you a friendly warning message.
            This avoids overwhelming you with "sea of red" exceptions, but is inconvenient because it basically disables advanced scripting.
            Using this switch turns this "nice by default" feature off and enables you to catch exceptions with your own try/catch.

        .NOTES
            Tags: ExtendedEvent, XE, XEvent
            Website: https://dbatools.io
            Copyright: (C) Chrissy LeMaire, [email protected]
            License: MIT https://opensource.org/licenses/MIT

        .LINK
            https://dbatools.io/Copy-DbaXESessionTemplate

        .EXAMPLE
            Copy-DbaXESessionTemplate

            Copies non-Microsoft templates from the dbatools template repository (\bin\xetemplates\) to $home\Documents\SQL Server Management Studio\Templates\XEventTemplates.

        .EXAMPLE
            Copy-DbaXESessionTemplate -Path C:\temp\xetemplates

            Copies your templates from C:\temp\xetemplates to $home\Documents\SQL Server Management Studio\Templates\XEventTemplates.

    #>
    [CmdletBinding()]
    param (
        [string[]]$Path = "$script:PSModuleRoot\bin\xetemplates",
        [string]$Destination = "$home\Documents\SQL Server Management Studio\Templates\XEventTemplates",
        [switch]$EnableException
    )
    process {
        if (Test-FunctionInterrupt) { return }
        foreach ($destinstance in $Destination) {
            if (-not (Test-Path -Path $destinstance)) {
                try {
                    $null = New-Item -ItemType Directory -Path $destinstance -ErrorAction Stop
                }
                catch {
                    Stop-Function -Message "Failure" -ErrorRecord $_ -Target $destinstance
                }
            }
            try {
                $files = (Get-DbaXESessionTemplate -Path $Path | Where-Object Source -ne Microsoft).Path
                foreach ($file in $files) {
                    Write-Message -Level Output -Message "Copying $($file.Name) to $destinstance."
                    Copy-Item -Path $file -Destination $destinstance -ErrorAction Stop
                }
            }
            catch {
                Stop-Function -Message "Failure" -ErrorRecord $_ -Target $path
            }
        }
    }
}
#ValidationTags#Messaging,FlowControl,Pipeline,CodeStyle#
function Disable-DbaAgHadr {
    <#
        .SYNOPSIS
            Disables the Hadr service setting on the specified SQL Server.

        .DESCRIPTION
            In order to build an AG a cluster has to be built and then the Hadr enabled for the SQL Server
            service. This function disables that feature for the SQL Server service.

        .PARAMETER SqlInstance
            The SQL Server that you're connecting to.

        .PARAMETER Credential
            Credential object used to connect to the Windows server itself as a different user

        .PARAMETER WhatIf
            Shows what would happen if the command were to run. No actions are actually performed.

        .PARAMETER Confirm
            Prompts you for confirmation before executing any changing operations within the command.

        .PARAMETER Force
            Will restart SQL Server and SQL Server Agent service to apply the change.

        .PARAMETER EnableException
            By default, when something goes wrong we try to catch it, interpret it and give you a friendly warning message.
            This avoids overwhelming you with "sea of red" exceptions, but is inconvenient because it basically disables advanced scripting.
            Using this switch turns this "nice by default" feature off and enables you to catch exceptions with your own try/catch.

        .NOTES
            Tags: Hadr, AG, AvailabilityGroup
            Author: Shawn Melton (@wsmelton | http://blog.wsmelton.info)

            Website: https://dbatools.io
            Copyright: (C) Chrissy LeMaire, [email protected]
            License: MIT https://opensource.org/licenses/MIT

        .LINK
            https://dbatools.io/Disable-DbaAgHadr

        .EXAMPLE
            Disable-DbaAgHadr -SqlInstance sql2016 -Force

            Sets Hadr service to disabled for the instance sql2016, and restart the service to apply the change.

        .EXAMPLE
            Disable-DbaAgHadr -SqlInstance sql2012\dev1 -Force

            Sets Hadr service to disabled for the instance dev1 on sq2012, and restart the service to apply the change.
    #>
    [CmdletBinding(SupportsShouldProcess = $true, ConfirmImpact = "High")]
    param (
        [parameter(Mandatory = $true, ValueFromPipeline = $true)]
        [Alias("ServerInstance", "SqlServer")]
        [DbaInstanceParameter[]]$SqlInstance,
        [PSCredential]$Credential,
        [switch]$Force,
        [Alias('Silent')]
        [switch]$EnableException
    )
    begin {
        function GetDbaAgHadr {
            [CmdletBinding()]
            param (
                [parameter(Mandatory = $true, ValueFromPipeline = $true)]
                [Alias("ServerInstance", "SqlServer")]
                [DbaInstanceParameter[]]$SqlInstance,
                [PSCredential]$Credential,
                [Alias('Silent')]
                [switch]$EnableException
            )
            process {
                foreach ($instance in $SqlInstance) {

                    try {
                        $computer = $computerName = $instance.ComputerName
                        $instanceName = $instance.InstanceName
                        Write-Message -Level Verbose -Message "Connecting to $computer"
                        $currentState = Invoke-ManagedComputerCommand -ComputerName $computerName -ScriptBlock { $wmi.Services[$args[0]] | Select-Object IsHadrEnabled } -ArgumentList $instanceName -Credential $Credential
                    }
                    catch {
                        Stop-Function -Message "Failure connecting to $computer" -Category ConnectionError -ErrorRecord $_ -Target $instance
                        return
                    }

                    if ($null -eq $currentState.IsHadrEnabled) {
                        $isenabled = $false
                    }
                    else {
                        $isenabled = $currentState.IsHadrEnabled
                    }
                    [PSCustomObject]@{
                        ComputerName     = $computer
                        InstanceName     = $instanceName
                        SqlInstance      = $instance.FullName
                        IsHadrEnabled    = $isenabled
                    }
                }
            }
        }
    }

    process {
        foreach ($instance in $SqlInstance) {
            $computer = $computerFullName = $instance.ComputerName
            $instanceName = $instance.InstanceName
            if (-not (Test-ElevationRequirement -ComputerName $instance)) {
                return
            }
            $noChange = $false

            switch ($instance.InstanceName) {
                'MSSQLSERVER' { $agentName = 'SQLSERVERAGENT' }
                default { $agentName = "SQLAgent`$$instanceName" }
            }

            try {
                Write-Message -Level Verbose -Message "Checking current Hadr setting for $computer"
                $currentState = GetDbaAgHadr -SqlInstance $instance -Credential $Credential
            }
            catch {
                Stop-Function -Message "Failure to pull current state of Hadr setting on $computer" -Category ConnectionError -ErrorRecord $_ -Target $instance -Continue
            }

            $isHadrEnabled = $currentState.IsHadrEnabled
            Write-Message -Level InternalComment -Message "$instance Hadr current value: $isHadrEnabled"

            # hadr results from sql wmi can be iffy, skip the check
            <#
            if (-not $isHadrEnabled) {
                Write-Message -Level Warning -Message "Hadr is already disabled for instance: $($instance.FullName)"
                $noChange = $true
                continue
            }
            #>

            $scriptblock = {
                $instance = $args[0]
                $sqlService = $wmi.Services | Where-Object DisplayName -eq "SQL Server ($instance)"
                $sqlService.ChangeHadrServiceSetting(0)
            }

            if ($noChange -eq $false) {
                if ($PSCmdlet.ShouldProcess($instance, "Changing Hadr from $isHadrEnabled to 0 for $instance")) {
                    try {
                        Invoke-ManagedComputerCommand -ComputerName $computerFullName -Credential $Credential -ScriptBlock $scriptblock -ArgumentList $instancename
                    }
                    catch {
                        Stop-Function -Continue -Message "Failure on $($instance.FullName) | This may be because AlwaysOn Availability Groups feature requires the x86(non-WOW) or x64 Enterprise Edition of SQL Server 2012 (or later version) running on Windows Server 2008 (or later version) with WSFC hotfix KB 2494036 installed."
                    }
                }
                if (Test-Bound 'Force') {
                    if ($PSCmdlet.ShouldProcess($instance, "Force provided, restarting Engine and Agent service for $instance on $computerFullName")) {
                        try {
                            $null = Stop-DbaSqlService -ComputerName $computerFullName -InstanceName $instanceName -Type Agent, Engine
                            $null = Start-DbaSqlService -ComputerName $computerFullName -InstanceName $instanceName -Type Agent, Engine
                        }
                        catch {
                            Stop-Function -Message "Issue restarting $instance" -Target $instance -Continue
                        }
                    }
                }
                $newState = GetDbaAgHadr -SqlInstance $instance -Credential $Credential

                if (Test-Bound -Not -ParameterName Force) {
                    Write-Message -Level Warning -Message "You must restart the SQL Server for it to take effect."
                }

                [PSCustomObject]@{
                    ComputerName   = $newState.ComputerName
                    InstanceName   = $newState.InstanceName
                    SqlInstance    = $newState.SqlInstance
                    IsHadrEnabled  = $false
                }
            }
        }
    }
}
#ValidationTags#Messaging,FlowControl,Pipeline,CodeStyle#

function Disable-DbaForceNetworkEncryption {
    <#
        .SYNOPSIS
            Disables Force Encryption for a SQL Server instance

        .DESCRIPTION
            Disables Force Encryption for a SQL Server instance. Note that this requires access to the Windows Server, not the SQL instance itself.

            This setting is found in Configuration Manager.

        .PARAMETER SqlInstance
            The target SQL Server. Defaults to localhost.

        .PARAMETER Credential
            Allows you to login to the computer (not SQL Server instance) using alternative Windows credentials.

        .PARAMETER WhatIf
            If this switch is enabled, no actions are performed but informational messages will be displayed that explain what would happen if the command were to run.

        .PARAMETER Confirm
            If this switch is enabled, you will be prompted for confirmation before executing any operations that change state.

        .PARAMETER EnableException
            By default, when something goes wrong we try to catch it, interpret it and give you a friendly warning message.
            This avoids overwhelming you with "sea of red" exceptions, but is inconvenient because it basically disables advanced scripting.
            Using this switch turns this "nice by default" feature off and enables you to catch exceptions with your own try/catch.

        .EXAMPLE
            Disable-DbaForceNetworkEncryption

            Disables Force Encryption on the default (MSSQLSERVER) instance on localhost - requires (and checks for) RunAs admin.

        .EXAMPLE
            Disable-DbaForceNetworkEncryption -SqlInstance sql01\SQL2008R2SP2

            Disables Force Network Encryption for the SQL2008R2SP2 on sql01. Uses Windows Credentials to both login and modify the registry.

        .EXAMPLE
            Disable-DbaForceNetworkEncryption -SqlInstance sql01\SQL2008R2SP2 -WhatIf

            Shows what would happen if the command were executed.

        .NOTES
            Tags: Certificate

            Website: https://dbatools.io
            Copyright: (C) Chrissy LeMaire, [email protected]
            License: MIT https://opensource.org/licenses/MIT
    #>
    [CmdletBinding(SupportsShouldProcess, ConfirmImpact = "Low")]
    param (
        [Parameter(ValueFromPipeline = $true)]
        [Alias("ServerInstance", "SqlServer", "ComputerName")]
        [DbaInstanceParameter[]]$SqlInstance = $env:COMPUTERNAME,
        [PSCredential]$Credential,
        [Alias('Silent')]
        [switch]$EnableException
    )
    process {

        foreach ($instance in $sqlinstance) {
            Write-Message -Level VeryVerbose -Message "Processing $instance." -Target $instance
            $null = Test-ElevationRequirement -ComputerName $instance -Continue

            Write-Message -Level Verbose -Message "Resolving hostname."
            $resolved = $null
            $resolved = Resolve-DbaNetworkName -ComputerName $instance -Turbo

            if ($null -eq $resolved) {
                Stop-Function -Message "Can't resolve $instance." -Target $instance -Continue -Category InvalidArgument
            }

            Write-Message -Level Output -Message "Connecting to SQL WMI on $($instance.ComputerName)."
            try {
                $sqlwmi = Invoke-ManagedComputerCommand -ComputerName $resolved.FullComputerName -ScriptBlock { $wmi.Services } -Credential $Credential -ErrorAction Stop | Where-Object DisplayName -eq "SQL Server ($($instance.InstanceName))"
            }
            catch {
                Stop-Function -Message "Failed to access $instance." -Target $instance -Continue -ErrorRecord $_
            }

            $regroot = ($sqlwmi.AdvancedProperties | Where-Object Name -eq REGROOT).Value
            $vsname = ($sqlwmi.AdvancedProperties | Where-Object Name -eq VSNAME).Value
            try {
                $instancename = $sqlwmi.DisplayName.Replace('SQL Server (', '').Replace(')', '') # Don't clown, I don't know regex :(
            }
            catch {
                # Probably because the instance name has been aliased or does not exist or samthin
            }
            $serviceaccount = $sqlwmi.ServiceAccount

            if ([System.String]::IsNullOrEmpty($regroot)) {
                $regroot = $sqlwmi.AdvancedProperties | Where-Object { $_ -match 'REGROOT' }
                $vsname = $sqlwmi.AdvancedProperties | Where-Object { $_ -match 'VSNAME' }

                if (![System.String]::IsNullOrEmpty($regroot)) {
                    $regroot = ($regroot -Split 'Value\=')[1]
                    $vsname = ($vsname -Split 'Value\=')[1]
                }
                else {
                    Stop-Function -Message "Can't find instance $vsname on $instance." -Continue -Category ObjectNotFound -Target $instance
                }
            }

            if ([System.String]::IsNullOrEmpty($vsname)) { $vsname = $instance }

            Write-Message -Level Output -Message "Regroot: $regroot" -Target $instance
            Write-Message -Level Output -Message "ServiceAcct: $serviceaccount" -Target $instance
            Write-Message -Level Output -Message "InstanceName: $instancename" -Target $instance
            Write-Message -Level Output -Message "VSNAME: $vsname" -Target $instance

            $scriptblock = {
                $regpath = "Registry::HKEY_LOCAL_MACHINE\$($args[0])\MSSQLServer\SuperSocketNetLib"
                $cert = (Get-ItemProperty -Path $regpath -Name Certificate).Certificate
                $oldvalue = (Get-ItemProperty -Path $regpath -Name ForceEncryption).ForceEncryption
                Set-ItemProperty -Path $regpath -Name ForceEncryption -Value $false
                $forceencryption = (Get-ItemProperty -Path $regpath -Name ForceEncryption).ForceEncryption

                [pscustomobject]@{
                    ComputerName          = $env:COMPUTERNAME
                    InstanceName          = $args[2]
                    SqlInstance           = $args[1]
                    ForceEncryption       = ($forceencryption -eq $true)
                    CertificateThumbprint = $cert
                }
            }

            if ($PScmdlet.ShouldProcess("local", "Connecting to $instance to modify the ForceEncryption value in $regroot for $($instance.InstanceName)")) {
                try {
                    Invoke-Command2 -ComputerName $resolved.FullComputerName -Credential $Credential -ArgumentList $regroot, $vsname, $instancename -ScriptBlock $scriptblock -ErrorAction Stop
                    Write-Message -Level Critical -Message "Force encryption was successfully set on $($resolved.FullComputerName) for the $instancename instance. You must now restart the SQL Server for changes to take effect." -Target $instance
                }
                catch {
                    Stop-Function -Message "Failed to connect to $($resolved.FullComputerName) using PowerShell remoting!" -ErrorRecord $_ -Target $instance -Continue
                }
            }
        }
    }
}
function Disable-DbaTraceFlag {
    <#
    .SYNOPSIS
        Disable a Global Trace Flag that is currently running

    .DESCRIPTION
        The function will disable a Trace Flag that is currently running globally on the SQL Server instance(s) listed

    .PARAMETER SqlInstance
        Allows you to specify a comma separated list of servers to query.

    .PARAMETER SqlCredential
        Login to the target instance using alternative credentials. Windows and SQL Authentication supported. Accepts credential objects (Get-Credential)

    .PARAMETER TraceFlag
        Trace flag number to enable globally

    .PARAMETER EnableException
        By default, when something goes wrong we try to catch it, interpret it and give you a friendly warning message.
        This avoids overwhelming you with "sea of red" exceptions, but is inconvenient because it basically disables advanced scripting.
        Using this switch turns this "nice by default" feature off and enables you to catch exceptions with your own try/catch.

    .NOTES
        Tags: TraceFlag
        Author: Garry Bargsley (@gbargsley), http://blog.garrybargsley.com

        Website: https://dbatools.io
        Copyright: (C) Chrissy LeMaire, [email protected]
        License: MIT https://opensource.org/licenses/MIT

    .LINK
        https://dbatools.io/Disable-DbaTraceFlag

    .EXAMPLE
        Disable-DbaTraceFlag -SqlInstance sql2016 -TraceFlag 3226
        Disable the globally running trace flag 3226 on SQL Server instance sql2016
#>
    [CmdletBinding()]
    param (
        [parameter(Position = 0, Mandatory = $true, ValueFromPipeline = $True)]
        [Alias("ServerInstance", "SqlServer", "SqlServers")]
        [DbaInstanceParameter[]]$SqlInstance,
        [PSCredential]$SqlCredential,
        [parameter(Mandatory)]
        [int[]]$TraceFlag,
        [Alias('Silent')]
        [switch]$EnableException
    )

    process {
        foreach ($instance in $SqlInstance) {
            Write-Message -Level Verbose -Message "Connecting to $instance"

            try {
                $server = Connect-SqlInstance -SqlInstance $instance -SqlCredential $SqlCredential
            }
            catch {
                Stop-Function -Message "Failure" -Category ConnectionError -ErrorRecord $_ -Target $instance -Continue
            }

            $current = Get-DbaTraceFlag -SqlInstance $server -EnableException

            foreach ($tf in $TraceFlag) {
                $TraceFlagInfo = [pscustomobject]@{
                    SourceServer = $server.ComputerName
                    InstanceName = $server.ServiceName
                    SqlInstance  = $server.DomainInstanceName
                    TraceFlag    = $tf
                    Status       = $null
                    Notes        = $null
                    DateTime     = [DbaDateTime](Get-Date)
                }
                if ($tf -notin $current.TraceFlag) {
                    $TraceFlagInfo.Status = 'Skipped'
                    $TraceFlagInfo.Notes = "Trace Flag is not running."
                    $TraceFlagInfo
                    Write-Message -Level Warning -Message "Trace Flag $tf is not currently running on $instance"
                    continue
                }

                try {
                    $query = "DBCC TRACEOFF ($tf, -1)"
                    $server.Query($query)
                }
                catch {
                    $TraceFlagInfo.Status = "Failed"
                    $TraceFlagInfo.Notes = $_.Exception.Message
                    $TraceFlagInfo
                    Stop-Function -Message "Failure" -ErrorRecord $_ -Target $server -Continue
                }
                $TraceFlagInfo.Status = "Successful"
                $TraceFlagInfo
            }
        }
    }
}
function Dismount-DbaDatabase {
    <#
        .SYNOPSIS
            Detach a SQL Server Database.

        .DESCRIPTION
            This command detaches one or more SQL Server databases. If necessary, -Force can be used to break mirrors and remove databases from availability groups prior to detaching.

        .PARAMETER SqlInstance
            The SQL Server instance.

        .PARAMETER SqlCredential
            Login to the target instance using alternative credentials. Windows and SQL Authentication supported. Accepts credential objects (Get-Credential)

        .PARAMETER Database
            The database(s) to detach.

        .PARAMETER FileStructure
            A StringCollection object value that contains a list database files. If FileStructure is not specified, BackupHistory will be used to guess the structure.

        .PARAMETER InputObject
            A collection of databases (such as returned by Get-DbaDatabase), to be detached.

        .PARAMETER UpdateStatistics
            If this switch is enabled, statistics for the database will be updated prior to detaching it.

        .PARAMETER Force
            If this switch is enabled and the database is part of a mirror, the mirror will be broken. If the database is part of an Availability Group, it will be removed from the AG.

        .PARAMETER WhatIf
            If this switch is enabled, no actions are performed but informational messages will be displayed that explain what would happen if the command were to run.

        .PARAMETER Confirm
            If this switch is enabled, you will be prompted for confirmation before executing any operations that change state.

        .PARAMETER EnableException
            By default, when something goes wrong we try to catch it, interpret it and give you a friendly warning message.
            This avoids overwhelming you with "sea of red" exceptions, but is inconvenient because it basically disables advanced scripting.
            Using this switch turns this "nice by default" feature off and enables you to catch exceptions with your own try/catch.

        .NOTES
            Tags: Database
            Website: https://dbatools.io
            Copyright: (C) Chrissy LeMaire, [email protected]
            License: MIT https://opensource.org/licenses/MIT

        .LINK
            https://dbatools.io/Dismount-DbaDatabase

        .EXAMPLE
            Detach-DbaDatabase -SqlInstance sql2016b -Database SharePoint_Config, WSS_Logging

            Detaches SharePoint_Config and WSS_Logging from sql2016b

        .EXAMPLE
            Get-DbaDatabase -SqlInstance sql2016b -Database 'PerformancePoint Service Application_10032db0fa0041df8f913f558a5dc0d4' | Detach-DbaDatabase -Force

            Detaches 'PerformancePoint Service Application_10032db0fa0041df8f913f558a5dc0d4' from sql2016b. Since Force was specified, if the database is part of mirror, the mirror will be broken prior to detaching.

            If the database is part of an Availability Group, it will first be dropped prior to detachment.

            .EXAMPLE
            Get-DbaDatabase -SqlInstance sql2016b -Database WSS_Logging | Detach-DbaDatabase -Force -WhatIf

            Shows what would happen if the command were to execute (without actually executing the detach/break/remove commands).

    #>
    [CmdletBinding(SupportsShouldProcess, DefaultParameterSetName = "Default")]
    Param (
        [parameter(Mandatory, ParameterSetName = 'SqlInstance')]
        [Alias("ServerInstance", "SqlServer")]
        [DbaInstanceParameter[]]$SqlInstance,
        [PSCredential]$SqlCredential,
        [parameter(Mandatory, ParameterSetName = 'SqlInstance')]
        [string]$Database,
        [parameter(Mandatory, ParameterSetName = 'Pipeline', ValueFromPipeline)]
        [Microsoft.SqlServer.Management.Smo.Database[]]$InputObject,
        [Switch]$UpdateStatistics,
        [switch]$Force,
        [Alias('Silent')]
        [switch]$EnableException
    )
    process {
        foreach ($instance in $SqlInstance) {
            try {
                $server = Connect-SqlInstance -SqlInstance $instance -SqlCredential $sqlcredential
            }
            catch {
                Stop-Function -Message "Failure" -Category ConnectionError -ErrorRecord $_ -Target $instance -Continue
            }

            if ($Database) {
                $InputObject += $server.Databases | Where-Object Name -in $Database
            }
            else {
                $InputObject += $server.Databases
            }

            if ($ExcludeDatabase) {
                $InputObject = $InputObject | Where-Object Name -NotIn $ExcludeDatabase
            }
        }

        foreach ($db in $InputObject) {
            $db.Refresh()
            $server = $db.Parent

            if ($db.IsSystemObject) {
                Stop-Function -Message "$db is a system database and cannot be detached using this method." -Target $db -Continue
            }

            Write-Message -Level Verbose -Message "Checking replication status."
            if ($db.ReplicationOptions -ne "None") {
                Stop-Function -Message "Skipping $db  on $server because it is replicated." -Target $db -Continue
            }

            # repeat because different servers could be piped in
            $snapshots = (Get-DbaDbSnapshot -SqlInstance $server).SnapshotOf
            Write-Message -Level Verbose -Message "Checking for snaps"
            if ($db.Name -in $snapshots) {
                Write-Message -Level Warning -Message "Database $db has snapshots, you need to drop them before detaching. Skipping $db on $server."
                Continue
            }

            Write-Message -Level Verbose -Message "Checking mirror status"
            if ($db.IsMirroringEnabled -and !$Force) {
                Stop-Function -Message "$db on $server is being mirrored. Use -Force to break mirror or use the safer backup/restore method." -Target $db -Continue
            }

            Write-Message -Level Verbose -Message "Checking Availability Group status"

            if ($db.AvailabilityGroupName -and !$Force) {
                $ag = $db.AvailabilityGroupName
                Stop-Function -Message "$db on $server is part of an Availability Group ($ag). Use -Force to drop from $ag availability group to detach. Alternatively, you can use the safer backup/restore method." -Target $db -Continue
            }

            $sessions = Get-DbaProcess -SqlInstance $db.Parent -Database $db.Name

            if ($sessions -and !$Force) {
                Stop-Function -Message "$db on $server currently has connected users and cannot be dropped. Use -Force to kill all connections and detach the database." -Target $db -Continue
            }

            if ($force) {

                if ($sessions) {
                    If ($Pscmdlet.ShouldProcess($server, "Killing $($sessions.count) sessions which are connected to $db")) {
                        $null = $sessions | Stop-DbaProcess -ErrorAction SilentlyContinue -WarningAction SilentlyContinue
                    }
                }

                if ($db.IsMirroringEnabled) {
                    If ($Pscmdlet.ShouldProcess($server, "Breaking mirror for $db on $server")) {
                        try {
                            Write-Message -Level Warning -Message "Breaking mirror for $db on $server."
                            $db.ChangeMirroringState([Microsoft.SqlServer.Management.Smo.MirroringOption]::Off)
                            $db.Alter()
                            $db.Refresh()
                        }
                        catch {
                            Stop-Function -Message "Could not break mirror for $db on $server - not detaching." -Target $db -ErrorRecord $_ -Continue
                        }
                    }
                }

                if ($db.AvailabilityGroupName) {
                    $ag = $db.AvailabilityGroupName
                    If ($Pscmdlet.ShouldProcess($server, "Attempting remove $db on $server from Availability Group $ag")) {
                        try {
                            $server.AvailabilityGroups[$ag].AvailabilityDatabases[$db.name].Drop()
                            Write-Message -Level Verbose -Message "Successfully removed $db from  detach from $ag on $server."
                        }
                        catch {
                            if ($_.Exception.InnerException) {
                                $exception = $_.Exception.InnerException.ToString() -Split "System.Data.SqlClient.SqlException: "
                                $exception = " | $(($exception[1] -Split "at Microsoft.SqlServer.Management.Common.ConnectionManager")[0])".TrimEnd()
                            }

                            Stop-Function -Message "Could not remove $db from $ag on $server $exception." -Target $db -ErrorRecord $_ -Continue
                        }
                    }
                }

                $sessions = Get-DbaProcess -SqlInstance $db.Parent -Database $db.Name

                if ($sessions) {
                    If ($Pscmdlet.ShouldProcess($server, "Killing $($sessions.count) sessions which are still connected to $db")) {
                        $null = $sessions | Stop-DbaProcess -ErrorAction SilentlyContinue -WarningAction SilentlyContinue
                    }
                }
            }

            If ($Pscmdlet.ShouldProcess($server, "Detaching $db on $server")) {
                try {
                    $server.DetachDatabase($db.Name, $UpdateStatistics)

                    [pscustomobject]@{
                        ComputerName = $server.ComputerName
                        InstanceName = $server.ServiceName
                        SqlInstance  = $server.DomainInstanceName
                        Database     = $db.name
                        DetachResult = "Success"
                    }
                }
                catch {
                    Stop-Function -Message "Failure" -Target $db -ErrorRecord $_ -Continue
                }
            }
        }
    }
}
#ValidationTags#Messaging,FlowControl,Pipeline,CodeStyle#
function Enable-DbaAgHadr {
    <#
        .SYNOPSIS
            Enables the Hadr service setting on the specified SQL Server.

        .DESCRIPTION
            In order to build an AG a cluster has to be built and then the Hadr enabled for the SQL Server
            service. This function enables that feature for the SQL Server service.

        .PARAMETER SqlInstance
            The SQL Server that you're connecting to.

        .PARAMETER Credential
            Credential object used to connect to the Windows server itself as a different user

        .PARAMETER WhatIf
            Shows what would happen if the command were to run. No actions are actually performed.

        .PARAMETER Confirm
            Prompts you for confirmation before executing any changing operations within the command.

        .PARAMETER Force
            Will restart SQL Server and SQL Server Agent service to apply the change.

        .PARAMETER EnableException
            By default, when something goes wrong we try to catch it, interpret it and give you a friendly warning message.
            This avoids overwhelming you with "sea of red" exceptions, but is inconvenient because it basically disables advanced scripting.
            Using this switch turns this "nice by default" feature off and enables you to catch exceptions with your own try/catch.

        .NOTES
            Tags: Hadr, AG, AvailabilityGroup
            Author: Shawn Melton (@wsmelton | http://blog.wsmelton.info)

            Website: https://dbatools.io
            Copyright: (C) Chrissy LeMaire, [email protected]
            License: MIT https://opensource.org/licenses/MIT

        .LINK
            https://dbatools.io/Enable-DbaAgHadr

        .EXAMPLE
            Enable-DbaAgHadr -SqlInstance sql2016 -Force

            Sets Hadr service to enabled for the instance sql2016, and restart the service to apply the change.

        .EXAMPLE
            Enable-DbaAgHadr -SqlInstance sql2012\dev1 -Force

            Sets Hadr service to disabled for the instance dev1 on sq2012, and restart the service to apply the change.
    #>
    [CmdletBinding(SupportsShouldProcess = $true, ConfirmImpact = "High")]
    param (
        [parameter(Mandatory = $true, ValueFromPipeline = $true)]
        [Alias("ServerInstance", "SqlServer")]
        [DbaInstanceParameter[]]$SqlInstance,
        [PSCredential]$Credential,
        [switch]$Force,
        [Alias('Silent')]
        [switch]$EnableException
    )
    begin {
        function GetDbaAgHadr {
    <#
        .SYNOPSIS
            Gets the Hadr service setting on the specified SQL Server instance.

        .DESCRIPTION
            Gets the Hadr setting, from the service level, and returns true or false for the specified SQL Server instance.

        .PARAMETER SqlInstance
            The SQL Server that you're connecting to.

        .PARAMETER Credential
            Credential object used to connect to the Windows server itself as a different user

        .PARAMETER EnableException
            By default, when something goes wrong we try to catch it, interpret it and give you a friendly warning message.
            This avoids overwhelming you with "sea of red" exceptions, but is inconvenient because it basically disables advanced scripting.
            Using this switch turns this "nice by default" feature off and enables you to catch exceptions with your own try/catch.

        .NOTES
            Tags: Hadr, AG, AvailabilityGroup
            Author: Shawn Melton (@wsmelton | http://blog.wsmelton.info)

            Website: https://dbatools.io
            Copyright: (C) Chrissy LeMaire, [email protected]
            License: MIT https://opensource.org/licenses/MIT

        .LINK
            https://dbatools.io/GetDbaAgHadr

        .EXAMPLE
            GetDbaAgHadr -SqlInstance sql2016

            Returns a status of the Hadr setting for sql2016 SQL Server instance.
    #>
            [CmdletBinding()]
            param (
                [parameter(Mandatory = $true, ValueFromPipeline = $true)]
                [Alias("ServerInstance", "SqlServer")]
                [DbaInstanceParameter[]]$SqlInstance,
                [PSCredential]$Credential,
                [Alias('Silent')]
                [switch]$EnableException
            )
            process {
                foreach ($instance in $SqlInstance) {

                    try {
                        $computer = $computerName = $instance.ComputerName
                        $instanceName = $instance.InstanceName
                        Write-Message -Level Verbose -Message "Connecting to $computer"
                        $currentState = Invoke-ManagedComputerCommand -ComputerName $computerName -ScriptBlock { $wmi.Services[$args[0]] | Select-Object IsHadrEnabled } -ArgumentList $instanceName -Credential $Credential
                    }
                    catch {
                        Stop-Function -Message "Failure connecting to $computer" -Category ConnectionError -ErrorRecord $_ -Target $instance
                        return
                    }

                    if ($null -eq $currentState.IsHadrEnabled) {
                        $isenabled = $false
                    }
                    else {
                        $isenabled = $currentState.IsHadrEnabled
                    }
                    [PSCustomObject]@{
                        ComputerName     = $computer
                        InstanceName     = $instanceName
                        SqlInstance      = $instance.FullName
                        IsHadrEnabled    = $isenabled
                    }
                }
            }
        }
    }
    process {
        foreach ($instance in $SqlInstance) {
            $computer = $computerFullName = $instance.ComputerName
            $instanceName = $instance.InstanceName
            if (-not (Test-ElevationRequirement -ComputerName $instance)) {
                return
            }
            $noChange = $false

            switch ($instance.InstanceName) {
                'MSSQLSERVER' { $agentName = 'SQLSERVERAGENT' }
                default { $agentName = "SQLAgent`$$instanceName" }
            }

            try {
                Write-Message -Level Verbose -Message "Checking current Hadr setting for $computer"
                $currentState = GetDbaAgHadr -SqlInstance $instance -Credential $Credential
            }
            catch {
                Stop-Function -Message "Failure to pull current state of Hadr setting on $computer" -Category ConnectionError -ErrorRecord $_ -Target $instance -Continue
            }
            $isHadrEnabled = $currentState.IsHadrEnabled
            Write-Message -Level InternalComment -Message "$instance Hadr current value: $isHadrEnabled"

            # hadr results from sql wmi can be iffy, skip the check
            <#
            if ($isHadrEnabled) {
                Write-Message -Level Warning -Message "Hadr is already enabled for instance: $($instance.FullName)"
                $noChange = $true
                continue
            }
            #>

            $scriptblock = {
                $instance = $args[0]
                $sqlService = $wmi.Services | Where-Object DisplayName -eq "SQL Server ($instance)"
                $sqlService.ChangeHadrServiceSetting(1)
            }

            if ($noChange -eq $false) {
                if ($PSCmdlet.ShouldProcess($instance, "Changing Hadr from $isHadrEnabled to 1 for $instance")) {
                    try {
                        Invoke-ManagedComputerCommand -ComputerName $computerFullName -Credential $Credential -ScriptBlock $scriptblock -ArgumentList $instancename
                    }
                    catch {
                        Stop-Function -Continue -Message "Failure on $($instance.FullName) | This may be because AlwaysOn Availability Groups feature requires the x86(non-WOW) or x64 Enterprise Edition of SQL Server 2012 (or later version) running on Windows Server 2008 (or later version) with WSFC hotfix KB 2494036 installed."
                    }
                }
            }

            if (Test-Bound -ParameterName Force) {
                if ($PSCmdlet.ShouldProcess($instance, "Force provided, restarting Engine and Agent service for $instance on $computerFullName")) {
                    try {
                        $null = Stop-DbaSqlService -ComputerName $computerFullName -InstanceName $instanceName -Type Agent, Engine
                        $null = Start-DbaSqlService -ComputerName $computerFullName -InstanceName $instanceName -Type Agent, Engine
                    }
                    catch {
                        Stop-Function -Message "Issue restarting $instance" -Target $instance -Continue
                    }
                }
            }
            $newState = GetDbaAgHadr -SqlInstance $instance -Credential $Credential

            if (Test-Bound -Not -ParameterName Force) {
                Write-Message -Level Warning -Message "You must restart the SQL Server for it to take effect."
            }

            [PSCustomObject]@{
                ComputerName    = $newState.ComputerName
                InstanceName    = $newState.InstanceName
                SqlInstance     = $newState.SqlInstance
                IsHadrEnabled   = $true
            }
        }
    }
}
#ValidationTags#Messaging,FlowControl,Pipeline,CodeStyle#
function Enable-DbaForceNetworkEncryption {
    <#
        .SYNOPSIS
            Enables Force Encryption for a SQL Server instance.

        .DESCRIPTION
            Enables Force Encryption for a SQL Server instance. Note that this requires access to the Windows Server, not the SQL instance itself.

            This setting is found in Configuration Manager.

        .PARAMETER SqlInstance
            The target SQL Server.

        .PARAMETER Credential
            Allows you to login to the computer (not SQL Server instance) using alternative Windows credentials

        .PARAMETER WhatIf
            If this switch is enabled, no actions are performed but informational messages will be displayed that explain what would happen if the command were to run.

        .PARAMETER Confirm
            If this switch is enabled, you will be prompted for confirmation before executing any operations that change state.

        .PARAMETER EnableException
        By default, when something goes wrong we try to catch it, interpret it and give you a friendly warning message.
        This avoids overwhelming you with "sea of red" exceptions, but is inconvenient because it basically disables advanced scripting.
        Using this switch turns this "nice by default" feature off and enables you to catch exceptions with your own try/catch.

        .EXAMPLE
            Enable-DbaForceNetworkEncryption

            Enables Force Encryption on the default (MSSQLSERVER) instance on localhost. Requires (and checks for) RunAs admin.

        .EXAMPLE
            Enable-DbaForceNetworkEncryption -SqlInstance sql01\SQL2008R2SP2

            Enables Force Network Encryption for the SQL2008R2SP2 on sql01. Uses Windows Credentials to both connect and modify the registry.

        .EXAMPLE
            Enable-DbaForceNetworkEncryption -SqlInstance sql01\SQL2008R2SP2 -WhatIf

            Shows what would happen if the command were executed.

        .NOTES
            Tags: Certificate

            Website: https://dbatools.io
            Copyright: (C) Chrissy LeMaire, [email protected]
            License: MIT https://opensource.org/licenses/MIT
    #>
    [CmdletBinding(SupportsShouldProcess, ConfirmImpact = "Low", DefaultParameterSetName = 'Default')]
    param (
        [Parameter(ValueFromPipeline = $true)]
        [Alias("ServerInstance", "SqlServer", "ComputerName")]
        [DbaInstanceParameter[]]
        $SqlInstance = $env:COMPUTERNAME,
        [PSCredential]$Credential,
        [Alias('Silent')]
        [switch]$EnableException
    )
    process {

        foreach ($instance in $sqlinstance) {
            Write-Message -Level VeryVerbose -Message "Processing $instance." -Target $instance
            $null = Test-ElevationRequirement -ComputerName $instance -Continue

            Write-Message -Level Verbose -Message "Resolving hostname."
            $resolved = $null
            $resolved = Resolve-DbaNetworkName -ComputerName $instance -Turbo

            if ($null -eq $resolved) {
                Stop-Function -Message "Can't resolve $instance." -Target $instance -Continue -Category InvalidArgument
            }

            Write-Message -Level Output -Message "Connecting to SQL WMI on $($instance.ComputerName)."
            try {
                $sqlwmi = Invoke-ManagedComputerCommand -ComputerName $resolved.FullComputerName -ScriptBlock { $wmi.Services } -Credential $Credential -ErrorAction Stop | Where-Object DisplayName -eq "SQL Server ($($instance.InstanceName))"
            }
            catch {
                Stop-Function -Message "Failed to access $instance" -Target $instance -Continue -ErrorRecord $_
            }

            $regroot = ($sqlwmi.AdvancedProperties | Where-Object Name -eq REGROOT).Value
            $vsname = ($sqlwmi.AdvancedProperties | Where-Object Name -eq VSNAME).Value
            try {
                $instancename = $sqlwmi.DisplayName.Replace('SQL Server (', '').Replace(')', '') # Don't clown, I don't know regex :(
            }
            catch {
                # Probably because the instance name has been aliased or does not exist or samthin
            }
            $serviceaccount = $sqlwmi.ServiceAccount

            if ([System.String]::IsNullOrEmpty($regroot)) {
                $regroot = $sqlwmi.AdvancedProperties | Where-Object { $_ -match 'REGROOT' }
                $vsname = $sqlwmi.AdvancedProperties | Where-Object { $_ -match 'VSNAME' }

                if (![System.String]::IsNullOrEmpty($regroot)) {
                    $regroot = ($regroot -Split 'Value\=')[1]
                    $vsname = ($vsname -Split 'Value\=')[1]
                }
                else {
                    Stop-Function -Message "Can't find instance $vsname on $instance." -Continue -Category ObjectNotFound -Target $instance
                }
            }

            if ([System.String]::IsNullOrEmpty($vsname)) { $vsname = $instance }

            Write-Message -Level Output -Message "Regroot: $regroot" -Target $instance
            Write-Message -Level Output -Message "ServiceAcct: $serviceaccount" -Target $instance
            Write-Message -Level Output -Message "InstanceName: $instancename" -Target $instance
            Write-Message -Level Output -Message "VSNAME: $vsname" -Target $instance

            $scriptblock = {
                $regpath = "Registry::HKEY_LOCAL_MACHINE\$($args[0])\MSSQLServer\SuperSocketNetLib"
                $cert = (Get-ItemProperty -Path $regpath -Name Certificate).Certificate
                $oldvalue = (Get-ItemProperty -Path $regpath -Name ForceEncryption).ForceEncryption
                Set-ItemProperty -Path $regpath -Name ForceEncryption -Value $true
                $forceencryption = (Get-ItemProperty -Path $regpath -Name ForceEncryption).ForceEncryption

                [pscustomobject]@{
                    ComputerName          = $env:COMPUTERNAME
                    InstanceName          = $args[2]
                    SqlInstance           = $args[1]
                    ForceEncryption       = ($forceencryption -eq $true)
                    CertificateThumbprint = $cert
                }
            }

            if ($PScmdlet.ShouldProcess("local", "Connecting to $instance to modify the ForceEncryption value in $regroot for $($instance.InstanceName)")) {
                try {
                    Invoke-Command2 -ComputerName $resolved.FullComputerName -Credential $Credential -ArgumentList $regroot, $vsname, $instancename -ScriptBlock $scriptblock -ErrorAction Stop | Select-Object -Property * -ExcludeProperty PSComputerName, RunspaceId, PSShowComputerName
                    Write-Message -Level Critical -Message "Force encryption was successfully set on $($resolved.FullComputerName) for the $instancename instance. You must now restart the SQL Server for changes to take effect." -Target $instance
                }
                catch {
                    Stop-Function -Message "Failed to connect to $($resolved.FullComputerName) using PowerShell remoting!" -ErrorRecord $_ -Target $instance -Continue
                }
            }
        }
    }
}
function Enable-DbaTraceFlag {
    <#
    .SYNOPSIS
        Enable Global Trace Flag(s)
    .DESCRIPTION
        The function will set one or multiple trace flags on the SQL Server instance(s) listed

    .PARAMETER SqlInstance
        Allows you to specify a comma separated list of servers to query.

    .PARAMETER SqlCredential
        Login to the target instance using alternative credentials. Windows and SQL Authentication supported. Accepts credential objects (Get-Credential)

    .PARAMETER TraceFlag
        Trace flag number(s) to enable globally

    .PARAMETER EnableException
        By default, when something goes wrong we try to catch it, interpret it and give you a friendly warning message.
        This avoids overwhelming you with "sea of red" exceptions, but is inconvenient because it basically disables advanced scripting.
        Using this switch turns this "nice by default" feature off and enables you to catch exceptions with your own try/catch.

    .NOTES
        Tags: TraceFlag
        Author: Garry Bargsley (@gbargsley), http://blog.garrybargsley.com

        Website: https://dbatools.io
        Copyright: (C) Chrissy LeMaire, [email protected]
        License: MIT https://opensource.org/licenses/MIT

    .LINK
        https://dbatools.io/Enable-DbaTraceFlag

    .EXAMPLE
        Enable-DbaTraceFlag -SqlInstance sql2016 -TraceFlag 3226
        Enable the trace flag 3226 on SQL Server instance sql2016

    .EXAMPLE
        Enable-DbaTraceFlag -SqlInstance sql2016 -TraceFlag 1117, 1118
        Enable multiple trace flags on SQL Server instance sql2016
#>
    [CmdletBinding()]
    param (
        [parameter(Position = 0, Mandatory = $true, ValueFromPipeline = $True)]
        [Alias("ServerInstance", "SqlServer", "SqlServers")]
        [DbaInstanceParameter[]]$SqlInstance,
        [PSCredential]$SqlCredential,
        [parameter(Mandatory)]
        [int[]]$TraceFlag,
        [Alias('Silent')]
        [switch]$EnableException
    )

    process {
        foreach ($instance in $SqlInstance) {
            Write-Message -Level Verbose -Message "Connecting to $instance"
            try {
                $server = Connect-SqlInstance -SqlInstance $instance -SqlCredential $SqlCredential
            }
            catch {
                Stop-Function -Message "Failure" -Category ConnectionError -ErrorRecord $_ -Target $instance -Continue
            }

            $CurrentRunningTraceFlags = Get-DbaTraceFlag -SqlInstance $server -EnableException

            # We could combine all trace flags but the granularity is worth it
            foreach ($tf in $TraceFlag) {
                $TraceFlagInfo = [PSCustomObject]@{
                    SourceServer = $server.ComputerName
                    InstanceName = $server.ServiceName
                    SqlInstance  = $server.DomainInstanceName
                    TraceFlag    = $tf
                    Status       = $null
                    Notes        = $null
                    DateTime     = [DbaDateTime](Get-Date)
                }
                if ($CurrentRunningTraceFlags.TraceFlag -contains $tf) {
                    $TraceFlagInfo.Status = 'Skipped'
                    $TraceFlagInfo.Notes = "The Trace flag is already running."
                    $TraceFlagInfo
                    Write-Message -Level Warning -Message "The Trace flag [$tf] is already running globally."
                    continue
                }

                try {
                    $query = "DBCC TRACEON ($tf, -1)"
                    $server.Query($query)
                    $server.Refresh()
                }
                catch {
                    $TraceFlagInfo.Status = "Failed"
                    $TraceFlagInfo.Notes = $_.Exception.Message
                    $TraceFlagInfo
                    Stop-Function -Message "Failure" -ErrorRecord $_ -Target $server -Continue
                }
                $TraceFlagInfo.Status = "Successful"
                $TraceFlagInfo
            }
        }
    }
}
function Expand-DbaTLogResponsibly {
    <#
        .SYNOPSIS
            This command will help you to automatically grow your transaction log  file in a responsible way (preventing the generation of too many VLFs).

        .DESCRIPTION
            As you may already know, having a transaction log file with too many Virtual Log Files (VLFs) can hurt your database performance in many ways.

            Example:
                Too many VLFs can cause transaction log backups to slow down and can also slow down database recovery and, in extreme cases, even impact insert/update/delete performance.

                References:
                    http://www.sqlskills.com/blogs/kimberly/transaction-log-vlfs-too-many-or-too-few/
                    http://blogs.msdn.com/b/saponsqlserver/archive/2012/02/22/too-many-virtual-log-files-vlfs-can-cause-slow-database-recovery.aspx
                    http://www.brentozar.com/blitz/high-virtual-log-file-vlf-count/

                In order to get rid of this fragmentation we need to grow the file taking the following into consideration:
                    - How many VLFs are created when we perform a grow operation or when an auto-grow is invoked?

                Note: In SQL Server 2014 this algorithm has changed (http://www.sqlskills.com/blogs/paul/important-change-vlf-creation-algorithm-sql-server-2014/)

            Attention:
                We are growing in MB instead of GB because of known issue prior to SQL 2012:
                    More detail here:
                        http://www.sqlskills.com/BLOGS/PAUL/post/Bug-log-file-growth-broken-for-multiples-of-4GB.aspx
                    and
                        http://connect.microsoft.com/SqlInstance/feedback/details/481594/log-growth-not-working-properly-with-specific-growth-sizes-vlfs-also-not-created-appropriately
                    or
                        https://connect.microsoft.com/SqlInstance/feedback/details/357502/transaction-log-file-size-will-not-grow-exactly-4gb-when-filegrowth-4gb

            Understanding related problems:
                    http://www.sqlskills.com/blogs/kimberly/transaction-log-vlfs-too-many-or-too-few/
                    http://blogs.msdn.com/b/saponsqlserver/archive/2012/02/22/too-many-virtual-log-files-vlfs-can-cause-slow-database-recovery.aspx
                    http://www.brentozar.com/blitz/high-virtual-log-file-vlf-count/

            Known bug before SQL Server 2012
                    http://www.sqlskills.com/BLOGS/PAUL/post/Bug-log-file-growth-broken-for-multiples-of-4GB.aspx
                    http://connect.microsoft.com/SqlInstance/feedback/details/481594/log-growth-not-working-properly-with-specific-growth-sizes-vlfs-also-not-created-appropriately
                    https://connect.microsoft.com/SqlInstance/feedback/details/357502/transaction-log-file-size-will-not-grow-exactly-4gb-when-filegrowth-4gb

            How it works?
                The transaction log will grow in chunks until it reaches the desired size.
                Example: If you have a log file with 8192MB and you say that the target size is 81920MB (80GB) it will grow in chunks of 8192MB until it reaches 81920MB. 8192 -> 16384 -> 24576 ... 73728 -> 81920

        .PARAMETER SqlInstance
            The target SQL Server instance.

        .PARAMETER Database
            The database(s) to process. Options for this list are auto-populated from the server. If unspecified, all databases will be processed.

        .PARAMETER TargetLogSizeMB
            Specifies the target size of the transaction log file in megabytes.

        .PARAMETER IncrementSizeMB
            Specifies the amount the transaction log should grow in megabytes. If this value differs from the suggested value based on your TargetLogSizeMB, you will be prompted to confirm your choice.

            This value will be calculated if not specified.

        .PARAMETER LogFileId
            Specifies the file number(s) of additional transaction log files to grow.

            If this value is not specified, only the first transaction log file will be processed.

        .PARAMETER ShrinkLogFile
            If this switch is enabled, your transaction log files will be shrunk.

        .PARAMETER ShrinkSizeMB
            Specifies the target size of the transaction log file for the shrink operation.

        .PARAMETER BackupDirectory
            Specifies the location of your backups. Backups must be performed to shrink the transaction log.

            If this value is not specified, the SQL Server instance's default backup directory will be used.

         .PARAMETER SqlCredential
            Login to the target instance using alternative credentials. Windows and SQL Authentication supported. Accepts credential objects (Get-Credential)

        .PARAMETER ExcludeDatabase
            The database(s) to exclude. Options for this list are auto-populated from the server.

        .PARAMETER WhatIf
            If this switch is enabled, no actions are performed but informational messages will be displayed that explain what would happen if the command were to run.

        .PARAMETER Confirm
            If this switch is enabled, you will be prompted for confirmation before executing any operations that change state.

        .PARAMETER EnableException
            By default, when something goes wrong we try to catch it, interpret it and give you a friendly warning message.
            This avoids overwhelming you with "sea of red" exceptions, but is inconvenient because it basically disables advanced scripting.
            Using this switch turns this "nice by default" feature off and enables you to catch exceptions with your own try/catch.

        .NOTES
            Tags: Storage, Backup
            This script uses Get-DbaDiskSpace dbatools command to get the TLog's drive free space

            Author: Claudio Silva (@ClaudioESSilva)
            Requires: ALTER DATABASE permission
            Limitations: Freespace cannot be validated on the directory where the log file resides in SQL Server 2005.

            Website: https://dbatools.io
            Copyright (C) 2016 Chrissy LeMaire
            Copyright: (C) Chrissy LeMaire, [email protected]
            License: MIT https://opensource.org/licenses/MIT

        .LINK
            https://dbatools.io/Expand-DbaTLogResponsibly

        .EXAMPLE
            Expand-DbaTLogResponsibly -SqlInstance sqlcluster -Database db1 -TargetLogSizeMB 50000

            Grows the transaction log for database db1 on sqlcluster to 50000 MB and calculates the increment size.

        .EXAMPLE
            Expand-DbaTLogResponsibly -SqlInstance sqlcluster -Database db1, db2 -TargetLogSizeMB 10000 -IncrementSizeMB 200

            Grows the transaction logs for databases db1 and db2 on sqlcluster to 1000MB and sets the growth increment to 200MB.

        .EXAMPLE
            Expand-DbaTLogResponsibly -SqlInstance sqlcluster -Database db1 -TargetLogSizeMB 10000 -LogFileId 9

            Grows the transaction log file  with FileId 9 of the db1 database on sqlcluster instance to 10000MB.

        .EXAMPLE
            Expand-DbaTLogResponsibly -SqlInstance sqlcluster -Database (Get-Content D:\DBs.txt) -TargetLogSizeMB 50000

            Grows the transaction log of the databases specified in the file 'D:\DBs.txt' on sqlcluster instance to 50000MB.

        .EXAMPLE
            Expand-DbaTLogResponsibly -SqlInstance SqlInstance -Database db1,db2 -TargetLogSizeMB 100 -IncrementSizeMB 10 -ShrinkLogFile -ShrinkSizeMB 10 -BackupDirectory R:\MSSQL\Backup

            Grows the transaction logs for databases db1 and db2 on SQL server SQLInstance to 100MB, sets the incremental growth to 10MB, shrinks the transaction log to 10MB and uses the directory R:\MSSQL\Backup for the required backups.
    #>
    [CmdletBinding(SupportsShouldProcess, DefaultParameterSetName = 'Default')]
    param (
        [parameter(Position = 1, Mandatory = $true)]
        [Alias("ServerInstance", "SqlServer")]
        [DbaInstanceParameter]$SqlInstance,
        [parameter(Position = 3)]
        [PSCredential]$SqlCredential,
        [Alias("Databases")]
        [object[]]$Database,
        [parameter(Position = 4)]
        [object[]]$ExcludeDatabase,
        [parameter(Position = 5, Mandatory = $true)]
        [int]$TargetLogSizeMB,
        [parameter(Position = 6)]
        [int]$IncrementSizeMB = -1,
        [parameter(Position = 7)]
        [int]$LogFileId = -1,
        [parameter(Position = 8, ParameterSetName = 'Shrink', Mandatory = $true)]
        [switch]$ShrinkLogFile,
        [parameter(Position = 9, ParameterSetName = 'Shrink', Mandatory = $true)]
        [int]$ShrinkSizeMB,
        [parameter(Position = 10, ParameterSetName = 'Shrink')]
        [AllowEmptyString()]
        [string]$BackupDirectory,
        [switch][Alias('Silent')]
        $EnableException
    )

    begin {
        Write-Message -Level Verbose -Message "Set ErrorActionPreference to Inquire."
        $ErrorActionPreference = 'Inquire'

        #Convert MB to KB (SMO works in KB)
        Write-Message -Level Verbose -Message "Convert variables MB to KB (SMO works in KB)."
        [int]$TargetLogSizeKB = $TargetLogSizeMB * 1024
        [int]$LogIncrementSize = $incrementSizeMB * 1024
        [int]$ShrinkSize = $ShrinkSizeMB * 1024
        [int]$SuggestLogIncrementSize = 0
        [bool]$LogByFileID = if ($LogFileId -eq -1) {
            $false
        }
        else {
            $true
        }

        #Set base information
        Write-Message -Level Verbose -Message "Initialize the instance '$SqlInstance'."

        $server = Connect-SqlInstance -SqlInstance $SqlInstance -SqlCredential $SqlCredential

        if ($ShrinkLogFile -eq $true) {
            if ($BackupDirectory.length -eq 0) {
                $backupdirectory = $server.Settings.BackupDirectory
            }

            $pathexists = Test-DbaSqlPath -SqlInstance $server -Path $backupdirectory

            if ($pathexists -eq $false) {
                Stop-Function -Message "Backup directory does not exist."
            }
        }
    }

    process {

        try {

            [datetime]$initialTime = Get-Date

            #control the iteration number
            $databaseProgressbar = 0;

            Write-Message -Level Verbose -Message "Resolving NetBIOS name."
            $sourcenetbios = Resolve-NetBiosName $server

            $databases = $server.Databases | Where-Object IsAccessible
            Write-Message -Level Verbose -Message "Number of databases found: $($databases.Count)."
            if ($Database) {
                $databases = $databases | Where-Object Name -In $Database
            }
            if ($ExcludeDatabase) {
                $database = $databases | Where-Object Name -NotIn $ExcludeDatabase
            }

            #go through all databases
            Write-Message -Level Verbose -Message "Processing...foreach database..."
            foreach ($db in $databases.Name) {
                Write-Message -Level Verbose -Message "Working on $db."
                $databaseProgressbar += 1

                #set step to reutilize on logging operations
                [string]$step = "$databaseProgressbar/$($Database.Count)"

                if ($server.Databases[$db]) {
                    Write-Progress `
                        -Id 1 `
                        -Activity "Using database: $db on Instance: '$SqlInstance'" `
                        -PercentComplete ($databaseProgressbar / $Database.Count * 100) `
                        -Status "Processing - $databaseProgressbar of $($Database.Count)"

                    #Validate which file will grow
                    if ($LogByFileID) {
                        $logfile = $server.Databases[$db].LogFiles.ItemById($LogFileId)
                    }
                    else {
                        $logfile = $server.Databases[$db].LogFiles[0]
                    }

                    $numLogfiles = $server.Databases[$db].LogFiles.Count

                    Write-Message -Level Verbose -Message "$step - Use log file: $logfile."
                    $currentSize = $logfile.Size
                    $currentSizeMB = $currentSize / 1024

                    #Get the number of VLFs
                    $initialVLFCount = Test-DbaDbVirtualLogFile -SqlInstance $server -Database $db

                    Write-Message -Level Verbose -Message "$step - Log file current size: $([System.Math]::Round($($currentSize/1024.0), 2)) MB "
                    [long]$requiredSpace = ($TargetLogSizeKB - $currentSize)

                    Write-Message -Level Verbose -Message "Verifying if sufficient space exists ($([System.Math]::Round($($requiredSpace / 1024.0), 2))MB) on the volume to perform this task."

                    [long]$TotalTLogFreeDiskSpaceKB = 0
                    Write-Message -Level Verbose -Message "Get TLog drive free space"
                    [object]$AllDrivesFreeDiskSpace = Get-DbaDiskSpace -ComputerName $sourcenetbios | Select-Object Name, SizeInKB

                    #Verify path using Split-Path on $logfile.FileName in backwards. This way we will catch the LUNs. Example: "K:\Log01" as LUN name. Need to add final backslash if not there
                    $DrivePath = Split-Path $logfile.FileName -parent
                    $DrivePath = if (!($DrivePath.EndsWith("\"))) { "$DrivePath\" }
                    else { $DrivePath }
                    Do {
                        if ($AllDrivesFreeDiskSpace | Where-Object { $DrivePath -eq "$($_.Name)" }) {
                            $TotalTLogFreeDiskSpaceKB = ($AllDrivesFreeDiskSpace | Where-Object { $DrivePath -eq $_.Name }).SizeInKB
                            $match = $true
                            break
                        }
                        else {
                            $match = $false
                            $DrivePath = Split-Path $DrivePath -parent
                            $DrivePath = if (!($DrivePath.EndsWith("\"))) { "$DrivePath\" }
                            else { $DrivePath }
                        }

                    }
                    while (!$match -or ([string]::IsNullOrEmpty($DrivePath)))

                    Write-Message -Level Verbose -Message "Total TLog Free Disk Space in MB: $([System.Math]::Round($($TotalTLogFreeDiskSpaceKB / 1024.0), 2))"

                    if (($TotalTLogFreeDiskSpaceKB -le 0) -or ([string]::IsNullOrEmpty($TotalTLogFreeDiskSpaceKB))) {
                        $title = "Choose increment value for database '$db':"
                        $message = "Cannot validate freespace on drive where the log file resides. Do you wish to continue? (Y/N)"
                        $yes = New-Object System.Management.Automation.Host.ChoiceDescription "&Yes", "Will continue"
                        $no = New-Object System.Management.Automation.Host.ChoiceDescription "&No", "Will exit"
                        $options = [System.Management.Automation.Host.ChoiceDescription[]]($yes, $no)
                        $result = $host.ui.PromptForChoice($title, $message, $options, 0)
                        #no
                        if ($result -eq 1) {
                            Write-Message -Level Warning -Message "You have cancelled the execution"
                            return
                        }
                    }

                    if ($requiredSpace -gt $TotalTLogFreeDiskSpaceKB) {
                        Write-Message -Level Verbose -Message "There is not enough space on volume to perform this task. `r`n" `
                            "Available space: $([System.Math]::Round($($TotalTLogFreeDiskSpaceKB / 1024.0), 2))MB;`r`n" `
                            "Required space: $([System.Math]::Round($($requiredSpace / 1024.0), 2))MB;"
                        return
                    }
                    else {
                        if ($currentSize -ige $TargetLogSizeKB -and ($ShrinkLogFile -eq $false)) {
                            Write-Message -Level Verbose -Message "$step - [INFO] The T-Log file '$logfile' size is already equal or greater than target size - No action required."
                        }
                        else {
                            Write-Message -Level Verbose -Message "$step - [OK] There is sufficient free space to perform this task."

                            # If SQL Server version is greater or equal to 2012
                            if ($server.Version.Major -ge "11") {
                                switch ($TargetLogSizeMB) {
                                    { $_ -le 64 } { $SuggestLogIncrementSize = 64 }
                                    { $_ -ge 64 -and $_ -lt 256 } { $SuggestLogIncrementSize = 256 }
                                    { $_ -ge 256 -and $_ -lt 1024 } { $SuggestLogIncrementSize = 512 }
                                    { $_ -ge 1024 -and $_ -lt 4096 } { $SuggestLogIncrementSize = 1024 }
                                    { $_ -ge 4096 -and $_ -lt 8192 } { $SuggestLogIncrementSize = 2048 }
                                    { $_ -ge 8192 -and $_ -lt 16384 } { $SuggestLogIncrementSize = 4096 }
                                    { $_ -ge 16384 } { $SuggestLogIncrementSize = 8192 }
                                }
                            }
                            # 2008 R2 or under
                            else {
                                switch ($TargetLogSizeMB) {
                                    { $_ -le 64 } { $SuggestLogIncrementSize = 64 }
                                    { $_ -ge 64 -and $_ -lt 256 } { $SuggestLogIncrementSize = 256 }
                                    { $_ -ge 256 -and $_ -lt 1024 } { $SuggestLogIncrementSize = 512 }
                                    { $_ -ge 1024 -and $_ -lt 4096 } { $SuggestLogIncrementSize = 1024 }
                                    { $_ -ge 4096 -and $_ -lt 8192 } { $SuggestLogIncrementSize = 2048 }
                                    { $_ -ge 8192 -and $_ -lt 16384 } { $SuggestLogIncrementSize = 4000 }
                                    { $_ -ge 16384 } { $SuggestLogIncrementSize = 8000 }
                                }

                                if (($IncrementSizeMB % 4096) -eq 0) {
                                    Write-Message -Level Verbose -Message "Your instance version is below SQL 2012, remember the known BUG mentioned on HELP. `r`nUse Get-Help Expand-DbaTLogFileResponsibly to read help`r`nUse a different value for incremental size.`r`n"
                                    return
                                }
                            }
                            Write-Message -Level Verbose -Message "Instance $server version: $($server.Version.Major) - Suggested TLog increment size: $($SuggestLogIncrementSize)MB"

                            # Shrink Log File to desired size before re-growth to desired size (You need to remove as many VLF's as possible to ensure proper growth)
                            $ShrinkSizeMB = $ShrinkSize / 1024
                            if ($ShrinkLogFile -eq $true) {
                                if ($server.Databases[$db].RecoveryModel -eq [Microsoft.SqlServer.Management.Smo.RecoveryModel]::Simple) {
                                    Write-Message -Level Warning -Message "Database '$db' is in Simple RecoveryModel which does not allow log backups. Do not specify -ShrinkLogFile and -ShrinkSizeMB parameters."
                                    Continue
                                }

                                try {
                                    $sql = "SELECT last_log_backup_lsn FROM sys.database_recovery_status WHERE database_id = DB_ID('$db')"
                                    $sqlResult = $server.ConnectionContext.ExecuteWithResults($sql);

                                    if ($sqlResult.Tables[0].Rows[0]["last_log_backup_lsn"] -is [System.DBNull]) {
                                        Write-Message -Level Warning -Message "First, you need to make a full backup before you can do Tlog backup on database '$db' (last_log_backup_lsn is null)."
                                        Continue
                                    }
                                }
                                catch {
                                    Stop-Function -Message "Can't execute SQL on $server. `r`n $($_)" -Continue
                                }

                                If ($Pscmdlet.ShouldProcess($($server.name), "Backing up TLog for $db")) {
                                    Write-Message -Level Verbose -Message "We are about to backup the Tlog for database '$db' to '$backupdirectory' and shrink the log."
                                    Write-Message -Level Verbose -Message "Starting Size = $currentSizeMB."

                                    $DefaultCompression = $server.Configuration.DefaultBackupCompression.ConfigValue

                                    if ($currentSizeMB -gt $ShrinkSizeMB) {
                                        $backupRetries = 1
                                        Do {
                                            try {
                                                $percent = $null
                                                $backup = New-Object Microsoft.SqlServer.Management.Smo.Backup
                                                $backup.Action = [Microsoft.SqlServer.Management.Smo.BackupActionType]::Log
                                                $backup.BackupSetDescription = "Transaction Log backup of " + $db
                                                $backup.BackupSetName = $db + " Backup"
                                                $backup.Database = $db
                                                $backup.MediaDescription = "Disk"
                                                $dt = get-date -format yyyyMMddHHmmssms
                                                $null = $backup.Devices.AddDevice($backupdirectory + "\" + $db + "_db_" + $dt + ".trn", 'File')
                                                if ($DefaultCompression = $true) {
                                                    $backup.CompressionOption = 1
                                                }
                                                else {
                                                    $backup.CompressionOption = 0
                                                }
                                                $null = [Microsoft.SqlServer.Management.Smo.PercentCompleteEventHandler] {
                                                    Write-Progress -id 2 -ParentId 1 -activity "Backing up $db to $server" -percentcomplete $_.Percent -status ([System.String]::Format("Progress: {0} %", $_.Percent))
                                                }
                                                $backup.add_PercentComplete($percent)
                                                $backup.PercentCompleteNotification = 10
                                                $backup.add_Complete($complete)
                                                Write-Progress -id 2 -ParentId 1 -activity "Backing up $db to $server" -percentcomplete 0 -Status ([System.String]::Format("Progress: {0} %", 0))
                                                $backup.SqlBackup($server)
                                                Write-Progress -id 2 -ParentId 1 -activity "Backing up $db to $server" -status "Complete" -Completed
                                                $logfile.Shrink($ShrinkSizeMB, [Microsoft.SqlServer.Management.SMO.ShrinkMethod]::TruncateOnly)
                                                $logfile.Refresh()
                                            }
                                            catch {
                                                Write-Progress -id 1 -activity "Backup" -status "Failed" -completed
                                                Stop-Function -Message "Backup failed for database" -ErrorRecord $_ -Target $db -Continue
                                                Continue
                                            }

                                        }
                                        while (($logfile.Size / 1024) -gt $ShrinkSizeMB -and ++$backupRetries -lt 6)

                                        $currentSize = $logfile.Size
                                        Write-Message -Level Verbose -Message "TLog backup and truncate for database '$db' finished. Current TLog size after $backupRetries backups is $($currentSize/1024)MB"
                                    }
                                }
                            }

                            # SMO uses values in KB
                            $SuggestLogIncrementSize = $SuggestLogIncrementSize * 1024

                            # If default, use $SuggestedLogIncrementSize
                            if ($IncrementSizeMB -eq -1) {
                                $LogIncrementSize = $SuggestLogIncrementSize
                            }
                            else {
                                $title = "Choose increment value for database '$db':"
                                $message = "The input value for increment size was $([System.Math]::Round($LogIncrementSize/1024, 0))MB. However the suggested value for increment is $($SuggestLogIncrementSize/1024)MB.`r`nDo you want to use the suggested value of $([System.Math]::Round($SuggestLogIncrementSize/1024, 0))MB insted of $([System.Math]::Round($LogIncrementSize/1024, 0))MB"
                                $yes = New-Object System.Management.Automation.Host.ChoiceDescription "&Yes", "Uses recomended size."
                                $no = New-Object System.Management.Automation.Host.ChoiceDescription "&No", "Will use parameter value."
                                $options = [System.Management.Automation.Host.ChoiceDescription[]]($yes, $no)
                                $result = $host.ui.PromptForChoice($title, $message, $options, 0)
                                #yes
                                if ($result -eq 0) {
                                    $LogIncrementSize = $SuggestLogIncrementSize
                                }
                            }

                            #start growing file
                            If ($Pscmdlet.ShouldProcess($($server.name), "Starting log growth. Increment chunk size: $($LogIncrementSize/1024)MB for database '$db'")) {
                                Write-Message -Level Verbose -Message "Starting log growth. Increment chunk size: $($LogIncrementSize/1024)MB for database '$db'"

                                Write-Message -Level Verbose -Message "$step - While current size less than target log size."

                                while ($currentSize -lt $TargetLogSizeKB) {

                                    Write-Progress `
                                        -Id 2 `
                                        -ParentId 1 `
                                        -Activity "Growing file $logfile on '$db' database" `
                                        -PercentComplete ($currentSize / $TargetLogSizeKB * 100) `
                                        -Status "Remaining - $([System.Math]::Round($($($TargetLogSizeKB - $currentSize) / 1024.0), 2)) MB"

                                    Write-Message -Level Verbose -Message "$step - Verifying if the log can grow or if it's already at the desired size."
                                    if (($TargetLogSizeKB - $currentSize) -lt $LogIncrementSize) {
                                        Write-Message -Level Verbose -Message "$step - Log size is lower than the increment size. Setting current size equals $TargetLogSizeKB."
                                        $currentSize = $TargetLogSizeKB
                                    }
                                    else {
                                        Write-Message -Level Verbose -Message "$step - Grow the $logfile file in $([System.Math]::Round($($LogIncrementSize / 1024.0), 2)) MB"
                                        $currentSize += $LogIncrementSize
                                    }

                                    #When -WhatIf Switch, do not run
                                    if ($PSCmdlet.ShouldProcess("$step - File will grow to $([System.Math]::Round($($currentSize/1024.0), 2)) MB", "This action will grow the file $logfile on database $db to $([System.Math]::Round($($currentSize/1024.0), 2)) MB .`r`nDo you wish to continue?", "Perform grow")) {
                                        Write-Message -Level Verbose -Message "$step - Set size $logfile to $([System.Math]::Round($($currentSize/1024.0), 2)) MB"
                                        $logfile.size = $currentSize

                                        Write-Message -Level Verbose -Message "$step - Applying changes"
                                        $logfile.Alter()
                                        Write-Message -Level Verbose -Message "$step - Changes have been applied"

                                        #Will put the info like VolumeFreeSpace up to date
                                        $logfile.Refresh()
                                    }
                                }

                                Write-Message -Level Verbose -Message "`r`n$step - [OK] Growth process for logfile '$logfile' on database '$db', has been finished."

                                Write-Message -Level Verbose -Message "$step - Grow $logfile log file on $db database finished."
                            }
                        }
                    } #else space available
                }
                #else verifying existence
                else {
                    Write-Message -Level Verbose -Message "Database '$db' does not exist on instance '$SqlInstance'."
                }

                #Get the number of VLFs
                $currentVLFCount = Test-DbaDbVirtualLogFile -SqlInstance $server -Database $db

                [pscustomobject]@{
                    ComputerName    = $server.ComputerName
                    InstanceName    = $server.ServiceName
                    SqlInstance     = $server.DomainInstanceName
                    Database        = $db
                    ID              = $logfile.ID
                    Name            = $logfile.Name
                    LogFileCount    = $numLogfiles
                    InitialSize     = [dbasize]($currentSizeMB * 1024)
                    CurrentSize     = [dbasize]($TargetLogSizeMB * 1024)
                    InitialVLFCount = $initialVLFCount.Total
                    CurrentVLFCount = $currentVLFCount.Total
                } | Select-DefaultView -ExcludeProperty LogFileCount
            } #foreach database
        }
        catch {
            Stop-Function -Message "Logfile $logfile on database $db not processed. Error: $($_.Exception.Message). Line Number:  $($_InvocationInfo.ScriptLineNumber)" -Continue
        }
    }

    end {
        Write-Message -Level Verbose -Message "Process finished $((Get-Date) - ($initialTime))"
        Test-DbaDeprecation -DeprecatedOn "1.0.0" -Alias Expand-SqlTLogResponsibly
    }
}
#ValidationTags#Messaging#
function Export-DbaAvailabilityGroup {
    <#
        .SYNOPSIS
            Exports SQL Server Availability Groups to a T-SQL file.

        .DESCRIPTION
            Exports SQL Server Availability Groups creation scripts to a T-SQL file. This is a function that is not available in SSMS.

        .PARAMETER SqlInstance
            The SQL Server instance name. SQL Server 2012 and above supported.

        .PARAMETER FilePath
            The directory name where the output files will be written. A sub directory with the format 'ServerName$InstanceName' will be created. A T-SQL scripts named 'AGName.sql' will be created under this subdirectory for each scripted Availability Group.

        .PARAMETER AvailabilityGroup
            The Availability Group(s) to export - this list is auto-populated from the server. If unspecified, all logins will be processed.

        .PARAMETER ExcludeAvailabilityGroup
            The Availability Group(s) to exclude - this list is auto-populated from the server.

        .PARAMETER NoClobber
            Do not overwrite existing export files.

        .PARAMETER SqlCredential
            Login to the target instance using alternative credentials. Windows and SQL Authentication supported. Accepts credential objects (Get-Credential)

        .PARAMETER WhatIf
            Shows you what it'd output if you were to run the command

        .PARAMETER Confirm
            Confirms each step/line of output

        .PARAMETER EnableException
            By default, when something goes wrong we try to catch it, interpret it and give you a friendly warning message.
            This avoids overwhelming you with "sea of red" exceptions, but is inconvenient because it basically disables advanced scripting.
            Using this switch turns this "nice by default" feature off and enables you to catch exceptions with your own try/catch.

        .NOTES
            Tags: Hadr, AG, AvailabilityGroup
            Author: Chris Sommer (@cjsommer), cjsommer.com

            dbatools PowerShell module (https://dbatools.io)
            Copyright (C) 2016 Chrissy LeMaire
            License: MIT https://opensource.org/licenses/MIT

        .LINK
            https://dbatools.io/Export-DbaAvailabilityGroup

        .EXAMPLE
            Export-DbaAvailabilityGroup -SqlInstance sql2012

            Exports all Availability Groups from SQL server "sql2012". Output scripts are written to the Documents\SqlAgExports directory by default.

        .EXAMPLE
            Export-DbaAvailabilityGroup -SqlInstance sql2012 -FilePath C:\temp\availability_group_exports

            Exports all Availability Groups from SQL server "sql2012". Output scripts are written to the C:\temp\availability_group_exports directory.

        .EXAMPLE
            Export-DbaAvailabilityGroup -SqlInstance sql2012 -FilePath 'C:\dir with spaces\availability_group_exports' -AvailabilityGroups AG1,AG2

            Exports Availability Groups AG1 and AG2 from SQL server "sql2012". Output scripts are written to the C:\dir with spaces\availability_group_exports directory.

        .EXAMPLE
            Export-DbaAvailabilityGroup -SqlInstance sql2014 -FilePath C:\temp\availability_group_exports -NoClobber

            Exports all Availability Groups from SQL server "sql2014". Output scripts are written to the C:\temp\availability_group_exports directory. If the export file already exists it will not be overwritten.
    #>
    [CmdletBinding(SupportsShouldProcess = $true)]
    param (
        [parameter(Mandatory = $true, ValueFromPipeline = $true)]
        [Alias("ServerInstance", "SqlServer")]
        [DbaInstanceParameter[]]$SqlInstance,
        [PSCredential]$SqlCredential,
        [object[]]$AvailabilityGroup,
        [object[]]$ExcludeAvailabilityGroup,
        [Alias("OutputLocation", "Path")]
        [string]$FilePath = "$([Environment]::GetFolderPath("MyDocuments"))\SqlAgExport",
        [switch]$NoClobber,
        [Alias('Silent')]
        [switch]$EnableException
    )

    process {
        foreach ($instance in $SqlInstance) {
            Write-Message -Level Verbose -Message "Connecting to $instance"
            try {
                $server = Connect-SqlInstance -SqlInstance $instance -SqlCredential $SqlCredential
            }
            catch {
                Stop-Function -Message "Failure" -Category ConnectionError -ErrorRecord $_ -Target $instance -Continue
            }

            if ($server.IsHadrEnabled -eq $false) {
                Stop-Function -Message "Hadr is not enabled on this instance" -Continue
            }
            else {
                # Get all of the Availability Groups and filter if required
                $ags = $server.AvailabilityGroups
            }

            if (Test-Bound 'AvailabilityGroup') {
                $ags = $ags | Where-Object Name -In $AvailabilityGroup
            }
            if (Test-Bound 'ExcludeAvailabilityGroup') {
                $ags = $ags | Where-Object Name -NotIn $ExcludeAvailabilityGroup
            }

            if ($ags) {

                # Set and create the OutputLocation if it doesn't exist
                $sqlinst = $instance.ToString().Replace('\', '$')
                $outputLocation = "$FilePath\$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"
            }
        }
    }
}
function Export-DbaDacpac {
    <#
    .SYNOPSIS
    Exports a dacpac from a server.

    .DESCRIPTION
    Using SQLPackage, export a dacpac from an instance of SQL Server.

    Note - Extract from SQL Server is notoriously flaky - for example if you have three part references to external databases it will not work.

    For help with the extract action parameters and properties, refer to https://msdn.microsoft.com/en-us/library/hh550080(v=vs.103).aspx

    .PARAMETER SqlInstance
    SQL Server name or SMO object representing the SQL Server to connect to and publish to.

    .PARAMETER SqlCredential
    Allows you to login to servers using alternative logins instead Integrated, accepts Credential object created by Get-Credential

    .PARAMETER Path
    The directory where the .dacpac files will be exported to. Defaults to documents.

    .PARAMETER Database
    The database(s) to process - this list is auto-populated from the server. If unspecified, all databases will be processed.

    .PARAMETER ExcludeDatabase
    The database(s) to exclude - this list is auto-populated from the server

    .PARAMETER AllUserDatabases
    Run command against all user databases

    .PARAMETER ExtendedParameters
    Optional parameters used to extract the DACPAC. More information can be found at
    https://msdn.microsoft.com/en-us/library/hh550080.aspx

    .PARAMETER ExtendedProperties
    Optional properties used to extract the DACPAC. More information can be found at
    https://msdn.microsoft.com/en-us/library/hh550080.aspx


    .PARAMETER EnableException
    By default, when something goes wrong we try to catch it, interpret it and give you a friendly warning message.
    This avoids overwhelming you with "sea of red" exceptions, but is inconvenient because it basically disables advanced scripting.
    Using this switch turns this "nice by default" feature off and enables you to catch exceptions with your own try/catch.

    .NOTES
    Tags: Migration, Database, Dacpac
    Author: Richie lee (@bzzzt_io)

    Website: https://dbatools.io
    Copyright: (C) Chrissy LeMaire, [email protected]
    License: MIT https://opensource.org/licenses/MIT

    .LINK
    https://dbatools.io/Export-DbaDacpac

    .EXAMPLE
    Export-DbaDacpac -SqlInstance sql2016 -Database SharePoint_Config
    Exports the dacpac for SharePoint_Config on sql2016 to $home\Documents\SharePoint_Config.dacpac

    .EXAMPLE
    $moreprops = "/p:VerifyExtraction=$true /p:CommandTimeOut=10"
    Export-DbaDacpac -SqlInstance sql2016 -Database SharePoint_Config -Path C:\temp -ExtendedProperties $moreprops

    Sets the CommandTimeout to 10 then extracts the dacpac for SharePoint_Config on sql2016 to C:\temp\SharePoint_Config.dacpac then verifies extraction.


    #>
    [CmdletBinding()]
    param
    (
        [parameter(Mandatory, ValueFromPipeline)]
        [Alias("ServerInstance", "SqlServer")]
        [DbaInstance[]]$SqlInstance,
        [Alias("Credential")]
        [PSCredential]$SqlCredential,
        [object[]]$Database,
        [object[]]$ExcludeDatabase,
        [switch]$AllUserDatabases,
        [string]$Path = "$home\Documents",
        [string]$ExtendedParameters,
        [string]$ExtendedProperties,
        [switch]$EnableException
    )

    process {
        if ((Test-Bound -Not -ParameterName Database) -and (Test-Bound -Not -ParameterName ExcludeDatabase) -and (Test-Bound -Not -ParameterName AllUserDatabases)) {
            Stop-Function -Message "You must specify databases to execute against using either -Database, -ExcludeDatabase or -AllUserDatabases"
        }

        if (-not (Test-Path $Path)) {
            Stop-Function -Message "$Path doesn't exist or access denied"
        }

        if ((Get-Item $path) -isnot [System.IO.DirectoryInfo]) {
            Stop-Function -Message "Path must be a directory"
        }

        foreach ($instance in $sqlinstance) {

            try {
                Write-Message -Level Verbose -Message "Connecting to $instance."
                $server = Connect-SqlInstance -SqlInstance $instance -SqlCredential $sqlcredential
            }
            catch {
                Stop-Function -Message "Failure" -Category ConnectionError -ErrorRecord $_ -Target $instance -Continue
            }
            $cleaninstance = $instance.ToString().Replace('\', '-')

            $dbs = $server.Databases | Where-Object { $_.IsSystemObject -eq $false -and $_.IsAccessible }

            if ($Database) {
                $dbs = $dbs | Where-Object Name -in $Database
                if (-not $dbs.name) {
                    Stop-Function -Message "Database $Database does not exist on $instance" -Target $instance -Continue
                }
            }

            if ($ExcludeDatabase) {
                $dbs = $dbs | Where-Object Name -notin $ExcludeDatabase
            }

            foreach ($db in $dbs) {
                $dbname = $db.name
                $connstring = $server.ConnectionContext.ConnectionString.Replace('"', "'")
                if ($connstring -notmatch 'Database=') {
                    $connstring = "$connstring;Database=$dbname"
                }
                $filename = "$Path\$cleaninstance-$dbname.dacpac"
                Write-Message -Level Verbose -Message "Exporting $filename"
                Write-Message -Level Verbose -Message "Using connection string $connstring"

                $sqlPackageArgs = "/action:Extract /tf:""$filename"" /SourceConnectionString:""$connstring"" $ExtendedParameters $ExtendedProperties"
                $resultstime = [diagnostics.stopwatch]::StartNew()

                try {
                    $startprocess = New-Object System.Diagnostics.ProcessStartInfo
                    $startprocess.FileName = "$script:PSModuleRoot\bin\smo\sqlpackage.exe"
                    $startprocess.Arguments = $sqlPackageArgs
                    $startprocess.RedirectStandardError = $true
                    $startprocess.RedirectStandardOutput = $true
                    $startprocess.UseShellExecute = $false
                    $startprocess.CreateNoWindow = $true
                    $process = New-Object System.Diagnostics.Process
                    $process.StartInfo = $startprocess
                    $process.Start() | Out-Null
                    $process.WaitForExit()
                    $stdout = $process.StandardOutput.ReadToEnd()
                    $stderr = $process.StandardError.ReadToEnd()
                    Write-Message -level Verbose -Message "StandardOutput: $stdout"

                    [pscustomobject]@{
                        ComputerName = $server.ComputerName
                        InstanceName = $server.ServiceName
                        SqlInstance  = $server.DomainInstanceName
                        Database     = $dbname
                        Path         = $filename
                        Elapsed      = [prettytimespan]($resultstime.Elapsed)
                    } | Select-DefaultView -ExcludeProperty ComputerName, InstanceName
                }
                catch {
                    Stop-Function -Message "SQLPackage Failure" -ErrorRecord $_ -Continue
                }

                if ($process.ExitCode -ne 0) {
                    Stop-Function -Message "Standard output - $stderr" -Continue
                }
            }
        }
    }
}
function Export-DbaDiagnosticQuery {
    <#
        .SYNOPSIS
            Export-DbaDiagnosticQuery can convert ouput generated by Invoke-DbaDiagnosticQuery to CSV or Excel

        .DESCRIPTION
            The default output format of Invoke-DbaDiagnosticQuery is a custom object. It can also output to CSV and Excel.
            However, CSV output can generate a lot of files and Excel output depends on the ImportExcel module by Doug Fike (https://github.com/dfinke/ImportExcel)
            Export-DbaDiagnosticQuery can be used to convert from the default export type to the other available export types.

        .PARAMETER InputObject
            Specifies the objects to convert

        .PARAMETER ConvertTo
            Specifies the output type. Valid choices are Excel and CSV. CSV is the default.

        .PARAMETER Path
            Specifies the path to the output files.

        .PARAMETER Suffix
            Suffix for the filename. It's datetime by default.

        .PARAMETER NoPlanExport
            Use this switch to suppress exporting of .sqlplan files

        .PARAMETER NoQueryExport
            Use this switch to suppress exporting of .sql files

        .PARAMETER EnableException
            By default, when something goes wrong we try to catch it, interpret it and give you a friendly warning message.
            This avoids overwhelming you with "sea of red" exceptions, but is inconvenient because it basically disables advanced scripting.
            Using this switch turns this "nice by default" feature off and enables you to catch exceptions with your own try/catch.

        .NOTES
            Tags: Query
            Author: Andre Kamman (@AndreKamman), http://clouddba.io

            Website: https://dbatools.io
            Copyright: (C) Chrissy LeMaire, [email protected]
            License: MIT https://opensource.org/licenses/MIT

        .LINK
            https://dbatools.io/Export-DbaDiagnosticQuery

        .EXAMPLE
            Invoke-DbaDiagnosticQuery -SqlInstance sql2016 | Export-DbaDiagnosticQuery -Path c:\temp

            Converts output from Invoke-DbaDiagnosticQuery to multiple CSV files

        .EXAMPLE
            $output = Invoke-DbaDiagnosticQuery -SqlInstance sql2016
            Export-DbaDiagnosticQuery -InputObject $output -ConvertTo Excel

            Converts output from Invoke-DbaDiagnosticQuery to Excel worksheet(s) in the Documents folder
    #>
    [CmdletBinding()]
    param (
        [parameter(Mandatory = $true, ValueFromPipeline = $true)]
        [object[]]$InputObject,
        [ValidateSet("Excel", "Csv")]
        [string]$ConvertTo = "Csv",
        [System.IO.FileInfo]$Path = [Environment]::GetFolderPath("mydocuments"),
        [string]$Suffix = "$(Get-Date -format 'yyyyMMddHHmmssms')",
        [switch]$NoPlanExport,
        [switch]$NoQueryExport,
        [Alias('Silent')]
        [switch]$EnableException
    )

    begin {
        if ($ConvertTo -eq "Excel") {
            try {
                Import-Module ImportExcel -ErrorAction Stop
            }
            catch {
                $message = "Failed to load module, exporting to Excel feature is not available
                            Install the module from: https://github.com/dfinke/ImportExcel
                            Valid alternative conversion format is csv"
                Stop-Function -Message $message
                return
            }
        }

        if (!$(Test-Path $Path)) {
            try {
                New-Item $Path -ItemType Directory -ErrorAction Stop | Out-Null
                Write-Message -Level Output -Message "Created directory $Path"
            }
            catch {
                Stop-Function -Message "Failed to create directory $Path" -Continue
            }
        }

        Function Remove-InvalidFileNameChars {
            [CmdletBinding()]
            param (
                [Parameter(Mandatory = $true,
                    Position = 0,
                    ValueFromPipeline = $true,
                    ValueFromPipelineByPropertyName = $true)]
                [String]$Name
            )
            $Name = $Name.Replace(" ", "-")
            $invalidChars = [IO.Path]::GetInvalidFileNameChars() -join ''
            $re = "[{0}]" -f [RegEx]::Escape($invalidChars)
            return ($Name -replace $re)
        }
    }

    process {
        if (Test-FunctionInterrupt) { return }

        foreach ($row in $InputObject) {
            $result = $row.Result
            $name = $row.Name
            $SqlInstance = $row.SqlInstance.Replace("\", "$")
            $dbname = $row.Database
            $number = $row.Number
            $note = $row.Note

            if ($null -eq $result) {
                Stop-Function -Message "Result was empty for $name" -Target $result -Continue
            }

            $queryname = Remove-InvalidFileNameChars -Name $Name
            $excelfilename = "$Path\$SqlInstance-DQ-$Suffix.xlsx"
            $exceldbfilename = "$Path\$SqlInstance-DQ-$dbname-$Suffix.xlsx"
            $csvdbfilename = "$Path\$SqlInstance-$dbname-DQ-$number-$queryname-$Suffix.csv"
            $csvfilename = "$Path\$SqlInstance-DQ-$number-$queryname-$Suffix.csv"

            $columnnameoptions = "Query Plan", "QueryPlan", "Query_Plan", "query_plan_xml"
            if (($result | Get-Member | Where-Object Name -in $columnnameoptions).Count -gt 0) {
                $plannr = 0
                $columnname = ($result | Get-Member | Where-Object Name -In $columnnameoptions).Name
                foreach ($plan in $result."$columnname") {
                    $plannr += 1
                    if ($row.DatabaseSpecific) {
                        $planfilename = "$Path\$SqlInstance-$dbname-DQ-$number-$queryname-$plannr-$Suffix.sqlplan"
                    }
                    else {
                        $planfilename = "$Path\$SqlInstance-DQ-$number-$queryname-$plannr-$Suffix.sqlplan"
                    }

                    if (!$NoPlanExport) {
                        Write-Message -Level Output -Message "Exporting $planfilename"
                        if ($plan) {$plan | Out-File -FilePath $planfilename}
                    }
                }

                $result = $result | Select-Object * -ExcludeProperty "$columnname"
            }

            $columnnameoptions = "Complete Query Text", "QueryText", "Query Text", "Query_Text", "query_sql_text"
            if (($result | Get-Member | Where-Object Name -In $columnnameoptions ).Count -gt 0) {
                $sqlnr = 0
                $columnname = ($result | Get-Member | Where-Object Name -In $columnnameoptions).Name
                foreach ($sql in $result."$columnname") {
                    $sqlnr += 1
                    if ($row.DatabaseSpecific) {
                        $sqlfilename = "$Path\$SqlInstance-$dbname-DQ-$number-$queryname-$sqlnr-$Suffix.sql"
                    }
                    else {
                        $sqlfilename = "$Path\$SqlInstance-DQ-$number-$queryname-$sqlnr-$Suffix.sql"
                    }

                    if (!$NoQueryExport) {
                        Write-Message -Level Output -Message "Exporting $sqlfilename"
                        if ($sql) {$sql | Out-File -FilePath $sqlfilename}
                    }
                }

                $result = $result | Select-Object * -ExcludeProperty "$columnname"
            }

            switch ($ConvertTo) {
                "Excel" {
                    if ($row.DatabaseSpecific) {
                        Write-Message -Level Output -Message "Exporting $exceldbfilename"
                        $result | Export-Excel -Path $exceldbfilename -WorkSheetname $Name -AutoSize -AutoFilter -BoldTopRow -FreezeTopRow
                    }
                    else {
                        Write-Message -Level Output -Message "Exporting $excelfilename"
                        $result | Export-Excel -Path $excelfilename -WorkSheetname $Name -AutoSize -AutoFilter -BoldTopRow -FreezeTopRow
                    }
                }
                "csv" {
                    if ($row.DatabaseSpecific) {
                        Write-Message -Level Output -Message "Exporting $csvdbfilename"
                        $result | Export-Csv -Path $csvdbfilename -NoTypeInformation -Append
                    }
                    else {
                        Write-Message -Level Output -Message "Exporting $csvfilename"
                        $result | Export-Csv -Path $csvfilename -NoTypeInformation -Append
                    }
                }
            }
        }
    }
}
#ValidationTags#Messaging#
function Export-DbaExecutionPlan {
    <#
        .SYNOPSIS
            Exports execution plans to disk.

        .DESCRIPTION
            Exports execution plans to disk. Can pipe from Export-DbaExecutionPlan

            Thanks to
                https://www.simple-talk.com/sql/t-sql-programming/dmvs-for-query-plan-metadata/
                and
                http://www.scarydba.com/2017/02/13/export-plans-cache-sqlplan-file/
            for the idea and query.

        .PARAMETER SqlInstance
            The SQL Server that you're connecting to.

        .PARAMETER SqlCredential
            Credential object used to connect to the SQL Server as a different user

        .PARAMETER Database
            The database(s) to process - this list is auto-populated from the server. If unspecified, all databases will be processed.

        .PARAMETER ExcludeDatabase
            The database(s) to exclude - this list is auto-populated from the server

        .PARAMETER SinceCreation
            Datetime object used to narrow the results to a date

        .PARAMETER SinceLastExecution
            Datetime object used to narrow the results to a date

        .PARAMETER Path
            The directory where all of the sqlxml files will be exported

        .PARAMETER WhatIf
            Shows what would happen if the command were to run. No actions are actually performed.

        .PARAMETER Confirm
            Prompts you for confirmation before executing any changing operations within the command.

        .PARAMETER PipedObject
            Internal parameter

        .PARAMETER EnableException
            By default, when something goes wrong we try to catch it, interpret it and give you a friendly warning message.
            This avoids overwhelming you with "sea of red" exceptions, but is inconvenient because it basically disables advanced scripting.
            Using this switch turns this "nice by default" feature off and enables you to catch exceptions with your own try/catch.

        .NOTES
            Tags: Performance, ExecutionPlan
            dbatools PowerShell module (https://dbatools.io, [email protected])
            Copyright (C) 2016 Chrissy LeMaire
            License: MIT https://opensource.org/licenses/MIT

        .LINK
            https://dbatools.io/Export-DbaExecutionPlan

        .EXAMPLE
            Export-DbaExecutionPlan -SqlInstance sqlserver2014a

            Exports all execution plans for sqlserver2014a.

        .EXAMPLE
            Export-DbaExecutionPlan -SqlInstance sqlserver2014a -Database db1, db2 -SinceLastExecution '7/1/2016 10:47:00'

            Exports all execution plans for databases db1 and db2 on sqlserver2014a since July 1, 2016 at 10:47 AM.
    #>
    [cmdletbinding(SupportsShouldProcess = $true, DefaultParameterSetName = "Default")]
    param (
        [parameter(ParameterSetName = 'NotPiped', Mandatory)]
        [Alias("ServerInstance", "SqlServer")]
        [DbaInstanceParameter[]]$SqlInstance,
        [parameter(ParameterSetName = 'NotPiped')]
        [PSCredential]$SqlCredential,
        [Alias("Databases")]
        [object[]]$Database,
        [object[]]$ExcludeDatabase,
        [parameter(ParameterSetName = 'Piped', Mandatory)]
        [parameter(ParameterSetName = 'NotPiped', Mandatory)]
        [string]$Path,
        [parameter(ParameterSetName = 'NotPiped')]
        [datetime]$SinceCreation,
        [parameter(ParameterSetName = 'NotPiped')]
        [datetime]$SinceLastExecution,
        [Parameter(ParameterSetName = 'Piped', Mandatory, ValueFromPipeline)]
        [object[]]$PipedObject,
        [Alias('Silent')]
        [switch]$EnableException
    )

    begin {
        if ($SinceCreation -ne $null) {
            $SinceCreation = $SinceCreation.ToString("yyyy-MM-dd HH:mm:ss")
        }

        if ($SinceLastExecution -ne $null) {
            $SinceLastExecution = $SinceLastExecution.ToString("yyyy-MM-dd HH:mm:ss")
        }

        function Export-Plan {
            param(
                [object]$object
            )
            $instanceName = $object.SqlInstance
            $dbName = $object.DatabaseName
            $queryPosition = $object.QueryPosition
            $sqlHandle = "0x"; $object.SqlHandle | ForEach-Object { $sqlHandle += ("{0:X}" -f $_).PadLeft(2, "0") }
            $sqlHandle = $sqlHandle.TrimStart('0x02000000').TrimEnd('0000000000000000000000000000000000000000')
            $shortName = "$instanceName-$dbName-$queryPosition-$sqlHandle"

            foreach ($queryPlan in $object.BatchQueryPlanRaw) {
                $fileName = "$path\$shortName-batch.sqlplan"

                try {
                    if ($Pscmdlet.ShouldProcess("localhost", "Writing XML file to $fileName")) {
                        $queryPlan.Save($fileName)
                    }
                }
                catch {
                    Stop-Function -Message "Skipped query plan for $fileName because it is null." -Target $fileName -ErrorRecord $_ -Continue
                }
            }

            foreach ($statementPlan in $object.SingleStatementPlanRaw) {
                $fileName = "$path\$shortName.sqlplan"

                try {
                    if ($Pscmdlet.ShouldProcess("localhost", "Writing XML file to $fileName")) {
                        $statementPlan.Save($fileName)
                    }
                }
                catch {
                    Stop-Function -Message "Skipped statement plan for $fileName because it is null." -Target $fileName -ErrorRecord $_ -Continue
                }
            }

            if ($Pscmdlet.ShouldProcess("console", "Showing output object")) {
                Add-Member -Force -InputObject $object -MemberType NoteProperty -Name OutputFile -Value $fileName
                Select-DefaultView -InputObject $object -Property ComputerName, InstanceName, SqlInstance, DatabaseName, SqlHandle, CreationTime, LastExecutionTime, OutputFile
            }
        }
    }

    process {
        if (!(Test-Path $Path)) {
            $null = New-Item -ItemType Directory -Path $Path
        }

        if ($PipedObject) {
            foreach ($object in $pipedobject) {
                Export-Plan $object
                return
            }
        }

        foreach ($instance in $SqlInstance) {
            Write-Message -Level Verbose -Message "Connecting to $instance"
            try {
                $server = Connect-SqlInstance -SqlInstance $instance -SqlCredential $SqlCredential -MinimumVersion 9
            }
            catch {
                Stop-Function -Message "Failure" -Category ConnectionError -ErrorRecord $_ -Target $instance -Continue
            }

            $select = "SELECT DB_NAME(deqp.dbid) as DatabaseName, OBJECT_NAME(deqp.objectid) as ObjectName,
                    detqp.query_plan AS SingleStatementPlan,
                    deqp.query_plan AS BatchQueryPlan,
                    ROW_NUMBER() OVER ( ORDER BY Statement_Start_offset ) AS QueryPosition,
                    sql_handle as SqlHandle,
                    plan_handle as PlanHandle,
                    creation_time as CreationTime,
                    last_execution_time as LastExecutionTime"

            $from = " FROM sys.dm_exec_query_stats deqs
                        CROSS APPLY sys.dm_exec_text_query_plan(deqs.plan_handle,
                            deqs.statement_start_offset,
                            deqs.statement_end_offset) AS detqp
                        CROSS APPLY sys.dm_exec_query_plan(deqs.plan_handle) AS deqp
                        CROSS APPLY sys.dm_exec_sql_text(deqs.plan_handle) AS execText"

            if ($ExcludeDatabase -or $Database -or $SinceCreation.Length -gt 0 -or $SinceLastExecution.length -gt 0 -or $ExcludeEmptyQueryPlan -eq $true) {
                $where = " WHERE "
            }

            $whereArray = @()

            if ($Database -gt 0) {
                $dbList = $Database -join "','"
                $whereArray += " DB_NAME(deqp.dbid) in ('$dbList') "
            }

            if (Test-Bound 'SinceCreation') {
                Write-Message -Level Verbose -Message "Adding creation time"
                $whereArray += " creation_time >= '$SinceCreation' "
            }

            if (Test-Bound 'SinceLastExecution') {
                Write-Message -Level Verbose -Message "Adding last execution time"
                $whereArray += " last_execution_time >= '$SinceLastExecution' "
            }

            if (Test-Bound 'ExcludeDatabase') {
                $dbList = $ExcludeDatabase -join "','"
                $whereArray += " DB_NAME(deqp.dbid) not in ('$dbList') "
            }

            if (Test-Bound 'ExcludeEmptyQueryPlan') {
                $whereArray += " detqp.query_plan is not null"
            }

            if ($where.Length -gt 0) {
                $whereArray = $whereArray -join " and "
                $where = "$where $whereArray"
            }

            $sql = "$select $from $where"
            Write-Message -Level Debug -Message "SQL Statement: $sql"
            try {
                $dataTable = $server.ConnectionContext.ExecuteWithResults($sql).Tables
            }
            catch {
                Stop-Function -Message "Issue collecting execution plans" -Target $instance -ErroRecord $_ -Continue
            }

            foreach ($row in ($dataTable.Rows)) {
                $sqlHandle = "0x"; $row.sqlhandle | ForEach-Object { $sqlHandle += ("{0:X}" -f $_).PadLeft(2, "0") }
                $planhandle = "0x"; $row.planhandle | ForEach-Object { $planhandle += ("{0:X}" -f $_).PadLeft(2, "0") }

                $object = [pscustomobject]@{
                    ComputerName           = $server.ComputerName
                    InstanceName           = $server.ServiceName
                    SqlInstance            = $server.DomainInstanceName
                    DatabaseName           = $row.DatabaseName
                    SqlHandle              = $sqlHandle
                    PlanHandle             = $planhandle
                    SingleStatementPlan    = $row.SingleStatementPlan
                    BatchQueryPlan         = $row.BatchQueryPlan
                    QueryPosition          = $row.QueryPosition
                    CreationTime           = $row.CreationTime
                    LastExecutionTime      = $row.LastExecutionTime
                    BatchQueryPlanRaw      = [xml]$row.BatchQueryPlan
                    SingleStatementPlanRaw = [xml]$row.SingleStatementPlan
                }
                Export-Plan $object
            }
        }
    }
}
function Export-DbaLogin {
    <#
        .SYNOPSIS
            Exports Windows and SQL Logins to a T-SQL file. Export includes login, SID, password, default database, default language, server permissions, server roles, db permissions, db roles.

        .DESCRIPTION
            Exports Windows and SQL Logins to a T-SQL file. Export includes login, SID, password, default database, default language, server permissions, server roles, db permissions, db roles.

        .PARAMETER SqlInstance
            The SQL Server instance name. SQL Server 2000 and above supported.

        .PARAMETER SqlCredential
            Login to the target instance using alternative credentials. Windows and SQL Authentication supported. Accepts credential objects (Get-Credential)

        .PARAMETER Login
            The login(s) to process. Options for this list are auto-populated from the server. If unspecified, all logins will be processed.

        .PARAMETER ExcludeLogin
            The login(s) to exclude. Options for this list are auto-populated from the server.

        .PARAMETER Database
            The database(s) to process. Options for this list are auto-populated from the server. If unspecified, all databases will be processed.

        .PARAMETER FilePath
            The file to write to.

        .PARAMETER NoClobber
            If this switch is enabled, a file already existing at the path specified by FilePath will not be overwritten.

        .PARAMETER Append
            If this switch is enabled, content will be appended to a file already existing at the path specified by FilePath. If the file does not exist, it will be created.

        .PARAMETER NoJobs
            If this switch is enabled, Agent job ownership will not be exported.

        .PARAMETER NoDatabases
            If this switch is enabled, mappings for databases will not be exported.

        .PARAMETER WhatIf
            If this switch is enabled, no actions are performed but informational messages will be displayed that explain what would happen if the command were to run.

        .PARAMETER Confirm
            If this switch is enabled, you will be prompted for confirmation before executing any operations that change state.

        .PARAMETER EnableException
            By default, when something goes wrong we try to catch it, interpret it and give you a friendly warning message.
            This avoids overwhelming you with "sea of red" exceptions, but is inconvenient because it basically disables advanced scripting.
            Using this switch turns this "nice by default" feature off and enables you to catch exceptions with your own try/catch.

        .PARAMETER ExcludeGoBatchSeparator
            If specified, will NOT script the 'GO' batch separator.

        .PARAMETER DestinationVersion
            To say to which version the script should be generated. If not specified will use instance major version.

        .NOTES
            Tags: Export, Login
            Author: Chrissy LeMaire (@cl), netnerds.net
            Website: https://dbatools.io
            Copyright: (C) Chrissy LeMaire, [email protected]
            License: MIT https://opensource.org/licenses/MIT

        .LINK
            https://dbatools.io/Export-DbaLogin

        .EXAMPLE
            Export-DbaLogin -SqlInstance sql2005 -FilePath C:\temp\sql2005-logins.sql

            Exports the logins for SQL Server "sql2005" and writes them to the file "C:\temp\sql2005-logins.sql"

        .EXAMPLE
            Export-DbaLogin -SqlInstance sqlserver2014a -Exclude realcajun -SqlCredential $scred -FilePath C:\temp\logins.sql -Append

            Authenticates to sqlserver2014a using SQL Authentication. Exports all logins except for realcajun to C:\temp\logins.sql, and appends to the file if it exists. If not, the file will be created.

        .EXAMPLE
            Export-DbaLogin -SqlInstance sqlserver2014a -Login realcajun, netnerds -FilePath C:\temp\logins.sql

            Exports ONLY logins netnerds and realcajun FROM sqlserver2014a to the file  C:\temp\logins.sql

        .EXAMPLE
            Export-DbaLogin -SqlInstance sqlserver2014a -Login realcajun, netnerds -Database HR, Accounting

            Exports ONLY logins netnerds and realcajun FROM sqlserver2014a with the permissions on databases HR and Accounting

        .EXAMPLE
            Export-DbaLogin -SqlInstance sqlserver2008 -Login realcajun, netnerds -FilePath C:\temp\login.sql -ExcludeGoBatchSeparator

            Exports ONLY logins netnerds and realcajun FROM sqlserver2008 server, to the C:\temp\login.sql file without the 'GO' batch separator.

        .EXAMPLE
            Export-DbaLogin -SqlInstance sqlserver2008 -Login realcajun -FilePath C:\temp\users.sql -DestinationVersion SQLServer2016

            Exports login realcajun fron sqlsever2008 to the file C:\temp\users.sql with sintax to run on SQL Server 2016
    #>
    [CmdletBinding(SupportsShouldProcess = $true)]
    param (
        [parameter(Mandatory = $true, ValueFromPipeline = $true)]
        [Alias("ServerInstance", "SqlServer")]
        [DbaInstanceParameter]$SqlInstance,
        [Alias("Credential")]
        [PSCredential]
        $SqlCredential,
        [object[]]$Login,
        [object[]]$ExcludeLogin,
        [Alias("Databases")]
        [object[]]$Database,
        [Alias("OutFile", "Path", "FileName")]
        [string]$FilePath,
        [Alias("NoOverwrite")]
        [switch]$NoClobber,
        [switch]$Append,
        [switch]$NoDatabases,
        [switch]$NoJobs,
        [Alias('Silent')]
        [switch]$EnableException,
        [switch]$ExcludeGoBatchSeparator,
        [ValidateSet('SQLServer2000', 'SQLServer2005', 'SQLServer2008/2008R2', 'SQLServer2012', 'SQLServer2014', 'SQLServer2016', 'SQLServer2017')]
        [string]$DestinationVersion
    )

    begin {

        if ($FilePath) {
            if ($FilePath -notlike "*\*") {
                $FilePath = ".\$filepath"
            }
            $directory = Split-Path $FilePath
            $exists = Test-Path $directory

            if ($exists -eq $false) {
                Write-Message -Level Warning -Message "Parent directory $directory does not exist."
            }
        }

        $outsql = @()

        $versions = @{
            'SQLServer2000'        = 'Version80'
            'SQLServer2005'        = 'Version90'
            'SQLServer2008/2008R2' = 'Version100'
            'SQLServer2012'        = 'Version110'
            'SQLServer2014'        = 'Version120'
            'SQLServer2016'        = 'Version130'
            'SQLServer2017'        = 'Version140'
        }

        $versionsNumbers = @{
            '8'  = 'Version80'
            '9'  = 'Version90'
            '10' = 'Version100'
            '11' = 'Version110'
            '12' = 'Version120'
            '13' = 'Version130'
            '14' = 'Version140'
        }
    }
    process {
        if (Test-FunctionInterrupt) {
            return
        }

        Write-Message -Level Verbose -Message "Connecting to $sqlinstance."
        $server = Connect-SqlInstance -SqlInstance $SqlInstance -SqlCredential $sqlcredential

        if ([string]::IsNullOrEmpty($destinationVersion)) {
            #Get compatibility level for scripting the objects
            $scriptVersion = $versionsNumbers[$server.VersionMajor.ToString()]
        }
        else {
            $scriptVersion = $versions[$destinationVersion]
        }

        if ($NoDatabases -eq $false -or $Database) {
            # if we got a database or a list of databases passed
            # and we need to enumerate mappings, login.enumdatabasemappings() takes forever
            # the cool thing though is that database.enumloginmappings() is fast. A lot.
            # if we get a list of databases passed (or even the default list of all the databases)
            # we save outself a call to enumloginmappings if there is no map at all
            $DbMapping = @()
            $DbsToMap = $server.Databases
            if ($Database) {
                $DbsToMap = $DbsToMap | Where-Object Name -in $Database
            }
            foreach ($db in $DbsToMap) {
                if ($db.IsAccessible -eq $false) {
                    continue
                }
                $dbmap = $db.EnumLoginMappings()
                foreach ($el in $dbmap) {
                    $DbMapping += [pscustomobject]@{
                        Database  = $db.Name
                        UserName  = $el.Username
                        LoginName = $el.LoginName
                    }
                }
            }
        }

        foreach ($sourceLogin in $server.Logins) {
            $userName = $sourceLogin.name

            if ($Login -and $Login -notcontains $userName -or $ExcludeLogin -contains $userName) {
                continue
            }

            if ($userName.StartsWith("##") -or $userName -eq 'sa') {
                Write-Message -Level Warning -Message "Skipping $userName."
                continue
            }

            $serverName = $server

            $userBase = ($userName.Split("\")[0]).ToLower()
            if ($serverName -eq $userBase -or $userName.StartsWith("NT ")) {
                if ($Pscmdlet.ShouldProcess("console", "Stating $userName is skipped because it is a local machine name.")) {
                    Write-Message -Level Warning -Message "$userName is skipped because it is a local machine name."
                    continue
                }
            }

            if ($Pscmdlet.ShouldProcess("Outfile", "Adding T-SQL for login $userName")) {
                if ($FilePath) {
                    Write-Message -Level Verbose -Message "Exporting $userName."
                }

                $outsql += "`r`nUSE master`n"
                # Getting some attributes
                $defaultDb = $sourceLogin.DefaultDatabase
                $language = $sourceLogin.Language

                if ($sourceLogin.PasswordPolicyEnforced -eq $false) {
                    $checkPolicy = "OFF"
                }
                else {
                    $checkPolicy = "ON"
                }

                if (!$sourceLogin.PasswordExpirationEnabled) {
                    $checkExpiration = "OFF"
                }
                else {
                    $checkExpiration = "ON"
                }

                # Attempt to script out SQL Login
                if ($sourceLogin.LoginType -eq "SqlLogin") {
                    $sourceLoginName = $sourceLogin.name

                    switch ($server.versionMajor) {
                        0 {
                            $sql = "SELECT CONVERT(VARBINARY(256),password) AS hashedpass FROM master.dbo.syslogins WHERE loginname='$sourceLoginName'"
                        }
                        8 {
                            $sql = "SELECT CONVERT(VARBINARY(256),password) AS hashedpass FROM dbo.syslogins WHERE name='$sourceLoginName'"
                        }
                        9 {
                            $sql = "SELECT CONVERT(VARBINARY(256),password_hash) as hashedpass FROM sys.sql_logins WHERE name='$sourceLoginName'"
                        }
                        default {
                            $sql = "SELECT CAST(CONVERT(varchar(256), CAST(LOGINPROPERTY(name,'PasswordHash') AS VARBINARY(256)), 1) AS NVARCHAR(max)) AS hashedpass FROM sys.server_principals WHERE principal_id = $($sourceLogin.id)"
                        }
                    }

                    try {
                        $hashedPass = $server.ConnectionContext.ExecuteScalar($sql)
                    }
                    catch {
                        $hashedPassDt = $server.Databases['master'].ExecuteWithResults($sql)
                        $hashedPass = $hashedPassDt.Tables[0].Rows[0].Item(0)
                    }

                    if ($hashedPass.GetType().Name -ne "String") {
                        $passString = "0x"; $hashedPass | ForEach-Object {
                            $passString += ("{0:X}" -f $_).PadLeft(2, "0")
                        }
                        $hashedPass = $passString
                    }

                    $sid = "0x"; $sourceLogin.sid | ForEach-Object {
                        $sid += ("{0:X}" -f $_).PadLeft(2, "0")
                    }
                    $outsql += "IF NOT EXISTS (SELECT loginname FROM master.dbo.syslogins WHERE name = '$userName') CREATE LOGIN [$userName] WITH PASSWORD = $hashedPass HASHED, SID = $sid, DEFAULT_DATABASE = [$defaultDb], CHECK_POLICY = $checkPolicy, CHECK_EXPIRATION = $checkExpiration, DEFAULT_LANGUAGE = [$language]"
                }
                # Attempt to script out Windows User
                elseif ($sourceLogin.LoginType -eq "WindowsUser" -or $sourceLogin.LoginType -eq "WindowsGroup") {
                    $outsql += "IF NOT EXISTS (SELECT loginname FROM master.dbo.syslogins WHERE name = '$userName') CREATE LOGIN [$userName] FROM WINDOWS WITH DEFAULT_DATABASE = [$defaultDb], DEFAULT_LANGUAGE = [$language]"
                }
                # This script does not currently support certificate mapped or asymmetric key users.
                else {
                    Write-Message -Level Warning -Message "$($sourceLogin.LoginType) logins not supported. $($sourceLogin.Name) skipped."
                    continue
                }

                if ($sourceLogin.IsDisabled) {
                    $outsql += "ALTER LOGIN [$userName] DISABLE"
                }

                if ($sourceLogin.DenyWindowsLogin) {
                    $outsql += "DENY CONNECT SQL TO [$userName]"
                }
            }

            # Server Roles: sysadmin, bulklogin, etc
            foreach ($role in $server.Roles) {
                $roleName = $role.Name

                # SMO changed over time
                try {
                    $roleMembers = $role.EnumMemberNames()
                }
                catch {
                    $roleMembers = $role.EnumServerRoleMembers()
                }

                if ($roleMembers -contains $userName) {
                    if (($server.VersionMajor -lt 11 -and [string]::IsNullOrEmpty($destinationVersion)) -or ($DestinationVersion -in "SQLServer2000", "SQLServer2005", "SQLServer2008/2008R2")) {
                        $outsql += "EXEC sys.sp_addsrvrolemember @rolename=N'$roleName', @loginame=N'$userName'"
                    }
                    else {
                        $outsql += "ALTER SERVER ROLE [$roleName] ADD MEMBER [$userName]"
                    }
                }
            }

            if ($NoJobs -eq $false) {
                $ownedJobs = $server.JobServer.Jobs | Where-Object { $_.OwnerLoginName -eq $userName }

                foreach ($ownedJob in $ownedJobs) {
                    $outsql += "`n`rUSE msdb`n"
                    $outsql += "EXEC msdb.dbo.sp_update_job @job_name=N'$ownedJob', @owner_login_name=N'$userName'"
                }
            }

            if ($server.VersionMajor -ge 9) {
                # These operations are only supported by SQL Server 2005 and above.
                # Securables: Connect SQL, View any database, Administer Bulk Operations, etc.

                $perms = $server.EnumServerPermissions($userName)
                $outsql += "`n`rUSE master`n"
                foreach ($perm in $perms) {
                    $permState = $perm.permissionstate
                    $permType = $perm.PermissionType
                    $grantor = $perm.grantor

                    if ($permState -eq "GrantWithGrant") {
                        $grantWithGrant = "WITH GRANT OPTION"
                        $permState = "GRANT"
                    }
                    else {
                        $grantWithGrant = $null
                    }

                    $outsql += "$permState $permType TO [$userName] $grantWithGrant AS [$grantor]"
                }

                # Credential mapping. Credential removal not currently supported for Syncs.
                $loginCredentials = $server.Credentials | Where-Object { $_.Identity -eq $sourceLogin.Name }
                foreach ($credential in $loginCredentials) {
                    $credentialName = $credential.Name
                    $outsql += "PRINT '$userName is associated with the $credentialName credential'"
                }
            }

            if ($NoDatabases -eq $false) {
                $dbs = $sourceLogin.EnumDatabaseMappings()

                if ($Database) {
                    $dbs = $dbs | Where-Object { $_.DBName -in $Database }
                }

                # Adding database mappings and securables
                foreach ($db in $dbs) {
                    $dbName = $db.dbname
                    $sourceDb = $server.Databases[$dbName]
                    $dbUserName = $db.username

                    $outsql += "`r`nUSE [$dbName]`n"
                    try {
                        $sql = $server.Databases[$dbName].Users[$dbUserName].Script()
                        $outsql += $sql
                    }
                    catch {
                        Write-Message -Level Warning -Message "User cannot be found in selected database."
                    }

                    # Skipping updating dbowner

                    # Database Roles: db_owner, db_datareader, etc
                    foreach ($role in $sourceDb.Roles) {
                        if ($role.EnumMembers() -contains $dbUserName) {
                            $roleName = $role.Name
                            if (($server.VersionMajor -lt 11 -and [string]::IsNullOrEmpty($destinationVersion)) -or ($DestinationVersion -in "SQLServer2000", "SQLServer2005", "SQLServer2008/2008R2")) {
                                $outsql += "EXEC sys.sp_addrolemember @rolename=N'$roleName', @membername=N'$dbUserName'"
                            }
                            else {
                                $outsql += "ALTER ROLE [$roleName] ADD MEMBER [$dbUserName]"
                            }
                        }
                    }

                    # Connect, Alter Any Assembly, etc
                    $perms = $sourceDb.EnumDatabasePermissions($dbUserName)
                    foreach ($perm in $perms) {
                        $permState = $perm.PermissionState
                        $permType = $perm.PermissionType
                        $grantor = $perm.Grantor

                        if ($permState -eq "GrantWithGrant") {
                            $grantWithGrant = "WITH GRANT OPTION"
                            $permState = "GRANT"
                        }
                        else {
                            $grantWithGrant = $null
                        }

                        $outsql += "$permState $permType TO [$userName] $grantWithGrant AS [$grantor]"
                    }
                }
            }
        }
    }
    end {
        $sql = $sql | Where-Object { $_ -notlike "CREATE USER [dbo] FOR LOGIN * WITH DEFAULT_SCHEMA=[dbo]" }

        if ($ExcludeGoBatchSeparator) {
            $sql = $outsql
        }
        else {
            $sql = $outsql -join "`r`nGO`r`n"
            #add the final GO
            $sql += "`r`nGO"
        }

        if ($FilePath) {
            $sql | Out-File -Encoding UTF8 -FilePath $FilePath -Append:$Append -NoClobber:$NoClobber
            Get-ChildItem $FilePath
        }
        else {
            $sql
        }
        Test-DbaDeprecation -DeprecatedOn "1.0.0" -EnableException:$false -Alias Export-SqlLogin
    }
}
function Export-DbaPfDataCollectorSetTemplate {
    <#
        .SYNOPSIS
            Exports a new Data Collector Set XML Template.

        .DESCRIPTION
            Exports a Data Collector Set XML Template from Get-DbaPfDataCollectorSet. Exports to "$home\Documents\Performance Monitor Templates" by default.

        .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 name of the collector set(s) to export.

        .PARAMETER Path
            The path to export the file. Can be .xml or directory.

        .PARAMETER InputObject
            Accepts the object output by Get-DbaPfDataCollectorSetTemplate via the pipeline.

        .PARAMETER EnableException
            By default, when something goes wrong we try to catch it, interpret it and give you a friendly warning message.
            This avoids overwhelming you with "sea of red" exceptions, but is inconvenient because it basically disables advanced scripting.
            Using this switch turns this "nice by default" feature off and enables you to catch exceptions with your own try/catch.

        .NOTES
            Tags: Performance, DataCollector
            Website: https://dbatools.io
            Copyright: (C) Chrissy LeMaire, [email protected]
            License: MIT https://opensource.org/licenses/MIT

        .LINK
            https://dbatools.io/Export-DbaPfDataCollectorSetTemplate

        .EXAMPLE
            Export-DbaPfDataCollectorSetTemplate -ComputerName sql2017 -Path C:\temp\pf

            Exports all data collector sets from to the C:\temp\pf folder.

        .EXAMPLE
            Get-DbaPfDataCollectorSet ComputerName sql2017 -CollectorSet 'System Correlation' | Export-DbaPfDataCollectorSetTemplate -Path C:\temp

            Exports the 'System Correlation' data collector set from sql2017 to C:\temp.
    #>
    [CmdletBinding()]
    param (
        [DbaInstance[]]$ComputerName = $env:COMPUTERNAME,
        [PSCredential]$Credential,
        [Alias("DataCollectorSet")]
        [string[]]$CollectorSet,
        [string]$Path = "$home\Documents\Performance Monitor Templates",
        [Parameter(ValueFromPipeline)]
        [object[]]$InputObject,
        [switch]$EnableException
    )
    process {
        if ($InputObject.Credential -and (Test-Bound -ParameterName Credential -Not)) {
            $Credential = $InputObject.Credential
        }

        if (-not $InputObject -or ($InputObject -and (Test-Bound -ParameterName ComputerName))) {
            foreach ($computer in $ComputerName) {
                $InputObject += Get-DbaPfDataCollectorSet -ComputerName $computer -Credential $Credential -CollectorSet $CollectorSet
            }
        }

        foreach ($object in $InputObject) {
            if (-not $object.DataCollectorSetObject) {
                Stop-Function -Message "InputObject is not of the right type. Please use Get-DbaPfDataCollectorSet."
                return
            }

            $csname = Remove-InvalidFileNameChars -Name $object.Name

            if ($path.EndsWith(".xml")) {
                $filename = $path
            }
            else {
                $filename = "$path\$csname.xml"
                if (-not (Test-Path -Path $path)) {
                    $null = New-Item -Type Directory -Path $path
                }
            }
            Write-Message -Level Verbose -Message "Wrote $csname to $filename."
            Set-Content -Path $filename -Value $object.Xml -Encoding Unicode
            Get-ChildItem -Path $filename
        }
    }
}
#ValidationTags#Messaging,FlowControl,Pipeline,CodeStyle#
function Export-DbaRegisteredServer {
    <#
        .SYNOPSIS
            Exports registered servers and registered server groups to file

        .DESCRIPTION
            Exports registered servers and registered server groups to file

        .PARAMETER SqlInstance
            SQL Server name or SMO object representing the SQL Server to connect to.

        .PARAMETER SqlCredential
            Login to the target instance using alternative credentials. Windows and SQL Authentication supported. Accepts credential objects (Get-Credential)

        .PARAMETER Group
            Exports a specific group.

        .PARAMETER CredentialPersistenceType
            Used to specify how the login and passwords are persisted. Valid values include None, PersistLoginName and PersistLoginNameAndPassword.

        .PARAMETER Path
            The path to the exported file. If no path is specified, one will be created.

        .PARAMETER InputObject
            Enables piping from Get-DbaRegisteredServer, Get-DbaRegisteredServerGroup, CSVs and other objects.

            If importing from CSV or other object, a column named ServerName is required. Optional columns include Name, Description and Group.

        .PARAMETER EnableException
            By default, when something goes wrong we try to catch it, interpret it and give you a friendly warning message.

            This avoids overwhelming you with "sea of red" exceptions, but is inconvenient because it basically disables advanced scripting.

            Using this switch turns this "nice by default" feature off and enables you to catch exceptions with your own try/catch.

        .NOTES
            Author: Chrissy LeMaire (@cl)
            Tags: RegisteredServer, CMS

            Website: https://dbatools.io
            Copyright: (C) Chrissy LeMaire, [email protected]
            License: MIT https://opensource.org/licenses/MIT

        .LINK
            https://dbatools.io/Export-DbaRegisteredServer

        .EXAMPLE
           Export-DbaRegisteredServer -SqlInstance sql2008

           Exports all Registered Server and Registered Server Groups on sql2008 to an automatically generated file name in the current directory

        .EXAMPLE
           Export-DbaRegisteredServer -SqlInstance sql2008 -Group hr\Seattle -Path C:\temp\Seattle.xml

           Exports all Registered Server and Registered Server Groups with the Seattle group within the HR group on sql2008 to C:\temp\Seattle.xml

        .EXAMPLE
           Get-DbaRegisteredServer -SqlInstance sql2008, sql2012 | Export-DbaRegisteredServer

           Exports all registered servers on sql2008 and sql2012. Warning - each one will have its own individual file. Consider piping groups.

        .EXAMPLE
           Get-DbaRegisteredServerGroup -SqlInstance sql2008, sql2012 | Export-DbaRegisteredServer

           Exports all registered servers on sql2008 and sql2012, organized by group.
    #>
    [CmdletBinding()]
    param (
        [Alias("ServerInstance", "SqlServer")]
        [DbaInstanceParameter[]]$SqlInstance,
        [PSCredential]$SqlCredential,
        [parameter(ValueFromPipeline)]
        [object[]]$InputObject,
        [string]$Path,
        [ValidateSet("None", "PersistLoginName", "PersistLoginNameAndPassword")]
        [string]$CredentialPersistenceType = "None",
        [switch]$EnableException
    )
    begin {
        if ((Test-Bound -ParameterName Path)) {
            if ($Path -notmatch '\\') {
                $Path = ".\$Path"
            }

            $directory = Split-Path $Path
            if (-not (Test-Path $directory)) {
                New-Item -Path $directory -ItemType Directory
            }
        }
        else {
            $timeNow = (Get-Date -uformat "%m%d%Y%H%M%S")
        }
    }
    process {
        foreach ($instance in $SqlInstance) {
            $InputObject += Get-DbaRegisteredServerGroup -SqlInstance $instance -SqlCredential $SqlCredential -Id 1
        }

        foreach ($object in $InputObject) {
            try {
                if ($object -is [Microsoft.SqlServer.Management.RegisteredServers.RegisteredServersStore]) {
                    $object = Get-DbaRegisteredServerGroup -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 $_
            }
        }
    }
}
function Export-DbaScript {
    <#
        .SYNOPSIS
            Exports scripts from SQL Management Objects (SMO)

        .DESCRIPTION
            Exports scripts from SQL Management Objects

        .PARAMETER InputObject
            A SQL Managment Object such as the one returned from Get-DbaLogin

        .PARAMETER Path
            The output filename and location. If no path is specified, one will be created. If the file already exists, the output will be appended.

        .PARAMETER Encoding
            Specifies the file encoding. The default is UTF8.

            Valid values are:
            -- ASCII: Uses the encoding for the ASCII (7-bit) character set.
            -- BigEndianUnicode: Encodes in UTF-16 format using the big-endian byte order.
            -- Byte: Encodes a set of characters into a sequence of bytes.
            -- String: Uses the encoding type for a string.
            -- Unicode: Encodes in UTF-16 format using the little-endian byte order.
            -- UTF7: Encodes in UTF-7 format.
            -- UTF8: Encodes in UTF-8 format.
            -- Unknown: The encoding type is unknown or invalid. The data can be treated as binary.

        .PARAMETER Passthru
            Output script to console

        .PARAMETER ScriptingOptionsObject
            An SMO Scripting Object that can be used to customize the output - see New-DbaScriptingOption

        .PARAMETER BatchSeparator
            Specifies the Batch Separator to use. Default is None

        .PARAMETER NoPrefix
            Do not include a Prefix

        .PARAMETER WhatIf
            Shows what would happen if the command were to run. No actions are actually performed

        .PARAMETER NoClobber
            Do not overwrite file

        .PARAMETER Append
            Append to file

        .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, Backup, Export

            Website: https://dbatools.io
            Copyright: (C) Chrissy LeMaire, [email protected]
            License: MIT https://opensource.org/licenses/MIT

        .LINK
            https://dbatools.io/Export-DbaScript

        .EXAMPLE
            Get-DbaAgentJob -SqlInstance sql2016 | Export-DbaScript

            Exports all jobs on the SQL Server sql2016 instance using a trusted connection - automatically determines filename as .\sql2016-Job-Export-date.sql

        .EXAMPLE
            Get-DbaAgentJob -SqlInstance sql2016 | Export-DbaScript -Path C:\temp\export.sql -Append

            Exports all jobs on the SQL Server sql2016 instance using a trusted connection - Will append the output to the file C:\temp\export.sql if it already exists

        .EXAMPLE
            Get-DbaAgentJob -SqlInstance sql2016 -Job syspolicy_purge_history, 'Hourly Log Backups' -SqlCredential (Get-Credential sqladmin) | Export-DbaScript -Path C:\temp\export.sql

            Exports only syspolicy_purge_history and 'Hourly Log Backups' to C:temp\export.sql and uses the SQL login "sqladmin" to login to sql2016

        .EXAMPLE
            Get-DbaAgentJob -SqlInstance sql2016 -Job syspolicy_purge_history, 'Hourly Log Backups' -SqlCredential (Get-Credential sqladmin) | Export-DbaScript -Path C:\temp\export.sql -NoPrefix

            Exports only syspolicy_purge_history and 'Hourly Log Backups' to C:temp\export.sql and uses the SQL login "sqladmin" to login to sql2016
            Suppress the output of a Prefix

        .EXAMPLE
            Get-DbaAgentJob -SqlInstance sql2016 -Job syspolicy_purge_history, 'Hourly Log Backups' -SqlCredential (Get-Credential sqladmin) | Export-DbaScript -Path C:\temp\export.sql -BatchSeparator "GO"

            Exports only syspolicy_purge_history and 'Hourly Log Backups' to C:temp\export.sql and uses the SQL login "sqladmin" to login to sql2016
            Appends a batch separator at end of each script.

        .EXAMPLE
            Get-DbaAgentJob -SqlInstance sql2014 | Export-DbaScript -Passthru | ForEach-Object { $_.Replace('sql2014','sql2016') } | Set-Content -Path C:\temp\export.sql

            Exports jobs and replaces all instances of the servername "sql2014" with "sql2016" then writes to C:\temp\export.sql

        .EXAMPLE
            $options = New-DbaScriptingOption
            $options.ScriptDrops = $false
            $options.WithDependencies = $true
            Get-DbaTable -SqlInstance sql2017 -Database PerformanceStore | Export-DbaScript -ScriptingOptionsObject $options

            Exports Agent Jobs with the Scripting Options ScriptDrops set to $false and WithDependencies set to $true.
    #>
    [CmdletBinding(SupportsShouldProcess = $true)]
    param (
        [parameter(Mandatory = $true, ValueFromPipeline = $true)]
        [object[]]$InputObject,
        [Alias("ScriptingOptionObject")]
        [Microsoft.SqlServer.Management.Smo.ScriptingOptions]$ScriptingOptionsObject,
        [string]$Path,
        [ValidateSet('ASCII', 'BigEndianUnicode', 'Byte', 'String', 'Unicode', 'UTF7', 'UTF8', 'Unknown')]
        [string]$Encoding = 'UTF8',
        [string]$BatchSeparator = '',
        [switch]$NoPrefix,
        [switch]$Passthru,
        [switch]$NoClobber,
        [switch]$Append,
        [Alias('Silent')]
        [switch]$EnableException
    )

    begin {
        $executingUser = [Security.Principal.WindowsIdentity]::GetCurrent().Name
        $commandName = $MyInvocation.MyCommand.Name
        $timeNow = (Get-Date -uformat "%m%d%Y%H%M%S")
        $prefixArray = @()
    }

    process {
        foreach ($object in $InputObject) {

            $typename = $object.GetType().ToString()

            if ($typename.StartsWith('Microsoft.SqlServer.')) {
                $shortype = $typename.Split(".")[-1]
            }
            else {
                Stop-Function -Message "InputObject is of type $typename which is not a SQL Management Object. Only SMO objects are supported." -Category InvalidData -Target $object -Continue
            }

            if ($shortype -in "LinkedServer", "Credential", "Login") {
                Write-Message -Level Warning -Message "Support for $shortype is limited at this time. No passwords, hashed or otherwise, will be exported if they exist."
            }

            # Just gotta add the stuff that Nic Cain added to his script

            if ($shortype -eq "Configuration") {
                Write-Message -Level Warning -Message "Support for $shortype is limited at this time."
            }

            # Find the server object to pass on to the function
            $parent = $object.parent

            do {
                if ($parent.Urn.Type -ne "Server") {
                    $parent = $parent.Parent
                }
            }
            until (($parent.Urn.Type -eq "Server") -or (-not $parent))

            if (-not $parent) {
                Stop-Function -Message "Failed to find valid SMO server object in input: $object." -Category InvalidData -Target $object -Continue
            }

            try {
                $server = $parent
                $serverName = $server.Name.Replace('\', '$')

                if ($ScriptingOptionsObject) {
                    $scripter = New-Object Microsoft.SqlServer.Management.Smo.Scripter $server
                    $scripter.Options = $ScriptingOptionsObject
                }

                if (!$passthru) {
                    if ($path) {
                        $actualPath = $path
                    }
                    else {
                        $actualPath = "$serverName-$shortype-Export-$timeNow.sql"
                    }
                }

                if ($NoPrefix) {
                    $prefix = ""
                }
                else {
                    $prefix = "/*`n`tCreated by $executingUser using dbatools $commandName for objects on $serverName at $(Get-Date)`n`tSee https://dbatools.io/$commandName for more information`n*/"
                }

                if ($passthru) {
                    $prefix | Out-String
                }
                else {
                    if ($prefixArray -notcontains $actualPath) {

                        if ((Test-Path -Path $actualPath) -and $NoClobber) {
                            Stop-Function -Message "File already exists. If you want to overwrite it remove the -NoClobber parameter. If you want to append data, please Use -Append parameter." -Target $actualPath -Continue
                        }
                        #Only at the first output we use the passed variables Append & NoClobber. For this execution the next ones need to buse -Append
                        $prefix | Out-File -FilePath $actualPath -Encoding $encoding -Append:$Append -NoClobber:$NoClobber
                        $prefixArray += $actualPath
                    }
                }

                if ($Pscmdlet.ShouldProcess($env:computername, "Exporting $object from $server to $actualPath")) {
                    Write-Message -Level Verbose -Message "Exporting $object"

                    if ($passthru) {
                        if ($ScriptingOptionsObject) {
                            foreach ($script in $scripter.EnumScript($object)) {
                                if ($BatchSeparator -ne "") {
                                    $script = "$script`n$BatchSeparator`n"
                                }
                                $script | Out-String
                            }
                        }
                        else {
                            $script = $object.Script()
                            if ($BatchSeparator -ne "") {
                                $script = "$script`n$BatchSeparator`n"
                            }
                            $script  | Out-String
                        }
                    }
                    else {
                        if ($ScriptingOptionsObject) {
                            if ($ScriptingOptionsObject.ScriptBatchTerminator) {
                                $ScriptingOptionsObject.AppendToFile = $true
                                $ScriptingOptionsObject.ToFileOnly = $true
                                $ScriptingOptionsObject.FileName = $actualPath
                                $object.Script($ScriptingOptionsObject)
                            }
                            else {
                                foreach ($script in $scripter.EnumScript($object)) {
                                    if ($BatchSeparator -ne "") {
                                        $script = "$script`n$BatchSeparator`n"
                                    }
                                    $script | Out-File -FilePath $actualPath -Encoding $encoding -Append
                                }
                            }

                        }
                        else {
                            $script = $object.Script()
                            if ($BatchSeparator -ne "") {
                                $script = "$script`n$BatchSeparator`n"
                            }
                            $script | Out-File -FilePath $actualPath -Encoding $encoding -Append
                        }
                    }

                    if (-not $passthru) {
                        Write-Message -Level Output -Message "Exported $object on $($server.Name) to $actualPath"
                    }
                }
            }
            catch {
                $message = $_.Exception.InnerException.InnerException.InnerException.Message
                if (-not $message) {
                    $message = $_.Exception
                }
                Stop-Function -Message "Failure on $($server.Name) | $message" -Target $server
            }
        }
    }
}
function Export-DbaSpConfigure {
    <#
        .SYNOPSIS
            Exports advanced sp_configure global configuration options to sql file.

        .DESCRIPTION
            Exports advanced sp_configure global configuration options to sql file.

        .PARAMETER SqlInstance
            Source SQL Server. You must have sysadmin access and server version must be SQL Server version 2005 or higher.

        .PARAMETER SqlCredential
            Login to the target instance using alternative credentials. Windows and SQL Authentication supported. Accepts credential objects (Get-Credential)

        .PARAMETER Path
            Specifies the path to a file which will contain the sp_configure queries necessary to replicate the configuration settings on another instance. This file is suitable for input into Import-DbaSPConfigure.

        .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: SpConfig, Configure, Configuration
            Website: https://dbatools.io
            Copyright: (C) Chrissy LeMaire, [email protected]
            License: MIT https://opensource.org/licenses/MIT

        .EXAMPLE
            Export-DbaSpConfigure -SqlInstance sourceserver -Path C:\temp\sp_configure.sql

            Exports the SPConfigure settings on sourceserver to the file C:\temp\sp_configure.sql

        .OUTPUTS
            File to disk, and string path.

    #>
    [CmdletBinding(DefaultParameterSetName = "Default", SupportsShouldProcess = $true)]
    param (
        [Parameter(Mandatory = $true)]
        [ValidateNotNullOrEmpty()]
        [Alias("ServerInstance", "SqlServer")]
        [DbaInstanceParameter]$SqlInstance,
        [string]$Path,
        [PSCredential]$SqlCredential
    )

    begin {
        $server = Connect-SqlInstance $sqlinstance $SqlCredential

        if ($server.versionMajor -lt 9) {
            Write-Error "Windows 2000 is not supported for sp_configure export."
            break
        }

        if ($path.length -eq 0) {
            $timenow = (Get-Date -uformat "%m%d%Y%H%M%S")
            $mydocs = [Environment]::GetFolderPath('MyDocuments')
            $path = "$mydocs\$($server.name.replace('\', '$'))-$timenow-sp_configure.sql"
        }

    }

    process {
        try {
            Set-Content -Path $path "EXEC sp_configure 'show advanced options' , 1;  RECONFIGURE WITH OVERRIDE"
        }
        catch {
            throw "Can't write to $path"
        }

        $server.Configuration.ShowAdvancedOptions.ConfigValue = $true
        $server.Configuration.Alter($true)
        foreach ($sourceprop in $server.Configuration.Properties) {
            $displayname = $sourceprop.DisplayName
            $configvalue = $sourceprop.ConfigValue
            Add-Content -Path $path "EXEC sp_configure '$displayname' , $configvalue;"
        }
        Add-Content -Path $path "EXEC sp_configure 'show advanced options' , 0;"
        Add-Content -Path $Path "RECONFIGURE WITH OVERRIDE"
        $server.Configuration.ShowAdvancedOptions.ConfigValue = $false
        $server.Configuration.Alter($true)
        return $path
    }

    end {
        $server.ConnectionContext.Disconnect()

        If ($Pscmdlet.ShouldProcess("console", "Showing finished message")) {
            Write-Output "Server configuration export finished"
        }
        Test-DbaDeprecation -DeprecatedOn "1.0.0" -EnableException:$false -Alias Export-SqlSpConfigure
    }
}
function Export-DbaUser {
    <#
        .SYNOPSIS
            Exports users creation and its permissions to a T-SQL file or host.

        .DESCRIPTION
            Exports users creation and its permissions to a T-SQL file or host. Export includes user, create and add to role(s), database level permissions, object level permissions.

        .PARAMETER SqlInstance
            The SQL Server instance name. SQL Server 2000 and above supported.

        .PARAMETER SqlCredential
            Allows you to login to servers using alternative credentials

            $scred = Get-Credential, then pass $scred object to the -SqlCredential parameter

            Windows Authentication will be used if SqlCredential is not specified

        .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 User
            Export only the specified database user(s). If not specified will export all users from the database(s)

        .PARAMETER DestinationVersion
            To say to which version the script should be generated. If not specified will use database compatibility level

        .PARAMETER FilePath
            The file to write to.

        .PARAMETER NoClobber
            Do not overwrite file

        .PARAMETER Append
            Append to file

        .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.

        .PARAMETER ScriptingOptionsObject
            A Microsoft.SqlServer.Management.Smo.ScriptingOptions object with the options that you want to use to generate the t-sql script.
            You can use the NEw-DbaScriptingOption to generate it.

        .PARAMETER ExcludeGoBatchSeparator
            If specified, will NOT script the 'GO' batch separator.

        .NOTES
            Tags: User, Export
            Author: Claudio Silva (@ClaudioESSilva)

            Website: https://dbatools.io
            Copyright: (C) Chrissy LeMaire, [email protected]
            License: MIT https://opensource.org/licenses/MIT

        .LINK
            https://dbatools.io/Export-DbaUser

        .EXAMPLE
            Export-DbaUser -SqlInstance sql2005 -FilePath C:\temp\sql2005-users.sql

            Exports SQL for the users in server "sql2005" and writes them to the file "C:\temp\sql2005-users.sql"

        .EXAMPLE
            Export-DbaUser -SqlInstance sqlserver2014a $scred -FilePath C:\temp\users.sql -Append

            Authenticates to sqlserver2014a using SQL Authentication. Exports all users to C:\temp\users.sql, and appends to the file if it exists. If not, the file will be created.

        .EXAMPLE
            Export-DbaUser -SqlInstance sqlserver2014a -User User1, User2 -FilePath C:\temp\users.sql

            Exports ONLY users User1 and User2 fron sqlsever2014a to the file  C:\temp\users.sql

        .EXAMPLE
            Export-DbaUser -SqlInstance sqlserver2008 -User User1 -FilePath C:\temp\users.sql -DestinationVersion SQLServer2016

            Exports user User1 fron sqlsever2008 to the file C:\temp\users.sql with sintax to run on SQL Server 2016

        .EXAMPLE
            Export-DbaUser -SqlInstance sqlserver2008 -Database db1,db2 -FilePath C:\temp\users.sql

            Exports ONLY users from db1 and db2 database on sqlserver2008 server, to the C:\temp\users.sql file.

        .EXAMPLE
            $options = New-DbaScriptingOption
            $options.ScriptDrops = $false
            $options.WithDependencies = $true

            Export-DbaUser -SqlInstance sqlserver2008 -Database db1,db2 -FilePath C:\temp\users.sql -ScriptingOptionsObject $options

            Exports ONLY users from db1 and db2 database on sqlserver2008 server, to the C:\temp\users.sql file.
            It will not script drops but will script dependencies.

        .EXAMPLE
            Export-DbaUser -SqlInstance sqlserver2008 -Database db1,db2 -FilePath C:\temp\users.sql -ExcludeGoBatchSeparator

            Exports ONLY users from db1 and db2 database on sqlserver2008 server, to the C:\temp\users.sql file without the 'GO' batch separator.

    #>
    [CmdletBinding(DefaultParameterSetName = "Default")]
    [OutputType([String])]
    param (
        [parameter(Mandatory = $true, ValueFromPipeline = $true)]
        [Alias("ServerInstance", "SqlServer")]
        [DbaInstanceParameter]$SqlInstance,
        [Alias("Credential")]
        [PSCredential]
        $SqlCredential,
        [Alias("Databases")]
        [object[]]$Database,
        [object[]]$ExcludeDatabase,
        [object[]]$User,
        [ValidateSet('SQLServer2000', 'SQLServer2005', 'SQLServer2008/2008R2', 'SQLServer2012', 'SQLServer2014', 'SQLServer2016', 'SQLServer2017')]
        [string]$DestinationVersion,
        [Alias("OutFile", "Path", "FileName")]
        [string]$FilePath,
        [Alias("NoOverwrite")]
        [switch]$NoClobber,
        [switch]$Append,
        [Alias('Silent')]
        [switch]$EnableException,
        [Microsoft.SqlServer.Management.Smo.ScriptingOptions]$ScriptingOptionsObject = $null,
        [switch]$ExcludeGoBatchSeparator
    )

    begin {
        if ($FilePath) {
            if ($FilePath -notlike "*\*") { $FilePath = ".\$filepath" }
            $directory = Split-Path $FilePath
            $exists = Test-Path $directory

            if ($exists -eq $false) {
                Stop-Function -Message "Parent directory $directory does not exist"
                return
            }
        }

        $outsql = @()

        $versions = @{
            'SQLServer2000'        = 'Version80'
            'SQLServer2005'        = 'Version90'
            'SQLServer2008/2008R2' = 'Version100'
            'SQLServer2012'        = 'Version110'
            'SQLServer2014'        = 'Version120'
            'SQLServer2016'        = 'Version130'
            'SQLServer2017'        = 'Version140'
        }

        $versionName = @{
            'Version80'  = 'SQLServer2000'
            'Version90'  = 'SQLServer2005'
            'Version100' = 'SQLServer2008/2008R2'
            'Version110' = 'SQLServer2012'
            'Version120' = 'SQLServer2014'
            'Version130' = 'SQLServer2016'
            'Version140' = 'SQLServer2017'
        }

    }
    process {
        if (Test-FunctionInterrupt) { return }

        try {
            Write-Message -Level Verbose -Message "Connecting to $sqlinstance"
            $server = Connect-SqlInstance -SqlInstance $SqlInstance -SqlCredential $SqlCredential
        }
        catch {
            Stop-Function -Message "Failure" -Category ConnectionError -ErrorRecord $_ -Target $instance -Continue
        }

        if (!$database) {
            $databases = $server.Databases | Where-Object { $ExcludeDatabase -notcontains $_.Name -and $_.IsAccessible -eq $true }
        }
        else {
            if ($pipedatabase) {
                $source = $pipedatabase[0].parent.name
                $databases = $pipedatabase.name
            }
            else {
                $databases = $server.Databases | Where-Object { $_.IsAccessible -eq $true -and ($database -contains $_.Name) }
            }
        }

        if ($exclude) {
            $databases = $databases | Where-Object Name -notin $ExcludeDatabase
        }

        if (@($databases).Count -gt 0) {

            #Database Permissions
            foreach ($db in $databases) {
                if ([string]::IsNullOrEmpty($destinationVersion)) {
                    #Get compatibility level for scripting the objects
                    $scriptVersion = $db.CompatibilityLevel
                }
                else {
                    $scriptVersion = $versions[$destinationVersion]
                }
                $versionNameDesc = $versionName[$scriptVersion.ToString()]

                #If not passed create new ScriptingOption. Otherwise use the one that was passed
                if ($null -eq $ScriptingOptionsObject) {
                    $ScriptingOptionsObject = New-DbaScriptingOption
                    $ScriptingOptionsObject.TargetServerVersion = [Microsoft.SqlServer.Management.Smo.SqlServerVersion]::$scriptVersion
                    $ScriptingOptionsObject.AllowSystemObjects = $false
                    $ScriptingOptionsObject.IncludeDatabaseRoleMemberships = $true
                    $ScriptingOptionsObject.ContinueScriptingOnError = $false
                    $ScriptingOptionsObject.IncludeDatabaseContext = $false
                    $ScriptingOptionsObject.IncludeIfNotExists = $true
                }

                Write-Message -Level Output -Message "Validating users on database $db"

                if ($User.Count -eq 0) {
                    $users = $db.Users | Where-Object { $_.IsSystemObject -eq $false -and $_.Name -notlike "##*" }
                }
                else {
                    if ($pipedatabase) {
                        $source = $pipedatabase[3].parent.name
                        $users = $pipedatabase.name
                    }
                    else {
                        $users = $db.Users | Where-Object { $User -contains $_.Name -and $_.IsSystemObject -eq $false -and $_.Name -notlike "##*" }
                    }
                }
                # Store roles between users so if we hit the same one we dont create it again
                $roles = @()
                if ($users.Count -gt 0) {
                    foreach ($dbuser in $users) {
                        Write-Message -Level Output -Message "Generating script for user $dbuser"

                        #setting database
                        $outsql += "USE [" + $db.Name + "]"

                        try {
                            #Fixed Roles #Dependency Issue. Create Role, before add to role.
                            foreach ($rolePermission in ($db.Roles | Where-Object { $_.IsFixedRole -eq $false })) {
                                foreach ($rolePermissionScript in $rolePermission.Script($ScriptingOptionsObject)) {
                                    if ($rolePermission.ToString() -notin $roles) {
                                        $roles += , $rolePermission.ToString()
                                        $outsql += "$($rolePermissionScript.ToString())"
                                    }

                                }
                            }

                            #Database Create User(s) and add to Role(s)
                            foreach ($dbUserPermissionScript in $dbuser.Script($ScriptingOptionsObject)) {
                                if ($dbuserPermissionScript.Contains("sp_addrolemember")) {
                                    $execute = "EXEC "
                                }
                                else {
                                    $execute = ""
                                }
                                $outsql += "$execute$($dbUserPermissionScript.ToString())"
                            }

                            #Database Permissions
                            foreach ($databasePermission in $db.EnumDatabasePermissions() | Where-Object { @("sa", "dbo", "information_schema", "sys") -notcontains $_.Grantee -and $_.Grantee -notlike "##*" -and ($dbuser.Name -contains $_.Grantee) }) {
                                if ($databasePermission.PermissionState -eq "GrantWithGrant") {
                                    $withGrant = " WITH GRANT OPTION"
                                    $grantDatabasePermission = 'GRANT'
                                }
                                else {
                                    $withGrant = " "
                                    $grantDatabasePermission = $databasePermission.PermissionState.ToString().ToUpper()
                                }

                                $outsql += "$($grantDatabasePermission) $($databasePermission.PermissionType) TO [$($databasePermission.Grantee)]$withGrant AS [$($databasePermission.Grantor)];"
                            }

                            #Database Object Permissions
                            # NB: This is a bit of a mess for a couple of reasons
                            # 1. $db.EnumObjectPermissions() doesn't enumerate all object types
                            # 2. Some (x)Collection types can have EnumObjectPermissions() called
                            #    on them directly (e.g. AssemblyCollection); others can't (e.g.
                            #    ApplicationRoleCollection). Those that can't we iterate the
                            #    collection explicitly and add each object's permission.

                            $perms = New-Object System.Collections.ArrayList

                            $null = $perms.AddRange($db.EnumObjectPermissions($dbuser.Name))

                            foreach ($item in $db.ApplicationRoles) {
                                $null = $perms.AddRange($item.EnumObjectPermissions($dbuser.Name))
                            }

                            foreach ($item in $db.Assemblies) {
                                $null = $perms.AddRange($item.EnumObjectPermissions($dbuser.Name))
                            }

                            foreach ($item in $db.Certificates) {
                                $null = $perms.AddRange($item.EnumObjectPermissions($dbuser.Name))
                            }

                            foreach ($item in $db.DatabaseRoles) {
                                $null = $perms.AddRange($item.EnumObjectPermissions($dbuser.Name))
                            }

                            foreach ($item in $db.FullTextCatalogs) {
                                $null = $perms.AddRange($item.EnumObjectPermissions($dbuser.Name))
                            }

                            foreach ($item in $db.FullTextStopLists) {
                                $null = $perms.AddRange($item.EnumObjectPermissions($dbuser.Name))
                            }

                            foreach ($item in $db.SearchPropertyLists) {
                                $null = $perms.AddRange($item.EnumObjectPermissions($dbuser.Name))
                            }

                            foreach ($item in $db.ServiceBroker.MessageTypes) {
                                $null = $perms.AddRange($item.EnumObjectPermissions($dbuser.Name))
                            }

                            foreach ($item in $db.RemoteServiceBindings) {
                                $null = $perms.AddRange($item.EnumObjectPermissions($dbuser.Name))
                            }

                            foreach ($item in $db.ServiceBroker.Routes) {
                                $null = $perms.AddRange($item.EnumObjectPermissions($dbuser.Name))
                            }

                            foreach ($item in $db.ServiceBroker.ServiceContracts) {
                                $null = $perms.AddRange($item.EnumObjectPermissions($dbuser.Name))
                            }

                            foreach ($item in $db.ServiceBroker.Services) {
                                $null = $perms.AddRange($item.EnumObjectPermissions($dbuser.Name))
                            }

                            if ($scriptVersion -ne "Version80") {
                                foreach ($item in $db.AsymmetricKeys) {
                                    $null = $perms.AddRange($item.EnumObjectPermissions($dbuser.Name))
                                }
                            }

                            foreach ($item in $db.SymmetricKeys) {
                                $null = $perms.AddRange($item.EnumObjectPermissions($dbuser.Name))
                            }

                            foreach ($item in $db.XmlSchemaCollections) {
                                $null = $perms.AddRange($item.EnumObjectPermissions($dbuser.Name))
                            }

                            foreach ($objectPermission in $perms | Where-Object { @("sa", "dbo", "information_schema", "sys") -notcontains $_.Grantee -and $_.Grantee -notlike "##*" -and $_.Grantee -eq $dbuser.Name }) {
                                switch ($objectPermission.ObjectClass) {
                                    'ApplicationRole' {
                                        $object = 'APPLICATION ROLE::[{0}]' -f $objectPermission.ObjectName
                                    }
                                    'AsymmetricKey' {
                                        $object = 'ASYMMETRIC KEY::[{0}]' -f $objectPermission.ObjectName
                                    }
                                    'Certificate' {
                                        $object = 'CERTIFICATE::[{0}]' -f $objectPermission.ObjectName
                                    }
                                    'DatabaseRole' {
                                        $object = 'ROLE::[{0}]' -f $objectPermission.ObjectName
                                    }
                                    'FullTextCatalog' {
                                        $object = 'FULLTEXT CATALOG::[{0}]' -f $objectPermission.ObjectName
                                    }
                                    'FullTextStopList' {
                                        $object = 'FULLTEXT STOPLIST::[{0}]' -f $objectPermission.ObjectName
                                    }
                                    'MessageType' {
                                        $object = 'Message Type::[{0}]' -f $objectPermission.ObjectName
                                    }
                                    'ObjectOrColumn' {
                                        if ($scriptVersion -ne "Version80") {
                                            $object = 'OBJECT::[{0}].[{1}]' -f $objectPermission.ObjectSchema, $objectPermission.ObjectName
                                            if ($null -ne $objectPermission.ColumnName) {
                                                $object += '([{0}])' -f $objectPermission.ColumnName
                                            }
                                        }
                                        #At SQL Server 2000 OBJECT did not exists
                                        else {
                                            $object = '[{0}].[{1}]' -f $objectPermission.ObjectSchema, $objectPermission.ObjectName
                                        }
                                    }
                                    'RemoteServiceBinding' {
                                        $object = 'REMOTE SERVICE BINDING::[{0}]' -f $objectPermission.ObjectName
                                    }
                                    'Schema' {
                                        $object = 'SCHEMA::[{0}]' -f $objectPermission.ObjectName
                                    }
                                    'SearchPropertyList' {
                                        $object = 'SEARCH PROPERTY LIST::[{0}]' -f $objectPermission.ObjectName
                                    }
                                    'Service' {
                                        $object = 'SERVICE::[{0}]' -f $objectPermission.ObjectName
                                    }
                                    'ServiceContract' {
                                        $object = 'CONTRACT::[{0}]' -f $objectPermission.ObjectName
                                    }
                                    'ServiceRoute' {
                                        $object = 'ROUTE::[{0}]' -f $objectPermission.ObjectName
                                    }
                                    'SqlAssembly' {
                                        $object = 'ASSEMBLY::[{0}]' -f $objectPermission.ObjectName