Notices: This section not yet converted to new layout. Download stats are rolling back out.

dbatools (PowerShell Module)

0.9.519

Package test results are passing.

There are versions of this package awaiting moderation . See the Version History section below.

This package was approved as a trusted package on 11/18/2018.

dbatools logo dbatools is sort of like a command-line SQL Server Management Studio. The project initially started out as Start-SqlMigration.ps1, but has now grown into a collection of over 300 commands that help automate SQL Server tasks and encourage best practices.

NOTE: This module requires a minimum of PowerShell v3.

NOTE: This is an automatically updated package. If you find it is out of date by more than a week, please contact the maintainer(s) and let them know the package is no longer updating correctly.

To install dbatools (PowerShell Module), run the following command from the command line or from PowerShell:

C:\> choco install dbatools

To upgrade dbatools (PowerShell Module), run the following command from the command line or from PowerShell:

C:\> choco upgrade dbatools

Files

Hide
  • tools\.skipAutoUninstaller
  • tools\chocolateyBeforeModify.ps1 Show
    $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 Show
    $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 Show
    $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 Show
    ### DO NOT EDIT THIS FILE DIRECTLY ###
    #ValidationTags#Messaging,FlowControl,Pipeline,CodeStyle#
    function Add-DbaAgDatabase {
        <#
        .SYNOPSIS
            Adds a database to an availability group on a SQL Server instance.
    
        .DESCRIPTION
            Adds a database to an availability group on a SQL Server instance.
    
       .PARAMETER SqlInstance
            The target SQL Server instance or instances. Server version must be SQL Server version 2012 or higher.
    
        .PARAMETER SqlCredential
            Login to the SqlInstance instance using alternative credentials. Windows and SQL Authentication supported. Accepts credential objects (Get-Credential)
    
        .PARAMETER Database
            The database or databases to add.
    
        .PARAMETER AvailabilityGroup
            The availability group where the databases will be added.
    
        .PARAMETER InputObject
            Enables piping from Get-DbaDatabase, Get-DbaDbSharePoint and more.
    
        .PARAMETER WhatIf
            Shows what would happen if the command were to run. No actions are actually performed.
    
        .PARAMETER Confirm
            Prompts you for confirmation before executing any changing operations within the command.
    
        .PARAMETER EnableException
            By default, when something goes wrong we try to catch it, interpret it and give you a friendly warning message.
            This avoids overwhelming you with "sea of red" exceptions, but is inconvenient because it basically disables advanced scripting.
            Using this switch turns this "nice by default" feature off and enables you to catch exceptions with your own try/catch.
    
        .NOTES
            Tags: AvailabilityGroup, HA, AG
            Author: Chrissy LeMaire (@cl), netnerds.net
            Website: https://dbatools.io
            Copyright: (c) 2018 by dbatools, licensed under MIT
            License: MIT https://opensource.org/licenses/MIT
    
        .LINK
            https://dbatools.io/Add-DbaAgDatabase
    
        .EXAMPLE
            PS C:\> Add-DbaAgDatabase -SqlInstance sql2017a -AvailabilityGroup ag1 -Database db1, db2 -Confirm
    
            Adds db1 and db2 to ag1 on sql2017a. Prompts for confirmation.
    
        .EXAMPLE
            PS C:\> Get-DbaDatabase -SqlInstance sql2017a | Out-GridView -Passthru | Add-DbaAgDatabase -AvailabilityGroup ag1
    
            Adds selected databases from sql2017a to ag1
    
        .EXAMPLE
            PS C:\> Get-DbaDbSharePoint -SqlInstance sqlcluster | Add-DbaAgDatabase -AvailabilityGroup SharePoint
    
            Adds SharePoint databases as found in SharePoint_Config on sqlcluster to ag1 on sqlcluster
    
        .EXAMPLE
            PS C:\> Get-DbaDbSharePoint -SqlInstance sqlcluster -ConfigDatabase SharePoint_Config_2019 | Add-DbaAgDatabase -AvailabilityGroup SharePoint
    
            Adds SharePoint databases as found in SharePoint_Config_2019 on sqlcluster to ag1 on sqlcluster
        #>
        [CmdletBinding(SupportsShouldProcess, ConfirmImpact = 'Low')]
        param (
            [DbaInstanceParameter[]]$SqlInstance,
            [PSCredential]$SqlCredential,
            [parameter(Mandatory)]
            [string]$AvailabilityGroup,
            [string[]]$Database,
            [parameter(ValueFromPipeline)]
            [Microsoft.SqlServer.Management.Smo.Database[]]$InputObject,
            [switch]$EnableException
        )
        process {
            if ((Test-Bound -ParameterName SqlInstance)) {
                if ((Test-Bound -Not -ParameterName Database) -or (Test-Bound -Not -ParameterName AvailabilityGroup)) {
                    Stop-Function -Message "You must specify one or more databases and one Availability Groups when using the SqlInstance parameter."
                    return
                }
            }
            
            foreach ($instance in $SqlInstance) {
                $InputObject += Get-DbaDatabase -SqlInstance $instance -SqlCredential $SqlCredential -Database $Database
            }
            
            foreach ($db in $InputObject) {
                $ags = Get-DbaAvailabilityGroup -SqlInstance $db.Parent -AvailabilityGroup $AvailabilityGroup
                
                foreach ($ag in $ags) {
                    if ($ag.AvailabilityDatabases.Name -contains $db.Name) {
                        Stop-Function -Message "$($db.Name) is already joined to $($ag.Name)" -Continue
                    }
                    
                    if ($Pscmdlet.ShouldProcess($ag.Parent.Name, "Adding availability group $db to $($db.Parent.Name)")) {
                        try {
                            $agdb = New-Object Microsoft.SqlServer.Management.Smo.AvailabilityDatabase($ag, $db.Name)
                            # something is up with .net create(), force a stop
                            Invoke-Create -Object $agdb
                            Get-DbaAgDatabase -SqlInstance $ag.Parent -Database $db.Name
                        } catch {
                            Stop-Function -Message "Failure" -ErrorRecord $_ -Continue
                        }
                    }
                    
                    $replicas = $ag.AvailabilityReplicas | Where-Object Role -eq Secondary | Select-Object -Unique -ExpandProperty Name
                    
                    foreach ($replica in $replicas) {
                        $replicadb = Get-DbaAgDatabase -SqlInstance $replica -SqlCredential $SqlCredential -Database $db.Name -AvailabilityGroup $ag.Name
                        $timeout = 1
                        do {
                            try {
                                Write-Message -Level Verbose -Message "Trying to add $($replicadb.Name) to $replica"
                                $timeout++
                                $replicadb.JoinAvailablityGroup()
                                $replicadb.Refresh()
                                Start-Sleep -Seconds 1
                            } catch {
                                Stop-Function -Message "Error joining database to availability group" -ErrorRecord $_ -Continue
                            }
                        } while (-not $replicadb.IsJoined -and $timeout -lt 10)
                        
                        if ($replicadb.IsJoined) {
                            $replicadb
                        } else {
                            Stop-Function -Continue -Message "Could not join $($replicadb.Name) to $replica"
                        }
                    }
                }
            }
        }
    }
    #ValidationTags#Messaging,FlowControl,Pipeline,CodeStyle#
    function Add-DbaAgListener {
        <#
        .SYNOPSIS
            Adds a listener to an availability group on a SQL Server instance.
    
        .DESCRIPTION
            Adds a listener to an availability group on a SQL Server instance.
    
       .PARAMETER SqlInstance
            The target SQL Server instance or instances. Server version must be SQL Server version 2012 or higher.
    
        .PARAMETER SqlCredential
            Login to the SqlInstance instance using alternative credentials. Windows and SQL Authentication supported. Accepts credential objects (Get-Credential)
    
        .PARAMETER AvailabilityGroup
            The Availability Group to which a listener will be bestowed upon.
    
        .PARAMETER IPAddress
            Sets the IP address of the availability group listener.
    
        .PARAMETER SubnetMask
            Sets the subnet IP mask of the availability group listener. Defaults to 255.255.255.0.
    
        .PARAMETER Port
            Sets the port number used to communicate with the availability group. Defaults to 1433.
    
        .PARAMETER Dhcp
            Indicates whether the listener uses DHCP.
    
        .PARAMETER Passthru
            Don't create the listener, just pass thru an object that can be further customized before creation.
    
        .PARAMETER InputObject
            Enables piping from Get-DbaAvailabilityGroup
    
        .PARAMETER WhatIf
            Shows what would happen if the command were to run. No actions are actually performed.
    
        .PARAMETER Confirm
            Prompts you for confirmation before executing any changing operations within the command.
    
        .PARAMETER EnableException
            By default, when something goes wrong we try to catch it, interpret it and give you a friendly warning message.
            This avoids overwhelming you with "sea of red" exceptions, but is inconvenient because it basically disables advanced scripting.
            Using this switch turns this "nice by default" feature off and enables you to catch exceptions with your own try/catch.
    
        .NOTES
            Tags: AvailabilityGroup, HA, AG
            Author: Chrissy LeMaire (@cl), netnerds.net
            Website: https://dbatools.io
            Copyright: (c) 2018 by dbatools, licensed under MIT
            License: MIT https://opensource.org/licenses/MIT
    
        .LINK
            https://dbatools.io/Add-DbaAgListener
    
        .EXAMPLE
            PS C:\> Add-DbaAgListener -SqlInstance sql2017 -AvailabilityGroup SharePoint -IPAddress 10.0.20.20
    
            Creates a listener on 10.0.20.20 port 1433 for the SharePoint availability group on sql2017.
    
        .EXAMPLE
            PS C:\> Get-DbaAvailabilityGroup -SqlInstance sql2017 -AvailabilityGroup availabilitygroup1 | Add-DbaAgListener -Dhcp
    
            Creates a listener on port 1433 with a dynamic IP for the group1 availability group on sql2017.
    
        #>
        [CmdletBinding(SupportsShouldProcess, ConfirmImpact = 'Low')]
        param (
            [DbaInstanceParameter[]]$SqlInstance,
            [PSCredential]$SqlCredential,
            [string[]]$AvailabilityGroup,
            [ipaddress]$IPAddress,
            [ipaddress]$SubnetMask = "255.255.255.0",
            [int]$Port = 1433,
            [switch]$Dhcp,
            [switch]$Passthru,
            [parameter(ValueFromPipeline)]
            [Microsoft.SqlServer.Management.Smo.AvailabilityGroup[]]$InputObject,
            [switch]$EnableException
        )
        process {
            if ((Test-Bound -ParameterName SqlInstance) -and (Test-Bound -Not -ParameterName AvailabilityGroup)) {
                Stop-Function -Message "You must specify one or more databases and one or more Availability Groups when using the SqlInstance parameter."
                return
            }
    
            if ($SqlInstance) {
                $InputObject += Get-DbaAvailabilityGroup -SqlInstance $SqlInstance -SqlCredential $SqlCredential -AvailabilityGroup $AvailabilityGroup
            }
    
            foreach ($ag in $InputObject) {
                if ((Test-Bound -Not -ParameterName Name)) {
                    $Name = $ag.Name
                }
                if ($Pscmdlet.ShouldProcess($ag.Parent.Name, "Adding $($IPAddress.IPAddressToString) to $($ag.Name)")) {
                    try {
                        $aglistener = New-Object Microsoft.SqlServer.Management.Smo.AvailabilityGroupListener -ArgumentList $ag, $Name
                        $aglistener.PortNumber = $Port
                        $listenerip = New-Object Microsoft.SqlServer.Management.Smo.AvailabilityGroupListenerIPAddress -ArgumentList $aglistener
    
                        if (Test-Bound -ParameterName IPAddress) {
                            $listenerip.IPAddress = $IPAddress.IPAddressToString
                            $listenerip.SubnetMask = $SubnetMask.IPAddressToString
                        }
    
                        $listenerip.IsDHCP = $Dhcp
                        $aglistener.AvailabilityGroupListenerIPAddresses.Add($listenerip)
    
                        if ($Passthru) {
                            return $aglistener
                        } else {
                            # something is up with .net create(), force a stop
                            Invoke-Create -Object $aglistener
                        }
                    } catch {
                        Stop-Function -Message "Failure" -ErrorRecord $_
                    }
                    Get-DbaAgListener -SqlInstance $ag.Parent -AvailabilityGroup $ag.Name -Listener $Name
                }
            }
        }
    }
    #ValidationTags#Messaging,FlowControl,Pipeline,CodeStyle#
    function Add-DbaAgReplica {
        <#
        .SYNOPSIS
            Adds a replica to an availability group on a SQL Server instance.
    
        .DESCRIPTION
            Adds a replica to an availability group on a SQL Server instance.
    
            Automatically creates a database mirroring endpoint if required.
    
       .PARAMETER SqlInstance
            The target SQL Server instance or instances. Server version must be SQL Server version 2012 or higher.
    
        .PARAMETER SqlCredential
            Login to the SqlInstance instance using alternative credentials. Windows and SQL Authentication supported. Accepts credential objects (Get-Credential)
    
        .PARAMETER Name
            The name of the replica. Defaults to the SQL Server instance name.
    
        .PARAMETER AvailabilityGroup
            The Availability Group to which a replica will be bestowed upon.
    
        .PARAMETER AvailabilityMode
            Sets the availability mode of the availability group replica. Options are: AsynchronousCommit and SynchronousCommit. SynchronousCommit is default.
    
        .PARAMETER FailoverMode
            Sets the failover mode of the availability group replica. Options are Automatic and Manual. Automatic is default.
    
        .PARAMETER BackupPriority
            Sets the backup priority availability group replica. Default is 50.
    
        .PARAMETER Endpoint
            By default, this command will attempt to find a DatabaseMirror endpoint. If one does not exist, it will create it.
    
            If an endpoint must be created, the name "hadr_endpoint" will be used. If an alternative is preferred, use Endpoint.
    
        .PARAMETER Passthru
            Don't create the replica, just pass thru an object that can be further customized before creation.
    
        .PARAMETER InputObject
            Enables piping from Get-DbaAvailabilityGroup.
    
        .PARAMETER ConnectionModeInPrimaryRole
            Specifies the connection intent modes of an Availability Replica in primary role. AllowAllConnections by default.
    
        .PARAMETER ConnectionModeInSecondaryRole
            Specifies the connection modes of an Availability Replica in secondary role. AllowAllConnections by default.
    
        .PARAMETER ReadonlyRoutingConnectionUrl
            Sets the read only routing connection url for the availability replica.
    
        .PARAMETER SeedingMode
            Specifies how the secondary replica will be initially seeded.
    
            Automatic enables direct seeding. This method will seed the secondary replica over the network. This method does not require you to backup and restore a copy of the primary database on the replica.
    
            Manual requires you to create a backup of the database on the primary replica and manually restore that backup on the secondary replica.
    
        .PARAMETER Certificate
            Specifies that the endpoint is to authenticate the connection using the certificate specified by certificate_name to establish identity for authorization.
    
            The far endpoint must have a certificate with the public key matching the private key of the specified certificate.
    
        .PARAMETER WhatIf
            Shows what would happen if the command were to run. No actions are actually performed.
    
        .PARAMETER Confirm
            Prompts you for confirmation before executing any changing operations within the command.
    
        .PARAMETER EnableException
            By default, when something goes wrong we try to catch it, interpret it and give you a friendly warning message.
            This avoids overwhelming you with "sea of red" exceptions, but is inconvenient because it basically disables advanced scripting.
            Using this switch turns this "nice by default" feature off and enables you to catch exceptions with your own try/catch.
    
        .NOTES
            Tags: AvailabilityGroup, HA, AG
            Author: Chrissy LeMaire (@cl), netnerds.net
            Website: https://dbatools.io
            Copyright: (c) 2018 by dbatools, licensed under MIT
            License: MIT https://opensource.org/licenses/MIT
    
        .LINK
            https://dbatools.io/Add-DbaAgReplica
    
        .EXAMPLE
            PS C:\> Get-DbaAvailabilityGroup -SqlInstance sql2017a -AvailabilityGroup SharePoint | Add-DbaAgReplica -SqlInstance sql2017b
    
            Adds sql2017b to the SharePoint availability group on sql2017a
    
        .EXAMPLE
            PS C:\> Get-DbaAvailabilityGroup -SqlInstance sql2017a -AvailabilityGroup SharePoint | Add-DbaAgReplica -SqlInstance sql2017b -FailoverMode Manual
    
            Adds sql2017b to the SharePoint availability group on sql2017a with a manual failover mode.
        #>
        [CmdletBinding(SupportsShouldProcess, ConfirmImpact = 'Low')]
        param (
            [DbaInstanceParameter[]]$SqlInstance,
            [PSCredential]$SqlCredential,
            [string]$AvailabilityGroup,
            [string]$Name,
            [ValidateSet('AsynchronousCommit', 'SynchronousCommit')]
            [string]$AvailabilityMode = "SynchronousCommit",
            [ValidateSet('Automatic', 'Manual', 'External')]
            [string]$FailoverMode = "Automatic",
            [int]$BackupPriority = 50,
            [ValidateSet('AllowAllConnections', 'AllowReadWriteConnections')]
            [string]$ConnectionModeInPrimaryRole = 'AllowAllConnections',
            [ValidateSet('AllowAllConnections', 'AllowNoConnections', 'AllowReadIntentConnectionsOnly')]
            [string]$ConnectionModeInSecondaryRole = 'AllowAllConnections',
            [ValidateSet('Automatic', 'Manual')]
            [string]$SeedingMode = 'Automatic',
            [string]$Endpoint,
            [switch]$Passthru,
            [string]$ReadonlyRoutingConnectionUrl,
            [string]$Certificate,
            [parameter(ValueFromPipeline)]
            [Microsoft.SqlServer.Management.Smo.AvailabilityGroup]$InputObject,
            [switch]$EnableException
        )
        process {
            if (-not $AvailabilityGroup -and -not $InputObject) {
                Stop-Function -Message "You must specify either AvailabilityGroup or pipe in an availabilty group to continue."
                return
            }
    
            foreach ($instance in $SqlInstance) {
                try {
                    $server = Connect-SqlInstance -SqlInstance $instance -SqlCredential $SqlCredential -MinimumVersion 11
                } catch {
                    Stop-Function -Message "Failure" -Category ConnectionError -ErrorRecord $_ -Target $instance -Continue
                }
    
                if ($Certificate) {
                    $cert = Get-DbaDbCertificate -SqlInstance $server -Certificate $Certificate
                    if (-not $cert) {
                        Stop-Function -Message "Certificate $Certificate does not exist on $instance" -ErrorRecord $_ -Target $Certificate -Continue
                    }
                }
    
                if ($AvailabilityGroup) {
                    $InputObject = Get-DbaAvailabilityGroup -SqlInstance $server -AvailabilityGroup $AvailabilityGroup
                }
    
                $ep = Get-DbaEndpoint -SqlInstance $server -Type DatabaseMirroring
    
                if (-not $ep) {
                    if ($Pscmdlet.ShouldProcess($server.Name, "Adding endpoint named $Endpoint to $instance")) {
                        if (-not $Endpoint) {
                            $Endpoint = "hadr_endpoint"
                        }
                        $ep = New-DbaEndpoint -SqlInstance $server -Name hadr_endpoint -Type DatabaseMirroring -EndpointEncryption Supported -EncryptionAlgorithm Aes -Certificate $Certificate
                        $null = $ep | Start-DbaEndpoint
                    }
                }
    
                if ((Test-Bound -Not -ParameterName Name)) {
                    $Name = $server.DomainInstanceName
                }
    
                if ($Pscmdlet.ShouldProcess($server.Name, "Creating a replica for $($InputObject.Name) named $Name")) {
                    try {
                        $replica = New-Object Microsoft.SqlServer.Management.Smo.AvailabilityReplica -ArgumentList $InputObject, $Name
                        $replica.EndpointUrl = $ep.Fqdn
                        $replica.FailoverMode = [Microsoft.SqlServer.Management.Smo.AvailabilityReplicaFailoverMode]::$FailoverMode
                        $replica.AvailabilityMode = [Microsoft.SqlServer.Management.Smo.AvailabilityReplicaAvailabilityMode]::$AvailabilityMode
                        if ($server.EngineEdition -ne "Standard") {
                            $replica.ConnectionModeInPrimaryRole = [Microsoft.SqlServer.Management.Smo.AvailabilityReplicaConnectionModeInPrimaryRole]::$ConnectionModeInPrimaryRole
                            $replica.ConnectionModeInSecondaryRole = [Microsoft.SqlServer.Management.Smo.AvailabilityReplicaConnectionModeInSecondaryRole]::$ConnectionModeInSecondaryRole
                        }
                        $replica.BackupPriority = $BackupPriority
    
                        if ($ReadonlyRoutingConnectionUrl) {
                            $replica.ReadonlyRoutingConnectionUrl = $ReadonlyRoutingConnectionUrl
                        }
    
                        if ($SeedingMode) {
                            $replica.SeedingMode = $SeedingMode
                        }
    
                        if ($Passthru) {
                            return $replica
                        }
    
                        $defaults = 'ComputerName', 'InstanceName', 'SqlInstance', 'AvailabilityGroup', 'Name', 'Role', 'RollupSynchronizationState', 'AvailabilityMode', 'BackupPriority', 'EndpointUrl', 'SessionTimeout', 'FailoverMode', 'ReadonlyRoutingList'
    
                        $InputObject.AvailabilityReplicas.Add($replica)
                        $agreplica = $InputObject.AvailabilityReplicas[$Name]
                        Add-Member -Force -InputObject $agreplica -MemberType NoteProperty -Name ComputerName -value $agreplica.Parent.ComputerName
                        Add-Member -Force -InputObject $agreplica -MemberType NoteProperty -Name InstanceName -value $agreplica.Parent.InstanceName
                        Add-Member -Force -InputObject $agreplica -MemberType NoteProperty -Name SqlInstance -value $agreplica.Parent.SqlInstance
                        Add-Member -Force -InputObject $agreplica -MemberType NoteProperty -Name AvailabilityGroup -value $agreplica.Parent.Name
                        Add-Member -Force -InputObject $agreplica -MemberType NoteProperty -Name Replica -value $agreplica.Name # backwards compat
    
                        Select-DefaultView -InputObject $agreplica -Property $defaults
                    } catch {
                        $msg = $_.Exception.InnerException.InnerException.Message
                        if (-not $msg) {
                            $msg = $_
                        }
                        Stop-Function -Message $msg -ErrorRecord $_ -Continue
                    }
                }
            }
        }
    }
    #ValidationTags#Messaging,FlowControl,Pipeline,CodeStyle#
    function Add-DbaCmsRegServer {
        <#
        .SYNOPSIS
            Adds registered servers to SQL Server Central Management Server (CMS)
    
        .DESCRIPTION
            Adds registered servers to SQL Server Central Management Server (CMS). If you need more flexiblity, look into Import-DbaCmsRegServer which
            accepts multiple kinds of input and allows you to add reg servers from different CMSes.
    
        .PARAMETER SqlInstance
            The target SQL Server instance
    
        .PARAMETER SqlCredential
            Login to the target instance using alternative credentials. Windows and SQL Authentication supported. Accepts credential objects (Get-Credential)
    
        .PARAMETER ServerName
            Server Name is the actual SQL instance name (labeled Server Name)
    
        .PARAMETER Name
            Name is basically the nickname in SSMS CMS interface (labeled Registered Server Name)
    
        .PARAMETER Description
            Adds a description for the registered server
    
        .PARAMETER Group
            Adds the registered server to a specific group.
    
        .PARAMETER InputObject
            Allows the piping of a registered server group
    
        .PARAMETER WhatIf
            Shows what would happen if the command were to run. No actions are actually performed.
    
        .PARAMETER Confirm
            Prompts you for confirmation before executing any changing operations within the command.
    
        .PARAMETER EnableException
            By default, when something goes wrong we try to catch it, interpret it and give you a friendly warning message.
    
            This avoids overwhelming you with "sea of red" exceptions, but is inconvenient because it basically disables advanced scripting.
    
            Using this switch turns this "nice by default" feature off and enables you to catch exceptions with your own try/catch.
    
        .NOTES
            Tags: RegisteredServer, CMS
            Author: Chrissy LeMaire (@cl), netnerds.net
    
            Website: https://dbatools.io
            Copyright: (c) 2018 by dbatools, licensed under MIT
            License: MIT https://opensource.org/licenses/MIT
    
        .LINK
            https://dbatools.io/Add-DbaCmsRegServer
    
        .EXAMPLE
            PS C:\> Add-DbaCmsRegServer -SqlInstance sql2008 -ServerName sql01
    
            Creates a registered server on sql2008's CMS which points to the SQL Server, sql01. When scrolling in CMS, the name "sql01" will be visible.
    
        .EXAMPLE
            PS C:\> Add-DbaCmsRegServer -SqlInstance sql2008 -ServerName sql01 -Name "The 2008 Clustered Instance" -Description "HR's Dedicated SharePoint instance"
    
            Creates a registered server on sql2008's CMS which points to the SQL Server, sql01. When scrolling in CMS, "The 2008 Clustered Instance" will be visible.
            Clearly this is hard to explain ;)
    
        .EXAMPLE
            PS C:\> Add-DbaCmsRegServer -SqlInstance sql2008 -ServerName sql01 -Group hr\Seattle
    
            Creates a registered server on sql2008's CMS which points to the SQL Server, sql01. When scrolling in CMS, the name "sql01" will be visible within the Seattle group which is in the hr group.
    
        .EXAMPLE
            PS C:\> Get-DbaCmsRegServerGroup -SqlInstance sql2008 -Group hr\Seattle | Add-DbaCmsRegServer -ServerName sql01111
    
            Creates a registered server on sql2008's CMS which points to the SQL Server, sql01. When scrolling in CMS, the name "sql01" will be visible within the Seattle group which is in the hr group.
    
        #>
        [CmdletBinding(SupportsShouldProcess)]
        param (
            [Alias("ServerInstance", "SqlServer")]
            [DbaInstanceParameter[]]$SqlInstance,
            [PSCredential]$SqlCredential,
            [parameter(Mandatory)]
            [string]$ServerName,
            [string]$Name = $ServerName,
            [string]$Description,
            [object]$Group,
            [parameter(ValueFromPipeline)]
            [Microsoft.SqlServer.Management.RegisteredServers.ServerGroup[]]$InputObject,
            [switch]$EnableException
        )
        process {
            if (-not $InputObject -and -not $SqlInstance) {
                Stop-Function -Message "You must either pipe in a registered server group or specify a sqlinstance"
                return
            }
    
            # double check in case a null name was bound
            if (-not $Name) {
                $Name = $ServerName
            }
    
            foreach ($instance in $SqlInstance) {
                if (($Group)) {
                    if ($Group -is [Microsoft.SqlServer.Management.RegisteredServers.ServerGroup]) {
                        $InputObject += Get-DbaCmsRegServerGroup -SqlInstance $instance -SqlCredential $SqlCredential -Group $Group.Name
                    } else {
                        $InputObject += Get-DbaCmsRegServerGroup -SqlInstance $instance -SqlCredential $SqlCredential -Group $Group
                    }
                } else {
                    $InputObject += Get-DbaCmsRegServerGroup -SqlInstance $instance -SqlCredential $SqlCredential -Id 1
                }
    
                if (-not $InputObject) {
                    Stop-Function -Message "No matching groups found on $instance" -Continue
                }
            }
    
            foreach ($reggroup in $InputObject) {
                $parentserver = Get-RegServerParent -InputObject $reggroup
    
                if ($null -eq $parentserver) {
                    Stop-Function -Message "Something went wrong and it's hard to explain, sorry. This basically shouldn't happen." -Continue
                }
    
                $server = $parentserver.ServerConnection.SqlConnectionObject
    
                if ($Pscmdlet.ShouldProcess($parentserver.SqlInstance, "Adding $ServerName")) {
                    try {
                        $newserver = New-Object Microsoft.SqlServer.Management.RegisteredServers.RegisteredServer($reggroup, $Name)
                        $newserver.ServerName = $ServerName
                        $newserver.Description = $Description
                        $newserver.Create()
    
                        Get-DbaCmsRegServer -SqlInstance $server -Name $Name -ServerName $ServerName
                    } catch {
                        Stop-Function -Message "Failed to add $ServerName on $($parentserver.SqlInstance)" -ErrorRecord $_ -Continue
                    }
                }
            }
        }
        end {
            Test-DbaDeprecation -DeprecatedOn "1.0.0" -Alias Add-DbaRegisteredServer
        }
    }
    #ValidationTags#Messaging,FlowControl,Pipeline,CodeStyle#
    function Add-DbaCmsRegServerGroup {
        <#
        .SYNOPSIS
            Adds registered server groups to SQL Server Central Management Server (CMS)
    
        .DESCRIPTION
            Adds registered server groups to SQL Server Central Management Server (CMS). If you need more flexibility, look into Import-DbaCmsRegServer which accepts multiple kinds of input and allows you to add reg servers and groups from different CMS.
    
        .PARAMETER SqlInstance
            The target SQL Server instance
    
        .PARAMETER SqlCredential
            Login to the target instance using alternative credentials. Windows and SQL Authentication supported. Accepts credential objects (Get-Credential)
    
        .PARAMETER Name
            The name of the registered server group
    
        .PARAMETER Description
            The description for the registered server group
    
        .PARAMETER Group
            The SQL Server Central Management Server group. If no groups are specified, the new group will be created at the root.
    
        .PARAMETER InputObject
            Allows results from Get-DbaCmsRegServerGroup to be piped in
    
        .PARAMETER WhatIf
            Shows what would happen if the command were to run. No actions are actually performed.
    
        .PARAMETER Confirm
            Prompts you for confirmation before executing any changing operations within the command.
    
        .PARAMETER EnableException
            By default, when something goes wrong we try to catch it, interpret it and give you a friendly warning message.
    
            This avoids overwhelming you with "sea of red" exceptions, but is inconvenient because it basically disables advanced scripting.
    
            Using this switch turns this "nice by default" feature off and enables you to catch exceptions with your own try/catch.
    
        .NOTES
            Tags: RegisteredServer, CMS
            Author: Chrissy LeMaire (@cl), netnerds.net
    
            Website: https://dbatools.io
            Copyright: (c) 2018 by dbatools, licensed under MIT
            License: MIT https://opensource.org/licenses/MIT
    
        .LINK
            https://dbatools.io/Add-DbaCmsRegServerGroup
    
        .EXAMPLE
            PS C:\> Add-DbaCmsRegServerGroup -SqlInstance sql2012 -Name HR
    
            Creates a registered server group called HR, in the root of sql2012's CMS
    
        .EXAMPLE
            PS C:\> Add-DbaCmsRegServerGroup -SqlInstance sql2012, sql2014 -Name sub-folder -Group HR
    
            Creates a registered server group on sql2012 and sql2014 called sub-folder within the HR group
    
        .EXAMPLE
            PS C:\> Get-DbaCmsRegServerGroup -SqlInstance sql2012, sql2014 -Group HR | Add-DbaCmsRegServerGroup -Name sub-folder
    
            Creates a registered server group on sql2012 and sql2014 called sub-folder within the HR group of each server
    
        #>
        [CmdletBinding(SupportsShouldProcess)]
        param (
            [Alias("ServerInstance", "SqlServer")]
            [DbaInstanceParameter[]]$SqlInstance,
            [PSCredential]$SqlCredential,
            [parameter(Mandatory)]
            [string]$Name,
            [string]$Description,
            [string]$Group,
            [parameter(ValueFromPipeline)]
            [Microsoft.SqlServer.Management.RegisteredServers.ServerGroup[]]$InputObject,
            [switch]$EnableException
        )
        process {
            if (-not $InputObject -and -not $SqlInstance) {
                Stop-Function -Message "You must either pipe in a registered server group or specify a sqlinstance"
                return
            }
            foreach ($instance in $SqlInstance) {
                if ((Test-Bound -ParameterName Group)) {
                    $InputObject += Get-DbaCmsRegServerGroup -SqlInstance $instance -SqlCredential $SqlCredential -Group $Group
                } else {
                    $InputObject += Get-DbaCmsRegServerGroup -SqlInstance $instance -SqlCredential $SqlCredential -Id 1
                }
            }
    
            foreach ($reggroup in $InputObject) {
                $parentserver = Get-RegServerParent -InputObject $reggroup
                $server = $parentserver.ServerConnection.ServerInstance.SqlConnectionObject
    
                if ($null -eq $parentserver) {
                    Stop-Function -Message "Something went wrong and it's hard to explain, sorry. This basically shouldn't happen." -Continue
                }
    
                if ($Pscmdlet.ShouldProcess($parentserver.SqlInstance, "Adding $Name")) {
                    try {
                        $newgroup = New-Object Microsoft.SqlServer.Management.RegisteredServers.ServerGroup($reggroup, $Name)
                        $newgroup.Description = $Description
                        $newgroup.Create()
    
                        Get-DbaCmsRegServerGroup -SqlInstance $parentserver.ServerConnection.SqlConnectionObject -Group (Get-RegServerGroupReverseParse -object $newgroup)
                        $parentserver.ServerConnection.Disconnect()
                    } catch {
                        Stop-Function -Message "Failed to add $reggroup on $server" -ErrorRecord $_ -Continue
                    }
                }
            }
        }
        end {
            Test-DbaDeprecation -DeprecatedOn "1.0.0" -Alias Add-DbaRegisteredServerGroup
        }
    }
    #ValidationTags#Messaging,FlowControl,Pipeline,CodeStyle#
    function Add-DbaComputerCertificate {
        <#
        .SYNOPSIS
            Adds a computer certificate - useful for older systems.
    
        .DESCRIPTION
            Adds a computer certificate from a local or remote computer.
    
        .PARAMETER ComputerName
            The target SQL Server instance or instances. Defaults to localhost.
    
        .PARAMETER Credential
            Allows you to login to $ComputerName using alternative credentials.
    
        .PARAMETER Password
            The password for the certificate, if it is password protected.
    
        .PARAMETER Certificate
            The target certificate object.
    
        .PARAMETER Path
            The local path to the target certificate object.
    
        .PARAMETER Store
            Certificate store. Default is LocalMachine.
    
        .PARAMETER Folder
            Certificate folder. Default is My (Personal).
    
        .PARAMETER EnableException
            By default, when something goes wrong we try to catch it, interpret it and give you a friendly warning message.
            This avoids overwhelming you with "sea of red" exceptions, but is inconvenient because it basically disables advanced scripting.
            Using this switch turns this "nice by default" feature off and enables you to catch exceptions with your own try/catch.
    
        .PARAMETER WhatIf
            If this switch is enabled, no actions are performed but informational messages will be displayed that explain what would happen if the command were to run.
    
        .PARAMETER Confirm
            If this switch is enabled, you will be prompted for confirmation before executing any operations that change state.
    
        .NOTES
            Tags: Certificate
            Author: Chrissy LeMaire (@cl), netnerds.net
    
            Website: https://dbatools.io
            Copyright: (c) 2018 by dbatools, licensed under MIT
            License: MIT https://opensource.org/licenses/MIT
    
        .EXAMPLE
            PS C:\> Add-DbaComputerCertificate -ComputerName Server1 -Path C:\temp\cert.cer
    
            Adds the local C:\temp\cert.cer to the remote server Server1 in LocalMachine\My (Personal).
    
        .EXAMPLE
            PS C:\> Add-DbaComputerCertificate -Path C:\temp\cert.cer
    
            Adds the local C:\temp\cert.cer to the local computer's LocalMachine\My (Personal) certificate store.
    
        #>
        [CmdletBinding(SupportsShouldProcess, ConfirmImpact = "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 oh well
                    $bytes = [System.IO.File]::ReadAllBytes($Path)
                    $Certificate = New-Object System.Security.Cryptography.X509Certificates.X509Certificate2
                    $Certificate.Import($bytes, $Password, [System.Security.Cryptography.X509Certificates.X509KeyStorageFlags]::DefaultKeySet)
                } catch {
                    Stop-Function -Message "Can't import certificate." -ErrorRecord $_
                    return
                }
            }
    
            #region Remoting Script
            $scriptBlock = {
    
                param (
                    $CertificateData,
    
                    [SecureString]$Password,
    
                    $Store,
    
                    $Folder
                )
    
                $cert = New-Object System.Security.Cryptography.X509Certificates.X509Certificate2
                $cert.Import($CertificateData, $Password, [System.Security.Cryptography.X509Certificates.X509KeyStorageFlags]::DefaultKeySet)
                Write-Message -Level Verbose -Message "Importing cert to $Folder\$Store"
                $tempStore = New-Object System.Security.Cryptography.X509Certificates.X509Store($Folder, $Store)
                $tempStore.Open('ReadWrite')
                $tempStore.Add($cert)
                $tempStore.Close()
    
                Write-Message -Level Verbose -Message "Searching Cert:\$Store\$Folder"
                Get-ChildItem "Cert:\$Store\$Folder" -Recurse | Where-Object { $_.Thumbprint -eq $cert.Thumbprint }
            }
            #endregion Remoting Script
        }
        process {
            if (Test-FunctionInterrupt) { return }
    
            if (-not $Certificate) {
                Stop-Function -Message "You must specify either Certificate or Path" -Category InvalidArgument
                return
            }
    
            foreach ($cert in $Certificate) {
    
                try {
                    $certData = $cert.Export([System.Security.Cryptography.X509Certificates.X509ContentType]::PFX, $Password)
                } catch {
                    Stop-Function -Message "Can't export certificate" -ErrorRecord $_ -Continue
                }
    
                foreach ($computer in $ComputerName) {
    
                    if ($PSCmdlet.ShouldProcess("local", "Connecting to $computer to import cert")) {
                        try {
                            Invoke-Command2 -ComputerName $computer -Credential $Credential -ArgumentList $certdata, $Password, $Store, $Folder -ScriptBlock $scriptblock -ErrorAction Stop |
                                Select-DefaultView -Property FriendlyName, DnsNameList, Thumbprint, NotBefore, NotAfter, Subject, Issuer
                        } catch {
                            Stop-Function -Message "Failure" -ErrorRecord $_ -Target $computer -Continue
                        }
                    }
                }
            }
        }
    }
    #ValidationTags#Messaging,FlowControl,Pipeline,CodeStyle#
    function Add-DbaDbMirrorMonitor {
        <#
        .SYNOPSIS
            Creates a database mirroring monitor job that periodically updates the mirroring status for every mirrored database on the server instance.
    
        .DESCRIPTION
            Creates a database mirroring monitor job that periodically updates the mirroring status for every mirrored database on the server instance.
    
            Basically executes sp_dbmmonitoraddmonitoring.
    
        .PARAMETER SqlInstance
            The target SQL Server instance
    
        .PARAMETER SqlCredential
            Login to the target instance using alternate Windows or SQL Login Authentication. Accepts credential objects (Get-Credential).
    
        .PARAMETER WhatIf
            Shows what would happen if the command were to run. No actions are actually performed.
    
        .PARAMETER Confirm
            Prompts you for confirmation before executing any changing operations within the command.
    
        .PARAMETER EnableException
            By default, when something goes wrong we try to catch it, interpret it and give you a friendly warning message.
            This avoids overwhelming you with "sea of red" exceptions, but is inconvenient because it basically disables advanced scripting.
            Using this switch turns this "nice by default" feature off and enables you to catch exceptions with your own try/catch.
    
        .NOTES
            Tags: Mirror, HA
            Author: Chrissy LeMaire (@cl), netnerds.net
    
            Website: https://dbatools.io
            Copyright: (c) 2018 by dbatools, licensed under MIT
            License: MIT https://opensource.org/licenses/MIT
    
        .LINK
            https://dbatools.io/Add-DbaDbMirrorMonitor
    
        .EXAMPLE
            PS C:\> Add-DbaDbMirrorMonitor -SqlInstance sql2008, sql2012
    
            Creates a database mirroring monitor job that periodically updates the mirroring status for every mirrored database on sql2008 and sql2012.
    
        #>
        [CmdletBinding(SupportsShouldProcess, ConfirmImpact = 'Low')]
        param (
            [parameter(Mandatory, ValueFromPipeline)]
            [DbaInstanceParameter[]]$SqlInstance,
            [PSCredential]$SqlCredential,
            [switch]$EnableException
        )
        process {
            foreach ($instance in $SqlInstance) {
                try {
                    $server = Connect-SqlInstance -SqlInstance $instance -SqlCredential $SqlCredential -MinimumVersion 9
                } catch {
                    Stop-Function -Message "Failure" -Category ConnectionError -ErrorRecord $_ -Target $instance -Continue
                }
    
                if ($Pscmdlet.ShouldProcess($instance, "add mirror monitoring")) {
                    try {
                        $server.Query("msdb.dbo.sp_dbmmonitoraddmonitoring")
                        [pscustomobject]@{
                            ComputerName  = $server.ComputerName
                            InstanceName  = $server.ServiceName
                            SqlInstance   = $server.DomainInstanceName
                            MonitorStatus = "Added"
                        }
                    } catch {
                        Stop-Function -Message "Failure" -ErrorRecord $_ -Continue
                    }
                }
            }
        }
    }
    #ValidationTags#Messaging,FlowControl,Pipeline,CodeStyle#
    function Add-DbaPfDataCollectorCounter {
        <#
        .SYNOPSIS
            Adds a Performance Data Collector Counter.
    
        .DESCRIPTION
            Adds a Performance Data Collector Counter.
    
        .PARAMETER ComputerName
            The target computer. Defaults to localhost.
    
        .PARAMETER Credential
            Allows you to login to $ComputerName using alternative credentials. To use:
    
            $cred = Get-Credential, then pass $cred object to the -Credential parameter.
    
        .PARAMETER CollectorSet
            The Collector Set name.
    
        .PARAMETER Collector
            The Collector name.
    
        .PARAMETER Counter
            The Counter name. This must be in the form of '\Processor(_Total)\% Processor Time'.
    
        .PARAMETER InputObject
            Accepts the object output by Get-DbaPfDataCollector via the pipeline.
    
        .PARAMETER WhatIf
            If this switch is enabled, no actions are performed but informational messages will be displayed that explain what would happen if the command were to run.
    
        .PARAMETER Confirm
            If this switch is enabled, you will be prompted for confirmation before executing any operations that change state.
    
        .PARAMETER EnableException
            By default, when something goes wrong we try to catch it, interpret it and give you a friendly warning message.
            This avoids overwhelming you with "sea of red" exceptions, but is inconvenient because it basically disables advanced scripting.
            Using this switch turns this "nice by default" feature off and enables you to catch exceptions with your own try/catch.
    
        .NOTES
            Tags: PerfMon
            Author: Chrissy LeMaire (@cl), netnerds.net
    
            Website: https://dbatools.io
            Copyright: (c) 2018 by dbatools, licensed under MIT
            License: MIT https://opensource.org/licenses/MIT
    
        .LINK
            https://dbatools.io/Add-DbaPfDataCollectorCounter
    
        .EXAMPLE
            PS C:\> Add-DbaPfDataCollectorCounter -ComputerName sql2017 -CollectorSet 'System Correlation' -Collector DataCollector01  -Counter '\LogicalDisk(*)\Avg. Disk Queue Length'
    
            Adds the '\LogicalDisk(*)\Avg. Disk Queue Length' counter within the DataCollector01 collector within the System Correlation collector set on sql2017.
    
        .EXAMPLE
            PS C:\> Get-DbaPfDataCollector | Out-GridView -PassThru | Add-DbaPfDataCollectorCounter -Counter '\LogicalDisk(*)\Avg. Disk Queue Length' -Confirm
    
            Allows you to select which Data Collector you'd like to add the counter '\LogicalDisk(*)\Avg. Disk Queue Length' on localhost and prompts for confirmation.
    
        #>
        [CmdletBinding(SupportsShouldProcess, ConfirmImpact = "Low")]
        param (
            [DbaInstance[]]$ComputerName = $env:COMPUTERNAME,
            [PSCredential]$Credential,
            [Alias("DataCollectorSet")]
            [string[]]$CollectorSet,
            [Alias("DataCollector")]
            [string[]]$Collector,
            [Alias("Name")]
            [parameter(Mandatory, ValueFromPipelineByPropertyName)]
            [object[]]$Counter,
            [parameter(ValueFromPipeline)]
            [object[]]$InputObject,
            [switch]$EnableException
        )
        begin {
            $setscript = {
                $setname = $args[0]; $Addxml = $args[1]
                $set = New-Object -ComObject Pla.DataCollectorSet
                $set.SetXml($Addxml)
                $set.Commit($setname, $null, 0x0003) #add or modify.
                $set.Query($setname, $Null)
            }
        }
        process {
            if ($InputObject.Credential -and (Test-Bound -ParameterName Credential -Not)) {
                $Credential = $InputObject.Credential
            }
    
            if (($InputObject | Get-Member -MemberType NoteProperty -ErrorAction SilentlyContinue).Count -le 3 -and $InputObject.ComputerName -and $InputObject.Name) {
                # it's coming from Get-DbaPfAvailableCounter
                $ComputerName = $InputObject.ComputerName
                $Counter = $InputObject.Name
                $InputObject = $null
            }
    
            if (-not $InputObject -or ($InputObject -and (Test-Bound -ParameterName ComputerName))) {
                foreach ($computer in $ComputerName) {
                    $InputObject += Get-DbaPfDataCollector -ComputerName $computer -Credential $Credential -CollectorSet $CollectorSet -Collector $Collector
                }
            }
    
            if ($InputObject) {
                if (-not $InputObject.DataCollectorObject) {
                    Stop-Function -Message "InputObject is not of the right type. Please use Get-DbaPfDataCollector or Get-DbaPfAvailableCounter."
                    return
                }
            }
    
            foreach ($object in $InputObject) {
                $computer = $InputObject.ComputerName
                $null = Test-ElevationRequirement -ComputerName $computer -Continue
                $setname = $InputObject.DataCollectorSet
                $collectorname = $InputObject.Name
                $xml = [xml]($InputObject.DataCollectorSetXml)
    
                foreach ($countername in $counter) {
                    $node = $xml.SelectSingleNode("//Name[.='$collectorname']")
                    $newitem = $xml.CreateElement('Counter')
                    $null = $newitem.PsBase.InnerText = $countername
                    $null = $node.ParentNode.AppendChild($newitem)
                    $newitem = $xml.CreateElement('CounterDisplayName')
                    $null = $newitem.PsBase.InnerText = $countername
                    $null = $node.ParentNode.AppendChild($newitem)
                }
                $plainxml = $xml.OuterXml
    
                if ($Pscmdlet.ShouldProcess("$computer", "Adding $counters to $collectorname with the $setname collection set")) {
                    try {
                        $results = Invoke-Command2 -ComputerName $computer -Credential $Credential -ScriptBlock $setscript -ArgumentList $setname, $plainxml -ErrorAction Stop
                        Write-Message -Level Verbose -Message " $results"
                        Get-DbaPfDataCollectorCounter -ComputerName $computer -Credential $Credential -CollectorSet $setname -Collector $collectorname -Counter $counter
                    } catch {
                        Stop-Function -Message "Failure importing $Countername to $computer." -ErrorRecord $_ -Target $computer -Continue
                    }
                }
            }
        }
    }
    function Backup-DbaDatabase {
        <#
        .SYNOPSIS
            Backup one or more SQL Sever databases from a single SQL Server SqlInstance.
    
        .DESCRIPTION
            Performs a backup of a specified type of 1 or more databases on a single SQL Server Instance. These backups may be Full, Differential or Transaction log backups.
    
        .PARAMETER SqlInstance
            The SQL Server instance hosting the databases to be backed up.
    
        .PARAMETER SqlCredential
            Credentials to connect to the SQL Server instance if the calling user does not have permission.
    
        .PARAMETER Database
            The database(s) to process. This list is auto-populated from the server. If unspecified, all databases will be processed.
    
        .PARAMETER ExcludeDatabase
            The database(s) to exclude. This list is auto-populated from the server.
    
        .PARAMETER BackupFileName
            The name of the file to backup to. This is only accepted for single database backups.
            If no name is specified then the backup files will be named DatabaseName_yyyyMMddHHmm (i.e. "Database1_201714022131") with the appropriate extension.
    
            If the same name is used repeatedly, SQL Server will add backups to the same file at an incrementing position.
    
            SQL Server needs permissions to write to the specified location. Path names are based on the SQL Server (C:\ is the C drive on the SQL Server, not the machine running the script).
    
            Passing in NUL as the BackupFileName will backup to the NUL: device
    
        .PARAMETER TimeStampFormat
            By default the command timestamps backups using the format yyyyMMddHHmm. Using this parameter this can be overridden. The timestamp format should be defined using the Get-Date formats, illegal formats will cause an error to be thrown
    
        .PARAMETER BackupDirectory
            Path in which to place the backup files. If not specified, the backups will be placed in the default backup location for SqlInstance.
            If multiple paths are specified, the backups will be striped across these locations. This will overwrite the FileCount option.
    
            If the path does not exist, Sql Server will attempt to create it. Folders are created by the Sql Instance, and checks will be made for write permissions.
    
            File Names with be suffixed with x-of-y to enable identifying striped sets, where y is the number of files in the set and x ranges from 1 to y.
    
        .PARAMETER ReplaceInName
            If this switch is set, the following list of strings will be replaced in the BackupFileName and BackupDirectory strings:
                instancename - will be replaced with the instance Name
                servername - will be replaced with the server name
                dbname - will be replaced with the database name
                timestamp - will be replaced with the timestamp (either the default, or the format provided)
                backuptype - will be replaced with Full, Log or Differential as appropriate
    
        .PARAMETER CopyOnly
            If this switch is enabled, CopyOnly backups will be taken. By default function performs a normal backup, these backups interfere with the restore chain of the database. CopyOnly backups will not interfere with the restore chain of the database.
    
            For more details please refer to this MSDN article - https://msdn.microsoft.com/en-us/library/ms191495.aspx
    
        .PARAMETER Type
            The type of SQL Server backup to perform. Accepted values are "Full", "Log", "Differential", "Diff", "Database"
    
        .PARAMETER FileCount
            This is the number of striped copies of the backups you wish to create.    This value is overwritten if you specify multiple Backup Directories.
    
        .PARAMETER CreateFolder
            If this switch is enabled, each database will be backed up into a separate folder on each of the paths specified by BackupDirectory.
    
        .PARAMETER CompressBackup
            If this switch is enabled, the function will try to perform a compressed backup if supported by the version and edition of SQL Server. Otherwise, this function will use the server(s) default setting for compression.
    
        .PARAMETER MaxTransferSize
            Sets the size of the unit of transfer. Values must be a multiple of 64kb.
    
        .PARAMETER Blocksize
            Specifies the block size to use. Must be one of 0.5KB, 1KB, 2KB, 4KB, 8KB, 16KB, 32KB or 64KB. This can be specified in bytes.
            Refer to https://msdn.microsoft.com/en-us/library/ms178615.aspx for more detail
    
        .PARAMETER BufferCount
            Number of I/O buffers to use to perform the operation.
            Refer to https://msdn.microsoft.com/en-us/library/ms178615.aspx for more detail
    
        .PARAMETER Checksum
            If this switch is enabled, the backup checksum will be calculated.
    
        .PARAMETER Verify
            If this switch is enabled, the backup will be verified by running a RESTORE VERIFYONLY against the SqlInstance
    
        .PARAMETER WithFormat
            Formats the media as the first step of the backup operation. NOTE: This will set Initialize and SkipTapeHeader to $true.
    
        .PARAMETER Initialize
            Initializes the media as part of the backup operation.
    
        .PARAMETER SkipTapeHeader
            Initializes the media as part of the backup operation.
    
        .PARAMETER InputObject
            Internal parameter
    
        .PARAMETER AzureBaseUrl
            The URL to the base container of an Azure Storage account to write backups to.
    
            If specified, the only other parameters than can be used are "NoCopyOnly", "Type", "CompressBackup", "Checksum", "Verify", "AzureCredential", "CreateFolder".
    
        .PARAMETER AzureCredential
            The name of the credential on the SQL instance that can write to the AzureBaseUrl, only needed if using Storage access keys
            If using SAS credentials, the command will look for a credential with a name matching the AzureBaseUrl
    
        .PARAMETER NoRecovery
            This is passed in to perform a tail log backup if needed
    
        .PARAMETER BuildPath
            By default this command will not attempt to create missing paths, this switch will change the behaviour so that it will
    
        .PARAMETER IgnoreFileChecks
            This switch stops the function from checking for the validity of paths. This can be useful if SQL Server only has read access to the backup area.
            Note, that as we cannot check the path you may well end up with errors.
    
        .PARAMETER OutputScriptOnly
            Switch causes only the T-SQL script for the backup to be generated. Will not create any paths if they do not exist
    
        .PARAMETER EnableException
            By default, when something goes wrong we try to catch it, interpret it and give you a friendly warning message.
            This avoids overwhelming you with "sea of red" exceptions, but is inconvenient because it basically disables advanced scripting.
            Using this switch turns this "nice by default" feature off and enables you to catch exceptions with your own try/catch.
    
        .PARAMETER WhatIf
            If this switch is enabled, no actions are performed but informational messages will be displayed that explain what would happen if the command were to run.
    
        .PARAMETER Confirm
            If this switch is enabled, you will be prompted for confirmation before executing any operations that change state.
    
        .NOTES
            Tags: DisasterRecovery, Backup, Restore
            Author: Stuart Moore (@napalmgram), stuart-moore.com
    
            Website: https://dbatools.io
            Copyright: (c) 2018 by dbatools, licensed under MIT
            License: MIT https://opensource.org/licenses/MIT
    
        .EXAMPLE
            PS C:\> Backup-DbaDatabase -SqlInstance Server1 -Database HR, Finance
    
            This will perform a full database backup on the databases HR and Finance on SQL Server Instance Server1 to Server1 default backup directory.
    
        .EXAMPLE
            PS C:\> Backup-DbaDatabase -SqlInstance sql2016 -BackupDirectory C:\temp -Database AdventureWorks2014 -Type Full
    
            Backs up AdventureWorks2014 to sql2016 C:\temp folder.
    
        .EXAMPLE
            PS C:\> Backup-DbaDatabase -SqlInstance sql2016 -AzureBaseUrl https://dbatoolsaz.blob.core.windows.net/azbackups/ -AzureCredential dbatoolscred -Type Full -CreateFolder
    
            Performs a full backup of all databases on the sql2016 instance to their own containers under the https://dbatoolsaz.blob.core.windows.net/azbackups/ container on Azure blog storage using the sql credential "dbatoolscred" registered on the sql2016 instance.
    
        .EXAMPLE
            PS C:\> Backup-DbaDatabase -SqlInstance sql2016 -AzureBaseUrl https://dbatoolsaz.blob.core.windows.net/azbackups/  -Type Full
    
            Performs a full backup of all databases on the sql2016 instance to the https://dbatoolsaz.blob.core.windows.net/azbackups/ container on Azure blog storage using the Shared Access Signature sql credential "https://dbatoolsaz.blob.core.windows.net/azbackups" registered on the sql2016 instance.
    
        .EXAMPLE
            PS C:\> Backup-Dbadatabase -SqlInstance Server1\Prod -Database db1 -BackupDirectory \\filestore\backups\servername\instancename\dbname\backuptype -Type Full -ReplaceInName
    
            Performs a full backup of db1 into the folder \\filestore\backups\server1\prod\db1
    
        .EXAMPLE
            PS C:\> Backup-Dbadatabase -SqlInstance Server1\Prod -BackupDirectory \\filestore\backups\servername\instancename\dbname\backuptype -BackupFileName dbname-backuptype-timestamp.trn -Type Log -ReplaceInName
    
            Performs a log backup for every database. For the database db1 this would results in backup files in \\filestore\backups\server1\prod\db1\Log\db1-log-31102018.trn
    
        .EXAMPLE
            PS C:\> Backup-DbaDatabase -SqlInstance Sql2017 -Database master -BackupFileName NUL
    
            Performs a backup of master, but sends the output to the NUL device (ie; throws it away)
        #>
        [CmdletBinding(DefaultParameterSetName = "Default", SupportsShouldProcess)]
        [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSAvoidUsingPlainTextForPassword", "")] #For AzureCredential
        param (
            [parameter(ParameterSetName = "Pipe", Mandatory)]
            [DbaInstanceParameter]$SqlInstance,
            [PSCredential]$SqlCredential,
            [Alias("Databases")]
            [object[]]$Database,
            [object[]]$ExcludeDatabase,
            [string[]]$BackupDirectory,
            [string]$BackupFileName,
            [switch]$ReplaceInName,
            [switch]$CopyOnly,
            [ValidateSet('Full', 'Log', 'Differential', 'Diff', 'Database')]
            [string]$Type = 'Database',
            [parameter(ParameterSetName = "NoPipe", Mandatory, ValueFromPipeline)]
            [object[]]$InputObject,
            [switch]$CreateFolder,
            [int]$FileCount = 0,
            [switch]$CompressBackup,
            [switch]$Checksum,
            [switch]$Verify,
            [int]$MaxTransferSize,
            [int]$BlockSize,
            [int]$BufferCount,
            [string]$AzureBaseUrl,
            [string]$AzureCredential,
            [switch]$NoRecovery,
            [switch]$BuildPath,
            [switch]$WithFormat,
            [switch]$Initialize,
            [switch]$SkipTapeHeader,
            [string]$TimeStampFormat,
            [switch]$IgnoreFileChecks,
            [switch]$OutputScriptOnly,
            [Alias('Silent')]
            [switch]$EnableException
        )
    
        begin {
            if (-not (Test-Bound 'TimeStampFormat')) {
                Write-Message -Message 'Setting Default timestampformat' -Level Verbose
                $TimeStampFormat = "yyyyMMddHHmm"
            }
            if ($SqlInstance.length -ne 0) {
                try {
                    $Server = Connect-SqlInstance -SqlInstance $SqlInstance -SqlCredential $SqlCredential -AzureUnsupported
                } catch {
                    Stop-Function -Message "Cannot connect to $SqlInstance" -ErrorRecord $_
                    return
                }
    
                if ($Database) {
                    $InputObject = $server.Databases | Where-Object Name -in $Database
                } else {
                    $InputObject = $server.Databases | Where-Object Name -ne 'tempdb'
                }
    
                if ($ExcludeDatabase) {
                    $InputObject = $InputObject | Where-Object Name -notin $ExcludeDatabase
                }
    
                if ($null -eq $BackupDirectory -and $backupfileName -ne 'NUL') {
                    Write-Message -Message 'No backupfolder passed in, setting it to instance default' -Level Verbose
                    $BackupDirectory = (Get-DbaDefaultPath -SqlInstance $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 '' -and $True -ne $ReplaceInFile) {
                    Stop-Function -Message "1 BackupFile specified, but more than 1 database."
                    return
                }
    
                if (($MaxTransferSize % 64kb) -ne 0 -or $MaxTransferSize -gt 4mb) {
                    Stop-Function -Message "MaxTransferSize value must be a multiple of 64kb and no greater than 4MB"
                    return
                }
                if ($BlockSize) {
                    if ($BlockSize -notin (0.5kb, 1kb, 2kb, 4kb, 8kb, 16kb, 32kb, 64kb)) {
                        Stop-Function -Message "Block size must be one of 0.5kb,1kb,2kb,4kb,8kb,16kb,32kb,64kb"
                        return
                    }
                }
                if ('' -ne $AzureBaseUrl) {
                    $AzureBaseUrl = $AzureBaseUrl.Trim("/")
                    if ('' -ne $AzureCredential) {
                        Write-Message -Message "Azure Credential name passed in, will proceed assuming it's value" -Level Verbose
                        $FileCount = 1
                    } else {
                        Write-Message -Message "AzureUrl and no credential, testing for SAS credential"
                        if (Get-DbaCredential -SqlInstance $server -Name $AzureBaseUrl) {
                            Write-Message -Message "Found a SAS backup credental" -Level Verbose
                        } else {
                            Stop-Function -Message "You must provide the credential name for the Azure Storage Account"
                            return
                        }
                    }
                    $BackupDirectory = $AzureBaseUrl
                }
    
                if ($OutputScriptOnly) {
                    $IgnoreFileChecks = $true
                }
            }
        }
    
        process {
            if (!$SqlInstance -and !$InputObject) {
                Stop-Function -Message "You must specify a server and database or pipe some databases"
                return
            }
    
            Write-Message -Level Verbose -Message "$($InputObject.Count) database to backup"
    
            foreach ($Database in $InputObject) {
                $ProgressId = Get-Random
                $failures = @()
                $dbname = $Database.Name
    
                if ($dbname -eq "tempdb") {
                    Stop-Function -Message "Backing up tempdb not supported" -Continue
                }
    
                if ('Normal' -notin ($Database.Status -split ',')) {
                    Stop-Function -Message "Database status not Normal. $dbname skipped." -Continue
                }
    
                if ($Database.DatabaseSnapshotBaseName) {
                    Stop-Function -Message "Backing up snapshots not supported. $dbname skipped." -Continue
                }
    
                if ($null -eq $server) { $server = $Database.Parent }
    
                Write-Message -Level Verbose -Message "Backup database $database"
    
                if ($null -eq $Database.RecoveryModel) {
                    $Database.RecoveryModel = $server.Databases[$Database.Name].RecoveryModel
                    Write-Message -Level Verbose -Message "$dbname is in $($Database.RecoveryModel) recovery model"
                }
    
                # Fixes one-off cases of StackOverflowException crashes, see issue 1481
                $dbRecovery = $Database.RecoveryModel.ToString()
                if ($dbRecovery -eq 'Simple' -and $Type -eq 'Log') {
                    $failreason = "$database is in simple recovery mode, cannot take log backup"
                    $failures += $failreason
                    Write-Message -Level Warning -Message "$failreason"
                }
    
                $lastfull = $database.Refresh().LastBackupDate.Year
    
                if ($Type -notin @("Database", "Full") -and $lastfull -eq 1) {
                    $failreason = "$database does not have an existing full backup, cannot take log or differentialbackup"
                    $failures += $failreason
                    Write-Message -Level Warning -Message "$failreason"
                }
    
                if ($CopyOnly -ne $true) {
                    $CopyOnly = $false
                }
    
                $server.ConnectionContext.StatementTimeout = 0
                $backup = New-Object Microsoft.SqlServer.Management.Smo.Backup
                $backup.Database = $Database.Name
                $Suffix = "bak"
    
                if ($CompressBackup) {
                    if ($database.EncryptionEnabled) {
                        Write-Message -Level Warning -Message "$dbname is enabled for encryption, will not compress"
                        $backup.CompressionOption = 2
                    } elseif ($server.Edition -like 'Express*' -or ($server.VersionMajor -eq 10 -and $server.VersionMinor -eq 0 -and $server.Edition -notlike '*enterprise*') -or $server.VersionMajor -lt 10) {
                        Write-Message -Level Warning -Message "Compression is not supported with this version/edition of Sql Server"
                    } else {
                        Write-Message -Level Verbose -Message "Compression enabled"
                        $backup.CompressionOption = 1
                    }
                }
    
                if ($Checksum) {
                    $backup.Checksum = $true
                }
    
                if ($Type -in 'Diff', 'Differential') {
                    Write-Message -Level VeryVerbose -Message "Creating differential backup"
                    $SMOBackuptype = "Database"
                    $backup.Incremental = $true
                    $outputType = 'Differential'
                }
                $Backup.NoRecovery = $false
                if ($Type -eq "Log") {
                    Write-Message -Level VeryVerbose -Message "Creating log backup"
                    $Suffix = "trn"
                    $OutputType = 'Log'
                    $SMOBackupType = 'Log'
                    $Backup.NoRecovery = $NoRecovery
                }
    
                if ($Type -in 'Full', 'Database') {
                    Write-Message -Level VeryVerbose -Message "Creating full backup"
                    $SMOBackupType = "Database"
                    $OutputType = 'Full'
                }
    
                $backup.CopyOnly = $copyonly
                $backup.Action = $SMOBackupType
                if ('' -ne $AzureBaseUrl -and $null -ne $AzureCredential) {
                    $backup.CredentialName = $AzureCredential
                }
    
                Write-Message -Level Verbose -Message "Building file name"
    
                $BackupFinalName = ''
                $FinalBackupPath = @()
                $timestamp = Get-Date -Format $TimeStampFormat
                if ('NUL' -eq $BackupFileName) {
                    $FinalBackupPath += 'NUL:'
                    $IgnoreFileChecks = $true
                } elseif ('' -ne $BackupFileName) {
                    $File = New-Object System.IO.FileInfo($BackupFileName)
                    $BackupFinalName = $file.Name
                    $suffix = $file.extension -Replace '^\.', ''
                    if ( '' -ne (Split-Path $BackupFileName)) {
                        Write-Message -Level Verbose -Message "Fully qualified path passed in"
                        $FinalBackupPath += [IO.Path]::GetFullPath($file.DirectoryName)
                    }
                } else {
                    Write-Message -Level VeryVerbose -Message "Setting filename - $timestamp"
                    $BackupFinalName = "$($dbname)_$timestamp.$suffix"
                }
    
                Write-Message -Level Verbose -Message "Building backup path"
                if ($FinalBackupPath.Count -eq 0) {
                    $FinalBackupPath += $BackupDirectory
                }
    
                if ($BackupDirectory.Count -eq 1 -and $Filecount -gt 1) {
                    for ($i = 0; $i -lt ($Filecount - 1); $i++) {
                        $FinalBackupPath += $FinalBackupPath[0]
                    }
                }
    
                if ($AzureBaseUrl -or $AzureCredential) {
                    $slash = "/"
                } else {
                    $slash = "\"
                }
                if ($FinalBackupPath.Count -gt 1) {
                    $File = New-Object System.IO.FileInfo($BackupFinalName)
                    for ($i = 0; $i -lt $FinalBackupPath.Count; $i++) {
                        $FinalBackupPath[$i] = $FinalBackupPath[$i] + $slash + $($File.BaseName) + "-$($i+1)-of-$FileCount.$suffix"
                    }
                } elseif ($FinalBackupPath[0] -ne 'NUL:') {
                    $FinalBackupPath[0] = $FinalBackupPath[0] + $slash + $BackupFinalName
                }
    
                if ($CreateFolder -and $FinalBackupPath[0] -ne 'NUL:') {
                    for ($i = 0; $i -lt $FinalBackupPath.Count; $i++) {
                        $parent = [IO.Path]::GetDirectoryName($FinalBackupPath[$i])
                        $leaf = [IO.Path]::GetFileName($FinalBackupPath[$i])
                        $FinalBackupPath[$i] = [IO.Path]::Combine($parent, $dbname, $leaf)
                    }
                }
    
                if ($True -eq $ReplaceInName) {
                    for ($i = 0; $i -lt $FinalBackupPath.count; $i++) {
                        $FinalBackupPath[$i] = $FinalBackupPath[$i] -replace ('dbname', $dbname)
                        $FinalBackupPath[$i] = $FinalBackupPath[$i] -replace ('instancename', $SqlInstance.InstanceName)
                        $FinalBackupPath[$i] = $FinalBackupPath[$i] -replace ('servername', $SqlInstance.ComputerName)
                        $FinalBackupPath[$i] = $FinalBackupPath[$i] -replace ('timestamp', $timestamp)
                        $FinalBackupPath[$i] = $FinalBackupPath[$i] -replace ('backuptype', $outputType)
                    }
                }
    
                if (-not $IgnoreFileChecks -and -not $AzureBaseUrl) {
                    $parentPaths = ($FinalBackupPath | ForEach-Object { Split-Path $_ } | Select-Object -Unique)
                    foreach ($parentPath in $parentPaths) {
                        if (-not (Test-DbaPath -SqlInstance $server -Path $parentPath)) {
                            if (($BuildPath -eq $true) -or ($CreateFolder -eq $True)) {
                                $null = New-DbaDirectory -SqlInstance $server -Path $parentPath
                            } else {
                                $failreason += "SQL Server cannot check if $parentPath exists. You can try disabling this check with -IgnoreFileChecks"
                                $failures += $failreason
                                Write-Message -Level Warning -Message "$failreason"
                            }
                        }
                    }
                }
    
    
                if ('' -eq $AzureBaseUrl -and $BackupDirectory) {
                    $FinalBackupPath = $FinalBackupPath | ForEach-Object { [IO.Path]::GetFullPath($_) }
                }
    
    
                $script = $null
                $backupComplete = $false
    
                if (!$failures) {
                    $Filecount = $FinalBackupPath.Count
    
                    foreach ($backupfile in $FinalBackupPath) {
                        $device = New-Object Microsoft.SqlServer.Management.Smo.BackupDeviceItem
                        if ('' -ne $AzureBaseUrl) {
                            $device.DeviceType = "URL"
                        } else {
                            $device.DeviceType = "File"
                        }
    
                        if ($WithFormat) {
                            Write-Message -Message "WithFormat specified. Ensuring Initialize and SkipTapeHeader are set to true." -Level Verbose
                            $Initialize = $true
                            $SkipTapeHeader = $true
                        }
    
                        $backup.FormatMedia = $WithFormat
                        $backup.Initialize = $Initialize
                        $backup.SkipTapeHeader = $SkipTapeHeader
                        $device.Name = $backupfile
                        $backup.Devices.Add($device)
                    }
                    $humanBackupFile = $FinalBackupPath -Join ','
                    Write-Message -Level Verbose -Message "Devices added"
                    $percent = [Microsoft.SqlServer.Management.Smo.PercentCompleteEventHandler] {
                        Write-Progress -id $ProgressId -activity "Backing up database $dbname to $humanBackupFile" -percentcomplete $_.Percent -status ([System.String]::Format("Progress: {0} %", $_.Percent))
                    }
                    $backup.add_PercentComplete($percent)
                    $backup.PercentCompleteNotification = 1
                    $backup.add_Complete($complete)
    
                    if ($MaxTransferSize) {
                        $backup.MaxTransferSize = $MaxTransferSize
                    }
                    if ($BufferCount) {
                        $backup.BufferCount = $BufferCount
                    }
                    if ($BlockSize) {
                        $backup.Blocksize = $BlockSize
                    }
    
                    Write-Progress -id $ProgressId -activity "Backing up database $dbname to $humanBackupFile" -percentcomplete 0 -status ([System.String]::Format("Progress: {0} %", 0))
    
                    try {
                        if ($Pscmdlet.ShouldProcess($server.Name, "Backing up $dbname to $humanBackupFile")) {
                            $CurrentLsn = (Read-DbaTransactionLog -SqlInstance $server -Database $dbname -RowLimit 1).'Current LSN'
                            $NumericLSN = (Convert-DbaLSN -LSN $CurrentLsn).Numeric
                            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 -LastLsn $NumericLsn  | 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" -ErrorRecord $_ -Continue
                            $BackupComplete = $false
                        }
                    }
                }
                $OutputExclude = 'FullName', 'FileList', 'SoftwareVersionMajor'
                if ($failures.Count -eq 0) {
                    $OutputExclude += ('Notes', 'FirstLsn', 'DatabaseBackupLsn', 'CheckpointLsn', 'LastLsn', 'BackupSetId', 'LastRecoveryForkGuid')
                }
                $headerinfo | Select-DefaultView -ExcludeProperty $OutputExclude
                $BackupFileName = $null
            }
        }
    }
    #ValidationTags#Messaging,FlowControl,Pipeline,CodeStyle#
    function Backup-DbaDbCertificate {
        <#
        .SYNOPSIS
            Exports database certificates from SQL Server using SMO.
    
        .DESCRIPTION
            Exports database certificates from SQL Server using SMO and outputs the .cer and .pvk files.
    
        .PARAMETER SqlInstance
            The target SQL Server instance or instances. This can be a collection and receive pipeline input to allow the function to be executed against multiple SQL Server instances.
    
        .PARAMETER SqlCredential
            Login to the target instance using alternative credentials. Windows and SQL Authentication supported. Accepts credential objects (Get-Credential)
    
        .PARAMETER Certificate
            Exports certificate that matches the name(s).
    
        .PARAMETER Database
            Exports the encryptor for specific database(s).
    
        .PARAMETER ExcludeDatabase
            Database(s) to skip when exporting encryptors.
    
        .PARAMETER EncryptionPassword
            A string value that specifies the system path to encrypt the private key.
    
        .PARAMETER DecryptionPassword
            A string value that specifies the system path to decrypt the private key.
    
        .PARAMETER Path
            The path to output the files to. The path is relative to the SQL Server itself. If no path is specified, the default data directory will be used.
    
        .PARAMETER Suffix
            The suffix of the filename of the exported certificate.
    
        .PARAMETER InputObject
            Enables piping from Get-DbaDbCertificate
    
        .PARAMETER Confirm
            If this switch is enabled, you will be prompted for confirmation before executing any operations that change state.
    
        .PARAMETER EnableException
            By default, when something goes wrong we try to catch it, interpret it and give you a friendly warning message.
            This avoids overwhelming you with "sea of red" exceptions, but is inconvenient because it basically disables advanced scripting.
            Using this switch turns this "nice by default" feature off and enables you to catch exceptions with your own try/catch.
    
        .PARAMETER WhatIf
            If this switch is enabled, no actions are performed but informational messages will be displayed that explain what would happen if the command were to run.
    
        .NOTES
            Tags: Migration, Certificate
            Author: Jess Pomfret (@jpomfret)
    
            Website: https://dbatools.io
            Copyright: (c) 2018 by dbatools, licensed under MIT
            License: MIT https://opensource.org/licenses/MIT
    
        .EXAMPLE
            PS C:\> Backup-DbaDbCertificate -SqlInstance Server1
    
            Exports all the certificates on the specified SQL Server to the default data path for the instance.
    
        .EXAMPLE
            PS C:\> $cred = Get-Credential sqladmin
            PS C:\> Backup-DbaDbCertificate -SqlInstance Server1 -SqlCredential $cred
    
            Connects using sqladmin credential and exports all the certificates on the specified SQL Server to the default data path for the instance.
    
        .EXAMPLE
            PS C:\> Backup-DbaDbCertificate -SqlInstance Server1 -Certificate Certificate1
    
            Exports only the certificate named Certificate1 on the specified SQL Server to the default data path for the instance.
    
        .EXAMPLE
            PS C:\> Backup-DbaDbCertificate -SqlInstance Server1 -Database AdventureWorks
    
            Exports only the certificates for AdventureWorks on the specified SQL Server to the default data path for the instance.
    
        .EXAMPLE
            PS C:\> Backup-DbaDbCertificate -SqlInstance Server1 -ExcludeDatabase AdventureWorks
    
            Exports all certificates except those for AdventureWorks on the specified SQL Server to the default data path for the instance.
    
        .EXAMPLE
            PS C:\> Backup-DbaDbCertificate -SqlInstance Server1 -Path \\Server1\Certificates -EncryptionPassword (ConvertTo-SecureString -force -AsPlainText GoodPass1234!!)
    
            Exports all the certificates and private keys on the specified SQL Server.
    
        .EXAMPLE
            PS C:\> $EncryptionPassword = ConvertTo-SecureString -AsPlainText "GoodPass1234!!" -force
            PS C:\> $DecryptionPassword = ConvertTo-SecureString -AsPlainText "Password4567!!" -force
            PS C:\> Backup-DbaDbCertificate -SqlInstance Server1 -EncryptionPassword $EncryptionPassword -DecryptionPassword $DecryptionPassword
    
            Exports all the certificates on the specified SQL Server using the supplied DecryptionPassword, since an EncryptionPassword is specified private keys are also exported.
    
        .EXAMPLE
            PS C:\> Backup-DbaDbCertificate -SqlInstance Server1 -Path \\Server1\Certificates
    
            Exports all certificates on the specified SQL Server to the specified path.
    
        .EXAMPLE
            PS C:\> Backup-DbaDbCertificate -SqlInstance Server1 -Suffix DbaTools
    
            Exports all certificates on the specified SQL Server to the specified path, appends DbaTools to the end of the filenames.
    
        .EXAMPLE
            PS C:\> Get-DbaDbCertificate -SqlInstance sql2016 | Backup-DbaDbCertificate
    
            Exports all certificates found on sql2016 to the default data directory.
    
        #>
        [CmdletBinding(DefaultParameterSetName = "Default", SupportsShouldProcess, ConfirmImpact = 'Low')]
        param (
            [parameter(Mandatory, ParameterSetName = "instance")]
            [Alias("ServerInstance", "SqlServer")]
            [DbaInstanceParameter[]]$SqlInstance,
            [PSCredential]$SqlCredential,
            [parameter(ParameterSetName = "instance")]
            [object[]]$Certificate,
            [parameter(ParameterSetName = "instance")]
            [object[]]$Database,
            [parameter(ParameterSetName = "instance")]
            [object[]]$ExcludeDatabase,
            [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 (-not $EncryptionPassword -and $DecryptionPassword) {
                Stop-Function -Message "If you specify a decryption password, you must also specify an encryption password" -Target $DecryptionPassword
            }
    
            Test-DbaDeprecation -DeprecatedOn "1.0.0" -Alias Backup-DbaDatabaseCertificate
    
            function export-cert ($cert) {
                $certName = $cert.Name
                $db = $cert.Parent
                $server = $db.Parent
                $instance = $server.Name
                $actualPath = $Path
    
                if ($null -eq $actualPath) {
                    $actualPath = Get-SqlDefaultPaths -SqlInstance $server -filetype Data
                }
    
                $actualPath = "$actualPath".TrimEnd('\')
                $fullCertName = "$actualPath\$certName$Suffix"
                $exportPathKey = "$fullCertName.pvk"
    
                if (!(Test-DbaPath -SqlInstance $server -Path $actualPath)) {
                    Stop-Function -Message "$SqlInstance cannot access $actualPath" -Target $actualPath
                }
    
                if ($Pscmdlet.ShouldProcess($instance, "Exporting certificate $certName from $db on $instance to $actualPath")) {
                    Write-Message -Level Verbose -Message "Exporting Certificate: $certName to $fullCertName"
                    try {
    
                        $exportPathCert = "$fullCertName.cer"
    
                        # because the password shouldn't go to memory...
                        if ($EncryptionPassword.Length -gt 0 -and $DecryptionPassword.Length -gt 0) {
    
                            Write-Message -Level Verbose -Message "Both passwords passed in. Will export both cer and pvk."
    
                            $cert.export(
                                $exportPathCert,
                                $exportPathKey,
                                [System.Runtime.InteropServices.marshal]::PtrToStringAuto([System.Runtime.InteropServices.marshal]::SecureStringToBSTR($EncryptionPassword)),
                                [System.Runtime.InteropServices.marshal]::PtrToStringAuto([System.Runtime.InteropServices.marshal]::SecureStringToBSTR($DecryptionPassword))
                            )
                        } elseif ($EncryptionPassword.Length -gt 0 -and $DecryptionPassword.Length -eq 0) {
                            Write-Message -Level Verbose -Message "Only encryption password passed in. Will export both cer and pvk."
    
                            $cert.export(
                                $exportPathCert,
                                $exportPathKey,
                                [System.Runtime.InteropServices.marshal]::PtrToStringAuto([System.Runtime.InteropServices.marshal]::SecureStringToBSTR($EncryptionPassword))
                            )
                        } else {
                            Write-Message -Level Verbose -Message "No passwords passed in. Will export just cer."
                            $exportPathKey = "Password required to export key"
                            $cert.export($exportPathCert)
                        }
    
                        [pscustomobject]@{
                            ComputerName   = $server.ComputerName
                            InstanceName   = $server.ServiceName
                            SqlInstance    = $server.DomainInstanceName
                            Database       = $db.Name
                            Certificate    = $certName
                            Path           = $exportPathCert
                            Key            = $exportPathKey
                            ExportPath     = $exportPathCert
                            ExportKey      = $exportPathKey
                            exportPathCert = $exportPathCert
                            exportPathKey  = $exportPathKey
                            Status         = "Success"
                        } | Select-DefaultView -ExcludeProperty exportPathCert, exportPathKey, ExportPath, ExportKey
                    } catch {
    
                        if ($_.Exception.InnerException) {
                            $exception = $_.Exception.InnerException.ToString() -Split "System.Data.SqlClient.SqlException: "
                            $exception = ($exception[1] -Split "at Microsoft.SqlServer.Management.Common.ConnectionManager")[0]
                        } else {
                            $exception = $_.Exception
                        }
                        [pscustomobject]@{
                            ComputerName   = $server.ComputerName
                            InstanceName   = $server.ServiceName
                            SqlInstance    = $server.DomainInstanceName
                            Database       = $db.Name
                            Certificate    = $certName
                            Path           = $exportPathCert
                            Key            = $exportPathKey
                            ExportPath     = $exportPathCert
                            ExportKey      = $exportPathKey
                            exportPathCert = $exportPathCert
                            exportPathKey  = $exportPathKey
                            Status         = "Failure: $exception"
                        } | Select-DefaultView -ExcludeProperty exportPathCert, exportPathKey, ExportPath, ExportKey
                        Stop-Function -Message "$certName from $db on $instance cannot be exported." -Continue -Target $cert -ErrorRecord $_
                    }
                }
            }
        }
    
        process {
            if (Test-FunctionInterrupt) { return }
    
            if ($SqlInstance) {
                $InputObject += Get-DbaDbCertificate -SqlInstance $SqlInstance -SqlCredential $SqlCredential -Database $Database -ExcludeDatabase $ExcludeDatabase
            }
    
            foreach ($cert in $InputObject) {
                if ($cert.Name.StartsWith("##")) {
                    Write-Message -Level Output -Message "Skipping system cert $cert"
                } else {
                    export-cert $cert
                }
            }
        }
    }
    #ValidationTags#Messaging,FlowControl,Pipeline,CodeStyle#
    function Backup-DbaDbMasterKey {
        <#
        .SYNOPSIS
            Backs up specified database master key.
    
        .DESCRIPTION
            Backs up specified database master key.
    
        .PARAMETER SqlInstance
            The target SQL Server instance or instances.
    
        .PARAMETER SqlCredential
            Allows you to login to SQL Server using alternative credentials.
    
        .PARAMETER Database
            Backup master key from specific database(s).
    
        .PARAMETER ExcludeDatabase
            The database(s) to exclude - this list is auto-populated from the server.
    
        .PARAMETER Path
            The directory to export the key. If no path is specified, the default backup directory for the instance will be used.
    
        .PARAMETER Credential
            Pass a credential object for the password
    
        .PARAMETER Password
            The password to encrypt the exported key. This must be a SecureString.
    
        .PARAMETER InputObject
            Database object piped in from Get-DbaDatabase
    
        .PARAMETER WhatIf
            Shows what would happen if the command were to run. No actions are actually performed.
    
        .PARAMETER Confirm
            Prompts you for confirmation before executing any changing operations within the command.
    
        .PARAMETER EnableException
            By default, when something goes wrong we try to catch it, interpret it and give you a friendly warning message.
            This avoids overwhelming you with "sea of red" exceptions, but is inconvenient because it basically disables advanced scripting.
            Using this switch turns this "nice by default" feature off and enables you to catch exceptions with your own try/catch.
    
        .NOTES
            Tags: Certificate, Database
            Author: Chrissy LeMaire (@cl), netnerds.net
    
            Website: https://dbatools.io
            Copyright: (c) 2018 by dbatools, licensed under MIT
            License: MIT https://opensource.org/licenses/MIT
    
        .EXAMPLE
            PS C:\> Backup-DbaDbMasterKey -SqlInstance server1\sql2016
            ```
            ComputerName : SERVER1
            InstanceName : SQL2016
            SqlInstance  : SERVER1\SQL2016
            Database     : master
            Filename     : E:\MSSQL13.SQL2016\MSSQL\Backup\server1$sql2016-master-20170614162311.key
            Status       : Success
            ```
    
            Prompts for export password, then logs into server1\sql2016 with Windows credentials then backs up all database keys to the default backup directory.
    
        .EXAMPLE
            PS C:\> Backup-DbaDbMasterKey -SqlInstance Server1 -Database db1 -Path \\nas\sqlbackups\keys
    
            Logs into sql2016 with Windows credentials then backs up db1's keys to the \\nas\sqlbackups\keys directory.
    
        #>
        [CmdletBinding(SupportsShouldProcess, ConfirmImpact = 'Low')]
        param (
            [DbaInstanceParameter[]]$SqlInstance,
            [PSCredential]$SqlCredential,
            [PSCredential]$Credential,
            [string[]]$Database,
            [string[]]$ExcludeDatabase,
            [Security.SecureString]$Password,
            [string]$Path,
            [parameter(ValueFromPipeline)]
            [Microsoft.SqlServer.Management.Smo.Database[]]$InputObject,
            [switch]$EnableException
        )
        begin {
            if ($Credential) {
                $Password = $Credential.Password
            }
        }
        process {
            foreach ($instance in $SqlInstance) {
                $InputObject += Get-DbaDatabase -SqlInstance $SqlInstance -SqlCredential $SqlCredential -Database $Database -ExcludeDatabase $ExcludeDatabase
            }
    
            foreach ($db in $InputObject) {
                $server = $db.Parent
    
                if (Test-Bound -ParameterName Path -Not) {
                    $Path = $server.BackupDirectory
                }
    
                if (-not $Path) {
                    Stop-Function -Message "Path discovery failed. Please explicitly specify -Path" -Target $server -Continue
                }
    
                if (!(Test-DbaPath -SqlInstance $server -Path $Path)) {
                    Stop-Function -Message "$instance cannot access $Path" -Target $server -ErrorRecord $_ -Continue
                }
    
                if (!$db.IsAccessible) {
                    Write-Message -Level Warning -Message "Database $db is not accessible. Skipping."
                    continue
                }
    
                $masterkey = $db.MasterKey
    
                if (!$masterkey) {
                    Write-Message -Message "No master key exists in the $db database on $instance" -Target $db -Level Verbose
                    continue
                }
    
                # If you pass a password param, then you will not be prompted for each database, but it wouldn't be a good idea to build in insecurity
                if (-not $Password -and -not $Credential) {
                    $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"
    
                if ($Pscmdlet.ShouldProcess($instance, "Backing up master key to $filename")) {
                    try {
                        $masterkey.Export($filename, [System.Runtime.InteropServices.marshal]::PtrToStringAuto([System.Runtime.InteropServices.marshal]::SecureStringToBSTR($password)))
                        $status = "Success"
                    } catch {
                        $status = "Failure"
                        Write-Message -Level Warning -Message "Backup failure: $($_.Exception.InnerException)"
                    }
    
                    Add-Member -Force -InputObject $masterkey -MemberType NoteProperty -Name ComputerName -value $server.ComputerName
                    Add-Member -Force -InputObject $masterkey -MemberType NoteProperty -Name InstanceName -value $server.ServiceName
                    Add-Member -Force -InputObject $masterkey -MemberType NoteProperty -Name SqlInstance -value $server.DomainInstanceName
                    Add-Member -Force -InputObject $masterkey -MemberType NoteProperty -Name Database -value $dbname
                    Add-Member -Force -InputObject $masterkey -MemberType NoteProperty -Name Filename -value $filename
                    Add-Member -Force -InputObject $masterkey -MemberType NoteProperty -Name Status -value $status
    
                    Select-DefaultView -InputObject $masterkey -Property ComputerName, InstanceName, SqlInstance, Database, 'Filename as Path', Status
                }
            }
        }
        end {
            Test-DbaDeprecation -DeprecatedOn "1.0.0" -EnableException:$false -Alias Backup-DbaDatabaseMasterKey
        }
    }
    function Clear-DbaConnectionPool {
        <#
        .SYNOPSIS
            Resets (or empties) the connection pool.
    
        .DESCRIPTION
            This command resets (or empties) the connection pool.
    
            If there are connections in use at the time of the call, they are marked appropriately and will be discarded (instead of being returned to the pool) when Close() is called on them.
    
            Ref: https://msdn.microsoft.com/en-us/library/system.data.sqlclient.sqlconnection.clearallpools(v=vs.110).aspx
    
        .PARAMETER ComputerName
            Target computer(s). If no computer name is specified, the local computer is targeted.
    
        .PARAMETER Credential
            Alternate credential object to use for accessing the target computer(s).
    
        .PARAMETER EnableException
            By default, when something goes wrong we try to catch it, interpret it and give you a friendly warning message.
            This avoids overwhelming you with "sea of red" exceptions, but is inconvenient because it basically disables advanced scripting.
            Using this switch turns this "nice by default" feature off and enables you to catch exceptions with your own try/catch.
    
        .NOTES
            Tags: Connection
            Author: Chrissy LeMaire (@cl), netnerds.net
    
            Website: https://dbatools.io
            Copyright: (c) 2018 by dbatools, licensed under MIT
            License: MIT https://opensource.org/licenses/MIT
    
        .LINK
            https://dbatools.io/Clear-DbaConnectionPool
    
        .EXAMPLE
            PS C:\> Clear-DbaConnectionPool
    
            Clears all local connection pools.
    
        .EXAMPLE
            PS C:\> Clear-DbaConnectionPool -ComputerName workstation27
    
            Clears all connection pools on workstation27.
    
        #>
        [CmdletBinding()]
        param (
            [Parameter(ValueFromPipeline)]
            [Alias("cn", "host", "Server")]
            [DbaInstanceParameter[]]$ComputerName = $env:COMPUTERNAME,
            [PSCredential]$Credential,
            [switch][Alias('Silent')]
            $EnableException
        )
    
        process {
            # TODO: https://jamessdixon.wordpress.com/2013/01/22/ado-net-and-connection-pooling
    
            foreach ($computer in $ComputerName) {
                try {
                    if (-not $computer.IsLocalhost) {
                        Write-Message -Level Verbose -Message "Clearing all pools on remote computer $computer"
                        if (Test-Bound 'Credential') {
                            Invoke-Command2 -ComputerName $computer -Credential $Credential -ScriptBlock { [System.Data.SqlClient.SqlConnection]::ClearAllPools() }
                        } else {
                            Invoke-Command2 -ComputerName $computer -ScriptBlock { [System.Data.SqlClient.SqlConnection]::ClearAllPools() }
                        }
                    } else {
                        Write-Message -Level Verbose -Message "Clearing all local pools"
                        if (Test-Bound 'Credential') {
                            Invoke-Command2 -Credential $Credential -ScriptBlock { [System.Data.SqlClient.SqlConnection]::ClearAllPools() }
                        } else {
                            Invoke-Command2 -ScriptBlock { [System.Data.SqlClient.SqlConnection]::ClearAllPools() }
                        }
                    }
                } catch {
                    Stop-Function -Message "Failure" -ErrorRecord $_ -Target $computer -Continue
                }
            }
        }
        end {
            Test-DbaDeprecation -DeprecatedOn "1.0.0" -EnableException:$false -Alias Clear-DbaSqlConnectionPool
        }
    }
    function Clear-DbaLatchStatistics {
        <#
        .SYNOPSIS
            Clears Latch Statistics
    
        .DESCRIPTION
            Reset the aggregated statistics - basically just executes DBCC SQLPERF (N'sys.dm_os_latch_stats', CLEAR)
    
        .PARAMETER SqlInstance
            Allows you to specify a comma separated list of servers to query.
    
        .PARAMETER SqlCredential
            Login to the target instance using alternative credentials. Windows and SQL Authentication supported. Accepts credential objects (Get-Credential)
    
        .PARAMETER WhatIf
            If this switch is enabled, no actions are performed but informational messages will be displayed that explain what would happen if the command were to run.
    
        .PARAMETER Confirm
            If this switch is enabled, you will be prompted for confirmation before executing any operations that change state.
    
        .PARAMETER EnableException
            By default, when something goes wrong we try to catch it, interpret it and give you a friendly warning message.
            This avoids overwhelming you with "sea of red" exceptions, but is inconvenient because it basically disables advanced scripting.
            Using this switch turns this "nice by default" feature off and enables you to catch exceptions with your own try/catch.
    
        .NOTES
            Tags: LatchStatistic, Waits
            Author: Patrick Flynn (@sqllensman)
    
            Website: https://dbatools.io
            Copyright: (c) 2018 by dbatools, licensed under MIT
            License: MIT https://opensource.org/licenses/MIT
    
        .LINK
            https://dbatools.io/Clear-DbaLatchStatistics
    
        .EXAMPLE
            PS C:\> Clear-DbaLatchStatistics -SqlInstance sql2008, sqlserver2012
    
            After confirmation, clears latch statistics on servers sql2008 and sqlserver2012
    
        .EXAMPLE
            PS C:\> Clear-DbaLatchStatistics -SqlInstance sql2008, sqlserver2012 -Confirm:$false
    
            Clears latch statistics on servers sql2008 and sqlserver2012, without prompting
    
        .EXAMPLE
            PS C:\> 'sql2008','sqlserver2012' | Clear-DbaLatchStatistics
    
            After confirmation, clears latch statistics on servers sql2008 and sqlserver2012
    
        .EXAMPLE
            PS C:\> $cred = Get-Credential sqladmin
            PS C:\> Clear-DbaLatchStatistics -SqlInstance sql2008 -SqlCredential $cred
    
            Connects using sqladmin credential and clears latch statistics on servers sql2008 and sqlserver2012
        #>
        [CmdletBinding(ConfirmImpact = 'High', SupportsShouldProcess)]
        [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUseSingularNouns", "", Justification = "Singular Noun doesn't make sense")]
        param (
            [parameter(Mandatory, ValueFromPipeline)]
            [Alias("ServerInstance", "SqlServer", "SqlServers")]
            [DbaInstance[]]$SqlInstance,
            [PSCredential]$SqlCredential,
            [Alias('Silent')]
            [switch]$EnableException
        )
        process {
            foreach ($instance in $SqlInstance) {
                Write-Message -Level Verbose -Message "Attempting to connect to $instance"
    
                try {
                    $server = Connect-SqlInstance -SqlInstance $instance -SqlCredential $SqlCredential -MinimumVersion 9
                } catch {
                    Stop-Function -Message "Failure" -Category ConnectionError -ErrorRecord $_ -Target $instance -Continue
                }
    
                if ($Pscmdlet.ShouldProcess($instance, "Performing CLEAR of sys.dm_os_latch_stats")) {
                    try {
                        $server.Query("DBCC SQLPERF (N'sys.dm_os_latch_stats' , CLEAR);")
                        $status = "Success"
                    } catch {
                        $status = $_.Exception
                    }
    
                    [PSCustomObject]@{
                        ComputerName = $server.NetName
                        InstanceName = $server.ServiceName
                        SqlInstance  = $server.DomainInstanceName
                        Status       = $status
                    }
                }
            }
        }
    }
    function Clear-DbaPlanCache {
        <#
        .SYNOPSIS
            Removes ad-hoc and prepared plan caches is single use plans are over defined threshold.
    
        .DESCRIPTION
            Checks ad-hoc and prepared plan cache for each database, if over 100 MBs removes from the cache.
    
            This command automates that process.
    
            References: https://www.sqlskills.com/blogs/kimberly/plan-cache-adhoc-workloads-and-clearing-the-single-use-plan-cache-bloat/
    
        .PARAMETER SqlInstance
            The target SQL Server instance or instances.
    
        .PARAMETER SqlCredential
            Login to the target instance using alternative credentials. Windows and SQL Authentication supported. Accepts credential objects (Get-Credential)
    
        .PARAMETER Threshold
            Memory used threshold.
    
        .PARAMETER InputObject
            Enables results to be piped in from Get-DbaPlanCache.
    
        .PARAMETER WhatIf
            If this switch is enabled, no actions are performed but informational messages will be displayed that explain what would happen if the command were to run.
    
        .PARAMETER Confirm
            If this switch is enabled, you will be prompted for confirmation before executing any operations that change state.
    
        .PARAMETER EnableException
            By default, when something goes wrong we try to catch it, interpret it and give you a friendly warning message.
            This avoids overwhelming you with "sea of red" exceptions, but is inconvenient because it basically disables advanced scripting.
            Using this switch turns this "nice by default" feature off and enables you to catch exceptions with your own try/catch.
    
        .NOTES
            Tags: Memory
            Author: Tracy Boggiano, databasesuperhero.com
    
            Website: https://dbatools.io
            Copyright: (c) 2018 by dbatools, licensed under MIT
            License: MIT https://opensource.org/licenses/MIT
    
        .LINK
            https://dbatools.io/Clear-DbaPlanCache
    
        .EXAMPLE
            PS C:\> Clear-DbaPlanCache -SqlInstance sql2017 -Threshold 200
    
            Logs into the SQL Server instance "sql2017" and removes plan caches if over 200 MB.
    
        .EXAMPLE
            PS C:\> Clear-DbaPlanCache -SqlInstance sql2017 -SqlCredential sqladmin
    
            Logs into the SQL instance using the SQL Login 'sqladmin' and then Windows instance as 'ad\sqldba'
            and removes if Threshold over 100 MB.
    
        .EXAMPLE
            PS C:\> Find-DbaInstance -ComputerName localhost | Get-DbaPlanCache | Clear-DbaPlanCache -Threshold 200
    
            Scans localhost for instances using the browser service, traverses all instances and gets the plan cache for each, clears them out if they are above 200 MB.
        #>
        [CmdletBinding(SupportsShouldProcess)]
        param (
            [Alias("ServerInstance", "SqlServer", "SqlServers")]
            [DbaInstanceParameter[]]$SqlInstance,
            [PSCredential]$SqlCredential,
            [int]$Threshold = 100,
            [parameter(ValueFromPipeline)]
            [object[]]$InputObject,
            [switch]$EnableException
        )
        process {
            foreach ($instance in $SqlInstance) {
                $InputObject += Get-DbaPlanCache -SqlInstance $instance -SqlCredential $SqlCredential
            }
    
            foreach ($result in $InputObject) {
                if ($result.MB -ge $Threshold) {
                    if ($Pscmdlet.ShouldProcess($($result.SqlInstance), "Cleared SQL Plans plan cache")) {
                        try {
                            $server = Connect-SqlInstance -SqlInstance $result.SqlInstance -SqlCredential $SqlCredential
                        } catch {
                            Stop-Function -Message "Failure" -Category ConnectionError -ErrorRecord $_ -Target $instance -Continue
                        }
    
                        $server.Query("DBCC FREESYSTEMCACHE('SQL Plans')")
                        [PSCustomObject]@{
                            ComputerName = $result.ComputerName
                            InstanceName = $result.InstanceName
                            SqlInstance  = $result.SqlInstance
                            Size         = $result.Size
                            Status       = "Plan cache cleared"
                        }
                    }
                } else {
                    if ($Pscmdlet.ShouldProcess($($result.SqlInstance), "Results $($result.Size) below threshold")) {
                        [PSCustomObject]@{
                            ComputerName = $result.ComputerName
                            InstanceName = $result.InstanceName
                            SqlInstance  = $result.SqlInstance
                            Size         = $result.Size
                            Status       = "Plan cache size below threshold ($Threshold) "
                        }
                        Write-Message -Level Verbose -Message "Plan cache size below threshold ($Threshold) "
                    }
                }
            }
        }
    }
    function Clear-DbaWaitStatistics {
        <#
        .SYNOPSIS
            Clears wait statistics
    
        .DESCRIPTION
            Reset the aggregated statistics - basically just executes DBCC SQLPERF (N'sys.dm_os_wait_stats', CLEAR)
    
        .PARAMETER SqlInstance
            The target SQL Server instance or instances.
    
        .PARAMETER SqlCredential
            Login to the target instance using alternative credentials. Windows and SQL Authentication supported. Accepts credential objects (Get-Credential)
    
        .PARAMETER WhatIf
            If this switch is enabled, no actions are performed but informational messages will be displayed that explain what would happen if the command were to run.
    
        .PARAMETER Confirm
            If this switch is enabled, you will be prompted for confirmation before executing any operations that change state.
    
        .PARAMETER EnableException
            By default, when something goes wrong we try to catch it, interpret it and give you a friendly warning message.
            This avoids overwhelming you with "sea of red" exceptions, but is inconvenient because it basically disables advanced scripting.
            Using this switch turns this "nice by default" feature off and enables you to catch exceptions with your own try/catch.
    
        .NOTES
            Tags: WaitStatistic, Waits
            Author: Chrissy LeMaire (@cl)
    
            Website: https://dbatools.io
            Copyright: (c) 2018 by dbatools, licensed under MIT
            License: MIT https://opensource.org/licenses/MIT
    
        .LINK
            https://dbatools.io/Clear-DbaWaitStatistics
    
        .EXAMPLE
            PS C:\> Clear-DbaWaitStatistics -SqlInstance sql2008, sqlserver2012
    
            After confirmation, clears wait stats on servers sql2008 and sqlserver2012
    
        .EXAMPLE
            PS C:\> Clear-DbaWaitStatistics -SqlInstance sql2008, sqlserver2012 -Confirm:$false
    
            Clears wait stats on servers sql2008 and sqlserver2012, without prompting
    
        #>
        [CmdletBinding(ConfirmImpact = 'High', SupportsShouldProcess)]
        [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUseSingularNouns", "", Justification = "Singular Noun doesn't make sense")]
        param (
            [parameter(Mandatory, ValueFromPipeline)]
            [Alias("ServerInstance", "SqlServer", "SqlServers")]
            [DbaInstance[]]$SqlInstance,
            [PSCredential]$SqlCredential,
            [Alias('Silent')]
            [switch]$EnableException
        )
        process {
            foreach ($instance in $SqlInstance) {
    
                try {
                    $server = Connect-SqlInstance -SqlInstance $instance -SqlCredential $SqlCredential -MinimumVersion 9
                } catch {
                    Stop-Function -Message "Failure" -Category ConnectionError -ErrorRecord $_ -Target $instance -Continue
                }
    
                if ($Pscmdlet.ShouldProcess($instance, "Performing CLEAR of sys.dm_os_wait_stats")) {
                    try {
                        $server.Query("DBCC SQLPERF (N'sys.dm_os_wait_stats', CLEAR);")
                        $status = "Success"
                    } catch {
                        $status = $_.Exception
                    }
    
                    [PSCustomObject]@{
                        ComputerName = $server.ComputerName
                        InstanceName = $server.ServiceName
                        SqlInstance  = $server.DomainInstanceName
                        Status       = $status
                    }
                }
            }
        }
    }
    function Connect-DbaInstance {
        <#
        .SYNOPSIS
            Creates a robust SMO SQL Server object.
    
        .DESCRIPTION
            This command is robust because it initializes properties that do not cause enumeration by default. It also supports both Windows and SQL Server authentication methods, and detects which to use based upon the provided credentials.
    
            By default, this command also sets the connection's ApplicationName property  to "dbatools PowerShell module - dbatools.io - custom connection". If you're doing anything that requires profiling, you can look for this client name.
    
            Alternatively, you can pass in whichever client name you'd like using the -ClientName parameter. There are a ton of other parameters for you to explore as well.
    
            See https://msdn.microsoft.com/en-us/library/system.data.sqlclient.sqlconnection.connectionstring.aspx
            and https://msdn.microsoft.com/en-us/library/system.data.sqlclient.sqlconnectionstringbuilder.aspx,
            and https://msdn.microsoft.com/en-us/library/system.data.sqlclient.sqlconnection.aspx
    
            To execute SQL commands, you can use $server.ConnectionContext.ExecuteReader($sql) or $server.Databases['master'].ExecuteNonQuery($sql)
    
        .PARAMETER SqlInstance
            The target SQL Server instance or instances. This can be a collection and receive pipeline input to allow the function to be executed against multiple SQL Server instances.
    
        .PARAMETER Credential
            Credential object used to connect to the SQL Server Instance as a different user. This can be a Windows or SQL Server account. Windows users are determined by the existence of a backslash, so if you are intending to use an alternative Windows connection instead of a SQL login, ensure it contains a backslash.
    
        .PARAMETER Database
            The database(s) to process. This list is auto-populated from the server.
    
        .PARAMETER AccessToken
            Gets or sets the access token for the connection.
    
        .PARAMETER AppendConnectionString
            Appends to the current connection string. Note that you cannot pass authentication information using this method. Use -SqlInstance and optionally -SqlCredential to set authentication information.
    
        .PARAMETER ApplicationIntent
            Declares the application workload type when connecting to a server.
    
            Valid values are "ReadOnly" and "ReadWrite".
    
        .PARAMETER BatchSeparator
            A string to separate groups of SQL statements being executed. By default, this is "GO".
    
        .PARAMETER ClientName
            By default, this command sets the client's ApplicationName property to "dbatools PowerShell module - dbatools.io - custom connection" if you're doing anything that requires profiling, you can look for this client name. Using -ClientName allows you to set your own custom client application name.
    
        .PARAMETER ConnectTimeout
            The length of time (in seconds) to wait for a connection to the server before terminating the attempt and generating an error.
    
            Valid values are integers between 0 and 2147483647.
    
            When opening a connection to a Azure SQL Database, set the connection timeout to 30 seconds.
    
        .PARAMETER EncryptConnection
            If this switch is enabled, SQL Server uses SSL encryption for all data sent between the client and server if the server has a certificate installed.
    
            For more information, see Connection String Syntax. https://docs.microsoft.com/en-us/dotnet/framework/data/adonet/connection-string-syntax
    
            Beginning in .NET Framework 4.5, when TrustServerCertificate is false and Encrypt is true, the server name (or IP address) in a SQL Server SSL certificate must exactly match the server name (or IP address) specified in the connection string. Otherwise, the connection attempt will fail. For information about support for certificates whose subject starts with a wildcard character (*), see Accepted wildcards used by server certificates for server authentication. https://support.microsoft.com/en-us/help/258858/accepted-wildcards-used-by-server-certificates-for-server-authenticati
    
        .PARAMETER FailoverPartner
            The name of the failover partner server where database mirroring is configured.
    
            If the value of this key is "" (an empty string), then Initial Catalog must be present in the connection string, and its value must not be "".
    
            The server name can be 128 characters or less.
    
            If you specify a failover partner but the failover partner server is not configured for database mirroring and the primary server (specified with the Server keyword) is not available, then the connection will fail.
    
            If you specify a failover partner and the primary server is not configured for database mirroring, the connection to the primary server (specified with the Server keyword) will succeed if the primary server is available.
    
        .PARAMETER IsActiveDirectoryUniversalAuth
            If this switch is enabled, the connection will be configured to use Azure Active Directory authentication.
    
        .PARAMETER LockTimeout
            Sets the time in seconds required for the connection to time out when the current transaction is locked.
    
        .PARAMETER MaxPoolSize
            Sets the maximum number of connections allowed in the connection pool for this specific connection string.
    
        .PARAMETER MinPoolSize
            Sets the minimum number of connections allowed in the connection pool for this specific connection string.
    
        .PARAMETER MultipleActiveResultSets
            If this switch is enabled, an application can maintain multiple active result sets (MARS).
    
            If this switch is not enabled, an application must process or cancel all result sets from one batch before it can execute any other batch on that connection.
    
        .PARAMETER MultiSubnetFailover
            If this switch is enabled, and your application is connecting to an AlwaysOn availability group (AG) on different subnets, detection of and connection to the currently active server will be faster. For more information about SqlClient support for Always On Availability Groups, see https://docs.microsoft.com/en-us/dotnet/framework/data/adonet/sql/sqlclient-support-for-high-availability-disaster-recovery
    
        .PARAMETER NetworkProtocol
            Explicitly sets the network protocol used to connect to the server.
    
            Valid values are "TcpIp","NamedPipes","Multiprotocol","AppleTalk","BanyanVines","Via","SharedMemory" and "NWLinkIpxSpx"
    
        .PARAMETER NonPooledConnection
            If this switch is enabled, a non-pooled connection will be requested.
    
        .PARAMETER PacketSize
            Sets the size in bytes of the network packets used to communicate with an instance of SQL Server. Must match at server.
    
        .PARAMETER PooledConnectionLifetime
            When a connection is returned to the pool, its creation time is compared with the current time and the connection is destroyed if that time span (in seconds) exceeds the value specified by Connection Lifetime. This is useful in clustered configurations to force load balancing between a running server and a server just brought online.
    
            A value of zero (0) causes pooled connections to have the maximum connection timeout.
    
        .PARAMETER SqlExecutionModes
            The SqlExecutionModes enumeration contains values that are used to specify whether the commands sent to the referenced connection to the server are executed immediately or saved in a buffer.
    
            Valid values include "CaptureSql", "ExecuteAndCaptureSql" and "ExecuteSql".
    
        .PARAMETER StatementTimeout
            Sets the number of seconds a statement is given to run before failing with a timeout error.
    
        .PARAMETER TrustServerCertificate
            When this switch is enabled, the channel will be encrypted while bypassing walking the certificate chain to validate trust.
    
        .PARAMETER WorkstationId
            Sets the name of the workstation connecting to SQL Server.
    
        .PARAMETER SqlConnectionOnly
            Instead of returning a rich SMO server object, this command will only return a SqlConnection object when setting this switch.
    
        .NOTES
            Tags: Connect, Connection
            Author: Chrissy LeMaire (@cl), netnerds.net
    
            Website: https://dbatools.io
            Copyright: (c) 2018 by dbatools, licensed under MIT
            License: MIT https://opensource.org/licenses/MIT
    
        .LINK
            https://dbatools.io/Connect-DbaInstance
    
        .EXAMPLE
            PS C:\> Connect-DbaInstance -SqlInstance sql2014
    
            Creates an SMO Server object that connects using Windows Authentication
    
        .EXAMPLE
            PS C:\> $wincred = Get-Credential ad\sqladmin
            PS C:\> Connect-DbaInstance -SqlInstance sql2014 -Credential $wincred
    
            Creates an SMO Server object that connects using alternative Windows credentials
    
        .EXAMPLE
            PS C:\> $sqlcred = Get-Credential sqladmin
            PS C:\> $server = Connect-DbaInstance -SqlInstance sql2014 -Credential $sqlcred
    
            Login to sql2014 as SQL login sqladmin.
    
        .EXAMPLE
            PS C:\> $server = Connect-DbaInstance -SqlInstance sql2014 -ClientName "my connection"
    
            Creates an SMO Server object that connects using Windows Authentication and uses the client name "my connection". So when you open up profiler or use extended events, you can search for "my connection".
    
        .EXAMPLE
            PS C:\> $server = Connect-DbaInstance -SqlInstance sql2014 -AppendConnectionString "Packet Size=4096;AttachDbFilename=C:\MyFolder\MyDataFile.mdf;User Instance=true;"
    
            Creates an SMO Server object that connects to sql2014 using Windows Authentication, then it sets the packet size (this can also be done via -PacketSize) and other connection attributes.
    
        .EXAMPLE
            PS C:\> $server = Connect-DbaInstance -SqlInstance sql2014 -NetworkProtocol TcpIp -MultiSubnetFailover
    
            Creates an SMO Server object that connects using Windows Authentication that uses TCP/IP and has MultiSubnetFailover enabled.
    
        .EXAMPLE
            PS C:\> $server = Connect-DbaInstance sql2016 -ApplicationIntent ReadOnly
    
            Connects with ReadOnly ApplicationIntent.
    
        #>
        [CmdletBinding()]
        param (
            [Parameter(Mandatory, ValueFromPipeline)]
            [Alias("ServerInstance", "SqlServer")]
            [DbaInstanceParameter[]]$SqlInstance,
            [Alias("SqlCredential")]
            [PSCredential]$Credential,
            [object[]]$Database,
            [string]$AccessToken,
            [ValidateSet('ReadOnly', 'ReadWrite')]
            [string]$ApplicationIntent,
            [string]$BatchSeparator,
            [string]$ClientName = "dbatools PowerShell module - dbatools.io - custom connection",
            [int]$ConnectTimeout = ([Sqlcollaborative.Dbatools.Connection.ConnectionHost]::SqlConnectionTimeout),
            [switch]$EncryptConnection,
            [string]$FailoverPartner,
            [switch]$IsActiveDirectoryUniversalAuth,
            [int]$LockTimeout,
            [int]$MaxPoolSize,
            [int]$MinPoolSize,
            [switch]$MultipleActiveResultSets,
            [switch]$MultiSubnetFailover,
            [ValidateSet('TcpIp', 'NamedPipes', 'Multiprotocol', 'AppleTalk', 'BanyanVines', 'Via', 'SharedMemory', 'NWLinkIpxSpx')]
            [string]$NetworkProtocol,
            [switch]$NonPooledConnection,
            [int]$PacketSize,
            [int]$PooledConnectionLifetime,
            [ValidateSet('CaptureSql', 'ExecuteAndCaptureSql', 'ExecuteSql')]
            [string]$SqlExecutionModes,
            [int]$StatementTimeout,
            [switch]$TrustServerCertificate,
            [string]$WorkstationId,
            [string]$AppendConnectionString,
            [switch]$SqlConnectionOnly
        )
        begin {
            Test-DbaDeprecation -DeprecatedOn "1.0.0" -EnableException:$false -Alias Connect-DbaServer
            Test-DbaDeprecation -DeprecatedOn "1.0.0" -EnableException:$false -Alias Get-DbaInstance
    
            $loadedSmoVersion = [AppDomain]::CurrentDomain.GetAssemblies() | Where-Object { $_.Fullname -like "Microsoft.SqlServer.SMO,*" }
    
            if ($loadedSmoVersion) {
                $loadedSmoVersion = $loadedSmoVersion | ForEach-Object {
                    if ($_.Location -match "__") {
                        ((Split-Path (Split-Path $_.Location) -Leaf) -split "__")[0]
                    } else {
                        ((Get-ChildItem -Path $_.Location).VersionInfo.ProductVersion)
                    }
                }
            }
            #'PrimaryFilePath' seems the culprit for slow SMO on databases
            $Fields2000_Db = 'Collation', 'CompatibilityLevel', 'CreateDate', 'ID', 'IsAccessible', 'IsFullTextEnabled', 'IsSystemObject', 'IsUpdateable', 'LastBackupDate', 'LastDifferentialBackupDate', 'LastLogBackupDate', 'Name', 'Owner', 'ReadOnly', 'RecoveryModel', 'ReplicationOptions', 'Status', 'Version'
            $Fields200x_Db = $Fields2000_Db + @('BrokerEnabled', 'DatabaseSnapshotBaseName', 'IsMirroringEnabled', 'Trustworthy')
            $Fields201x_Db = $Fields200x_Db + @('ActiveConnections', 'AvailabilityDatabaseSynchronizationState', 'AvailabilityGroupName', 'ContainmentType', 'EncryptionEnabled')
    
            $Fields2000_Login = 'CreateDate' , 'DateLastModified' , 'DefaultDatabase' , 'DenyWindowsLogin' , 'IsSystemObject' , 'Language' , 'LanguageAlias' , 'LoginType' , 'Name' , 'Sid' , 'WindowsLoginAccessType'
            $Fields200x_Login = $Fields2000_Login + @('AsymmetricKey', 'Certificate', 'Credential', 'ID', 'IsDisabled', 'IsLocked', 'IsPasswordExpired', 'MustChangePassword', 'PasswordExpirationEnabled', 'PasswordPolicyEnforced')
            $Fields201x_Login = $Fields200x_Login + @('PasswordHashAlgorithm')
    
    
        }
        process {
            foreach ($instance in $SqlInstance) {
                if ($instance.Type -like "Server") {
                    if ($instance.InputObject.ConnectionContext.IsOpen -eq $false) {
                        $instance.InputObject.ConnectionContext.Connect()
                    }
                    if ($SqlConnectionOnly) { return $instance.InputObject.ConnectionContext.SqlConnectionObject }
                    else { return $instance.InputObject }
                }
                if ($instance.Type -like "SqlConnection") {
                    $server = New-Object Microsoft.SqlServer.Management.Smo.Server($instance.InputObject)
    
                    if ($server.ConnectionContext.IsOpen -eq $false) {
                        $server.ConnectionContext.Connect()
                    }
                    if ($SqlConnectionOnly) { return $server.ConnectionContext.SqlConnectionObject }
                    else {
                        if (-not $server.ComputerName) {
                            $parsedcomputername = $server.NetName
                            if (-not $parsedcomputername) {
                                $parsedcomputername = ([dbainstance]$instance).ComputerName
                            }
                            Add-Member -InputObject $server -NotePropertyName ComputerName -NotePropertyValue $parsedcomputername -Force
                        }
                        return $server
                    }
                }
    
                if ($instance.IsConnectionString) { $server = New-Object Microsoft.SqlServer.Management.Smo.Server($instance.InputObject) }
                else { $server = New-Object Microsoft.SqlServer.Management.Smo.Server $instance.FullSmoName }
    
                if ($AppendConnectionString) {
                    $connstring = $server.ConnectionContext.ConnectionString
                    $server.ConnectionContext.ConnectionString = "$connstring;$appendconnectionstring"
                    $server.ConnectionContext.Connect()
                } else {
    
                    $server.ConnectionContext.ApplicationName = $ClientName
    
                    if (Test-Bound -ParameterName 'AccessToken') { $server.ConnectionContext.AccessToken = $AccessToken }
                    if (Test-Bound -ParameterName 'BatchSeparator') { $server.ConnectionContext.BatchSeparator = $BatchSeparator }
                    if (Test-Bound -ParameterName 'ConnectTimeout') { $server.ConnectionContext.ConnectTimeout = $ConnectTimeout }
                    if (Test-Bound -ParameterName 'Database') { $server.ConnectionContext.DatabaseName = $Database }
                    if (Test-Bound -ParameterName 'EncryptConnection') { $server.ConnectionContext.EncryptConnection = $true }
                    if (Test-Bound -ParameterName 'IsActiveDirectoryUniversalAuth') { $server.ConnectionContext.IsActiveDirectoryUniversalAuth = $true }
                    if (Test-Bound -ParameterName 'LockTimeout') { $server.ConnectionContext.LockTimeout = $LockTimeout }
                    if (Test-Bound -ParameterName 'MaxPoolSize') { $server.ConnectionContext.MaxPoolSize = $MaxPoolSize }
                    if (Test-Bound -ParameterName 'MinPoolSize') { $server.ConnectionContext.MinPoolSize = $MinPoolSize }
                    if (Test-Bound -ParameterName 'MultipleActiveResultSets') { $server.ConnectionContext.MultipleActiveResultSets = $true }
                    if (Test-Bound -ParameterName 'NetworkProtocol') { $server.ConnectionContext.NetworkProtocol = $NetworkProtocol }
                    if (Test-Bound -ParameterName 'NonPooledConnection') { $server.ConnectionContext.NonPooledConnection = $true }
                    if (Test-Bound -ParameterName 'PacketSize') { $server.ConnectionContext.PacketSize = $PacketSize }
                    if (Test-Bound -ParameterName 'PooledConnectionLifetime') { $server.ConnectionContext.PooledConnectionLifetime = $PooledConnectionLifetime }
                    if (Test-Bound -ParameterName 'StatementTimeout') { $server.ConnectionContext.StatementTimeout = $StatementTimeout }
                    if (Test-Bound -ParameterName 'SqlExecutionModes') { $server.ConnectionContext.SqlExecutionModes = $SqlExecutionModes }
                    if (Test-Bound -ParameterName 'TrustServerCertificate') { $server.ConnectionContext.TrustServerCertificate = $true }
                    if (Test-Bound -ParameterName 'WorkstationId') { $server.ConnectionContext.WorkstationId = $WorkstationId }
    
                    $connstring = $server.ConnectionContext.ConnectionString
                    if (Test-Bound -ParameterName 'MultiSubnetFailover') { $connstring = "$connstring;MultiSubnetFailover=True" }
                    if (Test-Bound -ParameterName 'FailoverPartner') { $connstring = "$connstring;Failover Partner=$FailoverPartner" }
                    if (Test-Bound -ParameterName 'ApplicationIntent') { $connstring = "$connstring;ApplicationIntent=$ApplicationIntent" }
    
                    if ($connstring -ne $server.ConnectionContext.ConnectionString) {
                        $server.ConnectionContext.ConnectionString = $connstring
                    }
    
                    try {
                        if ($null -ne $Credential.UserName) {
                            $username = ($Credential.UserName).TrimStart("\")
    
                            if ($username -like "*\*") {
                                $username = $username.Split("\")[1]
                                $authtype = "Windows Authentication with Credential"
                                $server.ConnectionContext.LoginSecure = $true
                                $server.ConnectionContext.ConnectAsUser = $true
                                $server.ConnectionContext.ConnectAsUserName = $username
                                $server.ConnectionContext.ConnectAsUserPassword = ($Credential).GetNetworkCredential().Password
                            } else {
                                $authtype = "SQL Authentication"
                                $server.ConnectionContext.LoginSecure = $false
                                $server.ConnectionContext.set_Login($username)
                                $server.ConnectionContext.set_SecurePassword($Credential.Password)
                            }
                        }
    
                        if ($NonPooled) {
                            $server.ConnectionContext.Connect()
                        } elseif ($authtype -eq "Windows Authentication with Credential") {
                            # Make it connect in a natural way, hard to explain.
                            $null = $server.Information.Version
                            if ($server.ConnectionContext.IsOpen -eq $false) {
                                $server.ConnectionContext.Connect()
                            }
                        } else {
                            $server.ConnectionContext.SqlConnectionObject.Open()
                        }
                    } catch {
                        $originalException = $_.Exception
                        try {
                            $message = $originalException.InnerException.InnerException.ToString()
                        } catch {
                            $message = $originalException.ToString()
                        }
                        $message = ($message -Split '-->')[0]
                        $message = ($message -Split 'at System.Data.SqlClient')[0]
                        $message = ($message -Split 'at System.Data.ProviderBase')[0]
                        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
                    }
                }
                $server
            }
        }
    }
    #ValidationTags#Messaging,FlowControl,Pipeline,CodeStyle#
    function ConvertTo-DbaDataTable {
        <#
        .SYNOPSIS
            Creates a DataTable for an object.
    
        .DESCRIPTION
            Creates a DataTable based on an object's properties. This allows you to easily write to SQL Server tables.
    
            Thanks to Chad Miller, this is based on his script. https://gallery.technet.microsoft.com/scriptcenter/4208a159-a52e-4b99-83d4-8048468d29dd
    
            If the attempt to convert to data table fails, try the -Raw parameter for less accurate datatype detection.
    
        .PARAMETER InputObject
            The object to transform into a DataTable.
    
        .PARAMETER TimeSpanType
            Specifies the type to convert TimeSpan objects into. Default is 'TotalMilliseconds'. Valid options are: 'Ticks', 'TotalDays', 'TotalHours', 'TotalMinutes', 'TotalSeconds', 'TotalMilliseconds', and 'String'.
    
        .PARAMETER SizeType
            Specifies the type to convert DbaSize objects to. Default is 'Int64'. Valid options are 'Int32', 'Int64', and 'String'.
    
        .PARAMETER IgnoreNull
            If this switch is enabled, objects with null values will be ignored (empty rows will be added by default).
    
        .PARAMETER Raw
            If this switch is enabled, the DataTable will be created with strings. No attempt will be made to parse/determine data types.
    
        .PARAMETER EnableException
            By default, when something goes wrong we try to catch it, interpret it and give you a friendly warning message.
            This avoids overwhelming you with "sea of red" exceptions, but is inconvenient because it basically disables advanced scripting.
            Using this switch turns this "nice by default" feature off and enables you to catch exceptions with your own try/catch.
    
        .NOTES
            Tags: DataTable, Table, Data
            Author: Chrissy LeMaire (@cl), netnerds.net
    
            Website: https://dbatools.io/
            Copyright: (c) 2018 by dbatools, licensed under MIT
            License: MIT https://opensource.org/licenses/MIT
    
        .LINK
            https://dbatools.io/ConvertTo-DbaDataTable
    
        .OUTPUTS
            System.Object[]
    
        .EXAMPLE
            PS C:\> Get-Service | ConvertTo-DbaDataTable
    
            Creates a DataTable from the output of Get-Service.
    
        .EXAMPLE
            PS C:\> ConvertTo-DbaDataTable -InputObject $csv.cheesetypes
    
            Creates a DataTable from the CSV object $csv.cheesetypes.
    
        .EXAMPLE
            PS C:\> $dblist | ConvertTo-DbaDataTable
    
            Creates a DataTable from the $dblist object passed in via pipeline.
    
        .EXAMPLE
            PS C:\> Get-Process | ConvertTo-DbaDataTable -TimeSpanType TotalSeconds
    
            Creates a DataTable with the running processes and converts any TimeSpan property to TotalSeconds.
    
        #>
        [CmdletBinding()]
        [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUseOutputTypeCorrectly", "", Justification = "PSSA Rule Ignored by BOH")]
        [OutputType([System.Object[]])]
        param (
            [Parameter(Position = 0,
                Mandatory,
                ValueFromPipeline)]
            [AllowNull()]
            [PSObject[]]$InputObject,
            [Parameter(Position = 1)]
            [ValidateSet("Ticks",
                "TotalDays",
                "TotalHours",
                "TotalMinutes",
                "TotalSeconds",
                "TotalMilliseconds",
                "String")]
            [ValidateNotNullOrEmpty()]
            [string]$TimeSpanType = "TotalMilliseconds",
            [ValidateSet("Int64", "Int32", "String")]
            [string]$SizeType = "Int64",
            [switch]$IgnoreNull,
            [switch]$Raw,
            [Alias('Silent')]
            [switch]$EnableException
        )
    
        begin {
            Write-Message -Level Debug -Message "Bound parameters: $($PSBoundParameters.Keys -join ", ")"
            Write-Message -Level Debug -Message "TimeSpanType = $TimeSpanType | SizeType = $SizeType"
            Test-DbaDeprecation -DeprecatedOn 1.0.0 -Alias Out-DbaDataTable
    
            function Convert-Type {
                # This function will check so that the type is an accepted type which could be used when inserting into a table.
                # If a type is accepted (included in the $type array) then it will be passed on, otherwise it will first change type before passing it on.
                # Special types will have both their types converted as well as the value.
                # TimeSpan is a special type and will be converted into the $timespantype. (default: TotalMilliseconds) so that the timespan can be stored in a database further down the line.
                [CmdletBinding()]
                param (
                    $type,
                    $value,
                    $timespantype = 'TotalMilliseconds',
                    $sizetype = 'Int64'
                )
    
                $types = [System.Collections.ArrayList]@(
                    'System.Int32',
                    'System.UInt32',
                    'System.Int16',
                    'System.UInt16',
                    'System.Int64',
                    'System.UInt64',
                    'System.Decimal',
                    'System.Single',
                    'System.Double',
                    'System.Byte',
                    'System.SByte',
                    'System.Boolean',
                    'System.DateTime',
                    'System.Guid',
                    'System.Char'
                )
    
                # The $special variable is used to mark the return value if a conversion was made on the value itself.
                # If this is set to true the original value will later be ignored when updating the DataTable.
                # And the value returned from this function will be used instead. (cannot modify existing properties)
                $special = $false
                $specialType = ""
    
                # Special types need to be converted in some way.
                # This attempt is to convert timespan into something that works in a table.
                # I couldn't decide on what to convert it to so the user can decide.
                # If the parameter is not used, TotalMilliseconds will be used as default.
                # Ticks are more accurate but I think milliseconds are more useful most of the time.
                if (($type -eq 'System.TimeSpan') -or ($type -eq 'Sqlcollaborative.Dbatools.Utility.DbaTimeSpan') -or ($type -eq 'Sqlcollaborative.Dbatools.Utility.DbaTimeSpanPretty')) {
                    $special = $true
                    if ($timespantype -eq 'String') {
                        $value = $value.ToString()
                        $type = 'System.String'
                    } else {
                        # Let's use Int64 for all other types than string.
                        # We could match the type more closely with the timespantype but that can be added in the future if needed.
                        $value = $value.$timespantype
                        $type = 'System.Int64'
                    }
                    $specialType = 'Timespan'
                } elseif ($type -eq 'Sqlcollaborative.Dbatools.Utility.Size') {
                    $special = $true
                    switch ($sizetype) {
                        'Int64' {
                            $value = $value.Byte
                            $type = 'System.Int64'
                        }
                        'Int32' {
                            $value = $value.Byte
                            $type = 'System.Int32'
                        }
                        'String' {
                            $value = $value.ToString()
                            $type = 'System.String'
                        }
                    }
                    $specialType = 'Size'
                } elseif (-not ($type -in $types)) {
                    # All types which are not found in the array will be converted into strings.
                    # In this way we don't ignore it completely and it will be clear in the end why it looks as it does.
                    $type = 'System.String'
                }
    
                # return a hashtable instead of an object. I like hashtables :)
                return @{ type = $type; Value = $value; Special = $special; SpecialType = $specialType }
            }
    
            function Convert-SpecialType {
                <#
                .SYNOPSIS
                    Converts a value for a known column.
    
                .DESCRIPTION
                    Converts a value for a known column.
    
                .PARAMETER Value
                    The value to convert
    
                .PARAMETER Type
                    The special type for which to convert
    
                .PARAMETER SizeType
                    The size type defined by the user
    
                .PARAMETER TimeSpanType
                    The timespan type defined by the user
            #>
                [CmdletBinding()]
                param (
                    $Value,
                    [ValidateSet('Timespan', 'Size')]
                    [string]$Type,
                    [string]$SizeType,
                    [string]$TimeSpanType
                )
    
                switch ($Type) {
                    'Size' {
                        if ($SizeType -eq 'String') { return $Value.ToString() }
                        else { return $Value.Byte }
                    }
                    'Timespan' {
                        if ($TimeSpanType -eq 'String') {
                            $Value.ToString()
                        } else {
                            $Value.$TimeSpanType
                        }
                    }
                }
            }
    
            function Add-Column {
                <#
                .SYNOPSIS
                    Adds a column to the datatable in progress.
    
                .DESCRIPTION
                    Adds a column to the datatable in progress.
    
                .PARAMETER Property
                    The property for which to add a column.
    
                .PARAMETER DataTable
                    Autofilled. The table for which to add a column.
    
                .PARAMETER TimeSpanType
                    Autofilled. How should timespans be handled?
    
                .PARAMETER SizeType
                    Autofilled. How should sizes be handled?
    
                .PARAMETER Raw
                    Autofilled. Whether the column should be string, no matter the input.
            #>
                [CmdletBinding()]
                param (
                    [System.Management.Automation.PSPropertyInfo]$Property,
                    [System.Data.DataTable]$DataTable = $datatable,
                    [string]$TimeSpanType = $TimeSpanType,
                    [string]$SizeType = $SizeType,
                    [bool]$Raw = $Raw
                )
    
                $type = $property.TypeNameOfValue
                try {
                    if ($Property.MemberType -like 'ScriptProperty') {
                        $type = $Property.GetType().FullName
                    }
                } catch { $type = 'System.String' }
    
                $converted = Convert-Type -type $type -value $property.Value -timespantype $TimeSpanType -sizetype $SizeType
    
                $column = New-Object System.Data.DataColumn
                $column.ColumnName = $property.Name.ToString()
                if (-not $Raw) {
                    $column.DataType = [System.Type]::GetType($converted.type)
                }
                $null = $DataTable.Columns.Add($column)
                $converted
            }
    
            $datatable = New-Object System.Data.DataTable
    
            # Accelerate subsequent lookups of columns and special type columns
            $columns = @()
            $specialColumns = @()
            $specialColumnsType = @{ }
    
            $ShouldCreateColumns = $true
        }
    
        process {
            #region Handle null objects
            if ($null -eq $InputObject) {
                if (-not $IgnoreNull) {
                    $datarow = $datatable.NewRow()
                    $datatable.Rows.Add($datarow)
                }
    
                # Only ends the current process block
                return
            }
            #endregion Handle null objects
    
    
            foreach ($object in $InputObject) {
                #region Handle null objects
                if ($null -eq $object) {
                    if (-not $IgnoreNull) {
                        $datarow = $datatable.NewRow()
                        $datatable.Rows.Add($datarow)
                    }
                    continue
                }
                #endregion Handle null objects
    
                #Handle rows already being System.Data.DataRow
                if ($object.GetType().FullName -eq 'System.Data.DataRow') {
                    if ($ShouldCreateColumns) {
                        $datatable = $object.Table.Copy()
                        $ShouldCreateColumns = $false
                    }
                    continue
                }
    
                # The new row to insert
                $datarow = $datatable.NewRow()
    
                #region Process Properties
                $objectProperties = $object.PSObject.Properties
                foreach ($property in $objectProperties) {
                    #region Create Columns as needed
                    if ($ShouldCreateColumns) {
                        $newColumn = Add-Column -Property $property
                        $columns += $property.Name
                        if ($newColumn.Special) {
                            $specialColumns += $property.Name
                            $specialColumnsType[$property.Name] = $newColumn.SpecialType
                        }
                    }
                    #endregion Create Columns as needed
    
                    # Handle null properties, as well as properties with access errors
                    try {
                        $propValueLength = $property.value.length
                    } catch {
                        $propValueLength = 0
                    }
    
                    #region Insert value into column of row
                    if ($propValueLength -gt 0) {
                        # If the typename was a special typename we want to use the value returned from Convert-Type instead.
                        # We might get error if we try to change the value for $property.value if it is read-only. That's why we use $converted.value instead.
                        if ($property.Name -in $specialColumns) {
                            $datarow.Item($property.Name) = Convert-SpecialType -Value $property.value -Type $specialColumnsType[$property.Name] -SizeType $SizeType -TimeSpanType $TimeSpanType
                        } else {
                            if ($property.value.ToString().length -eq 15) {
                                if ($property.value.ToString() -eq 'System.Object[]') {
                                    $value = $property.value -join ", "
                                } elseif ($property.value.ToString() -eq 'System.String[]') {
                                    $value = $property.value -join ", "
                                } else {
                                    $value = $property.value
                                }
                            } else {
                                $value = $property.value
                            }
    
                            try {
                                $datarow.Item($property.Name) = $value
                            } catch {
                                if ($property.Name -notin $columns) {
                                    try {
                                        $newColumn = Add-Column -Property $property
                                        $columns += $property.Name
                                        if ($newColumn.Special) {
                                            $specialColumns += $property.Name
                                            $specialColumnsType[$property.Name] = $newColumn.SpecialType
                                        }
    
                                        $datarow.Item($property.Name) = $newColumn.Value
                                    } catch {
                                        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 dbatools functions:
            Get-DbaAgentJobHistory
            Get-DbaBackupHistory
            (more to come...)
            And generates Bootstrap based, HTML file with Google Chart Timeline
    
        .PARAMETER InputObject
    
            Pipe input, must an output from the above functions.
    
        .PARAMETER EnableException
            By default, when something goes wrong we try to catch it, interpret it and give you a friendly warning message.
            This avoids overwhelming you with "sea of red" exceptions, but is inconvenient because it basically disables advanced scripting.
            Using this switch turns this "nice by default" feature off and enables you to catch exceptions with your own try/catch.
    
        .NOTES
            Tags: Chart
            Author: Marcin Gminski (@marcingminski)
    
            Website: https://dbatools.io
            Copyright: (c) 2018 by dbatools, licensed under MIT
            License: MIT https://opensource.org/licenses/MIT
    
            Dependency: ConvertTo-JsDate, Convert-DbaTimelineStatusColor
    
        .LINK
            https://dbatools.io/ConvertTo-DbaTimeline
    
        .EXAMPLE
            PS C:\> Get-DbaAgentJobHistory -SqlInstance sql-1 -StartDate '2018-08-13 00:00' -EndDate '2018-08-13 23:59' -ExcludeJobSteps | ConvertTo-DbaTimeline | Out-File C:\temp\DbaAgentJobHistory.html -Encoding ASCII
    
            Creates an output file containing a pretty timeline for all of the agent job history results for sql-1 the whole day of 2018-08-13
    
        .EXAMPLE
            PS C:\> Get-DbaCmsRegServer -SqlInstance sqlcm | Get-DbaBackupHistory -Since '2018-08-13 00:00' | ConvertTo-DbaTimeline | Out-File C:\temp\DbaBackupHistory.html -Encoding ASCII
    
            Creates an output file containing a pretty timeline for the agent job history since 2018-08-13 for all of the registered servers on sqlcm
    
        .EXAMPLE
            PS C:\> $messageParameters = @{
            >> Subject = "Backup history for sql2017 and sql2016"
            >> Body = Get-DbaBackupHistory -SqlInstance sql2017, sql2016 -Since '2018-08-13 00:00' | ConvertTo-DbaTimeline
            >> From = "[email protected]"
            >> To = "[email protected]"
            >> SmtpServer = "smtp.ad.local"
            >> }
            >>
            PS C:\> Send-MailMessage @messageParameters -BodyAsHtml
    
            Sends an email to [email protected] with the results of Get-DbaBackupHistory. Note that viewing these reports may not be supported in all email clients.
    
        #>
        [CmdletBinding()]
        [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUseOutputTypeCorrectly", "", Justification = "PSSA Rule Ignored by BOH")]
        param (
            [parameter(Mandatory, ValueFromPipeline)]
            [object[]]$InputObject,
            [switch]$EnableException
        )
        begin {
            $body = $servers = @()
            $begin = @"
    <html>
    <head>
    <!-- Developed by Marcin Gminski, https://marcin.gminski.net, 2018 -->
    <!-- Load jQuery required to autosize timeline -->
    <script src="https://code.jquery.com/jquery-3.3.1.min.js" integrity="sha256-FgpCb/KJQlLNfOu91ta32o/NMZxltwRo8QtmkMRdAu8=" crossorigin="anonymous"></script>
    <!-- Load Bootstrap -->
    <script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js" integrity="sha384-Tc5IQib027qvyjSMfHjOMaLkfuWVxZxUPnCJA7l2mCWNIpG9mGCD8wGNIcPD7Txa" crossorigin="anonymous"></script>
    <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous">
    <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap-theme.min.css" integrity="sha384-rHyoN1iRsVXV4nD0JutlnGaslCJuC7uwjduW9SVrLvRYooPp2bWYgmgJQIXwl/Sp" crossorigin="anonymous">
    <!-- Load Google Charts library -->
    <script type="text/javascript" src="https://www.gstatic.com/charts/loader.js"></script>
    <!-- a bit of custom styling to work with bootstrap grid -->
    <style>
    
        html,body{height:100%;background-color:#c2c2c2;}
        .viewport {height:100%}
    
        .chart{
            background-color:#fff;
            text-align:left;
            padding:0;
            border:1px solid #7D7D7D;
            -webkit-box-shadow:1px 1px 3px 0 rgba(0,0,0,.45);
            -moz-box-shadow:1px 1px 3px 0 rgba(0,0,0,.45);
            box-shadow:1px 1px 3px 0 rgba(0,0,0,.45)
        }
        .badge-custom{background-color:#939}
        .container {
            height:100%;
        }
        .fill{
            width:100%;
            height:100%;
            min-height:100%;
            padding:10px;
        }
        .timeline-tooltip{
            border:1px solid #E0E0E0;
            font-family:Arial,Helvetica;
            font-size:10pt;
            padding:12px
        }
        .timeline-tooltip div{padding:6px}
        .timeline-tooltip span{font-weight:700}
    </style>
        <script type="text/javascript">
        google.charts.load('43', {'packages':['timeline']});
        google.charts.setOnLoadCallback(drawChart);
        function drawChart() {
            var container = document.getElementById('Chart');
            var chart = new google.visualization.Timeline(container);
            var dataTable = new google.visualization.DataTable();
            dataTable.addColumn({type: 'string', id: 'vLabel'});
            dataTable.addColumn({type: 'string', id: 'hLabel'});
            dataTable.addColumn({type: 'string', role: 'style' });
            dataTable.addColumn({type: 'date', id: 'date_start'});
            dataTable.addColumn({type: 'date', id: 'date_end'});
    
            dataTable.addRows([
    "@
        }
    
        process {
            # create server list to support multiple servers
            if ($InputObject[0].SqlInstance -notin $servers) {
                $servers += $InputObject[0].SqlInstance
            }
            # This is where do column mapping.
    
            # Check for types - this will help support if someone assigns a variable then pipes
            # AgentJobHistory is a forced type while backuphistory is a legit type
            if ($InputObject[0].TypeName -eq 'AgentJobHistory') {
                $CallerName = "Get-DbaAgentJobHistory"
                $data = $InputObject | Select-Object @{ Name = "SqlInstance"; Expression = { $_.SqlInstance } }, @{ Name = "InstanceName"; Expression = { $_.InstanceName } }, @{ Name = "vLabel"; Expression = { $_.Job -replace "\'", ''} }, @{ Name = "hLabel"; Expression = { $_.Status } }, @{ Name = "Style"; Expression = { $(Convert-DbaTimelineStatusColor($_.Status)) } }, @{ Name = "StartDate"; Expression = { $(ConvertTo-JsDate($_.StartDate)) } }, @{ Name = "EndDate"; Expression = { $(ConvertTo-JsDate($_.EndDate)) } }
    
            } elseif ($InputObject[0] -is [Sqlcollaborative.Dbatools.Database.BackupHistory]) {
                $CallerName = "Get-DbaBackupHistory"
                $data = $InputObject | Select-Object @{ Name = "SqlInstance"; Expression = { $_.SqlInstance } }, @{ Name = "InstanceName"; Expression = { $_.InstanceName } }, @{ Name = "vLabel"; Expression = { $_.Database } }, @{ Name = "hLabel"; Expression = { $_.Type } }, @{ Name = "StartDate"; Expression = { $(ConvertTo-JsDate($_.Start)) } }, @{ Name = "EndDate"; Expression = { $(ConvertTo-JsDate($_.End)) } }
            } else {
                # sorry to be so formal, can't help it ;)
                Stop-Function -Message "Unsupported input data. To request support for additional commands, please file an issue at dbatools.io/issues and we'll take a look"
                return
            }
            $body += "$($data | ForEach-Object{ "['$($_.vLabel)','$($_.hLabel)','$($_.Style)',$($_.StartDate), $($_.EndDate)]," })"
        }
        end {
            if (Test-FunctionInterrupt) { return }
            $end = @"
    ]);
            var paddingHeight = 20;
            var rowHeight = dataTable.getNumberOfRows() * 41;
            var chartHeight = rowHeight + paddingHeight;
            dataTable.insertColumn(2, {type: 'string', role: 'tooltip', p: {html: true}});
            var dateFormat = new google.visualization.DateFormat({
              pattern: 'dd/MM/yy HH:mm:ss'
            });
            for (var i = 0; i < dataTable.getNumberOfRows(); i++) {
              var duration = (dataTable.getValue(i, 5).getTime() - dataTable.getValue(i, 4).getTime()) / 1000;
              var hours = parseInt( duration / 3600 ) % 24;
              var minutes = parseInt( duration / 60 ) % 60;
              var seconds = duration % 60;
              var tooltip = '<div class="timeline-tooltip"><span>' +
                dataTable.getValue(i, 1).split(",").join("<br />")  + '</span></div><div class="timeline-tooltip"><span>' +
                dataTable.getValue(i, 0) + '</span>: ' +
                dateFormat.formatValue(dataTable.getValue(i, 4)) + ' - ' +
                dateFormat.formatValue(dataTable.getValue(i, 5)) + '</div>' +
                '<div class="timeline-tooltip"><span>Duration: </span>' +
                hours + 'h ' + minutes + 'm ' + seconds + 's ';
              dataTable.setValue(i, 2, tooltip);
            }
            var options = {
                timeline: {
                    rowLabelStyle: { },
                    barLabelStyle: { },
                },
                hAxis: {
                    format: 'dd/MM HH:mm',
                },
            }
            // Autosize chart. It would not be enough to just count rows and expand based on row height as there can be overlapping rows.
            // this will draw the chart, get the size of the underlying div and apply that size to the parent container and redraw:
            chart.draw(dataTable, options);
            // get the size of the chold div:
            var realheight= parseInt(`$("#Chart div:first-child div:first-child div:first-child div svg").attr( "height"))+70;
            // set the height:
            options.height=realheight
            // draw again:
            chart.draw(dataTable, options);
        }
    </script>
    </head>
    <body>
        <div class="container-fluid">
        <div class="pull-left"><h3><code>$($CallerName)</code> timeline for server <code>$($servers -join ', ')</code></h3></div><div class="pull-right text-right"><img class="text-right" style="vertical-align:bottom; margin-top: 10px;" src="https://dbatools.io/wp-content/uploads/2016/05/dbatools-logo-1.png" width=150></div>
             <div class="clearfix"></div>
             <div class="col-12">
                <div class="chart" id="Chart"></div>
             </div>
             <hr>
        <p><a href="https://dbatools.io">dbatools.io</a> - the community's sql powershell module. Find us on Twitter: <a href="https://twitter.com/psdbatools">@psdbatools</a> | Chart by <a href="https://twitter.com/marcingminski">@marcingminski</a></p>
    </div>
    </body>
    </html>
    "@
            $begin, $body, $end
        }
    }
    #ValidationTags#Messaging,FlowControl,Pipeline,CodeStyle#
    function ConvertTo-DbaXESession {
        <#
        .SYNOPSIS
            Uses a slightly modified version of sp_SQLskills_ConvertTraceToExtendedEvents.sql to convert Traces to Extended Events.
    
        .DESCRIPTION
            Uses a slightly modified version of sp_SQLskills_ConvertTraceToExtendedEvents.sql to convert Traces to Extended Events.
    
            T-SQL code by: Jonathan M. Kehayias, SQLskills.com. T-SQL can be found in this module directory and at
            https://www.sqlskills.com/blogs/jonathan/converting-sql-trace-to-extended-events-in-sql-server-2012/
    
        .PARAMETER InputObject
            Specifies a Trace object output by Get-DbaTrace.
    
        .PARAMETER Name
            The name of the Trace to convert. If the name exists, characters will be appended to it.
    
        .PARAMETER OutputScriptOnly
            Outputs the T-SQL script to create the XE session and does not execute it.
    
        .PARAMETER EnableException
            By default, when something goes wrong we try to catch it, interpret it and give you a friendly warning message.
            This avoids overwhelming you with "sea of red" exceptions, but is inconvenient because it basically disables advanced scripting.
            Using this switch turns this "nice by default" feature off and enables you to catch exceptions with your own try/catch.
    
        .NOTES
            Tags: Trace, ExtendedEvent
            Author: Chrissy LeMaire (@cl), netnerds.net
    
            Website: https://dbatools.io
            Copyright: (c) 2018 by dbatools, licensed under MIT
            License: MIT https://opensource.org/licenses/MIT
    
        .EXAMPLE
            PS C:\> Get-DbaTrace -SqlInstance sql2017, sql2012 | Where Id -eq 2 | ConvertTo-DbaXESession -Name 'Test'
    
            Converts Trace with ID 2 to a Session named Test on SQL Server instances named sql2017 and sql2012 and creates the Session on each respective server.
    
        .EXAMPLE
            PS C:\> Get-DbaTrace -SqlInstance sql2014 | Out-GridView -PassThru | ConvertTo-DbaXESession -Name 'Test' | Start-DbaXESession
    
            Converts selected traces on sql2014 to sessions, creates the session, and starts it.
    
        .EXAMPLE
            PS C:\> Get-DbaTrace -SqlInstance sql2014 | Where Id -eq 1 | ConvertTo-DbaXESession -Name 'Test' -OutputScriptOnly
    
            Converts trace ID 1 on sql2014 to an Extended Event and outputs the resulting T-SQL.
    
        #>
        [CmdletBinding()]
        param (
            [parameter(Mandatory, ValueFromPipeline)]
            [object[]]$InputObject,
            [parameter(Mandatory)]
            [string]$Name,
            [switch]$OutputScriptOnly,
            [switch]$EnableException
        )
        begin {
            $rawsql = Get-Content "$script:PSModuleRoot\bin\sp_SQLskills_ConvertTraceToEEs.sql" -Raw
        }
        process {
            foreach ($trace in $InputObject) {
                if (-not $trace.id -and -not $trace.Parent) {
                    Stop-Function -Message "Input is of the wrong type. Use Get-DbaTrace." -Continue
                    return
                }
    
                $server = $trace.Parent
    
                if ($server.VersionMajor -lt 11) {
                    Stop-Function -Message "SQL Server version 2012+ required - $server not supported."
                    return
                }
    
                $tempdb = $server.Databases['tempdb']
                $traceid = $trace.id
    
                if ((Get-DbaXESession -SqlInstance $server -Session $PSBoundParameters.Name)) {
                    $oldname = $name
                    $Name = "$name-$traceid"
                    Write-Message -Level Output -Message "XE Session $oldname already exists on $server, trying $name."
                }
    
                if ((Get-DbaXESession -SqlInstance $server -Session $Name)) {
                    $oldname = $name
                    $Name = "$name-$(Get-Random)"
                    Write-Message -Level Output -Message "XE Session $oldname already exists on $server, trying $name."
                }
    
                $sql = $rawsql.Replace("--TRACEID--", $traceid)
                $sql = $sql.Replace("--SESSIONNAME--", $name)
    
                try {
                    Write-Message -Level Verbose -Message "Executing SQL in tempdb."
                    $results = $tempdb.ExecuteWithResults($sql).Tables.Rows.SqlString
                } catch {
                    Stop-Function -Message "Issue creating, dropping or executing sp_SQLskills_ConvertTraceToExtendedEvents in tempdb on $server." -Target $server -ErrorRecord $_
                }
    
                $results = $results -join "`r`n"
    
                if ($OutputScriptOnly) {
                    $results
                } else {
                    Write-Message -Level Verbose -Message "Creating XE Session $name."
                    try {
                        $tempdb.ExecuteNonQuery($results)
                    } catch {
                        Stop-Function -Message "Issue creating extended event $name on $server." -Target $server -ErrorRecord $_
                    }
                    Get-DbaXESession -SqlInstance $server -Session $name
                }
            }
        }
    }
    function Copy-DbaAgentAlert {
        <#
        .SYNOPSIS
            Copy-DbaAgentAlert migrates alerts from one SQL Server to another.
    
        .DESCRIPTION
            By default, all alerts are copied. The -Alert parameter is auto-populated for command-line completion and can be used to copy only specific alerts.
    
            If the alert already exists on the destination, it will be skipped unless -Force is used.
    
        .PARAMETER Source
            Source SQL Server. You must have sysadmin access and server version must be SQL Server version 2000 or higher.
    
        .PARAMETER SourceSqlCredential
            Login to the target instance using alternative credentials. Windows and SQL Authentication supported. Accepts credential objects (Get-Credential)
    
        .PARAMETER Destination
            Destination SQL Server. You must have sysadmin access and the server must be SQL Server 2000 or higher.
    
        .PARAMETER DestinationSqlCredential
            Login to the target instance using alternative credentials. Windows and SQL Authentication supported. Accepts credential objects (Get-Credential)
    
        .PARAMETER Alert
            The alert(s) to process. This list is auto-populated from the server. If unspecified, all alerts will be processed.
    
        .PARAMETER ExcludeAlert
            The alert(s) to exclude. This list is auto-populated from the server.
    
        .PARAMETER IncludeDefaults
            Copy SQL Agent defaults such as FailSafeEmailAddress, ForwardingServer, and PagerSubjectTemplate.
    
        .PARAMETER WhatIf
            If this switch is enabled, no actions are performed but informational messages will be displayed that explain what would happen if the command were to run.
    
        .PARAMETER Confirm
            If this switch is enabled, you will be prompted for confirmation before executing any operations that change state.
    
        .PARAMETER Force
            If this switch is enabled, the Alert will be dropped and recreated on Destination.
    
        .PARAMETER EnableException
            By default, when something goes wrong we try to catch it, interpret it and give you a friendly warning message.
            This avoids overwhelming you with "sea of red" exceptions, but is inconvenient because it basically disables advanced scripting.
            Using this switch turns this "nice by default" feature off and enables you to catch exceptions with your own try/catch.
    
        .NOTES
            Tags: Migration, Agent
            Author: Chrissy LeMaire (@cl), netnerds.net
    
            Website: https://dbatools.io
            Copyright: (c) 2018 by dbatools, licensed under MIT
            License: MIT https://opensource.org/licenses/MIT
    
            Requires: sysadmin access on SQL Servers
    
        .LINK
            https://dbatools.io/Copy-DbaAgentAlert
    
        .EXAMPLE
            PS C:\> Copy-DbaAgentAlert -Source sqlserver2014a -Destination sqlcluster
    
            Copies all alerts from sqlserver2014a to sqlcluster using Windows credentials. If alerts with the same name exist on sqlcluster, they will be skipped.
    
        .EXAMPLE
            PS C:\> Copy-DbaAgentAlert -Source sqlserver2014a -Destination sqlcluster -Alert PSAlert -SourceSqlCredential $cred -Force
    
            Copies a only the alert named PSAlert from sqlserver2014a to sqlcluster using SQL credentials for sqlserver2014a and Windows credentials for sqlcluster. If an alert with the same name exists on sqlcluster, it will be dropped and recreated because -Force was used.
    
        .EXAMPLE
            PS C:\> Copy-DbaAgentAlert -Source sqlserver2014a -Destination sqlcluster -WhatIf -Force
    
            Shows what would happen if the command were executed using force.
    
        #>
        [cmdletbinding(DefaultParameterSetName = "Default", SupportsShouldProcess = $true)]
        param (
            [parameter(Mandatory)]
            [DbaInstanceParameter]$Source,
            [PSCredential]$SourceSqlCredential,
            [parameter(Mandatory)]
            [DbaInstanceParameter[]]$Destination,
            [PSCredential]$DestinationSqlCredential,
            [object[]]$Alert,
            [object[]]$ExcludeAlert,
            [switch]$IncludeDefaults,
            [switch]$Force,
            [Alias('Silent')]
            [switch]$EnableException
        )
        begin {
            try {
                $sourceServer = Connect-SqlInstance -SqlInstance $Source -SqlCredential $SourceSqlCredential
                $serverAlerts = $sourceServer.JobServer.Alerts
            } catch {
                Stop-Function -Message "Failure" -Category ConnectionError -ErrorRecord $_ -Target $Source
                return
            }
        }
        process {
            if (Test-FunctionInterrupt) { return }
            foreach ($destinstance in $Destination) {
                try {
                    $destServer = Connect-SqlInstance -SqlInstance $destinstance -SqlCredential $DestinationSqlCredential
                } catch {
                    Stop-Function -Message "Failure" -Category ConnectionError -ErrorRecord $_ -Target $destinstance -Continue
                }
                $destAlerts = $destServer.JobServer.Alerts
    
                if ($IncludeDefaults -eq $true) {
                    if ($PSCmdlet.ShouldProcess($destinstance, "Creating Alert Defaults")) {
                        $copyAgentAlertStatus = [pscustomobject]@{
                            SourceServer      = $sourceServer.Name
                            DestinationServer = $destServer.Name
                            Name              = "Alert Defaults"
                            Type              = "Alert Defaults"
                            Status            = $null
                            Notes             = $null
                            DateTime          = [Sqlcollaborative.Dbatools.Utility.DbaDateTime](Get-Date)
                        }
                        try {
                            Write-Message -Message "Creating Alert Defaults" -Level Verbose
                            $sql = $sourceServer.JobServer.AlertSystem.Script() | Out-String
                            $sql = $sql -replace [Regex]::Escape("'$source'"), "'$destinstance'"
    
                            Write-Message -Message $sql -Level Debug
                            $null = $destServer.Query($sql)
    
                            $copyAgentAlertStatus.Status = "Successful"
                        } catch {
                            $copyAgentAlertStatus.Status = "Failed"
                            $copyAgentAlertStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
                            Stop-Function -Message "Issue creating alert defaults." -Category InvalidOperation -ErrorRecord $_ -Target $destServer -Continue
                        }
                        $copyAgentAlertStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
                    }
                }
    
                foreach ($serverAlert in $serverAlerts) {
                    $alertName = $serverAlert.name
                    $copyAgentAlertStatus = [pscustomobject]@{
                        SourceServer      = $sourceServer.Name
                        DestinationServer = $destServer.Name
                        Name              = $alertName
                        Type              = "Agent Alert"
                        Notes             = $null
                        Status            = $null
                        DateTime          = [Sqlcollaborative.Dbatools.Utility.DbaDateTime](Get-Date)
                    }
                    if (($Alert -and $Alert -notcontains $alertName) -or ($ExcludeAlert -and $ExcludeAlert -contains $alertName)) {
                        continue
                    }
    
                    if ($destAlerts.name -contains $serverAlert.name) {
                        if ($force -eq $false) {
                            if ($PSCmdlet.ShouldProcess($destinstance, "Alert [$alertName] exists at destination. Use -Force to drop and migrate.")) {
                                $copyAgentAlertStatus.Status = "Skipped"
                                $copyAgentAlertStatus.Notes = "Already exists"
                                $copyAgentAlertStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
                                Write-Message -Message "Alert [$alertName] exists at destination. Use -Force to drop and migrate." -Level Verbose
                            }
                            continue
                        }
    
                        if ($PSCmdlet.ShouldProcess($destinstance, "Dropping alert $alertName and recreating")) {
                            try {
                                Write-Message -Message "Dropping Alert $alertName on $destServer." -Level Verbose
    
                                $sql = "EXEC msdb.dbo.sp_delete_alert @name = N'$($alertname)';"
                                Write-Message -Message $sql -Level Debug
                                $null = $destServer.Query($sql)
                                $destAlerts.Refresh()
                            } catch {
                                $copyAgentAlertStatus.Status = "Failed"
                                $copyAgentAlertStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
                                Stop-Function -Message "Issue dropping/recreating alert" -Category InvalidOperation -ErrorRecord $_ -Target $destServer -Continue
                            }
                        }
                    }
    
                    if ($destAlerts | Where-Object { $_.Severity -eq $serverAlert.Severity -and $_.MessageID -eq $serverAlert.MessageID -and $_.DatabaseName -eq $serverAlert.DatabaseName -and $_.EventDescriptionKeyword -eq $serverAlert.EventDescriptionKeyword }) {
                        if ($PSCmdlet.ShouldProcess($destinstance, "Checking for conflicts")) {
                            $conflictMessage = "Alert [$alertName] has already been defined to use"
                            if ($serverAlert.Severity -gt 0) { $conflictMessage += " severity $($serverAlert.Severity)" }
                            if ($serverAlert.MessageID -gt 0) { $conflictMessage += " error number $($serverAlert.MessageID)" }
                            if ($serverAlert.DatabaseName) { $conflictMessage += " on database '$($serverAlert.DatabaseName)'" }
                            if ($serverAlert.EventDescriptionKeyword) { $conflictMessage += " with error text '$($serverAlert.Severity)'" }
                            $conflictMessage += ". Skipping."
    
                            Write-Message -Level Verbose -Message $conflictMessage
                            $copyAgentAlertStatus.Status = "Skipped"
                            $copyAgentAlertStatus.Notes = $conflictMessage
                            $copyAgentAlertStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
                        }
                        continue
                    }
                    if ($serverAlert.JobName -and $destServer.JobServer.Jobs.Name -NotContains $serverAlert.JobName) {
                        Write-Message -Level Verbose -Message "Alert [$alertName] has job [$($serverAlert.JobName)] configured as response. The job does not exist on destination $destServer. Skipping."
                        if ($PSCmdlet.ShouldProcess($destinstance, "Checking for conflicts")) {
                            $copyAgentAlertStatus.Status = "Skipped"
                            $copyAgentAlertStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
                        }
                        continue
                    }
    
                    if ($PSCmdlet.ShouldProcess($destinstance, "Creating Alert $alertName")) {
                        try {
                            Write-Message -Message "Copying Alert $alertName" -Level Verbose
                            $sql = $serverAlert.Script() | Out-String
                            $sql = $sql -replace "@job_id=N'........-....-....-....-............", "@job_id=N'00000000-0000-0000-0000-000000000000"
    
                            Write-Message -Message $sql -Level Debug
                            $null = $destServer.Query($sql)
    
                            $copyAgentAlertStatus.Status = "Successful"
                            $copyAgentAlertStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
                        } catch {
                            $copyAgentAlertStatus.Status = "Failed"
                            $copyAgentAlertStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
                            Stop-Function -Message "Issue creating alert" -Category InvalidOperation -ErrorRecord $_ -Target $destServer -Continue
                        }
                    }
    
                    $destServer.JobServer.Alerts.Refresh()
                    $destServer.JobServer.Jobs.Refresh()
    
                    $newAlert = $destServer.JobServer.Alerts[$alertName]
                    $notifications = $serverAlert.EnumNotifications()
                    $jobName = $serverAlert.JobName
    
                    # JobId = 00000000-0000-0000-0000-000 means the Alert does not execute/is attached to a SQL Agent Job.
                    if ($serverAlert.JobId -ne '00000000-0000-0000-0000-000000000000') {
                        $copyAgentAlertStatus = [pscustomobject]@{
                            SourceServer      = $sourceServer.Name
                            DestinationServer = $destServer.Name
                            Name              = $alertName
                            Type              = "Agent Alert Job Association"
                            Notes             = "Associated with $jobName"
                            Status            = $null
                            DateTime          = [Sqlcollaborative.Dbatools.Utility.DbaDateTime](Get-Date)
                        }
                        if ($PSCmdlet.ShouldProcess($destinstance, "Adding $alertName to $jobName")) {
                            try {
                                <# THERE needs to be validation within this block to see if the $jobName actually exists on the source server. #>
                                Write-Message -Message "Adding $alertName to $jobName" -Level Verbose
                                $newJob = $destServer.JobServer.Jobs[$jobName]
                                $newJobId = ($newJob.JobId) -replace " ", ""
                                $sql = $sql -replace '00000000-0000-0000-0000-000000000000', $newJobId
                                $sql = $sql -replace 'sp_add_alert', 'sp_update_alert'
    
                                Write-Message -Message $sql -Level Debug
                                $null = $destServer.Query($sql)
    
                                $copyAgentAlertStatus.Status = "Successful"
                                $copyAgentAlertStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
                            } catch {
                                $copyAgentAlertStatus.Status = "Failed"
                                $copyAgentAlertStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
                                Stop-Function -Message "Issue adding alert to job" -Category InvalidOperation -ErrorRecord $_ -Target $destServer
                            }
                        }
                    }
    
                    if ($PSCmdlet.ShouldProcess($destinstance, "Moving Notifications $alertName")) {
                        try {
                            $copyAgentAlertStatus = [pscustomobject]@{
                                SourceServer      = $sourceServer.Name
                                DestinationServer = $destServer.Name
                                Name              = $alertName
                                Type              = "Agent Alert Notification"
                                Notes             = $null
                                Status            = $null
                                DateTime          = [Sqlcollaborative.Dbatools.Utility.DbaDateTime](Get-Date)
                            }
                            # can't add them this way, we need to modify the existing one or give all options that are supported.
                            foreach ($notify in $notifications) {
                                $notifyCollection = @()
                                if ($notify.UseNetSend -eq $true) {
                                    Write-Message -Message "Adding net send" -Level Verbose
                                    $notifyCollection += "NetSend"
                                }
    
                                if ($notify.UseEmail -eq $true) {
                                    Write-Message -Message "Adding email" -Level Verbose
                                    $notifyCollection += "NotifyEmail"
                                }
    
                                if ($notify.UsePager -eq $true) {
                                    Write-Message -Message "Adding pager" -Level Verbose
                                    $notifyCollection += "Pager"
                                }
    
                                $notifyMethods = $notifyCollection -join ", "
                                $newAlert.AddNotification($notify.OperatorName, [Microsoft.SqlServer.Management.Smo.Agent.NotifyMethods]$notifyMethods)
                            }
                            $copyAgentAlertStatus.Status = "Successful"
                            $copyAgentAlertStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
                        } catch {
                            $copyAgentAlertStatus.Status = "Failed"
                            $copyAgentAlertStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
                            Stop-Function -Message "Issue moving notifications for the alert" -Category InvalidOperation -ErrorRecord $_ -Target $destServer
                        }
                    }
                }
            }
        }
        end {
            Test-DbaDeprecation -DeprecatedOn "1.0.0" -EnableException:$false -Alias Copy-SqlAlert
        }
    }
    #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
    
            Website: https://dbatools.io
            Copyright: (c) 2018 by dbatools, licensed under MIT
            License: MIT https://opensource.org/licenses/MIT
    
            Requires: sysadmin access on SQL Servers
    
        .LINK
            https://dbatools.io/Copy-DbaAgentCategory
    
        .EXAMPLE
            PS C:\> 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
            PS C:\> 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 an operator category with the same name exists on sqlcluster, it will be dropped and recreated because -Force was used.
    
        .EXAMPLE
            PS C:\> Copy-DbaAgentCategory -Source sqlserver2014a -Destination sqlcluster -WhatIf -Force
    
            Shows what would happen if the command were executed using force.
    
        #>
        [CmdletBinding(DefaultParameterSetName = "Default", SupportsShouldprocess = $true)]
        param (
            [parameter(Mandatory)]
            [DbaInstanceParameter]$Source,
            [PSCredential]$SourceSqlCredential,
            [parameter(Mandatory)]
            [DbaInstanceParameter[]]$Destination,
            [PSCredential]$DestinationSqlCredential,
            [Parameter(ParameterSetName = 'SpecificAlerts')]
            [ValidateSet('Job', 'Alert', 'Operator')]
            [string[]]$CategoryType,
            [string[]]$JobCategory,
            [string[]]$AgentCategory,
            [string[]]$OperatorCategory,
            [switch]$Force,
            [Alias('Silent')]
            [switch]$EnableException
        )
    
        begin {
            function Copy-JobCategory {
                <#
                    .SYNOPSIS
                        Copy-JobCategory migrates job categories from one SQL Server to another.
    
                    .DESCRIPTION
                        By default, all job categories are copied. The -JobCategories parameter is auto-populated for command-line completion and can be used to copy only specific job categories.
    
                        If the associated credential for the category does not exist on the destination, it will be skipped. If the job category already exists on the destination, it will be skipped unless -Force is used.
                #>
                param (
                    [string[]]$jobCategories
                )
    
                process {
    
                    $serverJobCategories = $sourceServer.JobServer.JobCategories | Where-Object ID -ge 100
                    $destJobCategories = $destServer.JobServer.JobCategories | Where-Object ID -ge 100
    
                    foreach ($jobCategory in $serverJobCategories) {
                        $categoryName = $jobCategory.Name
    
                        $copyJobCategoryStatus = [pscustomobject]@{
                            SourceServer      = $sourceServer.Name
                            DestinationServer = $destServer.Name
                            Name              = $categoryName
                            Type              = "Agent Job Category"
                            Status            = $null
                            Notes             = $null
                            DateTime          = [Sqlcollaborative.Dbatools.Utility.DbaDateTime](Get-Date)
                        }
    
                        if ($jobCategories.Count -gt 0 -and $jobCategories -notcontains $categoryName) {
                            continue
                        }
    
                        if ($destJobCategories.Name -contains $jobCategory.name) {
                            if ($force -eq $false) {
                                $copyJobCategoryStatus.Status = "Skipped"
                                $copyJobCategoryStatus.Notes = "Already exists"
                                $copyJobCategoryStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
                                Write-Message -Level Verbose -Message "Job category $categoryName exists at destination. Use -Force to drop and migrate."
                                continue
                            } else {
                                if ($Pscmdlet.ShouldProcess($destinstance, "Dropping job category $categoryName")) {
                                    try {
                                        Write-Message -Level Verbose -Message "Dropping Job category $categoryName"
                                        $destServer.JobServer.JobCategories[$categoryName].Drop()
                                    } catch {
                                        $copyJobCategoryStatus.Status = "Failed"
                                        $copyJobCategoryStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
                                        Stop-Function -Message "Issue dropping job category" -Target $categoryName -ErrorRecord $_ -Continue
                                    }
                                }
                            }
                        }
    
                        if ($Pscmdlet.ShouldProcess($destinstance, "Creating Job category $categoryName")) {
                            try {
                                Write-Message -Level Verbose -Message "Copying Job category $categoryName"
                                $sql = $jobCategory.Script() | Out-String
                                Write-Message -Level Debug -Message "SQL Statement: $sql"
                                $destServer.Query($sql)
    
                                $copyJobCategoryStatus.Status = "Successful"
                                $copyJobCategoryStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
                            } catch {
                                $copyJobCategoryStatus.Status = "Failed"
                                $copyJobCategoryStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
                                Stop-Function -Message "Issue copying job category" -Target $categoryName -ErrorRecord $_
                            }
                        }
                    }
                }
            }
    
            function Copy-OperatorCategory {
                <#
                    .SYNOPSIS
                        Copy-OperatorCategory migrates operator categories from one SQL Server to another.
    
                    .DESCRIPTION
                        By default, all operator categories are copied. The -OperatorCategories parameter is auto-populated for command-line completion and can be used to copy only specific operator categories.
    
                        If the associated credential for the category does not exist on the destination, it will be skipped. If the operator category already exists on the destination, it will be skipped unless -Force is used.
                #>
                [CmdletBinding(DefaultParameterSetName = "Default", SupportsShouldprocess = $true)]
                param (
                    [string[]]$operatorCategories
                )
                process {
                    $serverOperatorCategories = $sourceServer.JobServer.OperatorCategories | Where-Object ID -ge 100
                    $destOperatorCategories = $destServer.JobServer.OperatorCategories | Where-Object ID -ge 100
    
                    foreach ($operatorCategory in $serverOperatorCategories) {
                        $categoryName = $operatorCategory.Name
    
                        $copyOperatorCategoryStatus = [pscustomobject]@{
                            SourceServer      = $sourceServer.Name
                            DestinationServer = $destServer.Name
                            Type              = "Agent Operator Category"
                            Name              = $categoryName
                            Status            = $null
                            Notes             = $null
                            DateTime          = [Sqlcollaborative.Dbatools.Utility.DbaDateTime](Get-Date)
                        }
    
                        if ($operatorCategories.Count -gt 0 -and $operatorCategories -notcontains $categoryName) {
                            continue
                        }
    
                        if ($destOperatorCategories.Name -contains $operatorCategory.Name) {
                            if ($force -eq $false) {
                                $copyOperatorCategoryStatus.Status = "Skipped"
                                $copyOperatorCategoryStatus.Notes = "Already exists"
                                $copyOperatorCategoryStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
                                Write-Message -Level Verbose -Message "Operator category $categoryName exists at destination. Use -Force to drop and migrate."
                                continue
                            } else {
                                if ($Pscmdlet.ShouldProcess($destinstance, "Dropping operator category $categoryName and recreating")) {
                                    try {
                                        Write-Message -Level Verbose -Message "Dropping Operator category $categoryName"
                                        $destServer.JobServer.OperatorCategories[$categoryName].Drop()
                                        Write-Message -Level Verbose -Message "Copying Operator category $categoryName"
                                        $sql = $operatorCategory.Script() | Out-String
                                        Write-Message -Level Debug -Message $sql
                                        $destServer.Query($sql)
                                    } catch {
                                        $copyOperatorCategoryStatus.Status = "Failed"
                                        $copyOperatorCategoryStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
                                        Stop-Function -Message "Issue dropping operator category" -Target $categoryName -ErrorRecord $_
                                    }
                                }
                            }
                        } else {
                            if ($Pscmdlet.ShouldProcess($destinstance, "Creating Operator category $categoryName")) {
                                try {
                                    Write-Message -Level Verbose -Message "Copying Operator category $categoryName"
                                    $sql = $operatorCategory.Script() | Out-String
                                    Write-Message -Level Debug -Message $sql
                                    $destServer.Query($sql)
    
                                    $copyOperatorCategoryStatus.Status = "Successful"
                                    $copyOperatorCategoryStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
                                } catch {
                                    $copyOperatorCategoryStatus.Status = "Failed"
                                    $copyOperatorCategoryStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
                                    Stop-Function -Message "Issue copying operator category" -Target $categoryName -ErrorRecord $_
                                }
                            }
                        }
                    }
                }
            }
    
            function Copy-AlertCategory {
                <#
                    .SYNOPSIS
                        Copy-AlertCategory migrates alert categories from one SQL Server to another.
    
                    .DESCRIPTION
                        By default, all alert categories are copied. The -AlertCategories parameter is auto-populated for command-line completion and can be used to copy only specific alert categories.
    
                        If the associated credential for the category does not exist on the destination, it will be skipped. If the alert category already exists on the destination, it will be skipped unless -Force is used.
                #>
                [CmdletBinding(DefaultParameterSetName = "Default", SupportsShouldprocess = $true)]
                param (
                    [string[]]$AlertCategories
                )
    
                process {
                    if ($sourceServer.VersionMajor -lt 9 -or $destServer.VersionMajor -lt 9) {
                        throw "Server AlertCategories are only supported in SQL Server 2005 and above. Quitting."
                    }
    
                    $serverAlertCategories = $sourceServer.JobServer.AlertCategories | Where-Object ID -ge 100
                    $destAlertCategories = $destServer.JobServer.AlertCategories | Where-Object ID -ge 100
    
                    foreach ($alertCategory in $serverAlertCategories) {
                        $categoryName = $alertCategory.Name
    
                        $copyAlertCategoryStatus = [pscustomobject]@{
                            SourceServer      = $sourceServer.Name
                            DestinationServer = $destServer.Name
                            Type              = "Agent Alert Category"
                            Name              = $categoryName
                            Status            = $null
                            Notes             = $null
                            DateTime          = [Sqlcollaborative.Dbatools.Utility.DbaDateTime](Get-Date)
                        }
    
                        if ($alertCategories.Length -gt 0 -and $alertCategories -notcontains $categoryName) {
                            continue
                        }
    
                        if ($destAlertCategories.Name -contains $alertCategory.name) {
                            if ($force -eq $false) {
                                $copyAlertCategoryStatus.Status = "Skipped"
                                $copyAlertCategoryStatus.Notes = "Already exists"
                                $copyAlertCategoryStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
                                Write-Message -Level Verbose -Message "Alert category $categoryName exists at destination. Use -Force to drop and migrate."
                                continue
                            } else {
                                if ($Pscmdlet.ShouldProcess($destinstance, "Dropping alert category $categoryName and recreating")) {
                                    try {
                                        Write-Message -Level Verbose -Message "Dropping Alert category $categoryName"
                                        $destServer.JobServer.AlertCategories[$categoryName].Drop()
                                        Write-Message -Level Verbose -Message "Copying Alert category $categoryName"
                                        $sql = $alertcategory.Script() | Out-String
                                        Write-Message -Level Debug -Message "SQL Statement: $sql"
                                        $destServer.Query($sql)
                                    } catch {
                                        $copyAlertCategoryStatus.Status = "Failed"
                                        $copyAlertCategoryStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
                                        Stop-Function -Message "Issue dropping alert category" -Target $categoryName -ErrorRecord $_
                                    }
                                }
                            }
                        } else {
                            if ($Pscmdlet.ShouldProcess($destinstance, "Creating Alert category $categoryName")) {
                                try {
                                    Write-Message -Level Verbose -Message "Copying Alert category $categoryName"
                                    $sql = $alertCategory.Script() | Out-String
                                    Write-Message -Level Debug -Message $sql
                                    $destServer.Query($sql)
    
                                    $copyAlertCategoryStatus.Status = "Successful"
                                    $copyAlertCategoryStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
                                } catch {
                                    $copyAlertCategoryStatus.Status = "Failed"
                                    $copyAlertCategoryStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
                                    Stop-Function -Message "Issue creating alert category" -Target $categoryName -ErrorRecord $_
                                }
                            }
                        }
                    }
                }
            }
    
            try {
                $sourceServer = Connect-SqlInstance -SqlInstance $Source -SqlCredential $SourceSqlCredential
            } catch {
                Stop-Function -Message "Failure" -Category ConnectionError -ErrorRecord $_ -Target $Source
                return
            }
        }
        process {
            if (Test-FunctionInterrupt) { return }
            foreach ($destinstance in $Destination) {
                try {
                    $destServer = Connect-SqlInstance -SqlInstance $destinstance -SqlCredential $DestinationSqlCredential
                } catch {
                    Stop-Function -Message "Failure" -Category ConnectionError -ErrorRecord $_ -Target $destinstance -Continue
                }
    
                if ($CategoryType.count -gt 0) {
    
                    switch ($CategoryType) {
                        "Job" {
                            Copy-JobCategory
                        }
    
                        "Alert" {
                            Copy-AlertCategory
                        }
    
                        "Operator" {
                            Copy-OperatorCategory
                        }
                    }
                    continue
                }
    
                if (($OperatorCategory.Count + $AlertCategory.Count + $jobCategory.Count) -gt 0) {
    
                    if ($OperatorCategory.Count -gt 0) {
                        Copy-OperatorCategory -OperatorCategories $OperatorCategory
                    }
    
                    if ($AlertCategory.Count -gt 0) {
                        Copy-AlertCategory -AlertCategories $AlertCategory
                    }
    
                    if ($jobCategory.Count -gt 0) {
                        Copy-JobCategory -JobCategories $jobCategory
                    }
                    continue
                }
                Copy-OperatorCategory
                Copy-AlertCategory
                Copy-JobCategory
            }
        }
        end {
            Test-DbaDeprecation -DeprecatedOn "1.0.0" -EnableException:$false -Alias Copy-SqlAgentCategory
        }
    }
    function Copy-DbaAgentJob {
        <#
        .SYNOPSIS
            Copy-DbaAgentJob migrates jobs from one SQL Server to another.
    
        .DESCRIPTION
            By default, all jobs are copied. The -Job parameter is auto-populated for command-line completion and can be used to copy only specific jobs.
    
            If the job already exists on the destination, it will be skipped unless -Force is used.
    
        .PARAMETER Source
            Source SQL Server. You must have sysadmin access and server version must be SQL Server version 2000 or higher.
    
        .PARAMETER SourceSqlCredential
            Login to the target instance using alternative credentials. Windows and SQL Authentication supported. Accepts credential objects (Get-Credential)
    
        .PARAMETER Destination
            Destination SQL Server. You must have sysadmin access and the server must be SQL Server 2000 or higher.
    
        .PARAMETER DestinationSqlCredential
            Login to the target instance using alternative credentials. Windows and SQL Authentication supported. Accepts credential objects (Get-Credential)
    
        .PARAMETER Job
            The job(s) to process. This list is auto-populated from the server. If unspecified, all jobs will be processed.
    
        .PARAMETER ExcludeJob
            The job(s) to exclude. This list is auto-populated from the server.
    
        .PARAMETER DisableOnSource
            If this switch is enabled, the job will be disabled on the source server.
    
        .PARAMETER DisableOnDestination
            If this switch is enabled, the newly migrated job will be disabled on the destination server.
    
        .PARAMETER WhatIf
            If this switch is enabled, no actions are performed but informational messages will be displayed that explain what would happen if the command were to run.
    
        .PARAMETER Confirm
            If this switch is enabled, you will be prompted for confirmation before executing any operations that change state.
    
        .PARAMETER Force
            If this switch is enabled, the Job will be dropped and recreated on Destination.
    
        .PARAMETER EnableException
            By default, when something goes wrong we try to catch it, interpret it and give you a friendly warning message.
            This avoids overwhelming you with "sea of red" exceptions, but is inconvenient because it basically disables advanced scripting.
            Using this switch turns this "nice by default" feature off and enables you to catch exceptions with your own try/catch.
    
        .NOTES
            Tags: Migration, Agent, Job
            Author: Chrissy LeMaire (@cl), netnerds.net
    
            Website: https://dbatools.io
            Copyright: (c) 2018 by dbatools, licensed under MIT
            License: MIT https://opensource.org/licenses/MIT
    
        .LINK
            https://dbatools.io/Copy-DbaAgentJob
    
        .EXAMPLE
            PS C:\> Copy-DbaAgentJob -Source sqlserver2014a -Destination sqlcluster
    
            Copies all jobs from sqlserver2014a to sqlcluster, using Windows credentials. If jobs with the same name exist on sqlcluster, they will be skipped.
    
        .EXAMPLE
            PS C:\> Copy-DbaAgentJob -Source sqlserver2014a -Destination sqlcluster -Job PSJob -SourceSqlCredential $cred -Force
    
            Copies a single job, the PSJob job from sqlserver2014a to sqlcluster, using SQL credentials for sqlserver2014a and Windows credentials for sqlcluster. If a job with the same name exists on sqlcluster, it will be dropped and recreated because -Force was used.
    
        .EXAMPLE
            PS C:\> Copy-DbaAgentJob -Source sqlserver2014a -Destination sqlcluster -WhatIf -Force
    
            Shows what would happen if the command were executed using force.
    
        #>
        [cmdletbinding(DefaultParameterSetName = "Default", SupportsShouldProcess = $true)]
        param (
            [parameter(Mandatory)]
            [DbaInstanceParameter]$Source,
            [PSCredential]$SourceSqlCredential,
            [parameter(Mandatory)]
            [DbaInstanceParameter[]]$Destination,
            [PSCredential]$DestinationSqlCredential,
            [object[]]$Job,
            [object[]]$ExcludeJob,
            [switch]$DisableOnSource,
            [switch]$DisableOnDestination,
            [switch]$Force,
            [Alias('Silent')]
            [switch]$EnableException
        )
        begin {
            try {
                $sourceServer = Connect-SqlInstance -SqlInstance $Source -SqlCredential $SourceSqlCredential
            } catch {
                Stop-Function -Message "Failure" -Category ConnectionError -ErrorRecord $_ -Target $Source
                return
            }
    
            $serverJobs = $sourceServer.JobServer.Jobs
        }
        process {
            if (Test-FunctionInterrupt) { return }
            foreach ($destinstance in $Destination) {
                try {
                    $destServer = Connect-SqlInstance -SqlInstance $destinstance -SqlCredential $DestinationSqlCredential
                } catch {
                    Stop-Function -Message "Failure" -Category ConnectionError -ErrorRecord $_ -Target $destinstance -Continue
                }
                $destJobs = $destServer.JobServer.Jobs
    
                foreach ($serverJob in $serverJobs) {
                    $jobName = $serverJob.name
                    $jobId = $serverJob.JobId
    
                    $copyJobStatus = [pscustomobject]@{
                        SourceServer      = $sourceServer.Name
                        DestinationServer = $destServer.Name
                        Name              = $jobName
                        Type              = "Agent Job"
                        Status            = $null
                        Notes             = $null
                        DateTime          = [DbaDateTime](Get-Date)
                    }
    
                    if ($Job -and $jobName -notin $Job -or $jobName -in $ExcludeJob) {
                        Write-Message -Level Verbose -Message "Job [$jobName] filtered. Skipping."
                        continue
                    }
                    Write-Message -Message "Working on job: $jobName" -Level Verbose
                    $sql = "
                    SELECT sp.[name] AS MaintenancePlanName
                    FROM msdb.dbo.sysmaintplan_plans AS sp
                    INNER JOIN msdb.dbo.sysmaintplan_subplans AS sps
                        ON sps.plan_id = sp.id
                    WHERE job_id = '$($jobId)'"
                    Write-Message -Message $sql -Level Debug
    
                    $MaintenancePlanName = $sourceServer.Query($sql).MaintenancePlanName
    
                    if ($MaintenancePlanName) {
                        if ($Pscmdlet.ShouldProcess($destinstance, "Job [$jobName] is associated with Maintenance Plan: $MaintenancePlanNam")) {
                            $copyJobStatus.Status = "Skipped"
                            $copyJobStatus.Notes = "Job is associated with maintenance plan"
                            $copyJobStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
                            Write-Message -Level Verbose -Message "Job [$jobName] is associated with Maintenance Plan: $MaintenancePlanName"
                        }
                        continue
                    }
    
                    $dbNames = ($serverJob.JobSteps | where-object {$_.SubSystem -ne 'ActiveScripting'}).DatabaseName | Where-Object { $_.Length -gt 0 }
                    $missingDb = $dbNames | Where-Object { $destServer.Databases.Name -notcontains $_ }
    
                    if ($missingDb.Count -gt 0 -and $dbNames.Count -gt 0) {
                        if ($Pscmdlet.ShouldProcess($destinstance, "Database(s) $missingDb doesn't exist on destination. Skipping job [$jobName].")) {
                            $missingDb = ($missingDb | Sort-Object | Get-Unique) -join ", "
                            $copyJobStatus.Status = "Skipped"
                            $copyJobStatus.Notes = "Job is dependent on database: $missingDb"
                            $copyJobStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
                            Write-Message -Level Verbose -Message "Database(s) $missingDb doesn't exist on destination. Skipping job [$jobName]."
                        }
                        continue
                    }
    
                    $missingLogin = $serverJob.OwnerLoginName | Where-Object { $destServer.Logins.Name -notcontains $_ }
    
                    if ($missingLogin.Count -gt 0) {
                        if ($force -eq $false) {
                            if ($Pscmdlet.ShouldProcess($destinstance, "Login(s) $missingLogin doesn't exist on destination. Use -Force to set owner to [sa]. Skipping job [$jobName].")) {
                                $missingLogin = ($missingLogin | Sort-Object | Get-Unique) -join ", "
                                $copyJobStatus.Status = "Skipped"
                                $copyJobStatus.Notes = "Job is dependent on login $missingLogin"
                                $copyJobStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
                                Write-Message -Level Verbose -Message "Login(s) $missingLogin doesn't exist on destination. Use -Force to set owner to [sa]. Skipping job [$jobName]."
                            }
                            continue
                        }
                    }
    
                    $proxyNames = $serverJob.JobSteps.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
    
            Website: https://dbatools.io
            Copyright: (c) 2018 by dbatools, licensed under MIT
            License: MIT https://opensource.org/licenses/MIT
    
            Requires: sysadmin access on SQL Servers
    
        .LINK
            https://dbatools.io/Copy-DbaAgentOperator
    
        .EXAMPLE
            PS C:\> Copy-DbaAgentOperator -Source sqlserver2014a -Destination sqlcluster
    
            Copies all operators from sqlserver2014a to sqlcluster using Windows credentials. If operators with the same name exist on sqlcluster, they will be skipped.
    
        .EXAMPLE
            PS C:\> Copy-DbaAgentOperator -Source sqlserver2014a -Destination sqlcluster -Operator PSOperator -SourceSqlCredential $cred -Force
    
            Copies only the PSOperator operator from sqlserver2014a to sqlcluster using SQL credentials for sqlserver2014a and Windows credentials for sqlcluster. If an operator with the same name exists on sqlcluster, it will be dropped and recreated because -Force was used.
    
        .EXAMPLE
            PS C:\> Copy-DbaAgentOperator -Source sqlserver2014a -Destination sqlcluster -WhatIf -Force
    
            Shows what would happen if the command were executed using force.
    
        #>
        [CmdletBinding(DefaultParameterSetName = "Default", SupportsShouldProcess = $true)]
        param (
            [parameter(Mandatory)]
            [DbaInstanceParameter]$Source,
            [PSCredential]
            $SourceSqlCredential,
            [parameter(Mandatory)]
            [DbaInstanceParameter[]]$Destination,
            [PSCredential]
            $DestinationSqlCredential,
            [object[]]$Operator,
            [object[]]$ExcludeOperator,
            [switch]$Force,
            [Alias('Silent')]
            [switch]$EnableException
        )
    
        begin {
            try {
                $sourceServer = Connect-SqlInstance -SqlInstance $Source -SqlCredential $SourceSqlCredential
            } catch {
                Stop-Function -Message "Failure" -Category ConnectionError -ErrorRecord $_ -Target $Source
                return
            }
            $serverOperator = $sourceServer.JobServer.Operators
        }
        process {
            if (Test-FunctionInterrupt) { return }
            foreach ($destinstance in $Destination) {
                try {
                    $destServer = Connect-SqlInstance -SqlInstance $destinstance -SqlCredential $DestinationSqlCredential
                } catch {
                    Stop-Function -Message "Failure" -Category ConnectionError -ErrorRecord $_ -Target $destinstance -Continue
                }
    
                $destOperator = $destServer.JobServer.Operators
                $failsafe = $destServer.JobServer.AlertSystem | Select-Object FailSafeOperator
                foreach ($sOperator in $serverOperator) {
                    $operatorName = $sOperator.Name
    
                    $copyOperatorStatus = [pscustomobject]@{
                        SourceServer      = $sourceServer.Name
                        DestinationServer = $destServer.Name
                        Name              = $operatorName
                        Type              = "Agent Operator"
                        Status            = $null
                        Notes             = $null
                        DateTime          = [DbaDateTime](Get-Date)
                    }
    
                    if ($Operator -and $Operator -notcontains $operatorName -or $ExcludeOperator -in $operatorName) {
                        continue
                    }
    
                    if ($destOperator.Name -contains $sOperator.Name) {
                        if ($force -eq $false) {
                            if ($Pscmdlet.ShouldProcess($destinstance, "Operator $operatorName exists at destination. Use -Force to drop and migrate.")) {
                                $copyOperatorStatus.Status = "Skipped"
                                $copyOperatorStatus.Notes = "Already exists"
                                $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
    
            Website: https://dbatools.io
            Copyright: (c) 2018 by dbatools, licensed under MIT
            License: MIT https://opensource.org/licenses/MIT
    
            Requires: sysadmin access on SQL Servers
    
        .LINK
            https://dbatools.io/Copy-DbaAgentProxyAccount
    
        .EXAMPLE
            PS C:\> 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
            PS C:\> 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
            PS C:\> Copy-DbaAgentProxyAccount -Source sqlserver2014a -Destination sqlcluster -WhatIf -Force
    
            Shows what would happen if the command were executed using force.
    
        #>
        [CmdletBinding(DefaultParameterSetName = "Default", SupportsShouldProcess = $true)]
        param (
            [parameter(Mandatory)]
            [DbaInstanceParameter]$Source,
            [PSCredential]$SourceSqlCredential,
            [parameter(Mandatory)]
            [DbaInstanceParameter[]]$Destination,
            [PSCredential]$DestinationSqlCredential,
            [string[]]$ProxyAccount,
            [string[]]$ExcludeProxyAccount,
            [switch]$Force,
            [Alias('Silent')]
            [switch]$EnableException
        )
        begin {
            try {
                $sourceServer = Connect-SqlInstance -SqlInstance $Source -SqlCredential $SourceSqlCredential -MinimumVersion 9
            } catch {
                Stop-Function -Message "Failure" -Category ConnectionError -ErrorRecord $_ -Target $Source
                return
            }
            $serverProxyAccounts = $sourceServer.JobServer.ProxyAccounts
            if ($ProxyAccount) {
                $serverProxyAccounts | Where-Object Name -in $ProxyAccount
            }
            if ($ExcludeProxyAccount) {
                $serverProxyAccounts | Where-Object Name -notin $ProxyAccount
            }
        }
        process {
            if (Test-FunctionInterrupt) { return }
            foreach ($destinstance in $Destination) {
                try {
                    $destServer = Connect-SqlInstance -SqlInstance $destinstance -SqlCredential $DestinationSqlCredential -MinimumVersion 9
                } catch {
                    Stop-Function -Message "Failure" -Category ConnectionError -ErrorRecord $_ -Target $destinstance -Continue
                }
    
                $destProxyAccounts = $destServer.JobServer.ProxyAccounts
    
                foreach ($account in $serverProxyAccounts) {
                    $proxyName = $account.Name
    
                    $copyAgentProxyAccountStatus = [pscustomobject]@{
                        SourceServer      = $sourceServer.Name
                        DestinationServer = $destServer.Name
                        Name              = $null
                        Type              = "Agent Proxy"
                        Status            = $null
                        Notes             = $null
                        DateTime          = [Sqlcollaborative.Dbatools.Utility.DbaDateTime](Get-Date)
                    }
    
                    # Proxy accounts rely on Credential accounts
                    $credentialName = $account.CredentialName
                    $copyAgentProxyAccountStatus.Name = $credentialName
                    $copyAgentProxyAccountStatus.Type = "Credential"
    
                    try {
                        $credentialtest = $destServer.Credentials[$CredentialName]
                    } catch {
                        #here to avoid an empty catch
                        $null = 1
                    }
    
                    if ($null -eq $credentialtest) {
                        $copyAgentProxyAccountStatus.Status = "Skipped"
                        $copyAgentProxyAccountStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
                        Write-Message -Level Verbose -Message "Associated credential account, $CredentialName, does not exist on $destinstance. Skipping migration of $proxyName."
                        continue
                    }
    
                    if ($destProxyAccounts.Name -contains $proxyName) {
                        $copyAgentProxyAccountStatus.Name = $proxyName
                        $copyAgentProxyAccountStatus.Type = "ProxyAccount"
    
                        if ($force -eq $false) {
                            $copyAgentProxyAccountStatus.Status = "Skipped"
                            $copyAgentProxyAccountStatus
                            Write-Message -Level Verbose -Message "Server proxy account $proxyName exists at destination. Use -Force to drop and migrate."
                            continue
                        } else {
                            if ($Pscmdlet.ShouldProcess($destinstance, "Dropping server proxy account $proxyName and recreating")) {
                                try {
                                    Write-Message -Level Verbose -Message "Dropping server proxy account $proxyName"
                                    $destServer.JobServer.ProxyAccounts[$proxyName].Drop()
                                } catch {
                                    $copyAgentProxyAccountStatus.Status = "Failed"
                                    $copyAgentProxyAccountStatus.Notes = "Could not drop"
                                    $copyAgentProxyAccountStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
                                    Stop-Function -Message "Issue dropping proxy account" -Target $proxyName -ErrorRecord $_ -Continue
                                }
                            }
                        }
                    }
    
                    if ($Pscmdlet.ShouldProcess($destinstance, "Creating server proxy account $proxyName")) {
                        $copyAgentProxyAccountStatus.Name = $proxyName
                        $copyAgentProxyAccountStatus.Type = "ProxyAccount"
    
                        try {
                            Write-Message -Level Verbose -Message "Copying server proxy account $proxyName"
                            $sql = $account.Script() | Out-String
                            Write-Message -Level Debug -Message $sql
                            $destServer.Query($sql)
    
                            # Will fixing this misspelled status cause problems downstream?
                            $copyAgentProxyAccountStatus.Status = "Successful"
                            $copyAgentProxyAccountStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
                        } catch {
                            $exceptionstring = $_.Exception.InnerException.ToString()
                            if ($exceptionstring -match 'subsystem') {
                                $copyAgentProxyAccountStatus.Status = "Skipping"
                                $copyAgentProxyAccountStatus.Notes = "Failure"
                                $copyAgentProxyAccountStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
    
                                Write-Message -Level Verbose -Message "One or more subsystems do not exist on the destination server. Skipping that part."
                            } else {
                                $copyAgentProxyAccountStatus.Status = "Failed"
                                $copyAgentProxyAccountStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
    
                                Stop-Function -Message "Issue creating proxy account" -Target $proxyName -ErrorRecord $_
                            }
                        }
                    }
                }
            }
        }
        end {
            Test-DbaDeprecation -DeprecatedOn "1.0.0" -EnableException:$false -Alias Copy-SqlProxyAccount
        }
    }
    function Copy-DbaAgentServer {
        <#
        .SYNOPSIS
            Copy SQL Server Agent from one server to another.
    
        .DESCRIPTION
            A wrapper function that calls the associated Copy command for each of the object types seen in SSMS under SQL Server Agent. This also copies all of the the SQL Agent properties (job history max rows, DBMail profile name, etc.).
    
            You must have sysadmin access and server version must be SQL Server version 2000 or greater.
    
        .PARAMETER Source
            Source SQL Server. You must have sysadmin access and server version must be SQL Server version 2000 or higher.
    
        .PARAMETER SourceSqlCredential
            Login to the target instance using alternative credentials. Windows and SQL Authentication supported. Accepts credential objects (Get-Credential)
    
        .PARAMETER Destination
            Destination SQL Server. You must have sysadmin access and the server must be SQL Server 2000 or higher.
    
        .PARAMETER DestinationSqlCredential
            Login to the target instance using alternative credentials. Windows and SQL Authentication supported. Accepts credential objects (Get-Credential)
    
        .PARAMETER DisableJobsOnDestination
            If this switch is enabled, the jobs will be disabled on Destination after copying.
    
        .PARAMETER DisableJobsOnSource
            If this switch is enabled, the jobs will be disabled on Source after copying.
    
        .PARAMETER WhatIf
            If this switch is enabled, no actions are performed but informational messages will be displayed that explain what would happen if the command were to run.
    
        .PARAMETER Confirm
            If this switch is enabled, you will be prompted for confirmation before executing any operations that change state.
    
        .PARAMETER EnableException
            By default, when something goes wrong we try to catch it, interpret it and give you a friendly warning message.
            This avoids overwhelming you with "sea of red" exceptions, but is inconvenient because it basically disables advanced scripting.
            Using this switch turns this "nice by default" feature off and enables you to catch exceptions with your own try/catch.
    
        .PARAMETER Force
            If this switch is enabled, existing objects on Destination with matching names from Source will be dropped, then copied.
    
        .NOTES
            Tags: Migration, SqlServerAgent, SqlAgent
            Author: Chrissy LeMaire (@cl), netnerds.net
    
            Website: https://dbatools.io
            Copyright: (c) 2018 by dbatools, licensed under MIT
            License: MIT https://opensource.org/licenses/MIT
    
            Requires: sysadmin access on SQL Servers
    
        .LINK
            https://dbatools.io/Copy-DbaAgentServer
    
        .EXAMPLE
            PS C:\> Copy-DbaAgentServer -Source sqlserver2014a -Destination sqlcluster
    
            Copies all job server objects from sqlserver2014a to sqlcluster using Windows credentials for authentication. If job objects with the same name exist on sqlcluster, they will be skipped.
    
        .EXAMPLE
            PS C:\> Copy-DbaAgentServer -Source sqlserver2014a -Destination sqlcluster -SourceSqlCredential $cred
    
            Copies all job objects from sqlserver2014a to sqlcluster using SQL credentials to authentication to sqlserver2014a and Windows credentials to authenticate to sqlcluster.
    
        .EXAMPLE
            PS C:\> Copy-DbaAgentServer -Source sqlserver2014a -Destination sqlcluster -WhatIf
    
            Shows what would happen if the command were executed.
    
        #>
        [cmdletbinding(SupportsShouldProcess = $true)]
        param (
            [parameter(Mandatory)]
            [DbaInstanceParameter]$Source,
            [PSCredential]$SourceSqlCredential,
            [parameter(Mandatory)]
            [DbaInstanceParameter[]]$Destination,
            [PSCredential]$DestinationSqlCredential,
            [Switch]$DisableJobsOnDestination,
            [Switch]$DisableJobsOnSource,
            [switch]$Force,
            [Alias('Silent')]
            [switch]$EnableException
        )
    
        begin {
            try {
                $sourceServer = Connect-SqlInstance -SqlInstance $Source -SqlCredential $SourceSqlCredential
            } catch {
                Stop-Function -Message "Failure" -Category ConnectionError -ErrorRecord $_ -Target $Source
                return
            }
            Invoke-SmoCheck -SqlInstance $sourceServer
            $sourceAgent = $sourceServer.JobServer
        }
        process {
            if (Test-FunctionInterrupt) { return }
            foreach ($destinstance in $Destination) {
                try {
                    $destServer = Connect-SqlInstance -SqlInstance $destinstance -SqlCredential $DestinationSqlCredential
                } catch {
                    Stop-Function -Message "Failure" -Category ConnectionError -ErrorRecord $_ -Target $destinstance -Continue
                }
                Invoke-SmoCheck -SqlInstance $destServer
                # All of these support whatif inside of them
                Copy-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("[email protected]_file=")
                        $sql = $sql -replace [Regex]::Escape("@auto_start="), [Regex]::Escape("[email protected]_start=")
                        Write-Message -Level Debug -Message $sql
                        $null = $destServer.Query($sql)
    
                        $copyAgentPropStatus.Status = "Successful"
                        $copyAgentPropStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
                    } catch {
                        $message = $_.Exception.InnerException.InnerException.InnerException.Message
                        if (-not $message) { $message = $_.Exception.Message }
                        $copyAgentPropStatus.Status = "Failed"
                        $copyAgentPropStatus.Notes = $message
                        $copyAgentPropStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
                        Stop-Function -Message $message -Target $destinstance
                    }
                }
            }
        }
        end {
            Test-DbaDeprecation -DeprecatedOn "1.0.0" -EnableException:$false -Alias Copy-SqlServerAgent
            Test-DbaDeprecation -DeprecatedOn "1.0.0" -EnableException:$false -Alias Copy-DbaSqlServerAgent
        }
    }
    function Copy-DbaAgentSharedSchedule {
        <#
        .SYNOPSIS
            Copy-DbaAgentSharedSchedule migrates shared job schedules from one SQL Server to another.
    
        .DESCRIPTION
            All shared job schedules are copied.
    
            If the associated credential for the account does not exist on the destination, it will be skipped. If the shared job schedule already exists on the destination, it will be skipped unless -Force is used.
    
        .PARAMETER Source
            Source SQL Server. You must have sysadmin access and server version must be SQL Server version 2000 or higher.
    
        .PARAMETER SourceSqlCredential
            Login to the target instance using alternative credentials. Windows and SQL Authentication supported. Accepts credential objects (Get-Credential)
    
        .PARAMETER Destination
            Destination SQL Server. You must have sysadmin access and the server must be SQL Server 2000 or higher.
    
        .PARAMETER DestinationSqlCredential
            Login to the target instance using alternative credentials. Windows and SQL Authentication supported. Accepts credential objects (Get-Credential)
    
        .PARAMETER WhatIf
            If this switch is enabled, no actions are performed but informational messages will be displayed that explain what would happen if the command were to run.
    
        .PARAMETER Confirm
            If this switch is enabled, you will be prompted for confirmation before executing any operations that change state.
    
        .PARAMETER Force
            If this switch is enabled, the Operator will be dropped and recreated on Destination.
    
        .PARAMETER EnableException
            By default, when something goes wrong we try to catch it, interpret it and give you a friendly warning message.
            This avoids overwhelming you with "sea of red" exceptions, but is inconvenient because it basically disables advanced scripting.
            Using this switch turns this "nice by default" feature off and enables you to catch exceptions with your own try/catch.
    
        .NOTES
            Tags: Migration, Agent
            Author: Chrissy LeMaire (@cl), netnerds.net
    
            Website: https://dbatools.io
            Copyright: (c) 2018 by dbatools, licensed under MIT
            License: MIT https://opensource.org/licenses/MIT
    
            Requires: sysadmin access on SQL Servers
    
        .LINK
            https://dbatools.io/Copy-DbaAgentSharedSchedule
    
        .EXAMPLE
            PS C:\> 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
            PS C:\> Copy-DbaAgentSharedSchedule -Source sqlserver2014a -Destination sqlcluster -WhatIf -Force
    
            Shows what would happen if the command were executed using force.
    
        #>
        [CmdletBinding(DefaultParameterSetName = "Default", SupportsShouldProcess = $true)]
        param (
            [parameter(Mandatory)]
            [DbaInstanceParameter]$Source,
            [PSCredential]
            $SourceSqlCredential,
            [parameter(Mandatory)]
            [DbaInstanceParameter[]]$Destination,
            [PSCredential]
            $DestinationSqlCredential,
            [switch]$Force,
            [Alias('Silent')]
            [switch]$EnableException
        )
        begin {
            try {
                $sourceServer = Connect-SqlInstance -SqlInstance $Source -SqlCredential $SourceSqlCredential -MinimumVersion 9
            } catch {
                Stop-Function -Message "Failure" -Category ConnectionError -ErrorRecord $_ -Target $Source
                return
            }
            $serverSchedules = $sourceServer.JobServer.SharedSchedules
        }
        process {
            if (Test-FunctionInterrupt) { return }
            foreach ($destinstance in $Destination) {
                try {
                    $destServer = Connect-SqlInstance -SqlInstance $destinstance -SqlCredential $DestinationSqlCredential -MinimumVersion 9
                } catch {
                    Stop-Function -Message "Failure" -Category ConnectionError -ErrorRecord $_ -Target $destinstance -Continue
                }
    
                $destSchedules = $destServer.JobServer.SharedSchedules
                foreach ($schedule in $serverSchedules) {
                    $scheduleName = $schedule.Name
                    $copySharedScheduleStatus = [pscustomobject]@{
                        SourceServer      = $sourceServer.Name
                        DestinationServer = $destServer.Name
                        Type              = "Agent Schedule"
                        Name              = $scheduleName
                        Status            = $null
                        Notes             = $null
                        DateTime          = [Sqlcollaborative.Dbatools.Utility.DbaDateTime](Get-Date)
                    }
    
                    if ($schedules.Length -gt 0 -and $schedules -notcontains $scheduleName) {
                        continue
                    }
    
                    if ($destSchedules.Name -contains $scheduleName) {
                        if ($force -eq $false) {
                            if ($Pscmdlet.ShouldProcess($destinstance, "Shared job schedule $scheduleName exists at destination. Use -Force to drop and migrate.")) {
                                $copySharedScheduleStatus.Status = "Skipped"
                                $copySharedScheduleStatus.Notes = "Already exists"
                                $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
    
            Website: https://dbatools.io
            Copyright: (c) 2018 by dbatools, licensed under MIT
            License: MIT https://opensource.org/licenses/MIT
    
            Requires: sysadmin access on SQL Servers
    
        .LINK
            https://dbatools.io/Copy-DbaBackupDevice
    
        .EXAMPLE
            PS C:\> Copy-DbaBackupDevice -Source sqlserver2014a -Destination sqlcluster
    
            Copies all server backup devices from sqlserver2014a to sqlcluster using Windows credentials. If backup devices with the same name exist on sqlcluster, they will be skipped.
    
        .EXAMPLE
            PS C:\> Copy-DbaBackupDevice -Source sqlserver2014a -Destination sqlcluster -BackupDevice backup01 -SourceSqlCredential $cred -Force
    
            Copies only the backup device named backup01 from sqlserver2014a to sqlcluster using SQL credentials for sqlserver2014a    and Windows credentials for sqlcluster. If a backup device with the same name exists on sqlcluster, it will be dropped and recreated because -Force was used.
    
        .EXAMPLE
            PS C:\> Copy-DbaBackupDevice -Source sqlserver2014a -Destination sqlcluster -WhatIf -Force
    
            Shows what would happen if the command were executed using force.
    
        #>
        [CmdletBinding(DefaultParameterSetName = "Default", SupportsShouldProcess = $true)]
        param (
            [parameter(Mandatory)]
            [DbaInstanceParameter]$Source,
            [PSCredential]$SourceSqlCredential,
            [parameter(Mandatory)]
            [DbaInstanceParameter[]]$Destination,
            [PSCredential]$DestinationSqlCredential,
            [object[]]$BackupDevice,
            [switch]$Force,
            [Alias('Silent')]
            [switch]$EnableException
        )
        begin {
            try {
                $sourceServer = Connect-SqlInstance -SqlInstance $Source -SqlCredential $SourceSqlCredential
            } catch {
                Stop-Function -Message "Failure" -Category ConnectionError -ErrorRecord $_ -Target $Source
                return
            }
            $serverBackupDevices = $sourceServer.BackupDevices
            $sourceNetBios = $Source.ComputerName
        }
        process {
            if (Test-FunctionInterrupt) { return }
            foreach ($destinstance in $Destination) {
                try {
                    $destServer = Connect-SqlInstance -SqlInstance $destinstance -SqlCredential $DestinationSqlCredential
                } catch {
                    Stop-Function -Message "Failure" -Category ConnectionError -ErrorRecord $_ -Target $destinstance -Continue
                }
                $destBackupDevices = $destServer.BackupDevices
                $destNetBios = $destinstance.ComputerName
    
                foreach ($currentBackupDevice in $serverBackupDevices) {
                    $deviceName = $currentBackupDevice.Name
    
                    $copyBackupDeviceStatus = [pscustomobject]@{
                        SourceServer      = $sourceServer.Name
                        DestinationServer = $destServer.Name
                        Name              = $deviceName
                        Type              = "Backup Device"
                        Status            = $null
                        Notes             = $null
                        DateTime          = [Sqlcollaborative.Dbatools.Utility.DbaDateTime](Get-Date)
                    }
    
                    if ($BackupDevice -and $BackupDevice -notcontains $deviceName) {
                        continue
                    }
    
                    if ($destBackupDevices.Name -contains $deviceName) {
                        if ($force -eq $false) {
                            $copyBackupDeviceStatus.Status = "Skipped"
                            $copyBackupDeviceStatus.Notes = "Already exists"
                            $copyBackupDeviceStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
    
                            Write-Message -Level Verbose -Message "backup device $deviceName exists at destination. Use -Force to drop and migrate."
                            continue
                        } else {
                            if ($Pscmdlet.ShouldProcess($destinstance, "Dropping backup device $deviceName")) {
                                try {
                                    Write-Message -Level Verbose -Message "Dropping backup device $deviceName"
                                    $destServer.BackupDevices[$deviceName].Drop()
                                } catch {
                                    $copyBackupDeviceStatus.Status = "Failed"
                                    $copyBackupDeviceStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
    
                                    Stop-Function -Message "Issue dropping backup device" -Target $deviceName -ErrorRecord $_ -Continue
                                }
                            }
                        }
                    }
    
                    if ($Pscmdlet.ShouldProcess($destinstance, "Generating SQL code for $deviceName")) {
                        Write-Message -Level Verbose -Message "Scripting out SQL for $deviceName"
                        try {
                            $sql = $currentBackupDevice.Script() | Out-String
                            $sql = $sql -replace [Regex]::Escape("'$source'"), "'$destinstance'"
                        } catch {
                            $copyBackupDeviceStatus.Status = "Failed"
                            $copyBackupDeviceStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
    
                            Stop-Function -Message "Issue scripting out backup device" -Target $deviceName -ErrorRecord $_ -Continue
                        }
                    }
    
                    if ($Pscmdlet.ShouldProcess("console", "Stating that the actual file copy is about to occur")) {
                        Write-Message -Level Verbose -Message "Preparing to copy actual backup file"
                    }
    
                    $path = Split-Path $sourceServer.BackupDevices[$deviceName].PhysicalLocation
                    $destPath = Join-AdminUnc $destNetBios $path
                    $sourcepath = Join-AdminUnc $sourceNetBios $sourceServer.BackupDevices[$deviceName].PhysicalLocation
    
                    Write-Message -Level Verbose -Message "Checking if directory $destPath exists"
    
                    if ($(Test-DbaPath -SqlInstance $destinstance -Path $path) -eq $false) {
                        $backupDirectory = $destServer.BackupDirectory
                        $destPath = Join-AdminUnc $destNetBios $backupDirectory
    
                        if ($Pscmdlet.ShouldProcess($destinstance, "Updating create code to use new path")) {
                            Write-Message -Level Verbose -Message "$path doesn't exist on $destinstance"
                            Write-Message -Level Verbose -Message "Using default backup directory $backupDirectory"
    
                            try {
                                Write-Message -Level Verbose -Message "Updating $deviceName to use $backupDirectory"
                                $sql = $sql -replace [Regex]::Escape($path), $backupDirectory
                            } catch {
                                $copyBackupDeviceStatus.Status = "Failed"
                                $copyBackupDeviceStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
    
                                Stop-Function -Message "Issue updating script of backup device with new path" -Target $deviceName -ErrorRecord $_ -Continue
                            }
                        }
                    }
    
                    if ($Pscmdlet.ShouldProcess($destinstance, "Copying $sourcepath to $destPath using BITSTransfer")) {
                        try {
                            Start-BitsTransfer -Source $sourcepath -Destination $destPath -ErrorAction Stop
                            Write-Message -Level Verbose -Message "Backup device $deviceName successfully copied"
                        } catch {
                            $copyBackupDeviceStatus.Status = "Failed"
                            $copyBackupDeviceStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
    
                            Stop-Function -Message "Issue copying backup device to destination" -Target $deviceName -ErrorRecord $_ -Continue
                        }
                    }
    
                    if ($Pscmdlet.ShouldProcess($destinstance, "Adding backup device $deviceName")) {
                        Write-Message -Level Verbose -Message "Adding backup device $deviceName on $destinstance"
                        try {
                            $destServer.Query($sql)
                            $destServer.BackupDevices.Refresh()
    
                            $copyBackupDeviceStatus.Status = "Successful"
                            $copyBackupDeviceStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
                        } catch {
                            $copyBackupDeviceStatus.Status = "Failed"
                            $copyBackupDeviceStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
    
                            Stop-Function -Message "Issue adding backup device" -Target $deviceName -ErrorRecord $_ -Continue
                        }
                    }
                }
            }
        }
        end {
            Test-DbaDeprecation -DeprecatedOn "1.0.0" -EnableException:$false -Alias Copy-SqlBackupDevice
        }
    }
    function Copy-DbaCentralManagementServer {
        <#
        .SYNOPSIS
            Migrates SQL Server Central Management groups and server instances from one SQL Server to another.
    
        .DESCRIPTION
            Copy-DbaCentralManagementServer copies all groups, subgroups, and server instances from one SQL Server to another.
    
        .PARAMETER Source
            Source SQL Server. You must have sysadmin access and server version must be SQL Server version 2000 or higher.
    
        .PARAMETER SourceSqlCredential
            Login to the target instance using alternative credentials. Windows and SQL Authentication supported. Accepts credential objects (Get-Credential)
    
        .PARAMETER Destination
            Destination SQL Server. You must have sysadmin access and the server must be SQL Server 2000 or higher.
    
        .PARAMETER DestinationSqlCredential
            Login to the target instance using alternative credentials. Windows and SQL Authentication supported. Accepts credential objects (Get-Credential)
    
        .PARAMETER CMSGroup
            This is an auto-populated array that contains your Central Management Server top-level groups on Source. You can specify one, many or none.
    
            If CMSGroup is not specified, all groups in your Central Management Server will be copied.
    
        .PARAMETER SwitchServerName
            If this switch is enabled, all instance names will be changed from Source to Destination.
    
            Central Management Server does not allow you to add a shared registered server with the same name as the Configuration Server.
    
        .PARAMETER Force
            If this switch is enabled, group(s) will be dropped and recreated if they already exists on destination.
    
        .PARAMETER WhatIf
            If this switch is enabled, no actions are performed but informational messages will be displayed that explain what would happen if the command were to run.
    
        .PARAMETER Confirm
            If this switch is enabled, you will be prompted for confirmation before executing any operations that change state.
    
        .PARAMETER EnableException
            By default, when something goes wrong we try to catch it, interpret it and give you a friendly warning message.
            This avoids overwhelming you with "sea of red" exceptions, but is inconvenient because it basically disables advanced scripting.
            Using this switch turns this "nice by default" feature off and enables you to catch exceptions with your own try/catch.
    
        .NOTES
            Tags: Migration
            Author: Chrissy LeMaire (@cl), netnerds.net
    
            Website: https://dbatools.io
            Copyright: (c) 2018 by dbatools, licensed under MIT
            License: MIT https://opensource.org/licenses/MIT
    
            Requires: sysadmin access on SQL Servers
    
        .LINK
            https://dbatools.io/Copy-DbaCentralManagementServer
    
        .EXAMPLE
            PS C:\> Copy-DbaCentralManagementServer -Source sqlserver2014a -Destination sqlcluster
    
            All groups, subgroups, and server instances are copied from sqlserver2014a CMS to sqlcluster CMS.
    
        .EXAMPLE
            PS C:\> Copy-DbaCentralManagementServer -Source sqlserver2014a -Destination sqlcluster -CMSGroup Group1,Group3
    
            Top-level groups Group1 and Group3 along with their subgroups and server instances are copied from sqlserver to sqlcluster.
    
        .EXAMPLE
            PS C:\> Copy-DbaCentralManagementServer -Source sqlserver2014a -Destination sqlcluster -CMSGroup Group1,Group3 -SwitchServerName -SourceSqlCredential $SourceSqlCredential -DestinationSqlCredential $DestinationSqlCredential
    
            Top-level groups Group1 and Group3 along with their subgroups and server instances are copied from sqlserver to sqlcluster. When adding sql instances to sqlcluster, if the server name of the migrating instance is "sqlcluster", it will be switched to "sqlserver".
    
            If SwitchServerName is not specified, "sqlcluster" will be skipped.
    
        #>
        [CmdletBinding(DefaultParameterSetName = "Default", SupportsShouldProcess = $true)]
        param (
            [parameter(Mandatory)]
            [DbaInstanceParameter]$Source,
            [PSCredential]$SourceSqlCredential,
            [parameter(Mandatory)]
            [DbaInstanceParameter[]]$Destination,
            [PSCredential]$DestinationSqlCredential,
            [object[]]$CMSGroup,
            [switch]$SwitchServerName,
            [switch]$Force,
            [Alias('Silent')]
            [switch]$EnableException
        )
        begin {
            function Invoke-ParseServerGroup {
                [cmdletbinding()]
                param (
                    $sourceGroup,
                    $destinationGroup,
                    $SwitchServerName
                )
                if ($destinationGroup.Name -eq "DatabaseEngineServerGroup" -and $sourceGroup.Name -ne "DatabaseEngineServerGroup") {
                    $currentServerGroup = $destinationGroup
                    $groupName = $sourceGroup.Name
                    $destinationGroup = $destinationGroup.ServerGroups[$groupName]
    
                    $copyDestinationGroupStatus = [pscustomobject]@{
                        SourceServer      = $sourceServer.Name
                        DestinationServer = $destServer.Name
                        Name              = $groupName
                        Type              = "CMS Destination Group"
                        Status            = $null
                        Notes             = $null
                        DateTime          = [Sqlcollaborative.Dbatools.Utility.DbaDateTime](Get-Date)
                    }
    
                    if ($null -ne $destinationGroup) {
    
                        if ($force -eq $false) {
                            if ($Pscmdlet.ShouldProcess($destinstance, "Checking to see if $groupName exists")) {
                                $copyDestinationGroupStatus.Status = "Skipped"
                                $copyDestinationGroupStatus.Notes = "Already exists"
                                $copyDestinationGroupStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
                                Write-Message -Level Verbose -Message "Destination group $groupName exists at destination. Use -Force to drop and migrate."
                            }
                            continue
                        }
                        if ($Pscmdlet.ShouldProcess($destinstance, "Dropping group $groupName")) {
                            try {
                                Write-Message -Level Verbose -Message "Dropping group $groupName"
                                $destinationGroup.Drop()
                            } catch {
                                $copyDestinationGroupStatus.Status = "Failed"
                                $copyDestinationGroupStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
    
                                Stop-Function -Message "Issue dropping group" -Target $groupName -ErrorRecord $_ -Continue
                            }
                        }
                    }
    
                    if ($Pscmdlet.ShouldProcess($destinstance, "Creating group $groupName")) {
                        Write-Message -Level Verbose -Message "Creating group $($sourceGroup.Name)"
                        $destinationGroup = New-Object Microsoft.SqlServer.Management.RegisteredServers.ServerGroup($currentServerGroup, $sourceGroup.Name)
                        $destinationGroup.Create()
    
                        $copyDestinationGroupStatus.Status = "Successful"
                        $copyDestinationGroupStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
                    }
                }
    
                # Add Servers
                foreach ($instance in $sourceGroup.RegisteredServers) {
                    $instanceName = $instance.Name
                    $serverName = $instance.ServerName
    
                    $copyInstanceStatus = [pscustomobject]@{
                        SourceServer      = $sourceServer.Name
                        DestinationServer = $destServer.Name
                        Name              = $instanceName
                        Type              = "CMS Instance"
                        Status            = $null
                        Notes             = $null
                        DateTime          = [Sqlcollaborative.Dbatools.Utility.DbaDateTime](Get-Date)
                    }
    
                    if ($serverName.ToLower() -eq $toCmStore.DomainInstanceName.ToLower()) {
                        if ($Pscmdlet.ShouldProcess($destinstance, "Checking to see if server is the CMS equals current server name")) {
                            if ($SwitchServerName) {
                                $serverName = $fromCmStore.DomainInstanceName
                                $instanceName = $fromCmStore.DomainInstanceName
                                Write-Message -Level Verbose -Message "SwitchServerName was used and new CMS equals current server name. $($toCmStore.DomainInstanceName.ToLower()) changed to $serverName."
                            } else {
                                $copyInstanceStatus.Status = "Skipped"
                                $copyInstanceStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
    
                                Write-Message -Level Verbose -Message "$serverName is Central Management Server. Add prohibited. Skipping."
                                continue
                            }
                        }
                    }
    
                    if ($destinationGroup.RegisteredServers.Name -contains $instanceName) {
    
                        if ($force -eq $false) {
                            if ($Pscmdlet.ShouldProcess($destinstance, "Checking to see if $instanceName in $groupName exists")) {
                                $copyInstanceStatus.Status = "Skipped"
                                $copyInstanceStatus.Notes = "Already exists"
                                $copyInstanceStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
    
                                Write-Message -Level Verbose -Message "Instance $instanceName exists in group $groupName at destination. Use -Force to drop and migrate."
                            }
                            continue
                        }
    
                        if ($Pscmdlet.ShouldProcess($destinstance, "Dropping instance $instanceName from $groupName and recreating")) {
                            try {
                                Write-Message -Level Verbose -Message "Dropping instance $instance from $groupName"
                                $destinationGroup.RegisteredServers[$instanceName].Drop()
                            } catch {
                                $copyInstanceStatus.Status = "Failed"
                                $copyInstanceStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
    
                                Stop-Function -Message "Issue dropping instance from group" -Target $instanceName -ErrorRecord $_ -Continue
                            }
                        }
                    }
    
                    if ($Pscmdlet.ShouldProcess($destinstance, "Copying $instanceName")) {
                        $newServer = New-Object Microsoft.SqlServer.Management.RegisteredServers.RegisteredServer($destinationGroup, $instanceName)
                        $newServer.ServerName = $serverName
                        $newServer.Description = $instance.Description
    
                        if ($serverName -ne $fromCmStore.DomainInstanceName) {
                            $newServer.SecureConnectionString = $instance.SecureConnectionString.ToString()
                            $newServer.ConnectionString = $instance.ConnectionString.ToString()
                        }
    
                        try {
                            $newServer.Create()
    
                            $copyInstanceStatus.Status = "Successful"
                            $copyInstanceStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
                        } catch {
                            $copyInstanceStatus.Status = "Failed"
                            $copyInstanceStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
                            if ($_.Exception -match "same name") {
                                Stop-Function -Message "Could not add Switched Server instance name." -Target $instanceName -ErrorRecord $_ -Continue
                            } else {
                                Stop-Function -Message "Failed to add $serverName" -Target $instanceName -ErrorRecord $_ -Continue
                            }
                        }
                        Write-Message -Level Verbose -Message "Added Server $serverName as $instanceName to $($destinationGroup.Name)"
                    }
                }
    
                # Add Groups
                foreach ($fromSubGroup in $sourceGroup.ServerGroups) {
                    $fromSubGroupName = $fromSubGroup.Name
                    $toSubGroup = $destinationGroup.ServerGroups[$fromSubGroupName]
    
                    $copyGroupStatus = [pscustomobject]@{
                        SourceServer      = $sourceServer.Name
                        DestinationServer = $destServer.Name
                        Name              = $fromSubGroupName
                        Type              = "CMS Group"
                        Status            = $null
                        Notes             = $null
                        DateTime          = [Sqlcollaborative.Dbatools.Utility.DbaDateTime](Get-Date)
                    }
    
                    if ($null -ne $toSubGroup) {
                        if ($force -eq $false) {
                            if ($Pscmdlet.ShouldProcess($destinstance, "Checking to see if subgroup $fromSubGroupName exists")) {
                                $copyGroupStatus.Status = "Skipped"
                                $copyGroupStatus.Notes = "Already exists"
                                $copyGroupStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
    
                                Write-Message -Level Verbose -Message "Subgroup $fromSubGroupName exists at destination. Use -Force to drop and migrate."
                            }
                            continue
                        }
    
                        if ($Pscmdlet.ShouldProcess($destinstance, "Dropping subgroup $fromSubGroupName recreating")) {
                            try {
                                Write-Message -Level Verbose -Message "Dropping subgroup $fromSubGroupName"
                                $toSubGroup.Drop()
                            } catch {
                                $copyGroupStatus.Status = "Failed"
                                $copyGroupStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
    
                                Stop-Function -Message "Issue dropping subgroup" -Target $toSubGroup -ErrorRecord $_ -Continue
                            }
                        }
                    }
    
                    if ($Pscmdlet.ShouldProcess($destinstance, "Creating group $($fromSubGroup.Name)")) {
                        Write-Message -Level Verbose -Message "Creating group $($fromSubGroup.Name)"
                        $toSubGroup = New-Object Microsoft.SqlServer.Management.RegisteredServers.ServerGroup($destinationGroup, $fromSubGroup.Name)
                        $toSubGroup.create()
    
                        $copyGroupStatus.Status = "Successful"
                        $copyGroupStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
                    }
    
                    Invoke-ParseServerGroup -sourceGroup $fromSubGroup -destinationgroup $toSubGroup -SwitchServerName $SwitchServerName
                }
            }
    
            try {
                $sourceServer = Connect-SqlInstance -SqlInstance $Source -SqlCredential $SourceSqlCredential -MinimumVersion 10
                $fromCmStore = Get-DbaCmsRegServerStore -SqlInstance $sourceServer
            } catch {
                Stop-Function -Message "Failure" -Category ConnectionError -ErrorRecord $_ -Target $Source
                return
            }
        }
    
        process {
            if (Test-FunctionInterrupt) { return }
            foreach ($destinstance in $Destination) {
                try {
                    $destServer = Connect-SqlInstance -SqlInstance $destinstance -SqlCredential $DestinationSqlCredential -MinimumVersion 10
                } catch {
                    Stop-Function -Message "Failure" -Category ConnectionError -ErrorRecord $_ -Target $destinstance -Continue
                }
                $toCmStore = Get-DbaCmsRegServerStore -SqlInstance $destServer
    
                $stores = $fromCmStore.DatabaseEngineServerGroup
                if ($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/
    
        .PARAMETER Source
            Source SQL Server. You must have sysadmin access and server version must be SQL Server version 2000 or higher.
    
        .PARAMETER SourceSqlCredential
            Login to the target instance using alternative credentials. Windows and SQL Authentication supported. Accepts credential objects (Get-Credential)
    
        .PARAMETER Destination
            Destination SQL Server. You must have sysadmin access and the server must be SQL Server 2000 or higher.
    
        .PARAMETER DestinationSqlCredential
            Login to the target instance using alternative credentials. Windows and SQL Authentication supported. Accepts credential objects (Get-Credential)
    
        .PARAMETER Credential
            This command requires access to the Windows OS via PowerShell remoting. Use this credential to connect to Windows using alternative credentials.
    
        .PARAMETER Name
            Only include specific names
            Note: if spaces exist in the credential name, you will have to type "" or '' around it.
    
        .PARAMETER ExcludeName
            Excluded credential names
    
        .PARAMETER Identity
            Only include specific identities
            Note: if spaces exist in the credential identity, you will have to type "" or '' around it.
    
        .PARAMETER ExcludeIdentity
            Excluded identities
    
        .PARAMETER Force
            If this switch is enabled, the Credential will be dropped and recreated if it already exists on Destination.
    
        .PARAMETER WhatIf
            If this switch is enabled, no actions are performed but informational messages will be displayed that explain what would happen if the command were to run.
    
        .PARAMETER Confirm
            If this switch is enabled, you will be prompted for confirmation before executing any operations that change state.
    
        .PARAMETER EnableException
            By default, when something goes wrong we try to catch it, interpret it and give you a friendly warning message.
            This avoids overwhelming you with "sea of red" exceptions, but is inconvenient because it basically disables advanced scripting.
            Using this switch turns this "nice by default" feature off and enables you to catch exceptions with your own try/catch.
    
        .NOTES
            Tags: WSMan, Migration
            Author: Chrissy LeMaire (@cl), netnerds.net
    
            Website: https://dbatools.io
            Copyright: (c) 2018 by dbatools, licensed under MIT
            License: MIT https://opensource.org/licenses/MIT
    
            Requires:
            - PowerShell Version 3.0
            - Administrator access on Windows
            - sysadmin access on SQL Server.
            - DAC access enabled for local (default)
    
        .LINK
            https://dbatools.io/Copy-DbaCredential
    
        .EXAMPLE
            PS C:\> Copy-DbaCredential -Source sqlserver2014a -Destination sqlcluster
    
            Copies all SQL Server Credentials on sqlserver2014a to sqlcluster. If Credentials exist on destination, they will be skipped.
    
        .EXAMPLE
            PS C:\> Copy-DbaCredential -Source sqlserver2014a -Destination sqlcluster -Name "PowerShell Proxy Account" -Force
    
            Copies over one SQL Server Credential (PowerShell Proxy Account) from sqlserver to sqlcluster. If the Credential already exists on the destination, it will be dropped and recreated.
    
        #>
        [CmdletBinding(SupportsShouldProcess)]
        param (
            [parameter(Mandatory)]
            [DbaInstanceParameter]$Source,
            [PSCredential]
            $SourceSqlCredential,
            [PSCredential]
            $Credential,
            [parameter(Mandatory)]
            [DbaInstanceParameter[]]$Destination,
            [PSCredential]$DestinationSqlCredential,
            [string[]]$Name,
            [string[]]$ExcludeName,
            [Alias('CredentialIdentity')]
            [string[]]$Identity,
            [Alias('ExcludeCredentialIdentity')]
            [string[]]$ExcludeIdentity,
            [switch]$Force,
            [Alias('Silent')]
            [switch]$EnableException
        )
    
        begin {
            $null = Test-ElevationRequirement -ComputerName $Source.ComputerName
    
            function Copy-Credential {
                <#
                    .SYNOPSIS
                        Copies Credentials from one server to another using a combination of SMO's .Script() and manual password updates.
    
                    .OUTPUT
                        System.Data.DataTable
                #>
                [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSAvoidUsingPlainTextForPassword", "", Justification = "For Credentials")]
                param (
                    [string[]]$Credentials,
                    [bool]$Force
                )
    
                Write-Message -Level Verbose -Message "Collecting Credential logins and passwords on $($sourceServer.Name)"
                $sourceCredentials = Get-DecryptedObject -SqlInstance $sourceServer -Type Credential
                $credentialList = Get-DbaCredential -SqlInstance $sourceServer -Name $Name -ExcludeName $ExcludeName -Identity $Identity -ExcludeIdentity $ExcludeIdentity
    
                Write-Message -Level Verbose -Message "Starting migration"
                foreach ($credential in $credentialList) {
                    $destServer.Credentials.Refresh()
                    $credentialName = $credential.Name
    
                    $copyCredentialStatus = [pscustomobject]@{
                        SourceServer      = $sourceServer.Name
                        DestinationServer = $destServer.Name
                        Type              = "Credential"
                        Name              = $credentialName
                        Status            = $null
                        Notes             = $null
                        DateTime          = [DbaDateTime](Get-Date)
                    }
    
                    if ($null -ne $destServer.Credentials[$credentialName]) {
                        if (!$force) {
                            $copyCredentialStatus.Status = "Skipping"
                            $copyCredentialStatus.Notes = "Already exists"
                            $copyCredentialStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
    
                            Write-Message -Level Verbose -Message "$credentialName exists $($destServer.Name). Skipping."
                            continue
                        } else {
                            if ($Pscmdlet.ShouldProcess($destinstance.Name, "Dropping $identity")) {
                                $destServer.Credentials[$credentialName].Drop()
                                $destServer.Credentials.Refresh()
                            }
                        }
                    }
    
                    Write-Message -Level Verbose -Message "Attempting to migrate $credentialName"
                    try {
                        $currentCred = $sourceCredentials | Where-Object { $_.Name -eq "[$credentialName]" }
                        $sqlcredentialName = $credentialName.Replace("'", "''")
                        $identity = $currentCred.Identity.Replace("'", "''")
                        $password = $currentCred.Password.Replace("'", "''")
                        if ($Pscmdlet.ShouldProcess($destinstance.Name, "Copying $identity")) {
                            $destServer.Query("CREATE CREDENTIAL [$sqlcredentialName] WITH IDENTITY = N'$identity', SECRET = N'$password'")
                            $destServer.Credentials.Refresh()
                            Write-Message -Level Verbose -Message "$credentialName successfully copied"
                        }
    
                        $copyCredentialStatus.Status = "Successful"
                        $copyCredentialStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
                    } catch {
                        $copyCredentialStatus.Status = "Failed"
                        $copyCredentialStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
    
                        Stop-Function -Message "Error creating credential" -Target $credentialName -ErrorRecord $_
                    }
                }
            }
    
            try {
                $sourceServer = Connect-SqlInstance -SqlInstance $Source -SqlCredential $SourceSqlCredential -MinimumVersion 9
            } catch {
                Stop-Function -Message "Failure" -Category ConnectionError -ErrorRecord $_ -Target $instance
                return
            }
    
            if ($null -ne $SourceSqlCredential.Username) {
                Write-Message -Level Verbose -Message "You are using SQL credentials and this script requires Windows admin access to the $Source server. Trying anyway."
            }
    
            $sourceNetBios = Resolve-NetBiosName $sourceServer
    
            Invoke-SmoCheck -SqlInstance $sourceServer
    
            Write-Message -Level Verbose -Message "Checking if Remote Registry is enabled on $source"
            try {
                Invoke-Command2 -ComputerName $sourceNetBios -Credential $credential -ScriptBlock { Get-ItemProperty -Path "HKLM:\SOFTWARE\" }
            } catch {
                Stop-Function -Message "Can't connect to registry on $source" -Target $sourceNetBios -ErrorRecord $_
                return
            }
        }
        process {
            if (Test-FunctionInterrupt) { return }
    
            foreach ($destinstance in $Destination) {
                try {
                    $destServer = Connect-SqlInstance -SqlInstance $destinstance -SqlCredential $DestinationSqlCredential -MinimumVersion 9
                } catch {
                    Stop-Function -Message "Failure" -Category ConnectionError -ErrorRecord $_ -Target $destinstance -Continue
                }
                Invoke-SmoCheck -SqlInstance $destServer
    
                Copy-Credential $credentials -force:$force
            }
        }
        end {
            Test-DbaDeprecation -DeprecatedOn "1.0.0" -EnableException:$false -Alias Copy-SqlCredential
        }
    }
    function Copy-DbaCustomError {
        <#
        .SYNOPSIS
            Copy-DbaCustomError migrates custom errors (user defined messages), by the custom error ID, from one SQL Server to another.
    
        .DESCRIPTION
            By default, all custom errors are copied. The -CustomError parameter is auto-populated for command-line completion and can be used to copy only specific custom errors.
    
            If the custom error already exists on the destination, it will be skipped unless -Force is used. The us_english version must be created first. If you drop the us_english version, all the other languages will be dropped for that specific ID as well.
    
        .PARAMETER Source
            Source SQL Server. You must have sysadmin access and server version must be SQL Server version 2000 or higher.
    
        .PARAMETER SourceSqlCredential
            Login to the target instance using alternative credentials. Windows and SQL Authentication supported. Accepts credential objects (Get-Credential)
    
        .PARAMETER Destination
            Destination SQL Server. You must have sysadmin access and the server must be SQL Server 2000 or higher.
    
        .PARAMETER DestinationSqlCredential
            Login to the target instance using alternative credentials. Windows and SQL Authentication supported. Accepts credential objects (Get-Credential)
    
        .PARAMETER CustomError
            The custom error(s) to process. This list is auto-populated from the server. If unspecified, all custom errors will be processed.
    
        .PARAMETER ExcludeCustomError
            The custom error(s) to exclude. This list is auto-populated from the server.
    
        .PARAMETER WhatIf
            If this switch is enabled, no actions are performed but informational messages will be displayed that explain what would happen if the command were to run.
    
        .PARAMETER Confirm
            If this switch is enabled, you will be prompted for confirmation before executing any operations that change state.
    
        .PARAMETER EnableException
            By default, when something goes wrong we try to catch it, interpret it and give you a friendly warning message.
            This avoids overwhelming you with "sea of red" exceptions, but is inconvenient because it basically disables advanced scripting.
            Using this switch turns this "nice by default" feature off and enables you to catch exceptions with your own try/catch.
    
        .PARAMETER Force
            If this switch is enabled, the custom error will be dropped and recreated if it already exists on Destination.
    
        .NOTES
            Tags: Migration, CustomError
            Author: Chrissy LeMaire (@cl), netnerds.net
    
            Website: https://dbatools.io
            Copyright: (c) 2018 by dbatools, licensed under MIT
            License: MIT https://opensource.org/licenses/MIT
    
            Requires: sysadmin access on SQL Servers
    
        .LINK
            https://dbatools.io/Copy-DbaCustomError
    
        .EXAMPLE
            PS C:\> Copy-DbaCustomError -Source sqlserver2014a -Destination sqlcluster
    
            Copies all server custom errors from sqlserver2014a to sqlcluster using Windows credentials. If custom errors with the same name exist on sqlcluster, they will be skipped.
    
        .EXAMPLE
            PS C:\> Copy-DbaCustomError -Source sqlserver2014a -SourceSqlCredential $scred -Destination sqlcluster -DestinationSqlCredential $dcred -CustomError 60000 -Force
    
            Copies only the custom error with ID number 60000 from sqlserver2014a to sqlcluster using SQL credentials for sqlserver2014a and Windows credentials for sqlcluster. If a custom error with the same name exists on sqlcluster, it will be updated because -Force was used.
    
        .EXAMPLE
            PS C:\> Copy-DbaCustomError -Source sqlserver2014a -Destination sqlcluster -ExcludeCustomError 60000 -Force
    
            Copies all the custom errors found on sqlserver2014a except the custom error with ID number 60000 to sqlcluster. If a custom error with the same name exists on sqlcluster, it will be updated because -Force was used.
    
        .EXAMPLE
            PS C:\> Copy-DbaCustomError -Source sqlserver2014a -Destination sqlcluster -WhatIf -Force
    
            Shows what would happen if the command were executed using force.
    
        #>
        [CmdletBinding(DefaultParameterSetName = "Default", SupportsShouldProcess = $true)]
        param (
            [parameter(Mandatory)]
            [DbaInstanceParameter]$Source,
            [PSCredential]
            $SourceSqlCredential,
            [parameter(Mandatory)]
            [DbaInstanceParameter[]]$Destination,
            [PSCredential]
            $DestinationSqlCredential,
            [object[]]$CustomError,
            [object[]]$ExcludeCustomError,
            [switch]$Force,
            [Alias('Silent')]
            [switch]$EnableException
        )
    
        begin {
            try {
                $sourceServer = Connect-SqlInstance -SqlInstance $Source -SqlCredential $SourceSqlCredential -MinimumVersion 9
            } catch {
                Stop-Function -Message "Failure" -Category ConnectionError -ErrorRecord $_ -Target $Source
                return
            }
            $orderedCustomErrors = @($sourceServer.UserDefinedMessages | Where-Object Language -eq "us_english")
            $orderedCustomErrors += $sourceServer.UserDefinedMessages | Where-Object Language -ne "us_english"
        }
        process {
            if (Test-FunctionInterrupt) { return }
            foreach ($destinstance in $Destination) {
                try {
                    $destServer = Connect-SqlInstance -SqlInstance $destinstance -SqlCredential $DestinationSqlCredential -MinimumVersion 9
                } catch {
                    Stop-Function -Message "Failure" -Category ConnectionError -ErrorRecord $_ -Target $destinstance -Continue
                }
                # US has to go first
                $destCustomErrors = $destServer.UserDefinedMessages
    
                foreach ($currentCustomError in $orderedCustomErrors) {
                    $customErrorId = $currentCustomError.ID
                    $language = $currentCustomError.Language.ToString()
    
                    $copyCustomErrorStatus = [pscustomobject]@{
                        SourceServer      = $sourceServer.Name
                        DestinationServer = $destServer.Name
                        Type              = "Custom error"
                        Name              = $currentCustomError
                        Status            = $null
                        Notes             = $null
                        DateTime          = [DbaDateTime](Get-Date)
                    }
    
                    if ($CustomError -and ($customErrorId -notin $CustomError -or $customErrorId -in $ExcludeCustomError)) {
                        continue
                    }
    
                    if ($destCustomErrors.ID -contains $customErrorId) {
                        if ($force -eq $false) {
                            $copyCustomErrorStatus.Status = "Skipped"
                            $copyCustomErrorStatus.Notes = "Already exists"
                            $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 -SharedPath in a valid UNC format (\\server\share).
    
            Backups will be immediately deleted after use unless -NoBackupCleanup is specified.
    
        .PARAMETER SharedPath
            Specifies the network location for the backup files. The SQL Server service accounts must have read/write permission on this path.
    
        .PARAMETER WithReplace
            If this switch is enabled, the restore is executed with WITH REPLACE.
    
        .PARAMETER NoRecovery
            If this switch is enabled, the restore is executed with WITH NORECOVERY. Ideal for staging.
    
        .PARAMETER NoBackupCleanup
            If this switch is enabled, backups generated by this cmdlet will not be deleted after they are restored. The default behavior is to delete these backups.
    
        .PARAMETER NumberFiles
            Number of files to split the backup. Default is 3.
    
        .PARAMETER DetachAttach
            If this switch is enabled, the detach/copy/attach method is used to perform database migrations. No files are deleted on Source. If Destination attachment fails, the Source database will be reattached. File copies are performed over administrative shares (\\server\x$\mssql) using BITS. If a database is being mirrored, the mirror will be broken prior to migration.
    
        .PARAMETER Reattach
            If this switch is enabled, all databases are reattached to Source after DetachAttach migration.
    
        .PARAMETER SetSourceReadOnly
            If this switch is enabled, all migrated databases are set to ReadOnly on Source prior to detach/attach & backup/restore.
    
            If -Reattach is used, databases are set to read-only after reattaching.
    
        .PARAMETER ReuseSourceFolderStructure
            If this switch is enabled, databases will be migrated to a data and log directory structure on Destination mirroring that used on Source. By default, the default data and log directories for Destination will be used when the databases are migrated.
    
            The structure on Source  will be kept exactly, so consider this if you're migrating between different versions and use part of Microsoft's default Sql structure (MSSql12.INSTANCE, etc)
    
            To reuse Destination folder structure, use the  -WithReplace switch.
    
        .PARAMETER IncludeSupportDbs
            If this switch is enabled, ReportServer, ReportServerTempDb, SSISDB, and distribution databases will be copied if they exist on Source. A log file named $SOURCE-$destinstance-$date-Sqls.csv will be written to the current directory.
    
            Use of this switch requires -BackupRestore or -DetachAttach as well.
    
        .PARAMETER InputObject
            A collection of dbobjects from the pipeline.
    
        .PARAMETER UseLastBackup
            Use the last full, diff and logs instead of performing backups. Note that the backups must exist in a location accessible by all destination servers, such a network share.
    
        .PARAMETER Continue
            If specified, will to attempt to restore transaction log backups on top of existing database(s) in Recovering or Standby states. Only usable with -UseLastBackup
    
        .PARAMETER NoCopyOnly
            If this switch is enabled, backups will be taken without COPY_ONLY. This will break the LSN backup chain, which will interfere with the restore chain of the database.
    
            By default this switch is disabled, so backups will be taken with COPY_ONLY. This will preserve the LSN backup chain.
    
            For more details please refer to this MSDN article - https://msdn.microsoft.com/en-us/library/ms191495.aspx
    
        .PARAMETER NewName
            If a single database is being copied, this will be used to rename the database during the copy process. Any occurrence of the original database name in the physical file names will be replaced with NewName
            If specified with multiple databases a warning will be raised and the copy stopped
    
            This option is mutually exclusive of Prefix
    
        .PARAMETER Prefix
            All copied database names and physical files will be prefixed with this string
    
            This option is mutually exclusive of NewName
    
        .PARAMETER SetSourceOffline
            If this switch is enabled, the Source database will be set to Offline after being copied.
    
        .PARAMETER WhatIf
            If this switch is enabled, no actions are performed but informational messages will be displayed that explain what would happen if the command were to run.
    
        .PARAMETER Confirm
            If this switch is enabled, you will be prompted for confirmation before executing any operations that change state.
    
        .PARAMETER EnableException
            By default, when something goes wrong we try to catch it, interpret it and give you a friendly warning message.
            This avoids overwhelming you with "sea of red" exceptions, but is inconvenient because it basically disables advanced scripting.
            Using this switch turns this "nice by default" feature off and enables you to catch exceptions with your own try/catch.
    
        .PARAMETER Force
            If this switch is enabled, existing databases on Destination with matching names from Source will be dropped. If using -DetachReattach, mirrors will be broken and the database(s) dropped from Availability Groups.
    
        .NOTES
            Tags: Migration, Backup, Restore
            Author: Chrissy LeMaire (@cl), netnerds.net
    
            Website: https://dbatools.io
            Copyright: (c) 2018 by dbatools, licensed under MIT
            License: MIT https://opensource.org/licenses/MIT
    
            Requires: sysadmin access on SQL Servers
    
            Limitations:
            - Doesn't cover what it doesn't cover (replication, certificates, etc)
            - SQL Server 2000 databases cannot be directly migrated to SQL Server 2012 and above.
            - Logins within SQL Server 2012 and above logins cannot be migrated to SQL Server 2008 R2 and below.
    
        .LINK
            https://dbatools.io/Copy-DbaDatabase
    
        .EXAMPLE
            PS C:\> Copy-DbaDatabase -Source sql2014a -Destination sql2014b -Database TestDB -BackupRestore -SharedPath \\fileshare\sql\migration
    
            Migrates a single user database TestDB using Backup and restore from instance sql2014a to sql2014b. Backup files are stored in \\fileshare\sql\migration.
    
        .EXAMPLE
            PS C:\> Copy-DbaDatabase -Source sql2012 -Destination sql2014, sql2016 -DetachAttach -Reattach
    
            Databases will be migrated from sql2012 to both sql2014 and sql2016 using the detach/copy files/attach method.The following will be performed: kick all users out of the database, detach all data/log files, move files across the network over an admin share (\\SqlSERVER\M$\MSSql...), attach file on destination server, reattach at source. If the database files (*.mdf, *.ndf, *.ldf) on *destination* exist and aren't in use, they will be overwritten.
    
        .EXAMPLE
            PS C:\> Copy-DbaDatabase -Source sql2014a -Destination sqlcluster, sql2016 -BackupRestore -UseLastBackup -Force
    
            Migrates all user databases to sqlcluster and sql2016 using the last Full, Diff and Log backups from sql204a. If the databases exists on the destinations, they will be dropped prior to attach.
    
            Note that the backups must exist in a location accessible by all destination servers, such a network share.
    
        .EXAMPLE
            PS C:\> Copy-DbaDatabase -Source sql2014a -Destination sqlcluster -ExcludeDatabase Northwind, pubs -IncludeSupportDbs -Force -BackupRestore -SharedPath \\fileshare\sql\migration
    
            Migrates all user databases except for Northwind and pubs by using backup/restore (copy-only). Backup files are stored in \\fileshare\sql\migration. If the database exists on the destination, it will be dropped prior to attach.
    
            It also includes the support databases (ReportServer, ReportServerTempDb, distribution).
    
        #>
        [CmdletBinding(DefaultParameterSetName = "DbBackup", SupportsShouldProcess = $true)]
        [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUseOutputTypeCorrectly", "", Justification = "PSSA Rule Ignored by BOH")]
        param (
            [parameter(Mandatory = $false)]
            [DbaInstanceParameter]$Source,
            [PSCredential]$SourceSqlCredential,
            [parameter(Mandatory)]
            [DbaInstanceParameter[]]$Destination,
            [PSCredential]$DestinationSqlCredential,
            [Alias("Databases")]
            [object[]]$Database,
            [object[]]$ExcludeDatabase,
            [Alias("All")]
            [parameter(ParameterSetName = "DbBackup")]
            [parameter(ParameterSetName = "DbAttachDetach")]
            [switch]$AllDatabases,
            [parameter(Mandatory, ParameterSetName = "DbBackup")]
            [switch]$BackupRestore,
            [Alias("NetworkShare")]
            [parameter(ParameterSetName = "DbBackup",
                HelpMessage = "Specify a valid network share in the format \\server\share that can be accessed by your account and the SQL Server service accounts for both Source and Destination.")]
            [string]$SharedPath,
            [parameter(ParameterSetName = "DbBackup")]
            [switch]$WithReplace,
            [parameter(ParameterSetName = "DbBackup")]
            [switch]$NoRecovery,
            [parameter(ParameterSetName = "DbBackup")]
            [switch]$NoBackupCleanup,
            [parameter(ParameterSetName = "DbBackup")]
            [ValidateRange(1, 64)]
            [int]$NumberFiles = 3,
            [parameter(Mandatory, ParameterSetName = "DbAttachDetach")]
            [switch]$DetachAttach,
            [parameter(ParameterSetName = "DbAttachDetach")]
            [switch]$Reattach,
            [parameter(ParameterSetName = "DbBackup")]
            [parameter(ParameterSetName = "DbAttachDetach")]
            [switch]$SetSourceReadOnly,
            [Alias("ReuseFolderStructure")]
            [parameter(ParameterSetName = "DbBackup")]
            [parameter(ParameterSetName = "DbAttachDetach")]
            [switch]$ReuseSourceFolderStructure,
            [parameter(ParameterSetName = "DbBackup")]
            [parameter(ParameterSetName = "DbAttachDetach")]
            [switch]$IncludeSupportDbs,
            [parameter(ParameterSetName = "DbBackup")]
            [switch]$UseLastBackup,
            [parameter(ParameterSetName = "DbBackup")]
            [switch]$Continue,
            [parameter(ValueFromPipeline)]
            [Microsoft.SqlServer.Management.Smo.Database[]]$InputObject,
            [switch]$NoCopyOnly,
            [switch]$SetSourceOffline,
            [string]$NewName,
            [string]$Prefix,
            [switch]$Force,
            [switch]$EnableException
        )
        begin {
            Test-DbaDeprecation -DeprecatedOn 1.0.0 -Parameter NetworkShare -CustomMessage "Using the parameter NetworkShare is deprecated. This parameter will be removed in version 1.0.0 or before. Use SharedPath instead."
            
            $CopyOnly = -not $NoCopyOnly
    
            if ($BackupRestore -and (-not $SharedPath -and -not $UseLastBackup)) {
                Stop-Function -Message "When using -BackupRestore, you must specify -SharedPath or -UseLastBackup"
                return
            }
            if ($SharedPath -and $UseLastBackup) {
                Stop-Function -Message "-SharedPath cannot be used with -UseLastBackup because the backup path is determined by the paths in the last backups"
                return
            }
            if ($DetachAttach -and -not $Reattach -and $Destination.Count -gt 1) {
                Stop-Function -Message "When using -DetachAttach with multiple servers, you must specify -Reattach to reattach database at source"
                return
            }
            if ($Continue -and -not $UseLastBackup) {
                Stop-Function -Message "-Continue cannot be used without -UseLastBackup"
                return
            }
    
            function Join-Path {
                <#
            An internal command that does not require the local path to exist
    
            Boo, this does not work, but keeping it for future ref.
            #>
                [CmdletBinding()]
                param (
                    [string]$Path,
                    [string]$ChildPath
                )
                process {
                    try {
                        [IO.Path]::Combine($Path, $ChildPath)
                    } catch {
                        "$Path\$ChildPath"
                    }
                }
            }
    
            function Join-AdminUnc {
                <#
            .SYNOPSIS
            Internal function. Parses a path to make it an admin UNC.
            #>
                [CmdletBinding()]
                param (
                    [Parameter(Mandatory)]
                    [ValidateNotNullOrEmpty()]
                    [string]$servername,
                    [Parameter(Mandatory)]
                    [ValidateNotNullOrEmpty()]
                    [string]$filepath
    
                )
    
                if ($script:sameserver) {
                    return $filepath
                }
                if (-not $filepath) {
                    return
                }
                if ($filepath.StartsWith("\\")) {
                    return $filepath
                }
    
                $servername = $servername.Split("\")[0]
    
                if ($filepath -and $filepath -ne [System.DbNull]::Value) {
                    $newpath = Join-Path "\\$servername\" $filepath.replace(':', '$')
                    return $newpath
                } else {
                    return
                }
            }
    
            function Get-SqlFileStructure {
                $dbcollection = @{
                };
                $databaseProgressbar = 0
    
                foreach ($db in $databaseList) {
                    Write-Progress -Id 1 -Activity "Processing database file structure" -PercentComplete ($databaseProgressbar / $dbCount * 100) -Status "Processing $databaseProgressbar of $dbCount."
                    $dbName = $db.Name
                    Write-Message -Level Verbose -Message $dbName
    
                    $databaseProgressbar++
                    $dbStatus = $db.status.toString()
                    if ($dbStatus.StartsWith("Normal") -eq $false) {
                        continue
                    }
                    $destinstancefiles = @{
                    }; $sourcefiles = @{
                    }
    
                    $where = "Filetype <> 'LOG' and Filetype <> 'FULLTEXT'"
    
                    $datarows = $dbFileTable.Tables.Select("dbname = '$dbName' and $where")
    
                    # Data Files
                    foreach ($file in $datarows) {
                        # Destination File Structure
                        $d = @{
                        }
                        if ($ReuseSourceFolderStructure) {
                            $d.physical = $file.filename
                        } elseif ($WithReplace) {
                            $name = $file.Name
                            $destfile = $remoteDbFileTable.Tables[0].Select("dbname = '$dbName' and name = '$name'")
                            $d.physical = $destfile.filename
    
                            if ($null -eq $d.physical) {
                                $directory = Get-SqlDefaultPaths $destServer data
                                $fileName = Split-Path $file.filename -Leaf
                                $d.physical = "$directory\$fileName"
                            }
                        } else {
                            $directory = Get-SqlDefaultPaths $destServer data
                            $fileName = Split-Path $file.filename -Leaf
                            $d.physical = "$directory\$fileName"
                        }
                        $d.logical = $file.Name
    
                        $d.remotefilename = Join-AdminUNC $destNetBios $d.physical
                        $destinstancefiles.add($file.Name, $d)
    
                        # Source File Structure
                        $s = @{
                        }
                        $s.logical = $file.Name
                        $s.physical = $file.filename
                        $s.remotefilename = Join-AdminUNC $sourceNetBios $s.physical
                        $sourcefiles.add($file.Name, $s)
                    }
    
                    # Add support for Full Text Catalogs in SQL Server 2005 and below
                    if ($sourceServer.VersionMajor -lt 10) {
                        try {
                            $fttable = $null = $sourceServer.Databases[$dbName].ExecuteWithResults('sp_help_fulltext_catalogs')
                            $allrows = $fttable.Tables[0].rows
                        } catch {
                            # Nothing, it's just not enabled
                            # here to avoid an empty catch
                            $null = 1
                        }
    
                        foreach ($ftc in $allrows) {
                            # Destination File Structure
                            $d = @{
                            }
                            $pre = "sysft_"
                            $name = $ftc.Name
                            $physical = $ftc.Path # RootPath
                            $logical = "$pre$name"
                            if ($ReuseSourceFolderStructure) {
                                $d.physical = $physical
                            } else {
                                $directory = Get-SqlDefaultPaths $destServer data
                                if ($destServer.VersionMajor -lt 10) {
                                    $directory = "$directory\FTDATA"
                                }
                                $fileName = Split-Path($physical) -leaf
                                $d.physical = "$directory\$fileName"
                            }
                            $d.logical = $logical
                            $d.remotefilename = Join-AdminUNC $destNetBios $d.physical
                            $destinstancefiles.add($logical, $d)
    
                            # Source File Structure
                            $s = @{
                            }
                            $pre = "sysft_"
                            $name = $ftc.Name
                            $physical = $ftc.Path # RootPath
                            $logical = "$pre$name"
    
                            $s.logical = $logical
                            $s.physical = $physical
                            $s.remotefilename = Join-AdminUNC $sourceNetBios $s.physical
                            $sourcefiles.add($logical, $s)
                        }
                    }
    
                    $where = "Filetype = 'LOG'"
                    $datarows = $dbFileTable.Tables[0].Select("dbname = '$dbName' and $where")
    
                    # Log Files
                    foreach ($file in $datarows) {
                        $d = @{
                        }
                        if ($ReuseSourceFolderStructure) {
                            $d.physical = $file.filename
                        } elseif ($WithReplace) {
                            $name = $file.Name
                            $destfile = $remoteDbFileTable.Tables[0].Select("dbname = '$dbName' and name = '$name'")
                            $d.physical = $destfile.filename
    
                            if ($null -eq $d.physical) {
                                $directory = Get-SqlDefaultPaths $destServer data
                                $fileName = Split-Path $file.filename -Leaf
                                $d.physical = "$directory\$fileName"
                            }
                        } else {
                            $directory = Get-SqlDefaultPaths $destServer log
                            $fileName = Split-Path $file.filename -Leaf
                            $d.physical = "$directory\$fileName"
                        }
                        $d.logical = $file.Name
                        $d.remotefilename = Join-AdminUNC $destNetBios $d.physical
                        $destinstancefiles.add($file.Name, $d)
    
                        $s = @{
                        }
                        $s.logical = $file.Name
                        $s.physical = $file.filename
                        $s.remotefilename = Join-AdminUNC $sourceNetBios $s.physical
                        $sourcefiles.add($file.Name, $s)
                    }
    
                    $location = @{
                    }
                    $location.add("Destination", $destinstancefiles)
                    $location.add("Source", $sourcefiles)
                    $dbcollection.Add($($db.Name), $location)
                }
    
                $fileStructure = [pscustomobject]@{
                    "databases" = $dbcollection
                }
                Write-Progress -id 1 -Activity "Processing database file structure" -Status "Completed" -Completed
                return $fileStructure
            }
    
            function Dismount-SqlDatabase {
                [CmdletBinding()]
                param (
                    [object]$server,
                    [string]$dbName
                )
    
                $currentdb = $server.databases[$dbName]
                if ($currentdb.IsMirroringEnabled) {
                    try {
                        Write-Message -Level Verbose -Message "Breaking mirror for $dbName"
                        $currentdb.ChangeMirroringState([Microsoft.SqlServer.Management.Smo.MirroringOption]::Off)
                        $currentdb.Alter()
                        $currentdb.Refresh()
                        Write-Message -Level Verbose -Message "Could not break mirror for $dbName. Skipping."
                    } catch {
                        Stop-Function -Message "Issue breaking mirror." -Target $dbName -ErrorRecord $_
                        return $false
                    }
                }
    
                if ($currentdb.AvailabilityGroupName) {
                    $agName = $currentdb.AvailabilityGroupName
                    Write-Message -Level Verbose -Message "Attempting remove from Availability Group $agName."
                    try {
                        $server.AvailabilityGroups[$currentdb.AvailabilityGroupName].AvailabilityDatabases[$dbName].Drop()
                        Write-Message -Level Verbose -Message "Successfully removed $dbName from  detach from $agName on $($server.Name)."
                    } catch {
                        Stop-Function -Message "Could not remove $dbName from $agName on $($server.Name)." -Target $dbName -ErrorRecord $_
                        return $false
                    }
                }
    
                Write-Message -Level Verbose -Message "Attempting detach from $dbName from $source."
    
                ####### Using Sql to detach does not modify the $currentdb collection #######
    
                $server.KillAllProcesses($dbName)
    
                try {
                    $sql = "ALTER DATABASE [$dbName] SET SINGLE_USER WITH ROLLBACK IMMEDIATE"
                    Write-Message -Level Verbose -Message $sql
                    $null = $server.Query($sql)
                    Write-Message -Level Verbose -Message "Successfully set $dbName to single-user from $source."
                } catch {
                    Stop-Function -Message "Issue setting database to single-user." -Target $dbName -ErrorRecord $_
                }
    
                try {
                    $sql = "EXEC master.dbo.sp_detach_db N'$dbName'"
                    Write-Message -Level Verbose -Message $sql
                    $null = $server.Query($sql)
                    Write-Message -Level Verbose -Message "Successfully detached $dbName from $source."
                    return $true
                } catch {
                    Stop-Function -Message "Issue detaching database." -Target $dbName -ErrorRecord $_
                    return $false
                }
            }
    
            function Mount-SqlDatabase {
                [CmdletBinding()]
                param (
                    [object]$server,
                    [string]$dbName,
                    [object]$fileStructure,
                    [string]$dbOwner
                )
    
                if ($null -eq $server.Logins.Item($dbOwner)) {
                    try {
                        $dbOwner = ($destServer.logins | Where-Object {
                                $_.id -eq 1
                            }).Name
                    } catch {
                        $dbOwner = "sa"
                    }
                }
                try {
                    $null = $server.AttachDatabase($dbName, $fileStructure, $dbOwner, [Microsoft.SqlServer.Management.Smo.AttachOptions]::None)
                    return $true
                } catch {
                    Stop-Function -Message "Issue mounting database." -ErrorRecord $_
                    return $false
                }
            }
    
            function Start-SqlFileTransfer {
                <#
    
                SYNOPSIS
                Internal function. Uses BITS to transfer detached files (.mdf, .ndf, .ldf, and filegroups) to
                another server over admin UNC paths. Locations of data files are kept in the
                custom object generated by Get-SqlFileStructure
    
                #>
                [CmdletBinding(SupportsShouldProcess)]
                param (
                    [object]$fileStructure,
                    [string]$dbName
                )
                $filestructure
                $copydb = $fileStructure.databases[$dbName]
                $dbsource = $copydb.source
                $dbdestination = $copydb.destination
    
                foreach ($file in $dbsource.keys) {
                    if ($Pscmdlet.ShouldProcess($file, "Starting Sql File Transfer")) {
                        $remotefilename = $dbdestination[$file].remotefilename
                        $from = $dbsource[$file].remotefilename
                        try {
                            if (Test-Path $from -pathtype container) {
                                $null = New-Item -ItemType Directory -Path $remotefilename -Force
                                Start-BitsTransfer -Source "$from\*.*" -Destination $remotefilename
    
                                $directories = (Get-ChildItem -recurse $from | Where-Object {
                                        $_.PsIsContainer
                                    }).FullName
                                foreach ($directory in $directories) {
                                    $newdirectory = $directory.replace($from, $remotefilename)
                                    $null = New-Item -ItemType Directory -Path $newdirectory -Force
                                    Start-BitsTransfer -Source "$directory\*.*" -Destination $newdirectory
                                }
                            } else {
                                Write-Message -Level Verbose -Message "Copying $from for $dbName."
                                Start-BitsTransfer -Source $from -Destination $remotefilename
                            }
                        } catch {
                            try {
                                # Sometimes BITS trips out temporarily on cloned drives.
                                Start-BitsTransfer -Source $from -Destination $remotefilename
                            } catch {
                                Write-Message -Level Verbose -Message "Start-BitsTransfer did not succeed. Now attempting with Copy-Item - no progress bar will be shown."
                                try {
                                    Copy-Item -Path $from -Destination $remotefilename -ErrorAction Stop
                                    $remotefilename
                                } catch {
                                    Write-Message -Level Verbose -Message "Access denied. This can happen for a number of reasons including issues with cloned disks."
                                    Stop-Function -Message "Alternatively, you may need to run PowerShell as Administrator, especially when running on localhost." -Target $from -ErrorRecord $_
                                    return
                                }
                            }
                        }
                    }
                }
                return $true
            }
    
            function Start-SqlDetachAttach {
                <#
    
                .SYNOPSIS
                Internal function. Performs checks, then executes Dismount-SqlDatabase on a database, copies its files to the new server,    then performs Mount-SqlDatabase. $sourceServer and $destServer are SMO server objects.
    
                $fileStructure is a custom object generated by Get-SqlFileStructure
    
                #>
                [CmdletBinding(SupportsShouldProcess)]
                param (
                    [object]$sourceServer,
                    [object]$destServer,
                    [object]$fileStructure,
                    [string]$dbName
                )
                if ($Pscmdlet.ShouldProcess($dbname, "Starting detaching and re-attaching from $sourceServer to $destServer")) {
                    $destfilestructure = New-Object System.Collections.Specialized.StringCollection
                    $sourceFileStructure = New-Object System.Collections.Specialized.StringCollection
                    $dbOwner = $sourceServer.databases[$dbName].owner
                    $destDbName = $fileStructure.databases[$dbName].destinationDbName
    
                    if ($null -eq $dbOwner) {
                        try {
                            $dbOwner = ($destServer.logins | Where-Object {
                                    $_.id -eq 1
                                }).Name
                        } catch {
                            $dbOwner = "sa"
                        }
                    }
    
                    foreach ($file in $fileStructure.databases[$dbName].destination.values) {
                        $null = $destfilestructure.add($file.physical)
                    }
                    foreach ($file in $fileStructure.databases[$dbName].source.values) {
                        $null = $sourceFileStructure.add($file.physical)
                    }
    
                    $detachresult = Dismount-SqlDatabase $sourceServer $dbName
    
                    if ($detachresult) {
    
                        $transfer = Start-SqlFileTransfer $fileStructure $dbName
                        if ($transfer -eq $false) {
                            Write-Message -Level Verbose -Message "Could not copy files."
                            return "Could not copy files."
                        }
                        $attachresult = Mount-SqlDatabase $destServer $destDbName $destfilestructure $dbOwner
    
                        if ($attachresult -eq $true) {
                            # add to added dbs because ATTACH was successful
                            Write-Message -Level Verbose -Message "Successfully attached $dbName to $destinstance."
                            return $true
                        } else {
                            # add to failed because ATTACH was unsuccessful
                            Write-Message -Level Verbose -Message "Could not attach $dbName."
                            return "Could not attach database."
                        }
                    } else {
                        # add to failed because DETACH was unsuccessful
                        Write-Message -Level Verbose -Message "Could not detach $dbName."
                        return "Could not detach database."
                    }
                }
            }
            $backupCollection = @()
        }
        process {
            if (Test-FunctionInterrupt) {
                return
            }
    
            # testing twice for whatif reasons
            if ($BackupRestore -and (-not $SharedPath -and -not $UseLastBackup)) {
                Stop-Function -Message "When using -BackupRestore, you must specify -SharedPath or -UseLastBackup"
                return
            }
            if ($SharedPath -and $UseLastBackup) {
                Stop-Function -Message "-SharedPath cannot be used with -UseLastBackup because the backup path is determined by the paths in the last backups"
                return
            }
            if ($DetachAttach -and -not $Reattach -and $Destination.Count -gt 1) {
                Stop-Function -Message "When using -DetachAttach with multiple servers, you must specify -Reattach to reattach database at source"
                return
            }
            if (($AllDatabases -or $IncludeSupportDbs -or $Database) -and !$DetachAttach -and !$BackupRestore) {
                Stop-Function -Message "You must specify -DetachAttach or -BackupRestore when migrating databases."
                return
            }
    
            if (-not $AllDatabases -and -not $IncludeSupportDbs -and -not $Database -and -not $InputObject) {
                Stop-Function -Message "You must specify a -AllDatabases or -Database to continue."
                return
            }
    
            if ((Test-Bound 'NewName') -and (Test-Bound 'Prefix')) {
                Stop-Function -Message "NewName and Prefix are exclusive options, cannot specify both"
                return
            }
    
            if ($InputObject) {
                $Source = $InputObject[0].Parent
                $Database = $InputObject.Name
            }
    
    
            if ($Database -contains "master" -or $Database -contains "msdb" -or $Database -contains "tempdb") {
                Stop-Function -Message "Migrating system databases is not currently supported." -Continue
            }
    
            try {
                $sourceServer = Connect-SqlInstance -SqlInstance $Source -SqlCredential $SourceSqlCredential
            } catch {
                Stop-Function -Message "Failure" -Category ConnectionError -ErrorRecord $_ -Target $Source
                return
            }
    
            Invoke-SmoCheck -SqlInstance $sourceServer
            $sourceNetBios = $sourceServer.ComputerName
    
            Write-Message -Level Verbose -Message "Ensuring user databases exist (counting databases)."
    
            if ($sourceserver.Databases.IsSystemObject -notcontains $false) {
                Stop-Function -Message "No user databases to migrate"
                return
            }
    
            foreach ($destinstance in $Destination) {
                try {
                    $destServer = Connect-SqlInstance -SqlInstance $destinstance -SqlCredential $DestinationSqlCredential
                } catch {
                    Stop-Function -Message "Failure" -Category ConnectionError -ErrorRecord $_ -Target $destinstance -Continue
                }
    
                if ($sourceServer.ComputerName -eq $destServer.ComputerName) {
                    $script:sameserver = $true
                } else {
                    $script:sameserver = $false
                }
    
                if ($script:sameserver -and $DetachAttach) {
                    if (-not (Test-ElevationRequirement -ComputerName $sourceServer)) {
                        return
                    }
                }
    
                $destVersionLower = $destServer.VersionMajor -lt $sourceServer.VersionMajor
                $destVersionMinorLow = ($destServer.VersionMajor -eq 10 -and $sourceServer.VersionMajor -eq 10) -and ($destServer.VersionMinor -lt $sourceServer.VersionMinor)
    
                if ($destVersionLower -or $destVersionMinorLow) {
                    Stop-Function -Message "Error: copy database cannot be made from newer $($sourceServer.VersionString) to older $($destServer.VersionString) SQL Server version."
                    return
                }
    
                if ($DetachAttach) {
                    if ($sourceServer.ComputerName -eq $env:COMPUTERNAME -or $destServer.ComputerName -eq $env:COMPUTERNAME) {
                        if (-not ([Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole] "Administrator")) {
                            Write-Message -Level Verbose -Message "When running DetachAttach locally on the console, it's possible you'll need to Run As Administrator. Trying anyway."
                        }
                    }
                }
    
                if ($SharedPath) {
                    if ($(Test-DbaPath -SqlInstance $sourceServer -Path $SharedPath) -eq $false) {
                        Write-Message -Level Verbose -Message "$Source may not be able to access $SharedPath. Trying anyway."
                    }
    
                    if ($(Test-DbaPath -SqlInstance $destServer -Path $SharedPath) -eq $false) {
                        Write-Message -Level Verbose -Message "$destinstance may not be able to access $SharedPath. Trying anyway."
                    }
    
                    if ($SharedPath.StartsWith('\\')) {
                        try {
                            $shareServer = ($SharedPath -split "\\")[2]
                            $hostEntry = ([Net.Dns]::GetHostEntry($shareServer)).HostName -split "\."
    
                            if ($shareServer -ne $hostEntry[0]) {
                                Write-Message -Level Verbose -Message "Using CNAME records for the network share may present an issue if an SPN has not been created. Trying anyway. If it doesn't work, use a different (A record) hostname."
                            }
                        } catch {
                            Stop-Function -Message "Error validating unc path: $_"
                            return
                        }
                    }
                }
    
                $destNetBios = $destserver.ComputerName
    
                Write-Message -Level Verbose -Message "Performing SMO version check."
                Invoke-SmoCheck -SqlInstance $destServer
    
                Write-Message -Level Verbose -Message "Checking to ensure the source isn't the same as the destination."
                if ($source -eq $destinstance) {
                    Stop-Function -Message "Source and Destination SQL Servers instances are the same. Quitting." -Continue
                }
    
                if ($SharedPath) {
                    Write-Message -Level Verbose -Message "Checking to ensure network path is valid."
                    if (-not ($SharedPath.StartsWith("\\")) -and -not $script:sameserver) {
                        Stop-Function -Message "Network share must be a valid UNC path (\\server\share)." -Continue
                    }
    
                    if (-not $script:sameserver) {
                        try {
                            if ((Test-Path $SharedPath -ErrorAction Stop)) {
                                Write-Message -Level Verbose -Message "$SharedPath share can be accessed."
                            }
                        } catch {
                            Write-Message -Level Verbose -Message "$SharedPath share cannot be accessed. Still trying anyway, in case the SQL Server service accounts have access."
                        }
                    }
                }
    
                Write-Message -Level Verbose -Message "Checking to ensure server is not SQL Server 7 or below."
                if ($sourceServer.VersionMajor -lt 8 -and $destServer.VersionMajor -lt 8) {
                    Stop-Function -Message "This script can only be run on SQL Server 2000 and above. Quitting." -Continue
                }
    
                Write-Message -Level Verbose -Message "Checking to ensure detach/attach is not attempted on SQL Server 2000."
                if ($destServer.VersionMajor -lt 9 -and $DetachAttach) {
                    Stop-Function -Message "Detach/Attach not supported when destination SQL Server is version 2000. Quitting." -Target $destServer -Continue
                }
    
                Write-Message -Level Verbose -Message "Checking to ensure SQL Server 2000 migration isn't directly attempted to SQL Server 2012."
                if ($sourceServer.VersionMajor -lt 9 -and $destServer.VersionMajor -gt 10) {
                    Stop-Function -Message "SQL Server 2000 databases cannot be migrated to SQL Server versions 2012 and above. Quitting." -Target $destServer -Continue
                }
    
                Write-Message -Level Verbose -Message "Warning if migration from 2005 to 2012 and above and attach/detach is used."
                if ($sourceServer.VersionMajor -eq 9 -and $destServer.VersionMajor -gt 9 -and !$BackupRestore -and !$Force -and $DetachAttach) {
                    Stop-Function -Message "Backup and restore is the safest method for migrating from SQL Server 2005 to other SQL Server versions. Please use the -BackupRestore switch or override this requirement by specifying -Force." -Continue
                }
    
                if ($sourceServer.Collation -ne $destServer.Collation) {
                    Write-Message -Level Verbose -Message "Warning on different collation."
                    Write-Message -Level Verbose -Message "Collation on $Source, $($sourceServer.Collation) differs from the $destinstance, $($destServer.Collation)."
                }
    
                Write-Message -Level Verbose -Message "Ensuring destination server version is equal to or greater than source."
                if ($sourceServer.VersionMajor -ge $destServer.VersionMajor) {
                    if ($sourceServer.VersionMinor -gt $destServer.VersionMinor) {
                        Stop-Function -Message "Source SQL Server version build must be <= destination SQL Server for database migration." -Continue
                    }
                }
    
                # SMO's filestreamlevel is sometimes null
                $sql = "select coalesce(SERVERPROPERTY('FilestreamConfiguredLevel'),0) as fs"
                $sourceFilestream = $sourceServer.ConnectionContext.ExecuteScalar($sql)
                $destFilestream = $destServer.ConnectionContext.ExecuteScalar($sql)
                if ($sourceFilestream -gt 0 -and $destFilestream -eq 0) {
                    $fsWarning = $true
                }
    
                Write-Message -Level Verbose -Message "Writing warning about filestream being enabled."
                if ($fsWarning) {
                    Write-Message -Level Verbose -Message "FILESTREAM enabled on $source but not $destinstance. Databases that use FILESTREAM will be skipped."
                }
    
                if ($DetachAttach -eq $true) {
                    Write-Message -Level Verbose -Message "Checking access to remote directories."
                    $remoteSourcePath = Join-AdminUNC $sourceNetBios (Get-SqlDefaultPaths -SqlInstance $sourceServer -filetype data)
    
                    if ((Test-Path $remoteSourcePath) -ne $true -and $DetachAttach) {
                        Write-Message -Level Warning -Message "Can't access remote Sql directories on $source which is required to perform detach/copy/attach."
                        Write-Message -Level Warning -Message "You can manually try accessing $remoteSourcePath to diagnose any issues."
                        Stop-Function -Message "Halting database migration"
                        return
                    }
    
                    $remoteDestPath = Join-AdminUNC $destNetBios (Get-SqlDefaultPaths -SqlInstance $destServer -filetype data)
                    If ((Test-Path $remoteDestPath) -ne $true -and $DetachAttach) {
                        Write-Message -Level Warning -Message "Can't access remote Sql directories on $destinstance which is required to perform detach/copy/attach."
                        Write-Message -Level Warning -Message "You can manually try accessing $remoteDestPath to diagnose any issues."
                        Stop-Function -Message "Halting database migration" -Continue
                    }
                }
    
                if (($Database -or $ExcludeDatabase -or $IncludeSupportDbs) -and (!$DetachAttach -and !$BackupRestore)) {
                    Stop-Function -Message "You did not select a migration method. Please use -BackupRestore or -DetachAttach."
                    return
                }
    
                if ((!$Database -and !$AllDatabases -and !$IncludeSupportDbs) -and ($DetachAttach -or $BackupRestore)) {
                    Stop-Function -Message "You did not select any databases to migrate. Please use -AllDatabases or -Database or -IncludeSupportDbs."
                    return
                }
    
                Write-Message -Level Verbose -Message "Building database list."
                $databaseList = New-Object System.Collections.ArrayList
                $SupportDBs = "ReportServer", "ReportServerTempDB", "distribution"
                foreach ($currentdb in ($sourceServer.Databases | Where-Object IsAccessible)) {
                    $dbName = $currentdb.Name
                    $dbOwner = $currentdb.Owner
    
                    if ($currentdb.Id -le 4) {
                        continue
                    }
                    if ($Database -and $Database -notcontains $dbName) {
                        continue
                    }
                    if ($IncludeSupportDBs -eq $false -and $SupportDBs -contains $dbName) {
                        continue
                    }
                    if ($IncludeSupportDBs -eq $true -and $SupportDBs -notcontains $dbName) {
                        if ($AllDatabases -eq $false -and $Database.length -eq 0) {
                            continue
                        }
                    }
                    $null = $databaseList.Add($currentdb)
                }
    
                Write-Message -Level Verbose -Message "Performing count."
                $dbCount = $databaseList.Count
    
                if ((Test-Bound 'NewName') -and $dbCount -gt 1) {
                    Stop-Function -Message "Cannot use NewName when copying multiple databases"
                    return
                }
    
    
                Write-Message -Level Verbose -Message "Building file structure inventory for $dbCount databases."
    
                if ($sourceServer.VersionMajor -eq 8) {
                    $sql = "select DB_NAME (dbid) as dbname, name, filename, CASE WHEN groupid = 0 THEN 'LOG' ELSE 'ROWS' END as filetype from sysaltfiles"
                } else {
                    $sql = "SELECT db.Name AS dbname, type_desc AS FileType, mf.Name, Physical_Name AS filename FROM sys.master_files mf INNER JOIN  sys.databases db ON db.database_id = mf.database_id"
                }
    
                $dbFileTable = $sourceServer.Databases['master'].ExecuteWithResults($sql)
    
                if ($destServer.VersionMajor -eq 8) {
                    $sql = "select DB_NAME (dbid) as dbname, name, filename, CASE WHEN groupid = 0 THEN 'LOG' ELSE 'ROWS' END as filetype from sysaltfiles"
                } else {
                    $sql = "SELECT db.Name AS dbname, type_desc AS FileType, mf.Name, Physical_Name AS filename FROM sys.master_files mf INNER JOIN  sys.databases db ON db.database_id = mf.database_id"
                }
    
                $remoteDbFileTable = $destServer.Databases['master'].ExecuteWithResults($sql)
    
                $fileStructure = Get-SqlFileStructure -sourceserver $sourceServer -destserver $destServer -databaselist $databaseList -ReuseSourceFolderStructure $ReuseSourceFolderStructure
    
                $elapsed = [System.Diagnostics.Stopwatch]::StartNew()
                $started = Get-Date
                $script:TimeNow = (Get-Date -UFormat "%m%d%Y%H%M%S")
    
                if ($AllDatabases -or $ExcludeDatabase -or $IncludeSupportDbs -or $Database) {
                    foreach ($currentdb in $databaseList) {
                        $dbName = $currentdb.Name
                        $dbOwner = $currentdb.Owner
                        $destinationDbName = $dbName
                        if ((Test-Bound "NewName")) {
                            Write-Message -Level Verbose -Message "NewName specified, copying $dbname as $NewName"
                            $destinationDbName = $NewName
                            $replaceInFile = $True
                        }
                        if ($(Test-Bound "Prefix")) {
                            $destinationDbName = $prefix + $destinationDbName
                            Write-Message -Level Verbose -Message "Prefix supplied, copying $dbname as $destinationDbName"
                        }
    
                        $filestructure.databases[$dbname].Add('destinationDbName', $destinationDbName)
                        ForEach ($key in $filestructure.databases[$dbname].Destination.Keys) {
                            $splitFileName = Split-Path $fileStructure.databases[$dbname].Destination[$key].remotefilename -Leaf
                            $SplitPath = Split-Path $fileStructure.databases[$dbname].Destination[$key].remotefilename
                            if ($replaceInFile) {
                                $splitFileName = $splitFileName.replace($dbname, $destinationDbName)
                            }
                            $splitFileName = $prefix + $splitFileName
                            $filestructure.databases[$dbname].Destination.$key.remotefilename = Join-Path $SplitPath $splitFileName
                            $splitFileName = Split-Path $filestructure.databases[$dbname].Destination[$key].physical -Leaf
                            $SplitPath = Split-Path $fileStructure.databases[$dbname].Destination[$key].physical
                            if ($replaceInFile) {
                                $splitFileName = $splitFileName.replace($dbname, $destinationDbName)
                            }
                            $splitFileName = $prefix + $splitFileName
                            $filestructure.databases[$dbname].Destination.$key.physical = Join-Path $SplitPath $splitFileName
                        }
    
                        $copyDatabaseStatus = [pscustomobject]@{
                            SourceServer        = $sourceServer.Name
                            DestinationServer   = $destServer.Name
                            Name                = $dbName
                            DestinationDatabase = $DestinationDbname
                            Type                = "Database"
                            Status              = $null
                            Notes               = $null
                            DateTime            = [DbaDateTime](Get-Date)
                        }
    
                        Write-Message -Level Verbose -Message "`n######### Database: $dbName #########"
                        $dbStart = Get-Date
    
                        if ($ExcludeDatabase -contains $dbName) {
                            Write-Message -Level Verbose -Message "$dbName excluded. Skipping."
                            continue
                        }
    
                        Write-Message -Level Verbose -Message "Checking for accessibility."
                        if ($currentdb.IsAccessible -eq $false) {
                            if ($Pscmdlet.ShouldProcess($destinstance, "Skipping $dbName. Database is inaccessible.")) {
                                Write-Message -Level Verbose -Message "Skipping $dbName. Database is inaccessible."
    
                                $copyDatabaseStatus.Status = "Skipped"
                                $copyDatabaseStatus.Notes = "Database is not accessible"
                                $copyDatabaseStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
                            }
                            continue
                        }
    
                        if ($fsWarning) {
                            $fsRows = $dbFileTable.Tables[0].Select("dbname = '$dbName' and FileType = 'FileStream'")
    
                            if ($fsRows.Count -gt 0) {
                                if ($Pscmdlet.ShouldProcess($destinstance, "Skipping $dbName (contains FILESTREAM).")) {
                                    Write-Message -Level Verbose -Message "Skipping $dbName (contains FILESTREAM)."
                                    $copyDatabaseStatus.Status = "Skipped"
                                    $copyDatabaseStatus.Notes = "Contains FILESTREAM"
                                    $copyDatabaseStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
                                }
                                continue
                            }
                        }
    
                        if ($ReuseSourceFolderStructure) {
                            $fgRows = $dbFileTable.Tables[0].Select("dbname = '$dbName' and FileType = 'ROWS'")[0]
                            $remotePath = Split-Path $fgRows.Filename
    
                            if (!(Test-DbaPath -SqlInstance $destServer -Path $remotePath)) {
                                if ($Pscmdlet.ShouldProcess($destinstance, "$remotePath does not exist on $destinstance and ReuseSourceFolderStructure was specified")) {
                                    # Stop-Function -Message "Cannot resolve $remotePath on $source. `n`nYou have specified ReuseSourceFolderStructure and exact folder structure does not exist. Halting script."
                                    $copyDatabaseStatus.Status = "Failed"
                                    $copyDatabaseStatus.Notes = "$remotePath does not exist on $destinstance and ReuseSourceFolderStructure was specified" #"Can't resolve $remotePath"
                                    $copyDatabaseStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
                                }
                                continue
                            }
                        }
    
                        Write-Message -Level Verbose -Message "Checking Availability Group status."
                        if ($currentdb.AvailabilityGroupName -and !$force -and $DetachAttach) {
                            $agName = $currentdb.AvailabilityGroupName
                            Write-Message -Level Verbose -Message "Database is part of an Availability Group ($agName). Use -Force to drop from $agName and migrate. Alternatively, you can use the safer backup/restore method."
                            continue
                        }
    
                        $dbStatus = $currentdb.Status.ToString()
    
                        if ($dbStatus.StartsWith("Normal") -eq $false) {
                            if ($Pscmdlet.ShouldProcess($destinstance, "$dbName is not in a Normal state. Skipping.")) {
                                Write-Message -Level Verbose -Message "$dbName is not in a Normal state. Skipping."
    
                                $copyDatabaseStatus.Status = "Skipped"
                                $copyDatabaseStatus.Notes = "Not in normal state"
                                $copyDatabaseStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
                            }
                            continue
                        }
    
                        if ($currentdb.ReplicationOptions -ne "None" -and $DetachAttach -eq $true) {
                            if ($Pscmdlet.ShouldProcess($destinstance, "$dbName is part of replication. Skipping.")) {
                                Write-Message -Level Verbose -Message "$dbName is part of replication. Skipping."
    
                                $copyDatabaseStatus.Status = "Skipped"
                                $copyDatabaseStatus.Notes = "Part of replication"
                                $copyDatabaseStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
                            }
                            continue
                        }
    
                        if ($currentdb.IsMirroringEnabled -and !$force -and $DetachAttach) {
                            if ($Pscmdlet.ShouldProcess($destinstance, "Database is being mirrored. Use -Force to break mirror and migrate. Alternatively, you can use the safer backup/restore method.")) {
                                Write-Message -Level Verbose -Message "Database is being mirrored. Use -Force to break mirror and migrate. Alternatively, you can use the safer backup/restore method."
    
                                $copyDatabaseStatus.Status = "Skipped"
                                $copyDatabaseStatus.Notes = "Database is mirrored"
                                $copyDatabaseStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
                            }
    
                            continue
                        }
    
                        if (($null -ne $destServer.Databases[$DestinationdbName]) -and !$force -and !$WithReplace) {
                            if ($Pscmdlet.ShouldProcess($destinstance, "$DestinationdbName exists at destination. Use -Force to drop and migrate. Aborting routine for this database.")) {
                                Write-Message -Level Verbose -Message "$DestinationdbName exists at destination. Use -Force to drop and migrate. Aborting routine for this database."
    
                                $copyDatabaseStatus.Status = "Skipped"
                                $copyDatabaseStatus.Notes = "Already exists"
                                $copyDatabaseStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
                            }
                            continue
                        } elseif ($null -ne $destServer.Databases[$DestinationdbName] -and $force) {
                            if ($Pscmdlet.ShouldProcess($destinstance, "DROP DATABASE $DestinationdbName")) {
                                Write-Message -Level Verbose -Message "$DestinationdbName already exists. -Force was specified. Dropping $DestinationdbName on $destinstance."
                                $removeresult = Remove-DbaDatabase -SqlInstance $destserver -Database $DestinationdbName -Confirm:$false
                                $dropResult = $removeresult.Status -eq 'Dropped'
    
                                if ($dropResult -eq $false) {
                                    Write-Message -Level Verbose -Message "Database could not be dropped. Aborting routine for this database."
    
                                    $copyDatabaseStatus.Status = "Failed"
                                    $copyDatabaseStatus.Notes = "Could not drop database"
                                    $copyDatabaseStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
                                    continue
                                }
                            }
                        }
    
                        if ($force) {
                            $WithReplace = $true
                        }
    
                        Write-Message -Level Verbose -Message "Started: $dbStart."
    
                        if ($sourceServer.VersionMajor -ge 9) {
                            $sourceDbOwnerChaining = $sourceServer.Databases[$dbName].DatabaseOwnershipChaining
                            $sourceDbTrustworthy = $sourceServer.Databases[$dbName].Trustworthy
                            $sourceDbBrokerEnabled = $sourceServer.Databases[$dbName].BrokerEnabled
                        }
    
                        $sourceDbReadOnly = $sourceServer.Databases[$dbName].ReadOnly
    
                        if ($SetSourceReadOnly) {
                            If ($Pscmdlet.ShouldProcess($source, "Set $dbName to read-only")) {
                                Write-Message -Level Verbose -Message "Setting database to read-only."
                                try {
                                    $result = Set-DbaDbState -SqlInstance $sourceServer -Database $dbName -ReadOnly -EnableException
                                } catch {
                                    Stop-Function -Continue -Message "Couldn't set database to read-only. Aborting routine for this database" -ErrorRecord $_
                                }
                            }
                        }
    
                        if ($BackupRestore) {
                            if ($UseLastBackup) {
                                $whatifmsg = "Gathering last backup information for $dbName from $Source and restoring"
                            } else {
                                $whatifmsg = "Backup $dbName from $source and restoring"
                            }
                            If ($Pscmdlet.ShouldProcess($destinstance, $whatifmsg)) {
                                if ($UseLastBackup) {
                                    $backupTmpResult = Get-DbaBackupHistory -SqlInstance $sourceServer -Database $dbName -IncludeCopyOnly -Last
                                    if (-not $backupTmpResult) {
                                        $copyDatabaseStatus.Type = "Database (BackupRestore)"
                                        $copyDatabaseStatus.Status = "Failed"
                                        $copyDatabaseStatus.Notes = "No backups for $dbName on $source"
                                        $copyDatabaseStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
                                        continue
                                    }
                                } else {
                                    $backupTmpResult = $backupCollection | Where-Object Database -eq $dbName
                                    if (-not $backupTmpResult) {
                                        $backupTmpResult = Backup-DbaDatabase -SqlInstance $sourceServer -Database $dbName -BackupDirectory $SharedPath -FileCount $numberfiles -CopyOnly:$CopyOnly
                                    }
                                    if ($backupTmpResult) {
                                        $backupCollection += $backupTmpResult
                                    }
                                    $backupResult = $BackupTmpResult.BackupComplete
                                    if (-not $backupResult) {
                                        $serviceAccount = $sourceServer.ServiceAccount
                                        Write-Message -Level Verbose -Message "Backup Failed. Does SQL Server account $serviceAccount have access to $($SharedPath)? Aborting routine for this database."
    
                                        $copyDatabaseStatus.Status = "Failed"
                                        $copyDatabaseStatus.Notes = "Backup failed. Verify service account access to $SharedPath."
                                        $copyDatabaseStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
                                        continue
                                    }
                                }
                                Write-Message -Level Verbose -Message "Reuse = $ReuseSourceFolderStructure."
                                try {
                                    $msg = $null
                                    $restoreResultTmp = $backupTmpResult | Restore-DbaDatabase -SqlInstance $destServer -DatabaseName $DestinationdbName -ReuseSourceFolderStructure:$ReuseSourceFolderStructure -NoRecovery:$NoRecovery -TrustDbBackupHistory -WithReplace:$WithReplace -Continue:$Continue -EnableException -ReplaceDbNameInFile
                                } catch {
                                    $msg = $_.Exception.InnerException.InnerException.InnerException.InnerException.Message
                                    Stop-Function -Message "Failure attempting to restore $dbName to $destinstance" -Exception $_.Exception.InnerException.InnerException.InnerException.InnerException
                                }
                                $restoreResult = $restoreResultTmp.RestoreComplete
    
                                if ($restoreResult -eq $true) {
                                    Write-Message -Level Verbose -Message "Successfully restored $dbName to $destinstance."
                                    $copyDatabaseStatus.Status = "Successful"
                                    $copyDatabaseStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
                                } else {
                                    if ($ReuseSourceFolderStructure) {
                                        Write-Message -Level Verbose -Message "Failed to restore $dbName to $destinstance. You specified -ReuseSourceFolderStructure. Does the exact same destination directory structure exist?"
                                        Write-Message -Level Verbose -Message "Aborting routine for this database."
    
                                        $copyDatabaseStatus.Status = "Failed"
                                        $copyDatabaseStatus.Notes = "Failed to restore. ReuseSourceFolderStructure was specified, verify same directory structure exist on destination."
                                        $copyDatabaseStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
                                        continue
                                    } else {
                                        Write-Message -Level Verbose -Message "Failed to restore $dbName to $destinstance. Aborting routine for this database."
    
                                        $copyDatabaseStatus.Status = "Failed"
                                        if (-not $msg) {
                                            $msg = "Failed to restore database"
                                        }
                                        $copyDatabaseStatus.Notes = $msg
                                        $copyDatabaseStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
                                        continue
                                    }
                                }
                                if (-not $NoBackupCleanUp -and $Destination.Count -eq 1) {
                                    foreach ($backupFile in ($backupTmpResult.BackupPath)) {
                                        try {
                                            if (Test-Path $backupFile -ErrorAction Stop) {
                                                Write-Message -Level Verbose -Message "Deleting $backupFile."
                                                Remove-Item $backupFile -ErrorAction Stop
                                            }
                                        } catch {
                                            try {
                                                Write-Message -Level Verbose -Message "Trying alternate SQL method to delete $backupFile."
                                                $sql = "EXEC master.sys.xp_delete_file 0, '$backupFile'"
                                                Write-Message -Level Debug -Message $sql
                                                $null = $sourceServer.Query($sql)
                                            } catch {
                                                Write-Message -Level Verbose -Message "Cannot delete backup file $backupFile."
    
                                                # Set NoBackupCleanup so that there's a warning at the end
                                                $NoBackupCleanup = $true
                                            }
                                        }
                                    }
                                }
                            }
    
                            $dbFinish = Get-Date
                            if ($NoRecovery -eq $false) {
                                # needed because the newly restored database doesn't show up
                                $destServer.Databases.Refresh()
                                $dbOwner = $sourceServer.Databases[$dbName].Owner
                                if ($null -eq $dbOwner -or $destServer.Logins.Name -notcontains $dbOwner) {
                                    $dbOwner = Get-SaLoginName -SqlInstance $destServer
                                }
                                Write-Message -Level Verbose -Message "Updating database owner to $dbOwner."
                                $OwnerResult = Set-DbaDbOwner -SqlInstance $destServer -Database $dbName -TargetLogin $dbOwner -EnableException
                                if ($OwnerResult.Length -eq 0) {
                                    Write-Message -Level Verbose -Message "Failed to update database owner."
                                }
                            }
                        }
    
                        if ($DetachAttach) {
    
                            $copyDatabaseStatus.Type = "Database (DetachAttach)"
    
                            $sourceFileStructure = New-Object System.Collections.Specialized.StringCollection
                            foreach ($file in $fileStructure.Databases[$dbName].Source.Values) {
                                $null = $sourceFileStructure.Add($file.Physical)
                            }
    
                            $dbOwner = $sourceServer.Databases[$dbName].Owner
    
                            if ($null -eq $dbOwner -or $destServer.Logins.Name -notcontains $dbOwner) {
                                $dbOwner = Get-SaLoginName -SqlInstance $destServer
                            }
    
                            if ($Pscmdlet.ShouldProcess($destinstance, "Detach $dbName from $source and attach, then update dbowner")) {
                                $migrationResult = Start-SqlDetachAttach $sourceServer $destServer $fileStructure $dbName
    
                                $dbFinish = Get-Date
    
                                if ($reattach -eq $true) {
                                    $sourceServer.Databases.Refresh()
                                    $destServer.Databases.Refresh()
                                    $result = Mount-SqlDatabase $sourceServer $dbName $sourceFileStructure $dbOwner
    
                                    if ($result -eq $true) {
                                        $sourceServer.Databases[$dbName].DatabaseOwnershipChaining = $sourceDbOwnerChaining
                                        $sourceServer.Databases[$dbName].Trustworthy = $sourceDbTrustworthy
                                        $sourceServer.Databases[$dbName].BrokerEnabled = $sourceDbBrokerEnabled
                                        $sourceServer.Databases[$dbName].Alter()
    
                                        if ($SetSourceReadOnly -or $sourceDbReadOnly) {
                                            try {
                                                $result = Set-DbaDbState -SqlInstance $sourceServer -Database $dbName -ReadOnly -EnableException
                                            } catch {
                                                Stop-Function -Message "Couldn't set database to read-only" -ErrorRecord $_
                                            }
                                        }
                                        Write-Message -Level Verbose -Message "Successfully reattached $dbName to $source."
                                    } else {
                                        Write-Message -Level Verbose -Message "Could not reattach $dbName to $source."
                                        $copyDatabaseStatus.Status = "Failed"
                                        $copyDatabaseStatus.Notes = "Could not reattach database to $source"
                                        $copyDatabaseStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
                                    }
                                }
    
                                if ($migrationResult -eq $true) {
                                    Write-Message -Level Verbose -Message "Successfully attached $dbName to $destinstance."
                                    $copyDatabaseStatus.Status = "Successful"
                                    $copyDatabaseStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
                                } else {
                                    Write-Message -Level Verbose -Message "Failed to attach $dbName to $destinstance. Aborting routine for this database."
    
                                    $copyDatabaseStatus.Status = "Failed"
                                    $copyDatabaseStatus.Notes = "Failed to attach database to destination"
                                    $copyDatabaseStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
    
                                    continue
                                }
                            }
                        }
                        $NewDatabase = Get-DbaDatabase -SqlInstance $destServer -database $DestinationdbName
    
                        # restore potentially lost settings
                        if ($destServer.VersionMajor -ge 9 -and $NoRecovery -eq $false) {
                            if ($sourceDbOwnerChaining -ne $NewDatabase.DatabaseOwnershipChaining) {
                                if ($Pscmdlet.ShouldProcess($destinstance, "Updating DatabaseOwnershipChaining on $DestinationdbName")) {
                                    try {
                                        $NewDatabase.DatabaseOwnershipChaining = $sourceDbOwnerChaining
                                        $NewDatabase.Alter()
                                        Write-Message -Level Verbose -Message "Successfully updated DatabaseOwnershipChaining for $sourceDbOwnerChaining on $DestinationdbName on $destinstance."
                                    } catch {
                                        $copyDatabaseStatus.Status = "Successful - failed to apply DatabaseOwnershipChaining."
                                        $copyDatabaseStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
                                        Stop-Function -Message "Failed to update DatabaseOwnershipChaining for $sourceDbOwnerChaining on $DestinationdbName on $destinstance." -Target $destinstance -ErrorRecord $_ -Continue
                                    }
                                }
                            }
    
                            if ($sourceDbTrustworthy -ne $NewDatabase.Trustworthy) {
                                if ($Pscmdlet.ShouldProcess($destinstance, "Updating Trustworthy on $DestinationdbName")) {
                                    try {
                                        $NewDatabase.Trustworthy = $sourceDbTrustworthy
                                        $NewDatabase.Alter()
                                        Write-Message -Level Verbose -Message "Successfully updated Trustworthy to $sourceDbTrustworthy for $DestinationdbName on $destinstance"
                                    } catch {
                                        $copyDatabaseStatus.Status = "Successful - failed to apply Trustworthy"
                                        $copyDatabaseStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
                                        Stop-Function -Message "Failed to update Trustworthy to $sourceDbTrustworthy for $DestinationdbName on $destinstance." -Target $destinstance -ErrorRecord $_ -Continue
                                    }
                                }
                            }
    
                            if ($sourceDbBrokerEnabled -ne $NewDatabase.BrokerEnabled) {
                                if ($Pscmdlet.ShouldProcess($destinstance, "Updating BrokerEnabled on $dbName")) {
                                    try {
                                        $NewDatabase.BrokerEnabled = $sourceDbBrokerEnabled
                                        $NewDatabase.Alter()
                                        Write-Message -Level Verbose -Message "Successfully updated BrokerEnabled to $sourceDbBrokerEnabled for $DestinationdbName on $destinstance."
                                    } catch {
                                        $copyDatabaseStatus.Status = "Successful - failed to apply BrokerEnabled"
                                        $copyDatabaseStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
                                        Stop-Function -Message "Failed to update BrokerEnabled to $sourceDbBrokerEnabled for $DestinationdbName on $destinstance." -Target $destinstance -ErrorRecord $_ -Continue
                                    }
                                }
                            }
                        }
    
                        if ($sourceDbReadOnly -ne $NewDatabase.ReadOnly -and -not $NoRecovery) {
                            if ($Pscmdlet.ShouldProcess($destinstance, "Updating ReadOnly status on $DestinationdbName")) {
                                try {
                                    if ($sourceDbReadOnly) {
                                        $result = Set-DbaDbState -SqlInstance $destserver -Database $DestinationdbName -ReadOnly -EnableException
                                    } else {
                                        $result = Set-DbaDbState -SqlInstance $destserver -Database $DestinationdbName -ReadWrite -EnableException
                                    }
                                } catch {
                                    $copyDatabaseStatus.Status = "Successful - failed to apply ReadOnly."
                                    $copyDatabaseStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
                                    Stop-Function -Message "Failed to update ReadOnly status on $DestinationdbName." -Target $destinstance -ErrorRecord $_ -Continue
                                }
                            }
                        }
    
                        if ($SetSourceOffline -and $sourceServer.databases[$DestinationdbName].status -notlike '*offline*') {
                            if ($Pscmdlet.ShouldProcess($destinstance, "Setting $DestinationdbName offline on $source")) {
                                Stop-DbaProcess -SqlInstance $sourceServer -Database $DestinationdbName
                                Set-DbaDbState -SqlInstance $sourceServer -SqlCredential $SourceSqlCredential -database $DestinationdbName -Offline
                            }
                        }
    
                        $dbTotalTime = $dbFinish - $dbStart
                        $dbTotalTime = ($dbTotalTime.ToString().Split(".")[0])
    
                        Write-Message -Level Verbose -Message "Finished: $dbFinish."
                        Write-Message -Level Verbose -Message "Elapsed time: $dbTotalTime."
    
                    } # end db by db processing
                }
            }
        }
        end {
            if (Test-FunctionInterrupt) {
                return
            }
            if (-not $NoBackupCleanUp -and $Destination.Count -gt 1) {
                foreach ($backupFile in ($backupCollection.BackupPath)) {
                    try {
                        if (Test-Path $backupFile -ErrorAction Stop) {
                            Write-Message -Level Verbose -Message "Deleting $backupFile."
                            Remove-Item $backupFile -ErrorAction Stop
                        }
                    } catch {
                        try {
                            Write-Message -Level Verbose -Message "Trying alternate SQL method to delete $backupFile."
                            $sql = "EXEC master.sys.xp_delete_file 0, '$backupFile'"
                            Write-Message -Level Debug -Message $sql
                            $null = $sourceServer.Query($sql)
                        } catch {
                            Write-Message -Level Verbose -Message "Cannot delete backup file $backupFile."
                        }
                    }
                }
            }
            if (Test-FunctionInterrupt) {
                return
            }
            if ($null -ne $elapsed) {
                $totalTime = ($elapsed.Elapsed.toString().Split(".")[0])
    
                Write-Message -Level Verbose -Message "`nDatabase migration finished"
                Write-Message -Level Verbose -Message "Migration started: $started"
                Write-Message -Level Verbose -Message "Migration completed: $(Get-Date)"
                Write-Message -Level Verbose -Message "Total Elapsed time: $totalTime"
    
                if ($SharedPath -and $NoBackupCleanup) {
                    Write-Message -Level Verbose -Message "Backups still exist at $SharedPath."
                }
            } else {
                Write-Message -Level Verbose -Message "No work was done, as we stopped during setup phase"
            }
            Test-DbaDeprecation -DeprecatedOn "1.0.0" -EnableException:$false -Alias Copy-SqlDatabase
        }
    }
    function Copy-DbaDataCollector {
        <#
        .SYNOPSIS
            Migrates user SQL Data Collector collection sets. SQL Data Collector configuration is on the agenda, but it's hard.
    
        .DESCRIPTION
            By default, all data collector objects are migrated. If the object already exists on the destination, it will be skipped unless -Force is used.
    
            The -CollectionSet parameter is auto-populated for command-line completion and can be used to copy only specific objects.
    
        .PARAMETER Source
            Source SQL Server. You must have sysadmin access and server version must be SQL Server version 2008 or higher.
    
        .PARAMETER SourceSqlCredential
            Login to the target instance using alternative credentials. Windows and SQL Authentication supported. Accepts credential objects (Get-Credential)
    
        .PARAMETER Destination
            Destination Sql Server. You must have sysadmin access and server version must be SQL Server version 2008 or higher.
    
        .PARAMETER DestinationSqlCredential
            Login to the target instance using alternative credentials. Windows and SQL Authentication supported. Accepts credential objects (Get-Credential)
    
        .PARAMETER CollectionSet
            The collection set(s) to process - this list is auto-populated from the server. If unspecified, all collection sets will be processed.
    
        .PARAMETER ExcludeCollectionSet
            The collection set(s) to exclude - this list is auto-populated from the server
    
        .PARAMETER NoServerReconfig
            Upcoming parameter to enable server reconfiguration
    
        .PARAMETER WhatIf
            Shows what would happen if the command were to run. No actions are actually performed.
    
        .PARAMETER Confirm
            Prompts you for confirmation before executing any changing operations within the command.
    
        .PARAMETER Force
            If collection sets exists on destination server, it will be dropped and recreated.
    
        .PARAMETER EnableException
            By default, when something goes wrong we try to catch it, interpret it and give you a friendly warning message.
            This avoids overwhelming you with "sea of red" exceptions, but is inconvenient because it basically disables advanced scripting.
            Using this switch turns this "nice by default" feature off and enables you to catch exceptions with your own try/catch.
    
        .NOTES
            Tags: Migration,DataCollection
            Author: Chrissy LeMaire (@cl), netnerds.net
    
            Website: https://dbatools.io
            Copyright: (c) 2018 by dbatools, licensed under MIT
            License: MIT https://opensource.org/licenses/MIT
    
            Requires: sysadmin access on SQL Servers
    
        .LINK
            https://dbatools.io/Copy-DbaDataCollector
    
        .EXAMPLE
            PS C:\> Copy-DbaDataCollector -Source sqlserver2014a -Destination sqlcluster
    
            Copies all Data Collector Objects and Configurations from sqlserver2014a to sqlcluster, using Windows credentials.
    
        .EXAMPLE
            PS C:\> Copy-DbaDataCollector -Source sqlserver2014a -Destination sqlcluster -SourceSqlCredential $cred
    
            Copies all Data Collector Objects and Configurations from sqlserver2014a to sqlcluster, using SQL credentials for sqlserver2014a and Windows credentials for sqlcluster.
    
        .EXAMPLE
            PS C:\> Copy-DbaDataCollector -Source sqlserver2014a -Destination sqlcluster -WhatIf
    
            Shows what would happen if the command were executed.
    
        .EXAMPLE
            PS C:\> Copy-DbaDataCollector -Source sqlserver2014a -Destination sqlcluster -CollectionSet 'Server Activity', 'Table Usage Analysis'
    
            Copies two Collection Sets, Server Activity and Table Usage Analysis, from sqlserver2014a to sqlcluster.
    
        #>
        [CmdletBinding(DefaultParameterSetName = "Default", SupportsShouldProcess = $true)]
        param (
            [parameter(Mandatory)]
            [DbaInstanceParameter]$Source,
            [PSCredential]
            $SourceSqlCredential,
            [parameter(Mandatory)]
            [DbaInstanceParameter[]]$Destination,
            [PSCredential]
            $DestinationSqlCredential,
            [object[]]$CollectionSet,
            [object[]]$ExcludeCollectionSet,
            [switch]$NoServerReconfig,
            [switch]$Force,
            [Alias('Silent')]
            [switch]$EnableException
        )
        begin {
            try {
                $sourceServer = Connect-SqlInstance -SqlInstance $Source -SqlCredential $SourceSqlCredential -MinimumVersion 10
            } catch {
                Stop-Function -Message "Failure" -Category ConnectionError -ErrorRecord $_ -Target $Source
                return
            }
            $sourceSqlConn = $sourceServer.ConnectionContext.SqlConnectionObject
            $sourceSqlStoreConnection = New-Object Microsoft.SqlServer.Management.Sdk.Sfc.SqlStoreConnection $sourceSqlConn
            $sourceStore = New-Object Microsoft.SqlServer.Management.Collector.CollectorConfigStore $sourceSqlStoreConnection
            $configDb = $sourceStore.ScriptAlter().GetScript() | Out-String
            $configDb = $configDb -replace [Regex]::Escape("'$source'"), "'$destReplace'"
        }
        process {
            if (Test-FunctionInterrupt) { return }
            foreach ($destinstance in $Destination) {
    
                try {
                    $destServer = Connect-SqlInstance -SqlInstance $destinstance -SqlCredential $DestinationSqlCredential -MinimumVersion 10
                } catch {
                    Stop-Function -Message "Failure" -Category ConnectionError -ErrorRecord $_ -Target $destinstance -Continue
                }
                if ($NoServerReconfig -eq $false) {
                    if ($Pscmdlet.ShouldProcess($destinstance, "Server reconfiguration not yet supported. Only Collection Set migration will be migrated at this time.")) {
                        Write-Message -Level Verbose -Message "Server reconfiguration not yet supported. Only Collection Set migration will be migrated at this time."
                        $NoServerReconfig = $true
    
                        <# for future use when this support is added #>
                        $copyServerConfigStatus = [pscustomobject]@{
                            SourceServer      = $sourceServer.Name
                            DestinationServer = $destServer.Name
                            Name              = $userName
                            Type              = "Data Collection Server Config"
                            Status            = "Skipped"
                            Notes             = "Not supported at this time"
                            DateTime          = [DbaDateTime](Get-Date)
                        }
                        $copyServerConfigStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
                    }
                }
                $destSqlConn = $destServer.ConnectionContext.SqlConnectionObject
                $destSqlStoreConnection = New-Object Microsoft.SqlServer.Management.Sdk.Sfc.SqlStoreConnection $destSqlConn
                $destStore = New-Object Microsoft.SqlServer.Management.Collector.CollectorConfigStore $destSqlStoreConnection
    
                if (!$NoServerReconfig) {
                    if ($Pscmdlet.ShouldProcess($destinstance, "Attempting to modify Data Collector configuration")) {
                        try {
                            $sql = "Unknown at this time"
                            $destServer.Query($sql)
                            $destStore.Alter()
                        } catch {
                            $copyServerConfigStatus.Status = "Failed"
                            $copyServerConfigStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
                            Stop-Function -Message "Issue modifying Data Collector configuration" -Target $destServer -ErrorRecord $_
                        }
                    }
                }
    
                if ($destStore.Enabled -eq $false) {
                    Write-Message -Level Verbose -Message "The Data Collector must be setup initially for Collection Sets to be migrated. Setup the Data Collector and try again."
                    continue
                }
    
                $storeCollectionSets = $sourceStore.CollectionSets | Where-Object { $_.IsSystem -eq $false }
                if ($CollectionSet) {
                    $storeCollectionSets = $storeCollectionSets | Where-Object Name -In $CollectionSet
                }
                if ($ExcludeCollectionSet) {
                    $storeCollectionSets = $storeCollectionSets | Where-Object Name -NotIn $ExcludeCollectionSet
                }
    
                Write-Message -Level Verbose -Message "Migrating collection sets"
                foreach ($set in $storeCollectionSets) {
                    $collectionName = $set.Name
    
                    $copyCollectionSetStatus = [pscustomobject]@{
                        SourceServer      = $sourceServer.Name
                        DestinationServer = $destServer.Name
                        Name              = $collectionName
                        Type              = "Collection Set"
                        Status            = $null
                        Notes             = $null
                        DateTime          = [DbaDateTime](Get-Date)
                    }
    
                    if ($null -ne $destStore.CollectionSets[$collectionName]) {
                        if ($force -eq $false) {
                            if ($Pscmdlet.ShouldProcess($destinstance, "Collection Set '$collectionName' was skipped because it already exists on $destinstance. Use -Force to drop and recreate")) {
                                Write-Message -Level Verbose -Message "Collection Set '$collectionName' was skipped because it already exists on $destinstance. Use -Force to drop and recreate"
    
                                $copyCollectionSetStatus.Status = "Skipped"
                                $copyCollectionSetStatus.Notes = "Already exists"
                                $copyCollectionSetStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
                            }
                            continue
                        } else {
                            if ($Pscmdlet.ShouldProcess($destinstance, "Attempting to drop $collectionName")) {
                                Write-Message -Level Verbose -Message "Collection Set '$collectionName' exists on $destinstance"
                                Write-Message -Level Verbose -Message "Force specified. Dropping $collectionName."
    
                                try {
                                    $destStore.CollectionSets[$collectionName].Drop()
                                } catch {
                                    $copyCollectionSetStatus.Status = "Failed to drop on destination"
                                    $copyCollectionSetStatus.Notes = (Get-ErrorMessage -Record $_)
                                    $copyCollectionSetStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
                                    Stop-Function -Message "Issue dropping collection" -Target $collectionName -ErrorRecord $_ -Continue
                                }
                            }
                        }
                    }
    
                    if ($Pscmdlet.ShouldProcess($destinstance, "Migrating collection set $collectionName")) {
                        try {
                            $sql = $set.ScriptCreate().GetScript() | Out-String
                            $sql = $sql -replace [Regex]::Escape("'$source'"), "'$destinstance'"
                            Write-Message -Level Debug -Message $sql
                            Write-Message -Level Verbose -Message "Migrating collection set $collectionName"
                            $destServer.Query($sql)
    
                            $copyCollectionSetStatus.Status = "Successful"
                            $copyCollectionSetStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
                        } catch {
                            $copyCollectionSetStatus.Status = "Failed to create collection"
                            $copyCollectionSetStatus.Notes = (Get-ErrorMessage -Record $_)
    
                            Stop-Function -Message "Issue creating collection set" -Target $collectionName -ErrorRecord $_
                        }
    
                        try {
                            if ($set.IsRunning) {
                                Write-Message -Level Verbose -Message "Starting collection set $collectionName"
                                $destStore.CollectionSets.Refresh()
                                $destStore.CollectionSets[$collectionName].Start()
                            }
    
                            $copyCollectionSetStatus.Status = "Successful started Collection"
                            $copyCollectionSetStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
                        } catch {
                            $copyCollectionSetStatus.Status = "Failed to start collection"
                            $copyCollectionSetStatus.Notes = (Get-ErrorMessage -Record $_)
                            $copyCollectionSetStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
    
                            Stop-Function -Message "Issue starting collection set" -Target $collectionName -ErrorRecord $_
                        }
                    }
                }
            }
        }
        end {
            Test-DbaDeprecation -DeprecatedOn "1.0.0" -EnableException:$false -Alias Copy-SqlDataCollector
            Test-DbaDeprecation -DeprecatedOn "1.0.0" -EnableException:$false -Alias Copy-DbaSqlDataCollector
            if (Test-FunctionInterrupt) { return }
        }
    }
    function Copy-DbaDbAssembly {
        <#
        .SYNOPSIS
            Copy-DbaDbAssembly migrates assemblies from one SQL Server to another.
    
        .DESCRIPTION
            By default, all assemblies are copied.
    
            If the assembly already exists on the destination, it will be skipped unless -Force is used.
    
            This script does not yet copy dependencies or dependent objects.
    
        .PARAMETER Source
            Source SQL Server. You must have sysadmin access and server version must be SQL Server version 2000 or higher.
    
        .PARAMETER SourceSqlCredential
            Login to the target instance using alternative credentials. Windows and SQL Authentication supported. Accepts credential objects (Get-Credential)
    
        .PARAMETER Destination
            Destination SQL Server. You must have sysadmin access and the server must be SQL Server 2000 or higher.
    
        .PARAMETER DestinationSqlCredential
            Login to the target instance using alternative credentials. Windows and SQL Authentication supported. Accepts credential objects (Get-Credential)
    
        .PARAMETER Assembly
            The assembly(ies) to process. This list is auto-populated from the server. If unspecified, all assemblies will be processed.
    
        .PARAMETER ExcludeAssembly
            The assembly(ies) to exclude. This list is auto-populated from the server.
    
        .PARAMETER WhatIf
            If this switch is enabled, no actions are performed but informational messages will be displayed that explain what would happen if the command were to run.
    
        .PARAMETER Confirm
            If this switch is enabled, you will be prompted for confirmation before executing any operations that change state.
    
        .PARAMETER EnableException
            By default, when something goes wrong we try to catch it, interpret it and give you a friendly warning message.
            This avoids overwhelming you with "sea of red" exceptions, but is inconvenient because it basically disables advanced scripting.
            Using this switch turns this "nice by default" feature off and enables you to catch exceptions with your own try/catch.
    
        .PARAMETER Force
            If this switch is enabled, existing assemblies on Destination with matching names from Source will be dropped.
    
        .NOTES
            Tags: Migration, Assembly
            Author: Chrissy LeMaire (@cl), netnerds.net
    
            Website: https://dbatools.io
            Copyright: (c) 2018 by dbatools, licensed under MIT
            License: MIT https://opensource.org/licenses/MIT
    
            Requires: sysadmin access on SQL Servers
    
        .LINK
            http://dbatools.io/Get-SqlDatabaseAssembly
    
        .EXAMPLE
            PS C:\> Copy-DbaDbAssembly -Source sqlserver2014a -Destination sqlcluster
    
            Copies all assemblies from sqlserver2014a to sqlcluster using Windows credentials. If assemblies with the same name exist on sqlcluster, they will be skipped.
    
        .EXAMPLE
            PS C:\> Copy-DbaDbAssembly -Source sqlserver2014a -Destination sqlcluster -Assembly dbname.assemblyname, dbname3.anotherassembly -SourceSqlCredential $cred -Force
    
            Copies two assemblies, the dbname.assemblyname and dbname3.anotherassembly from sqlserver2014a to sqlcluster using SQL credentials for sqlserver2014a and Windows credentials for sqlcluster. If an assembly with the same name exists on sqlcluster, it will be dropped and recreated because -Force was used.
    
            In this example, anotherassembly will be copied to the dbname3 database on the server sqlcluster.
    
        .EXAMPLE
            PS C:\> Copy-DbaDbAssembly -Source sqlserver2014a -Destination sqlcluster -WhatIf -Force
    
            Shows what would happen if the command were executed using force.
    
        #>
        [CmdletBinding(DefaultParameterSetName = "Default", SupportsShouldProcess = $true)]
        param (
            [parameter(Mandatory)]
            [DbaInstanceParameter]$Source,
            [PSCredential]$SourceSqlCredential,
            [parameter(Mandatory)]
            [DbaInstanceParameter[]]$Destination,
            [PSCredential]$DestinationSqlCredential,
            [object[]]$Assembly,
            [object[]]$ExcludeAssembly,
            [switch]$Force,
            [Alias('Silent')]
            [switch]$EnableException
        )
        begin {
            try {
                $sourceServer = Connect-SqlInstance -SqlInstance $Source -SqlCredential $SourceSqlCredential -MinimumVersion 9
            } catch {
                Stop-Function -Message "Failure" -Category ConnectionError -ErrorRecord $_ -Target $Source
                return
            }
            $sourceAssemblies = @()
            foreach ($database in ($sourceServer.Databases | Where-Object IsAccessible)) {
                Write-Message -Level Verbose -Message "Processing $database on source"
    
                try {
                    # a bug here requires a try/catch
                    $userAssemblies = $database.Assemblies | Where-Object IsSystemObject -eq $false
                    foreach ($assembly in $userAssemblies) {
                        $sourceAssemblies += $assembly
                    }
                } catch {
                    #here to avoid an empty catch
                    $null = 1
                }
            }
        }
        process {
            if (Test-FunctionInterrupt) { return }
            foreach ($destinstance in $Destination) {
                try {
                    $destServer = Connect-SqlInstance -SqlInstance $destinstance -SqlCredential $DestinationSqlCredential -MinimumVersion 9
                } catch {
                    Stop-Function -Message "Failure" -Category ConnectionError -ErrorRecord $_ -Target $destinstance -Continue
                }
    
                $destAssemblies = @()
                foreach ($database in $destServer.Databases) {
                    Write-Message -Level VeryVerbose -Message "Processing $database on destination"
                    try {
                        # a bug here requires a try/catch
                        $userAssemblies = $database.Assemblies | Where-Object IsSystemObject -eq $false
                        foreach ($assembly in $userAssemblies) {
                            $destAssemblies += $assembly
                        }
                    } catch {
                        #here to avoid an empty catch
                        $null = 1
                    }
                }
                foreach ($currentAssembly in $sourceAssemblies) {
                    $assemblyName = $currentAssembly.Name
                    $dbName = $currentAssembly.Parent.Name
                    $destDb = $destServer.Databases[$dbName]
                    Write-Message -Level VeryVerbose -Message "Processing $assemblyName on $dbname"
                    $copyDbAssemblyStatus = [pscustomobject]@{
                        SourceServer        = $sourceServer.Name
                        SourceDatabase      = $dbName
                        DestinationServer   = $destServer.Name
                        DestinationDatabase = $destDb
                        type                = "Database Assembly"
                        Name                = $assemblyName
                        Status              = $null
                        Notes               = $null
                        DateTime            = [Sqlcollaborative.Dbatools.Utility.DbaDateTime](Get-Date)
                    }
    
    
                    if (!$destDb) {
                        $copyDbAssemblyStatus.Status = "Skipped"
                        $copyDbAssemblyStatus.Notes = "Destination database does not exist"
                        $copyDbAssemblyStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
    
                        Write-Message -Level Verbose -Message "Destination database $dbName does not exist. Skipping $assemblyName.";
                        continue
                    }
    
                    if ((Test-Bound -ParameterName Assembly) -and $Assembly -notcontains "$dbName.$assemblyName" -or $ExcludeAssembly -contains "$dbName.$assemblyName") {
                        continue
                    }
    
                    if ($currentAssembly.AssemblySecurityLevel -eq "External" -and -not $destDb.Trustworthy) {
                        if ($Pscmdlet.ShouldProcess($destinstance, "Setting $dbName to External")) {
                            Write-Message -Level Verbose -Message "Setting $dbName Security Level to External on $destinstance."
                            $sql = "ALTER DATABASE $dbName SET TRUSTWORTHY ON"
                            try {
                                Write-Message -Level Debug -Message $sql
                                $destServer.Query($sql)
                            } catch {
                                $copyDbAssemblyStatus.Status = "Failed"
                                $copyDbAssemblyStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
    
                                Stop-Function -Message "Issue setting security level." -Target $destDb -ErrorRecord $_
                            }
                        }
                    }
    
                    if ($destServer.Databases[$dbName].Assemblies.Name -contains $currentAssembly.name) {
                        if ($force -eq $false) {
                            $copyDbAssemblyStatus.Status = "Skipped"
                            $copyDbAssemblyStatus.Notes = "Already exists"
                            $copyDbAssemblyStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
    
                            Write-Message -Level Verbose -Message "Assembly $assemblyName exists at destination in the $dbName database. Use -Force to drop and migrate."
                            continue
                        } else {
                            if ($Pscmdlet.ShouldProcess($destinstance, "Dropping assembly $assemblyName and recreating")) {
                                try {
                                    Write-Message -Level Verbose -Message "Dropping assembly $assemblyName."
                                    Write-Message -Level Verbose -Message "This won't work if there are dependencies."
                                    $destServer.Databases[$dbName].Assemblies[$assemblyName].Drop()
                                    Write-Message -Level Verbose -Message "Copying assembly $assemblyName."
                                    $sql = $currentAssembly.Script()
                                    Write-Message -Level Debug -Message $sql
                                    $destServer.Query($sql, $dbName)
                                } catch {
                                    $copyDbAssemblyStatus.Status = "Failed"
                                    $copyDbAssemblyStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
    
                                    Stop-Function -Message "Issue dropping assembly." -Target $assemblyName -ErrorRecord $_ -Continue
                                }
                            }
                        }
                    }
    
                    if ($Pscmdlet.ShouldProcess($destinstance, "Creating assembly $assemblyName")) {
                        try {
                            Write-Message -Level Verbose -Message "Copying assembly $assemblyName from database."
                            $sql = $currentAssembly.Script()
                            Write-Message -Level Debug -Message $sql
                            $destServer.Query($sql, $dbName)
    
                            $copyDbAssemblyStatus.Status = "Successful"
                            $copyDbAssemblyStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
    
                        } catch {
                            $copyDbAssemblyStatus.Status = "Failed"
                            $copyDbAssemblyStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
    
                            Stop-Function -Message "Issue creating assembly." -Target $assemblyName -ErrorRecord $_
                        }
                    }
                }
            }
        }
        end {
            Test-DbaDeprecation -DeprecatedOn "1.0.0" -EnableException:$false -Alias Copy-SqlDatabaseAssembly
            Test-DbaDeprecation -DeprecatedOn "1.0.0" -EnableException:$false -Alias Copy-DbaDatabaseAssembly
        }
    }
    function Copy-DbaDbMail {
        <#
        .SYNOPSIS
            Migrates Mail Profiles, Accounts, Mail Servers and Mail Server Configs from one SQL Server to another.
    
        .DESCRIPTION
            By default, all mail configurations for Profiles, Accounts, Mail Servers and Configs are copied.
    
        .PARAMETER Source
            Source SQL Server. You must have sysadmin access and server version must be SQL Server version 2000 or higher.
    
        .PARAMETER SourceSqlCredential
            Login to the target instance using alternative credentials. Windows and SQL Authentication supported. Accepts credential objects (Get-Credential)
    
        .PARAMETER Destination
            Destination SQL Server. You must have sysadmin access and the server must be SQL Server 2000 or higher.
    
        .PARAMETER DestinationSqlCredential
            Login to the target instance using alternative credentials. Windows and SQL Authentication supported. Accepts credential objects (Get-Credential)
    
        .PARAMETER Type
            Specifies the object type to migrate. Valid options are "Job", "Alert" and "Operator". When Type is specified, all categories from the selected type will be migrated.
    
        .PARAMETER WhatIf
            If this switch is enabled, no actions are performed but informational messages will be displayed that explain what would happen if the command were to run.
    
        .PARAMETER Confirm
            If this switch is enabled, you will be prompted for confirmation before executing any operations that change state.
    
        .PARAMETER EnableException
            By default, when something goes wrong we try to catch it, interpret it and give you a friendly warning message.
            This avoids overwhelming you with "sea of red" exceptions, but is inconvenient because it basically disables advanced scripting.
            Using this switch turns this "nice by default" feature off and enables you to catch exceptions with your own try/catch.
    
        .PARAMETER Force
            If this switch is enabled, existing objects on Destination with matching names from Source will be dropped.
    
        .NOTES
            Tags: Migration, Mail
            Author: Chrissy LeMaire (@cl), netnerds.net
    
            Website: https://dbatools.io
            Copyright: (c) 2018 by dbatools, licensed under MIT
            License: MIT https://opensource.org/licenses/MIT
    
            Requires: sysadmin access on SQL Servers
    
        .LINK
            https://dbatools.io/Copy-DbaDbMail
    
        .EXAMPLE
            PS C:\> Copy-DbaDbMail -Source sqlserver2014a -Destination sqlcluster
    
            Copies all database mail objects from sqlserver2014a to sqlcluster using Windows credentials. If database mail objects with the same name exist on sqlcluster, they will be skipped.
    
        .EXAMPLE
            PS C:\> Copy-DbaDbMail -Source sqlserver2014a -Destination sqlcluster -SourceSqlCredential $cred
    
            Copies all database mail objects from sqlserver2014a to sqlcluster using SQL credentials for sqlserver2014a and Windows credentials for sqlcluster.
    
        .EXAMPLE
            PS C:\> Copy-DbaDbMail -Source sqlserver2014a -Destination sqlcluster -WhatIf
    
            Shows what would happen if the command were executed.
    
        .EXAMPLE
            PS C:\> Copy-DbaDbMail -Source sqlserver2014a -Destination sqlcluster -EnableException
    
            Performs execution of function, and will throw a terminating exception if something breaks
    
        #>
        [cmdletbinding(DefaultParameterSetName = "Default", SupportsShouldProcess = $true)]
        param (
            [parameter(Mandatory)]
            [DbaInstanceParameter]$Source,
            [parameter(Mandatory)]
            [DbaInstanceParameter[]]$Destination,
            [Parameter(ParameterSetName = 'SpecificTypes')]
            [ValidateSet('ConfigurationValues', 'Profiles', 'Accounts', 'mailServers')]
            [string[]]$Type,
            [PSCredential]$SourceSqlCredential,
            [PSCredential]$DestinationSqlCredential,
            [switch]$Force,
            [Alias('Silent')]
            [switch]$EnableException
        )
        begin {
            function Copy-DbaDbMailConfig {
                [cmdletbinding(SupportsShouldProcess)]
                param ()
    
                Write-Message -Message "Migrating mail server configuration values." -Level Verbose
                $copyMailConfigStatus = [pscustomobject]@{
                    SourceServer      = $sourceServer.Name
                    DestinationServer = $destServer.Name
                    Name              = "Server Configuration"
                    Type              = "Mail Configuration"
                    Status            = $null
                    Notes             = $null
                    DateTime          = [Sqlcollaborative.Dbatools.Utility.DbaDateTime](Get-Date)
                }
                if ($pscmdlet.ShouldProcess($destinstance, "Migrating all mail server configuration values.")) {
                    try {
                        $sql = $mail.ConfigurationValues.Script() | Out-String
                        $sql = $sql -replace [Regex]::Escape("'$source'"), "'$destinstance'"
                        Write-Message -Message $sql -Level Debug
                        $destServer.Query($sql) | Out-Null
                        $mail.ConfigurationValues.Refresh()
                        $copyMailConfigStatus.Status = "Successful"
                    } catch {
                        $copyMailConfigStatus.Status = "Failed"
                        $copyMailConfigStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
                        Stop-Function -Message "Unable to migrate mail configuration." -Category InvalidOperation -InnerErrorRecord $_ -Target $destServer
                    }
                    $copyMailConfigStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
                }
            }
    
            function Copy-DbaDatabaseAccount {
                [cmdletbinding(SupportsShouldProcess)]
                $sourceAccounts = $sourceServer.Mail.Accounts
                $destAccounts = $destServer.Mail.Accounts
    
                Write-Message -Message "Migrating accounts." -Level Verbose
                foreach ($account in $sourceAccounts) {
                    $accountName = $account.name
                    $copyMailAccountStatus = [pscustomobject]@{
                        SourceServer      = $sourceServer.Name
                        DestinationServer = $destServer.Name
                        Name              = $accountName
                        Type              = "Mail Account"
                        Status            = $null
                        Notes             = $null
                        DateTime          = [Sqlcollaborative.Dbatools.Utility.DbaDateTime](Get-Date)
                    }
    
                    if ($accounts.count -gt 0 -and $accounts -notcontains $accountName) {
                        continue
                    }
    
                    if ($destAccounts.name -contains $accountName) {
                        if ($force -eq $false) {
                            If ($pscmdlet.ShouldProcess($destinstance, "Account $accountName exists at destination. Use -Force to drop and migrate.")) {
                                $copyMailAccountStatus.Status = "Skipped"
                                $copyMailAccountStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
                                Write-Message -Message "Account $accountName exists at destination. Use -Force to drop and migrate." -Level Verbose
                            }
                            continue
                        }
    
                        If ($pscmdlet.ShouldProcess($destinstance, "Dropping account $accountName and recreating.")) {
                            try {
                                Write-Message -Message "Dropping account $accountName." -Level Verbose
                                $destServer.Mail.Accounts[$accountName].Drop()
                                $destServer.Mail.Accounts.Refresh()
                            } catch {
                                $copyMailAccountStatus.Status = "Failed"
                                $copyMailAccountStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
                                Stop-Function -Message "Issue dropping account." -Target $accountName -Category InvalidOperation -InnerErrorRecord $_ -Continue
                            }
                        }
                    }
    
                    if ($pscmdlet.ShouldProcess($destinstance, "Migrating account $accountName.")) {
                        try {
                            Write-Message -Message "Copying mail account $accountName." -Level Verbose
                            $sql = $account.Script() | Out-String
                            $sql = $sql -replace [Regex]::Escape("'$source'"), "'$destinstance'"
                            Write-Message -Message $sql -Level Debug
                            $destServer.Query($sql) | Out-Null
                            $copyMailAccountStatus.Status = "Successful"
                        } catch {
                            $copyMailAccountStatus.Status = "Failed"
                            $copyMailAccountStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
                            Stop-Function -Message "Issue copying mail account." -Target $accountName -Category InvalidOperation -InnerErrorRecord $_
                        }
                        $copyMailAccountStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
                    }
                }
            }
    
            function Copy-DbaDbMailProfile {
    
                $sourceProfiles = $sourceServer.Mail.Profiles
                $destProfiles = $destServer.Mail.Profiles
    
                Write-Message -Message "Migrating mail profiles." -Level Verbose
                foreach ($profile in $sourceProfiles) {
    
                    $profileName = $profile.name
                    $copyMailProfileStatus = [pscustomobject]@{
                        SourceServer      = $sourceServer.Name
                        DestinationServer = $destServer.Name
                        Name              = $profileName
                        Type              = "Mail Profile"
                        Status            = $null
                        Notes             = $null
                        DateTime          = [Sqlcollaborative.Dbatools.Utility.DbaDateTime](Get-Date)
                    }
    
                    if ($profiles.count -gt 0 -and $profiles -notcontains $profileName) {
                        continue
                    }
    
                    if ($destProfiles.name -contains $profileName) {
                        if ($force -eq $false) {
                            If ($pscmdlet.ShouldProcess($destinstance, "Profile $profileName exists at destination. Use -Force to drop and migrate.")) {
                                $copyMailProfileStatus.Status = "Skipped"
                                $copyMailProfileStatus.Notes = "Already exists"
                                $copyMailProfileStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
                                Write-Message -Message "Profile $profileName exists at destination. Use -Force to drop and migrate." -Level Verbose
                            }
                            continue
                        }
    
                        If ($pscmdlet.ShouldProcess($destinstance, "Dropping profile $profileName and recreating.")) {
                            try {
                                Write-Message -Message "Dropping profile $profileName." -Level Verbose
                                $destServer.Mail.Profiles[$profileName].Drop()
                                $destServer.Mail.Profiles.Refresh()
                            } catch {
                                $copyMailProfileStatus.Status = "Failed"
                                $copyMailProfileStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
                                Stop-Function -Message "Issue dropping profile." -Target $profileName -Category InvalidOperation -InnerErrorRecord $_ -Continue
                            }
                        }
                    }
    
                    if ($pscmdlet.ShouldProcess($destinstance, "Migrating mail profile $profileName.")) {
                        try {
                            Write-Message -Message "Copying mail profile $profileName." -Level Verbose
                            $sql = $profile.Script() | Out-String
                            $sql = $sql -replace [Regex]::Escape("'$source'"), "'$destinstance'"
                            Write-Message -Message $sql -Level Debug
                            $destServer.Query($sql) | Out-Null
                            $destServer.Mail.Profiles.Refresh()
                            $copyMailProfileStatus.Status = "Successful"
                        } catch {
                            $copyMailProfileStatus.Status = "Failed"
                            $copyMailProfileStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
                            Stop-Function -Message "Issue copying mail profile." -Target $profileName -Category InvalidOperation -InnerErrorRecord $_
                        }
                        $copyMailProfileStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
                    }
                }
            }
    
            function Copy-DbaDbMailServer {
                [cmdletbinding(SupportsShouldProcess)]
                $sourceMailServers = $sourceServer.Mail.Accounts.MailServers
                $destMailServers = $destServer.Mail.Accounts.MailServers
    
                Write-Message -Message "Migrating mail servers." -Level Verbose
                foreach ($mailServer in $sourceMailServers) {
                    $mailServerName = $mailServer.name
                    $copyMailServerStatus = [pscustomobject]@{
                        SourceServer      = $sourceServer.Name
                        DestinationServer = $destServer.Name
                        Name              = $mailServerName
                        Type              = "Mail Server"
                        Status            = $null
                        Notes             = $null
                        DateTime          = [Sqlcollaborative.Dbatools.Utility.DbaDateTime](Get-Date)
                    }
                    if ($mailServers.count -gt 0 -and $mailServers -notcontains $mailServerName) {
                        continue
                    }
    
                    if ($destMailServers.name -contains $mailServerName) {
                        if ($force -eq $false) {
                            if ($pscmdlet.ShouldProcess($destinstance, "Mail server $mailServerName exists at destination. Use -Force to drop and migrate.")) {
                                $copyMailServerStatus.Status = "Skipped"
                                $copyMailServerStatus.Notes = "Already exists"
                                $copyMailServerStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
                                Write-Message -Message "Mail server $mailServerName exists at destination. Use -Force to drop and migrate." -Level Verbose
                            }
                            continue
                        }
    
                        If ($pscmdlet.ShouldProcess($destinstance, "Dropping mail server $mailServerName and recreating.")) {
                            try {
                                Write-Message -Message "Dropping mail server $mailServerName." -Level Verbose
                                $destServer.Mail.Accounts.MailServers[$mailServerName].Drop()
                            } catch {
                                $copyMailServerStatus.Status = "Failed"
                                $copyMailServerStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
                                Stop-Function -Message "Issue dropping mail server." -Target $mailServerName -Category InvalidOperation -InnerErrorRecord $_ -Continue
                            }
                        }
                    }
    
                    if ($pscmdlet.ShouldProcess($destinstance, "Migrating account mail server $mailServerName.")) {
                        try {
                            Write-Message -Message "Copying mail server $mailServerName." -Level Verbose
                            $sql = $mailServer.Script() | Out-String
                            $sql = $sql -replace [Regex]::Escape("'$source'"), "'$destinstance'"
                            Write-Message -Message $sql -Level Debug
                            $destServer.Query($sql) | Out-Null
                            $copyMailServerStatus.Status = "Successful"
                        } catch {
                            $copyMailServerStatus.Status = "Failed"
                            $copyMailServerStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
                            Stop-Function -Message "Issue copying mail server" -Target $mailServerName -Category InvalidOperation -InnerErrorRecord $_
                        }
                        $copyMailServerStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
                    }
                }
            }
    
            try {
                $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 {
                    $destServer = Connect-SqlInstance -SqlInstance $destinstance -SqlCredential $DestinationSqlCredential -MinimumVersion 9
                } catch {
                    Stop-Function -Message "Failure" -Category ConnectionError -ErrorRecord $_ -Target $destinstance -Continue
                }
    
                if ($type.Count -gt 0) {
    
                    switch ($type) {
                        "ConfigurationValues" {
                            Copy-DbaDbMailConfig
                            $destServer.Mail.ConfigurationValues.Refresh()
                        }
    
                        "Profiles" {
                            Copy-DbaDbMailProfile
                            $destServer.Mail.Profiles.Refresh()
                        }
    
                        "Accounts" {
                            Copy-DbaDatabaseAccount
                            $destServer.Mail.Accounts.Refresh()
                        }
    
                        "mailServers" {
                            Copy-DbaDbMailServer
                        }
                    }
    
                    continue
                }
    
                if (($profiles.count + $accounts.count + $mailServers.count) -gt 0) {
    
                    if ($profiles.count -gt 0) {
                        Copy-DbaDbMailProfile -Profiles $profiles
                        $destServer.Mail.Profiles.Refresh()
                    }
    
                    if ($accounts.count -gt 0) {
                        Copy-DbaDatabaseAccount -Accounts $accounts
                        $destServer.Mail.Accounts.Refresh()
                    }
    
                    if ($mailServers.count -gt 0) {
                        Copy-DbaDbMailServer -mailServers $mailServers
                    }
    
                    continue
                }
    
                Copy-DbaDbMailConfig
                $destServer.Mail.ConfigurationValues.Refresh()
                Copy-DbaDatabaseAccount
                $destServer.Mail.Accounts.Refresh()
                Copy-DbaDbMailProfile
                $destServer.Mail.Profiles.Refresh()
                Copy-DbaDbMailServer
                $copyMailConfigStatus
                $copyMailAccountStatus
                $copyMailProfileStatus
                $copyMailServerStatus
                $enableDBMailStatus
    
                <# ToDo: Use Get/Set-DbaSpConfigure once the dynamic parameters are replaced. #>
    
                if (($sourceDbMailEnabled -eq 1) -and ($destDbMailEnabled -eq 0)) {
                    if ($pscmdlet.ShouldProcess($destinstance, "Enabling Database Mail")) {
                        $sourceDbMailEnabled = ($sourceServer.Configuration.DatabaseMailEnabled).ConfigValue
                        Write-Message -Message "$sourceServer DBMail configuration value: $sourceDbMailEnabled." -Level Verbose
    
                        $destDbMailEnabled = ($destServer.Configuration.DatabaseMailEnabled).ConfigValue
                        Write-Message -Message "$destServer DBMail configuration value: $destDbMailEnabled." -Level Verbose
                        $enableDBMailStatus = [pscustomobject]@{
                            SourceServer      = $sourceServer.name
                            DestinationServer = $destServer.name
                            Name              = "Enabled on Destination"
                            Type              = "Mail Configuration"
                            Status            = if ($destDbMailEnabled -eq 1) { "Enabled" } else { $null }
                            DateTime          = [Sqlcollaborative.Dbatools.Utility.DbaDateTime](Get-Date)
                        }
                        try {
                            Write-Message -Message "Enabling Database Mail on $destServer." -Level Verbose
                            $destServer.Configuration.DatabaseMailEnabled.ConfigValue = 1
                            $destServer.Alter()
                            $enableDBMailStatus.Status = "Successful"
                        } catch {
                            $enableDBMailStatus.Status = "Failed"
                            $enableDBMailStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
                            Stop-Function -Message "Cannot enable Database Mail." -Category InvalidOperation -ErrorRecord $_ -Target $destServer
                        }
                        $enableDBMailStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
                    }
                }
            }
        }
        end {
            Test-DbaDeprecation -DeprecatedOn "1.0.0" -EnableException:$false -Alias Copy-SqlDatabaseMail
            Test-DbaDeprecation -DeprecatedOn "1.0.0" -EnableException:$false -Alias Copy-DbaDatabaseMail
        }
    }
    #ValidationTags#Messaging,FlowControl,Pipeline,CodeStyle#
    function Copy-DbaDbTableData {
        <#
        .SYNOPSIS
            Copies data between SQL Server tables.
    
        .DESCRIPTION
            Copies data between SQL Server tables using SQL Bulk Copy.
            The same can be achieved also doing
            $sourcetable = Invoke-DbaQuery -SqlInstance instance1 ... -As DataTable
            Write-DbaDataTable -SqlInstance ... -InputObject $sourcetable
            but it will force buffering the contents on the table in memory (high RAM usage for large tables).
            With this function, a streaming copy will be done in the most speedy and least resource-intensive way.
    
        .PARAMETER SqlInstance
            Source SQL Server.You must have sysadmin access and server version must be SQL Server version 2000 or greater.
    
        .PARAMETER SqlCredential
            Login to the target instance using alternative credentials. Windows and SQL Authentication supported. Accepts credential objects (Get-Credential)
    
        .PARAMETER Destination
            Destination Sql Server. You must have sysadmin access and server version must be SQL Server version 2000 or greater.
    
        .PARAMETER DestinationSqlCredential
            Login to the target instance using alternative credentials. Windows and SQL Authentication supported. Accepts credential objects (Get-Credential)
    
        .PARAMETER Database
            The database to copy the table from.
    
        .PARAMETER DestinationDatabase
            The database to copy the table to. If not specified, it is assumed to be the same of Database
    
        .PARAMETER Table
            Define a specific table you would like to use as source. You can specify a three-part name like db.sch.tbl.
            If the object has special characters please wrap them in square brackets [ ].
            This dbo.First.Table will try to find table named 'Table' on schema 'First' and database 'dbo'.
            The correct way to find table named 'First.Table' on schema 'dbo' is passing dbo.[First.Table]
    
        .PARAMETER DestinationTable
            The table you want to use as destination. If not specified, it is assumed to be the same of Table
    
        .PARAMETER Query
            If you want to copy only a portion of a table or selected tables, specify the query.
            Ensure to select all required columns. Calculated Columns or columns with default values may be excluded.
            The tablename should be a full three-part name in form [Database].[Schema].[Table]
    
        .PARAMETER AutoCreateTable
            Creates the destination table if it does not already exist, based off of the "Export..." script of the source table.
    
        .PARAMETER BatchSize
            The BatchSize for the import defaults to 5000.
    
        .PARAMETER NotifyAfter
            Sets the option to show the notification after so many rows of import
    
        .PARAMETER NoTableLock
            If this switch is enabled, a table lock (TABLOCK) will not be placed on the destination table. By default, this operation will lock the destination table while running.
    
        .PARAMETER CheckConstraints
            If this switch is enabled, the SqlBulkCopy option to process check constraints will be enabled.
    
            Per Microsoft "Check constraints while data is being inserted. By default, constraints are not checked."
    
        .PARAMETER FireTriggers
            If this switch is enabled, the SqlBulkCopy option to fire insert triggers will be enabled.
    
            Per Microsoft "When specified, cause the server to fire the insert triggers for the rows being inserted into the Database."
    
        .PARAMETER KeepIdentity
            If this switch is enabled, the SqlBulkCopy option to preserve source identity values will be enabled.
    
            Per Microsoft "Preserve source identity values. When not specified, identity values are assigned by the destination."
    
        .PARAMETER KeepNulls
            If this switch is enabled, the SqlBulkCopy option to preserve NULL values will be enabled.
    
            Per Microsoft "Preserve null values in the destination table regardless of the settings for default values. When not specified, null values are replaced by default values where applicable."
    
        .PARAMETER Truncate
            If this switch is enabled, the destination table will be truncated after prompting for confirmation.
    
        .PARAMETER BulkCopyTimeOut
            Value in seconds for the BulkCopy operations timeout. The default is 30 seconds.
    
        .PARAMETER InputObject
            Enables piping of Table objects from Get-DbaDbTable
    
        .PARAMETER WhatIf
            If this switch is enabled, no actions are performed but informational messages will be displayed that explain what would happen if the command were to run.
    
        .PARAMETER Confirm
            If this switch is enabled, you will be prompted for confirmation before executing any operations that change state.
    
        .PARAMETER EnableException
            By default, when something goes wrong we try to catch it, interpret it and give you a friendly warning message.
            This avoids overwhelming you with "sea of red" exceptions, but is inconvenient because it basically disables advanced scripting.
            Using this switch turns this "nice by default" feature off and enables you to catch exceptions with your own try/catch.
    
        .NOTES
            Tags: Migration
            Author: Simone Bizzotto (@niphlod)
    
            Website: https://dbatools.io
            Copyright: (c) 2018 by dbatools, licensed under MIT
            License: MIT https://opensource.org/licenses/MIT
    
        .LINK
            https://dbatools.io/Copy-DbaDbTableData
    
        .EXAMPLE
            PS C:\> Copy-DbaDbTableData -SqlInstance sql1 -Destination sql2 -Database dbatools_from -Table dbo.test_table
    
            Copies all the data from table dbo.test_table in database dbatools_from on sql1 to table test_table in database dbatools_from on sql2.
    
        .EXAMPLE
            PS C:\> Copy-DbaDbTableData -SqlInstance sql1 -Destination sql2 -Database dbatools_from -DestinationDatabase dbatools_dest -Table [Schema].[test table]
    
            Copies all the data from table [Schema].[test table] in database dbatools_from on sql1 to table [Schema].[test table] in database dbatools_dest on sql2
    
        .EXAMPLE
            PS C:\> Get-DbaDbTable -SqlInstance sql1 -Database tempdb -Table tb1, tb2 | Copy-DbaDbTableData -DestinationTable tb3
    
            Copies all data from tables tb1 and tb2 in tempdb on sql1 to tb3 in tempdb on sql1
    
        .EXAMPLE
            PS C:\> Get-DbaDbTable -SqlInstance sql1 -Database tempdb -Table tb1, tb2 | Copy-DbaDbTableData -Destination sql2
    
            Copies data from tbl1 in tempdb on sql1 to tbl1 in tempdb on sql2
            then
            Copies data from tbl2 in tempdb on sql1 to tbl2 in tempdb on sql2
    
        .EXAMPLE
            PS C:\> Copy-DbaDbTableData -SqlInstance sql1 -Destination sql2 -Database dbatools_from -Table test_table -KeepIdentity -Truncate
    
            Copies all the data in table test_table from sql1 to sql2, using the database dbatools_from, keeping identity columns and truncating the destination
    
        .EXAMPLE
            PS C:\> $params = @{
            >> SourceSqlInstance = 'sql1'
            >> DestinationSqlInstance = 'sql2'
            >> Database = 'dbatools_from'
            >> DestinationDatabase = 'dbatools_dest'
            >> Table = '[Schema].[Table]'
            >> DestinationTable = '[dbo].[Table.Copy]'
            >> KeepIdentity = $true
            >> KeepNulls = $true
            >> Truncate = $true
            >> BatchSize = 10000
            >> }
            >>
            PS C:\> Copy-DbaDbTableData @params
    
            Copies all the data from table [Schema].[Table] in database dbatools_from on sql1 to table [dbo].[Table.Copy] in database dbatools_dest on sql2
            Keeps identity columns and Nulls, truncates the destination and processes in BatchSize of 10000.
    
           #>
        [CmdletBinding(DefaultParameterSetName = "Default", SupportsShouldProcess = $true)]
        param (
            [Alias("ServerInstance", "SqlServer", "Source")]
            [DbaInstanceParameter]$SqlInstance,
            [PSCredential]$SqlCredential,
            [DbaInstanceParameter[]]$Destination,
            [PSCredential]$DestinationSqlCredential,
            [string]$Database,
            [string]$DestinationDatabase,
            [string[]]$Table,
            [string]$Query,
            [switch]$AutoCreateTable,
            [int]$BatchSize = 50000,
            [int]$NotifyAfter = 5000,
            [string]$DestinationTable,
            [switch]$NoTableLock,
            [switch]$CheckConstraints,
            [switch]$FireTriggers,
            [switch]$KeepIdentity,
            [switch]$KeepNulls,
            [switch]$Truncate,
            [int]$bulkCopyTimeOut = 5000,
            [Parameter(ValueFromPipeline)]
            [Microsoft.SqlServer.Management.Smo.Table[]]$InputObject,
            [switch]$EnableException
        )
    
        begin {
            # Getting the total rows copied is a challenge. Use SqlBulkCopyExtension.
            # http://stackoverflow.com/questions/1188384/sqlbulkcopy-row-count-when-complete
    
            $sourcecode = 'namespace System.Data.SqlClient {
                using Reflection;
    
                public static class SqlBulkCopyExtension
                {
                    const String _rowsCopiedFieldName = "_rowsCopied";
                    static FieldInfo _rowsCopiedField = null;
    
                    public static int RowsCopiedCount(this SqlBulkCopy bulkCopy)
                    {
                        if (_rowsCopiedField == null) _rowsCopiedField = typeof(SqlBulkCopy).GetField(_rowsCopiedFieldName, BindingFlags.NonPublic | BindingFlags.GetField | BindingFlags.Instance);
                        return (int)_rowsCopiedField.GetValue(bulkCopy);
                    }
                }
            }'
    
            Add-Type -ReferencedAssemblies System.Data.dll -TypeDefinition $sourcecode -ErrorAction 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
                }
    
                if ((Test-Bound -Not -ParameterName Destination, DestinationDatabase, DestinationTable)) {
                    Stop-Function -Message "Cannot copy $Table into itself. One of destination Server, Database or Table must be specified " -Target $Table
                    return
                }
    
                try {
                    $server = Connect-SqlInstance -SqlInstance $SqlInstance -SqlCredential $SqlCredential
                } catch {
                    Stop-Function -Message "Failure" -Category ConnectionError -ErrorRecord $_ -Target $SqlInstance
                    return
                }
    
                if ($Database -notin $server.Databases.Name) {
                    Stop-Function -Message "Database $Database doesn't exist on $server"
                    return
                }
    
                try {
                    foreach ($tbl in $Table) {
                        $dbTable = Get-DbaDbTable -SqlInstance $server -Table $tbl -Database $Database -EnableException -Verbose:$false
                        if ($dbTable.Count -eq 1) {
                            $InputObject += $dbTable
                        } else {
                            Stop-Function -Message "The table $tbl matches $($dbTable.Count) objects. Unable to determine which object to copy" -Continue
                        }
                    }
                } catch {
                    Stop-Function -Message "Unable to determine source table : $Table"
                    return
                }
            }
    
            foreach ($sqltable in $InputObject) {
                $Database = $sqltable.Parent.Name
                $server = $sqltable.Parent.Parent
    
                if ((Test-Bound -Not -ParameterName DestinationTable)) {
                    $DestinationTable = '[' + $sqltable.Schema + '].[' + $sqltable.Name + ']'
                }
    
                $newTableParts = Get-TableNameParts $DestinationTable
                #using FQTN to determine database name
                if ($newTableParts.Database) {
                    $DestinationDatabase = $newTableParts.Database
                } elseif ((Test-Bound -Not -ParameterName DestinationDatabase)) {
                    $DestinationDatabase = $Database
                }
    
                if (-not $Destination) {
                    $Destination = $server
                }
    
                foreach ($destinationserver in $Destination) {
                    try {
                        $destServer = Connect-SqlInstance -SqlInstance $destinationserver -SqlCredential $DestinationSqlCredential
                    } catch {
                        Stop-Function -Message "Failure" -Category ConnectionError -ErrorRecord $_ -Target $destinationserver
                        return
                    }
    
                    if ($DestinationDatabase -notin $destServer.Databases.Name) {
                        Stop-Function -Message "Database $DestinationDatabase doesn't exist on $destServer"
                        return
                    }
    
                    $desttable = Get-DbaDbTable -SqlInstance $destServer -Table $DestinationTable -Database $DestinationDatabase -Verbose:$false | Select-Object -First 1
                    if (-not $desttable -and $AutoCreateTable) {
                        try {
                            $tablescript = $sqltable | Export-DbaScript -Passthru | Out-String
                            #replacing table name
                            if ($newTableParts.Table) {
                                $rX = "(CREATE TABLE \[$([regex]::Escape($sqltable.Schema))\]\.\[)$([regex]::Escape($sqltable.Name))(\]\()"
                                $tablescript = $tablescript -replace $rX, "`$1$($newTableParts.Table)`$2"
                            }
                            #replacing table schema
                            if ($newTableParts.Schema) {
                                $rX = "(CREATE TABLE \[)$([regex]::Escape($sqltable.Schema))(\]\.\[$([regex]::Escape($newTableParts.Schema))\]\()"
                                $tablescript = $tablescript -replace $rX, "`$1$($newTableParts.Schema)`$2"
                            }
    
                            if ($PSCmdlet.ShouldProcess($destServer, "Creating new table: $DestinationTable")) {
                                Write-Message -Message "New table script: $tablescript" -Level VeryVerbose
                                Invoke-DbaQuery -SqlInstance $destServer -Database $DestinationDatabase -Query "$tablescript" -EnableException # add some string assurance there
                                #table list was updated, let's grab a fresh one
                                $destServer.Databases[$DestinationDatabase].Tables.Refresh()
                                $desttable = Get-DbaDbTable -SqlInstance $destServer -Table $DestinationTable -Database $DestinationDatabase -Verbose:$false
                                Write-Message -Message "New table created: $desttable" -Level Verbose
                            }
                        } catch {
                            Stop-Function -Message "Unable to determine destination table: $DestinationTable" -ErrorRecord $_
                            return
                        }
                    }
                    if (-not $desttable) {
                        Stop-Function -Message "Table $DestinationTable cannot be found in $DestinationDatabase. Use -AutoCreateTable to automatically create the table on the destination." -Continue
                    }
    
                    $connstring = $destServer.ConnectionContext.ConnectionString
    
                    if ($server.DatabaseEngineType -eq "SqlAzureDatabase") {
                        $fqtnfrom = "$sqltable"
                    } else {
                        $fqtnfrom = "$($server.Databases[$Database]).$sqltable"
                    }
    
                    if ($destServer.DatabaseEngineType -eq "SqlAzureDatabase") {
                        $fqtndest = "$desttable"
                    } else {
                        $fqtndest = "$($destServer.Databases[$DestinationDatabase]).$desttable"
                    }
    
                    if ($fqtndest -eq $fqtnfrom -and $server.Name -eq $destServer.Name) {
                        Stop-Function -Message "Cannot copy $fqtnfrom on $($server.Name) into $fqtndest on ($destServer.Name). Source and Destination must be different " -Target $Table
                        return
                    }
    
    
                    if (Test-Bound -ParameterName Query -Not) {
                        $Query = "SELECT * FROM $fqtnfrom"
                    }
                    try {
                        if ($Truncate -eq $true) {
                            if ($Pscmdlet.ShouldProcess($destServer, "Truncating table $fqtndest")) {
                                $null = $destServer.Databases[$DestinationDatabase].ExecuteNonQuery("TRUNCATE TABLE $fqtndest")
                            }
                        }
                        if ($Pscmdlet.ShouldProcess($server, "Copy data from $fqtnfrom")) {
                            $cmd = $server.ConnectionContext.SqlConnectionObject.CreateCommand()
                            $cmd.CommandText = $Query
                            if ($server.ConnectionContext.IsOpen -eq $false) {
                                $server.ConnectionContext.SqlConnectionObject.Open()
                            }
                            $bulkCopy = New-Object Data.SqlClient.SqlBulkCopy("$connstring;Database=$DestinationDatabase", $bulkCopyOptions)
                            $bulkCopy.DestinationTableName = $fqtndest
                            $bulkCopy.EnableStreaming = $true
                            $bulkCopy.BatchSize = $BatchSize
                            $bulkCopy.NotifyAfter = $NotifyAfter
                            $bulkCopy.BulkCopyTimeOut = $BulkCopyTimeOut
    
                            $elapsed = [System.Diagnostics.Stopwatch]::StartNew()
                            # Add RowCount output
                            $bulkCopy.Add_SqlRowsCopied( {
                                    $RowsPerSec = [math]::Round($args[1].RowsCopied / $elapsed.ElapsedMilliseconds * 1000.0, 1)
                                    Write-Progress -id 1 -activity "Inserting rows" -Status ([System.String]::Format("{0} rows ({1} rows/sec)", $args[1].RowsCopied, $RowsPerSec))
                                })
                        }
    
                        if ($Pscmdlet.ShouldProcess($destServer, "Writing rows to $fqtndest")) {
                            $reader = $cmd.ExecuteReader()
                            $bulkCopy.WriteToServer($reader)
                            $RowsTotal = [System.Data.SqlClient.SqlBulkCopyExtension]::RowsCopiedCount($bulkCopy)
                            $TotalTime = [math]::Round($elapsed.Elapsed.TotalSeconds, 1)
                            Write-Message -Level Verbose -Message "$RowsTotal rows inserted in $TotalTime sec"
                            if ($rowCount -is [int]) {
                                Write-Progress -id 1 -activity "Inserting rows" -status "Complete" -Completed
                            }
    
                            $bulkCopy.Close()
                            $bulkCopy.Dispose()
                            $reader.Close()
    
                            [pscustomobject]@{
                                SourceInstance      = $server.Name
                                SourceDatabase      = $Database
                                SourceTable         = $sqltable.Name
                                DestinationInstance = $destServer.name
                                DestinationDatabase = $DestinationDatabase
                                DestinationTable    = $desttable.Name
                                RowsCopied          = $rowstotal
                                Elapsed             = [prettytimespan]$elapsed.Elapsed
                            }
                        }
                    } catch {
                        Stop-Function -Message "Something went wrong" -ErrorRecord $_ -Target $server -continue
                    }
                }
            }
        }
        end {
            Test-DbaDeprecation -DeprecatedOn "1.0.0" -EnableException:$false -Alias Copy-DbaTableData
        }
    }
    function Copy-DbaEndpoint {
        <#
        .SYNOPSIS
            Copy-DbaEndpoint migrates server endpoints from one SQL Server to another.
    
        .DESCRIPTION
            By default, all endpoints are copied.
    
            If the endpoint already exists on the destination, it will be skipped unless -Force is used.
    
        .PARAMETER Source
            Source SQL Server. You must have sysadmin access and server version must be SQL Server version 2000 or higher.
    
        .PARAMETER SourceSqlCredential
            Login to the target instance using alternative credentials. Windows and SQL Authentication supported. Accepts credential objects (Get-Credential)
    
        .PARAMETER Destination
            Destination SQL Server. You must have sysadmin access and the server must be SQL Server 2000 or higher.
    
        .PARAMETER DestinationSqlCredential
            Login to the target instance using alternative credentials. Windows and SQL Authentication supported. Accepts credential objects (Get-Credential)
    
        .PARAMETER Endpoint
            The endpoint(s) to process. This list is auto-populated from the server. If unspecified, all endpoints will be processed.
    
        .PARAMETER ExcludeEndpoint
            The endpoint(s) to exclude. This list is auto-populated from the server.
    
        .PARAMETER WhatIf
            If this switch is enabled, no actions are performed but informational messages will be displayed that explain what would happen if the command were to run.
    
        .PARAMETER Confirm
            If this switch is enabled, you will be prompted for confirmation before executing any operations that change state.
    
        .PARAMETER EnableException
            By default, when something goes wrong we try to catch it, interpret it and give you a friendly warning message.
            This avoids overwhelming you with "sea of red" exceptions, but is inconvenient because it basically disables advanced scripting.
            Using this switch turns this "nice by default" feature off and enables you to catch exceptions with your own try/catch.
    
        .PARAMETER Force
            If this switch is enabled, existing endpoints on Destination with matching names from Source will be dropped.
    
        .NOTES
            Tags: Migration, Endpoint
            Author: Chrissy LeMaire (@cl), netnerds.net
    
            Website: https://dbatools.io
            Copyright: (c) 2018 by dbatools, licensed under MIT
            License: MIT https://opensource.org/licenses/MIT
    
            Requires: sysadmin access on SQL Servers
    
        .LINK
            https://dbatools.io/Copy-DbaEndpoint
    
        .EXAMPLE
            PS C:\> Copy-DbaEndpoint -Source sqlserver2014a -Destination sqlcluster
    
            Copies all server endpoints from sqlserver2014a to sqlcluster, using Windows credentials. If endpoints with the same name exist on sqlcluster, they will be skipped.
    
        .EXAMPLE
            PS C:\> Copy-DbaEndpoint -Source sqlserver2014a -SourceSqlCredential $cred -Destination sqlcluster -Endpoint tg_noDbDrop -Force
    
            Copies only the tg_noDbDrop endpoint from sqlserver2014a to sqlcluster using SQL credentials for sqlserver2014a and Windows credentials for sqlcluster. If an endpoint with the same name exists on sqlcluster, it will be dropped and recreated because -Force was used.
    
        .EXAMPLE
            PS C:\> Copy-DbaEndpoint -Source sqlserver2014a -Destination sqlcluster -WhatIf -Force
    
            Shows what would happen if the command were executed using force.
    
        #>
        [CmdletBinding(DefaultParameterSetName = "Default", SupportsShouldProcess = $true)]
        param (
            [parameter(Mandatory)]
            [DbaInstanceParameter]$Source,
            [PSCredential]
            $SourceSqlCredential,
            [parameter(Mandatory)]
            [DbaInstanceParameter[]]$Destination,
            [PSCredential]
            $DestinationSqlCredential,
            [object[]]$Endpoint,
            [object[]]$ExcludeEndpoint,
            [switch]$Force,
            [Alias('Silent')]
            [switch]$EnableException
        )
    
        begin {
            try {
                $sourceServer = Connect-SqlInstance -SqlInstance $Source -SqlCredential $SourceSqlCredential -MinimumVersion 9
            } catch {
                Stop-Function -Message "Failure" -Category ConnectionError -ErrorRecord $_ -Target $Source
                return
            }
            $serverEndpoints = $sourceServer.Endpoints | Where-Object IsSystemObject -eq $false
        }
        process {
            if (Test-FunctionInterrupt) { return }
            foreach ($destinstance in $Destination) {
                try {
                    $destServer = Connect-SqlInstance -SqlInstance $destinstance -SqlCredential $DestinationSqlCredential -MinimumVersion 9
                } catch {
                    Stop-Function -Message "Failure" -Category ConnectionError -ErrorRecord $_ -Target $destinstance -Continue
                }
                $destEndpoints = $destServer.Endpoints
    
                foreach ($currentEndpoint in $serverEndpoints) {
                    $endpointName = $currentEndpoint.Name
    
                    $copyEndpointStatus = [pscustomobject]@{
                        SourceServer      = $sourceServer.Name
                        DestinationServer = $destServer.Name
                        Name              = $endpointName
                        Type              = "Endpoint"
                        Status            = $null
                        Notes             = $null
                        DateTime          = [DbaDateTime](Get-Date)
                    }
    
                    if ($Endpoint -and $Endpoint -notcontains $endpointName -or $ExcludeEndpoint -contains $endpointName) {
                        continue
                    }
    
                    if ($destEndpoints.Name -contains $endpointName) {
                        if ($force -eq $false) {
                            $copyEndpointStatus.Status = "Skipped"
                            $copyEndpointStatus.Notes = "Already exists"
                            $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
    
            Website: https://dbatools.io
            Copyright: (c) 2018 by dbatools, licensed under MIT
            License: MIT https://opensource.org/licenses/MIT
    
            Requires: sysadmin access on SQL Servers
    
        .LINK
            https://dbatools.io/Copy-DbaExtendedEvent
    
        .EXAMPLE
            PS C:\> Copy-DbaExtendedEvent -Source sqlserver2014a -Destination sqlcluster
    
            Copies all Extended Event sessions from sqlserver2014a to sqlcluster using Windows credentials.
    
        .EXAMPLE
            PS C:\> 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
            PS C:\> Copy-DbaExtendedEvent -Source sqlserver2014a -Destination sqlcluster -WhatIf
    
            Shows what would happen if the command were executed.
    
        .EXAMPLE
            PS C:\> Copy-DbaExtendedEvent -Source sqlserver2014a -Destination sqlcluster -XeSession CheckQueries, MonitorUserDefinedException
    
            Copies only the Extended Events named CheckQueries and MonitorUserDefinedException from sqlserver2014a to sqlcluster.
    
        #>
        [CmdletBinding(DefaultParameterSetName = "Default", SupportsShouldProcess = $true)]
        param (
            [parameter(Mandatory)]
            [DbaInstanceParameter]$Source,
            [parameter(Mandatory)]
            [DbaInstanceParameter[]]$Destination,
            [PSCredential]
            $SourceSqlCredential,
            [PSCredential]
            $DestinationSqlCredential,
            [object[]]$XeSession,
            [object[]]$ExcludeXeSession,
            [switch]$Force,
            [Alias('Silent')]
            [switch]$EnableException
        )
        begin {
            try {
                $sourceServer = Connect-SqlInstance -SqlInstance $Source -SqlCredential $SourceSqlCredential -MinimumVersion 11
            } catch {
                Stop-Function -Message "Failure" -Category ConnectionError -ErrorRecord $_ -Target $Source
                return
            }
            $sourceSqlConn = $sourceServer.ConnectionContext.SqlConnectionObject
            $sourceSqlStoreConnection = New-Object Microsoft.SqlServer.Management.Sdk.Sfc.SqlStoreConnection $sourceSqlConn
            $sourceStore = New-Object  Microsoft.SqlServer.Management.XEvent.XEStore $sourceSqlStoreConnection
            $storeSessions = $sourceStore.Sessions | Where-Object { $_.Name -notin 'AlwaysOn_health', 'system_health' }
            if ($XeSession) {
                $storeSessions = $storeSessions | Where-Object Name -In $XeSession
            }
            if ($ExcludeXeSession) {
                $storeSessions = $storeSessions | Where-Object Name -NotIn $ExcludeXeSession
            }
        }
        process {
            if (Test-FunctionInterrupt) { return }
            foreach ($destinstance in $Destination) {
                try {
                    $destServer = Connect-SqlInstance -SqlInstance $destinstance -SqlCredential $DestinationSqlCredential -MinimumVersion 11
                } catch {
                    Stop-Function -Message "Failure" -Category ConnectionError -ErrorRecord $_ -Target $destinstance -Continue
                }
    
                $destSqlConn = $destServer.ConnectionContext.SqlConnectionObject
                $destSqlStoreConnection = New-Object Microsoft.SqlServer.Management.Sdk.Sfc.SqlStoreConnection $destSqlConn
                $destStore = New-Object  Microsoft.SqlServer.Management.XEvent.XEStore $destSqlStoreConnection
    
                Write-Message -Level Verbose -Message "Migrating sessions."
                foreach ($session in $storeSessions) {
                    $sessionName = $session.Name
    
                    $copyXeSessionStatus = [pscustomobject]@{
                        SourceServer      = $sourceServer.Name
                        DestinationServer = $destServer.Name
                        Name              = $sessionName
                        Type              = "Extended Event"
                        Status            = $null
                        Notes             = $null
                        DateTime          = [DbaDateTime](Get-Date)
                    }
    
                    if ($null -ne $destStore.Sessions[$sessionName]) {
                        if ($force -eq $false) {
                            if ($Pscmdlet.ShouldProcess($destinstance, "Extended Event Session '$sessionName' was skipped because it already exists on $destinstance.")) {
                                $copyXeSessionStatus.Status = "Skipped"
                                $copyXeSessionStatus.Notes = "Already exists"
                                $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/
    
        .PARAMETER Source
            Source SQL Server (2005 and above). You must have sysadmin access to both SQL Server and Windows.
    
        .PARAMETER SourceSqlCredential
            Login to the target instance using alternative credentials. Windows and SQL Authentication supported. Accepts credential objects (Get-Credential)
    
        .PARAMETER Destination
            Destination SQL Server (2005 and above). You must have sysadmin access to both SQL Server and Windows.
    
        .PARAMETER DestinationSqlCredential
            Login to the target instance using alternative credentials. Windows and SQL Authentication supported. Accepts credential objects (Get-Credential)
    
        .PARAMETER LinkedServer
            The linked server(s) to process - this list is auto-populated from the server. If unspecified, all linked servers will be processed.
    
        .PARAMETER ExcludeLinkedServer
            The linked server(s) to exclude - this list is auto-populated from the server
    
        .PARAMETER UpgradeSqlClient
            Upgrade any SqlClient Linked Server to the current Version
    
        .PARAMETER WhatIf
            Shows what would happen if the command were to run. No actions are actually performed.
    
        .PARAMETER Confirm
            Prompts you for confirmation before executing any changing operations within the command.
    
        .PARAMETER Force
            By default, if a Linked Server exists on the source and destination, the Linked Server is not copied over. Specifying -force will drop and recreate the Linked Server on the Destination server.
    
        .PARAMETER EnableException
            By default, when something goes wrong we try to catch it, interpret it and give you a friendly warning message.
            This avoids overwhelming you with "sea of red" exceptions, but is inconvenient because it basically disables advanced scripting.
            Using this switch turns this "nice by default" feature off and enables you to catch exceptions with your own try/catch.
    
        .NOTES
            Tags: WSMan, Migration, LinkedServer
            Author: Chrissy LeMaire (@cl), netnerds.net
    
            Website: https://dbatools.io
            Copyright: (c) 2018 by dbatools, licensed under MIT
            License: MIT https://opensource.org/licenses/MIT
    
            Requires: sysadmin access on SQL Servers
            Limitations: This just copies the SQL portion. It does not copy files (i.e. a local SQLite database, or Microsoft Access DB), nor does it configure ODBC entries.
    
        .LINK
            https://dbatools.io/Copy-DbaLinkedServer
    
        .EXAMPLE
            PS C:\> Copy-DbaLinkedServer -Source sqlserver2014a -Destination sqlcluster
    
            Copies all SQL Server Linked Servers on sqlserver2014a to sqlcluster. If Linked Server exists on destination, it will be skipped.
    
        .EXAMPLE
            PS C:\> Copy-DbaLinkedServer -Source sqlserver2014a -Destination sqlcluster -LinkedServer SQL2K5,SQL2k -Force
    
            Copies over two SQL Server Linked Servers (SQL2K and SQL2K2) from sqlserver to sqlcluster. If the credential already exists on the destination, it will be dropped.
    
        #>
        [CmdletBinding(DefaultParameterSetName = "Default", SupportsShouldProcess = $true)]
        [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUseSingularNouns", "", Justification = "Internal functions are ignored")]
        param (
            [parameter(Mandatory)]
            [DbaInstanceParameter]$Source,
            [PSCredential]$SourceSqlCredential,
            [parameter(Mandatory)]
            [DbaInstanceParameter[]]$Destination,
            [PSCredential]$DestinationSqlCredential,
            [object[]]$LinkedServer,
            [object[]]$ExcludeLinkedServer,
            [switch]$UpgradeSqlClient,
            [switch]$Force,
            [Alias('Silent')]
            [switch]$EnableException
        )
        begin {
            $null = Test-ElevationRequirement -ComputerName $Source.ComputerName
            function Copy-DbaLinkedServers {
                param (
                    [string[]]$LinkedServer,
                    [bool]$force
                )
    
                Write-Message -Level Verbose -Message "Collecting Linked Server logins and passwords on $($sourceServer.Name)."
                $sourcelogins = Get-DecryptedObject -SqlInstance $sourceServer -Type LinkedServer
    
                $serverlist = $sourceServer.LinkedServers
    
                if ($LinkedServer) {
                    $serverlist = $serverlist | Where-Object Name -In $LinkedServer
                }
                if ($ExcludeLinkedServer) {
                    $serverList = $serverlist | Where-Object Name -NotIn $ExcludeLinkedServer
                }
    
                foreach ($currentLinkedServer in $serverlist) {
                    $provider = $currentLinkedServer.ProviderName
                    try {
                        $destServer.LinkedServers.Refresh()
                        $destServer.LinkedServers.LinkedServerLogins.Refresh()
                    } catch {
                        #here to avoid an empty catch
                        $null = 1
                    }
    
                    $linkedServerName = $currentLinkedServer.Name
                    $linkedServerDataSource = $currentLinkedServer.DataSource
    
                    $copyLinkedServer = [pscustomobject]@{
                        SourceServer      = $sourceServer.Name
                        DestinationServer = $destServer.Name
                        Name              = $linkedServerName
                        DataSource        = $linkedServerDataSource
                        Type              = "Linked Server"
                        Status            = $null
                        Notes             = $provider
                        DateTime          = [DbaDateTime](Get-Date)
                    }
    
                    # This does a check to warn of missing OleDbProviderSettings but should only be checked on SQL on Windows
                    if ($destServer.Settings.OleDbProviderSettings.Name.Length -ne 0) {
                        if (!$destServer.Settings.OleDbProviderSettings.Name -contains $provider -and !$provider.StartsWith("SQLN")) {
                            $copyLinkedServer.Status = "Skipped"
                            $copyLinkedServer.Notes = "Missing provider"
                            $copyLinkedServer | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
    
                            Write-Message -Level Verbose -Message "$($destServer.Name) does not support the $provider provider. Skipping $linkedServerName."
                            continue
                        }
                    }
    
                    if ($null -ne $destServer.LinkedServers[$linkedServerName]) {
                        if (!$force) {
                            if ($Pscmdlet.ShouldProcess($destinstance, "$linkedServerName exists $($destServer.Name). Skipping.")) {
                                $copyLinkedServer.Status = "Skipped"
                                $copyLinkedServer | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
    
                                Write-Message -Level Verbose -Message "$linkedServerName exists $($destServer.Name). Skipping."
                            }
                            continue
                        } else {
                            if ($Pscmdlet.ShouldProcess($destinstance, "Dropping $linkedServerName")) {
                                if ($currentLinkedServer.Name -eq 'repl_distributor') {
                                    Write-Message -Level Verbose -Message "repl_distributor cannot be dropped. Not going to try."
                                    continue
                                }
    
                                $destServer.LinkedServers[$linkedServerName].Drop($true)
                                $destServer.LinkedServers.refresh()
                            }
                        }
                    }
    
                    Write-Message -Level Verbose -Message "Attempting to migrate: $linkedServerName."
                    If ($Pscmdlet.ShouldProcess($destinstance, "Migrating $linkedServerName")) {
                        try {
                            $sql = $currentLinkedServer.Script() | Out-String
                            Write-Message -Level Debug -Message $sql
    
                            if ($UpgradeSqlClient -and $sql -match "sqlncli") {
                                $destProviders = $destServer.Settings.OleDbProviderSettings | Where-Object { $_.Name -like 'SQLNCLI*' }
                                $newProvider = $destProviders | Sort-Object Name -Descending | Select-Object -First 1 -ExpandProperty Name
    
                                Write-Message -Level Verbose -Message "Changing sqlncli to $newProvider"
                                $sql = $sql -replace ("sqlncli[0-9]+", $newProvider)
                            }
    
                            $destServer.Query($sql)
    
                            if ($copyLinkedServer.Name -ne $copyLinkedServer.DataSource) {
                                $sql2 = "EXEC sp_setnetname '$($copyLinkedServer.Name)', '$($copyLinkedServer.DataSource)'; "
                                $destServer.Query($sql2)
                            }
    
                            $destServer.LinkedServers.Refresh()
                            Write-Message -Level Verbose -Message "$linkedServerName successfully copied."
    
                            $copyLinkedServer.Status = "Successful"
                            $copyLinkedServer | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
                        } catch {
                            $copyLinkedServer.Status = "Failed"
                            $copyLinkedServer | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
    
                            Stop-Function -Message "Issue adding linked server $destServer." -Target $linkedServerName -InnerErrorRecord $_
                            $skiplogins = $true
                        }
                    }
    
                    if ($skiplogins -ne $true) {
                        $destlogins = $destServer.LinkedServers[$linkedServerName].LinkedServerLogins
                        $lslogins = $sourcelogins | Where-Object { $_.Name -eq $linkedServerName }
    
                        foreach ($login in $lslogins) {
                            if ($Pscmdlet.ShouldProcess($destinstance, "Migrating $($login.Login)")) {
                                $currentlogin = $destlogins | Where-Object { $_.RemoteUser -eq $login.Identity }
    
                                $copyLinkedServer.Type = $login.Identity
    
                                if ($currentlogin.RemoteUser.length -ne 0) {
                                    try {
                                        $currentlogin.SetRemotePassword($login.Password)
                                        $currentlogin.Alter()
    
                                        $copyLinkedServer.Status = "Successful"
                                        $copyLinkedServer | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
                                    } catch {
                                        $copyLinkedServer.Status = "Failed"
                                        $copyLinkedServer | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
    
                                        Stop-Function -Message "Failed to copy login." -Target $login -InnerErrorRecord $_
                                    }
                                }
                            }
                        }
                    }
                }
            }
    
            if ($null -ne $SourceSqlCredential.Username) {
                Write-Message -Level Verbose -Message "You are using a SQL Credential. Note that this script requires Windows Administrator access on the source server. Attempting with $($SourceSqlCredential.Username)."
            }
            try {
                $sourceServer = Connect-SqlInstance -SqlInstance $Source -SqlCredential $SourceSqlCredential
                return
            } catch {
                Stop-Function -Message "Failure" -Category ConnectionError -ErrorRecord $_ -Target $Source
                return
            }
            if (!(Test-SqlSa -SqlInstance $sourceServer -SqlCredential $SourceSqlCredential)) {
                Stop-Function -Message "Not a sysadmin on $source. Quitting." -Target $sourceServer
                return
            }
            Write-Message -Level Verbose -Message "Getting NetBios name for $source."
            $sourceNetBios = Resolve-NetBiosName $sourceserver
    
            Write-Message -Level Verbose -Message "Checking if Remote Registry is enabled on $source."
            try {
                Invoke-Command2 -Raw -Credential $Credential -ComputerName $sourceNetBios -ScriptBlock { Get-ItemProperty -Path "HKLM:\SOFTWARE\" } -ErrorAction Stop
            } catch {
                Stop-Function -Message "Can't connect to registry on $source." -Target $sourceNetBios -ErrorRecord $_
                return
            }
        }
        process {
            if (Test-FunctionInterrupt) { return }
    
            foreach ($destinstance in $Destination) {
                try {
                    $destServer = Connect-SqlInstance -SqlInstance $destinstance -SqlCredential $DestinationSqlCredential
                } catch {
                    Stop-Function -Message "Failure" -Category ConnectionError -ErrorRecord $_ -Target $destinstance -Continue
                }
                if (!(Test-SqlSa -SqlInstance $destServer -SqlCredential $DestinationSqlCredential)) {
                    Stop-Function -Message "Not a sysadmin on $destinstance" -Target $destServer -Continue
                }
    
                # Magic happens here
                Copy-DbaLinkedServers $LinkedServer -Force:$force
            }
        }
        end {
            Test-DbaDeprecation -DeprecatedOn "1.0.0" -EnableException:$false -Alias Copy-SqlLinkedServer
        }
    }
    function Copy-DbaLogin {
        <#
        .SYNOPSIS
            Migrates logins from source to destination SQL Servers. Supports SQL Server versions 2000 and newer.
    
        .DESCRIPTION
            SQL Server 2000: Migrates logins with SIDs, passwords, server roles and database roles.
    
            SQL Server 2005 & newer: Migrates logins with SIDs, passwords, defaultdb, server roles & securables, database permissions & securables, login attributes (enforce password policy, expiration, etc.)
    
            The login hash algorithm changed in SQL Server 2012, and is not backwards compatible with previous SQL Server versions. This means that while SQL Server 2000 logins can be migrated to SQL Server 2012, logins created in SQL Server 2012 can only be migrated to SQL Server 2012 and above.
    
        .PARAMETER Source
            Source SQL Server. You must have sysadmin access and server version must be SQL Server version 2000 or higher.
    
        .PARAMETER SourceSqlCredential
            Login to the target instance using alternative credentials. Windows and SQL Authentication supported. Accepts credential objects (Get-Credential)
    
        .PARAMETER Destination
            Destination SQL Server. You must have sysadmin access and the server must be SQL Server 2000 or higher.
    
        .PARAMETER DestinationSqlCredential
            Login to the target instance using alternative credentials. Windows and SQL Authentication supported. Accepts credential objects (Get-Credential)
    
        .PARAMETER Login
            The login(s) to process. Options for this list are auto-populated from the server. If unspecified, all logins will be processed.
    
        .PARAMETER ExcludeLogin
            The login(s) to exclude. Options for this list are auto-populated from the server.
    
        .PARAMETER ExcludeSystemLogins
            If this switch is enabled, NT SERVICE accounts will be skipped.
    
        .PARAMETER ExcludePermissionSync
            Skips permission syncs
    
        .PARAMETER SyncOnly
            If this switch is enabled, only SQL Server login permissions, roles, etc. will be synced. Logins and users will not be added or dropped.  If a matching Login does not exist on the destination, the Login will be skipped.
            Credential removal is not currently supported for this parameter.
    
        .PARAMETER SyncSaName
            If this switch is enabled, the name of the sa account will be synced between Source and Destination
    
        .PARAMETER OutFile
            Calls Export-DbaLogin and exports all logins to a T-SQL formatted file. This does not perform a copy, so no destination is required.
    
        .PARAMETER InputObject
            Takes the parameters required from a Login object that has been piped into the command
    
        .PARAMETER LoginRenameHashtable
            Pass a hash table into this parameter to be passed into Rename-DbaLogin to update the Login and mappings after the Login is completed.
    
        .PARAMETER KillActiveConnection
            If this switch and -Force are enabled, all active connections and sessions on Destination will be killed.
    
            A login cannot be dropped when it has active connections on the instance.
    
        .PARAMETER WhatIf
            If this switch is enabled, no actions are performed but informational messages will be displayed that explain what would happen if the command were to run.
    
        .PARAMETER Confirm
            If this switch is enabled, you will be prompted for confirmation before executing any operations that change state.
    
        .PARAMETER Force
            If this switch is enabled, the Login(s) will be dropped and recreated on Destination. Logins that own Agent jobs cannot be dropped at this time.
    
        .PARAMETER EnableException
            By default, when something goes wrong we try to catch it, interpret it and give you a friendly warning message.
            This avoids overwhelming you with "sea of red" exceptions, but is inconvenient because it basically disables advanced scripting.
            Using this switch turns this "nice by default" feature off and enables you to catch exceptions with your own try/catch.
    
        .NOTES
            Tags: Migration, Login
            Author: Chrissy LeMaire (@cl), netnerds.net
    
            Website: https://dbatools.io
            Copyright: (c) 2018 by dbatools, licensed under MIT
            License: MIT https://opensource.org/licenses/MIT
    
            Requires: sysadmin access on SQL Servers
    
        .LINK
            https://dbatools.io/Copy-DbaLogin
    
        .EXAMPLE
            PS C:\> Copy-DbaLogin -Source sqlserver2014a -Destination sqlcluster -Force
    
            Copies all logins from Source Destination. If a SQL Login on Source exists on the Destination, the Login on Destination will be dropped and recreated.
    
            If active connections are found for a login, the copy of that Login will fail as it cannot be dropped.
    
        .EXAMPLE
            PS C:\> Copy-DbaLogin -Source sqlserver2014a -Destination sqlcluster -Force -KillActiveConnection
    
            Copies all logins from Source Destination. If a SQL Login on Source exists on the Destination, the Login on Destination will be dropped and recreated.
    
            If any active connections are found they will be killed.
    
        .EXAMPLE
            PS C:\> Copy-DbaLogin -Source sqlserver2014a -Destination sqlcluster -ExcludeLogin realcajun -SourceSqlCredential $scred -DestinationSqlCredential $dcred
    
            Copies all Logins from Source to Destination except for realcajun using SQL Authentication to connect to both instances.
    
            If a Login already exists on the destination, it will not be migrated.
    
        .EXAMPLE
            PS C:\> Copy-DbaLogin -Source sqlserver2014a -Destination sqlcluster -Login realcajun, netnerds -force
    
            Copies ONLY Logins netnerds and realcajun. If Login realcajun or netnerds exists on Destination, the existing Login(s) will be dropped and recreated.
    
        .EXAMPLE
            PS C:\> Copy-DbaLogin -Source sqlserver2014a -Destination sqlcluster -SyncOnly
    
            Syncs only SQL Server login permissions, roles, etc. Does not add or drop logins or users.
    
            If a matching Login does not exist on Destination, the Login will be skipped.
    
        .EXAMPLE
            PS C:\> Copy-DbaLogin -LoginRenameHashtable @{ "PreviousUser" = "newlogin" } -Source $Sql01 -Destination Localhost -SourceSqlCredential $sqlcred
    
            Copies PreviousUser and then renames it to newlogin.
    
        .EXAMPLE
            PS C:\> Get-DbaLogin -SqlInstance sql2016 | Out-GridView -Passthru | Copy-DbaLogin -Destination sql2017
    
            Displays all available logins on sql2016 in a grid view, then copies all selected logins to sql2017.
    
        #>
        [CmdletBinding(DefaultParameterSetName = "Default", SupportsShouldProcess)]
        param (
            [parameter(ParameterSetName = "SqlInstance", Mandatory)]
            [DbaInstanceParameter]$Source,
            [PSCredential]$SourceSqlCredential,
            [parameter(Mandatory)]
            [DbaInstanceParameter[]]$Destination,
            [PSCredential]$DestinationSqlCredential,
            [object[]]$Login,
            [object[]]$ExcludeLogin,
            [switch]$ExcludeSystemLogins,
            [switch]$SyncOnly,
            [parameter(ParameterSetName = "Live")]
            [parameter(ParameterSetName = "SqlInstance")]
            [switch]$SyncSaName,
            [parameter(ParameterSetName = "File", Mandatory)]
            [string]$OutFile,
            [parameter(ParameterSetName = "InputObject", ValueFromPipeline)]
            [object]$InputObject,
            [hashtable]$LoginRenameHashtable,
            [switch]$KillActiveConnection,
            [switch]$Force,
            [switch]$ExcludePermissionSync,
            [switch]$EnableException
        )
    
        begin {
            function Copy-Login {
                foreach ($sourceLogin in $sourceServer.Logins) {
                    $userName = $sourceLogin.name
    
                    $copyLoginStatus = [pscustomobject]@{
                        SourceServer      = $sourceServer.Name
                        DestinationServer = $destServer.Name
                        Type              = "Login - $($sourceLogin.LoginType)"
                        Name              = $userName
                        DestinationLogin  = $userName
                        SourceLogin       = $userName
                        Status            = $null
                        Notes             = $null
                        DateTime          = [DbaDateTime](Get-Date)
                    }
    
                    if ($Login -and $Login -notcontains $userName -or $ExcludeLogin -contains $userName) { continue }
    
                    if ($sourceLogin.id -eq 1) { continue }
    
                    if ($userName.StartsWith("##") -or $userName -eq 'sa') {
                        Write-Message -Level Verbose -Message "Skipping $userName."
                        continue
                    }
    
                    $serverName = Resolve-NetBiosName $sourceServer
    
                    $currentLogin = $sourceServer.ConnectionContext.truelogin
    
                    if ($currentLogin -eq $userName -and $force) {
                        if ($Pscmdlet.ShouldProcess("console", "Stating $userName is skipped because it is performing the migration.")) {
                            Write-Message -Level Verbose -Message "Cannot drop login performing the migration. Skipping."
                            $copyLoginStatus.Status = "Skipped"
                            $copyLoginStatus.Notes = "Current login"
                            $copyLoginStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
                        }
                        continue
                    }
    
                    if (($destServer.LoginMode -ne [Microsoft.SqlServer.Management.Smo.ServerLoginMode]::Mixed) -and ($sourceLogin.LoginType -eq [Microsoft.SqlServer.Management.Smo.LoginType]::SqlLogin)) {
                        Write-Message -Level Verbose -Message "$Destination does not have Mixed Mode enabled. [$userName] is an SQL Login. Enable mixed mode authentication after the migration completes to use this type of login."
                    }
    
                    $userBase = ($userName.Split("\")[0]).ToLower()
    
                    if ($serverName -eq $userBase -or $userName.StartsWith("NT ")) {
                        if ($sourceServer.ComputerName -ne $destServer.ComputerName) {
                            if ($Pscmdlet.ShouldProcess("console", "Stating $userName was skipped because it is a local machine name.")) {
                                Write-Message -Level Verbose -Message "$userName was skipped because it is a local machine name."
                                $copyLoginStatus.Status = "Skipped"
                                $copyLoginStatus.Notes = "Local machine name"
                                $copyLoginStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
                            }
                            continue
                        } else {
                            if ($ExcludeSystemLogins) {
                                if ($Pscmdlet.ShouldProcess("console", "$userName was skipped because ExcludeSystemLogins was specified.")) {
                                    Write-Message -Level Verbose -Message "$userName was skipped because ExcludeSystemLogins was specified."
    
                                    $copyLoginStatus.Status = "Skipped"
                                    $copyLoginStatus.Notes = "System login"
                                    $copyLoginStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
                                }
                                continue
                            }
    
                            if ($Pscmdlet.ShouldProcess("console", "Stating local login $userName since the source and destination server reside on the same machine.")) {
                                Write-Message -Level Verbose -Message "Copying local login $userName since the source and destination server reside on the same machine."
                            }
                        }
                    }
    
                    if ($null -ne $destServer.Logins.Item($userName) -and !$force) {
                        if ($Pscmdlet.ShouldProcess("console", "Stating $userName is skipped because it exists at destination.")) {
                            Write-Message -Level Verbose -Message "$userName already exists in destination. Use -Force to drop and recreate."
                            $copyLoginStatus.Status = "Skipped"
                            $copyLoginStatus.Notes = "Already exists"
                            $copyLoginStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
                        }
                        continue
                    }
    
                    if ($null -ne $destServer.Logins.Item($userName) -and $force) {
                        if ($userName -eq $destServer.ServiceAccount) {
                            if ($Pscmdlet.ShouldProcess("console", "$userName is the destination service account. Skipping drop.")) {
                                Write-Message -Level Verbose -Message "$userName is the destination service account. Skipping drop."
    
                                $copyLoginStatus.Status = "Skipped"
                                $copyLoginStatus.Notes = "Destination service account"
                                $copyLoginStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
                            }
                            continue
                        }
    
                        if ($Pscmdlet.ShouldProcess($destinstance, "Dropping $userName")) {
    
                            # Kill connections, delete user
                            Write-Message -Level Verbose -Message "Attempting to migrate $userName"
                            Write-Message -Level Verbose -Message "Force was specified. Attempting to drop $userName on $destinstance."
    
                            try {
                                $ownedDbs = $destServer.Databases | Where-Object Owner -eq $userName
    
                                foreach ($ownedDb in $ownedDbs) {
                                    Write-Message -Level Verbose -Message "Changing database owner for $($ownedDb.name) from $userName to sa."
                                    $ownedDb.SetOwner('sa')
                                    $ownedDb.Alter()
                                }
    
                                $ownedJobs = $destServer.JobServer.Jobs | Where-Object OwnerLoginName -eq $userName
    
                                foreach ($ownedJob in $ownedJobs) {
                                    Write-Message -Level Verbose -Message "Changing job owner for $($ownedJob.name) from $userName to sa."
                                    $ownedJob.Set_OwnerLoginName('sa')
                                    $ownedJob.Alter()
                                }
    
                                $activeConnections = $destServer.EnumProcesses() | Where-Object Login -eq $userName
    
                                if ($activeConnections -and $KillActiveConnection) {
                                    if (!$destServer.Logins.Item($userName).IsDisabled) {
                                        $disabled = $true
                                        $destServer.Logins.Item($userName).Disable()
                                    }
    
                                    $activeConnections | ForEach-Object { $destServer.KillProcess($_.Spid) }
                                    Write-Message -Level Verbose -Message "-KillActiveConnection was provided. There are $($activeConnections.Count) active connections killed."
                                    # just in case the kill didn't work, it'll leave behind a disabled account
                                    if ($disabled) { $destServer.Logins.Item($userName).Enable() }
                                } elseif ($activeConnections) {
                                    Write-Message -Level Verbose -Message "There are $($activeConnections.Count) active connections found for the login $userName. Utilize -KillActiveConnection with -Force to kill the connections."
                                }
                                $destServer.Logins.Item($userName).Drop()
    
                                Write-Message -Level Verbose -Message "Successfully dropped $userName on $destinstance."
                            } catch {
                                $copyLoginStatus.Status = "Failed"
                                $copyLoginStatus.Notes = (Get-ErrorMessage -Record $_).Message
                                $copyLoginStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
    
                                Stop-Function -Message "Could not drop $userName." -Category InvalidOperation -ErrorRecord $_ -Target $destServer -Continue 3>$null
                            }
                        }
                    }
    
                    if ($Pscmdlet.ShouldProcess($destinstance, "Adding SQL login $userName")) {
    
                        Write-Message -Level Verbose -Message "Attempting to add $userName to $destinstance."
                        $destLogin = New-Object Microsoft.SqlServer.Management.Smo.Login($destServer, $userName)
    
                        Write-Message -Level Verbose -Message "Setting $userName SID to source username SID."
                        $destLogin.Set_Sid($sourceLogin.Get_Sid())
    
                        $defaultDb = $sourceLogin.DefaultDatabase
    
                        Write-Message -Level Verbose -Message "Setting login language to $($sourceLogin.Language)."
                        $destLogin.Language = $sourceLogin.Language
    
                        if ($null -eq $destServer.databases[$defaultDb]) {
                            # we end up here when the default database on source doesn't exist on dest
                            # if source login is a sysadmin, then set the default database to master
                            # if not, set it to tempdb (see #303)
                            $OrigdefaultDb = $defaultDb
                            try { $sourcesysadmins = $sourceServer.roles['sysadmin'].EnumMemberNames() }
                            catch { $sourcesysadmins = $sourceServer.roles['sysadmin'].EnumServerRoleMembers() }
                            if ($sourcesysadmins -contains $userName) {
                                $defaultDb = "master"
                            } else {
                                $defaultDb = "tempdb"
                            }
                            Write-Message -Level Verbose -Message "$OrigdefaultDb does not exist on destination. Setting defaultdb to $defaultDb."
                        }
    
                        Write-Message -Level Verbose -Message "Set $userName defaultdb to $defaultDb."
                        $destLogin.DefaultDatabase = $defaultDb
    
                        $checkexpiration = "ON"; $checkpolicy = "ON"
    
                        if ($sourceLogin.PasswordPolicyEnforced -eq $false) { $checkpolicy = "OFF" }
    
                        if (!$sourceLogin.PasswordExpirationEnabled) { $checkexpiration = "OFF" }
    
                        $destLogin.PasswordPolicyEnforced = $sourceLogin.PasswordPolicyEnforced
                        $destLogin.PasswordExpirationEnabled = $sourceLogin.PasswordExpirationEnabled
    
                        # Attempt to add SQL Login User
                        if ($sourceLogin.LoginType -eq "SqlLogin") {
                            $destLogin.LoginType = "SqlLogin"
                            $sourceLoginname = $sourceLogin.name
    
                            switch ($sourceServer.versionMajor) {
                                0 { $sql = "SELECT CONVERT(VARBINARY(256),password) as hashedpass FROM master.dbo.syslogins WHERE loginname='$sourceLoginname'" }
                                8 { $sql = "SELECT CONVERT(VARBINARY(256),password) as hashedpass FROM dbo.syslogins WHERE name='$sourceLoginname'" }
                                9 { $sql = "SELECT CONVERT(VARBINARY(256),password_hash) as hashedpass FROM sys.sql_logins where name='$sourceLoginname'" }
                                default {
                                    $sql = "SELECT CAST(CONVERT(VARCHAR(256), CAST(LOGINPROPERTY(name,'PasswordHash')
                            AS VARBINARY(256)), 1) AS NVARCHAR(max)) AS hashedpass FROM sys.server_principals
                            WHERE principal_id = $($sourceLogin.id)"
                                }
                            }
    
                            try {
                                $hashedPass = $sourceServer.ConnectionContext.ExecuteScalar($sql)
                            } catch {
                                $hashedPassDt = $sourceServer.Databases['master'].ExecuteWithResults($sql)
                                $hashedPass = $hashedPassDt.Tables[0].Rows[0].Item(0)
                            }
    
                            if ($hashedPass.GetType().Name -ne "String") {
                                $passString = "0x"; $hashedPass | ForEach-Object { $passString += ("{0:X}" -f $_).PadLeft(2, "0") }
                                $hashedPass = $passString
                            }
    
                            try {
                                $destLogin.Create($hashedPass, [Microsoft.SqlServer.Management.Smo.LoginCreateOptions]::IsHashed)
                                $destLogin.Refresh()
                                Write-Message -Level Verbose -Message "Successfully added $userName to $destinstance."
    
                                $copyLoginStatus.Status = "Successful"
                                $copyLoginStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
                            } catch {
                                try {
                                    $sid = "0x"; $sourceLogin.sid | ForEach-Object { $sid += ("{0:X}" -f $_).PadLeft(2, "0") }
                                    $sql = "CREATE LOGIN [$userName] WITH PASSWORD = $hashedPass HASHED, SID = $sid,
                                                    DEFAULT_DATABASE = [$defaultDb], CHECK_POLICY = $checkpolicy,
                                                    CHECK_EXPIRATION = $checkexpiration, DEFAULT_LANGUAGE = [$($sourceLogin.Language)]"
    
                                    $null = $destServer.Query($sql)
    
                                    $destLogin = $destServer.logins[$userName]
                                    Write-Message -Level Verbose -Message "Successfully added $userName to $destinstance."
    
                                    $copyLoginStatus.Status = "Successful"
                                    $copyLoginStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
    
                                } catch {
                                    $copyLoginStatus.Status = "Failed"
                                    $copyLoginStatus.Notes = (Get-ErrorMessage -Record $_).Message
                                    $copyLoginStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
    
                                    Stop-Function -Message "Failed to add $userName to $destinstance." -Category InvalidOperation -ErrorRecord $_ -Target $destServer -Continue 3>$null
                                }
                            }
                        }
                        # Attempt to add Windows User
                        elseif ($sourceLogin.LoginType -eq "WindowsUser" -or $sourceLogin.LoginType -eq "WindowsGroup") {
                            Write-Message -Level Verbose -Message "Adding as login type $($sourceLogin.LoginType)"
                            $destLogin.LoginType = $sourceLogin.LoginType
    
                            Write-Message -Level Verbose -Message "Setting language as $($sourceLogin.Language)"
                            $destLogin.Language = $sourceLogin.Language
    
                            try {
                                $destLogin.Create()
                                $destLogin.Refresh()
                                Write-Message -Level Verbose -Message "Successfully added $userName to $destinstance."
    
                                $copyLoginStatus.Status = "Successful"
                                $copyLoginStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
    
                            } catch {
                                $copyLoginStatus.Status = "Failed"
                                $copyLoginStatus.Notes = (Get-ErrorMessage -Record $_).Message
                                $copyLoginStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
    
                                Stop-Function -Message "Failed to add $userName to $destinstance" -Category InvalidOperation -ErrorRecord $_ -Target $destServer -Continue 3>$null
                            }
                        }
                        # This script does not currently support certificate mapped or asymmetric key users.
                        else {
                            Write-Message -Level Verbose -Message "$($sourceLogin.LoginType) logins not supported. $($sourceLogin.name) skipped."
    
                            $copyLoginStatus.Status = "Skipped"
                            $copyLoginStatus.Notes = "$($sourceLogin.LoginType) not supported"
                            $copyLoginStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
    
                            continue
                        }
    
                        if ($sourceLogin.IsDisabled) {
                            try {
                                $destLogin.Disable()
                            } catch {
                                $copyLoginStatus.Status = "Successful - but could not disable on destination"
                                $copyLoginStatus.Notes = (Get-ErrorMessage -Record $_).Message
                                $copyLoginStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
    
                                Stop-Function -Message "$userName disabled on source, could not be disabled on $destinstance." -Category InvalidOperation -ErrorRecord $_ -Target $destServer  3>$null
                            }
                        }
                        if ($sourceLogin.DenyWindowsLogin) {
                            try {
                                $destLogin.DenyWindowsLogin = $true
                            } catch {
                                $copyLoginStatus.Status = "Successful - but could not deny login on destination"
                                $copyLoginStatus.Notes = (Get-ErrorMessage -Record $_).Message
                                $copyLoginStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
    
                                Stop-Function -Message "$userName denied login on source, could not be denied login on $destinstance." -Category InvalidOperation -ErrorRecord $_ -Target $destServer 3>$null
                            }
                        }
                    }
    
                    if (-not $ExcludePermissionSync) {
                        if ($Pscmdlet.ShouldProcess($destinstance, "Updating SQL login $userName permissions")) {
                            Update-SqlPermission -sourceserver $sourceServer -sourcelogin $sourceLogin -destserver $destServer -destlogin $destLogin
                        }
                    }
    
                    if ($LoginRenameHashtable.Keys -contains $userName) {
                        $NewLogin = $LoginRenameHashtable[$userName]
    
                        if ($Pscmdlet.ShouldProcess($destinstance, "Renaming SQL Login $userName to $NewLogin")) {
                            try {
                                Rename-DbaLogin -SqlInstance $destServer -Login $userName -NewLogin $NewLogin
    
                                $copyLoginStatus.DestinationLogin = $NewLogin
                                $copyLoginStatus.Status = "Successful"
                                $copyLoginStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
    
                            } catch {
                                $copyLoginStatus.DestinationLogin = $NewLogin
                                $copyLoginStatus.Status = "Failed to rename"
                                $copyLoginStatus.Notes = (Get-ErrorMessage -Record $_).Message
                                $copyLoginStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
    
                                Stop-Function -Message "Issue renaming $userName to $NewLogin" -Category InvalidOperation -ErrorRecord $_ -Target $destServer 3>$null
                            }
                        }
                    }
                }
            }
        }
        process {
            if (Test-FunctionInterrupt) { return }
            if ($InputObject) {
                $Source = $InputObject[0].Parent.Name
                $Sourceserver = $InputObject[0].Parent
                $Login = $InputObject.Name
            } else {
                try {
                    $sourceServer = Connect-SqlInstance -SqlInstance $Source -SqlCredential $SourceSqlCredential
                } catch {
                    Stop-Function -Message "Failure" -Category ConnectionError -ErrorRecord $_ -Target $Source
                    return
                }
            }
            $sourceVersionMajor = $sourceServer.VersionMajor
    
            if ($OutFile) {
                Export-DbaLogin -SqlInstance $sourceServer -FilePath $OutFile -Login $Login -ExcludeLogin $ExcludeLogin
                continue
            }
    
            foreach ($destinstance in $Destination) {
                try {
                    $destServer = Connect-SqlInstance -SqlInstance $destinstance -SqlCredential $DestinationSqlCredential
                } catch {
                    Stop-Function -Message "Failure" -Category ConnectionError -ErrorRecord $_ -Target $destinstance -Continue
                }
    
                $destVersionMajor = $destServer.VersionMajor
                if ($sourceVersionMajor -gt 10 -and $destVersionMajor -lt 11) {
                    Stop-Function -Message "Login migration from version $sourceVersionMajor to $destVersionMajor is not supported." -Category InvalidOperation -ErrorRecord $_ -Target $sourceServer
                }
    
                if ($sourceVersionMajor -lt 8 -or $destVersionMajor -lt 8) {
                    Stop-Function -Message "SQL Server 7 and below are not supported." -Category InvalidOperation -ErrorRecord $_ -Target $sourceServer
                }
    
                if ($SyncOnly) {
                    if ($Pscmdlet.ShouldProcess($destinstance, "Syncing $Login permissions")) {
                        Sync-DbaLoginPermission -Source $sourceServer -Destination $destServer -Login $Login -ExcludeLogin $ExcludeLogin
                        continue
                    }
                }
    
                Write-Message -Level Verbose -Message "Attempting Login Migration."
                Copy-Login -sourceserver $sourceServer -destserver $destServer -Login $Login -Exclude $ExcludeLogin
    
                if ($SyncSaName) {
                    $sa = $sourceServer.Logins | Where-Object id -eq 1
                    $destSa = $destServer.Logins | Where-Object id -eq 1
                    $saName = $sa.Name
                    if ($saName -ne $destSa.name) {
                        Write-Message -Level Verbose -Message "Changing sa username to match source ($saName)."
                        if ($Pscmdlet.ShouldProcess($destinstance, "Changing sa username to match source ($saName)")) {
                            $destSa.Rename($saName)
                            $destSa.Alter()
                        }
                    }
                }
            }
        }
        end {
            Test-DbaDeprecation -DeprecatedOn "1.0.0" -EnableException:$false -Alias Copy-SqlLogin
        }
    }
    function Copy-DbaPolicyManagement {
        <#
        .SYNOPSIS
            Migrates SQL Policy Based Management Objects, including both policies and conditions.
    
        .DESCRIPTION
            By default, all policies and conditions are copied. If an object already exist on the destination, it will be skipped unless -Force is used.
    
            The -Policy and -Condition parameters are auto-populated for command-line completion and can be used to copy only specific objects.
    
        .PARAMETER Source
            Source SQL Server.You must have sysadmin access and server version must be SQL Server version 2008 or higher.
    
        .PARAMETER SourceSqlCredential
            Login to the target instance using alternative credentials. Windows and SQL Authentication supported. Accepts credential objects (Get-Credential)
    
        .PARAMETER Destination
            Destination Sql Server. You must have sysadmin access and server version must be SQL Server version 2008 or higher.
    
        .PARAMETER DestinationSqlCredential
            Login to the target instance using alternative credentials. Windows and SQL Authentication supported. Accepts credential objects (Get-Credential)
    
        .PARAMETER Policy
            The policy(ies) to process - this list is auto-populated from the server. If unspecified, all policies will be processed.
    
        .PARAMETER ExcludePolicy
            The policy(ies) to exclude - this list is auto-populated from the server
    
        .PARAMETER Condition
            The condition(s) to process - this list is auto-populated from the server. If unspecified, all conditions will be processed.
    
        .PARAMETER ExcludeCondition
            The condition(s) to exclude - this list is auto-populated from the server
    
        .PARAMETER Force
            If policies exists on destination server, it will be dropped and recreated.
    
        .PARAMETER WhatIf
            Shows what would happen if the command were to run. No actions are actually performed.
    
        .PARAMETER Confirm
            Prompts you for confirmation before executing any changing operations within the command.
    
        .PARAMETER EnableException
            By default, when something goes wrong we try to catch it, interpret it and give you a friendly warning message.
            This avoids overwhelming you with "sea of red" exceptions, but is inconvenient because it basically disables advanced scripting.
            Using this switch turns this "nice by default" feature off and enables you to catch exceptions with your own try/catch.
    
        .NOTES
            Tags: Migration
            Author: Chrissy LeMaire (@cl), netnerds.net
    
            Website: https://dbatools.io
            Copyright: (c) 2018 by dbatools, licensed under MIT
            License: MIT https://opensource.org/licenses/MIT
    
            Requires: sysadmin access on SQL Servers
    
        .LINK
            https://dbatools.io/Copy-DbaPolicyManagement
    
        .EXAMPLE
            PS C:\> Copy-DbaPolicyManagement -Source sqlserver2014a -Destination sqlcluster
    
            Copies all policies and conditions from sqlserver2014a to sqlcluster, using Windows credentials.
    
        .EXAMPLE
            PS C:\> Copy-DbaPolicyManagement -Source sqlserver2014a -Destination sqlcluster -SourceSqlCredential $cred
    
            Copies all policies and conditions from sqlserver2014a to sqlcluster, using SQL credentials for sqlserver2014a and Windows credentials for sqlcluster.
    
        .EXAMPLE
            PS C:\> Copy-DbaPolicyManagement -Source sqlserver2014a -Destination sqlcluster -WhatIf
    
            Shows what would happen if the command were executed.
    
        .EXAMPLE
            PS C:\> Copy-DbaPolicyManagement -Source sqlserver2014a -Destination sqlcluster -Policy 'xp_cmdshell must be disabled'
    
            Copies only one policy, 'xp_cmdshell must be disabled' from sqlserver2014a to sqlcluster. No conditions are migrated.
    
        #>
        [CmdletBinding(DefaultParameterSetName = "Default", SupportsShouldProcess = $true)]
        param (
            [parameter(Mandatory)]
            [DbaInstanceParameter]$Source,
            [PSCredential]
            $SourceSqlCredential,
            [parameter(Mandatory)]
            [DbaInstanceParameter[]]$Destination,
            [PSCredential]
            $DestinationSqlCredential,
            [object[]]$Policy,
            [object[]]$ExcludePolicy,
            [object[]]$Condition,
            [object[]]$ExcludeCondition,
            [switch]$Force,
            [Alias('Silent')]
            [switch]$EnableException
        )
    
        begin {
            try {
                $sourceServer = Connect-SqlInstance -SqlInstance $Source -SqlCredential $SourceSqlCredential -MinimumVersion 10
            } catch {
                Stop-Function -Message "Failure" -Category ConnectionError -ErrorRecord $_ -Target $Source
                return
            }
            $sourceSqlConn = $sourceServer.ConnectionContext.SqlConnectionObject
            $sourceSqlStoreConnection = New-Object Microsoft.SqlServer.Management.Sdk.Sfc.SqlStoreConnection $sourceSqlConn
            $sourceStore = New-Object  Microsoft.SqlServer.Management.DMF.PolicyStore $sourceSqlStoreConnection
            $storePolicies = $sourceStore.Policies | Where-Object { $_.IsSystemObject -eq $false }
            $storeConditions = $sourceStore.Conditions | Where-Object { $_.IsSystemObject -eq $false }
        }
        process {
            if (Test-FunctionInterrupt) { return }
            foreach ($destinstance in $Destination) {
                try {
                    $destServer = Connect-SqlInstance -SqlInstance $destinstance -SqlCredential $DestinationSqlCredential -MinimumVersion 10
                } catch {
                    Stop-Function -Message "Failure" -Category ConnectionError -ErrorRecord $_ -Target $destinstance -Continue
                }
                $destSqlConn = $destServer.ConnectionContext.SqlConnectionObject
                $destSqlStoreConnection = New-Object Microsoft.SqlServer.Management.Sdk.Sfc.SqlStoreConnection $destSqlConn
                $destStore = New-Object  Microsoft.SqlServer.Management.DMF.PolicyStore $destSqlStoreConnection
    
                if ($Policy) {
                    $storePolicies = $storePolicies | Where-Object Name -In $Policy
                }
                if ($ExcludePolicy) {
                    $storePolicies = $storePolicies | Where-Object Name -NotIn $ExcludePolicy
                }
                if ($Condition) {
                    $storeConditions = $storeConditions | Where-Object Name -In $Condition
                }
                if ($ExcludeCondition) {
                    $storeConditions = $storeConditions | Where-Object Name -NotIn $ExcludeCondition
                }
    
                if ($Policy -and $Condition) {
                    $storeConditions = $null
                    $storePolicies = $null
                }
    
                <#
                            Conditions
            #>
    
                Write-Message -Level Verbose -Message "Migrating conditions"
                foreach ($condition in $storeConditions) {
                    $conditionName = $condition.Name
    
                    $copyConditionStatus = [pscustomobject]@{
                        SourceServer      = $sourceServer.Name
                        DestinationServer = $destServer.Name
                        Name              = $conditionName
                        Type              = "Policy Condition"
                        Status            = $null
                        Notes             = $null
                        DateTime          = [DbaDateTime](Get-Date)
                    }
    
                    if ($null -ne $destStore.Conditions[$conditionName]) {
                        if ($force -eq $false) {
                            Write-Message -Level Verbose -Message "condition '$conditionName' was skipped because it already exists on $destinstance. Use -Force to drop and recreate"
    
                            $copyConditionStatus.Status = "Skipped"
                            $copyConditionStatus.Notes = "Already exists"
                            $copyConditionStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
                            continue
                        } else {
                            if ($Pscmdlet.ShouldProcess($destinstance, "Attempting to drop $conditionName")) {
                                Write-Message -Level Verbose -Message "Condition '$conditionName' exists on $destinstance. Force specified. Dropping $conditionName."
    
                                try {
                                    $dependentPolicies = $destStore.Conditions[$conditionName].EnumDependentPolicies()
                                    foreach ($dependent in $dependentPolicies) {
                                        $dependent.Drop()
                                        $destStore.Conditions.Refresh()
                                    }
                                    $destStore.Conditions[$conditionName].Drop()
                                } catch {
                                    $copyConditionStatus.Status = "Failed"
                                    $copyConditionStatus.Notes = (Get-ErrorMessage -Record $_).Message
                                    $copyConditionStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
                                    Stop-Function -Message "Issue dropping condition on $destinstance" -Target $conditionName -ErrorRecord $_ -Continue
                                }
                            }
                        }
                    }
    
                    if ($Pscmdlet.ShouldProcess($destinstance, "Migrating condition $conditionName")) {
                        try {
                            $sql = $condition.ScriptCreate().GetScript() | Out-String
                            Write-Message -Level Debug -Message $sql
                            Write-Message -Level Verbose -Message "Copying condition $conditionName"
                            $null = $destServer.Query($sql)
                            $destStore.Conditions.Refresh()
    
                            $copyConditionStatus.Status = "Successful"
                            $copyConditionStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
                        } catch {
                            $copyConditionStatus.Status = "Failed"
                            $copyConditionStatus.Notes = (Get-ErrorMessage -Record $_).Message
                            $copyConditionStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
    
                            Stop-Function -Message "Issue creating condition on $destinstance" -Target $conditionName -ErrorRecord $_
                        }
                    }
                }
    
                <#
                            Policies
            #>
    
                Write-Message -Level Verbose -Message "Migrating policies"
                foreach ($policy in $storePolicies) {
                    $policyName = $policy.Name
    
                    $copyPolicyStatus = [pscustomobject]@{
                        SourceServer      = $sourceServer.Name
                        DestinationServer = $destServer.Name
                        Name              = $policyName
                        Type              = "Policy"
                        Status            = $null
                        Notes             = $null
                        DateTime          = [DbaDateTime](Get-Date)
                    }
    
                    if ($null -ne $destStore.Policies[$policyName]) {
                        if ($force -eq $false) {
                            Write-Message -Level Verbose -Message "Policy '$policyName' was skipped because it already exists on $destinstance. Use -Force to drop and recreate"
    
                            $copyPolicyStatus.Status = "Skipped"
                            $copyPolicyStatus.Notes = "Already exists"
                            $copyPolicyStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
                            continue
                        } else {
                            if ($Pscmdlet.ShouldProcess($destinstance, "Attempting to drop $policyName")) {
                                Write-Message -Level Verbose -Message "Policy '$policyName' exists on $destinstance. Force specified. Dropping $policyName."
    
                                try {
                                    $destStore.Policies[$policyName].Drop()
                                    $destStore.Policies.refresh()
                                } catch {
                                    $copyPolicyStatus.Status = "Failed"
                                    $copyPolicyStatus.Notes = (Get-ErrorMessage -Record $_).Message
                                    $copyPolicyStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
    
                                    Stop-Function -Message "Issue dropping policy on $destinstance" -Target $policyName -ErrorRecord $_ -Continue
                                }
                            }
                        }
                    }
    
                    if ($Pscmdlet.ShouldProcess($destinstance, "Migrating policy $policyName")) {
                        try {
                            $destStore.Conditions.Refresh()
                            $destStore.Policies.Refresh()
                            $sql = $policy.ScriptCreateWithDependencies().GetScript() | Out-String
                            Write-Message -Level Debug -Message $sql
                            Write-Message -Level Verbose -Message "Copying policy $policyName"
                            $null = $destServer.Query($sql)
    
                            $copyPolicyStatus.Status = "Successful"
                            $copyPolicyStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
                        } catch {
                            $copyPolicyStatus.Status = "Failed"
                            $copyPolicyStatus.Notes = (Get-ErrorMessage -Record $_).Message
                            $copyPolicyStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
    
                            # This is usually because of a duplicate dependent from above. Just skip for now.
                            Stop-Function -Message "Issue creating policy on $destinstance" -Target $policyName -ErrorRecord $_ -Continue
                        }
                    }
                }
            }
        }
        end {
            Test-DbaDeprecation -DeprecatedOn "1.0.0" -EnableException:$false -Alias Copy-SqlPolicyManagement
            Test-DbaDeprecation -DeprecatedOn "1.0.0" -EnableException:$false -Alias Copy-DbaSqlPolicyManagement
        }
    }
    function Copy-DbaQueryStoreConfig {
        <#
        .SYNOPSIS
            Copies the configuration of a Query Store enabled database and sets the copied configuration on other databases.
    
        .DESCRIPTION
            Copies the configuration of a Query Store enabled database and sets the copied configuration on other databases.
    
        .PARAMETER Source
            Source SQL Server. You must have sysadmin access and server version must be SQL Server version 2016 or higher.
    
        .PARAMETER SourceSqlCredential
            Login to the target instance using alternative credentials. Windows and SQL Authentication supported. Accepts credential objects (Get-Credential)
    
        .PARAMETER SourceDatabase
            Specifies the database to copy the Query Store configuration from.
    
        .PARAMETER Destination
            Destination SQL Server. You must have sysadmin access and the server must be SQL Server 2016 or higher.
    
        .PARAMETER DestinationSqlCredential
            Login to the target instance using alternative credentials. Windows and SQL Authentication supported. Accepts credential objects (Get-Credential)
    
        .PARAMETER DestinationDatabase
            Specifies a list of databases that will receive a copy of the Query Store configuration of the SourceDatabase.
    
        .PARAMETER Exclude
            Specifies a list of databases which will NOT receive a copy of the Query Store configuration.
    
        .PARAMETER AllDatabases
            If this switch is enabled, the Query Store configuration will be copied to all databases on the destination instance.
    
        .PARAMETER WhatIf
            If this switch is enabled, no actions are performed but informational messages will be displayed that explain what would happen if the command were to run.
    
        .PARAMETER Confirm
            If this switch is enabled, you will be prompted for confirmation before executing any operations that change state.
    
        .PARAMETER EnableException
            By default, when something goes wrong we try to catch it, interpret it and give you a friendly warning message.
            This avoids overwhelming you with "sea of red" exceptions, but is inconvenient because it basically disables advanced scripting.
            Using this switch turns this "nice by default" feature off and enables you to catch exceptions with your own try/catch.
    
        .NOTES
            Tags: QueryStore
            Author: Enrico van de Laar (@evdlaar)
    
            Website: https://dbatools.io
            Copyright: (c) 2018 by dbatools, licensed under MIT
            License: MIT https://opensource.org/licenses/MIT
    
        .LINK
            https://dbatools.io/Copy-QueryStoreConfig
    
        .EXAMPLE
            PS C:\> Copy-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
            PS C:\> Copy-DbaQueryStoreConfig -Source ServerA\SQL -SourceDatabase AdventureWorks -Destination ServerB\SQL -DestinationDatabase WorldWideTraders
    
            Copy the Query Store configuration of the AdventureWorks database in the ServerA\SQL instance and apply it to the WorldWideTraders database in the ServerB\SQL Instance.
    
        #>
        [CmdletBinding(SupportsShouldProcess = $true)]
        param (
            [parameter(Mandatory, ValueFromPipeline)]
            [DbaInstanceParameter]$Source,
            [PSCredential]$SourceSqlCredential,
            [parameter(Mandatory, ValueFromPipeline)]
            [object]$SourceDatabase,
            [parameter(Mandatory, ValueFromPipeline)]
            [DbaInstanceParameter[]]$Destination,
            [PSCredential]$DestinationSqlCredential,
            [object[]]$DestinationDatabase,
            [object[]]$Exclude,
            [switch]$AllDatabases,
            [Alias('Silent')]
            [switch]$EnableException
        )
    
        begin {
            try {
                $sourceServer = Connect-SqlInstance -SqlInstance $Source -SqlCredential $SourceSqlCredential
            } catch {
                Stop-Function -Message "Can't connect to $Source." -ErrorRecord $_ -Target $Source
                return
            }
        }
    
        process {
            if (Test-FunctionInterrupt) { return }
            foreach ($destinstance in $Destination) {
                # Grab the Query Store configuration from the SourceDatabase through the Get-DbaQueryStoreConfig function
                $SourceQSConfig = Get-DbaDbQueryStoreOption -SqlInstance $sourceServer -Database $SourceDatabase
    
                if (!$DestinationDatabase -and !$Exclude -and !$AllDatabases) {
                    Stop-Function -Message "You must specify databases to execute against using either -DestinationDatabase, -Exclude or -AllDatabases." -Continue
                }
    
                foreach ($destinationServer in $destinstance) {
    
                    try {
                        $destServer = Connect-SqlInstance -SqlInstance $destinstance -SqlCredential $DestinationSqlCredential
                    } catch {
                        Stop-Function -Message "Failure" -Category ConnectionError -ErrorRecord $_ -Target $destinstance -Continue
                    }
    
                    # We have to exclude all the system databases since they cannot have the Query Store feature enabled
                    $dbs = Get-DbaDatabase -SqlInstance $destServer -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
                        if ($PSCmdlet.ShouldProcess("$db", "Copying QueryStoreConfig")) {
                            try {
                                $null = Set-DbaDbQueryStoreOption -SqlInstance $destinationServer -SqlCredential $DestinationSqlCredential `
                                    -Database $db.name `
                                    -State $SourceQSConfig.ActualState `
                                    -FlushInterval $SourceQSConfig.FlushInterval `
                                    -CollectionInterval $SourceQSConfig.CollectionInterval `
                                    -MaxSize $SourceQSConfig.MaxSize `
                                    -CaptureMode $SourceQSConfig.CaptureMode `
                                    -CleanupMode $SourceQSConfig.CleanupMode `
                                    -StaleQueryThreshold $SourceQSConfig.StaleQueryThreshold
                                $copyQueryStoreStatus.Status = "Successful"
                            } catch {
                                $copyQueryStoreStatus.Status = "Failed"
                                Stop-Function -Message "Issue setting Query Store on $db." -Target $db -ErrorRecord $_ -Continue
                            }
                            $copyQueryStoreStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
                        }
                    }
                }
            }
        }
    }
    function Copy-DbaResourceGovernor {
        <#
        .SYNOPSIS
            Migrates Resource Pools
    
        .DESCRIPTION
            By default, all non-system resource pools are migrated. If the pool already exists on the destination, it will be skipped unless -Force is used.
    
            The -ResourcePool parameter is auto-populated for command-line completion and can be used to copy only specific objects.
    
        .PARAMETER Source
            Source SQL Server. You must have sysadmin access and server version must be SQL Server version 2008 or higher.
    
        .PARAMETER SourceSqlCredential
            Login to the target instance using alternative credentials. Windows and SQL Authentication supported. Accepts credential objects (Get-Credential)
    
        .PARAMETER Destination
            Destination SQL Server. You must have sysadmin access and the server must be SQL Server 2008 or higher.
    
        .PARAMETER DestinationSqlCredential
            Login to the target instance using alternative credentials. Windows and SQL Authentication supported. Accepts credential objects (Get-Credential)
    
        .PARAMETER ResourcePool
            Specifies the resource pool(s) to process. Options for this list are auto-populated from the server. If unspecified, all resource pools will be processed.
    
        .PARAMETER ExcludeResourcePool
            Specifies the resource pool(s) to exclude. Options for this list are auto-populated from the server
    
        .PARAMETER WhatIf
            If this switch is enabled, no actions are performed but informational messages will be displayed that explain what would happen if the command were to run.
    
        .PARAMETER Confirm
            If this switch is enabled, you will be prompted for confirmation before executing any operations that change state.
    
        .PARAMETER Force
            If this switch is enabled, the policies will be dropped and recreated on Destination.
    
        .PARAMETER EnableException
            By default, when something goes wrong we try to catch it, interpret it and give you a friendly warning message.
            This avoids overwhelming you with "sea of red" exceptions, but is inconvenient because it basically disables advanced scripting.
            Using this switch turns this "nice by default" feature off and enables you to catch exceptions with your own try/catch.
    
        .NOTES
            Tags: Migration, ResourceGovernor
            Author: Chrissy LeMaire (@cl), netnerds.net
    
            Website: https://dbatools.io
            Copyright: (c) 2018 by dbatools, licensed under MIT
            License: MIT https://opensource.org/licenses/MIT
    
            Requires: sysadmin access on SQL Servers
    
        .LINK
            https://dbatools.io/Copy-DbaResourceGovernor
    
        .EXAMPLE
            PS C:\> Copy-DbaResourceGovernor -Source sqlserver2014a -Destination sqlcluster
    
            Copies all all non-system resource pools from sqlserver2014a to sqlcluster using Windows credentials to connect to the SQL Server instances..
    
        .EXAMPLE
            PS C:\> Copy-DbaResourceGovernor -Source sqlserver2014a -Destination sqlcluster -SourceSqlCredential $cred
    
            Copies all all non-system resource pools from sqlserver2014a to sqlcluster using SQL credentials to connect to sqlserver2014a and Windows credentials to connect to sqlcluster.
    
        .EXAMPLE
            PS C:\> Copy-DbaResourceGovernor -Source sqlserver2014a -Destination sqlcluster -WhatIf
    
            Shows what would happen if the command were executed.
    
        #>
        [CmdletBinding(DefaultParameterSetName = "Default", SupportsShouldProcess = $true)]
        param (
            [parameter(Mandatory)]
            [DbaInstanceParameter]$Source,
            [PSCredential]$SourceSqlCredential,
            [parameter(Mandatory)]
            [DbaInstanceParameter[]]$Destination,
            [PSCredential]$DestinationSqlCredential,
            [object[]]$ResourcePool,
            [object[]]$ExcludeResourcePool,
            [switch]$Force,
            [Alias('Silent')]
            [switch]$EnableException
        )
        process {
            try {
                $sourceServer = Connect-SqlInstance -SqlInstance $Source -SqlCredential $SourceSqlCredential -MinimumVersion 10
            } catch {
                Stop-Function -Message "Failure" -Category ConnectionError -ErrorRecord $_ -Target $Source
                return
            }
            $sourceClassifierFunction = Get-DbaRgClassifierFunction -SqlInstance $sourceServer
    
            foreach ($destinstance in $Destination) {
                try {
                    $destServer = Connect-SqlInstance -SqlInstance $destinstance -SqlCredential $DestinationSqlCredential -MinimumVersion 10
                } catch {
                    Stop-Function -Message "Failure" -Category ConnectionError -ErrorRecord $_ -Target $destinstance -Continue
                }
                $destClassifierFunction = Get-DbaRgClassifierFunction -SqlInstance $destServer
    
                $copyResourceGovSetting = [pscustomobject]@{
                    SourceServer      = $sourceServer.Name
                    DestinationServer = $destServer.Name
                    Type              = "Resource Governor Settings"
                    Name              = "All Settings"
                    Status            = $null
                    Notes             = $null
                    DateTime          = [DbaDateTime](Get-Date)
                }
    
                $copyResourceGovClassifierFunc = [pscustomobject]@{
                    SourceServer      = $sourceServer.Name
                    DestinationServer = $destServer.Name
                    Type              = "Resource Governor Settings"
                    Name              = "Classifier Function"
                    Status            = $null
                    Notes             = $null
                    DateTime          = [DbaDateTime](Get-Date)
                }
                
                if ($Pscmdlet.ShouldProcess($destinstance, "Updating Resource Governor settings")) {
                    if ($destServer.Edition -notmatch 'Enterprise' -and $destServer.Edition -notmatch 'Datacenter' -and $destServer.Edition -notmatch 'Developer') {
                        Write-Message -Level Verbose -Message "The resource governor is not available in this edition of SQL Server. You can manipulate resource governor metadata but you will not be able to apply resource governor configuration. Only Enterprise edition of SQL Server supports resource governor."
                    } else {
                        try {
                            Write-Message -Level Verbose -Message "Managing classifier function."
                            if (!$sourceClassifierFunction) {
                                $copyResourceGovClassifierFunc.Status = "Skipped"
                                $copyResourceGovClassifierFunc.Notes = $null
                                $copyResourceGovClassifierFunc | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
                            } else {
                                $fullyQualifiedFunctionName = $sourceClassifierFunction.Schema + "." + $sourceClassifierFunction.Name
    
                                if (!$destClassifierFunction) {
                                    $destServer = Connect-SqlInstance -SqlInstance $destinstance -SqlCredential $DestinationSqlCredential
                                    $destFunction = $destServer.Databases["master"].UserDefinedFunctions[$sourceClassifierFunction.Name]
                                    if ($destFunction) {
                                        Write-Message -Level Verbose -Message "Dropping the function with the source classifier function name."
                                        $destFunction.Drop()
                                    }
    
                                    Write-Message -Level Verbose -Message "Creating function."
                                    $destServer.Query($sourceClassifierFunction.Script())
    
                                    $sql = "ALTER RESOURCE GOVERNOR WITH (CLASSIFIER_FUNCTION = $fullyQualifiedFunctionName);"
                                    Write-Message -Level Debug -Message $sql
                                    Write-Message -Level Verbose -Message "Mapping Resource Governor classifier function."
                                    $destServer.Query($sql)
    
                                    $copyResourceGovClassifierFunc.Status = "Successful"
                                    $copyResourceGovClassifierFunc.Notes = "The new classifier function has been created"
                                    $copyResourceGovClassifierFunc | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
                                    
                                    $sql = "ALTER RESOURCE GOVERNOR RECONFIGURE;"
                                    Write-Message -Level Debug -Message $sql
                                    Write-Message -Level Verbose -Message "Reconfiguring Resource Governor."
                                    $destServer.Query($sql)
                                } else {
                                    if ($Force -eq $false) {
                                        $copyResourceGovClassifierFunc.Status = "Skipped"
                                        $copyResourceGovClassifierFunc.Notes = "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)
                                        
                                        $sql = "ALTER RESOURCE GOVERNOR RECONFIGURE;"
                                        Write-Message -Level Debug -Message $sql
                                        Write-Message -Level Verbose -Message "Reconfiguring Resource Governor."
                                        $destServer.Query($sql)
                                        
                                        $copyResourceGovClassifierFunc.Status = "Successful"
                                        $copyResourceGovClassifierFunc.Notes = "The old classifier function has been overwritten."
                                        $copyResourceGovClassifierFunc | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
                                    }
                                }
                            }
                        } catch {
                            $copyResourceGovSetting.Status = "Failed"
                            $copyResourceGovSetting.Notes = (Get-ErrorMessage -Record $_)
                            $copyResourceGovSetting | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
                            
                            $sql = "ALTER RESOURCE GOVERNOR RECONFIGURE;"
                            Write-Message -Level Debug -Message $sql
                            Write-Message -Level Verbose -Message "Reconfiguring Resource Governor."
                            $destServer.Query($sql)
                            
                            Stop-Function -Message "Not able to update settings." -Target $destServer -ErrorRecord $_
                        }
                    }
                }
    
                # Pools
                if ($ResourcePool) {
                    $pools = $sourceServer.ResourceGovernor.ResourcePools | Where-Object Name -In $ResourcePool
                } elseif ($ExcludeResourcePool) {
                    $pool = $sourceServer.ResourceGovernor.ResourcePools | Where-Object Name -NotIn $ExcludeResourcePool
                } else {
                    $pools = $sourceServer.ResourceGovernor.ResourcePools | Where-Object { $_.Name -notin "internal", "default" }
                }
    
                Write-Message -Level Verbose -Message "Migrating pools."
                foreach ($pool in $pools) {
                    $poolName = $pool.Name
    
                    $copyResourceGovPool = [pscustomobject]@{
                        SourceServer      = $sourceServer.Name
                        DestinationServer = $destServer.Name
                        Type              = "Resource Governor Pool"
                        Name              = $poolName
                        Status            = $null
                        Notes             = $null
                        DateTime          = [DbaDateTime](Get-Date)
                    }
    
                    if ($null -ne $destServer.ResourceGovernor.ResourcePools[$poolName]) {
                        if ($force -eq $false) {
                            Write-Message -Level Verbose -Message "Pool '$poolName' was skipped because it already exists on $destinstance. Use -Force to drop and recreate."
    
                            $copyResourceGovPool.Status = "Skipped"
                            $copyResourceGovPool.Notes = "Already exists"
                            $copyResourceGovPool | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
                            continue
                        } else {
                            if ($Pscmdlet.ShouldProcess($destinstance, "Attempting to drop $poolName")) {
                                Write-Message -Level Verbose -Message "Pool '$poolName' exists on $destinstance."
                                Write-Message -Level Verbose -Message "Force specified. Dropping $poolName."
    
                                try {
                                    $destServer = Connect-SqlInstance -SqlInstance $destinstance -SqlCredential $DestinationSqlCredential
                                    $destPool = $destServer.ResourceGovernor.ResourcePools[$poolName]
                                    $workloadGroups = $destPool.WorkloadGroups
                                    foreach ($workloadGroup in $workloadGroups) {
                                        $workloadGroup.Drop()
                                    }
                                    $destPool.Drop()
                                    $destServer.ResourceGovernor.Alter()
                                } catch {
                                    $copyResourceGovPool.Status = "Failed to drop from Destination"
                                    $copyResourceGovPool.Notes = (Get-ErrorMessage -Record $_)
                                    $copyResourceGovPool | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
    
                                    Stop-Function -Message "Unable to drop: $_ Moving on." -Target $destPool -ErrorRecord $_ -Continue
                                    
                                    $sql = "ALTER RESOURCE GOVERNOR RECONFIGURE;"
                                    Write-Message -Level Debug -Message $sql
                                    Write-Message -Level Verbose -Message "Reconfiguring Resource Governor."
                                    $destServer.Query($sql)
                                }
                            }
                        }
                    }
    
                    if ($Pscmdlet.ShouldProcess($destinstance, "Migrating pool $poolName")) {
                        try {
                            $sql = $pool.Script() | Out-String
                            Write-Message -Level Debug -Message $sql
                            Write-Message -Level Verbose -Message "Copying pool $poolName."
                            $destServer.Query($sql)
    
                            $copyResourceGovPool.Status = "Successful"
                            $copyResourceGovPool | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
    
                            $workloadGroups = $pool.WorkloadGroups
                            foreach ($workloadGroup in $workloadGroups) {
                                $workgroupName = $workloadGroup.Name
    
                                $copyResourceGovWorkGroup = [pscustomobject]@{
                                    SourceServer      = $sourceServer.Name
                                    DestinationServer = $destServer.Name
                                    Type              = "Resource Governor Pool Workgroup"
                                    Name              = $workgroupName
                                    Status            = $null
                                    Notes             = $null
                                    DateTime          = [DbaDateTime](Get-Date)
                                }
    
                                $sql = $workloadGroup.Script() | Out-String
                                Write-Message -Level Debug -Message $sql
                                Write-Message -Level Verbose -Message "Copying $workgroupName."
                                $destServer.Query($sql)
    
                                $copyResourceGovWorkGroup.Status = "Successful"
                                $copyResourceGovWorkGroup | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
                                
                                $sql = "ALTER RESOURCE GOVERNOR RECONFIGURE;"
                                Write-Message -Level Debug -Message $sql
                                Write-Message -Level Verbose -Message "Reconfiguring Resource Governor."
                                $destServer.Query($sql)
                            }
                        } catch {
                            if ($copyResourceGovWorkGroup) {
                                $copyResourceGovWorkGroup.Status = "Failed"
                                $copyResourceGovWorkGroup.Notes = (Get-ErrorMessage -Record $_)
                                $copyResourceGovWorkGroup | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
                            }
                            Stop-Function -Message "Unable to migrate pool." -Target $pool -ErrorRecord $_
                        }
                    }
                }
    
                if ($Pscmdlet.ShouldProcess($destinstance, "Reconfiguring")) {
                    if ($destServer.Edition -notmatch 'Enterprise' -and $destServer.Edition -notmatch 'Datacenter' -and $destServer.Edition -notmatch 'Developer') {
                        Write-Message -Level Verbose -Message "The resource governor is not available in this edition of SQL Server. You can manipulate resource governor metadata but you will not be able to apply resource governor configuration. Only Enterprise edition of SQL Server supports resource governor."
                    } else {
    
                        Write-Message -Level Verbose -Message "Reconfiguring Resource Governor."
                        try {
                            if (!$sourceServer.ResourceGovernor.Enabled) {
                                $sql = "ALTER RESOURCE GOVERNOR DISABLE"
                                $destServer.Query($sql)
                                
                                $sql = "ALTER RESOURCE GOVERNOR RECONFIGURE;"
                                Write-Message -Level Debug -Message $sql
                                Write-Message -Level Verbose -Message "Reconfiguring Resource Governor."
                                $destServer.Query($sql)
                            } else {
                                $sql = "ALTER RESOURCE GOVERNOR RECONFIGURE"
                                $destServer.Query($sql)
                            }
                        } catch {
                            $altermsg = $_.Exception
                        }
    
    
                        $copyResourceGovReconfig = [pscustomobject]@{
                            SourceServer      = $sourceServer.Name
                            DestinationServer = $destServer.Name
                            Type              = "Reconfigure Resource Governor"
                            Name              = "Reconfigure Resource Governor"
                            Status            = "Successful"
                            Notes             = $altermsg
                            DateTime          = [DbaDateTime](Get-Date)
                        }
                        $copyResourceGovReconfig | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
                    }
                }
            }
        }
        end {
            Test-DbaDeprecation -DeprecatedOn "1.0.0" -EnableException:$false -Alias Copy-SqlResourceGovernor
        }
    }
    function Copy-DbaServerAudit {
        <#
        .SYNOPSIS
            Copy-DbaServerAudit migrates server audits from one SQL Server to another.
    
        .DESCRIPTION
            By default, all audits are copied. The -Audit parameter is auto-populated for command-line completion and can be used to copy only specific audits.
    
            If the audit already exists on the destination, it will be skipped unless -Force is used.
    
        .PARAMETER Source
            Source SQL Server. You must have sysadmin access and server version must be SQL Server version 2000 or higher.
    
        .PARAMETER SourceSqlCredential
            Login to the target instance using alternative credentials. Windows and SQL Authentication supported. Accepts credential objects (Get-Credential)
    
        .PARAMETER Destination
            Destination SQL Server. You must have sysadmin access and the server must be SQL Server 2000 or higher.
    
        .PARAMETER DestinationSqlCredential
            Login to the target instance using alternative credentials. Windows and SQL Authentication supported. Accepts credential objects (Get-Credential)
    
        .PARAMETER Audit
            The audit(s) to process. Options for this list are auto-populated from the server. If unspecified, all audits will be processed.
    
        .PARAMETER ExcludeAudit
            The audit(s) to exclude. Options for this list are auto-populated from the server.
    
        .PARAMETER WhatIf
            If this switch is enabled, no actions are performed but informational messages will be displayed that explain what would happen if the command were to run.
    
        .PARAMETER Confirm
            If this switch is enabled, you will be prompted for confirmation before executing any operations that change state.
    
        .PARAMETER Force
            If this switch is enabled, the audits will be dropped and recreated on Destination.
    
        .PARAMETER EnableException
            By default, when something goes wrong we try to catch it, interpret it and give you a friendly warning message.
            This avoids overwhelming you with "sea of red" exceptions, but is inconvenient because it basically disables advanced scripting.
            Using this switch turns this "nice by default" feature off and enables you to catch exceptions with your own try/catch.
    
        .NOTES
            Tags: Migration
            Author: Chrissy LeMaire (@cl), netnerds.net
    
            Website: https://dbatools.io
            Copyright: (c) 2018 by dbatools, licensed under MIT
            License: MIT https://opensource.org/licenses/MIT
    
            Requires: sysadmin access on SQL Servers
    
        .LINK
            https://dbatools.io/Copy-DbaServerAudit
    
        .EXAMPLE
            PS C:\> Copy-DbaServerAudit -Source sqlserver2014a -Destination sqlcluster
    
            Copies all server audits from sqlserver2014a to sqlcluster, using Windows credentials. If audits with the same name exist on sqlcluster, they will be skipped.
    
        .EXAMPLE
            PS C:\> Copy-DbaServerAudit -Source sqlserver2014a -Destination sqlcluster -Audit tg_noDbDrop -SourceSqlCredential $cred -Force
    
            Copies a single audit, the tg_noDbDrop audit from sqlserver2014a to sqlcluster, using SQL credentials for sqlserver2014a and Windows credentials for sqlcluster. If an audit with the same name exists on sqlcluster, it will be dropped and recreated because -Force was used.
    
        .EXAMPLE
            PS C:\> Copy-DbaServerAudit -Source sqlserver2014a -Destination sqlcluster -WhatIf -Force
    
            Shows what would happen if the command were executed using force.
    
        #>
        [CmdletBinding(DefaultParameterSetName = "Default", SupportsShouldProcess = $true)]
        param (
            [parameter(Mandatory)]
            [DbaInstanceParameter]$Source,
            [PSCredential]
            $SourceSqlCredential,
            [parameter(Mandatory)]
            [DbaInstanceParameter[]]$Destination,
            [PSCredential]
            $DestinationSqlCredential,
            [object[]]$Audit,
            [object[]]$ExcludeAudit,
            [switch]$Force,
            [Alias('Silent')]
            [switch]$EnableException
        )
    
        begin {
    
            try {
                $sourceServer = Connect-SqlInstance -SqlInstance $Source -SqlCredential $SourceSqlCredential -MinimumVersion 10
            } catch {
                Stop-Function -Message "Failure" -Category ConnectionError -ErrorRecord $_ -Target $Source
                return
            }
            $serverAudits = $sourceServer.Audits
        }
        process {
            if (Test-FunctionInterrupt) { return }
            foreach ($destinstance in $Destination) {
    
                try {
                    $destServer = Connect-SqlInstance -SqlInstance $destinstance -SqlCredential $DestinationSqlCredential -MinimumVersion 10
                } catch {
                    Stop-Function -Message "Failure" -Category ConnectionError -ErrorRecord $_ -Target $destinstance -Continue
                }
                $destAudits = $destServer.Audits
                foreach ($currentAudit in $serverAudits) {
                    $auditName = $currentAudit.Name
    
                    $copyAuditStatus = [pscustomobject]@{
                        SourceServer      = $sourceServer.Name
                        DestinationServer = $destServer.Name
                        Name              = $auditName
                        Type              = "Server Audit"
                        Status            = $null
                        Notes             = $null
                        DateTime          = [DbaDateTime](Get-Date)
                    }
    
                    if ($Audit -and $auditName -notin $Audit -or $auditName -in $ExcludeAudit) {
                        continue
                    }
    
                    $sql = $currentAudit.Script() | Out-String
    
                    if ($destAudits.Name -contains $auditName) {
                        if ($force -eq $false) {
                            if ($Pscmdlet.ShouldProcess($destinstance, "Server audit $auditName exists at destination. Use -Force to drop and migrate.")) {
                                $copyAuditStatus.Status = "Skipped"
                                $copyAuditStatus.Notes = "Already exists"
                                Write-Message -Level Verbose -Message "Server audit $auditName exists at destination. Use -Force to drop and migrate."
                            }
                            continue
                        } else {
                            if ($Pscmdlet.ShouldProcess($destinstance, "Dropping server audit $auditName")) {
                                try {
                                    Write-Message -Level Verbose -Message "Dropping server audit $auditName."
                                    foreach ($spec in $destServer.ServerAuditSpecifications) {
                                        if ($auditSpecification.Auditname -eq $auditName) {
                                            $auditSpecification.Drop()
                                        }
                                    }
    
                                    $destServer.audits[$auditName].Disable()
                                    $destServer.audits[$auditName].Alter()
                                    $destServer.audits[$auditName].Drop()
                                } catch {
                                    $copyAuditStatus.Status = "Failed"
                                    $copyAuditStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
    
                                    Stop-Function -Message "Issue dropping audit from destination." -Target $auditName -ErrorRecord $_
                                }
                            }
                        }
                    }
    
                    if ($null -ne ($currentAudit.Filepath) -and -not (Test-DbaPath -SqlInstance $destServer -Path $currentAudit.Filepath)) {
                        if ($Force -eq $false) {
                            if ($Pscmdlet.ShouldProcess($destinstance, "$($currentAudit.Filepath) does not exist on $destinstance. Skipping $auditName. Specify -Force to create the directory.")) {
                                $copyAuditStatus.Status = "Skipped"
                                $copyAuditStatus.Notes = "$($currentAudit.Filepath) does not exist on $destinstance. Skipping $auditName. Specify -Force to create the directory."
                                $copyAuditStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
                            }
                            continue
                        } else {
                            Write-Message -Level Verbose -Message "Force specified. Creating directory."
    
                            $destNetBios = Resolve-NetBiosName $destServer
                            #Variable marked as unused by PSScriptAnalyzer
                            #$path = Join-AdminUnc $destNetBios $currentAudit.Filepath
                            $root = $currentAudit.Filepath.Substring(0, 3)
                            $rootUnc = Join-AdminUnc $destNetBios $root
    
                            if ((Test-Path $rootUnc) -eq $true) {
                                if ($Pscmdlet.ShouldProcess($destinstance, "Creating directory $($currentAudit.Filepath)")) {
                                    try {
                                        $null = New-DbaDirectory -SqlInstance $destServer -Path $currentAudit.Filepath -EnableException
                                    } catch {
                                        Write-Message -Level Warning -Message "Couldn't create directory $($currentAudit.Filepath). Using default data directory."
                                        $datadir = Get-SqlDefaultPaths $destServer data
                                        $sql = $sql.Replace($currentAudit.FilePath, $datadir)
                                    }
                                }
                            } else {
                                $datadir = Get-SqlDefaultPaths $destServer data
                                $sql = $sql.Replace($currentAudit.FilePath, $datadir)
                            }
                        }
                    }
                    if ($Pscmdlet.ShouldProcess($destinstance, "Creating server audit $auditName")) {
                        try {
                            Write-Message -Level Verbose -Message "File path $($currentAudit.Filepath) exists on $destinstance."
                            Write-Message -Level Verbose -Message "Copying server audit $auditName."
                            $destServer.Query($sql)
    
                            $copyAuditStatus.Status = "Successful"
                            $copyAuditStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
                        } catch {
                            $copyAuditStatus.Status = "Failed"
                            $copyAuditStatus.Notes = (Get-ErrorMessage -Record $_)
                            $copyAuditStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
    
                            Stop-Function -Message "Issue creating audit." -Target $auditName -ErrorRecord $_
                        }
                    }
                }
            }
        }
        end {
            Test-DbaDeprecation -DeprecatedOn "1.0.0" -EnableException:$false -Alias Copy-SqlAudit
        }
    }
    function Copy-DbaServerAuditSpecification {
        <#
        .SYNOPSIS
            Copy-DbaServerAuditSpecification migrates server audit specifications from one SQL Server to another.
    
        .DESCRIPTION
            By default, all audits are copied. The -AuditSpecification parameter is auto-populated for command-line completion and can be used to copy only specific audits.
    
            If the audit specification already exists on the destination, it will be skipped unless -Force is used.
    
        .PARAMETER Source
            Source SQL Server. You must have sysadmin access and server version must be SQL Server version 2000 or higher.
    
        .PARAMETER SourceSqlCredential
            Login to the target instance using alternative credentials. Windows and SQL Authentication supported. Accepts credential objects (Get-Credential)
    
        .PARAMETER Destination
            Destination SQL Server. You must have sysadmin access and the server must be SQL Server 2000 or higher.
    
        .PARAMETER DestinationSqlCredential
            Login to the target instance using alternative credentials. Windows and SQL Authentication supported. Accepts credential objects (Get-Credential)
    
        .PARAMETER AuditSpecification
            The Server Audit Specification(s) to process. Options for this list are auto-populated from the server. If unspecified, all Server Audit Specifications will be processed.
    
        .PARAMETER ExcludeAuditSpecification
            The Server Audit Specification(s) to exclude. Options for this list are auto-populated from the server
    
        .PARAMETER WhatIf
            If this switch is enabled, no actions are performed but informational messages will be displayed that explain what would happen if the command were to run.
    
        .PARAMETER Confirm
            If this switch is enabled, you will be prompted for confirmation before executing any operations that change state.
    
        .PARAMETER Force
            If this switch is enabled, the Audits Specifications will be dropped and recreated on Destination.
    
        .PARAMETER EnableException
            By default, when something goes wrong we try to catch it, interpret it and give you a friendly warning message.
            This avoids overwhelming you with "sea of red" exceptions, but is inconvenient because it basically disables advanced scripting.
            Using this switch turns this "nice by default" feature off and enables you to catch exceptions with your own try/catch.
    
        .NOTES
            Tags: Migration,ServerAudit,AuditSpecification
            Author: Chrissy LeMaire (@cl), netnerds.net
    
            Website: https://dbatools.io
            Copyright: (c) 2018 by dbatools, licensed under MIT
            License: MIT https://opensource.org/licenses/MIT
    
            Requires: sysadmin access on SQL Servers
    
        .LINK
            https://dbatools.io/Copy-DbaServerAuditSpecification
    
        .EXAMPLE
            PS C:\> Copy-DbaServerAuditSpecification -Source sqlserver2014a -Destination sqlcluster
    
            Copies all server audits from sqlserver2014a to sqlcluster using Windows credentials to connect. If audits with the same name exist on sqlcluster, they will be skipped.
    
        .EXAMPLE
            PS C:\> Copy-DbaServerAuditSpecification -Source sqlserver2014a -Destination sqlcluster -AuditSpecification tg_noDbDrop -SourceSqlCredential $cred -Force
    
            Copies a single audit, the tg_noDbDrop audit from sqlserver2014a to sqlcluster using SQL credentials to connect to sqlserver2014a and Windows credentials to connect to sqlcluster. If an audit specification with the same name exists on sqlcluster, it will be dropped and recreated because -Force was used.
    
        .EXAMPLE
            PS C:\> Copy-DbaServerAuditSpecification -Source sqlserver2014a -Destination sqlcluster -WhatIf -Force
    
            Shows what would happen if the command were executed using force.
    
        #>
        [CmdletBinding(DefaultParameterSetName = "Default", SupportsShouldProcess = $true)]
        param (
            [parameter(Mandatory)]
            [DbaInstanceParameter]$Source,
            [PSCredential]$SourceSqlCredential,
            [parameter(Mandatory)]
            [DbaInstanceParameter[]]$Destination,
            [PSCredential]$DestinationSqlCredential,
            [object[]]$AuditSpecification,
            [object[]]$ExcludeAuditSpecification,
            [switch]$Force,
            [Alias('Silent')]
            [switch]$EnableException
        )
    
        begin {
            try {
                $sourceServer = Connect-SqlInstance -SqlInstance $Source -SqlCredential $SourceSqlCredential -MinimumVersion 10
            } catch {
                Stop-Function -Message "Failure" -Category ConnectionError -ErrorRecord $_ -Target $Source
                return
            }
    
            if (!(Test-SqlSa -SqlInstance $sourceServer -SqlCredential $SourceSqlCredential)) {
                Stop-Function -Message "Not a sysadmin on $source. Quitting."
                return
            }
    
            $AuditSpecifications = $sourceServer.ServerAuditSpecifications
        }
        process {
            if (Test-FunctionInterrupt) { return }
            foreach ($destinstance in $Destination) {
                try {
                    $destServer = Connect-SqlInstance -SqlInstance $destinstance -SqlCredential $DestinationSqlCredential -MinimumVersion 10
                } catch {
                    Stop-Function -Message "Failure" -Category ConnectionError -ErrorRecord $_ -Target $destinstance -Continue
                }
    
                if (!(Test-SqlSa -SqlInstance $destServer -SqlCredential $DestinationSqlCredential)) {
                    Stop-Function -Message "Not a sysadmin on $destinstance. Quitting."
                    return
                }
    
                if ($destServer.VersionMajor -lt $sourceServer.VersionMajor) {
                    Stop-Function -Message "Migration from version $($destServer.VersionMajor) to version $($sourceServer.VersionMajor) is not supported."
                    return
                }
                $destAudits = $destServer.ServerAuditSpecifications
                foreach ($auditSpec in $AuditSpecifications) {
                    $auditSpecName = $auditSpec.Name
    
                    $copyAuditSpecStatus = [pscustomobject]@{
                        SourceServer      = $sourceServer.Name
                        DestinationServer = $destServer.Name
                        Type              = "Server Audit Specification"
                        Name              = $auditSpecName
                        Status            = $null
                        Notes             = $null
                        DateTime          = [DbaDateTime](Get-Date)
                    }
    
                    if ($AuditSpecification -and $auditSpecName -notin $AuditSpecification -or $auditSpecName -in $ExcludeAuditSpecification) {
                        continue
                    }
    
                    $destServer.Audits.Refresh()
                    if ($destServer.Audits.Name -notcontains $auditSpec.AuditName) {
                        if ($Pscmdlet.ShouldProcess($destinstance, "Audit $($auditSpec.AuditName) does not exist on $destinstance. Skipping $auditSpecName.")) {
                            $copyAuditSpecStatus.Status = "Skipped"
                            $copyAuditSpecStatus.Notes = "Audit $($auditSpec.AuditName) does not exist on $destinstance. Skipping $auditSpecName."
                            Write-Message -Level Warning -Message "Audit $($auditSpec.AuditName) does not exist on $destinstance. Skipping $auditSpecName."
                            $copyAuditSpecStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
                        }
                        continue
                    }
    
                    if ($destAudits.name -contains $auditSpecName) {
                        if ($force -eq $false) {
                            Write-Message -Level Verbose -Message "Server audit $auditSpecName exists at destination. Use -Force to drop and migrate."
    
                            $copyAuditSpecStatus.Status = "Skipped"
                            $copyAuditSpecStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
                            continue
                        } else {
                            if ($Pscmdlet.ShouldProcess($destinstance, "Dropping server audit $auditSpecName and recreating")) {
                                try {
                                    Write-Message -Level Verbose -Message "Dropping server audit $auditSpecName"
                                    $destServer.ServerAuditSpecifications[$auditSpecName].Drop()
                                } catch {
                                    $copyAuditSpecStatus.Status = "Failed"
                                    $copyAuditSpecStatus.Notes = (Get-ErrorMessage -Record $_)
                                    $copyAuditSpecStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
    
                                    Stop-Function -Message "Issue dropping audit spec" -Target $auditSpecName -ErrorRecord $_ -Continue
                                }
                            }
                        }
                    }
                    if ($Pscmdlet.ShouldProcess($destinstance, "Creating server audit $auditSpecName")) {
                        try {
                            Write-Message -Level Verbose -Message "Copying server audit $auditSpecName"
                            $sql = $auditSpec.Script() | Out-String
                            Write-Message -Level Debug -Message $sql
                            $destServer.Query($sql)
    
                            $copyAuditSpecStatus.Status = "Successful"
                            $copyAuditSpecStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
                        } catch {
                            $copyAuditSpecStatus.Status = "Failed"
                            $copyAuditSpecStatus.Notes = (Get-ErrorMessage -Record $_)
                            $copyAuditSpecStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
    
                            Stop-Function -Message "Issue creating audit spec on destination" -Target $auditSpecName -ErrorRecord $_
                        }
                    }
                }
            }
        }
        end {
            Test-DbaDeprecation -DeprecatedOn "1.0.0" -EnableException:$false -Alias Copy-SqlAuditSpecification
        }
    }
    function Copy-DbaServerTrigger {
        <#
        .SYNOPSIS
            Copy-DbaServerTrigger migrates server triggers from one SQL Server to another.
    
        .DESCRIPTION
            By default, all triggers are copied. The -ServerTrigger parameter is auto-populated for command-line completion and can be used to copy only specific triggers.
    
            If the trigger already exists on the destination, it will be skipped unless -Force is used.
    
        .PARAMETER Source
            Source SQL Server.You must have sysadmin access and server version must be SQL Server version 2000 or greater.
    
        .PARAMETER SourceSqlCredential
            Login to the target instance using alternative credentials. Windows and SQL Authentication supported. Accepts credential objects (Get-Credential)
    
        .PARAMETER Destination
            Destination Sql Server. You must have sysadmin access and server version must be SQL Server version 2000 or greater.
    
        .PARAMETER DestinationSqlCredential
            Login to the target instance using alternative credentials. Windows and SQL Authentication supported. Accepts credential objects (Get-Credential)
    
        .PARAMETER ServerTrigger
            The Server Trigger(s) to process - this list is auto-populated from the server. If unspecified, all Server Triggers will be processed.
    
        .PARAMETER ExcludeServerTrigger
            The Server Trigger(s) to exclude - this list is auto-populated from the server
    
        .PARAMETER WhatIf
            Shows what would happen if the command were to run. No actions are actually performed.
    
        .PARAMETER Confirm
            Prompts you for confirmation before executing any changing operations within the command.
    
        .PARAMETER Force
            Drops and recreates the Trigger if it exists
    
        .PARAMETER EnableException
            By default, when something goes wrong we try to catch it, interpret it and give you a friendly warning message.
            This avoids overwhelming you with "sea of red" exceptions, but is inconvenient because it basically disables advanced scripting.
            Using this switch turns this "nice by default" feature off and enables you to catch exceptions with your own try/catch.
    
        .NOTES
            Tags: Migration
            Author: Chrissy LeMaire (@cl), netnerds.net
    
            Website: https://dbatools.io
            Copyright: (c) 2018 by dbatools, licensed under MIT
            License: MIT https://opensource.org/licenses/MIT
    
            Requires: sysadmin access on SQL Servers
    
        .LINK
            https://dbatools.io/Copy-DbaServerTrigger
    
        .EXAMPLE
            PS C:\> Copy-DbaServerTrigger -Source sqlserver2014a -Destination sqlcluster
    
            Copies all server triggers from sqlserver2014a to sqlcluster, using Windows credentials. If triggers with the same name exist on sqlcluster, they will be skipped.
    
        .EXAMPLE
            PS C:\> Copy-DbaServerTrigger -Source sqlserver2014a -Destination sqlcluster -ServerTrigger tg_noDbDrop -SourceSqlCredential $cred -Force
    
            Copies a single trigger, the tg_noDbDrop trigger from sqlserver2014a to sqlcluster, using SQL credentials for sqlserver2014a and Windows credentials for sqlcluster. If a trigger with the same name exists on sqlcluster, it will be dropped and recreated because -Force was used.
    
        .EXAMPLE
            PS C:\> Copy-DbaServerTrigger -Source sqlserver2014a -Destination sqlcluster -WhatIf -Force
    
            Shows what would happen if the command were executed using force.
    
        #>
        [CmdletBinding(DefaultParameterSetName = "Default", SupportsShouldProcess = $true)]
        param (
            [parameter(Mandatory)]
            [DbaInstanceParameter]$Source,
            [PSCredential]
            $SourceSqlCredential,
            [parameter(Mandatory)]
            [DbaInstanceParameter[]]$Destination,
            [PSCredential]
            $DestinationSqlCredential,
            [object[]]$ServerTrigger,
            [object[]]$ExcludeServerTrigger,
            [switch]$Force,
            [Alias('Silent')]
            [switch]$EnableException
        )
    
        begin {
            try {
                $sourceServer = Connect-SqlInstance -SqlInstance $Source -SqlCredential $SourceSqlCredential -MinimumVersion 9
            } catch {
                Stop-Function -Message "Failure" -Category ConnectionError -ErrorRecord $_ -Target $Source
                return
            }
            $serverTriggers = $sourceServer.Triggers
        }
        process {
            if (Test-FunctionInterrupt) { return }
            foreach ($destinstance in $Destination) {
                try {
                    $destServer = Connect-SqlInstance -SqlInstance $destinstance -SqlCredential $DestinationSqlCredential -MinimumVersion 9
                } catch {
                    Stop-Function -Message "Failure" -Category ConnectionError -ErrorRecord $_ -Target $destinstance -Continue
                }
                if ($destServer.VersionMajor -lt $sourceServer.VersionMajor) {
                    Stop-Function -Message "Migration from version $($destServer.VersionMajor) to version $($sourceServer.VersionMajor) is not supported."
                    return
                }
                $destTriggers = $destServer.Triggers
    
                foreach ($trigger in $serverTriggers) {
                    $triggerName = $trigger.Name
    
                    $copyTriggerStatus = [pscustomobject]@{
                        SourceServer      = $sourceServer.Name
                        DestinationServer = $destServer.Name
                        Name              = $triggerName
                        Type              = "Server Trigger"
                        Status            = $null
                        Notes             = $null
                        DateTime          = [DbaDateTime](Get-Date)
                    }
    
                    if ($ServerTrigger -and $triggerName -notin $ServerTrigger -or $triggerName -in $ExcludeServerTrigger) {
                        continue
                    }
    
                    if ($destTriggers.Name -contains $triggerName) {
                        if ($force -eq $false) {
                            Write-Message -Level Verbose -Message "Server trigger $triggerName exists at destination. Use -Force to drop and migrate."
    
                            $copyTriggerStatus.Status = "Skipped"
                            $copyTriggerStatus.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
    
            Website: https://dbatools.io
            Copyright: (c) 2018 by dbatools, licensed under MIT
            License: MIT https://opensource.org/licenses/MIT
    
            Requires: sysadmin access on SQL Servers
    
        .LINK
            https://dbatools.io/Copy-DbaSpConfigure
    
        .EXAMPLE
            PS C:\> Copy-DbaSpConfigure -Source sqlserver2014a -Destination sqlcluster
    
            Copies all sp_configure settings from sqlserver2014a to sqlcluster
    
        .EXAMPLE
            PS C:\> Copy-DbaSpConfigure -Source sqlserver2014a -Destination sqlcluster -ConfigName DefaultBackupCompression, IsSqlClrEnabled -SourceSqlCredential $cred
    
            Copies the values for IsSqlClrEnabled and DefaultBackupCompression from sqlserver2014a to sqlcluster using SQL credentials to authenticate to sqlserver2014a and Windows credentials to authenticate to sqlcluster.
    
        .EXAMPLE
            PS C:\> Copy-DbaSpConfigure -Source sqlserver2014a -Destination sqlcluster -ExcludeConfigName DefaultBackupCompression, IsSqlClrEnabled
    
            Copies all configs except for IsSqlClrEnabled and DefaultBackupCompression, from sqlserver2014a to sqlcluster.
    
        .EXAMPLE
            PS C:\> Copy-DbaSpConfigure -Source sqlserver2014a -Destination sqlcluster -WhatIf
    
            Shows what would happen if the command were executed.
    
        #>
        [CmdletBinding(DefaultParameterSetName = "Default", SupportsShouldProcess = $true)]
        param (
            [parameter(Mandatory)]
            [DbaInstanceParameter]$Source,
            [PSCredential]$SourceSqlCredential,
            [parameter(Mandatory)]
            [DbaInstanceParameter[]]$Destination,
            [PSCredential]$DestinationSqlCredential,
            [object[]]$ConfigName,
            [object[]]$ExcludeConfigName,
            [Alias('Silent')]
            [switch]$EnableException
        )
        begin {
            try {
                $sourceServer = Connect-SqlInstance -SqlInstance $Source -SqlCredential $SourceSqlCredential
                $sourceProps = Get-DbaSpConfigure -SqlInstance $sourceServer
            } catch {
                Stop-Function -Message "Failure" -Category ConnectionError -ErrorRecord $_ -Target $Source
                return
            }
        }
        process {
            if (Test-FunctionInterrupt) { return }
            foreach ($destinstance in $Destination) {
                try {
                    $destServer = Connect-SqlInstance -SqlInstance $destinstance -SqlCredential $DestinationSqlCredential
                    $destProps = Get-DbaSpConfigure -SqlInstance $destServer
                } catch {
                    Stop-Function -Message "Failure" -Category ConnectionError -ErrorRecord $_ -Target $destinstance -Continue
                }
    
                foreach ($sourceProp in $sourceProps) {
                    $displayName = $sourceProp.DisplayName
                    $sConfigName = $sourceProp.ConfigName
                    $sConfiguredValue = $sourceProp.ConfiguredValue
                    $requiresRestart = $sourceProp.IsDynamic
    
                    $copySpConfigStatus = [pscustomobject]@{
                        SourceServer      = $sourceServer.Name
                        DestinationServer = $destServer.Name
                        Name              = $sConfigName
                        Type              = "Configuration Value"
                        Status            = $null
                        Notes             = $null
                        DateTime          = [DbaDateTime](Get-Date)
                    }
    
                    if ($ConfigName -and $sConfigName -notin $ConfigName -or $sConfigName -in $ExcludeConfigName) {
                        continue
                    }
    
                    $destProp = $destProps | Where-Object ConfigName -eq $sConfigName
                    if (!$destProp) {
                        Write-Message -Level Verbose -Message "Configuration $sConfigName ('$displayName') does not exist on the destination instance."
    
                        $copySpConfigStatus.Status = "Skipped"
                        $copySpConfigStatus.Notes = "Configuration does not exist on destination"
                        $copySpConfigStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
    
                        continue
                    }
    
                    if ($Pscmdlet.ShouldProcess($destinstance, "Updating $sConfigName [$displayName]")) {
                        try {
                            $destOldConfigValue = $destProp.ConfiguredValue
    
                            if ($sConfiguredValue -ne $destOldConfigValue) {
                                $result = Set-DbaSpConfigure -SqlInstance $destServer -Name $sConfigName -Value $sConfiguredValue -EnableException -WarningAction SilentlyContinue
                                if ($result) {
                                    Write-Message -Level Verbose -Message "Updated $($destProp.ConfigName) ($($destProp.DisplayName)) from $destOldConfigValue to $sConfiguredValue."
                                }
                            }
                            if ($requiresRestart -eq $false) {
                                Write-Message -Level Verbose -Message "Configuration option $sConfigName ($displayName) requires restart."
                                $copySpConfigStatus.Notes = "Requires restart"
                            }
                            $copySpConfigStatus.Status = "Successful"
                            $copySpConfigStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
                        } catch {
                            if ($_.Exception -match 'the same as the') {
                                $copySpConfigStatus.Status = "Successful"
                            } else {
                                $copySpConfigStatus.Status = "Failed"
                                $copySpConfigStatus.Notes = (Get-ErrorMessage -Record $_)
                            }
                            $copySpConfigStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
    
                            Stop-Function -Message "Could not set $($destProp.ConfigName) to $sConfiguredValue." -Target $sConfigName -ErrorRecord $_
                        }
                    }
                }
            }
        }
        end {
            Test-DbaDeprecation -DeprecatedOn "1.0.0" -EnableException:$false -Alias Copy-SqlSpConfigure
        }
    }
    #ValidationTags#Messaging#
    function Copy-DbaSsisCatalog {
        <#
        .SYNOPSIS
            Copy-DbaSsisCatalog migrates Folders, SSIS projects, and environments from one SQL Server to another.
    
        .DESCRIPTION
            By default, all folders, projects, and environments are copied. The -Project parameter can be specified to copy only one project, if desired.
    
            The parameters get more granular from the Folder level. For example, specifying -Folder will only deploy projects/environments from within that folder.
    
        .PARAMETER Source
            Source SQL Server. You must have sysadmin access and server version must be SQL Server version 2012 or higher.
    
        .PARAMETER SourceSqlCredential
            Login to the target instance using alternative credentials. Windows and SQL Authentication supported. Accepts credential objects (Get-Credential)
    
        .PARAMETER Destination
            Destination SQL Server. You must have sysadmin access and the server must be SQL Server 2012 or higher.
    
        .PARAMETER DestinationSqlCredential
            Login to the target instance using alternative credentials. Windows and SQL Authentication supported. Accepts credential objects (Get-Credential)
    
        .PARAMETER Force
            If this switch is enabled, the SSIS Catalog will be dropped and recreated on Destination if it already exists.
    
        .PARAMETER Project
            Specifies a source Project name.
    
        .PARAMETER Folder
            Specifies a source folder name.
    
        .PARAMETER Environment
            Specifies an environment to copy.
    
        .PARAMETER EnableSqlClr
            If this switch is enabled and Destination does not have the SQL CLR configuration option enabled, user prompts for enabling it on Destination will be skipped. SQL CLR is required for SSISDB.
    
        .PARAMETER CreateCatalogPassword
            Specifies a secure string to use in creating an SSISDB catalog on Destination. If this is specified, prompts for the password will be skipped.
    
        .PARAMETER WhatIf
            If this switch is enabled, no actions are performed but informational messages will be displayed that explain what would happen if the command were to run.
    
        .PARAMETER Confirm
            If this switch is enabled, you will be prompted for confirmation before executing any operations that change state.
    
        .PARAMETER EnableException
            By default, when something goes wrong we try to catch it, interpret it and give you a friendly warning message.
            This avoids overwhelming you with "sea of red" exceptions, but is inconvenient because it basically disables advanced scripting.
            Using this switch turns this "nice by default" feature off and enables you to catch exceptions with your own try/catch.
    
        .NOTES
            Tags: Migration, SSIS
            Author: Phil Schwartz (philschwartz.me, @pschwartzzz)
    
            dbatools PowerShell module (https://dbatools.io, [email protected])
            Copyright: (c) 2018 by dbatools, licensed under MIT
            License: MIT https://opensource.org/licenses/MIT
    
        .LINK
            https://dbatools.io/Copy-DbaSsisCatalog
    
        .EXAMPLE
            PS C:\> Copy-DbaSsisCatalog -Source sqlserver2014a -Destination sqlcluster
    
            Copies all folders, environments and SSIS Projects from sqlserver2014a to sqlcluster, using Windows credentials to authenticate to both instances. If folders with the same name exist on the destination they will be skipped, but projects will be redeployed.
    
        .EXAMPLE
            PS C:\> Copy-DbaSsisCatalog -Source sqlserver2014a -Destination sqlcluster -Project Archive_Tables -SourceSqlCredential $cred -Force
    
            Copies a single Project, the Archive_Tables Project, from sqlserver2014a to sqlcluster using SQL credentials to authenticate to sqlserver2014a and Windows credentials to authenticate to sqlcluster. If a Project with the same name exists on sqlcluster, it will be deleted and recreated because -Force was used.
    
        .EXAMPLE
            PS C:\> Copy-DbaSsisCatalog -Source sqlserver2014a -Destination sqlcluster -WhatIf -Force
    
            Shows what would happen if the command were executed using force.
    
        .EXAMPLE
            PS C:\> $SecurePW = Read-Host "Enter password" -AsSecureString
            PS C:\> Copy-DbaSsisCatalog -Source sqlserver2014a -Destination sqlcluster -CreateCatalogPassword $SecurePW
    
            Deploy entire SSIS catalog to an instance without a destination catalog. User prompts for creating the catalog on Destination will be bypassed.
    
           #>
        [CmdletBinding(DefaultParameterSetName = "Default", SupportsShouldProcess)]
        param (
            [parameter(Mandatory)]
            [DbaInstanceParameter]$Source,
            [parameter(Mandatory)]
            [DbaInstanceParameter[]]$Destination,
            [PSCredential]$SourceSqlCredential,
            [PSCredential]$DestinationSqlCredential,
            [String]$Project,
            [String]$Folder,
            [String]$Environment,
            [System.Security.SecureString]$CreateCatalogPassword,
            [Switch]$EnableSqlClr,
            [Switch]$Force,
            [Alias('Silent')]
            [switch]$EnableException
        )
        <# Developer note: The throw calls must stay in this command #>
        begin {
            function Get-RemoteIntegrationService {
                param (
                    [Object]$Computer
                )
                $result = Get-DbaService -ComputerName $Computer -Type SSIS
                if ($result) {
                    #Variable marked as unused by PSScriptAnalyzer
                    #$running = $false
                    foreach ($service in $result) {
                        if (!$service.State -eq "Running") {
                            Write-Message -Level Warning -Message "Service $($service.DisplayName) was found on the destination, but is currently not running."
                        } else {
                            Write-Message -Level Verbose -Message "Service $($service.DisplayName) was found running on the destination."
                            #$running = $true
                        }
                    }
                } else {
                    throw "No Integration Services service was found on the destination, please ensure the feature is installed and running."
                }
            }
            function Invoke-ProjectDeployment {
                param (
                    [String]$Project,
                    [String]$Folder
                )
                $sqlConn = New-Object System.Data.SqlClient.SqlConnection
                $sqlConn.ConnectionString = $sourceServer.ConnectionContext.ConnectionString
                if ($sqlConn.State -eq "Closed") {
                    $sqlConn.Open()
                }
                try {
                    Write-Message -Level Verbose -Message "Deploying project $Project from folder $Folder."
                    $cmd = New-Object System.Data.SqlClient.SqlCommand
                    $cmd.CommandType = "StoredProcedure"
                    $cmd.connection = $sqlConn
                    $cmd.CommandText = "SSISDB.Catalog.get_project"
                    $cmd.Parameters.Add("@folder_name", $Folder) | out-null;
                    $cmd.Parameters.Add("@project_name", $Project) | out-null;
                    [byte[]]$results = $cmd.ExecuteScalar();
                    if ($null -ne $results) {
                        $destFolder = $destinationFolders | Where-Object {
                            $_.Name -eq $Folder
                        }
                        $deployedProject = $destFolder.DeployProject($Project, $results)
                        if ($deployedProject.Status -ne "Success") {
                            Stop-Function -Message "An error occurred deploying project $Project." -Target $Project -Continue
                        }
                    } else {
                        Stop-Function -Message "Failed deploying $Project from folder $Folder." -Target $Project -Continue
                    }
                } catch {
                    Stop-Function -Message "Failed to deploy project." -Target $Project -ErrorRecord $_
                } finally {
                    if ($sqlConn.State -eq "Open") {
                        $sqlConn.Close()
                    }
                }
            }
            function New-CatalogFolder {
                [CmdletBinding(SupportsShouldProcess)]
                param (
                    [String]$Folder,
                    [String]$Description,
                    [Switch]$Force
                )
                if ($Pscmdlet.ShouldProcess($folder, "Creating new Catalog Folder")) {
                    if ($Force) {
                        $remove = $destinationFolders | Where-Object {
                            $_.Name -eq $Folder
                        }
                        $envs = $remove.Environments.Name
                        foreach ($e in $envs) {
                            $remove.Environments[$e].Drop()
                        }
                        $projs = $remove.Projects.Name
                        foreach ($p in $projs) {
                            $remove.Projects[$p].Drop()
                        }
                        $remove.Drop()
                        $destinationCatalog.Alter()
                        $destinationCatalog.Refresh()
                    }
                    Write-Message -Level Verbose -Message "Creating folder $Folder."
                    $destFolder = New-Object "$ISNamespace.CatalogFolder" ($destinationCatalog, $Folder, $Description)
                    $destFolder.Create()
                    $destFolder.Alter()
                    $destFolder.Refresh()
                }
            }
            function New-FolderEnvironment {
                [CmdletBinding(SupportsShouldProcess)]
                param (
                    [String]$Folder,
                    [String]$Environment,
                    [Switch]$Force
                )
                if ($Pscmdlet.ShouldProcess($folder, "Creating new Environment Folder")) {
                    $envDestFolder = $destinationFolders | Where-Object {
                        $_.Name -eq $Folder
                    }
                    if ($force) {
                        $envDestFolder.Environments[$Environment].Drop()
                        $envDestFolder.Alter()
                        $envDestFolder.Refresh()
                    }
                    $srcEnv = ($sourceFolders | Where-Object {
                            $_.Name -eq $Folder
                        }).Environments[$Environment]
                    $targetEnv = New-Object "$ISNamespace.EnvironmentInfo" ($envDestFolder, $srcEnv.Name, $srcEnv.Description)
                    foreach ($var in $srcEnv.Variables) {
                        if ($var.Value.ToString() -eq "") {
                            $finalValue = ""
                        } else {
                            $finalValue = $var.Value
                        }
                        $targetEnv.Variables.Add($var.Name, $var.Type, $finalValue, $var.Sensitive, $var.Description)
                    }
                    Write-Message -Level Verbose -Message "Creating environment $Environment."
                    $targetEnv.Create()
                    $targetEnv.Alter()
                    $targetEnv.Refresh()
                }
            }
            function New-SSISDBCatalog {
                [CmdletBinding(SupportsShouldProcess)]
                param (
                    [System.Security.SecureString]$Password
                )
                if ($Pscmdlet.ShouldProcess("Creating New SSISDB Catalog")) {
                    if (!$Password) {
                        Write-Message -Level Verbose -Message "SSISDB Catalog requires a password."
                        $pass1 = Read-Host "Enter a password" -AsSecureString
                        $plainTextPass1 = [Runtime.InteropServices.Marshal]::PtrToStringAuto([Runtime.InteropServices.Marshal]::SecureStringToBSTR($pass1))
                        $pass2 = Read-Host "Re-enter password" -AsSecureString
                        $plainTextPass2 = [Runtime.InteropServices.Marshal]::PtrToStringAuto([Runtime.InteropServices.Marshal]::SecureStringToBSTR($pass2))
                        if ($plainTextPass1 -ne $plainTextPass2) {
                            throw "Validation error, passwords entered do not match."
                        }
                        $plainTextPass = $plainTextPass1
                    } else {
                        $plainTextPass = [Runtime.InteropServices.Marshal]::PtrToStringAuto([Runtime.InteropServices.Marshal]::SecureStringToBSTR($Password))
                    }
    
                    $catalog = New-Object "$ISNamespace.Catalog" ($destinationSSIS, "SSISDB", $plainTextPass)
                    $catalog.Create()
                    $catalog.Refresh()
                }
            }
    
            try {
                $sourceServer = Connect-SqlInstance -SqlInstance $Source -SqlCredential $SourceSqlCredential -MinimumVersion 11
            } catch {
                Stop-Function -Message "Failure" -Category ConnectionError -ErrorRecord $_ -Target $Source
                return
            }
    
            try {
                $sourceSSIS = New-Object Microsoft.SqlServer.Management.IntegrationServices.IntegrationServices $sourceServer
            } catch {
                Stop-Function -Message "There was an error connecting to the source integration services." -Target $sourceServer -ErrorRecord $_
                return
            }
    
            $sourceCatalog = $sourceSSIS.Catalogs | Where-Object {
                $_.Name -eq "SSISDB"
            }
            if (!$sourceCatalog) {
                Stop-Function -Message "The source SSISDB catalog on $Source does not exist."
                return
            }
            $sourceFolders = $sourceCatalog.Folders
        }
        process {
            if (Test-FunctionInterrupt) {
                return
            }
            foreach ($destinstance in $Destination) {
                try {
                    $destinationConnection = Connect-SqlInstance -SqlInstance $destinstance -SqlCredential $DestinationSqlCredential -MinimumVersion 1
                } catch {
                    Stop-Function -Message "Failure" -Category ConnectionError -ErrorRecord $_ -Target $destinstance -Continue
                }
    
                try {
                    Get-RemoteIntegrationService -Computer $destinstance.ComputerName
                } catch {
                    Stop-Function -Message "An error occurred when checking the destination for Integration Services. Is Integration Services installed?" -Target $destinstance -ErrorRecord $_
                }
    
                try {
                    $destinationSSIS = New-Object Microsoft.SqlServer.Management.IntegrationServices.IntegrationServices $destinationConnection
                } catch {
                    Stop-Function -Message "There was an error connecting to the destination integration services." -Target $destinationCon -ErrorRecord $_
                }
    
                $destinationCatalog = $destinationSSIS.Catalogs | Where-Object {
                    $_.Name -eq "SSISDB"
                }
                $destinationFolders = $destinationCatalog.Folders
    
                if (!$destinationCatalog) {
                    if (!$destinationConnection.Configuration.IsSqlClrEnabled.ConfigValue) {
                        if ($Pscmdlet.ShouldProcess($destinstance, "Enabling SQL CLR configuration option.")) {
                            if (!$EnableSqlClr) {
                                $message = "The destination does not have SQL CLR configuration option enabled (required by SSISDB), would you like to enable it?"
                                $yes = New-Object System.Management.Automation.Host.ChoiceDescription "&Yes", "Enable SQL CLR on $destinstance."
                                $no = New-Object System.Management.Automation.Host.ChoiceDescription "&No", "Exit."
                                $options = [System.Management.Automation.Host.ChoiceDescription[]]($yes, $no)
                                $result = $host.ui.PromptForChoice($null, $message, $options, 0)
                                switch ($result) {
                                    0 {
                                        continue
                                    }
                                    1 {
                                        return
                                    }
                                }
                            }
                            Write-Message -Level Verbose -Message "Enabling SQL CLR configuration option at the destination."
                            if ($destinationConnection.Configuration.ShowAdvancedOptions.ConfigValue -eq $false) {
                                $destinationConnection.Configuration.ShowAdvancedOptions.ConfigValue = $true
                                $changeback = $true
                            }
    
                            $destinationConnection.Configuration.IsSqlClrEnabled.ConfigValue = $true
    
                            if ($changeback -eq $true) {
                                $destinationConnection.Configuration.ShowAdvancedOptions.ConfigValue = $false
                            }
                            $destinationConnection.Configuration.Alter()
                        }
                    } else {
                        Write-Message -Level Verbose -Message "SQL CLR configuration option is already enabled at the destination."
                    }
                    if ($Pscmdlet.ShouldProcess($destinstance, "Create destination SSISDB Catalog")) {
                        if (!$CreateCatalogPassword) {
                            $message = "The destination SSISDB catalog does not exist, would you like to create one?"
                            $yes = New-Object System.Management.Automation.Host.ChoiceDescription "&Yes", "Create an SSISDB catalog on $destinstance."
                            $no = New-Object System.Management.Automation.Host.ChoiceDescription "&No", "Exit."
                            $options = [System.Management.Automation.Host.ChoiceDescription[]]($yes, $no)
                            $result = $host.ui.PromptForChoice($null, $message, $options, 0)
                            switch ($result) {
                                0 {
                                    New-SSISDBCatalog
                                }
                                1 {
                                    return
                                }
                            }
                        } else {
                            New-SSISDBCatalog -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
                        # here to avoid an empty catch
                        $null = 1
                    }
                    $destinationFolders.Refresh()
                }
    
                if ($folder) {
                    $sourceFolders = $sourceFolders | Where-Object {
                        $_.Name -eq $folder
                    }
                    if (!$sourceFolders) {
                        throw "The source folder $folder does not exist in the source Integration Services catalog."
                    }
                }
                if ($project) {
                    $folderDeploy = $sourceFolders | Where-Object {
                        $_.Projects.Name -eq $project
                    }
                    if (!$folderDeploy) {
                        throw "The project $project cannot be found in the source Integration Services catalog."
                    } else {
                        foreach ($f in $folderDeploy) {
                            if ($Pscmdlet.ShouldProcess($destinstance, "Deploying project $project from folder $($f.Name)")) {
                                try {
                                    Invoke-ProjectDeployment -Folder $f.Name -Project $project
                                } catch {
                                    Stop-Function -Message "Issue deploying project" -Target $project -ErrorRecord $_
                                }
                            }
                        }
                    }
                } else {
                    foreach ($curFolder in $sourceFolders) {
                        foreach ($proj in $curFolder.Projects) {
                            if ($Pscmdlet.ShouldProcess($destinstance, "Deploying project $($proj.Name) from folder $($curFolder.Name)")) {
                                try {
                                    Invoke-ProjectDeployment -Project $proj.Name -Folder $curFolder.Name
                                } catch {
                                    Stop-Function -Message "Issue deploying project" -Target $proj -ErrorRecord $_
                                }
                            }
                        }
                    }
                }
    
                if ($environment) {
                    $folderDeploy = $sourceFolders | Where-Object {
                        $_.Environments.Name -eq $environment
                    }
                    if (!$folderDeploy) {
                        throw "The environment $environment cannot be found in the source Integration Services catalog."
                    } else {
                        foreach ($f in $folderDeploy) {
                            if ($destinationFolders[$f.Name].Environments.Name -notcontains $environment) {
                                if ($Pscmdlet.ShouldProcess($destinstance, "Deploying environment $environment from folder $($f.Name)")) {
                                    try {
                                        New-FolderEnvironment -Folder $f.Name -Environment $environment
                                    } catch {
                                        Stop-Function -Message "Issue deploying environment" -Target $environment -ErrorRecord $_
                                    }
                                }
                            } else {
                                if (!$force) {
                                    Write-Message -Level Warning -Message "Integration services catalog environment $environment exists in folder $($f.Name) at destination. Use -Force to drop and recreate."
                                } else {
                                    If ($Pscmdlet.ShouldProcess($destinstance, "Dropping existing environment $environment and deploying environment $environment from folder $($f.Name)")) {
                                        try {
                                            New-FolderEnvironment -Folder $f.Name -Environment $environment -Force
                                        } catch {
                                            Stop-Function -Message "Issue dropping existing environment" -Target $environment -ErrorRecord $_
                                        }
                                    }
                                }
                            }
                        }
                    }
                } else {
                    foreach ($curFolder in $sourceFolders) {
                        foreach ($env in $curFolder.Environments) {
                            if ($destinationFolders[$curFolder.Name].Environments.Name -notcontains $env.Name) {
                                if ($Pscmdlet.ShouldProcess($destinstance, "Deploying environment $($env.Name) from folder $($curFolder.Name)")) {
                                    try {
                                        New-FolderEnvironment -Environment $env.Name -Folder $curFolder.Name
                                    } catch {
                                        Stop-Function -Message "Issue deploying environment" -Target $env -ErrorRecord $_
                                    }
                                }
                            } else {
                                if (!$force) {
                                    Write-Message -Level Warning -Message "Integration services catalog environment $($env.Name) exists in folder $($curFolder.Name) at destination. Use -Force to drop and recreate."
                                    continue
                                } else {
                                    if ($Pscmdlet.ShouldProcess($destinstance, "Deploying environment $($env.Name) from folder $($curFolder.Name)")) {
                                        try {
                                            New-FolderEnvironment -Environment $env.Name -Folder $curFolder.Name -Force
                                        } catch {
                                            Stop-Function -Message "Issue deploying environment" -Target $env -ErrorRecord $_
                                        }
                                    }
                                }
                            }
                        }
                    }
                }
            }
        }
        end {
            Test-DbaDeprecation -DeprecatedOn "1.0.0" -EnableException:$false -Alias Copy-SqlSsisCatalog
        }
    }
    function Copy-DbaSysDbUserObject {
        <#
        .SYNOPSIS
            Imports all user objects found in source SQL Server's master, msdb and model databases to the destination.
    
        .DESCRIPTION
            Imports all user objects found in source SQL Server's master, msdb and model databases to the destination. This is useful because many DBAs store backup/maintenance procs/tables/triggers/etc (among other things) in master or msdb.
    
            It is also useful for migrating objects within the model database.
    
        .PARAMETER Source
            Source SQL Server. You must have sysadmin access and server version must be SQL Server version 2000 or higher.
    
        .PARAMETER SourceSqlCredential
            Login to the target instance using alternative credentials. Windows and SQL Authentication supported. Accepts credential objects (Get-Credential)
    
        .PARAMETER Destination
            Destination SQL Server. You must have sysadmin access and the server must be SQL Server 2000 or higher.
    
        .PARAMETER DestinationSqlCredential
            Login to the target instance using alternative credentials. Windows and SQL Authentication supported. Accepts credential objects (Get-Credential)
    
        .PARAMETER Classic
            Perform the migration the old way
    
        .PARAMETER Force
            Drop destination objects first. Has no effect if you use Classic. This doesn't work really well, honestly.
    
        .PARAMETER WhatIf
            If this switch is enabled, no actions are performed but informational messages will be displayed that explain what would happen if the command were to run.
    
        .PARAMETER Confirm
            If this switch is enabled, you will be prompted for confirmation before executing any operations that change state.
    
        .PARAMETER EnableException
            By default, when something goes wrong we try to catch it, interpret it and give you a friendly warning message.
            This avoids overwhelming you with "sea of red" exceptions, but is inconvenient because it basically disables advanced scripting.
            Using this switch turns this "nice by default" feature off and enables you to catch exceptions with your own try/catch.
    
        .NOTES
            Tags: Migration, SystemDatabase, UserObject
            Author: Chrissy LeMaire (@cl), netnerds.net
    
            Website: https://dbatools.io
            Copyright: (c) 2018 by dbatools, licensed under MIT
            License: MIT https://opensource.org/licenses/MIT
    
        .LINK
            https://dbatools.io/Copy-DbaSysDbUserObject
    
        .EXAMPLE
            PS C:\> Copy-DbaSysDbUserObject -Source $sourceServer -Destination $destserver
    
            Copies user objects from source to destination
    
        #>
        [CmdletBinding(SupportsShouldProcess = $true)]
        param (
            [Parameter(Mandatory)]
            [ValidateNotNullOrEmpty()]
            [DbaInstanceParameter]$Source,
            [PSCredential]$SourceSqlCredential,
            [Parameter(Mandatory)]
            [ValidateNotNullOrEmpty()]
            [DbaInstanceParameter[]]$Destination,
            [PSCredential]$DestinationSqlCredential,
            [switch]$Force,
            [switch]$Classic,
            [Alias('Silent')]
            [switch]$EnableException
        )
        begin {
            function get-sqltypename ($type) {
                switch ($type) {
                    "VIEW" { "view" }
                    "SQL_TABLE_VALUED_FUNCTION" { "User table valued fsunction" }
                    "DEFAULT_CONSTRAINT" { "User default constraint" }
                    "SQL_STORED_PROCEDURE" { "User stored procedure" }
                    "RULE" { "User rule" }
                    "SQL_INLINE_TABLE_VALUED_FUNCTION" { "User inline table valued function" }
                    "SQL_TRIGGER" { "User server trigger" }
                    "SQL_SCALAR_FUNCTION" { "User scalar function" }
                    default { $type }
                }
            }
        }
        process {
            try {
                $sourceServer = Connect-SqlInstance -SqlInstance $Source -SqlCredential $SourceSqlCredential
            } catch {
                Stop-Function -Message "Failure" -Category ConnectionError -ErrorRecord $_ -Target $Source
                return
            }
    
            if (!(Test-SqlSa -SqlInstance $sourceServer -SqlCredential $SourceSqlCredential)) {
                Stop-Function -Message "Not a sysadmin on $source. Quitting."
                return
            }
    
            if (Test-FunctionInterrupt) { return }
            foreach ($destinstance in $Destination) {
                try {
                    $destServer = Connect-SqlInstance -SqlInstance $destinstance -SqlCredential $DestinationSqlCredential
                } catch {
                    Stop-Function -Message "Failure" -Category ConnectionError -ErrorRecord $_ -Target $destinstance -Continue
                }
    
                if (!(Test-SqlSa -SqlInstance $destServer -SqlCredential $DestinationSqlCredential)) {
                    Stop-Function -Message "Not a sysadmin on $destinstance" -Continue
                }
    
                $systemDbs = "master", "model", "msdb"
    
                if (-not $Classic) {
                    foreach ($systemDb in $systemDbs) {
                        $smodb = $sourceServer.databases[$systemDb]
                        $destdb = $destserver.databases[$systemDb]
    
                        $tables = $smodb.Tables | Where-Object IsSystemObject -ne $true
                        $schemas = $smodb.Schemas | Where-Object IsSystemObject -ne $true
    
                        foreach ($schema in $schemas) {
                            $copyobject = [pscustomobject]@{
                                SourceServer      = $sourceServer.Name
                                DestinationServer = $destServer.Name
                                Name              = $schema
                                Type              = "User schema in $systemDb"
                                Status            = $null
                                Notes             = $null
                                DateTime          = [Sqlcollaborative.Dbatools.Utility.DbaDateTime](Get-Date)
                            }
    
                            $destschema = $destdb.Schemas | Where-Object Name -eq $schema.Name
                            $schmadoit = $true
    
                            if ($destschema) {
                                if (-not $force) {
                                    $copyobject.Status = "Skipped"
                                    $copyobject.Notes = "$schema exists on destination"
                                    $schmadoit = $false
                                } else {
                                    if ($PSCmdlet.ShouldProcess($destServer, "Dropping schema $schema in $systemDb")) {
                                        try {
                                            Write-Message -Level Verbose -Message "Force specified. Dropping $schema in $destdb on $destinstance"
                                            $destschema.Drop()
                                        } catch {
                                            $schmadoit = $false
                                            $copyobject.Status = "Failed"
                                            $copyobject.Notes = $_.Exception.InnerException.InnerException.InnerException.Message
                                        }
                                    }
                                }
                            }
    
                            if ($schmadoit) {
                                $transfer = New-Object Microsoft.SqlServer.Management.Smo.Transfer $smodb
                                $null = $transfer.CopyAllObjects = $false
                                $null = $transfer.Options.WithDependencies = $true
                                $null = $transfer.ObjectList.Add($schema)
                                $sql = $transfer.ScriptTransfer()
                                if ($PSCmdlet.ShouldProcess($destServer, "Attempting to add schema $($schema.Name) to $systemDb")) {
                                    try {
                                        Write-Message -Level Debug -Message "$sql"
                                        $null = $destServer.Query($sql, $systemDb)
                                        $copyobject.Status = "Successful"
                                        $copyobject.Notes = "May have also created dependencies"
                                    } catch {
                                        $copyobject.Status = "Failed"
                                        $copyobject.Notes = (Get-ErrorMessage -Record $_)
                                    }
                                }
                            }
    
                            $copyobject | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
                        }
    
                        foreach ($table in $tables) {
                            $copyobject = [pscustomobject]@{
                                SourceServer      = $sourceServer.Name
                                DestinationServer = $destServer.Name
                                Name              = $table
                                Type              = "User table in $systemDb"
                                Status            = $null
                                Notes             = $null
                                DateTime          = [Sqlcollaborative.Dbatools.Utility.DbaDateTime](Get-Date)
                            }
    
                            $desttable = $destdb.Tables.Item($table.Name, $table.Schema)
                            $doit = $true
    
                            if ($desttable) {
                                if (-not $force) {
                                    $copyobject.Status = "Skipped"
                                    $copyobject.Notes = "$table exists on destination"
                                    $doit = $false
                                } else {
                                    if ($PSCmdlet.ShouldProcess($destServer, "Dropping table $table in $systemDb")) {
                                        try {
                                            Write-Message -Level Verbose -Message "Force specified. Dropping $table in $destdb on $destinstance"
                                            $desttable.Drop()
                                        } catch {
                                            $doit = $false
                                            $copyobject.Status = "Failed"
                                            $copyobject.Notes = $_.Exception.InnerException.InnerException.InnerException.Message
                                        }
                                    }
                                }
                            }
    
                            if ($doit) {
                                $transfer = New-Object Microsoft.SqlServer.Management.Smo.Transfer $smodb
                                $null = $transfer.CopyAllObjects = $false
                                $null = $transfer.Options.WithDependencies = $true
                                $null = $transfer.ObjectList.Add($table)
                                $sql = $transfer.ScriptTransfer()
                                if ($PSCmdlet.ShouldProcess($destServer, "Attempting to add table $table to $systemDb")) {
                                    try {
                                        Write-Message -Level Debug -Message "$sql"
                                        $null = $destServer.Query($sql, $systemDb)
                                        $copyobject.Status = "Successful"
                                        $copyobject.Notes = "May have also created dependencies"
                                    } catch {
                                        $copyobject.Status = "Failed"
                                        $copyobject.Notes = (Get-ErrorMessage -Record $_)
                                    }
                                }
                            }
                            $copyobject | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
                        }
    
                        $userobjects = Get-DbaModule -SqlInstance $sourceserver -Database $systemDb -ExcludeSystemObjects | Sort-Object Type
                        Write-Message -Level Verbose -Message "Copying from $systemDb"
                        foreach ($userobject in $userobjects) {
    
                            $name = "[$($userobject.SchemaName)].[$($userobject.Name)]"
                            $db = $userobject.Database
                            $type = get-sqltypename $userobject.Type
                            $sql = $userobject.Definition
                            $schema = $userobject.SchemaName
    
                            $copyobject = [pscustomobject]@{
                                SourceServer      = $sourceServer.Name
                                DestinationServer = $destServer.Name
                                Name              = $name
                                Type              = "$type in $systemDb"
                                Status            = $null
                                Notes             = $null
                                DateTime          = [Sqlcollaborative.Dbatools.Utility.DbaDateTime](Get-Date)
                            }
                            Write-Message -Level Debug -Message $sql
                            try {
                                Write-Message -Level Verbose -Message "Searching for $name in $db on $destinstance"
                                $result = Get-DbaModule -SqlInstance $destServer -ExcludeSystemObjects -Database $db |
                                    Where-Object { $psitem.Name -eq $userobject.Name -and $psitem.Type -eq $userobject.Type }
                                if ($result) {
                                    Write-Message -Level Verbose -Message "Found $name in $db on $destinstance"
                                    if (-not $Force) {
                                        $copyobject.Status = "Skipped"
                                        $copyobject.Notes = "$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
                                        # here to avoid an empty catch
                                        $null = 1
                                    }
                                }
                            }
                        } catch {
                            # Don't care - long story having to do with duplicate stuff
                            # here to avoid an empty catch
                            $null = 1
                        }
                    }
                }
            }
        }
        end {
            Test-DbaDeprecation -DeprecatedOn "1.0.0" -EnableException:$false -Alias Copy-SqlSysDbUserObjects
        }
    }
    #ValidationTags#Messaging,FlowControl,Pipeline,CodeStyle#
    function Copy-DbaXESessionTemplate {
        <#
        .SYNOPSIS
            Copies non-Microsoft templates from the dbatools template repository (\bin\xetemplates\) to $home\Documents\SQL Server Management Studio\Templates\XEventTemplates.
    
        .DESCRIPTION
            Copies non-Microsoft templates from the dbatools template repository (\bin\xetemplates\) to $home\Documents\SQL Server Management Studio\Templates\XEventTemplates.
    
            Useful for when you want to use the SSMS GUI.
    
        .PARAMETER Path
            The path to the template directory. Defaults to the dbatools template repository (\bin\xetemplates\).
    
        .PARAMETER Destination
            Path to the Destination directory, defaults to $home\Documents\SQL Server Management Studio\Templates\XEventTemplates.
    
        .PARAMETER EnableException
            By default, when something goes wrong we try to catch it, interpret it and give you a friendly warning message.
            This avoids overwhelming you with "sea of red" exceptions, but is inconvenient because it basically disables advanced scripting.
            Using this switch turns this "nice by default" feature off and enables you to catch exceptions with your own try/catch.
    
        .NOTES
            Tags: ExtendedEvent, XE, XEvent
            Author: Chrissy LeMaire (@cl), netnerds.net
    
            Website: https://dbatools.io
            Copyright: (c) 2018 by dbatools, licensed under MIT
            License: MIT https://opensource.org/licenses/MIT
    
        .LINK
            https://dbatools.io/Copy-DbaXESessionTemplate
    
        .EXAMPLE
            PS C:\> Copy-DbaXESessionTemplate
    
            Copies non-Microsoft templates from the dbatools template repository (\bin\xetemplates\) to $home\Documents\SQL Server Management Studio\Templates\XEventTemplates.
    
        .EXAMPLE
            PS C:\> Copy-DbaXESessionTemplate -Path C:\temp\xetemplates
    
            Copies your templates from C:\temp\xetemplates to $home\Documents\SQL Server Management Studio\Templates\XEventTemplates.
    
        #>
        [CmdletBinding()]
        param (
            [string[]]$Path = "$script:PSModuleRoot\bin\xetemplates",
            [string]$Destination = "$home\Documents\SQL Server Management Studio\Templates\XEventTemplates",
            [switch]$EnableException
        )
        process {
            if (Test-FunctionInterrupt) { return }
            foreach ($destinstance in $Destination) {
                if (-not (Test-Path -Path $destinstance)) {
                    try {
                        $null = New-Item -ItemType Directory -Path $destinstance -ErrorAction Stop
                    } catch {
                        Stop-Function -Message "Failure" -ErrorRecord $_ -Target $destinstance
                    }
                }
                try {
                    $files = (Get-DbaXESessionTemplate -Path $Path | Where-Object Source -ne Microsoft).Path
                    foreach ($file in $files) {
                        Write-Message -Level Output -Message "Copying $($file.Name) to $destinstance."
                        Copy-Item -Path $file -Destination $destinstance -ErrorAction Stop
                    }
                } catch {
                    Stop-Function -Message "Failure" -ErrorRecord $_ -Target $path
                }
            }
        }
    }
    #ValidationTags#Messaging,FlowControl,Pipeline,CodeStyle#
    function Disable-DbaAgHadr {
        <#
        .SYNOPSIS
            Disables the Hadr service setting on the specified SQL Server.
    
        .DESCRIPTION
            In order to build an AG a cluster has to be built and then the Hadr enabled for the SQL Server
            service. This function disables that feature for the SQL Server service.
    
        .PARAMETER SqlInstance
            The target SQL Server instance or instances.
    
        .PARAMETER Credential
            Credential object used to connect to the Windows server as a different user
    
        .PARAMETER WhatIf
            Shows what would happen if the command were to run. No actions are actually performed.
    
        .PARAMETER Confirm
            Prompts you for confirmation before executing any changing operations within the command.
    
        .PARAMETER Force
            Will restart SQL Server and SQL Server Agent service to apply the change.
    
        .PARAMETER EnableException
            By default, when something goes wrong we try to catch it, interpret it and give you a friendly warning message.
            This avoids overwhelming you with "sea of red" exceptions, but is inconvenient because it basically disables advanced scripting.
            Using this switch turns this "nice by default" feature off and enables you to catch exceptions with your own try/catch.
    
        .NOTES
            Tags: Hadr, AG, AvailabilityGroup
            Author: Shawn Melton (@wsmelton), http://wsmelton.github.io
    
            Website: https://dbatools.io
            Copyright: (c) 2018 by dbatools, licensed under MIT
            License: MIT https://opensource.org/licenses/MIT
    
        .LINK
            https://dbatools.io/Disable-DbaAgHadr
    
        .EXAMPLE
            PS C:\> Disable-DbaAgHadr -SqlInstance sql2016
    
            Sets Hadr service to disabled for the instance sql2016 but changes will not be applied until the next time the server restarts.
    
        .EXAMPLE
            PS C:\> Disable-DbaAgHadr -SqlInstance sql2016 -Force
    
            Sets Hadr service to disabled for the instance sql2016, and restart the service to apply the change.
    
        .EXAMPLE
            PS C:\> Disable-DbaAgHadr -SqlInstance sql2012\dev1 -Force
    
            Sets Hadr service to disabled for the instance dev1 on sq2012, and restart the service to apply the change.
    
        #>
        [CmdletBinding(SupportsShouldProcess = $true, ConfirmImpact = "High")]
        param (
            [parameter(Mandatory, ValueFromPipeline)]
            [DbaInstanceParameter[]]$SqlInstance,
            [PSCredential]$Credential,
            [switch]$Force,
            [switch]$EnableException
        )
        process {
            foreach ($instance in $SqlInstance) {
                $computer = $computerFullName = $instance.ComputerName
                $instanceName = $instance.InstanceName
                if (-not (Test-ElevationRequirement -ComputerName $instance)) {
                    return
                }
                $noChange = $false
    
                <#
                #Variable marked as unused by PSScriptAnalyzer
                switch ($instance.InstanceName) {
                    'MSSQLSERVER' { $agentName = 'SQLSERVERAGENT' }
                    default { $agentName = "SQLAgent`$$instanceName" }
                }
                #>
    
                try {
                    Write-Message -Level Verbose -Message "Checking current Hadr setting for $computer"
                    $currentState = Get-WmiHadr -SqlInstance $instance -Credential $Credential
                } catch {
                    Stop-Function -Message "Failure to pull current state of Hadr setting on $computer" -Category ConnectionError -ErrorRecord $_ -Target $instance -Continue
                }
    
                $isHadrEnabled = $currentState.IsHadrEnabled
                Write-Message -Level InternalComment -Message "$instance Hadr current value: $isHadrEnabled"
    
                # hadr results from sql wmi can be iffy, skip the check
                <#
                if (-not $isHadrEnabled) {
                    Write-Message -Level Warning -Message "Hadr is already disabled for instance: $($instance.FullName)"
                    $noChange = $true
                    continue
                }
                #>
    
                $scriptblock = {
                    $instance = $args[0]
                    $sqlService = $wmi.Services | Where-Object DisplayName -eq "SQL Server ($instance)"
                    $sqlService.ChangeHadrServiceSetting(0)
                }
    
                if ($noChange -eq $false) {
                    if ($PSCmdlet.ShouldProcess($instance, "Changing Hadr from $isHadrEnabled to 0 for $instance")) {
                        try {
                            Invoke-ManagedComputerCommand -ComputerName $computerFullName -Credential $Credential -ScriptBlock $scriptblock -ArgumentList $instancename
                        } catch {
                            Stop-Function -Continue -Message "Failure on $($instance.FullName) | This may be because AlwaysOn Availability Groups feature requires the x86(non-WOW) or x64 Enterprise Edition of SQL Server 2012 (or later version) running on Windows Server 2008 (or later version) with WSFC hotfix KB 2494036 installed."
                        }
                    }
                    if (Test-Bound 'Force') {
                        if ($PSCmdlet.ShouldProcess($instance, "Force provided, restarting Engine and Agent service for $instance on $computerFullName")) {
                            try {
                                $null = Stop-DbaService -ComputerName $computerFullName -InstanceName $instanceName -Type Agent, Engine
                                $null = Start-DbaService -ComputerName $computerFullName -InstanceName $instanceName -Type Agent, Engine
                            } catch {
                                Stop-Function -Message "Issue restarting $instance" -Target $instance -Continue
                            }
                        }
                    }
                    $newState = Get-WmiHadr -SqlInstance $instance -Credential $Credential
    
                    if (Test-Bound -Not -ParameterName Force) {
                        Write-Message -Level Warning -Message "You must restart the SQL Server for it to take effect."
                    }
    
                    [PSCustomObject]@{
                        ComputerName  = $newState.ComputerName
                        InstanceName  = $newState.InstanceName
                        SqlInstance   = $newState.SqlInstance
                        IsHadrEnabled = $false
                    }
                }
            }
        }
    }
    #ValidationTags#CodeStyle,Messaging,FlowControl,Pipeline#
    function Disable-DbaFilestream {
        <#
        .SYNOPSIS
            Sets the status of FileStream on specified SQL Server instances both at the server level and the instance level
    
        .DESCRIPTION
            Connects to the specified SQL Server instances, and sets the status of the FileStream feature to the required value
    
            To perform the action, the SQL Server instance must be restarted. By default we will prompt for confirmation for this action, this can be overridden with the -Force switch
    
        .PARAMETER SqlInstance
            The target SQL Server instance or instances. Defaults to localhost.
    
        .PARAMETER SqlCredential
            Login to the target instance using alternative credentials. Windows and SQL Authentication supported. Accepts credential objects (Get-Credential)
    
        .PARAMETER Credential
            Login to the target server using alternative credentials.
    
        .PARAMETER EnableException
            By default, when something goes wrong we try to catch it, interpret it and give you a friendly warning message.
            This avoids overwhelming you with "sea of red" exceptions, but is inconvenient because it basically disables advanced scripting.
            Using this switch turns this "nice by default" feature off and enables you to catch exceptions with your own try/catch.
    
        .PARAMETER Force
            Restart SQL Instance after changes. Use this parameter with care as it overrides whatif.
    
        .PARAMETER WhatIf
            Shows what would happen if the command runs. The command is not run unless Force is specified.
    
        .PARAMETER Confirm
            Prompts you for confirmation before running the command.
    
        .NOTES
            Tags: Filestream
            Author: Stuart Moore ( @napalmgram ) | Chrissy LeMaire ( @cl )
            Website: https://dbatools.io
            Copyright: (c) 2018 by dbatools, licensed under MIT
            License: MIT https://opensource.org/licenses/MIT
    
        .EXAMPLE
            PS C:\> Disable-DbaFilestream -SqlInstance server1\instance2
    
            Prompts for confirmation. Disables filestream on the service and instance levels.
    
        .EXAMPLE
            PS C:\> Disable-DbaFilestream -SqlInstance server1\instance2 -Confirm:$false
    
            Does not prompt for confirmation. Disables filestream on the service and instance levels.
    
        .EXAMPLE
            PS C:\> Get-DbaFilestream -SqlInstance server1\instance2, server5\instance5, prod\hr | Where-Object InstanceAccessLevel -gt 0 | Disable-DbaFilestream -Force
    
            Using this pipeline you can scan a range of SQL instances and disable filestream on only those on which it's enabled.
    
           #>
        [CmdletBinding(SupportsShouldProcess, ConfirmImpact = "High")]
        param (
            [Parameter(Mandatory, ValueFromPipelineByPropertyName)]
            [DbaInstance[]]$SqlInstance,
            [Parameter(ValueFromPipelineByPropertyName)]
            [PSCredential]$SqlCredential,
            [Parameter(ValueFromPipelineByPropertyName)]
            [PSCredential]$Credential,
            [switch]$Force,
            [switch]$EnableException
        )
        begin {
            $FileStreamLevel = $level = 0
    
            $OutputLookup = @{
                0 = 'Disabled'
                1 = 'FileStream enabled for T-Sql access'
                2 = 'FileStream enabled for T-Sql and IO streaming access'
                3 = 'FileStream enabled for T-Sql, IO streaming, and remote clients'
            }
        }
        process {
            foreach ($instance in $SqlInstance) {
                try {
                    $server = Connect-SqlInstance -SqlInstance $instance -SqlCredential $SqlCredential
                } catch {
                    Stop-Function -Message "Failure connecting to $computer" -Category ConnectionError -ErrorRecord $_ -Target $instance -Continue
                }
    
                # Instance level
                $filestreamstate = [int]$server.Configuration.FilestreamAccessLevel.RunningValue
    
                if ($Force -or $PSCmdlet.ShouldProcess($instance, "Changing from '$($OutputLookup[$filestreamstate])' to '$($OutputLookup[$level])' at the instance level")) {
                    try {
                        $null = Set-DbaSpConfigure -SqlInstance $server -Name FilestreamAccessLevel -Value $level -EnableException
                    } catch {
                        Stop-Function -Message "Failure" -ErrorRecord $_ -Continue
                    }
    
    
                    # Server level
                    if ($server.IsClustered) {
                        $nodes = Get-DbaWsfcNode -ComputerName $instance -Credential $Credential
                        foreach ($node in $nodes.Name) {
                            $result = Set-FileSystemSetting -Instance $node -Credential $Credential -FilestreamLevel $FileStreamLevel
                        }
                    } else {
                        $result = Set-FileSystemSetting -Instance $instance -Credential $Credential -FilestreamLevel $FileStreamLevel
                    }
    
                    if ($Force) {
                        #$restart replaced with $null as it was identified as a unused variable
                        $null = Restart-DbaService -ComputerName $instance.ComputerName -InstanceName $server.ServiceName -Type Engine -Force
                    }
    
                    Get-DbaFilestream -SqlInstance $instance -SqlCredential $SqlCredential -Credential $Credential
    
                    if ($filestreamstate -ne $level -and -not $Force) {
                        Write-Message -Level Warning -Message "[$instance] $result"
                    }
                }
            }
        }
    }
    #ValidationTags#Messaging,FlowControl,Pipeline,CodeStyle#
    
    function Disable-DbaForceNetworkEncryption {
        <#
        .SYNOPSIS
            Disables Force Encryption for a SQL Server instance
    
        .DESCRIPTION
            Disables Force Encryption for a SQL Server instance. Note that this requires access to the Windows Server, not the SQL instance itself.
    
            This setting is found in Configuration Manager.
    
        .PARAMETER SqlInstance
            The target SQL Server instance or instances. Defaults to localhost.
    
        .PARAMETER Credential
            Allows you to login to the computer (not SQL Server instance) using alternative Windows credentials.
    
        .PARAMETER WhatIf
            If this switch is enabled, no actions are performed but informational messages will be displayed that explain what would happen if the command were to run.
    
        .PARAMETER Confirm
            If this switch is enabled, you will be prompted for confirmation before executing any operations that change state.
    
        .PARAMETER EnableException
            By default, when something goes wrong we try to catch it, interpret it and give you a friendly warning message.
            This avoids overwhelming you with "sea of red" exceptions, but is inconvenient because it basically disables advanced scripting.
            Using this switch turns this "nice by default" feature off and enables you to catch exceptions with your own try/catch.
    
        .NOTES
            Tags: Certificate
            Author: Chrissy LeMaire (@cl), netnerds.net
    
            Website: https://dbatools.io
            Copyright: (c) 2018 by dbatools, licensed under MIT
            License: MIT https://opensource.org/licenses/MIT
    
        .EXAMPLE
            PS C:\> Disable-DbaForceNetworkEncryption
    
            Disables Force Encryption on the default (MSSQLSERVER) instance on localhost - requires (and checks for) RunAs admin.
    
        .EXAMPLE
            PS C:\> Disable-DbaForceNetworkEncryption -SqlInstance sql01\SQL2008R2SP2
    
            Disables Force Network Encryption for the SQL2008R2SP2 on sql01. Uses Windows Credentials to both login and modify the registry.
    
        .EXAMPLE
            PS C:\> Disable-DbaForceNetworkEncryption -SqlInstance sql01\SQL2008R2SP2 -WhatIf
    
            Shows what would happen if the command were executed.
    
        #>
        [CmdletBinding(SupportsShouldProcess, ConfirmImpact = "Low")]
        param (
            [Parameter(ValueFromPipeline)]
            [Alias("ServerInstance", "SqlServer", "ComputerName")]
            [DbaInstanceParameter[]]$SqlInstance = $env:COMPUTERNAME,
            [PSCredential]$Credential,
            [Alias('Silent')]
            [switch]$EnableException
        )
        process {
    
            foreach ($instance in $sqlinstance) {
                Write-Message -Level VeryVerbose -Message "Processing $instance." -Target $instance
                $null = Test-ElevationRequirement -ComputerName $instance -Continue
    
                Write-Message -Level Verbose -Message "Resolving hostname."
                $resolved = $null
                $resolved = Resolve-DbaNetworkName -ComputerName $instance -Turbo
    
                if ($null -eq $resolved) {
                    Stop-Function -Message "Can't resolve $instance." -Target $instance -Continue -Category InvalidArgument
                }
    
                try {
                    $sqlwmi = Invoke-ManagedComputerCommand -ComputerName $resolved.FullComputerName -ScriptBlock { $wmi.Services } -Credential $Credential -ErrorAction Stop | Where-Object DisplayName -eq "SQL Server ($($instance.InstanceName))"
                } catch {
                    Stop-Function -Message "Failed to access $instance." -Target $instance -Continue -ErrorRecord $_
                }
    
                $regroot = ($sqlwmi.AdvancedProperties | Where-Object Name -eq REGROOT).Value
                $vsname = ($sqlwmi.AdvancedProperties | Where-Object Name -eq VSNAME).Value
                try {
                    $instancename = $sqlwmi.DisplayName.Replace('SQL Server (', '').Replace(')', '') # Don't clown, I don't know regex :(
                } catch {
                    # Probably because the instance name has been aliased or does not exist or something
                    # here to avoid an empty catch
                    $null = 1
                }
                $serviceaccount = $sqlwmi.ServiceAccount
    
                if ([System.String]::IsNullOrEmpty($regroot)) {
                    $regroot = $sqlwmi.AdvancedProperties | Where-Object { $_ -match 'REGROOT' }
                    $vsname = $sqlwmi.AdvancedProperties | Where-Object { $_ -match 'VSNAME' }
    
                    if (![System.String]::IsNullOrEmpty($regroot)) {
                        $regroot = ($regroot -Split 'Value\=')[1]
                        $vsname = ($vsname -Split 'Value\=')[1]
                    } else {
                        Stop-Function -Message "Can't find instance $vsname on $instance." -Continue -Category ObjectNotFound -Target $instance
                    }
                }
    
                if ([System.String]::IsNullOrEmpty($vsname)) { $vsname = $instance }
    
                Write-Message -Level Output -Message "Regroot: $regroot" -Target $instance
                Write-Message -Level Output -Message "ServiceAcct: $serviceaccount" -Target $instance
                Write-Message -Level Output -Message "InstanceName: $instancename" -Target $instance
                Write-Message -Level Output -Message "VSNAME: $vsname" -Target $instance
    
                $scriptblock = {
                    $regpath = "Registry::HKEY_LOCAL_MACHINE\$($args[0])\MSSQLServer\SuperSocketNetLib"
                    $cert = (Get-ItemProperty -Path $regpath -Name Certificate).Certificate
                    #Variable marked as unused by PSScriptAnalyzer
                    #$oldvalue = (Get-ItemProperty -Path $regpath -Name ForceEncryption).ForceEncryption
                    Set-ItemProperty -Path $regpath -Name ForceEncryption -Value $false
                    $forceencryption = (Get-ItemProperty -Path $regpath -Name ForceEncryption).ForceEncryption
    
                    [pscustomobject]@{
                        ComputerName          = $env:COMPUTERNAME
                        InstanceName          = $args[2]
                        SqlInstance           = $args[1]
                        ForceEncryption       = ($forceencryption -eq $true)
                        CertificateThumbprint = $cert
                    }
                }
    
                if ($PScmdlet.ShouldProcess("local", "Connecting to $instance to modify the ForceEncryption value in $regroot for $($instance.InstanceName)")) {
                    try {
                        Invoke-Command2 -ComputerName $resolved.FullComputerName -Credential $Credential -ArgumentList $regroot, $vsname, $instancename -ScriptBlock $scriptblock -ErrorAction Stop
                        Write-Message -Level Critical -Message "Force encryption was successfully set on $($resolved.FullComputerName) for the $instancename instance. You must now restart the SQL Server for changes to take effect." -Target $instance
                    } catch {
                        Stop-Function -Message "Failed to connect to $($resolved.FullComputerName) using PowerShell remoting!" -ErrorRecord $_ -Target $instance -Continue
                    }
                }
            }
        }
    }
    function Disable-DbaTraceFlag {
        <#
        .SYNOPSIS
            Disable a Global Trace Flag that is currently running
    
        .DESCRIPTION
            The function will disable a Trace Flag that is currently running globally on the SQL Server instance(s) listed
    
        .PARAMETER SqlInstance
            The target SQL Server instance or instances.
    
        .PARAMETER SqlCredential
            Login to the target instance using alternative credentials. Windows and SQL Authentication supported. Accepts credential objects (Get-Credential)
    
        .PARAMETER TraceFlag
            Trace flag number to enable globally
    
        .PARAMETER EnableException
            By default, when something goes wrong we try to catch it, interpret it and give you a friendly warning message.
            This avoids overwhelming you with "sea of red" exceptions, but is inconvenient because it basically disables advanced scripting.
            Using this switch turns this "nice by default" feature off and enables you to catch exceptions with your own try/catch.
    
        .NOTES
            Tags: TraceFlag
            Author: Garry Bargsley (@gbargsley), http://blog.garrybargsley.com
    
            Website: https://dbatools.io
            Copyright: (c) 2018 by dbatools, licensed under MIT
            License: MIT https://opensource.org/licenses/MIT
    
        .LINK
            https://dbatools.io/Disable-DbaTraceFlag
    
        .EXAMPLE
            PS C:\> Disable-DbaTraceFlag -SqlInstance sql2016 -TraceFlag 3226
    
            Disable the globally running trace flag 3226 on SQL Server instance sql2016
    
        #>
        [CmdletBinding()]
        param (
            [parameter(Position = 0, Mandatory, ValueFromPipeline)]
            [Alias("ServerInstance", "SqlServer", "SqlServers")]
            [DbaInstanceParameter[]]$SqlInstance,
            [PSCredential]$SqlCredential,
            [parameter(Mandatory)]
            [int[]]$TraceFlag,
            [Alias('Silent')]
            [switch]$EnableException
        )
    
        process {
            foreach ($instance in $SqlInstance) {
    
                try {
                    $server = Connect-SqlInstance -SqlInstance $instance -SqlCredential $SqlCredential
                } catch {
                    Stop-Function -Message "Failure" -Category ConnectionError -ErrorRecord $_ -Target $instance -Continue
                }
    
                $current = Get-DbaTraceFlag -SqlInstance $server -EnableException
    
                foreach ($tf in $TraceFlag) {
                    $TraceFlagInfo = [pscustomobject]@{
                        SourceServer = $server.ComputerName
                        InstanceName = $server.ServiceName
                        SqlInstance  = $server.DomainInstanceName
                        TraceFlag    = $tf
                        Status       = $null
                        Notes        = $null
                        DateTime     = [DbaDateTime](Get-Date)
                    }
                    if ($tf -notin $current.TraceFlag) {
                        $TraceFlagInfo.Status = 'Skipped'
                        $TraceFlagInfo.Notes = "Trace Flag is not running."
                        $TraceFlagInfo
                        Write-Message -Level Warning -Message "Trace Flag $tf is not currently running on $instance"
                        continue
                    }
    
                    try {
                        $query = "DBCC TRACEOFF ($tf, -1)"
                        $server.Query($query)
                    } catch {
                        $TraceFlagInfo.Status = "Failed"
                        $TraceFlagInfo.Notes = $_.Exception.Message
                        $TraceFlagInfo
                        Stop-Function -Message "Failure" -ErrorRecord $_ -Target $server -Continue
                    }
                    $TraceFlagInfo.Status = "Successful"
                    $TraceFlagInfo
                }
            }
        }
    }
    function Dismount-DbaDatabase {
        <#
        .SYNOPSIS
            Detach a SQL Server Database.
    
        .DESCRIPTION
            This command detaches one or more SQL Server databases. If necessary, -Force can be used to break mirrors and remove databases from availability groups prior to detaching.
    
        .PARAMETER SqlInstance
            The target SQL Server instance or instances.
    
        .PARAMETER SqlCredential
            Login to the target instance using alternative credentials. Windows and SQL Authentication supported. Accepts credential objects (Get-Credential)
    
        .PARAMETER Database
            The database(s) to detach.
    
        .PARAMETER FileStructure
            A StringCollection object value that contains a list database files. If FileStructure is not specified, BackupHistory will be used to guess the structure.
    
        .PARAMETER InputObject
            A collection of databases (such as returned by Get-DbaDatabase), to be detached.
    
        .PARAMETER UpdateStatistics
            If this switch is enabled, statistics for the database will be updated prior to detaching it.
    
        .PARAMETER Force
            If this switch is enabled and the database is part of a mirror, the mirror will be broken. If the database is part of an Availability Group, it will be removed from the AG.
    
        .PARAMETER WhatIf
            If this switch is enabled, no actions are performed but informational messages will be displayed that explain what would happen if the command were to run.
    
        .PARAMETER Confirm
            If this switch is enabled, you will be prompted for confirmation before executing any operations that change state.
    
        .PARAMETER EnableException
            By default, when something goes wrong we try to catch it, interpret it and give you a friendly warning message.
            This avoids overwhelming you with "sea of red" exceptions, but is inconvenient because it basically disables advanced scripting.
            Using this switch turns this "nice by default" feature off and enables you to catch exceptions with your own try/catch.
    
        .NOTES
            Tags: Database
            Author: Chrissy LeMaire (@cl), netnerds.net
    
            Website: https://dbatools.io
            Copyright: (c) 2018 by dbatools, licensed under MIT
            License: MIT https://opensource.org/licenses/MIT
    
        .LINK
            https://dbatools.io/Dismount-DbaDatabase
    
        .EXAMPLE
            PS C:\> Detach-DbaDatabase -SqlInstance sql2016b -Database SharePoint_Config, WSS_Logging
    
            Detaches SharePoint_Config and WSS_Logging from sql2016b
    
        .EXAMPLE
            PS C:\> Get-DbaDatabase -SqlInstance sql2016b -Database 'PerformancePoint Service Application_10032db0fa0041df8f913f558a5dc0d4' | Detach-DbaDatabase -Force
    
            Detaches 'PerformancePoint Service Application_10032db0fa0041df8f913f558a5dc0d4' from sql2016b. Since Force was specified, if the database is part of mirror, the mirror will be broken prior to detaching.
    
            If the database is part of an Availability Group, it will first be dropped prior to detachment.
    
        .EXAMPLE
            PS C:\> Get-DbaDatabase -SqlInstance sql2016b -Database WSS_Logging | Detach-DbaDatabase -Force -WhatIf
    
            Shows what would happen if the command were to execute (without actually executing the detach/break/remove commands).
    
        #>
        [CmdletBinding(SupportsShouldProcess, DefaultParameterSetName = "Default")]
        param (
            [parameter(Mandatory, ParameterSetName = 'SqlInstance')]
            [Alias("ServerInstance", "SqlServer")]
            [DbaInstanceParameter[]]$SqlInstance,
            [PSCredential]$SqlCredential,
            [parameter(Mandatory, ParameterSetName = 'SqlInstance')]
            [string[]]$Database,
            [parameter(Mandatory, ParameterSetName = 'Pipeline', ValueFromPipeline)]
            [Microsoft.SqlServer.Management.Smo.Database[]]$InputObject,
            [Switch]$UpdateStatistics,
            [switch]$Force,
            [Alias('Silent')]
            [switch]$EnableException
        )
        process {
            foreach ($instance in $SqlInstance) {
                try {
                    $server = Connect-SqlInstance -SqlInstance $instance -SqlCredential $sqlcredential
                } catch {
                    Stop-Function -Message "Failure" -Category ConnectionError -ErrorRecord $_ -Target $instance -Continue
                }
    
                if ($Database) {
                    $InputObject += $server.Databases | Where-Object Name -in $Database
                } else {
                    $InputObject += $server.Databases
                }
    
                if ($ExcludeDatabase) {
                    $InputObject = $InputObject | Where-Object Name -NotIn $ExcludeDatabase
                }
            }
    
            foreach ($db in $InputObject) {
                $db.Refresh()
                $server = $db.Parent
    
                if ($db.IsSystemObject) {
                    Stop-Function -Message "$db is a system database and cannot be detached using this method." -Target $db -Continue
                }
    
                Write-Message -Level Verbose -Message "Checking replication status."
                if ($db.ReplicationOptions -ne "None") {
                    Stop-Function -Message "Skipping $db  on $server because it is replicated." -Target $db -Continue
                }
    
                # repeat because different servers could be piped in
                $snapshots = (Get-DbaDbSnapshot -SqlInstance $server).SnapshotOf
                Write-Message -Level Verbose -Message "Checking for snaps"
                if ($db.Name -in $snapshots) {
                    Write-Message -Level Warning -Message "Database $db has snapshots, you need to drop them before detaching. Skipping $db on $server."
                    Continue
                }
    
                Write-Message -Level Verbose -Message "Checking mirror status"
                if ($db.IsMirroringEnabled -and !$Force) {
                    Stop-Function -Message "$db on $server is being mirrored. Use -Force to break mirror or use the safer backup/restore method." -Target $db -Continue
                }
    
                Write-Message -Level Verbose -Message "Checking Availability Group status"
    
                if ($db.AvailabilityGroupName -and !$Force) {
                    $ag = $db.AvailabilityGroupName
                    Stop-Function -Message "$db on $server is part of an Availability Group ($ag). Use -Force to drop from $ag availability group to detach. Alternatively, you can use the safer backup/restore method." -Target $db -Continue
                }
    
                $sessions = Get-DbaProcess -SqlInstance $db.Parent -Database $db.Name
    
                if ($sessions -and !$Force) {
                    Stop-Function -Message "$db on $server currently has connected users and cannot be dropped. Use -Force to kill all connections and detach the database." -Target $db -Continue
                }
    
                if ($force) {
    
                    if ($sessions) {
                        If ($Pscmdlet.ShouldProcess($server, "Killing $($sessions.count) sessions which are connected to $db")) {
                            $null = $sessions | Stop-DbaProcess -ErrorAction SilentlyContinue -WarningAction SilentlyContinue
                        }
                    }
    
                    if ($db.IsMirroringEnabled) {
                        If ($Pscmdlet.ShouldProcess($server, "Breaking mirror for $db on $server")) {
                            try {
                                Write-Message -Level Warning -Message "Breaking mirror for $db on $server."
                                $db.ChangeMirroringState([Microsoft.SqlServer.Management.Smo.MirroringOption]::Off)
                                $db.Alter()
                                $db.Refresh()
                            } catch {
                                Stop-Function -Message "Could not break mirror for $db on $server - not detaching." -Target $db -ErrorRecord $_ -Continue
                            }
                        }
                    }
    
                    if ($db.AvailabilityGroupName) {
                        $ag = $db.AvailabilityGroupName
                        If ($Pscmdlet.ShouldProcess($server, "Attempting remove $db on $server from Availability Group $ag")) {
                            try {
                                $server.AvailabilityGroups[$ag].AvailabilityDatabases[$db.name].Drop()
                                Write-Message -Level Verbose -Message "Successfully removed $db from  detach from $ag on $server."
                            } catch {
                                if ($_.Exception.InnerException) {
                                    $exception = $_.Exception.InnerException.ToString() -Split "System.Data.SqlClient.SqlException: "
                                    $exception = " | $(($exception[1] -Split "at Microsoft.SqlServer.Management.Common.ConnectionManager")[0])".TrimEnd()
                                }
    
                                Stop-Function -Message "Could not remove $db from $ag on $server $exception." -Target $db -ErrorRecord $_ -Continue
                            }
                        }
                    }
    
                    $sessions = Get-DbaProcess -SqlInstance $db.Parent -Database $db.Name
    
                    if ($sessions) {
                        If ($Pscmdlet.ShouldProcess($server, "Killing $($sessions.count) sessions which are still connected to $db")) {
                            $null = $sessions | Stop-DbaProcess -ErrorAction SilentlyContinue -WarningAction SilentlyContinue
                        }
                    }
                }
    
                If ($Pscmdlet.ShouldProcess($server, "Detaching $db on $server")) {
                    try {
                        $server.DetachDatabase($db.Name, $UpdateStatistics)
    
                        [pscustomobject]@{
                            ComputerName = $server.ComputerName
                            InstanceName = $server.ServiceName
                            SqlInstance  = $server.DomainInstanceName
                            Database     = $db.name
                            DetachResult = "Success"
                        }
                    } catch {
                        Stop-Function -Message "Failure" -Target $db -ErrorRecord $_ -Continue
                    }
                }
            }
        }
    }
    #ValidationTags#Messaging,FlowControl,Pipeline,CodeStyle#
    function Enable-DbaAgHadr {
        <#
        .SYNOPSIS
            Enables the Hadr service setting on the specified SQL Server.
    
        .DESCRIPTION
            In order to build an AG a cluster has to be built and then the Hadr enabled for the SQL Server
            service. This function enables that feature for the SQL Server service.
    
        .PARAMETER SqlInstance
            The target SQL Server instance or instances.
    
        .PARAMETER Credential
            Credential object used to connect to the Windows server as a different user
    
        .PARAMETER WhatIf
            Shows what would happen if the command were to run. No actions are actually performed.
    
        .PARAMETER Confirm
            Prompts you for confirmation before executing any changing operations within the command.
    
        .PARAMETER Force
            Will restart SQL Server and SQL Server Agent service to apply the change.
    
        .PARAMETER EnableException
            By default, when something goes wrong we try to catch it, interpret it and give you a friendly warning message.
            This avoids overwhelming you with "sea of red" exceptions, but is inconvenient because it basically disables advanced scripting.
            Using this switch turns this "nice by default" feature off and enables you to catch exceptions with your own try/catch.
    
        .NOTES
            Tags: Hadr, HA, AG, AvailabilityGroup
            Author: Shawn Melton (@wsmelton), http://wsmelton.github.io
    
            Website: https://dbatools.io
            Copyright: (c) 2018 by dbatools, licensed under MIT
            License: MIT https://opensource.org/licenses/MIT
    
        .LINK
            https://dbatools.io/Enable-DbaAgHadr
    
        .EXAMPLE
            PS C:\> Enable-DbaAgHadr -SqlInstance sql2016
    
            Sets Hadr service to enabled for the instance sql2016 but changes will not be applied until the next time the server restarts.
    
        .EXAMPLE
            PS C:\> Enable-DbaAgHadr -SqlInstance sql2016 -Force
    
            Sets Hadr service to enabled for the instance sql2016, and restart the service to apply the change.
    
        .EXAMPLE
            PS C:\> Enable-DbaAgHadr -SqlInstance sql2012\dev1 -Force
    
            Sets Hadr service to disabled for the instance dev1 on sq2012, and restart the service to apply the change.
    
        #>
        [CmdletBinding(SupportsShouldProcess = $true, ConfirmImpact = "High")]
        param (
            [parameter(Mandatory, ValueFromPipeline)]
            [DbaInstanceParameter[]]$SqlInstance,
            [PSCredential]$Credential,
            [switch]$Force,
            [switch]$EnableException
        )
        process {
            foreach ($instance in $SqlInstance) {
                $computer = $computerFullName = $instance.ComputerName
                $instanceName = $instance.InstanceName
                if (-not (Test-ElevationRequirement -ComputerName $instance)) {
                    return
                }
                $noChange = $false
    
                <#
                #Variable marked as unused by PSScriptAnalyzer
                switch ($instance.InstanceName) {
                    'MSSQLSERVER' { $agentName = 'SQLSERVERAGENT' }
                    default { $agentName = "SQLAgent`$$instanceName" }
                }
                #>
    
                try {
                    Write-Message -Level Verbose -Message "Checking current Hadr setting for $computer"
                    $currentState = Get-WmiHadr -SqlInstance $instance -Credential $Credential
                } catch {
                    Stop-Function -Message "Failure to pull current state of Hadr setting on $computer" -Category ConnectionError -ErrorRecord $_ -Target $instance -Continue
                }
                $isHadrEnabled = $currentState.IsHadrEnabled
                Write-Message -Level InternalComment -Message "$instance Hadr current value: $isHadrEnabled"
    
                # hadr results from sql wmi can be iffy, skip the check
                <#
                if ($isHadrEnabled) {
                    Write-Message -Level Warning -Message "Hadr is already enabled for instance: $($instance.FullName)"
                    $noChange = $true
                    continue
                }
                #>
    
                $scriptblock = {
                    $instance = $args[0]
                    $sqlService = $wmi.Services | Where-Object DisplayName -eq "SQL Server ($instance)"
                    $sqlService.ChangeHadrServiceSetting(1)
                }
    
                if ($noChange -eq $false) {
                    if ($PSCmdlet.ShouldProcess($instance, "Changing Hadr from $isHadrEnabled to 1 for $instance")) {
                        try {
                            Invoke-ManagedComputerCommand -ComputerName $computerFullName -Credential $Credential -ScriptBlock $scriptblock -ArgumentList $instancename
                        } catch {
                            Stop-Function -Continue -Message "Failure on $($instance.FullName) | This may be because AlwaysOn Availability Groups feature requires the x86(non-WOW) or x64 Enterprise Edition of SQL Server 2012 (or later version) running on Windows Server 2008 (or later version) with WSFC hotfix KB 2494036 installed."
                        }
                    }
                }
    
                if (Test-Bound -ParameterName Force) {
                    if ($PSCmdlet.ShouldProcess($instance, "Force provided, restarting Engine and Agent service for $instance on $computerFullName")) {
                        try {
                            $null = Stop-DbaService -ComputerName $computerFullName -InstanceName $instanceName -Type Agent, Engine
                            $null = Start-DbaService -ComputerName $computerFullName -InstanceName $instanceName -Type Agent, Engine
                        } catch {
                            Stop-Function -Message "Issue restarting $instance" -Target $instance -Continue
                        }
                    }
                }
                $newState = Get-WmiHadr -SqlInstance $instance -Credential $Credential
    
                if (Test-Bound -Not -ParameterName Force) {
                    Write-Message -Level Warning -Message "You must restart the SQL Server for it to take effect."
                }
    
                [PSCustomObject]@{
                    ComputerName  = $newState.ComputerName
                    InstanceName  = $newState.InstanceName
                    SqlInstance   = $newState.SqlInstance
                    IsHadrEnabled = $true
                }
            }
        }
    }
    #ValidationTags#CodeStyle,Messaging,FlowControl,Pipeline#
    function Enable-DbaFilestream {
        <#
        .SYNOPSIS
            Enables FileStream on specified SQL Server instances
    
        .DESCRIPTION
            Connects to the specified SQL Server instances, and Enables the FileStream feature to the required value
    
            To perform the action, the SQL Server instance must be restarted. By default we will prompt for confirmation for this action, this can be overridden with the -Force switch
    
        .PARAMETER SqlInstance
            The target SQL Server instance or instances. Defaults to localhost.
    
        .PARAMETER SqlCredential
            Login to the target instance using alternative credentials. Windows and SQL Authentication supported. Accepts credential objects (Get-Credential)
    
        .PARAMETER Credential
            Login to the target server using alternative credentials.
    
        .PARAMETER FileStreamLevel
            The level to of FileStream to be enabled:
            1 or TSql - T-Sql Access Only
            2 or TSqlIoStreaming - T-Sql and Win32 access enabled
            3 or TSqlIoStreamingRemoteClient T-Sql, Win32 and Remote access enabled
    
        .PARAMETER ShareName
            Specifies the Windows file share name to be used for storing the FILESTREAM data.
    
        .PARAMETER EnableException
            By default, when something goes wrong we try to catch it, interpret it and give you a friendly warning message.
            This avoids overwhelming you with "sea of red" exceptions, but is inconvenient because it basically disables advanced scripting.
            Using this switch turns this "nice by default" feature off and enables you to catch exceptions with your own try/catch.
    
        .PARAMETER Force
            Restart SQL Instance after changes. Use this parameter with care as it overrides whatif.
    
        .PARAMETER WhatIf
            Shows what would happen if the command runs. The command is not run unless Force is specified.
    
        .PARAMETER Confirm
            Prompts you for confirmation before running the command.
    
        .NOTES
            Tags: Filestream
            Author: Stuart Moore ( @napalmgram ) | Chrissy LeMaire ( @cl )
            Website: https://dbatools.io
            Copyright: (c) 2018 by dbatools, licensed under MIT
            License: MIT https://opensource.org/licenses/MIT
    
        .EXAMPLE
            PS C:\> Enable-DbaFilestream -SqlInstance server1\instance2 -FileStreamLevel TSql
            PS C:\> Enable-DbaFilestream -SqlInstance server1\instance2 -FileStreamLevel 1
    
            These commands are functionally equivalent, both will set Filestream level on server1\instance2 to T-Sql Only
    
        .EXAMPLE
            PS C:\> Get-DbaFilestream -SqlInstance server1\instance2, server5\instance5, prod\hr | Where-Object InstanceAccessLevel -eq 0 | Enable-DbaFilestream -FileStreamLevel TSqlIoStreamingRemoteClient -Force
    
            Using this pipeline you can scan a range of SQL instances and enable filestream on only those on which it's disabled.
    
           #>
        [CmdletBinding(SupportsShouldProcess, ConfirmImpact = "Medium")]
        param (
            [Parameter(Mandatory, ValueFromPipelineByPropertyName)]
            [DbaInstance[]]$SqlInstance,
            [Parameter(ValueFromPipelineByPropertyName)]
            [PSCredential]$SqlCredential,
            [Parameter(ValueFromPipelineByPropertyName)]
            [PSCredential]$Credential,
            [ValidateSet("TSql", "TSqlIoStreaming", "TSqlIoStreamingRemoteClient", 1, 2, 3)]
            [string]$FileStreamLevel = 1,
            [string]$ShareName,
            [switch]$Force,
            [switch]$EnableException
        )
        begin {
            if ($FileStreamLevel -notin (1, 2, 3)) {
                $FileStreamLevel = switch ($FileStreamLevel) {
                    "TSql" {
                        1
                    }
                    "TSqlIoStreaming" {
                        2
                    }
                    "TSqlIoStreamingRemoteClient" {
                        3
                    }
                }
            }
            # = $finallevel removed as it was identified as a unused variable
            $level = [int]$FileStreamLevel
            $OutputLookup = @{
                0 = 'Disabled'
                1 = 'FileStream enabled for T-Sql access'
                2 = 'FileStream enabled for T-Sql and IO streaming access'
                3 = 'FileStream enabled for T-Sql, IO streaming, and remote clients'
            }
        }
        process {
            if ($ShareName -and $level -lt 2) {
                Stop-Function -Message "Filestream must be at least level 2 when using ShareName"
                return
            }
    
            foreach ($instance in $SqlInstance) {
                try {
                    $server = Connect-SqlInstance -SqlInstance $instance -SqlCredential $SqlCredential
                } catch {
                    Stop-Function -Message "Failure connecting to $computer" -Category ConnectionError -ErrorRecord $_ -Target $instance -Continue
                }
    
                $filestreamstate = [int]$server.Configuration.FilestreamAccessLevel.ConfigValue
    
                if ($Force -or $PSCmdlet.ShouldProcess($instance, "Changing from '$($OutputLookup[$filestreamstate])' to '$($OutputLookup[$level])' at the instance level")) {
                    # Server level
                    if ($server.IsClustered) {
                        $nodes = Get-DbaWsfcNode -ComputerName $instance
                        foreach ($node in $nodes.Name) {
                            $result = Set-FileSystemSetting -Instance $node -Credential $Credential -ShareName $ShareName -FilestreamLevel $level
                        }
                    } else {
                        $result = Set-FileSystemSetting -Instance $instance -Credential $Credential -ShareName $ShareName -FilestreamLevel $level
                    }
    
                    # Instance level
                    if ($level -eq 3) {
                        $level = 2
                    }
    
                    try {
                        $null = Set-DbaSpConfigure -SqlInstance $server -Name FilestreamAccessLevel -Value $level -EnableException
                    } catch {
                        Stop-Function -Message "Failure" -ErrorRecord $_ -Continue
                    }
    
                    if ($Force) {
                        #$restart replaced with $null as it was identified as a unused variable
                        $null = Restart-DbaService -ComputerName $server.ComputerName -InstanceName $server.ServiceName -Type Engine -Force
                    }
    
                    Get-DbaFilestream -SqlInstance $instance -SqlCredential $SqlCredential -Credential $Credential
                    if ($filestreamstate -ne $level -and -not $Force) {
                        Write-Message -Level Warning -Message "[$instance] $result"
                    }
                }
            }
        }
    }
    #ValidationTags#Messaging,FlowControl,Pipeline,CodeStyle#
    function Enable-DbaForceNetworkEncryption {
        <#
        .SYNOPSIS
            Enables Force Encryption for a SQL Server instance.
    
        .DESCRIPTION
            Enables Force Encryption for a SQL Server instance. Note that this requires access to the Windows Server, not the SQL instance itself.
    
            This setting is found in Configuration Manager.
    
        .PARAMETER SqlInstance
            The target SQL Server instance or instances.
    
        .PARAMETER Credential
            Allows you to login to the computer (not SQL Server instance) using alternative Windows credentials
    
        .PARAMETER WhatIf
            If this switch is enabled, no actions are performed but informational messages will be displayed that explain what would happen if the command were to run.
    
        .PARAMETER Confirm
            If this switch is enabled, you will be prompted for confirmation before executing any operations that change state.
    
        .PARAMETER EnableException
            By default, when something goes wrong we try to catch it, interpret it and give you a friendly warning message.
            This avoids overwhelming you with "sea of red" exceptions, but is inconvenient because it basically disables advanced scripting.
            Using this switch turns this "nice by default" feature off and enables you to catch exceptions with your own try/catch.
    
        .NOTES
            Tags: Certificate, Encryption
            Author: Chrissy LeMaire (@cl), netnerds.net
    
            Website: https://dbatools.io
            Copyright: (c) 2018 by dbatools, licensed under MIT
            License: MIT https://opensource.org/licenses/MIT
    
        .EXAMPLE
            PS C:\> Enable-DbaForceNetworkEncryption
    
            Enables Force Encryption on the default (MSSQLSERVER) instance on localhost. Requires (and checks for) RunAs admin.
    
        .EXAMPLE
            PS C:\> Enable-DbaForceNetworkEncryption -SqlInstance sql01\SQL2008R2SP2
    
            Enables Force Network Encryption for the SQL2008R2SP2 on sql01. Uses Windows Credentials to both connect and modify the registry.
    
        .EXAMPLE
            PS C:\> Enable-DbaForceNetworkEncryption -SqlInstance sql01\SQL2008R2SP2 -WhatIf
    
            Shows what would happen if the command were executed.
    
        #>
        [CmdletBinding(SupportsShouldProcess, ConfirmImpact = "Low", DefaultParameterSetName = 'Default')]
        param (
            [Parameter(ValueFromPipeline)]
            [Alias("ServerInstance", "SqlServer", "ComputerName")]
            [DbaInstanceParameter[]]
            $SqlInstance = $env:COMPUTERNAME,
            [PSCredential]$Credential,
            [Alias('Silent')]
            [switch]$EnableException
        )
        process {
    
            foreach ($instance in $sqlinstance) {
                Write-Message -Level VeryVerbose -Message "Processing $instance." -Target $instance
                $null = Test-ElevationRequirement -ComputerName $instance -Continue
    
                Write-Message -Level Verbose -Message "Resolving hostname."
                $resolved = $null
                $resolved = Resolve-DbaNetworkName -ComputerName $instance -Turbo
    
                if ($null -eq $resolved) {
                    Stop-Function -Message "Can't resolve $instance." -Target $instance -Continue -Category InvalidArgument
                }
    
                try {
                    $sqlwmi = Invoke-ManagedComputerCommand -ComputerName $resolved.FullComputerName -ScriptBlock { $wmi.Services } -Credential $Credential -ErrorAction Stop | Where-Object DisplayName -eq "SQL Server ($($instance.InstanceName))"
                } catch {
                    Stop-Function -Message "Failed to access $instance" -Target $instance -Continue -ErrorRecord $_
                }
    
                $regroot = ($sqlwmi.AdvancedProperties | Where-Object Name -eq REGROOT).Value
                $vsname = ($sqlwmi.AdvancedProperties | Where-Object Name -eq VSNAME).Value
                try {
                    $instancename = $sqlwmi.DisplayName.Replace('SQL Server (', '').Replace(')', '') # Don't clown, I don't know regex :(
                } catch {
                    # Probably because the instance name has been aliased or does not exist or something
                    # here to avoid an empty catch
                    $null = 1
                }
                $serviceaccount = $sqlwmi.ServiceAccount
    
                if ([System.String]::IsNullOrEmpty($regroot)) {
                    $regroot = $sqlwmi.AdvancedProperties | Where-Object { $_ -match 'REGROOT' }
                    $vsname = $sqlwmi.AdvancedProperties | Where-Object { $_ -match 'VSNAME' }
    
                    if (![System.String]::IsNullOrEmpty($regroot)) {
                        $regroot = ($regroot -Split 'Value\=')[1]
                        $vsname = ($vsname -Split 'Value\=')[1]
                    } else {
                        Stop-Function -Message "Can't find instance $vsname on $instance." -Continue -Category ObjectNotFound -Target $instance
                    }
                }
    
                if ([System.String]::IsNullOrEmpty($vsname)) { $vsname = $instance }
    
                Write-Message -Level Output -Message "Regroot: $regroot" -Target $instance
                Write-Message -Level Output -Message "ServiceAcct: $serviceaccount" -Target $instance
                Write-Message -Level Output -Message "InstanceName: $instancename" -Target $instance
                Write-Message -Level Output -Message "VSNAME: $vsname" -Target $instance
    
                $scriptblock = {
                    $regpath = "Registry::HKEY_LOCAL_MACHINE\$($args[0])\MSSQLServer\SuperSocketNetLib"
                    $cert = (Get-ItemProperty -Path $regpath -Name Certificate).Certificate
                    #Variable marked as unused by PSScriptAnalyzer
                    #$oldvalue = (Get-ItemProperty -Path $regpath -Name ForceEncryption).ForceEncryption
                    Set-ItemProperty -Path $regpath -Name ForceEncryption -Value $true
                    $forceencryption = (Get-ItemProperty -Path $regpath -Name ForceEncryption).ForceEncryption
    
                    [pscustomobject]@{
                        ComputerName          = $env:COMPUTERNAME
                        InstanceName          = $args[2]
                        SqlInstance           = $args[1]
                        ForceEncryption       = ($forceencryption -eq $true)
                        CertificateThumbprint = $cert
                    }
                }
    
                if ($PScmdlet.ShouldProcess("local", "Connecting to $instance to modify the ForceEncryption value in $regroot for $($instance.InstanceName)")) {
                    try {
                        Invoke-Command2 -ComputerName $resolved.FullComputerName -Credential $Credential -ArgumentList $regroot, $vsname, $instancename -ScriptBlock $scriptblock -ErrorAction Stop | Select-Object -Property * -ExcludeProperty PSComputerName, RunspaceId, PSShowComputerName
                        Write-Message -Level Critical -Message "Force encryption was successfully set on $($resolved.FullComputerName) for the $instancename instance. You must now restart the SQL Server for changes to take effect." -Target $instance
                    } catch {
                        Stop-Function -Message "Failed to connect to $($resolved.FullComputerName) using PowerShell remoting!" -ErrorRecord $_ -Target $instance -Continue
                    }
                }
            }
        }
    }
    function Enable-DbaTraceFlag {
        <#
        .SYNOPSIS
            Enable Global Trace Flag(s)
    
        .DESCRIPTION
            The function will set one or multiple trace flags on the SQL Server instance(s) listed
    
        .PARAMETER SqlInstance
            The target SQL Server instance or instances.
    
        .PARAMETER SqlCredential
            Login to the target instance using alternative credentials. Windows and SQL Authentication supported. Accepts credential objects (Get-Credential)
    
        .PARAMETER TraceFlag
            Trace flag number(s) to enable globally
    
        .PARAMETER EnableException
            By default, when something goes wrong we try to catch it, interpret it and give you a friendly warning message.
            This avoids overwhelming you with "sea of red" exceptions, but is inconvenient because it basically disables advanced scripting.
            Using this switch turns this "nice by default" feature off and enables you to catch exceptions with your own try/catch.
    
        .NOTES
            Tags: TraceFlag
            Author: Garry Bargsley (@gbargsley), http://blog.garrybargsley.com
    
            Website: https://dbatools.io
            Copyright: (c) 2018 by dbatools, licensed under MIT
            License: MIT https://opensource.org/licenses/MIT
    
        .LINK
            https://dbatools.io/Enable-DbaTraceFlag
    
        .EXAMPLE
            PS C:\> Enable-DbaTraceFlag -SqlInstance sql2016 -TraceFlag 3226
    
            Enable the trace flag 3226 on SQL Server instance sql2016
    
        .EXAMPLE
            PS C:\> Enable-DbaTraceFlag -SqlInstance sql2016 -TraceFlag 1117, 1118
    
            Enable multiple trace flags on SQL Server instance sql2016
    
        #>
        [CmdletBinding()]
        param (
            [parameter(Position = 0, Mandatory, ValueFromPipeline)]
            [Alias("ServerInstance", "SqlServer", "SqlServers")]
            [DbaInstanceParameter[]]$SqlInstance,
            [PSCredential]$SqlCredential,
            [parameter(Mandatory)]
            [int[]]$TraceFlag,
            [Alias('Silent')]
            [switch]$EnableException
        )
    
        process {
            foreach ($instance in $SqlInstance) {
                try {
                    $server = Connect-SqlInstance -SqlInstance $instance -SqlCredential $SqlCredential
                } catch {
                    Stop-Function -Message "Failure" -Category ConnectionError -ErrorRecord $_ -Target $instance -Continue
                }
    
                $CurrentRunningTraceFlags = Get-DbaTraceFlag -SqlInstance $server -EnableException
    
                # We could combine all trace flags but the granularity is worth it
                foreach ($tf in $TraceFlag) {
                    $TraceFlagInfo = [PSCustomObject]@{
                        SourceServer = $server.ComputerName
                        InstanceName = $server.ServiceName
                        SqlInstance  = $server.DomainInstanceName
                        TraceFlag    = $tf
                        Status       = $null
                        Notes        = $null
                        DateTime     = [DbaDateTime](Get-Date)
                    }
                    if ($CurrentRunningTraceFlags.TraceFlag -contains $tf) {
                        $TraceFlagInfo.Status = 'Skipped'
                        $TraceFlagInfo.Notes = "The Trace flag is already running."
                        $TraceFlagInfo
                        Write-Message -Level Warning -Message "The Trace flag [$tf] is already running globally."
                        continue
                    }
    
                    try {
                        $query = "DBCC TRACEON ($tf, -1)"
                        $server.Query($query)
                        $server.Refresh()
                    } catch {
                        $TraceFlagInfo.Status = "Failed"
                        $TraceFlagInfo.Notes = $_.Exception.Message
                        $TraceFlagInfo
                        Stop-Function -Message "Failure" -ErrorRecord $_ -Target $server -Continue
                    }
                    $TraceFlagInfo.Status = "Successful"
                    $TraceFlagInfo
                }
            }
        }
    }
    function Expand-DbaDbLogFile {
        <#
        .SYNOPSIS
            This command will help you to automatically grow your transaction log  file in a responsible way (preventing the generation of too many VLFs).
    
        .DESCRIPTION
            As you may already know, having a transaction log file with too many Virtual Log Files (VLFs) can hurt your database performance in many ways.
    
            Example:
            Too many VLFs can cause transaction log backups to slow down and can also slow down database recovery and, in extreme cases, even impact insert/update/delete performance.
    
            References:
            http://www.sqlskills.com/blogs/kimberly/transaction-log-vlfs-too-many-or-too-few/
            http://blogs.msdn.com/b/saponsqlserver/archive/2012/02/22/too-many-virtual-log-files-vlfs-can-cause-slow-database-recovery.aspx
            http://www.brentozar.com/blitz/high-virtual-log-file-vlf-count/
    
            In order to get rid of this fragmentation we need to grow the file taking the following into consideration:
            - How many VLFs are created when we perform a grow operation or when an auto-grow is invoked?
    
            Note: In SQL Server 2014 this algorithm has changed (http://www.sqlskills.com/blogs/paul/important-change-vlf-creation-algorithm-sql-server-2014/)
    
            Attention:
            We are growing in MB instead of GB because of known issue prior to SQL 2012:
            More detail here:
            http://www.sqlskills.com/BLOGS/PAUL/post/Bug-log-file-growth-broken-for-multiples-of-4GB.aspx
            and
            http://connect.microsoft.com/SqlInstance/feedback/details/481594/log-growth-not-working-properly-with-specific-growth-sizes-vlfs-also-not-created-appropriately
            or
            https://connect.microsoft.com/SqlInstance/feedback/details/357502/transaction-log-file-size-will-not-grow-exactly-4gb-when-filegrowth-4gb
    
            Understanding related problems:
            http://www.sqlskills.com/blogs/kimberly/transaction-log-vlfs-too-many-or-too-few/
            http://blogs.msdn.com/b/saponsqlserver/archive/2012/02/22/too-many-virtual-log-files-vlfs-can-cause-slow-database-recovery.aspx
            http://www.brentozar.com/blitz/high-virtual-log-file-vlf-count/
    
            Known bug before SQL Server 2012
            http://www.sqlskills.com/BLOGS/PAUL/post/Bug-log-file-growth-broken-for-multiples-of-4GB.aspx
            http://connect.microsoft.com/SqlInstance/feedback/details/481594/log-growth-not-working-properly-with-specific-growth-sizes-vlfs-also-not-created-appropriately
            https://connect.microsoft.com/SqlInstance/feedback/details/357502/transaction-log-file-size-will-not-grow-exactly-4gb-when-filegrowth-4gb
    
            How it works?
            The transaction log will grow in chunks until it reaches the desired size.
            Example: If you have a log file with 8192MB and you say that the target size is 81920MB (80GB) it will grow in chunks of 8192MB until it reaches 81920MB. 8192 -> 16384 -> 24576 ... 73728 -> 81920
    
        .PARAMETER SqlInstance
            The target SQL Server instance or instances.
    
        .PARAMETER Database
            The database(s) to process. Options for this list are auto-populated from the server. If unspecified, all databases will be processed.
    
        .PARAMETER TargetLogSize
            Specifies the target size of the transaction log file in megabytes.
    
        .PARAMETER IncrementSize
            Specifies the amount the transaction log should grow in megabytes. If this value differs from the suggested value based on your TargetLogSize, you will be prompted to confirm your choice.
    
            This value will be calculated if not specified.
    
        .PARAMETER LogFileId
            Specifies the file number(s) of additional transaction log files to grow.
    
            If this value is not specified, only the first transaction log file will be processed.
    
        .PARAMETER ShrinkLogFile
            If this switch is enabled, your transaction log files will be shrunk.
    
        .PARAMETER ShrinkSize
            Specifies the target size of the transaction log file for the shrink operation in megabytes.
    
        .PARAMETER BackupDirectory
            Specifies the location of your backups. Backups must be performed to shrink the transaction log.
    
            If this value is not specified, the SQL Server instance's default backup directory will be used.
    
        .PARAMETER ExcludeDiskSpaceValidation
            If this switch is enabled, the validation for enough disk space using Get-DbaDiskSpace command will be skipped.
            This can be useful when you know that you have enough space to grow your TLog but you don't have PowerShell Remoting enabled to validate it.
    
        .PARAMETER SqlCredential
            Login to the target instance using alternative credentials. Windows and SQL Authentication supported. Accepts credential objects (Get-Credential)
    
        .PARAMETER ExcludeDatabase
            The database(s) to exclude. Options for this list are auto-populated from the server.
    
        .PARAMETER WhatIf
            If this switch is enabled, no actions are performed but informational messages will be displayed that explain what would happen if the command were to run.
    
        .PARAMETER Confirm
            If this switch is enabled, you will be prompted for confirmation before executing any operations that change state.
    
        .PARAMETER EnableException
            By default, when something goes wrong we try to catch it, interpret it and give you a friendly warning message.
            This avoids overwhelming you with "sea of red" exceptions, but is inconvenient because it basically disables advanced scripting.
            Using this switch turns this "nice by default" feature off and enables you to catch exceptions with your own try/catch.
    
        .NOTES
            Tags: Storage, Backup
            Author: Claudio Silva (@ClaudioESSilva)
    
            Website: https://dbatools.io
            Copyright: (c) 2018 by dbatools, licensed under MIT
            License: MIT https://opensource.org/licenses/MIT
    
            Requires: ALTER DATABASE permission
            Limitations: Freespace cannot be validated on the directory where the log file resides in SQL Server 2005.
            This script uses Get-DbaDiskSpace dbatools command to get the TLog's drive free space
    
        .LINK
            https://dbatools.io/Expand-DbaDbLogFile
    
        .EXAMPLE
            PS C:\> Expand-DbaDbLogFile -SqlInstance sqlcluster -Database db1 -TargetLogSize 50000
    
            Grows the transaction log for database db1 on sqlcluster to 50000 MB and calculates the increment size.
    
        .EXAMPLE
            PS C:\> Expand-DbaDbLogFile -SqlInstance sqlcluster -Database db1, db2 -TargetLogSize 10000 -IncrementSize 200
    
            Grows the transaction logs for databases db1 and db2 on sqlcluster to 1000MB and sets the growth increment to 200MB.
    
        .EXAMPLE
            PS C:\> Expand-DbaDbLogFile -SqlInstance sqlcluster -Database db1 -TargetLogSize 10000 -LogFileId 9
    
            Grows the transaction log file  with FileId 9 of the db1 database on sqlcluster instance to 10000MB.
    
        .EXAMPLE
            PS C:\> Expand-DbaDbLogFile -SqlInstance sqlcluster -Database (Get-Content D:\DBs.txt) -TargetLogSize 50000
    
            Grows the transaction log of the databases specified in the file 'D:\DBs.txt' on sqlcluster instance to 50000MB.
    
        .EXAMPLE
            PS C:\> Expand-DbaDbLogFile -SqlInstance SqlInstance -Database db1,db2 -TargetLogSize 100 -IncrementSize 10 -ShrinkLogFile -ShrinkSize 10 -BackupDirectory R:\MSSQL\Backup
    
            Grows the transaction logs for databases db1 and db2 on SQL server SQLInstance to 100MB, sets the incremental growth to 10MB, shrinks the transaction log to 10MB and uses the directory R:\MSSQL\Backup for the required backups.
    
           #>
        [CmdletBinding(SupportsShouldProcess, DefaultParameterSetName = 'Default')]
        param (
            [parameter(Position = 1, Mandatory)]
            [Alias("ServerInstance", "SqlServer")]
            [DbaInstanceParameter]$SqlInstance,
            [parameter(Position = 3)]
            [PSCredential]$SqlCredential,
            [Alias("Databases")]
            [object[]]$Database,
            [parameter(Position = 4)]
            [object[]]$ExcludeDatabase,
            [parameter(Position = 5, Mandatory)]
            [Alias('TargetLogSizeMB')]
            [int]$TargetLogSize,
            [parameter(Position = 6)]
            [Alias('IncrementSizeMB')]
            [int]$IncrementSize = -1,
            [parameter(Position = 7)]
            [int]$LogFileId = -1,
            [parameter(Position = 8, ParameterSetName = 'Shrink', Mandatory)]
            [switch]$ShrinkLogFile,
            [parameter(Position = 9, ParameterSetName = 'Shrink', Mandatory)]
            [Alias('ShrinkSizeMB')]
            [int]$ShrinkSize,
            [parameter(Position = 10, ParameterSetName = 'Shrink')]
            [AllowEmptyString()]
            [string]$BackupDirectory,
            [switch]$ExcludeDiskSpaceValidation,
            [switch][Alias('Silent')]
            $EnableException
        )
    
        begin {
            Write-Message -Level Verbose -Message "Set ErrorActionPreference to Inquire."
            $ErrorActionPreference = 'Inquire'
    
            #Convert MB to KB (SMO works in KB)
            Write-Message -Level Verbose -Message "Convert variables MB to KB (SMO works in KB)."
            [int]$TargetLogSizeKB = $TargetLogSize * 1024
            [int]$LogIncrementSize = $IncrementSize * 1024
            [int]$ShrinkSizeKB = $ShrinkSize * 1024
            [int]$SuggestLogIncrementSize = 0
            [bool]$LogByFileID = if ($LogFileId -eq -1) {
                $false
            } else {
                $true
            }
    
            #Set base information
            Write-Message -Level Verbose -Message "Initialize the instance '$SqlInstance'."
    
            $server = Connect-SqlInstance -SqlInstance $SqlInstance -SqlCredential $SqlCredential
    
            if ($ShrinkLogFile -eq $true) {
                if ($BackupDirectory.length -eq 0) {
                    $backupdirectory = $server.Settings.BackupDirectory
                }
    
                $pathexists = Test-DbaPath -SqlInstance $server -Path $backupdirectory
    
                if ($pathexists -eq $false) {
                    Stop-Function -Message "Backup directory does not exist."
                }
            }
        }
    
        process {
    
            try {
    
                [datetime]$initialTime = Get-Date
    
                #control the iteration number
                $databaseProgressbar = 0;
    
                Write-Message -Level Verbose -Message "Resolving NetBIOS name."
                $sourcenetbios = Resolve-NetBiosName $server
    
                $databases = $server.Databases | Where-Object IsAccessible
                Write-Message -Level Verbose -Message "Number of databases found: $($databases.Count)."
                if ($Database) {
                    $databases = $databases | Where-Object Name -In $Database
                }
                if ($ExcludeDatabase) {
                    $databases = $databases | Where-Object Name -NotIn $ExcludeDatabase
                }
    
                #go through all databases
                Write-Message -Level Verbose -Message "Processing...foreach database..."
                foreach ($db in $databases.Name) {
                    Write-Message -Level Verbose -Message "Working on $db."
                    $databaseProgressbar += 1
    
                    #set step to reutilize on logging operations
                    [string]$step = "$databaseProgressbar/$($Databases.Count)"
    
                    if ($server.Databases[$db]) {
                        Write-Progress `
                            -Id 1 `
                            -Activity "Using database: $db on Instance: '$SqlInstance'" `
                            -PercentComplete ($databaseProgressbar / $Databases.Count * 100) `
                            -Status "Processing - $databaseProgressbar of $($Databases.Count)"
    
                        #Validate which file will grow
                        if ($LogByFileID) {
                            $logfile = $server.Databases[$db].LogFiles.ItemById($LogFileId)
                        } else {
                            $logfile = $server.Databases[$db].LogFiles[0]
                        }
    
                        $numLogfiles = $server.Databases[$db].LogFiles.Count
    
                        Write-Message -Level Verbose -Message "$step - Use log file: $logfile."
                        $currentSize = $logfile.Size
                        $currentSizeMB = $currentSize / 1024
    
                        #Get the number of VLFs
                        $initialVLFCount = Test-DbaDbVirtualLogFile -SqlInstance $server -Database $db
    
                        Write-Message -Level Verbose -Message "$step - Log file current size: $([System.Math]::Round($($currentSize/1024.0), 2)) MB "
                        [long]$requiredSpace = ($TargetLogSizeKB - $currentSize)
    
                        if ($ExcludeDiskSpaceValidation -eq $false) {
                            Write-Message -Level Verbose -Message "Verifying if sufficient space exists ($([System.Math]::Round($($requiredSpace / 1024.0), 2))MB) on the volume to perform this task."
    
                            [long]$TotalTLogFreeDiskSpaceKB = 0
                            Write-Message -Level Verbose -Message "Get TLog drive free space"
    
                            try {
                                [object]$AllDrivesFreeDiskSpace = Get-DbaDiskSpace -ComputerName $sourcenetbios | Select-Object Name, SizeInKB
    
                                #Verify path using Split-Path on $logfile.FileName in backwards. This way we will catch the LUNs. Example: "K:\Log01" as LUN name. Need to add final backslash if not there
                                $DrivePath = Split-Path $logfile.FileName -parent
                                $DrivePath = if (!($DrivePath.EndsWith("\"))) { "$DrivePath\" }
                                else { $DrivePath }
                                Do {
                                    if ($AllDrivesFreeDiskSpace | Where-Object { $DrivePath -eq "$($_.Name)" }) {
                                        $TotalTLogFreeDiskSpaceKB = ($AllDrivesFreeDiskSpace | Where-Object { $DrivePath -eq $_.Name }).SizeInKB
                                        $match = $true
                                        break
                                    } else {
                                        $match = $false
                                        $DrivePath = Split-Path $DrivePath -parent
                                        $DrivePath = if (!($DrivePath.EndsWith("\"))) { "$DrivePath\" }
                                        else { $DrivePath }
                                    }
    
                                }
                                while (!$match -or ([string]::IsNullOrEmpty($DrivePath)))
    
                                Write-Message -Level Verbose -Message "Total TLog Free Disk Space in MB: $([System.Math]::Round($($TotalTLogFreeDiskSpaceKB / 1024.0), 2))"
    
                            } catch {
                                #Could not validate the disk space. Will ask if we want to continue.
                                $TotalTLogFreeDiskSpaceKB = 0
                            }
    
                            if (($TotalTLogFreeDiskSpaceKB -le 0) -or ([string]::IsNullOrEmpty($TotalTLogFreeDiskSpaceKB))) {
                                $title = "Choose increment value for database '$db':"
                                $message = "Cannot validate freespace on drive where the log file resides. Do you wish to continue? (Y/N)"
                                $yes = New-Object System.Management.Automation.Host.ChoiceDescription "&Yes", "Will continue"
                                $no = New-Object System.Management.Automation.Host.ChoiceDescription "&No", "Will exit"
                                $options = [System.Management.Automation.Host.ChoiceDescription[]]($yes, $no)
                                $result = $host.ui.PromptForChoice($title, $message, $options, 0)
                                #no
                                if ($result -eq 1) {
                                    Write-Message -Level Warning -Message "You have cancelled the execution"
                                    return
                                }
                            } elseif ($requiredSpace -gt $TotalTLogFreeDiskSpaceKB) {
                                Write-Message -Level Verbose -Message "There is not enough space on volume to perform this task. `r`n" `
                                    "Available space: $([System.Math]::Round($($TotalTLogFreeDiskSpaceKB / 1024.0), 2))MB;`r`n" `
                                    "Required space: $([System.Math]::Round($($requiredSpace / 1024.0), 2))MB;"
                                return
                            }
                        }
    
                        if ($currentSize -ige $TargetLogSizeKB -and ($ShrinkLogFile -eq $false)) {
                            Write-Message -Level Verbose -Message "$step - [INFO] The T-Log file '$logfile' size is already equal or greater than target size - No action required."
                        } else {
                            Write-Message -Level Verbose -Message "$step - [OK] There is sufficient free space to perform this task."
    
                            # If SQL Server version is greater or equal to 2012
                            if ($server.Version.Major -ge "11") {
                                switch ($TargetLogSize) {
                                    { $_ -le 64 } { $SuggestLogIncrementSize = 64 }
                                    { $_ -ge 64 -and $_ -lt 256 } { $SuggestLogIncrementSize = 256 }
                                    { $_ -ge 256 -and $_ -lt 1024 } { $SuggestLogIncrementSize = 512 }
                                    { $_ -ge 1024 -and $_ -lt 4096 } { $SuggestLogIncrementSize = 1024 }
                                    { $_ -ge 4096 -and $_ -lt 8192 } { $SuggestLogIncrementSize = 2048 }
                                    { $_ -ge 8192 -and $_ -lt 16384 } { $SuggestLogIncrementSize = 4096 }
                                    { $_ -ge 16384 } { $SuggestLogIncrementSize = 8192 }
                                }
                            }
                            # 2008 R2 or under
                            else {
                                switch ($TargetLogSize) {
                                    { $_ -le 64 } { $SuggestLogIncrementSize = 64 }
                                    { $_ -ge 64 -and $_ -lt 256 } { $SuggestLogIncrementSize = 256 }
                                    { $_ -ge 256 -and $_ -lt 1024 } { $SuggestLogIncrementSize = 512 }
                                    { $_ -ge 1024 -and $_ -lt 4096 } { $SuggestLogIncrementSize = 1024 }
                                    { $_ -ge 4096 -and $_ -lt 8192 } { $SuggestLogIncrementSize = 2048 }
                                    { $_ -ge 8192 -and $_ -lt 16384 } { $SuggestLogIncrementSize = 4000 }
                                    { $_ -ge 16384 } { $SuggestLogIncrementSize = 8000 }
                                }
    
                                if (($IncrementSize % 4096) -eq 0) {
                                    Write-Message -Level Verbose -Message "Your instance version is below SQL 2012, remember the known BUG mentioned on HELP. `r`nUse Get-Help Expand-DbaTLogFileResponsibly to read help`r`nUse a different value for incremental size.`r`n"
                                    return
                                }
                            }
                            Write-Message -Level Verbose -Message "Instance $server version: $($server.Version.Major) - Suggested TLog increment size: $($SuggestLogIncrementSize)MB"
    
                            # Shrink Log File to desired size before re-growth to desired size (You need to remove as many VLF's as possible to ensure proper growth)
                            $ShrinkSize = $ShrinkSizeKB / 1024
                            if ($ShrinkLogFile -eq $true) {
                                if ($server.Databases[$db].RecoveryModel -eq [Microsoft.SqlServer.Management.Smo.RecoveryModel]::Simple) {
                                    Write-Message -Level Warning -Message "Database '$db' is in Simple RecoveryModel which does not allow log backups. Do not specify -ShrinkLogFile and -ShrinkSize parameters."
                                    Continue
                                }
    
                                try {
                                    $sql = "SELECT last_log_backup_lsn FROM sys.database_recovery_status WHERE database_id = DB_ID('$db')"
                                    $sqlResult = $server.ConnectionContext.ExecuteWithResults($sql);
    
                                    if ($sqlResult.Tables[0].Rows[0]["last_log_backup_lsn"] -is [System.DBNull]) {
                                        Write-Message -Level Warning -Message "First, you need to make a full backup before you can do Tlog backup on database '$db' (last_log_backup_lsn is null)."
                                        Continue
                                    }
                                } catch {
                                    Stop-Function -Message "Can't execute SQL on $server. `r`n $($_)" -Continue
                                }
    
                                If ($Pscmdlet.ShouldProcess($($server.name), "Backing up TLog for $db")) {
                                    Write-Message -Level Verbose -Message "We are about to backup the Tlog for database '$db' to '$backupdirectory' and shrink the log."
                                    Write-Message -Level Verbose -Message "Starting Size = $currentSizeMB."
    
                                    $DefaultCompression = $server.Configuration.DefaultBackupCompression.ConfigValue
    
                                    if ($currentSizeMB -gt $ShrinkSize) {
                                        $backupRetries = 1
                                        Do {
                                            try {
                                                $percent = $null
                                                $backup = New-Object Microsoft.SqlServer.Management.Smo.Backup
                                                $backup.Action = [Microsoft.SqlServer.Management.Smo.BackupActionType]::Log
                                                $backup.BackupSetDescription = "Transaction Log backup of " + $db
                                                $backup.BackupSetName = $db + " Backup"
                                                $backup.Database = $db
                                                $backup.MediaDescription = "Disk"
                                                $dt = get-date -format yyyyMMddHHmmssms
                                                $null = $backup.Devices.AddDevice($backupdirectory + "\" + $db + "_db_" + $dt + ".trn", 'File')
                                                if ($DefaultCompression -eq $true) {
                                                    $backup.CompressionOption = 1
                                                } else {
                                                    $backup.CompressionOption = 0
                                                }
                                                $null = [Microsoft.SqlServer.Management.Smo.PercentCompleteEventHandler] {
                                                    Write-Progress -id 2 -ParentId 1 -activity "Backing up $db to $server" -percentcomplete $_.Percent -status ([System.String]::Format("Progress: {0} %", $_.Percent))
                                                }
                                                $backup.add_PercentComplete($percent)
                                                $backup.PercentCompleteNotification = 10
                                                $backup.add_Complete($complete)
                                                Write-Progress -id 2 -ParentId 1 -activity "Backing up $db to $server" -percentcomplete 0 -Status ([System.String]::Format("Progress: {0} %", 0))
                                                $backup.SqlBackup($server)
                                                Write-Progress -id 2 -ParentId 1 -activity "Backing up $db to $server" -status "Complete" -Completed
                                                $logfile.Shrink($ShrinkSize, [Microsoft.SqlServer.Management.SMO.ShrinkMethod]::TruncateOnly)
                                                $logfile.Refresh()
                                            } catch {
                                                Write-Progress -id 1 -activity "Backup" -status "Failed" -completed
                                                Stop-Function -Message "Backup failed for database" -ErrorRecord $_ -Target $db -Continue
                                                Continue
                                            }
    
                                        }
                                        while (($logfile.Size / 1024) -gt $ShrinkSize -and ++$backupRetries -lt 6)
    
                                        $currentSize = $logfile.Size
                                        Write-Message -Level Verbose -Message "TLog backup and truncate for database '$db' finished. Current TLog size after $backupRetries backups is $($currentSize/1024)MB"
                                    }
                                }
                            }
    
                            # SMO uses values in KB
                            $SuggestLogIncrementSize = $SuggestLogIncrementSize * 1024
    
                            # If default, use $SuggestedLogIncrementSize
                            if ($IncrementSize -eq -1) {
                                $LogIncrementSize = $SuggestLogIncrementSize
                            } else {
                                $title = "Choose increment value for database '$db':"
                                $message = "The input value for increment size was $([System.Math]::Round($LogIncrementSize/1024, 0))MB. However the suggested value for increment is $($SuggestLogIncrementSize/1024)MB.`r`nDo you want to use the suggested value of $([System.Math]::Round($SuggestLogIncrementSize/1024, 0))MB insted of $([System.Math]::Round($LogIncrementSize/1024, 0))MB"
                                $yes = New-Object System.Management.Automation.Host.ChoiceDescription "&Yes", "Uses recomended size."
                                $no = New-Object System.Management.Automation.Host.ChoiceDescription "&No", "Will use parameter value."
                                $options = [System.Management.Automation.Host.ChoiceDescription[]]($yes, $no)
                                $result = $host.ui.PromptForChoice($title, $message, $options, 0)
                                #yes
                                if ($result -eq 0) {
                                    $LogIncrementSize = $SuggestLogIncrementSize
                                }
                            }
    
                            #start growing file
                            If ($Pscmdlet.ShouldProcess($($server.name), "Starting log growth. Increment chunk size: $($LogIncrementSize/1024)MB for database '$db'")) {
                                Write-Message -Level Verbose -Message "Starting log growth. Increment chunk size: $($LogIncrementSize/1024)MB for database '$db'"
    
                                Write-Message -Level Verbose -Message "$step - While current size less than target log size."
    
                                while ($currentSize -lt $TargetLogSizeKB) {
    
                                    Write-Progress `
                                        -Id 2 `
                                        -ParentId 1 `
                                        -Activity "Growing file $logfile on '$db' database" `
                                        -PercentComplete ($currentSize / $TargetLogSizeKB * 100) `
                                        -Status "Remaining - $([System.Math]::Round($($($TargetLogSizeKB - $currentSize) / 1024.0), 2)) MB"
    
                                    Write-Message -Level Verbose -Message "$step - Verifying if the log can grow or if it's already at the desired size."
                                    if (($TargetLogSizeKB - $currentSize) -lt $LogIncrementSize) {
                                        Write-Message -Level Verbose -Message "$step - Log size is lower than the increment size. Setting current size equals $TargetLogSizeKB."
                                        $currentSize = $TargetLogSizeKB
                                    } else {
                                        Write-Message -Level Verbose -Message "$step - Grow the $logfile file in $([System.Math]::Round($($LogIncrementSize / 1024.0), 2)) MB"
                                        $currentSize += $LogIncrementSize
                                    }
    
                                    #When -WhatIf Switch, do not run
                                    if ($PSCmdlet.ShouldProcess("$step - File will grow to $([System.Math]::Round($($currentSize/1024.0), 2)) MB", "This action will grow the file $logfile on database $db to $([System.Math]::Round($($currentSize/1024.0), 2)) MB .`r`nDo you wish to continue?", "Perform grow")) {
                                        Write-Message -Level Verbose -Message "$step - Set size $logfile to $([System.Math]::Round($($currentSize/1024.0), 2)) MB"
                                        $logfile.size = $currentSize
    
                                        Write-Message -Level Verbose -Message "$step - Applying changes"
                                        $logfile.Alter()
                                        Write-Message -Level Verbose -Message "$step - Changes have been applied"
    
                                        #Will put the info like VolumeFreeSpace up to date
                                        $logfile.Refresh()
                                    }
                                }
    
                                Write-Message -Level Verbose -Message "`r`n$step - [OK] Growth process for logfile '$logfile' on database '$db', has been finished."
    
                                Write-Message -Level Verbose -Message "$step - Grow $logfile log file on $db database finished."
                            }
                        }
                    }
                    #else verifying existence
                    else {
                        Write-Message -Level Verbose -Message "Database '$db' does not exist on instance '$SqlInstance'."
                    }
    
                    #Get the number of VLFs
                    $currentVLFCount = Test-DbaDbVirtualLogFile -SqlInstance $server -Database $db
    
                    [pscustomobject]@{
                        ComputerName    = $server.ComputerName
                        InstanceName    = $server.ServiceName
                        SqlInstance     = $server.DomainInstanceName
                        Database        = $db
                        ID              = $logfile.ID
                        Name            = $logfile.Name
                        LogFileCount    = $numLogfiles
                        InitialSize     = [dbasize]($currentSizeMB * 1024 * 1024)
                        CurrentSize     = [dbasize]($TargetLogSize * 1024 * 1024)
                        InitialVLFCount = $initialVLFCount.Total
                        CurrentVLFCount = $currentVLFCount.Total
                    } | Select-DefaultView -ExcludeProperty LogFileCount
                } #foreach database
            } catch {
                Stop-Function -Message "Logfile $logfile on database $db not processed. Error: $($_.Exception.Message). Line Number:  $($_InvocationInfo.ScriptLineNumber)" -Continue
            }
        }
    
        end {
            Write-Message -Level Verbose -Message "Process finished $((Get-Date) - ($initialTime))"
            Test-DbaDeprecation -DeprecatedOn "1.0.0" -Alias Expand-SqlTLogResponsibly
            Test-DbaDeprecation -DeprecatedOn "1.0.0" -Parameter TargetLogSizeMB
            Test-DbaDeprecation -DeprecatedOn "1.0.0" -Parameter IncrementSizeMB
            Test-DbaDeprecation -DeprecatedOn "1.0.0" -Parameter ShrinkSizeMB
            Test-DbaDeprecation -DeprecatedOn "1.0.0" -Alias Expand-DbaTLogResponsibly
        }
    }
    #ValidationTags#Messaging#
    function Export-DbaAvailabilityGroup {
        <#
        .SYNOPSIS
            Exports SQL Server Availability Groups to a T-SQL file.
    
        .DESCRIPTION
            Exports SQL Server Availability Groups creation scripts to a T-SQL file. This is a function that is not available in SSMS.
    
        .PARAMETER SqlInstance
            The target SQL Server instance or instances. SQL Server 2012 and above supported.
    
        .PARAMETER SqlCredential
            Login to the target instance using alternative credentials. Windows and SQL Authentication supported. Accepts credential objects (Get-Credential)
    
        .PARAMETER Path
            The directory name where the output files will be written. A sub directory with the format 'ServerName$InstanceName' will be created. A T-SQL scripts named 'AGName.sql' will be created under this subdirectory for each scripted Availability Group.
    
        .PARAMETER AvailabilityGroup
            The Availability Group(s) to export - this list is auto-populated from the server. If unspecified, all logins will be processed.
    
        .PARAMETER ExcludeAvailabilityGroup
            The Availability Group(s) to exclude - this list is auto-populated from the server.
    
        .PARAMETER NoClobber
            Do not overwrite existing export files.
    
        .PARAMETER WhatIf
            Shows you what it'd output if you were to run the command
    
        .PARAMETER Confirm
            Confirms each step/line of output
    
        .PARAMETER EnableException
            By default, when something goes wrong we try to catch it, interpret it and give you a friendly warning message.
            This avoids overwhelming you with "sea of red" exceptions, but is inconvenient because it basically disables advanced scripting.
            Using this switch turns this "nice by default" feature off and enables you to catch exceptions with your own try/catch.
    
        .NOTES
            Tags: Hadr, HA, AG, AvailabilityGroup
            Author: Chris Sommer (@cjsommer), cjsommer.com
    
            dbatools PowerShell module (https://dbatools.io)
            Copyright: (c) 2018 by dbatools, licensed under MIT
            License: MIT https://opensource.org/licenses/MIT
    
        .LINK
            https://dbatools.io/Export-DbaAvailabilityGroup
    
        .EXAMPLE
            PS C:\> Export-DbaAvailabilityGroup -SqlInstance sql2012
    
            Exports all Availability Groups from SQL server "sql2012". Output scripts are written to the Documents\SqlAgExports directory by default.
    
        .EXAMPLE
            PS C:\> Export-DbaAvailabilityGroup -SqlInstance sql2012 -Path C:\temp\availability_group_exports
    
            Exports all Availability Groups from SQL server "sql2012". Output scripts are written to the C:\temp\availability_group_exports directory.
    
        .EXAMPLE
            PS C:\> Export-DbaAvailabilityGroup -SqlInstance sql2012 -Path 'C:\dir with spaces\availability_group_exports' -AvailabilityGroup AG1,AG2
    
            Exports Availability Groups AG1 and AG2 from SQL server "sql2012". Output scripts are written to the C:\dir with spaces\availability_group_exports directory.
    
        .EXAMPLE
            PS C:\> Export-DbaAvailabilityGroup -SqlInstance sql2014 -Path C:\temp\availability_group_exports -NoClobber
    
            Exports all Availability Groups from SQL server "sql2014". Output scripts are written to the C:\temp\availability_group_exports directory. If the export file already exists it will not be overwritten.
    
        #>
        [CmdletBinding(SupportsShouldProcess = $true)]
        param (
            [parameter(Mandatory, ValueFromPipeline)]
            [Alias("ServerInstance", "SqlServer")]
            [DbaInstanceParameter[]]$SqlInstance,
            [PSCredential]$SqlCredential,
            [object[]]$AvailabilityGroup,
            [object[]]$ExcludeAvailabilityGroup,
            [Alias("OutputLocation", "FilePath")]
            [string]$Path = "$([Environment]::GetFolderPath("MyDocuments"))\SqlAgExport",
            [switch]$NoClobber,
            [Alias('Silent')]
            [switch]$EnableException
        )
    
        process {
            foreach ($instance in $SqlInstance) {
                try {
                    $server = Connect-SqlInstance -SqlInstance $instance -SqlCredential $SqlCredential
                } catch {
                    Stop-Function -Message "Failure" -Category ConnectionError -ErrorRecord $_ -Target $instance -Continue
                }
    
                if ($server.IsHadrEnabled -eq $false) {
                    Stop-Function -Message "Hadr is not enabled on this instance" -Continue
                } else {
                    # Get all of the Availability Groups and filter if required
                    $ags = $server.AvailabilityGroups
                }
    
                if (Test-Bound 'AvailabilityGroup') {
                    $ags = $ags | Where-Object Name -In $AvailabilityGroup
                }
                if (Test-Bound 'ExcludeAvailabilityGroup') {
                    $ags = $ags | Where-Object Name -NotIn $ExcludeAvailabilityGroup
                }
    
                if ($ags) {
    
                    # Set and create the OutputLocation if it doesn't exist
                    $sqlinst = $instance.ToString().Replace('\', '$')
                    $outputLocation = "$Path\$sqlinst"
    
                    if (!(Test-Path $outputLocation -PathType Container)) {
                        $null = New-Item -Path $outputLocation -ItemType Directory -Force
                    }
    
                    # Script each Availability Group
                    foreach ($ag in $ags) {
                        $agName = $ag.Name
    
                        # Set the outfile name
                        if ($AppendDateToOutputFilename.IsPresent) {
                            $formatteddate = (Get-Date -Format 'yyyyMMdd_hhmm')
                            $outFile = "$outputLocation\${AGname}_${formatteddate}.sql"
                        } else {
                            $outFile = "$outputLocation\$agName.sql"
                        }
    
                        # Check NoClobber and script out the AG
                        if ($NoClobber.IsPresent -and (Test-Path -Path $outFile -PathType Leaf)) {
                            Write-Message -Level Warning -Message "OutputFile $outFile already exists. Skipping due to -NoClobber parameter"
                        } else {
                            Write-Message -Level Verbose -Message "Scripting Availability Group [$agName] on $instance to $outFile"
    
                            # Create comment block header for AG script
                            "/*" | Out-File -FilePath $outFile -Encoding ASCII -Force
                            " * Created by dbatools 'Export-DbaAvailabilityGroup' cmdlet on '$(Get-Date)'" | Out-File -FilePath $outFile -Encoding ASCII -Append
                            " * See https://dbatools.io/Export-DbaAvailabilityGroup for more help" | Out-File -FilePath $outFile -Encoding ASCII -Append
    
                            # Output AG and listener names
                            " *" | Out-File -FilePath $outFile -Encoding ASCII -Append
                            " * Availability Group Name: $($ag.name)" | Out-File -FilePath $outFile -Encoding ASCII -Append
                            $ag.AvailabilityGroupListeners | ForEach-Object { " * Listener Name: $($_.name)" } | Out-File -FilePath $outFile -Encoding ASCII -Append
    
                            # Output all replicas
                            " *" | Out-File -FilePath $outFile -Encoding ASCII -Append
                            $ag.AvailabilityReplicas | ForEach-Object { " * Replica: $($_.name)" } | Out-File -FilePath $outFile -Encoding ASCII -Append
    
                            # Output all databases
                            " *" | Out-File -FilePath $outFile -Encoding ASCII -Append
                            $ag.AvailabilityDatabases | ForEach-Object { " * Database: $($_.name)" } | Out-File -FilePath $outFile -Encoding ASCII -Append
    
                            # $ag | Select-Object -Property * | Out-File -FilePath $outFile -Encoding ASCII -Append
    
                            "*/" | Out-File -FilePath $outFile -Encoding ASCII -Append
    
                            # Script the AG
                            try {
                                $ag.Script() | Out-File -FilePath $outFile -Encoding ASCII -Append
                                Get-ChildItem $outFile
                            } catch {
                                Stop-Function -ErrorRecord $_ -Message "Error scripting out the availability groups. This is likely due to a bug in SMO." -Continue
                            }
                        }
                    }
                } else {
                    Write-Message -Level Output -Message "No Availability Groups detected on $instance"
                }
            }
        }
    }
    #ValidationTags#Messaging,FlowControl,Pipeline,CodeStyle#
    function Export-DbaCmsRegServer {
        <#
        .SYNOPSIS
            Exports registered servers and registered server groups to file
    
        .DESCRIPTION
            Exports registered servers and registered server groups to file
    
        .PARAMETER SqlInstance
            The target SQL Server instance or instances.
    
        .PARAMETER SqlCredential
            Login to the target instance using alternative credentials. Windows and SQL Authentication supported. Accepts credential objects (Get-Credential)
    
        .PARAMETER CredentialPersistenceType
            Used to specify how the login and passwords are persisted. Valid values include None, PersistLoginName and PersistLoginNameAndPassword.
    
        .PARAMETER Path
            The path to the exported file. If no path is specified, one will be created.
    
        .PARAMETER InputObject
            Enables piping from Get-DbaCmsRegServer, Get-DbaCmsRegServerGroup, CSVs and other objects.
    
            If importing from CSV or other object, a column named ServerName is required. Optional columns include Name, Description and Group.
    
        .PARAMETER EnableException
            By default, when something goes wrong we try to catch it, interpret it and give you a friendly warning message.
    
            This avoids overwhelming you with "sea of red" exceptions, but is inconvenient because it basically disables advanced scripting.
    
            Using this switch turns this "nice by default" feature off and enables you to catch exceptions with your own try/catch.
    
        .NOTES
            Tags: RegisteredServer, CMS
            Author: Chrissy LeMaire (@cl), netnerds.net
    
            Website: https://dbatools.io
            Copyright: (c) 2018 by dbatools, licensed under MIT
            License: MIT https://opensource.org/licenses/MIT
    
        .LINK
            https://dbatools.io/Export-DbaCmsRegServer
    
        .EXAMPLE
            PS C:\> Export-DbaCmsRegServer -SqlInstance sql2008
    
            Exports all Registered Server and Registered Server Groups on sql2008 to an automatically generated file name in the current directory
    
        .EXAMPLE
            PS C:\> Get-DbaCmsRegServer -SqlInstance sql2008, sql2012 | Export-DbaCmsRegServer
    
            Exports all registered servers on sql2008 and sql2012. Warning - each one will have its own individual file. Consider piping groups.
    
        .EXAMPLE
            PS C:\> Get-DbaCmsRegServerGroup -SqlInstance sql2008, sql2012 | Export-DbaCmsRegServer
    
            Exports all registered servers on sql2008 and sql2012, organized by group.
    
        #>
        [CmdletBinding()]
        [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSAvoidUsingPlainTextForPassword", "", Justification = "For Parameter CredentialPersistenceType")]
        param (
            [Alias("ServerInstance", "SqlServer")]
            [DbaInstanceParameter[]]$SqlInstance,
            [PSCredential]$SqlCredential,
            [parameter(ValueFromPipeline)]
            [object[]]$InputObject,
            [string]$Path,
            [ValidateSet("None", "PersistLoginName", "PersistLoginNameAndPassword")]
            [string]$CredentialPersistenceType = "None",
            [switch]$EnableException
        )
        begin {
            if ((Test-Bound -ParameterName Path)) {
                if ($Path -notmatch '\\') {
                    $Path = ".\$Path"
                }
    
                $directory = Split-Path $Path
                if (-not (Test-Path $directory)) {
                    New-Item -Path $directory -ItemType Directory
                }
            } else {
                $timeNow = (Get-Date -uformat "%m%d%Y%H%M%S")
            }
        }
        process {
            foreach ($instance in $SqlInstance) {
                $InputObject += Get-DbaCmsRegServerGroup -SqlInstance $instance -SqlCredential $SqlCredential -Id 1
            }
    
            foreach ($object in $InputObject) {
                try {
                    if ($object -is [Microsoft.SqlServer.Management.RegisteredServers.RegisteredServersStore]) {
                        $object = Get-DbaCmsRegServerGroup -SqlInstance $object.ServerConnection.SqlConnectionObject -Id 1
                    }
    
                    if ($object -is [Microsoft.SqlServer.Management.RegisteredServers.RegisteredServer]) {
                        if ((Test-Bound -ParameterName Path -Not)) {
                            $servername = $object.SqlInstance.Replace('\', '$')
                            $regservername = $object.Name.Replace('\', '$')
                            $Path = "$serverName-regserver-$regservername-$timeNow.xml"
                        }
                        $object.Export($Path, $CredentialPersistenceType)
                    } elseif ($object -is [Microsoft.SqlServer.Management.RegisteredServers.ServerGroup]) {
                        if ((Test-Bound -ParameterName Path -Not)) {
                            $servername = $object.SqlInstance.Replace('\', '$')
                            $regservergroup = $object.Name.Replace('\', '$')
                            $Path = "$serverName-reggroup-$regservergroup-$timeNow.xml"
                        }
                        $object.Export($Path, $CredentialPersistenceType)
                    } else {
                        Stop-Function -Message "InputObject is not a registered server or server group" -Continue
                    }
                    Get-ChildItem $Path -ErrorAction Stop
                } catch {
                    Stop-Function -Message "Failure" -ErrorRecord $_
                }
            }
        }
    
        end {
            Test-DbaDeprecation -DeprecatedOn "1.0.0" -Alias Export-DbaRegisteredServer
        }
    }
    function Export-DbaCredential {
        <#
        .SYNOPSIS
            Exports credentials INCLUDING PASSWORDS, unless specified otherwise, to sql file.
    
        .DESCRIPTION
            Exports credentials INCLUDING PASSWORDS, unless specified otherwise, to sql file.
    
            Requires remote Windows access if exporting the password.
    
        .PARAMETER SqlInstance
            The target SQL Server instance or instances.
    
        .PARAMETER SqlCredential
            Login to the target instance using alternative credentials. Windows and SQL Authentication supported. Accepts credential objects (Get-Credential)
    
        .PARAMETER Credential
            Login to the target OS using alternative credentials. Accepts credential objects (Get-Credential)
    
        .PARAMETER Path
            The path to the exported sql file.
    
        .PARAMETER Identity
            The credentials to export. If unspecified, all credentials will be exported.
    
        .PARAMETER InputObject
            Allow credentials to be piped in from Get-DbaCredential
    
        .PARAMETER ExcludePassword
            Exports the SQL credential without any sensitive information.
    
        .PARAMETER InputObject
            Allow credentials to be piped in from Get-DbaCredential
    
        .PARAMETER Append
            Append to Path
    
        .PARAMETER EnableException
            By default, when something goes wrong we try to catch it, interpret it and give you a friendly warning message.
            This avoids overwhelming you with "sea of red" exceptions, but is inconvenient because it basically disables advanced scripting.
            Using this switch turns this "nice by default" feature off and enables you to catch exceptions with your own try/catch.
    
        .NOTES
            Tags: Credential
            Author: Chrissy LeMaire (@cl), netnerds.net
    
            Website: https://dbatools.io
            Copyright: (c) 2018 by dbatools, licensed under MIT
            License: MIT https://opensource.org/licenses/MIT
    
        .EXAMPLE
            PS C:\> Export-DbaCredential -SqlInstance sql2017 -Path C:\temp\cred.sql
    
            Exports credentials, including passwords, from sql2017 to the file C:\temp\cred.sql
    
        #>
        [CmdletBinding()]
        param (
            [Parameter(Mandatory, ValueFromPipeline)]
            [Alias("ServerInstance", "SqlServer")]
            [DbaInstanceParameter[]]$SqlInstance,
            [string[]]$Identity,
            [PSCredential]$SqlCredential,
            [PSCredential]$Credential,
            [string]$Path,
            [switch]$ExcludePassword,
            [switch]$Append,
            [Microsoft.SqlServer.Management.Smo.Credential[]]$InputObject,
            [switch]$EnableException
        )
        process {
            foreach ($instance in $SqlInstance) {
                try {
                    $server = Connect-SqlInstance -SqlInstance $instance -SqlCredential $sqlcredential -MinimumVersion 9
                    $InputObject += $server.Credentials
                } catch {
                    Stop-Function -Message "Failure" -Category ConnectionError -ErrorRecord $_ -Target $instance -Continue
                }
    
                if ($Identity) {
                    $InputObject = $InputObject | Where-Object Identity -in $Identity
                }
    
                if (!(Test-SqlSa -SqlInstance $instance -SqlCredential $sqlcredential)) {
                    Stop-Function -Message "Not a sysadmin on $instance. Quitting." -Target $instance -Continue
                }
    
                Write-Message -Level Verbose -Message "Getting NetBios name for $instance."
                $sourceNetBios = Resolve-NetBiosName $server
    
                Write-Message -Level Verbose -Message "Checking if Remote Registry is enabled on $instance."
                try {
                    Invoke-Command2 -Raw -Credential $Credential -ComputerName $sourceNetBios -ScriptBlock { Get-ItemProperty -Path "HKLM:\SOFTWARE\" } -ErrorAction Stop
                } catch {
                    Stop-Function -Message "Can't connect to registry on $instance." -Target $sourceNetBios -ErrorRecord $_
                    return
                }
    
                if (-not (Test-Bound -ParameterName Path)) {
                    $timenow = (Get-Date -uformat "%m%d%Y%H%M%S")
                    $mydocs = [Environment]::GetFolderPath('MyDocuments')
                    $path = "$mydocs\$($server.name.replace('\', '$'))-$timenow-credential.sql"
                }
    
                $sql = @()
    
                if ($ExcludePassword) {
                    Stop-Function -Message "So sorry, there's no other way around it for now. The password has to be exported in plain text."
                    return
                } else {
                    try {
                        $creds = Get-DecryptedObject -SqlInstance $server -Type Credential
                    } catch {
                        Stop-Function -Continue -Message "Failure" -ErrorRecord $_
                    }
                    foreach ($currentCred in $creds) {
                        $name = $currentCred.Name.Replace("'", "''")
                        $identity = $currentCred.Identity.Replace("'", "''")
                        $password = $currentCred.Password.Replace("'", "''")
                        $sql += "CREATE CREDENTIAL $name WITH IDENTITY = N'$identity', SECRET = N'$password'"
                    }
                }
    
                try {
                    if ($Append) {
                        Add-Content -Path $path -Value $sql
                    } else {
                        Set-Content -Path $path -Value $sql
                    }
                    Get-ChildItem -Path $path
                } catch {
                    Stop-Function -Message "Can't write to $path" -ErrorRecord $_ -Continue
                }
    
    
                Write-Message -Level Verbose -Message "Attempting to migrate $credentialName"
                Get-ChildItem -Path $path
            }
        }
    }
    function Export-DbaDacPackage {
        <#
        .SYNOPSIS
            Exports a dacpac from a server.
    
        .DESCRIPTION
            Using SQLPackage, export a dacpac from an instance of SQL Server.
    
            Note - Extract from SQL Server is notoriously flaky - for example if you have three part references to external databases it will not work.
    
            For help with the extract action parameters and properties, refer to https://msdn.microsoft.com/en-us/library/hh550080(v=vs.103).aspx
    
        .PARAMETER SqlInstance
            The target SQL Server instance or instances.
    
        .PARAMETER SqlCredential
            Allows you to login to servers using alternative logins instead Integrated, accepts Credential object created by Get-Credential
    
        .PARAMETER Path
            The directory where the .dacpac files will be exported to. Defaults to documents.
    
        .PARAMETER Database
            The database(s) to process - this list is auto-populated from the server. If unspecified, all databases will be processed.
    
        .PARAMETER ExcludeDatabase
            The database(s) to exclude - this list is auto-populated from the server
    
        .PARAMETER AllUserDatabases
            Run command against all user databases
    
        .PARAMETER Type
            Selecting the type of the export: Dacpac (default) or Bacpac.
    
        .PARAMETER Table
            List of the tables to include into the export. Should be provided as an array of strings: dbo.Table1, Table2, Schema1.Table3.
    
        .PARAMETER DacOption
            Export options for a corresponding export type. Can be created by New-DbaDacOption -Type Dacpac | Bacpac
    
        .PARAMETER ExtendedParameters
            Optional parameters used to extract the DACPAC. More information can be found at
            https://msdn.microsoft.com/en-us/library/hh550080.aspx
    
        .PARAMETER ExtendedProperties
            Optional properties used to extract the DACPAC. More information can be found at
            https://msdn.microsoft.com/en-us/library/hh550080.aspx
    
        .PARAMETER EnableException
            By default, when something goes wrong we try to catch it, interpret it and give you a friendly warning message.
            This avoids overwhelming you with "sea of red" exceptions, but is inconvenient because it basically disables advanced scripting.
            Using this switch turns this "nice by default" feature off and enables you to catch exceptions with your own try/catch.
    
        .NOTES
            Tags: Migration, Database, Dacpac
            Author: Richie lee (@richiebzzzt)
    
            Website: https://dbatools.io
            Copyright: (c) 2018 by dbatools, licensed under MIT
            License: MIT https://opensource.org/licenses/MIT
    
        .LINK
            https://dbatools.io/Export-DbaDacPackage
    
        .EXAMPLE
            PS C:\> Export-DbaDacPackage -SqlInstance sql2016 -Database SharePoint_Config
    
            Exports the dacpac for SharePoint_Config on sql2016 to $home\Documents\SharePoint_Config.dacpac
    
        .EXAMPLE
            PS C:\> $options = New-DbaDacOption -Type Dacpac -Action Export
            PS C:\> $options.ExtractAllTableData = $true
            PS C:\> $options.CommandTimeout = 0
            PS C:\> Export-DbaDacPackage -SqlInstance sql2016 -Database DB1 -Options $options
    
            Uses DacOption object to set the CommandTimeout to 0 then extracts the dacpac for DB1 on sql2016 to $home\Documents\DB1.dacpac including all table data.
    
        .EXAMPLE
            PS C:\> Export-DbaDacPackage -SqlInstance sql2016 -AllUserDatabases -ExcludeDatabase "DBMaintenance","DBMonitoring" C:\temp
    
            Exports dacpac packages for all USER databases, excluding "DBMaintenance" & "DBMonitoring", on sql2016 and saves them to C:\temp
    
        .EXAMPLE
            PS C:\> $moreparams = "/OverwriteFiles:$true /Quiet:$true"
            PS C:\> Export-DbaDacPackage -SqlInstance sql2016 -Database SharePoint_Config -Path C:\temp -ExtendedParameters $moreparams
    
            Using extended parameters to over-write the files and performs the extraction in quiet mode. Uses command line instead of SMO behind the scenes.
        #>
        [CmdletBinding(DefaultParameterSetName = 'SMO')]
        param
        (
            [parameter(Mandatory, ValueFromPipeline)]
            [Alias("ServerInstance", "SqlServer")]
            [DbaInstance[]]$SqlInstance,
            [Alias("Credential")]
            [PSCredential]$SqlCredential,
            [object[]]$Database,
            [object[]]$ExcludeDatabase,
            [switch]$AllUserDatabases,
            [string]$Path = "$home\Documents",
            [parameter(ParameterSetName = 'SMO')]
            [Alias('ExtractOptions', 'ExportOptions', 'DacExtractOptions', 'DacExportOptions', 'Options', 'Option')]
            [object]$DacOption,
            [parameter(ParameterSetName = 'CMD')]
            [string]$ExtendedParameters,
            [parameter(ParameterSetName = 'CMD')]
            [string]$ExtendedProperties,
            [ValidateSet('Dacpac', 'Bacpac')]
            [string]$Type = 'Dacpac',
            [parameter(ParameterSetName = 'SMO')]
            [string[]]$Table,
            [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"
                return
            }
    
            if (-not (Test-Path $Path)) {
                Write-Message -Level Verbose "Assuming that $Path is a file path"
                $parentFolder = Split-Path $path -Parent
                if (-not (Test-Path $parentFolder)) {
                    Stop-Function -Message "$parentFolder doesn't exist or access denied"
                    return
                }
                $leaf = Split-Path $path -Leaf
                $fileName = Join-Path (Get-Item $parentFolder) $leaf
            } else {
                $fileItem = Get-Item $Path
                if ($fileItem -is [System.IO.DirectoryInfo]) {
                    $parentFolder = $fileItem.FullName
                } elseif ($fileItem -is [System.IO.FileInfo]) {
                    $fileName = $fileItem.FullName
                }
            }
    
            $dacfxPath = "$script:PSModuleRoot\bin\smo\Microsoft.SqlServer.Dac.dll"
            if ((Test-Path $dacfxPath) -eq $false) {
                Stop-Function -Message 'Dac Fx library not found.' -EnableException $EnableException
                return
            } else {
                try {
                    Add-Type -Path $dacfxPath
                    Write-Message -Level Verbose -Message "Dac Fx loaded."
                } catch {
                    Stop-Function -Message 'No usable version of Dac Fx found.' -ErrorRecord $_
                    return
                }
            }
    
            #check that at least one of the DB selection parameters was specified
            if (!$AllUserDatabases -and !$Database) {
                Stop-Function -Message "Either -Database or -AllUserDatabases should be specified" -Continue
            }
            #Check Option object types - should have a specific type
            if ($Type -eq 'Dacpac') {
                if ($DacOption -and $DacOption -isnot [Microsoft.SqlServer.Dac.DacExtractOptions]) {
                    Stop-Function -Message "Microsoft.SqlServer.Dac.DacExtractOptions object type is expected - got $($DacOption.GetType())."
                    return
                }
            } elseif ($Type -eq 'Bacpac') {
                if ($DacOption -and $DacOption -isnot [Microsoft.SqlServer.Dac.DacExportOptions]) {
                    Stop-Function -Message "Microsoft.SqlServer.Dac.DacExportOptions object type is expected - got $($DacOption.GetType())."
                    return
                }
            }
    
            #Create a tuple to be used as a table filter
            if ($Table) {
                $tblList = New-Object 'System.Collections.Generic.List[Tuple[String, String]]'
                foreach ($tableItem in $Table) {
                    $tableSplit = $tableItem.Split('.')
                    if ($tableSplit.Count -gt 1) {
                        $tblName = $tableSplit[-1]
                        $schemaName = $tableSplit[-2]
                    } else {
                        $tblName = [string]$tableSplit
                        $schemaName = 'dbo'
                    }
                    $tblList.Add((New-Object "tuple[String, String]" -ArgumentList $schemaName, $tblName))
                }
            } else {
                $tblList = $null
            }
    
            foreach ($instance in $sqlinstance) {
                try {
                    $server = Connect-SqlInstance -SqlInstance $instance -SqlCredential $sqlcredential
                } catch {
                    Stop-Function -Message "Failure" -Category ConnectionError -ErrorRecord $_ -Target $instance -Continue
                }
                $cleaninstance = $instance.ToString().Replace('\', '-')
    
                $dbs = Get-DbaDatabase -SqlInstance $server -OnlyAccessible -ExcludeAllSystemDb -Database $Database -ExcludeDatabase $ExcludeDatabase
                if (-not $dbs) {
                    Stop-Function -Message "Databases not found on $instance" -Target $instance -Continue
                }
    
                foreach ($db in $dbs) {
                    $dbname = $db.name
                    $connstring = $server.ConnectionContext.ConnectionString
                    if ($connstring -notmatch 'Database=') {
                        $connstring = "$connstring;Database=$dbname"
                    }
                    if ($fileName) {
                        $currentFileName = $fileName
                    } else {
                        if ($Type -eq 'Dacpac') { $ext = 'dacpac' }
                        elseif ($Type -eq 'Bacpac') { $ext = 'bacpac' }
                        $currentFileName = Join-Path $parentFolder "$cleaninstance-$dbname.$ext"
                    }
                    Write-Message -Level Verbose -Message "Using connection string $connstring"
    
                    #using SMO by default
                    if ($PsCmdlet.ParameterSetName -eq 'SMO') {
                        try {
                            $dacSvc = New-Object -TypeName Microsoft.SqlServer.Dac.DacServices -ArgumentList $connstring -ErrorAction Stop
                        } catch {
                            Stop-Function -Message "Could not connect to the connection string $connstring" -Target $instance -Continue
                        }
                        if (!$DacOption) {
                            $opts = New-DbaDacOption -Type $Type -Action Export
                        } else {
                            $opts = $DacOption
                        }
    
                        $null = $output = Register-ObjectEvent -InputObject $dacSvc -EventName "Message" -SourceIdentifier "msg" -Action { $EventArgs.Message.Message }
    
                        if ($Type -eq 'Dacpac') {
                            Write-Message -Level Verbose -Message "Initiating Dacpac extract to $currentFileName"
                            #not sure how to extract that info from the existing DAC application, leaving 1.0.0.0 for now
                            $version = New-Object System.Version -ArgumentList '1.0.0.0'
                            try {
                                $dacSvc.Extract($currentFileName, $dbname, $dbname, $version, $null, $tblList, $opts, $null)
                            } catch {
                                Stop-Function -Message "DacServices extraction failure" -ErrorRecord $_ -Continue
                            } finally {
                                Unregister-Event -SourceIdentifier "msg"
                            }
                        } elseif ($Type -eq 'Bacpac') {
                            Write-Message -Level Verbose -Message "Initiating Bacpac export to $currentFileName"
                            try {
                                $dacSvc.ExportBacpac($currentFileName, $dbname, $opts, $tblList, $null)
                            } catch {
                                Stop-Function -Message "DacServices export failure" -ErrorRecord $_ -Continue
                            } finally {
                                Unregister-Event -SourceIdentifier "msg"
                            }
                        }
                        $finalResult = ($output.output -join "`r`n" | Out-String).Trim()
                    } elseif ($PsCmdlet.ParameterSetName -eq 'CMD') {
                        if ($Type -eq 'Dacpac') { $action = 'Extract' }
                        elseif ($Type -eq 'Bacpac') { $action = 'Export' }
                        $cmdConnString = $connstring.Replace('"', "'")
    
                        $sqlPackageArgs = "/action:$action /tf:""$currentFileName"" /SourceConnectionString:""$cmdConnString"" $ExtendedParameters $ExtendedProperties"
                        $resultstime = [diagnostics.stopwatch]::StartNew()
    
                        try {
                            $startprocess = New-Object System.Diagnostics.ProcessStartInfo
                            $startprocess.FileName = "$script:PSModuleRoot\bin\smo\sqlpackage.exe"
                            $startprocess.Arguments = $sqlPackageArgs
                            $startprocess.RedirectStandardError = $true
                            $startprocess.RedirectStandardOutput = $true
                            $startprocess.UseShellExecute = $false
                            $startprocess.CreateNoWindow = $true
                            $process = New-Object System.Diagnostics.Process
                            $process.StartInfo = $startprocess
                            $process.Start() | Out-Null
                            $stdout = $process.StandardOutput.ReadToEnd()
                            $stderr = $process.StandardError.ReadToEnd()
                            $process.WaitForExit()
                            Write-Message -level Verbose -Message "StandardOutput: $stdout"
                            $finalResult = $stdout
                        } catch {
                            Stop-Function -Message "SQLPackage Failure" -ErrorRecord $_ -Continue
                        }
    
                        if ($process.ExitCode -ne 0) {
                            Stop-Function -Message "Standard output - $stderr" -Continue
                        }
                    }
                    [pscustomobject]@{
                        ComputerName = $server.ComputerName
                        InstanceName = $server.ServiceName
                        SqlInstance  = $server.DomainInstanceName
                        Database     = $dbname
                        Path         = $currentFileName
                        Elapsed      = [prettytimespan]($resultstime.Elapsed)
                        Result       = $finalResult
                    } | Select-DefaultView -ExcludeProperty ComputerName, InstanceName
                }
            }
        }
        end {
            Test-DbaDeprecation -DeprecatedOn "1.0.0" -EnableException:$false -Alias Export-DbaDacpac
        }
    }
    function Export-DbaDiagnosticQuery {
        <#
        .SYNOPSIS
            Export-DbaDiagnosticQuery can convert output 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 Finke (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) 2018 by dbatools, licensed under MIT
            License: MIT https://opensource.org/licenses/MIT
    
        .LINK
            https://dbatools.io/Export-DbaDiagnosticQuery
    
        .EXAMPLE
            PS C:\> Invoke-DbaDiagnosticQuery -SqlInstance sql2016 | Export-DbaDiagnosticQuery -Path c:\temp
    
            Converts output from Invoke-DbaDiagnosticQuery to multiple CSV files
    
        .EXAMPLE
            PS C:\> $output = Invoke-DbaDiagnosticQuery -SqlInstance sql2016
            PS C:\> Export-DbaDiagnosticQuery -InputObject $output -ConvertTo Excel
    
            Converts output from Invoke-DbaDiagnosticQuery to Excel worksheet(s) in the Documents folder
    
        #>
        [CmdletBinding()]
        param (
            [parameter(Mandatory, ValueFromPipeline)]
            [object[]]$InputObject,
            [ValidateSet("Excel", "Csv")]
            [string]$ConvertTo = "Csv",
            [System.IO.FileInfo]$Path = [Environment]::GetFolderPath("mydocuments"),
            [string]$Suffix = "$(Get-Date -format 'yyyyMMddHHmmssms')",
            [switch]$NoPlanExport,
            [switch]$NoQueryExport,
            [Alias('Silent')]
            [switch]$EnableException
        )
    
        begin {
            if ($ConvertTo -eq "Excel") {
                try {
                    Import-Module ImportExcel -ErrorAction Stop
                } catch {
                    $message = "Failed to load module, exporting to Excel feature is not available
                                Install the module from: https://github.com/dfinke/ImportExcel
                                Valid alternative conversion format is csv"
                    Stop-Function -Message $message
                    return
                }
            }
    
            if (!$(Test-Path $Path)) {
                try {
                    New-Item $Path -ItemType Directory -ErrorAction Stop | Out-Null
                    Write-Message -Level Output -Message "Created directory $Path"
                } catch {
                    Stop-Function -Message "Failed to create directory $Path" -Continue
                }
            }
        }
    
        process {
            if (Test-FunctionInterrupt) { return }
    
            foreach ($row in $InputObject) {
                $result = $row.Result
                $name = $row.Name
                $SqlInstance = $row.SqlInstance.Replace("\", "$")
                $dbname = $row.Database
                $number = $row.Number
    
                if ($null -eq $result) {
                    Stop-Function -Message "Result was empty for $name" -Target $result -Continue
                }
    
                $queryname = Remove-InvalidFileNameChars -Name $Name
                $excelfilename = "$Path\$SqlInstance-DQ-$Suffix.xlsx"
                $exceldbfilename = "$Path\$SqlInstance-DQ-$dbname-$Suffix.xlsx"
                $csvdbfilename = "$Path\$SqlInstance-$dbname-DQ-$number-$queryname-$Suffix.csv"
                $csvfilename = "$Path\$SqlInstance-DQ-$number-$queryname-$Suffix.csv"
    
                $columnnameoptions = "Query Plan", "QueryPlan", "Query_Plan", "query_plan_xml"
                if (($result | Get-Member | Where-Object Name -in $columnnameoptions).Count -gt 0) {
                    $plannr = 0
                    $columnname = ($result | Get-Member | Where-Object Name -In $columnnameoptions).Name
                    foreach ($plan in $result."$columnname") {
                        $plannr += 1
                        if ($row.DatabaseSpecific) {
                            $planfilename = "$Path\$SqlInstance-$dbname-DQ-$number-$queryname-$plannr-$Suffix.sqlplan"
                        } else {
                            $planfilename = "$Path\$SqlInstance-DQ-$number-$queryname-$plannr-$Suffix.sqlplan"
                        }
    
                        if (!$NoPlanExport) {
                            Write-Message -Level Output -Message "Exporting $planfilename"
                            if ($plan) {$plan | Out-File -FilePath $planfilename}
                        }
                    }
    
                    $result = $result | Select-Object * -ExcludeProperty "$columnname"
                }
    
                $columnnameoptions = "Complete Query Text", "QueryText", "Query Text", "Query_Text", "query_sql_text"
                if (($result | Get-Member | Where-Object Name -In $columnnameoptions ).Count -gt 0) {
                    $sqlnr = 0
                    $columnname = ($result | Get-Member | Where-Object Name -In $columnnameoptions).Name
                    foreach ($sql in $result."$columnname") {
                        $sqlnr += 1
                        if ($row.DatabaseSpecific) {
                            $sqlfilename = "$Path\$SqlInstance-$dbname-DQ-$number-$queryname-$sqlnr-$Suffix.sql"
                        } else {
                            $sqlfilename = "$Path\$SqlInstance-DQ-$number-$queryname-$sqlnr-$Suffix.sql"
                        }
    
                        if (!$NoQueryExport) {
                            Write-Message -Level Output -Message "Exporting $sqlfilename"
                            if ($sql) {$sql | Out-File -FilePath $sqlfilename}
                        }
                    }
    
                    $result = $result | Select-Object * -ExcludeProperty "$columnname"
                }
    
                switch ($ConvertTo) {
                    "Excel" {
                        if ($row.DatabaseSpecific) {
                            Write-Message -Level Output -Message "Exporting $exceldbfilename"
                            $result | Export-Excel -Path $exceldbfilename -WorkSheetname $Name -AutoSize -AutoFilter -BoldTopRow -FreezeTopRow
                        } else {
                            Write-Message -Level Output -Message "Exporting $excelfilename"
                            $result | Export-Excel -Path $excelfilename -WorkSheetname $Name -AutoSize -AutoFilter -BoldTopRow -FreezeTopRow
                        }
                    }
                    "csv" {
                        if ($row.DatabaseSpecific) {
                            Write-Message -Level Output -Message "Exporting $csvdbfilename"
                            $result | Export-Csv -Path $csvdbfilename -NoTypeInformation -Append
                        } else {
                            Write-Message -Level Output -Message "Exporting $csvfilename"
                            $result | Export-Csv -Path $csvfilename -NoTypeInformation -Append
                        }
                    }
                }
            }
        }
    }
    #ValidationTags#Messaging,FlowControl,Pipeline,CodeStyle#
    function Export-DbaExecutionPlan {
        <#
        .SYNOPSIS
            Exports execution plans to disk.
    
        .DESCRIPTION
            Exports execution plans to disk. Can pipe from Get-DbaExecutionPlan
    
            Thanks to
            https://www.simple-talk.com/sql/t-sql-programming/dmvs-for-query-plan-metadata/
            and
            http://www.scarydba.com/2017/02/13/export-plans-cache-sqlplan-file/
            for the idea and query.
    
        .PARAMETER SqlInstance
            The target SQL Server instance or instances.
    
        .PARAMETER SqlCredential
            Credential object used to connect to the SQL Server as a different user
    
        .PARAMETER Database
            The database(s) to process - this list is auto-populated from the server. If unspecified, all databases will be processed.
    
        .PARAMETER ExcludeDatabase
            The database(s) to exclude - this list is auto-populated from the server
    
        .PARAMETER SinceCreation
            Datetime object used to narrow the results to a date
    
        .PARAMETER SinceLastExecution
            Datetime object used to narrow the results to a date
    
        .PARAMETER Path
            The directory where all of the sqlxml files will be exported
    
        .PARAMETER WhatIf
            Shows what would happen if the command were to run. No actions are actually performed.
    
        .PARAMETER Confirm
            Prompts you for confirmation before executing any changing operations within the command.
    
        .PARAMETER PipedObject
            Internal parameter
    
        .PARAMETER EnableException
            By default, when something goes wrong we try to catch it, interpret it and give you a friendly warning message.
            This avoids overwhelming you with "sea of red" exceptions, but is inconvenient because it basically disables advanced scripting.
            Using this switch turns this "nice by default" feature off and enables you to catch exceptions with your own try/catch.
    
        .NOTES
            Tags: Performance, ExecutionPlan
            Author: Chrissy LeMaire (@cl), netnerds.net
    
            Website: https://dbatools.io
            Copyright: (c) 2018 by dbatools, licensed under MIT
            License: MIT https://opensource.org/licenses/MIT
    
        .LINK
            https://dbatools.io/Export-DbaExecutionPlan
    
        .EXAMPLE
            PS C:\> Export-DbaExecutionPlan -SqlInstance sqlserver2014a -Path C:\Temp
    
            Exports all execution plans for sqlserver2014a. Files saved in to C:\Temp
    
        .EXAMPLE
            PS C:\> Export-DbaExecutionPlan -SqlInstance sqlserver2014a -Database db1, db2 -SinceLastExecution '2016-07-01 10:47:00' -Path C:\Temp
    
            Exports all execution plans for databases db1 and db2 on sqlserver2014a since July 1, 2016 at 10:47 AM. Files saved in to C:\Temp
    
        .EXAMPLE
            PS C:\> Get-DbaExecutionPlan -SqlInstance sqlserver2014a | Export-DbaExecutionPlan -Path C:\Temp
    
            Gets all execution plans for sqlserver2014a. Using Pipeline exports them all to C:\Temp
    
        .EXAMPLE
            PS C:\> Get-DbaExecutionPlan -SqlInstance sqlserver2014a | Export-DbaExecutionPlan -Path C:\Temp -WhatIf
    
            Gets all execution plans for sqlserver2014a. Then shows what would happen if the results where piped to Export-DbaExecutionPlan
    
        #>
        [cmdletbinding(SupportsShouldProcess = $true, DefaultParameterSetName = "Default")]
        param (
            [parameter(ParameterSetName = 'NotPiped', Mandatory)]
            [Alias("ServerInstance", "SqlServer")]
            [DbaInstanceParameter[]]$SqlInstance,
            [parameter(ParameterSetName = 'NotPiped')]
            [PSCredential]$SqlCredential,
            [Alias("Databases")]
            [object[]]$Database,
            [object[]]$ExcludeDatabase,
            [parameter(ParameterSetName = 'Piped', Mandatory)]
            [parameter(ParameterSetName = 'NotPiped', Mandatory)]
            [string]$Path,
            [parameter(ParameterSetName = 'NotPiped')]
            [datetime]$SinceCreation,
            [parameter(ParameterSetName = 'NotPiped')]
            [datetime]$SinceLastExecution,
            [Parameter(ParameterSetName = 'Piped', Mandatory, ValueFromPipeline)]
            [object[]]$PipedObject,
            [Alias('Silent')]
            [switch]$EnableException
        )
    
        begin {
    
            function Export-Plan {
                param(
                    [object]$object
                )
                $instanceName = $object.SqlInstance
                $dbName = $object.DatabaseName
                $queryPosition = $object.QueryPosition
                $sqlHandle = "0x"; $object.SqlHandle | ForEach-Object { $sqlHandle += ("{0:X}" -f $_).PadLeft(2, "0") }
                $sqlHandle = $sqlHandle.TrimStart('0x02000000').TrimEnd('0000000000000000000000000000000000000000')
                $shortName = "$instanceName-$dbName-$queryPosition-$sqlHandle"
    
                foreach ($queryPlan in $object.BatchQueryPlanRaw) {
                    $fileName = "$path\$shortName-batch.sqlplan"
    
                    try {
                        if ($Pscmdlet.ShouldProcess("localhost", "Writing XML file to $fileName")) {
                            $queryPlan.Save($fileName)
                        }
                    } catch {
                        Stop-Function -Message "Skipped query plan for $fileName because it is null." -Target $fileName -ErrorRecord $_ -Continue
                    }
                }
    
                foreach ($statementPlan in $object.SingleStatementPlanRaw) {
                    $fileName = "$path\$shortName.sqlplan"
    
                    try {
                        if ($Pscmdlet.ShouldProcess("localhost", "Writing XML file to $fileName")) {
                            $statementPlan.Save($fileName)
                        }
                    } catch {
                        Stop-Function -Message "Skipped statement plan for $fileName because it is null." -Target $fileName -ErrorRecord $_ -Continue
                    }
                }
    
                if ($Pscmdlet.ShouldProcess("console", "Showing output object")) {
                    Add-Member -Force -InputObject $object -MemberType NoteProperty -Name OutputFile -Value $fileName
                    Select-DefaultView -InputObject $object -Property ComputerName, InstanceName, SqlInstance, DatabaseName, SqlHandle, CreationTime, LastExecutionTime, OutputFile
                }
            }
        }
    
        process {
            if (!(Test-Path $Path)) {
                $null = New-Item -ItemType Directory -Path $Path
            }
    
            if ($PipedObject) {
                foreach ($object in $pipedobject) {
                    Export-Plan $object
                    return
                }
            }
    
            foreach ($instance in $SqlInstance) {
                try {
                    $server = Connect-SqlInstance -SqlInstance $instance -SqlCredential $SqlCredential -MinimumVersion 9
                } catch {
                    Stop-Function -Message "Failure" -Category ConnectionError -ErrorRecord $_ -Target $instance -Continue
                }
    
                $select = "SELECT DB_NAME(deqp.dbid) as DatabaseName, OBJECT_NAME(deqp.objectid) as ObjectName,
                        detqp.query_plan AS SingleStatementPlan,
                        deqp.query_plan AS BatchQueryPlan,
                        ROW_NUMBER() OVER ( ORDER BY Statement_Start_offset ) AS QueryPosition,
                        sql_handle as SqlHandle,
                        plan_handle as PlanHandle,
                        creation_time as CreationTime,
                        last_execution_time as LastExecutionTime"
    
                $from = " FROM sys.dm_exec_query_stats deqs
                            CROSS APPLY sys.dm_exec_text_query_plan(deqs.plan_handle,
                                deqs.statement_start_offset,
                                deqs.statement_end_offset) AS detqp
                            CROSS APPLY sys.dm_exec_query_plan(deqs.plan_handle) AS deqp
                            CROSS APPLY sys.dm_exec_sql_text(deqs.plan_handle) AS execText"
    
                if ($ExcludeDatabase -or $Database -or $SinceCreation -or $SinceLastExecution -or $ExcludeEmptyQueryPlan -eq $true) {
                    $where = " WHERE "
                }
    
                $whereArray = @()
    
                if ($Database -gt 0) {
                    $dbList = $Database -join "','"
                    $whereArray += " DB_NAME(deqp.dbid) in ('$dbList') "
                }
    
                if (Test-Bound 'SinceCreation') {
                    Write-Message -Level Verbose -Message "Adding creation time"
                    $whereArray += " creation_time >= '" + $SinceCreation.ToString("yyyy-MM-dd HH:mm:ss") + "' "
                }
    
                if (Test-Bound 'SinceLastExecution') {
                    Write-Message -Level Verbose -Message "Adding last execution time"
                    $whereArray += " last_execution_time >= '" + $SinceLastExecution.ToString("yyyy-MM-dd HH:mm:ss") + "' "
                }
    
                if (Test-Bound 'ExcludeDatabase') {
                    $dbList = $ExcludeDatabase -join "','"
                    $whereArray += " DB_NAME(deqp.dbid) not in ('$dbList') "
                }
    
                if (Test-Bound 'ExcludeEmptyQueryPlan') {
                    $whereArray += " detqp.query_plan is not null"
                }
    
                if ($where.Length -gt 0) {
                    $whereArray = $whereArray -join " and "
                    $where = "$where $whereArray"
                }
    
                $sql = "$select $from $where"
                Write-Message -Level Debug -Message "SQL Statement: $sql"
                try {
                    $dataTable = $server.ConnectionContext.ExecuteWithResults($sql).Tables
                } catch {
                    Stop-Function -Message "Issue collecting execution plans" -Target $instance -ErroRecord $_ -Continue
                }
    
                foreach ($row in ($dataTable.Rows)) {
                    $sqlHandle = "0x"; $row.sqlhandle | ForEach-Object { $sqlHandle += ("{0:X}" -f $_).PadLeft(2, "0") }
                    $planhandle = "0x"; $row.planhandle | ForEach-Object { $planhandle += ("{0:X}" -f $_).PadLeft(2, "0") }
    
                    $object = [pscustomobject]@{
                        ComputerName           = $server.ComputerName
                        InstanceName           = $server.ServiceName
                        SqlInstance            = $server.DomainInstanceName
                        DatabaseName           = $row.DatabaseName
                        SqlHandle              = $sqlHandle
                        PlanHandle             = $planhandle
                        SingleStatementPlan    = $row.SingleStatementPlan
                        BatchQueryPlan         = $row.BatchQueryPlan
                        QueryPosition          = $row.QueryPosition
                        CreationTime           = $row.CreationTime
                        LastExecutionTime      = $row.LastExecutionTime
                        BatchQueryPlanRaw      = [xml]$row.BatchQueryPlan
                        SingleStatementPlanRaw = [xml]$row.SingleStatementPlan
                    }
                    Export-Plan $object
                }
            }
        }
    }
    function Export-DbaInstance {
        <#
        .SYNOPSIS
            Exports SQL Server *ALL* database restore scripts, logins, database mail profiles/accounts, credentials, SQL Agent objects, linked servers,
            Central Management Server objects, server configuration settings (sp_configure), user objects in systems databases,
            system triggers and backup devices from one SQL Server to another.
    
            For more granular control, please use one of the -Exclude parameters and use the other functions available within the dbatools module.
    
        .DESCRIPTION
            Export-DbaInstance consolidates most of the export scripts in dbatools into one command.
    
            This is useful when you're looking to Export entire instances. It less flexible than using the underlying functions.
            Think of it as an easy button. Unless an -Exclude is specified, it exports:
    
            All database restore scripts.
            All logins.
            All database mail objects.
            All credentials.
            All objects within the Job Server (SQL Agent).
            All linked servers.
            All groups and servers within Central Management Server.
            All SQL Server configuration objects (everything in sp_configure).
            All user objects in system databases.
            All system triggers.
            All system backup devices.
            All Audits.
            All Endpoints.
            All Extended Events.
            All Policy Management objects.
            All Resource Governor objects.
            All Server Audit Specifications.
            All Custom Errors (User Defined Messages).
            All Server Roles.
            All Availability Groups.
    
        .PARAMETER SqlInstance
            The target SQL Server instances
    
        .PARAMETER SqlCredential
            Login to the target instance using alternative credentials. Windows and SQL Authentication supported. Accepts credential objects (Get-Credential)
    
        .PARAMETER Credential
            Alternative Windows credentials for exporting Linked Servers and Credentials. Accepts credential objects (Get-Credential)
    
        .PARAMETER Path
            The path to the export file
    
        .PARAMETER SharedPath
            Specifies the network location for the backup files. The SQL Server service accounts on both Source and Destination must have read/write permission to access this location.
    
        .PARAMETER WithReplace
            If this switch is used, databases are restored from backup using WITH REPLACE. This is useful if you want to stage some complex file paths.
    
        .PARAMETER NoRecovery
            If this switch is used, databases will be left in the No Recovery state to enable further backups to be added.
    
        .PARAMETER IncludeDbMasterKey
            Exports the db master key then logs into the server to copy it to the $Path
    
        .PARAMETER Exclude
            Exclude one or more objects to export
    
            Databases
            Logins
            AgentServer
            Credentials
            LinkedServers
            SpConfigure
            CentralManagementServer
            DatabaseMail
            SysDbUserObjects
            SystemTriggers
            BackupDevices
            Audits
            Endpoints
            ExtendedEvents
            PolicyManagement
            ResourceGovernor
            ServerAuditSpecifications
            CustomErrors
            ServerRoles
            AvailabilityGroups
            ReplicationSettings
    
        .PARAMETER BatchSeparator
            Batch separator for scripting output. "GO" by default.
    
        .PARAMETER ScriptingOption
            Add scripting options to scripting output for all objects except Registered Servers and Extended Events.
    
        .PARAMETER EnableException
            By default, when something goes wrong we try to catch it, interpret it and give you a friendly warning message.
            This avoids overwhelming you with "sea of red" exceptions, but is inconvenient because it basically disables advanced scripting.
            Using this switch turns this "nice by default" feature off and enables you to catch exceptions with your own try/catch.
    
        .NOTES
            Tags: Export
            Author: Chrissy LeMaire (@cl), netnerds.net
    
            Website: https://dbatools.io
            Copyright: (c) 2018 by dbatools, licensed under MIT
            License: MIT https://opensource.org/licenses/MIT
    
        .LINK
            https://dbatools.io/Export-DbaInstance
    
        .EXAMPLE
            PS C:\> Export-DbaInstance -SqlInstance sqlserver\instance
    
            All databases, logins, job objects and sp_configure options will be exported from
            sqlserver\instance to an automatically generated folder name in Documents.
    
        .EXAMPLE
            PS C:\> Export-DbaInstance -SqlInstance sqlcluster -Exclude Databases, Logins -Path C:\dr\sqlcluster
    
            Exports everything but logins and database restore scripts to C:\dr\sqlcluster
    
        #>
        [CmdletBinding()]
        param (
            [parameter(Mandatory, ValueFromPipeline)]
            [Alias("ServerInstance", "SqlServer")]
            [DbaInstanceParameter[]]$SqlInstance,
            [PSCredential]$SqlCredential,
            [PSCredential]$Credential,
            [string]$Path,
            [switch]$NoRecovery,
            [switch]$IncludeDbMasterKey,
            [ValidateSet('Databases', 'Logins', 'AgentServer', 'Credentials', 'LinkedServers', 'SpConfigure', 'CentralManagementServer', 'DatabaseMail', 'SysDbUserObjects', 'SystemTriggers', 'BackupDevices', 'Audits', 'Endpoints', 'ExtendedEvents', 'PolicyManagement', 'ResourceGovernor', 'ServerAuditSpecifications', 'CustomErrors', 'ServerRoles', 'AvailabilityGroups', 'ReplicationSettings')]
            [string[]]$Exclude,
            [string]$BatchSeparator = 'GO',
            [Microsoft.SqlServer.Management.Smo.ScriptingOptions]$ScriptingOption,
            [switch]$EnableException
        )
        begin {
            if ((Test-Bound -ParameterName Path)) {
                if (-not ((Get-Item $Path -ErrorAction Ignore) -is [System.IO.DirectoryInfo])) {
                    Stop-Function -Message "Path must be a directory"
                }
            }
    
            if (-not $ScriptingOption) {
                $ScriptingOption = New-DbaScriptingOption
            }
    
            $elapsed = [System.Diagnostics.Stopwatch]::StartNew()
            $started = Get-Date
    
            $ScriptingOptions = New-Object Microsoft.SqlServer.Management.Smo.ScriptingOptions
            $ScriptingOptions.ScriptBatchTerminator = $true
    
        }
        process {
            if (Test-FunctionInterrupt) { return }
            foreach ($instance in $SqlInstance) {
                $stepCounter = $filecounter = 0
                try {
                    $server = Connect-SqlInstance -SqlInstance $instance -SqlCredential $sqlcredential -MinimumVersion 10
                } catch {
                    Stop-Function -Message "Failure" -Category ConnectionError -ErrorRecord $_ -Target $instance -Continue
                }
    
                if (-not (Test-Bound -ParameterName Path)) {
                    $timenow = (Get-Date -uformat "%m%d%Y%H%M%S")
                    $mydocs = [Environment]::GetFolderPath('MyDocuments')
                    $path = "$mydocs\$($server.name.replace('\', '$'))-$timenow"
                }
    
                if (-not (Test-Path $Path)) {
                    try {
                        $null = New-Item -ItemType Directory -Path $Path -ErrorAction Stop
                    } catch {
                        Stop-Function -Message "Failure" -ErrorRecord $_
                        return
                    }
                }
    
                if ($Exclude -notcontains 'SpConfigure') {
                    $fileCounter++
                    Write-Message -Level Verbose -Message "Exporting SQL Server Configuration"
                    Write-ProgressHelper -StepNumber ($stepCounter++) -Message "Exporting SQL Server Configuration"
                    Export-DbaSpConfigure -SqlInstance $server -Path "$Path\$fileCounter-sp_configure.sql"
                    if (-not (Test-Path "$Path\$fileCounter-sp_configure.sql")) {
                        $fileCounter--
                    }
                }
    
                if ($Exclude -notcontains 'CustomErrors') {
                    $fileCounter++
                    Write-Message -Level Verbose -Message "Exporting custom errors (user defined messages)"
                    Write-ProgressHelper -StepNumber ($stepCounter++) -Message "Exporting custom errors (user defined messages)"
                    $null = Get-DbaCustomError -SqlInstance $server | Export-DbaScript -Path "$Path\$fileCounter-customererrors.sql" -Append -BatchSeparator $BatchSeparator -ScriptingOptionsObject $ScriptingOption
                    Get-ChildItem -ErrorAction Ignore -Path "$Path\$fileCounter-customererrors.sql"
                    if (-not (Test-Path "$Path\$fileCounter-customererrors.sql")) {
                        $fileCounter--
                    }
                }
    
                if ($Exclude -notcontains 'ServerRoles') {
                    $fileCounter++
                    Write-Message -Level Verbose -Message "Exporting server roles"
                    Write-ProgressHelper -StepNumber ($stepCounter++) -Message "Exporting server roles"
                    $null = Get-DbaServerRole -SqlInstance $server | Export-DbaScript -Path "$Path\$fileCounter-serverroles.sql" -Append -BatchSeparator $BatchSeparator -ScriptingOptionsObject $ScriptingOption
                    Get-ChildItem -ErrorAction Ignore -Path "$Path\$fileCounter-serverroles.sql"
                    if (-not (Test-Path "$Path\$fileCounter-serverroles.sql")) {
                        $fileCounter--
                    }
                }
    
                if ($Exclude -notcontains 'Credentials') {
                    $fileCounter++
                    Write-Message -Level Verbose -Message "Exporting SQL credentials"
                    Write-ProgressHelper -StepNumber ($stepCounter++) -Message "Exporting SQL credentials"
                    $null = Export-DbaCredential -SqlInstance $server -Credential $Credential -Path "$Path\$fileCounter-credentials.sql" -Append
                    Get-ChildItem -ErrorAction Ignore -Path "$Path\$fileCounter-credentials.sql"
                    if (-not (Test-Path "$Path\$fileCounter-credentials.sql")) {
                        $fileCounter--
                    }
                }
    
                if ($Exclude -notcontains 'DatabaseMail') {
                    $fileCounter++
                    Write-Message -Level Verbose -Message "Exporting database mail"
                    Write-ProgressHelper -StepNumber ($stepCounter++) -Message "Exporting database mail"
                    $null = Get-DbaDbMailConfig -SqlInstance $server | Export-DbaScript -Path "$Path\$fileCounter-dbmail.sql" -Append -BatchSeparator $BatchSeparator -ScriptingOptionsObject $ScriptingOption
                    $null = Get-DbaDbMailAccount -SqlInstance $server | Export-DbaScript -Path "$Path\$fileCounter-dbmail.sql" -Append -BatchSeparator $BatchSeparator -ScriptingOptionsObject $ScriptingOption
                    $null = Get-DbaDbMailProfile -SqlInstance $server | Export-DbaScript -Path "$Path\$fileCounter-dbmail.sql" -Append -BatchSeparator $BatchSeparator -ScriptingOptionsObject $ScriptingOption
                    $null = Get-DbaDbMailServer -SqlInstance $server | Export-DbaScript -Path "$Path\$fileCounter-dbmail.sql" -Append -BatchSeparator $BatchSeparator -ScriptingOptionsObject $ScriptingOption
                    $null = Get-DbaDbMail -SqlInstance $server | Export-DbaScript -Path "$Path\$fileCounter-dbmail.sql" -Append -BatchSeparator $BatchSeparator -ScriptingOptionsObject $ScriptingOption
                    Get-ChildItem -ErrorAction Ignore -Path "$Path\$fileCounter-dbmail.sql"
                    if (-not (Test-Path "$Path\$fileCounter-dbmail.sql")) {
                        $fileCounter--
                    }
                }
    
                if ($Exclude -notcontains 'CentralManagementServer') {
                    $fileCounter++
                    Write-Message -Level Verbose -Message "Exporting Central Management Server"
                    Write-ProgressHelper -StepNumber ($stepCounter++) -Message "Exporting Central Management Server"
                    $null = Get-DbaCmsRegServerGroup -SqlInstance $server | Export-DbaScript -Path "$Path\$fileCounter-regserver.sql" -Append -BatchSeparator 'GO'
                    $null = Get-DbaCmsRegServer -SqlInstance $server | Export-DbaScript -Path "$Path\$fileCounter-regserver.sql" -Append -BatchSeparator 'GO'
                    Get-ChildItem -ErrorAction Ignore -Path "$Path\$fileCounter-regserver.sql"
                    if (-not (Test-Path "$Path\$fileCounter-regserver.sql")) {
                        $fileCounter--
                    }
                }
    
                if ($Exclude -notcontains 'BackupDevices') {
                    $fileCounter++
                    Write-Message -Level Verbose -Message "Exporting Backup Devices"
                    Write-ProgressHelper -StepNumber ($stepCounter++) -Message "Exporting Backup Devices"
                    $null = Get-DbaBackupDevice -SqlInstance $server | Export-DbaScript -Path "$Path\$fileCounter-backupdevices.sql" -Append -BatchSeparator $BatchSeparator -ScriptingOptionsObject $ScriptingOption
                    Get-ChildItem -ErrorAction Ignore -Path "$Path\$fileCounter-backupdevices.sql"
                    if (-not (Test-Path "$Path\$fileCounter-backupdevices.sql")) {
                        $fileCounter--
                    }
                }
    
                if ($Exclude -notcontains 'LinkedServers') {
                    $fileCounter++
                    Write-Message -Level Verbose -Message "Exporting linked servers"
                    Write-ProgressHelper -StepNumber ($stepCounter++) -Message "Exporting linked servers"
                    Export-DbaLinkedServer -SqlInstance $server -Path "$Path\$fileCounter-linkedservers.sql" -Credential $Credential -Append
                    if (-not (Test-Path "$Path\$fileCounter-linkedservers.sql")) {
                        $fileCounter--
                    }
                }
    
                if ($Exclude -notcontains 'SystemTriggers') {
                    $fileCounter++
                    Write-Message -Level Verbose -Message "Exporting System Triggers"
                    Write-ProgressHelper -StepNumber ($stepCounter++) -Message "Exporting System Triggers"
                    $null = Get-DbaServerTrigger -SqlInstance $server | Export-DbaScript -Path "$Path\$fileCounter-servertriggers.sql" -Append -BatchSeparator $BatchSeparator -ScriptingOptionsObject $ScriptingOption
                    $triggers = Get-Content -Path "$Path\$fileCounter-servertriggers.sql" -Raw -ErrorAction Ignore
                    if ($triggers) {
                        $triggers = $triggers.ToString() -replace 'CREATE TRIGGER', "GO`r`nCREATE TRIGGER"
                        $triggers = $triggers.ToString() -replace 'ENABLE TRIGGER', "GO`r`nENABLE TRIGGER"
                        $null = $triggers | Set-Content -Path "$Path\$fileCounter-servertriggers.sql" -Force
                        Get-ChildItem -ErrorAction Ignore -Path "$Path\$fileCounter-servertriggers.sql"
                    }
                    if (-not (Test-Path "$Path\$fileCounter-servertriggers.sql")) {
                        $fileCounter--
                    }
                }
    
                if ($Exclude -notcontains 'Databases') {
                    $fileCounter++
                    Write-Message -Level Verbose -Message "Exporting database restores"
                    Write-ProgressHelper -StepNumber ($stepCounter++) -Message "Exporting database restores"
                    Get-DbaBackupHistory -SqlInstance $server -Last | Restore-DbaDatabase -SqlInstance $server -NoRecovery:$NoRecovery -WithReplace -OutputScriptOnly -WarningAction SilentlyContinue | Out-File -FilePath "$Path\$fileCounter-databases.sql" -Append
                    Get-ChildItem -ErrorAction Ignore -Path "$Path\$fileCounter-databases.sql"
                    if (-not (Test-Path "$Path\$fileCounter-databases.sql")) {
                        $fileCounter--
                    }
                }
    
                if ($Exclude -notcontains 'Logins') {
                    $fileCounter++
                    Write-Message -Level Verbose -Message "Exporting logins"
                    Write-ProgressHelper -StepNumber ($stepCounter++) -Message "Exporting logins"
                    Export-DbaLogin -SqlInstance $server -Path "$Path\$fileCounter-logins.sql" -Append -WarningAction SilentlyContinue
                    if (-not (Test-Path "$Path\$fileCounter-logins.sql")) {
                        $fileCounter--
                    }
                }
    
                if ($Exclude -notcontains 'Audits') {
                    $fileCounter++
                    Write-Message -Level Verbose -Message "Exporting Audits"
                    Write-ProgressHelper -StepNumber ($stepCounter++) -Message "Exporting Audits"
                    $null = Get-DbaServerAudit -SqlInstance $server | Export-DbaScript -Path "$Path\$fileCounter-audits.sql" -Append -BatchSeparator $BatchSeparator -ScriptingOptionsObject $ScriptingOption
                    Get-ChildItem -ErrorAction Ignore -Path "$Path\$fileCounter-audits.sql"
                    if (-not (Test-Path "$Path\$fileCounter-audits.sql")) {
                        $fileCounter--
                    }
                }
    
                if ($Exclude -notcontains 'ServerAuditSpecifications') {
                    $fileCounter++
                    Write-Message -Level Verbose -Message "Exporting Server Audit Specifications"
                    Write-ProgressHelper -StepNumber ($stepCounter++) -Message "Exporting Server Audit Specifications"
                    $null = Get-DbaServerAuditSpecification -SqlInstance $server | Export-DbaScript -Path "$Path\$fileCounter-auditspecs.sql" -Append -BatchSeparator $BatchSeparator -ScriptingOptionsObject $ScriptingOption
                    Get-ChildItem -ErrorAction Ignore -Path "$Path\$fileCounter-auditspecs.sql"
                    if (-not (Test-Path "$Path\$fileCounter-auditspecs.sql")) {
                        $fileCounter--
                    }
                }
    
                if ($Exclude -notcontains 'Endpoints') {
                    $fileCounter++
                    Write-Message -Level Verbose -Message "Exporting Endpoints"
                    Write-ProgressHelper -StepNumber ($stepCounter++) -Message "Exporting Endpoints"
                    $null = Get-DbaEndpoint -SqlInstance $server | Where-Object IsSystemObject -eq $false | Export-DbaScript -Path "$Path\$fileCounter-endpoints.sql" -Append -BatchSeparator $BatchSeparator -ScriptingOptionsObject $ScriptingOption
                    Get-ChildItem -ErrorAction Ignore -Path "$Path\$fileCounter-endpoints.sql"
                    if (-not (Test-Path "$Path\$fileCounter-endpoints.sql")) {
                        $fileCounter--
                    }
                }
    
                if ($Exclude -notcontains 'PolicyManagement') {
                    $fileCounter++
                    Write-Message -Level Verbose -Message "Exporting Policy Management"
                    Write-ProgressHelper -StepNumber ($stepCounter++) -Message "Exporting Policy Management"
                    $null = Get-DbaPbmCondition -SqlInstance $server | Export-DbaScript -Path "$Path\$fileCounter-policymanagement.sql" -Append -BatchSeparator $BatchSeparator
                    $null = Get-DbaPbmObjectSet -SqlInstance $server | Export-DbaScript -Path "$Path\$fileCounter-policymanagement.sql" -Append -BatchSeparator $BatchSeparator
                    $null = Get-DbaPbmPolicy -SqlInstance $server | Export-DbaScript -Path "$Path\$fileCounter-policymanagement.sql" -Append -BatchSeparator $BatchSeparator
                    Get-ChildItem -ErrorAction Ignore -Path "$Path\$fileCounter-policymanagement.sql"
                    if (-not (Test-Path "$Path\$fileCounter-policymanagement.sql")) {
                        $fileCounter--
                    }
                }
    
                if ($Exclude -notcontains 'ResourceGovernor') {
                    $fileCounter++
                    Write-Message -Level Verbose -Message "Exporting Resource Governor"
                    Write-ProgressHelper -StepNumber ($stepCounter++) -Message "Exporting Resource Governor"
                    $null = Get-DbaResourceGovernor -SqlInstance $server | Export-DbaScript -Path "$Path\$fileCounter-resourcegov.sql" -Append -BatchSeparator $BatchSeparator -ScriptingOptionsObject $ScriptingOption
                    $null = Get-DbaRgClassifierFunction -SqlInstance $server | Export-DbaScript -Path "$Path\$fileCounter-resourcegov.sql" -Append -BatchSeparator $BatchSeparator -ScriptingOptionsObject $ScriptingOption
                    $null = Get-DbaRgResourcePool -SqlInstance $server | Where-Object Name -notin 'default', 'internal' | Export-DbaScript -Path "$Path\$fileCounter-resourcegov.sql" -Append -BatchSeparator $BatchSeparator -ScriptingOptionsObject $ScriptingOption
                    $null = Get-DbaRgWorkloadGroup -SqlInstance $server | Where-Object Name -notin 'default', 'internal' | Export-DbaScript -Path "$Path\$fileCounter-resourcegov.sql" -Append -BatchSeparator $BatchSeparator -ScriptingOptionsObject $ScriptingOption
                    $null = Add-Content -Value "ALTER RESOURCE GOVERNOR RECONFIGURE" -Path "$Path\$stepCounter-resourcegov.sql"
                    Get-ChildItem -ErrorAction Ignore -Path "$Path\$fileCounter-resourcegov.sql"
                    if (-not (Test-Path "$Path\$fileCounter-resourcegov.sql")) {
                        $fileCounter--
                    }
                }
    
                if ($Exclude -notcontains 'ExtendedEvents') {
                    $fileCounter++
                    Write-Message -Level Verbose -Message "Exporting Extended Events"
                    Write-ProgressHelper -StepNumber ($stepCounter++) -Message "Exporting Extended Events"
                    $null = Get-DbaXESession -SqlInstance $server | Export-DbaScript -Path "$Path\$fileCounter-extendedevents.sql" -Append -BatchSeparator 'GO'
                    Get-ChildItem -ErrorAction Ignore -Path "$Path\$fileCounter-extendedevents.sql"
                    if (-not (Test-Path "$Path\$fileCounter-extendedevents.sql")) {
                        $fileCounter--
                    }
                }
    
                if ($Exclude -notcontains 'AgentServer') {
                    $fileCounter++
                    Write-Message -Level Verbose -Message "Exporting job server"
    
                    Write-ProgressHelper -StepNumber ($stepCounter++) -Message "Exporting job server"
                    $null = Get-DbaAgentJobCategory -SqlInstance $server | Export-DbaScript -Path "$Path\$fileCounter-sqlagent.sql" -Append -BatchSeparator $BatchSeparator -ScriptingOptionsObject $ScriptingOption
                    $null = Get-DbaAgentOperator -SqlInstance $server | Export-DbaScript -Path "$Path\$fileCounter-sqlagent.sql" -Append -BatchSeparator $BatchSeparator -ScriptingOptionsObject $ScriptingOption
                    $null = Get-DbaAgentAlert -SqlInstance $server | Export-DbaScript -Path "$Path\$fileCounter-sqlagent.sql" -Append -BatchSeparator $BatchSeparator -ScriptingOptionsObject $ScriptingOption
                    $null = Get-DbaAgentProxy -SqlInstance $server | Export-DbaScript  -Path "$Path\$fileCounter-sqlagent.sql" -Append -BatchSeparator $BatchSeparator -ScriptingOptionsObject $ScriptingOption
                    $null = Get-DbaAgentSchedule -SqlInstance $server | Export-DbaScript -Path "$Path\$fileCounter-sqlagent.sql" -Append -BatchSeparator $BatchSeparator -ScriptingOptionsObject $ScriptingOption
                    $null = Get-DbaAgentJob -SqlInstance $server | Export-DbaScript -Path "$Path\$fileCounter-sqlagent.sql" -Append -BatchSeparator $BatchSeparator -ScriptingOptionsObject $ScriptingOption
                    Get-ChildItem -ErrorAction Ignore -Path "$Path\$fileCounter-sqlagent.sql"
                    if (-not (Test-Path "$Path\$fileCounter-sqlagent.sql")) {
                        $fileCounter--
                    }
                }
    
                if ($Exclude -notcontains 'ReplicationSettings') {
                    $fileCounter++
                    Write-Message -Level Verbose -Message "Exporting replication settings"
                    Write-ProgressHelper -StepNumber ($stepCounter++) -Message "Exporting replication settings"
                    $null = Export-DbaRepServerSetting -SqlInstance $instance -SqlCredential $SqlCredential -Path "$Path\$fileCounter-replication.sql"
                    Get-ChildItem -ErrorAction Ignore -Path "$Path\$fileCounter-replication.sql"
                    if (-not (Test-Path "$Path\$fileCounter-replication.sql")) {
                        $fileCounter--
                    }
                }
    
                if ($Exclude -notcontains 'SysDbUserObjects') {
                    $fileCounter++
                    Write-Message -Level Verbose -Message "Exporting user objects in system databases (this can take a minute)."
                    Write-ProgressHelper -StepNumber ($stepCounter++) -Message "Exporting user objects in system databases (this can take a minute)."
                    $null = Get-DbaSysDbUserObjectScript -SqlInstance $server | Out-File -FilePath "$Path\$fileCounter-userobjectsinsysdbs.sql" -Append
                    Get-ChildItem -ErrorAction Ignore -Path "$Path\$fileCounter-userobjectsinsysdbs.sql"
                    if (-not (Test-Path "$Path\$fileCounter-userobjectsinsysdbs.sql")) {
                        $fileCounter--
                    }
                }
    
                if ($Exclude -notcontains 'AvailabilityGroups') {
                    $fileCounter++
                    Write-Message -Level Verbose -Message "Exporting availability group"
                    Write-ProgressHelper -StepNumber ($stepCounter++) -Message "Exporting availability groups"
                    $null = Get-DbaAvailabilityGroup -SqlInstance $server -WarningAction SilentlyContinue | Export-DbaScript -Path "$Path\$fileCounter-DbaAvailabilityGroups.sql" -Append -BatchSeparator $BatchSeparator #-ScriptingOptionsObject $ScriptingOption
                    Get-ChildItem -ErrorAction Ignore -Path "$Path\$fileCounter-DbaAvailabilityGroups.sql"
                    if (-not (Test-Path "$Path\$fileCounter-DbaAvailabilityGroups.sql")) {
                        $fileCounter--
                    }
                }
    
                Write-Progress -Activity "Performing Instance Export for $instance" -Completed
            }
        }
        end {
            $totaltime = ($elapsed.Elapsed.toString().Split(".")[0])
            Write-Message -Level Verbose -Message "SQL Server export complete."
            Write-Message -Level Verbose -Message "Export started: $started"
            Write-Message -Level Verbose -Message "Export completed: $(Get-Date)"
            Write-Message -Level Verbose -Message "Total Elapsed time: $totaltime"
        }
    }
    function Export-DbaLinkedServer {
        <#
        .SYNOPSIS
            Exports linked servers INCLUDING PASSWORDS, unless specified otherwise, to sql file.
    
        .DESCRIPTION
            Exports linked servers INCLUDING PASSWORDS, unless specified otherwise, to sql file.
    
            Requires remote Windows access if exporting the password.
    
        .PARAMETER SqlInstance
            Source SQL Server. You must have sysadmin access and server version must be SQL Server version 2005 or higher.
    
        .PARAMETER SqlCredential
            Login to the target instance using alternative linked servers. Windows and SQL Authentication supported. Accepts credential objects (Get-Credential)
    
        .PARAMETER Credential
            Login to the target OS using alternative linked servers. Accepts credential objects (Get-Credential)
    
        .PARAMETER Path
            The path to the exported sql file.
    
        .PARAMETER LinkedServer
            The linked server(s) to export. If unspecified, all linked servers will be processed.
    
        .PARAMETER InputObject
            Allow credentials to be piped in from Get-DbaLinkedServer
    
        .PARAMETER ExcludePassword
            Exports the linked server without any sensitive information.
    
        .PARAMETER Append
            Append to Path
    
        .PARAMETER EnableException
            By default, when something goes wrong we try to catch it, interpret it and give you a friendly warning message.
            This avoids overwhelming you with "sea of red" exceptions, but is inconvenient because it basically disables advanced scripting.
            Using this switch turns this "nice by default" feature off and enables you to catch exceptions with your own try/catch.
    
        .NOTES
            Tags: LinkedServer
            Author: Chrissy LeMaire (@cl), netnerds.net
    
            Website: https://dbatools.io
            Copyright: (c) 2018 by dbatools, licensed under MIT
            License: MIT https://opensource.org/licenses/MIT
    
        .EXAMPLE
            PS C:\> Export-DbaLinkedServer -SqlInstance sql2017 -Path C:\temp\ls.sql
    
            Exports the linked servers, including passwords, from sql2017 to the file C:\temp\ls.sql
    
        .EXAMPLE
            PS C:\> Export-DbaLinkedServer -SqlInstance sql2017 -Path C:\temp\ls.sql -ExcludePassword
    
            Exports the linked servers, without passwords, from sql2017 to the file C:\temp\ls.sql
    
        #>
        [CmdletBinding()]
        param (
            [Parameter(Mandatory, ValueFromPipeline)]
            [Alias("ServerInstance", "SqlServer")]
            [DbaInstanceParameter[]]$SqlInstance,
            [string[]]$LinkedServer,
            [PSCredential]$SqlCredential,
            [PSCredential]$Credential,
            [string]$Path,
            [switch]$ExcludePassword,
            [switch]$Append,
            [Microsoft.SqlServer.Management.Smo.LinkedServer[]]$InputObject,
            [switch]$EnableException
        )
        process {
            foreach ($instance in $SqlInstance) {
                try {
                    $server = Connect-SqlInstance -SqlInstance $instance -SqlCredential $sqlcredential -MinimumVersion 9
                    $InputObject += $server.LinkedServers
                } catch {
                    Stop-Function -Message "Failure" -Category ConnectionError -ErrorRecord $_ -Target $instance -Continue
                }
    
                if ($LinkedServer) {
                    $InputObject = $InputObject | Where-Object Name -in $LinkedServer
                }
    
                if (-not $InputObject) {
                    Write-Message -Level Verbose -Message "Nothing to export"
                    continue
                }
    
                if (!(Test-SqlSa -SqlInstance $instance -SqlCredential $sqlcredential)) {
                    Stop-Function -Message "Not a sysadmin on $instance. Quitting." -Target $instance -Continue
                }
    
                Write-Message -Level Verbose -Message "Getting NetBios name for $instance."
                $sourceNetBios = Resolve-NetBiosName $server
    
                Write-Message -Level Verbose -Message "Checking if Remote Registry is enabled on $instance."
                try {
                    Invoke-Command2 -Raw -Credential $Credential -ComputerName $sourceNetBios -ScriptBlock { Get-ItemProperty -Path "HKLM:\SOFTWARE\" } -ErrorAction Stop
                } catch {
                    Stop-Function -Message "Can't connect to registry on $instance." -Target $sourceNetBios -ErrorRecord $_
                    return
                }
    
                if (-not (Test-Bound -ParameterName Path)) {
                    $timenow = (Get-Date -uformat "%m%d%Y%H%M%S")
                    $mydocs = [Environment]::GetFolderPath('MyDocuments')
                    $path = "$mydocs\$($server.name.replace('\', '$'))-$timenow-linkedserver.sql"
                }
    
                $sql = @()
    
                if ($ExcludePassword) {
                    $sql += $InputObject.Script()
                } else {
                    try {
                        $decrypted = Get-DecryptedObject -SqlInstance $server -Type LinkedServer
                    } catch {
                        Stop-Function -Continue -Message "Failure" -ErrorRecord $_
                    }
    
                    foreach ($ls in $InputObject) {
                        $currentls = $decrypted | Where-Object Name -eq $ls.Name
    
                        if ($currentls.Password) {
                            $password = $currentls.Password.Replace("'", "''")
    
                            $tempsql = $ls.Script()
                            $tempsql = $tempsql.Replace(' /* For security reasons the linked server remote logins password is changed with ######## */', '')
                            $tempsql = $tempsql.Replace("rmtpassword='########'", "rmtpassword='$password'")
                            $sql += $tempsql
                        } else {
                            $sql += $ls.Script()
                        }
                    }
                }
    
                try {
                    if ($Append) {
                        Add-Content -Path $path -Value $sql
                    } else {
                        Set-Content -Path $path -Value $sql
                    }
                    Get-ChildItem -Path $path
                } catch {
                    Stop-Function -Message "Can't write to $path" -ErrorRecord $_ -Continue
                }
            }
        }
    }
    function Export-DbaLogin {
        <#
        .SYNOPSIS
            Exports Windows and SQL Logins to a T-SQL file. Export includes login, SID, password, default database, default language, server permissions, server roles, db permissions, db roles.
    
        .DESCRIPTION
            Exports Windows and SQL Logins to a T-SQL file. Export includes login, SID, password, default database, default language, server permissions, server roles, db permissions, db roles.
    
        .PARAMETER SqlInstance
            The target SQL Server instance or instances. SQL Server 2000 and above supported.
    
        .PARAMETER SqlCredential
            Login to the target instance using alternative credentials. Windows and SQL Authentication supported. Accepts credential objects (Get-Credential)
    
        .PARAMETER Login
            The login(s) to process. Options for this list are auto-populated from the server. If unspecified, all logins will be processed.
    
        .PARAMETER ExcludeLogin
            The login(s) to exclude. Options for this list are auto-populated from the server.
    
        .PARAMETER Database
            The database(s) to process. Options for this list are auto-populated from the server. If unspecified, all databases will be processed.
    
        .PARAMETER Path
            The file to write to.
    
        .PARAMETER NoClobber
            If this switch is enabled, a file already existing at the path specified by Path will not be overwritten.
    
        .PARAMETER Append
            If this switch is enabled, content will be appended to a file already existing at the path specified by Path. If the file does not exist, it will be created.
    
        .PARAMETER ExcludeJobs
            If this switch is enabled, Agent job ownership will not be exported.
    
        .PARAMETER ExcludeDatabases
            If this switch is enabled, mappings for databases will not be exported.
    
        .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.
    
        .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: Export, Login
            Author: Chrissy LeMaire (@cl), netnerds.net
    
            Website: https://dbatools.io
            Copyright: (c) 2018 by dbatools, licensed under MIT
            License: MIT https://opensource.org/licenses/MIT
    
        .LINK
            https://dbatools.io/Export-DbaLogin
    
        .EXAMPLE
            PS C:\> Export-DbaLogin -SqlInstance sql2005 -Path C:\temp\sql2005-logins.sql
    
            Exports the logins for SQL Server "sql2005" and writes them to the file "C:\temp\sql2005-logins.sql"
    
        .EXAMPLE
            PS C:\> Export-DbaLogin -SqlInstance sqlserver2014a -ExcludeLogin realcajun -SqlCredential $scred -Path C:\temp\logins.sql -Append
    
            Authenticates to sqlserver2014a using SQL Authentication. Exports all logins except for realcajun to C:\temp\logins.sql, and appends to the file if it exists. If not, the file will be created.
    
        .EXAMPLE
            PS C:\> Export-DbaLogin -SqlInstance sqlserver2014a -Login realcajun, netnerds -Path C:\temp\logins.sql
    
            Exports ONLY logins netnerds and realcajun FROM sqlserver2014a to the file  C:\temp\logins.sql
    
        .EXAMPLE
            PS C:\> 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
            PS C:\> Export-DbaLogin -SqlInstance sqlserver2008 -Login realcajun, netnerds -Path C:\temp\login.sql -ExcludeGoBatchSeparator
    
            Exports ONLY logins netnerds and realcajun FROM sqlserver2008 server, to the C:\temp\login.sql file without the 'GO' batch separator.
    
        .EXAMPLE
            PS C:\> Export-DbaLogin -SqlInstance sqlserver2008 -Login realcajun -Path C:\temp\users.sql -DestinationVersion SQLServer2016
    
            Exports login realcajun from sqlserver2008 to the file C:\temp\users.sql with syntax to run on SQL Server 2016
    
        #>
        [CmdletBinding(SupportsShouldProcess = $true)]
        param (
            [parameter(Mandatory, ValueFromPipeline)]
            [Alias("ServerInstance", "SqlServer")]
            [DbaInstanceParameter]$SqlInstance,
            [Alias("Credential")]
            [PSCredential]
            $SqlCredential,
            [object[]]$Login,
            [object[]]$ExcludeLogin,
            [Alias("Databases")]
            [object[]]$Database,
            [Alias("OutFile", "FilePath", "FileName")]
            [string]$Path,
            [Alias("NoOverwrite")]
            [switch]$NoClobber,
            [switch]$Append,
            [switch]$ExcludeDatabases,
            [switch]$ExcludeJobs,
            [Alias('Silent')]
            [switch]$EnableException,
            [switch]$ExcludeGoBatchSeparator,
            [ValidateSet('SQLServer2000', 'SQLServer2005', 'SQLServer2008/2008R2', 'SQLServer2012', 'SQLServer2014', 'SQLServer2016', 'SQLServer2017')]
            [string]$DestinationVersion
        )
    
        begin {
    
            if ($Path) {
                if ($Path -notlike "*\*") {
                    $Path = ".\$Path"
                }
                $directory = Split-Path $Path
                $exists = Test-Path $directory
    
                if ($exists -eq $false) {
                    Write-Message -Level Warning -Message "Parent directory $directory does not exist"
                }
            }
    
            $outsql = @()
    
        }
        process {
            if (Test-FunctionInterrupt) {
                return
            }
    
            $server = Connect-SqlInstance -SqlInstance $SqlInstance -SqlCredential $sqlcredential
    
            if ($ExcludeDatabases -eq $false -or $Database) {
                # if we got a database or a list of databases passed
                # and we need to enumerate mappings, login.enumdatabasemappings() takes forever
                # the cool thing though is that database.enumloginmappings() is fast. A lot.
                # if we get a list of databases passed (or even the default list of all the databases)
                # we save outself a call to enumloginmappings if there is no map at all
                $DbMapping = @()
                $DbsToMap = $server.Databases
                if ($Database) {
                    $DbsToMap = $DbsToMap | Where-Object Name -in $Database
                }
                foreach ($db in $DbsToMap) {
                    if ($db.IsAccessible -eq $false) {
                        continue
                    }
                    $dbmap = $db.EnumLoginMappings()
                    foreach ($el in $dbmap) {
                        $DbMapping += [pscustomobject]@{
                            Database  = $db.Name
                            UserName  = $el.Username
                            LoginName = $el.LoginName
                        }
                    }
                }
            }
    
            foreach ($sourceLogin in $server.Logins) {
                $userName = $sourceLogin.name
    
                if ($Login -and $Login -notcontains $userName -or $ExcludeLogin -contains $userName) {
                    continue
                }
    
                if ($userName.StartsWith("##") -or $userName -eq 'sa') {
                    Write-Message -Level Warning -Message "Skipping $userName"
                    continue
                }
    
                $serverName = $server
    
                $userBase = ($userName.Split("\")[0]).ToLower()
                if ($serverName -eq $userBase -or $userName.StartsWith("NT ")) {
                    if ($Pscmdlet.ShouldProcess("console", "Stating $userName is skipped because it is a local machine name")) {
                        Write-Message -Level Warning -Message "$userName is skipped because it is a local machine name"
                        continue
                    }
                }
    
                if ($Pscmdlet.ShouldProcess("Outfile", "Adding T-SQL for login $userName")) {
                    if ($Path) {
                        Write-Message -Level Verbose -Message "Exporting $userName"
                    }
    
                    $outsql += "`r`nUSE master`n"
                    # Getting some attributes
                    $defaultDb = $sourceLogin.DefaultDatabase
                    $language = $sourceLogin.Language
    
                    if ($sourceLogin.PasswordPolicyEnforced -eq $false) {
                        $checkPolicy = "OFF"
                    } else {
                        $checkPolicy = "ON"
                    }
    
                    if (!$sourceLogin.PasswordExpirationEnabled) {
                        $checkExpiration = "OFF"
                    } else {
                        $checkExpiration = "ON"
                    }
    
                    # Attempt to script out SQL Login
                    if ($sourceLogin.LoginType -eq "SqlLogin") {
                        $sourceLoginName = $sourceLogin.name
    
                        switch ($server.versionMajor) {
                            0 {
                                $sql = "SELECT CONVERT(VARBINARY(256),password) AS hashedpass FROM master.dbo.syslogins WHERE loginname='$sourceLoginName'"
                            }
                            8 {
                                $sql = "SELECT CONVERT(VARBINARY(256),password) AS hashedpass FROM dbo.syslogins WHERE name='$sourceLoginName'"
                            }
                            9 {
                                $sql = "SELECT CONVERT(VARBINARY(256),password_hash) as hashedpass FROM sys.sql_logins WHERE name='$sourceLoginName'"
                            }
                            default {
                                $sql = "SELECT CAST(CONVERT(varchar(256), CAST(LOGINPROPERTY(name,'PasswordHash') AS VARBINARY(256)), 1) AS NVARCHAR(max)) AS hashedpass FROM sys.server_principals WHERE principal_id = $($sourceLogin.id)"
                            }
                        }
    
                        try {
                            $hashedPass = $server.ConnectionContext.ExecuteScalar($sql)
                        } catch {
                            $hashedPassDt = $server.Databases['master'].ExecuteWithResults($sql)
                            $hashedPass = $hashedPassDt.Tables[0].Rows[0].Item(0)
                        }
    
                        if ($hashedPass.GetType().Name -ne "String") {
                            $passString = "0x"; $hashedPass | ForEach-Object {
                                $passString += ("{0:X}" -f $_).PadLeft(2, "0")
                            }
                            $hashedPass = $passString
                        }
    
                        $sid = "0x"; $sourceLogin.sid | ForEach-Object {
                            $sid += ("{0:X}" -f $_).PadLeft(2, "0")
                        }
                        $outsql += "IF NOT EXISTS (SELECT loginname FROM master.dbo.syslogins WHERE name = '$userName') CREATE LOGIN [$userName] WITH PASSWORD = $hashedPass HASHED, SID = $sid, DEFAULT_DATABASE = [$defaultDb], CHECK_POLICY = $checkPolicy, CHECK_EXPIRATION = $checkExpiration, DEFAULT_LANGUAGE = [$language]"
                    }
                    # Attempt to script out Windows User
                    elseif ($sourceLogin.LoginType -eq "WindowsUser" -or $sourceLogin.LoginType -eq "WindowsGroup") {
                        $outsql += "IF NOT EXISTS (SELECT loginname FROM master.dbo.syslogins WHERE name = '$userName') CREATE LOGIN [$userName] FROM WINDOWS WITH DEFAULT_DATABASE = [$defaultDb], DEFAULT_LANGUAGE = [$language]"
                    }
                    # This script does not currently support certificate mapped or asymmetric key users.
                    else {
                        Write-Message -Level Warning -Message "$($sourceLogin.LoginType) logins not supported. $($sourceLogin.Name) skipped"
                        continue
                    }
    
                    if ($sourceLogin.IsDisabled) {
                        $outsql += "ALTER LOGIN [$userName] DISABLE"
                    }
    
                    if ($sourceLogin.DenyWindowsLogin) {
                        $outsql += "DENY CONNECT SQL TO [$userName]"
                    }
                }
    
                # Server Roles: sysadmin, bulklogin, etc
                foreach ($role in $server.Roles) {
                    $roleName = $role.Name
    
                    # SMO changed over time
                    try {
                        $roleMembers = $role.EnumMemberNames()
                    } catch {
                        $roleMembers = $role.EnumServerRoleMembers()
                    }
    
                    if ($roleMembers -contains $userName) {
                        if (($server.VersionMajor -lt 11 -and [string]::IsNullOrEmpty($destinationVersion)) -or ($DestinationVersion -in "SQLServer2000", "SQLServer2005", "SQLServer2008/2008R2")) {
                            $outsql += "EXEC sys.sp_addsrvrolemember @rolename=N'$roleName', @loginame=N'$userName'"
                        } else {
                            $outsql += "ALTER SERVER ROLE [$roleName] ADD MEMBER [$userName]"
                        }
                    }
                }
    
                if ($ExcludeJobs -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 ($ExcludeDatabases -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 ($Path) {
                $sql | Out-File -Encoding UTF8 -FilePath $Path -Append:$Append -NoClobber:$NoClobber
                Get-ChildItem $Path
            } else {
                $sql
            }
            Test-DbaDeprecation -DeprecatedOn "1.0.0" -EnableException:$false -Alias Export-SqlLogin
        }
    }
    #ValidationTags#Messaging,FlowControl,Pipeline,CodeStyle#
    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
            Author: Chrissy LeMaire (@cl), netnerds.net
    
            Website: https://dbatools.io
            Copyright: (c) 2018 by dbatools, licensed under MIT
            License: MIT https://opensource.org/licenses/MIT
    
        .LINK
            https://dbatools.io/Export-DbaPfDataCollectorSetTemplate
    
        .EXAMPLE
            PS C:\> Export-DbaPfDataCollectorSetTemplate -ComputerName sql2017 -Path C:\temp\pf
    
            Exports all data collector sets from to the C:\temp\pf folder.
    
        .EXAMPLE
            PS C:\> 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-DbaRepServerSetting {
        <#
        .SYNOPSIS
            Exports replication server settings to file.
    
        .DESCRIPTION
            Exports replication server settings to file.
    
        .PARAMETER SqlInstance
            The target SQL Server instance or instances
    
        .PARAMETER SqlCredential
            Login to the target instance using alternative credentials. Windows and SQL Authentication supported. Accepts credential objects (Get-Credential)
    
        .PARAMETER Path
            Specifies the path to a file which will contain the output.
    
        .PARAMETER Passthru
            Output script to console
    
        .PARAMETER NoClobber
            Do not overwrite file
    
        .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 Append
            Append to file
    
        .PARAMETER ScriptOption
            Not real sure how to use this yet
    
        .PARAMETER InputObject
            Allows piping from Get-DbaRepServer
    
        .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: Replication
            Author: Chrissy LeMaire (@cl), netnerds.net
    
            Website: https://dbatools.io
            Copyright: (c) 2018 by dbatools, licensed under MIT
            License: MIT https://opensource.org/licenses/MIT
    
        .EXAMPLE
            PS C:\> Export-DbaRepServerSetting -SqlInstance sql2017 -Path C:\temp\replication.sql
    
            Exports the replication settings on sql2017 to the file C:\temp\replication.sql
    
        .EXAMPLE
            PS C:\> Get-DbaRepServer -SqlInstance sql2017 | Export-DbaRepServerSettings -Path C:\temp\replication.sql
    
            Exports the replication settings on sql2017 to the file C:\temp\replication.sql
    
        #>
        [CmdletBinding()]
        param (
            [Alias("ServerInstance", "SqlServer")]
            [DbaInstanceParameter[]]$SqlInstance,
            [PSCredential]$SqlCredential,
            [string]$Path,
            [object[]]$ScriptOption,
            [Parameter(ValueFromPipeline)]
            [Microsoft.SqlServer.Replication.ReplicationServer[]]$InputObject,
            [ValidateSet('ASCII', 'BigEndianUnicode', 'Byte', 'String', 'Unicode', 'UTF7', 'UTF8', 'Unknown')]
            [string]$Encoding = 'UTF8',
            [switch]$Passthru,
            [switch]$NoClobber,
            [switch]$Append,
            [switch]$EnableException
        )
        process {
            foreach ($instance in $SqlInstance) {
                $InputObject += Get-DbaRepServer -SqlInstance $instance -SqlCredential $sqlcredential
            }
    
            foreach ($repserver in $InputObject) {
                $server = $repserver.SqlServerName
                if (-not (Test-Bound -ParameterName Path)) {
                    $timenow = (Get-Date -uformat "%m%d%Y%H%M%S")
                    $mydocs = [Environment]::GetFolderPath('MyDocuments')
                    $path = "$mydocs\$($server.replace('\', '$'))-$timenow-replication.sql"
                }
                try {
                    if (-not $ScriptOption) {
                        $out = $repserver.Script([Microsoft.SqlServer.Replication.ScriptOptions]::Creation `
                                -bor [Microsoft.SqlServer.Replication.ScriptOptions]::IncludeAll `
                                -bor [Microsoft.SqlServer.Replication.ScriptOptions]::EnableReplicationDB `
                                -bor [Microsoft.SqlServer.Replication.ScriptOptions]::IncludeInstallDistributor
                        )
                    } else {
                        $out = $repserver.Script($scriptOption)
                    }
                } catch {
                    Stop-Function -ErrorRecord $_ -Message "Replication export failed. Is it setup?" -Continue
                }
                if ($Passthru) {
                    "exec sp_dropdistributor @no_checks = 1, @ignore_distributor = 1" | Out-String
                    $out | Out-String
                }
    
                if ($Path) {
    
                    "exec sp_dropdistributor @no_checks = 1, @ignore_distributor = 1" | Out-File -FilePath $path -Encoding $encoding -Append
                    $out | Out-File -FilePath $path -Encoding $encoding -Append
                }
            }
        }
    }
    function Export-DbaScript {
        <#
        .SYNOPSIS
            Exports scripts from SQL Management Objects (SMO)
    
        .DESCRIPTION
            Exports scripts from SQL Management Objects
    
        .PARAMETER InputObject
            A SQL Management 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
            Author: Chrissy LeMaire (@cl), netnerds.net
    
            Website: https://dbatools.io
            Copyright: (c) 2018 by dbatools, licensed under MIT
            License: MIT https://opensource.org/licenses/MIT
    
        .LINK
            https://dbatools.io/Export-DbaScript
    
        .EXAMPLE
            PS C:\> 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
            PS C:\> 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
            Script does not include Batch Separator and will not compile
    
        .EXAMPLE
            PS C:\> Get-DbaDbTable -SqlInstance sql2016 -Database MyDatabase -Table 'dbo.Table1', 'dbo.Table2' -SqlCredential sqladmin | Export-DbaScript -Path C:\temp\export.sql
    
            Exports only script for 'dbo.Table1' and 'dbo.Table2' in MyDatabase to C:temp\export.sql and uses the SQL login "sqladmin" to login to sql2016
    
        .EXAMPLE
            PS C:\> Get-DbaAgentJob -SqlInstance sql2016 -Job syspolicy_purge_history, 'Hourly Log Backups' -SqlCredential 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
            PS C:\> $options = New-DbaScriptingOption
            PS C:\> $options.ScriptSchema = $true
            PS C:\> $options.IncludeDatabaseContext  = $true
            PS C:\> $options.IncludeHeaders = $false
            PS C:\> $Options.NoCommandTerminator = $false
            PS C:\> $Options.ScriptBatchTerminator = $true
            PS C:\> $Options.AnsiFile = $true
            PS C:\> Get-DbaAgentJob -SqlInstance sql2016 -Job syspolicy_purge_history, 'Hourly Log Backups' -SqlCredential sqladmin | Export-DbaScript -Path C:\temp\export.sql -ScriptingOptionsObject $options
    
            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
            PS C:\> 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
            PS C:\> $options = New-DbaScriptingOption
            PS C:\> $options.ScriptSchema = $true
            PS C:\> $options.IncludeDatabaseContext  = $true
            PS C:\> $options.IncludeHeaders = $false
            PS C:\> $Options.NoCommandTerminator = $false
            PS C:\> $Options.ScriptBatchTerminator = $true
            PS C:\> $Options.AnsiFile = $true
            PS C:\> $Databases = Get-DbaDatabase -SqlInstance sql2016 -ExcludeDatabase master, model, msdb, tempdb
            PS C:\> foreach ($db in $Databases) {
            >>        Export-DbaScript -InputObject $db -Path C:\temp\export.sql -Append -Encoding UTF8 -ScriptingOptionsObject $options -NoPrefix
            >> }
    
            Exports Script for each database on sql2016 excluding system databases
            Uses Scripting options to ensure Batch Terminator is set
            Will append the output to the file C:\temp\export.sql if it already exists
    
        #>
        [CmdletBinding(SupportsShouldProcess = $true)]
        param (
            [parameter(Mandatory, ValueFromPipeline)]
            [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 -and -not (Get-Member -InputObject $object -Name ScriptCreate) ) {
                    Stop-Function -Message "Failed to find valid SMO server object in input: $object." -Category InvalidData -Target $object -Continue
                }
    
                try {
                    $server = $parent
                    if (-not $server) {
                        $server = $object.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`r`n$BatchSeparator`r`n"
                                    }
                                    $script | Out-String
                                }
                            } else {
                                if (Get-Member -Name ScriptCreate -InputObject $object) {
                                    $script = $object.ScriptCreate().GetScript()
                                } else {
                                    $script = $object.Script()
                                }
    
                                if ($BatchSeparator -ne "") {
                                    $script = "$script`r`n$BatchSeparator`r`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`r`n$BatchSeparator`r`n"
                                        }
                                        $script | Out-File -FilePath $actualPath -Encoding $encoding -Append
                                    }
                                }
    
                            } else {
                                if (Get-Member -Name ScriptCreate -InputObject $object) {
                                    $script = $object.ScriptCreate().GetScript()
                                } else {
                                    $script = $object.Script()
                                }
                                if ($BatchSeparator -ne "") {
                                    $script = "$script`r`n$BatchSeparator`r`n"
                                }
                                $script | Out-File -FilePath $actualPath -Encoding $encoding -Append
                            }
                        }
    
                        if (-not $passthru) {
                            Write-Message -Level Verbose -Message "Exported $object on $($server.Name) to $actualPath"
                            Get-ChildItem -Path $actualPath
                        }
                    }
                } catch {
                    $message = $_.Exception.InnerException.InnerException.InnerException.Message
                    if (-not $message) {
                        $message = $_.Exception
                    }
                    Stop-Function -Message "Failure on $($server.Name) | $message" -Target $server
                }
            }
        }
    }
    #ValidationTags#Messaging,FlowControl,Pipeline,CodeStyle#
    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
            The target SQL Server instance or instances. This can be a collection and receive pipeline input.
            You must have sysadmin access if needs to set 'show advanced options' to 1 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.
            If not specified will output to My Documents folder with default name of ServerName-MMDDYYYYhhmmss-sp_configure.sql
            If a directory is passed then uses default name of ServerName-MMDDYYYYhhmmss-sp_configure.sql
    
        .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: SpConfig, Configure, Configuration
            Author: Chrissy LeMaire (@cl), netnerds.net
    
            Website: https://dbatools.io
            Copyright: (c) 2018 by dbatools, licensed under MIT
            License: MIT https://opensource.org/licenses/MIT
    
        .LINK
            https://dbatools.io/Export-DbaSpConfigure
    
        .INPUTS
            A DbaInstanceParameter representing an array of SQL Server instances.
    
        .OUTPUTS
            Creates a new file for each SQL Server Instance
    
        .EXAMPLE
            PS C:\> Export-DbaSpConfigure -SqlInstance sourceserver
    
            Exports the SPConfigure settings on sourceserver. As no Path was defined outputs to My Documents folder with default name format of Servername-MMDDYYYYhhmmss-sp_configure.sql
    
        .EXAMPLE
            PS C:\> Export-DbaSpConfigure -SqlInstance sourceserver -Path C:\temp
    
            Exports the SPConfigure settings on sourceserver to the directory C:\temp using the default name format
    
        .EXAMPLE
            PS C:\> $cred = Get-Credential sqladmin
            PS C:\> Export-DbaSpConfigure -SqlInstance sourceserver -SqlCredential $cred -Path C:\temp\sp_configure.sql
    
            Exports the SPConfigure settings on sourceserver to the file C:\temp\sp_configure.sql. Uses SQL Authentication to connect. Will require SysAdmin rights if needs to set 'show advanced options'
    
        .EXAMPLE
            PS C:\> 'Server1', 'Server2' | Export-DbaSpConfigure -Path C:\temp\configure.sql
    
            Exports the SPConfigure settings for Server1 and Server2 using pipeline. As more than 1 Server adds prefix of Servername and date to the file name and saves to file like  C:\temp\Servername-MMDDYYYYhhmmss-configure.sql
    
        #>
        [CmdletBinding()]
        param (
            [Parameter(Mandatory, ValueFromPipeline)]
            [Alias("ServerInstance", "SqlServer")]
            [DbaInstanceParameter[]]$SqlInstance,
            [PSCredential]$SqlCredential,
            [string]$Path,
            [switch]$EnableException
        )
        process {
            foreach ($instance in $SqlInstance) {
                try {
                    $server = Connect-SqlInstance -SqlInstance $instance -SqlCredential $sqlcredential -MinimumVersion 9
                } catch {
                    Stop-Function -Message "Failure" -Category ConnectionError -ErrorRecord $_ -Target $instance -Continue
                }
    
                if (-not (Test-Bound -ParameterName Path)) {
                    $timenow = (Get-Date -uformat "%m%d%Y%H%M%S")
                    $mydocs = [Environment]::GetFolderPath('MyDocuments')
                    $filepath = "$mydocs\$($server.name.replace('\', '$'))-$timenow-sp_configure.sql"
                }
    
                if (Test-Path $Path -PathType Container) {
                    $timenow = (Get-Date -uformat "%m%d%Y%H%M%S")
                    $filepath = Join-Path -Path $Path -ChildPath "$($server.name.replace('\', '$'))-$timenow-sp_configure.sql"
                } elseif (Test-Path $Path -PathType Leaf) {
                    if ($SqlInstance.Count -gt 1) {
                        $timenow = (Get-Date -uformat "%m%d%Y%H%M%S")
                        $PathData = Get-ChildItem $Path
                        $filepath = "$($PathData.DirectoryName)\$($server.name.replace('\', '$'))-$timenow-$($PathData.Name)"
                    } else {
                        $filepath = $Path
                    }
                }
    
                If (-not $filepath) {
                    $filepath = $Path
                }
    
                $topdir = Split-Path -Path $filepath
    
                if (-not (Test-Path -Path $topdir)) {
                    New-Item -Path $topdir -ItemType Directory
                }
    
                $ShowAdvancedOptions = $server.Configuration.ShowAdvancedOptions.ConfigValue
    
                if ($ShowAdvancedOptions -eq 0) {
                    try {
                        $server.Configuration.ShowAdvancedOptions.ConfigValue = $true
                        $server.Configuration.Alter($true)
                    } catch {
                        Stop-Function -Message "Can't set 'show advanced options' to 1 on instance $instance" -ErrorRecord $_ -Continue
                    }
                }
    
                try {
                    Set-Content -Path $filepath "EXEC sp_configure 'show advanced options' , 1;  RECONFIGURE WITH OVERRIDE"
                } catch {
                    Stop-Function -Message "Can't write to $filepath" -ErrorRecord $_ -Continue
                }
    
                foreach ($sourceprop in $server.Configuration.Properties) {
                    $displayname = $sourceprop.DisplayName
                    $configvalue = $sourceprop.ConfigValue
                    Add-Content -Path $filepath "EXEC sp_configure '$displayname' , $configvalue;"
                }
    
                if ($ShowAdvancedOptions -eq 0) {
                    Add-Content -Path $filepath "EXEC sp_configure 'show advanced options' , 0;"
                    Add-Content -Path $filepath "RECONFIGURE WITH OVERRIDE"
    
                    $server.Configuration.ShowAdvancedOptions.ConfigValue = $false
                    $server.Configuration.Alter($true)
                }
                Get-ChildItem -Path $filepath
            }
        }
    
        end {
            Write-Message -Level Verbose -Message "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 target SQL Server instance or instances. 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 Path
            Specifies the full path of a file to write the script 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) 2018 by dbatools, licensed under MIT
            License: MIT https://opensource.org/licenses/MIT
    
        .LINK
            https://dbatools.io/Export-DbaUser
    
        .EXAMPLE
            PS C:\> Export-DbaUser -SqlInstance sql2005 -Path 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
            PS C:\> Export-DbaUser -SqlInstance sqlserver2014a $scred -Path 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
            PS C:\> Export-DbaUser -SqlInstance sqlserver2014a -User User1, User2 -Path C:\temp\users.sql
    
            Exports ONLY users User1 and User2 from sqlserver2014a to the file  C:\temp\users.sql
    
        .EXAMPLE
            PS C:\> Export-DbaUser -SqlInstance sqlserver2008 -User User1 -Path C:\temp\users.sql -DestinationVersion SQLServer2016
    
            Exports user User1 from sqlserver2008 to the file C:\temp\users.sql with syntax to run on SQL Server 2016
    
        .EXAMPLE
            PS C:\> Export-DbaUser -SqlInstance sqlserver2008 -Database db1,db2 -Path C:\temp\users.sql
    
            Exports ONLY users from db1 and db2 database on sqlserver2008 server, to the C:\temp\users.sql file.
    
        .EXAMPLE
            PS C:\> $options = New-DbaScriptingOption
            PS C:\> $options.ScriptDrops = $false
            PS C:\> $options.WithDependencies = $true
            PS C:\> Export-DbaUser -SqlInstance sqlserver2008 -Database db1,db2 -Path 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
            PS C:\> Export-DbaUser -SqlInstance sqlserver2008 -Database db1,db2 -Path 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, ValueFromPipeline)]
            [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", "FilePath", "FileName")]
            [string]$Path,
            [Alias("NoOverwrite")]
            [switch]$NoClobber,
            [switch]$Append,
            [Alias('Silent')]
            [switch]$EnableException,
            [Microsoft.SqlServer.Management.Smo.ScriptingOptions]$ScriptingOptionsObject = $null,
            [switch]$ExcludeGoBatchSeparator
        )
    
        begin {
            if ($Path) {
                if ($Path -notlike "*\*") { $Path = ".\$Path" }
                $directory = Split-Path $Path
                $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 {
                $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) {
                    $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) {
                            $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 don't 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
                                        }
                                        'SymmetricKey' {
                                            $object = 'SYMMETRIC KEY::[{0}]' -f $objectPermission.ObjectName
                                        }
                                        'User' {
                                            $object = 'USER::[{0}]' -f $objectPermission.ObjectName
                                        }
                                        'UserDefinedType' {
                                            $object = 'TYPE::[{0}].[{1}]' -f $objectPermission.ObjectSchema, $objectPermission.ObjectName
                                        }
                                        'XmlNamespace' {
                                            $object = 'XML SCHEMA COLLECTION::[{0}]' -f $objectPermission.ObjectName
                                        }
                                    }
    
                                    if ($objectPermission.PermissionState -eq "GrantWithGrant") {
                                        $withGrant = " WITH GRANT OPTION"
                                        $grantObjectPermission = 'GRANT'
                                    } else {
                                        $withGrant = " "
                                        $grantObjectPermission = $objectPermission.PermissionState.ToString().ToUpper()
                                    }
    
                                    $outsql += "$grantObjectPermission $($objectPermission.PermissionType) ON $object TO [$($objectPermission.Grantee)]$withGrant AS [$($objectPermission.Grantor)];"
                                }
    
                            } catch {
                                Stop-Function -Message "This user may be using functionality from $($versionName[$db.CompatibilityLevel.ToString()]) that does not exist on the destination version ($versionNameDesc)." -Continue -InnerErrorRecord $_ -Target $db
                            }
                        }
                    } else {
                        Write-Message -Level Output -Message "No users found on database '$db'"
                    }
    
                    #reset collection
                    $users = $null
                }
            } else {
                Write-Message -Level Output -Message "No users found on instance '$server'"
            }
        }
    
        end {
            if (Test-FunctionInterrupt) { return }
    
            if ($ExcludeGoBatchSeparator) {
                $sql = $outsql
            } else {
                $sql = $outsql -join "`r`nGO`r`n"
                #add the final GO
                $sql += "`r`nGO"
            }
    
            if ($Path) {
                $sql | Out-File -Encoding UTF8 -FilePath $Path -Append:$Append -NoClobber:$NoClobber
            } else {
                $sql
            }
            Test-DbaDeprecation -DeprecatedOn "1.0.0" -EnableException:$false -Alias Export-SqlUser
        }
    }
    #ValidationTags#Messaging,FlowControl,Pipeline,CodeStyle#
    function Export-DbaXECsv {
        <#
        .SYNOPSIS
            Exports Extended Events to a CSV file.
    
        .DESCRIPTION
            Exports Extended Events to a CSV file.
    
        .PARAMETER Path
            Specifies the InputObject to the output CSV file
    
        .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 InputObject
            Allows Piping
    
        .NOTES
            Tags: ExtendedEvent, XE, XEvent
            Author: Gianluca Sartori (@spaghettidba)
    
            Website: https://dbatools.io
            Copyright: (c) 2018 by dbatools, licensed under MIT
            License: MIT https://opensource.org/licenses/MIT
    
        .LINK
            https://dbatools.io/Export-DbaXECsv
    
        .EXAMPLE
            PS C:\> Get-ChildItem -Path C:\temp\sample.xel | Export-DbaXECsv -Path c:\temp\sample.csv
    
            Writes Extended Events data to the file "C:\temp\events.csv".
    
        .EXAMPLE
            PS C:\> Get-DbaXESession -SqlInstance sql2014 -Session deadlocks | Export-DbaXECsv -Path c:\temp\events.csv
    
            Writes Extended Events data to the file "C:\temp\events.csv".
    
        #>
        [CmdletBinding()]
        param (
            [parameter(Mandatory, ValueFromPipeline)]
            [Alias('FullName')]
            [object[]]$InputObject,
            [parameter(Mandatory)]
            [string]$Path,
            [switch]$EnableException
        )
        begin {
            try {
                Add-Type -Path "$script:PSModuleRoot\bin\XESmartTarget\XESmartTarget.Core.dll" -ErrorAction Stop
            } catch {
                Stop-Function -Message "Could not load XESmartTarget.Core.dll" -ErrorRecord $_ -Target "XESmartTarget"
                return
            }
    
            function Get-FileFromXE ($InputObject) {
                if ($InputObject.TargetFile) {
                    if ($InputObject.TargetFile.Length -eq 0) {
                        Stop-Function -Message "This session does not have an associated Target File."
                        return
                    }
    
                    $instance = [dbainstance]$InputObject.ComputerName
    
                    if ($instance.IsLocalHost) {
                        $xelpath = $InputObject.TargetFile
                    } else {
                        $xelpath = $InputObject.RemoteTargetFile
                    }
    
                    if ($xelpath -notmatch ".xel") {
                        $xelpath = "$xelpath*.xel"
                    }
    
                    try {
                        Get-ChildItem -Path $xelpath -ErrorAction Stop
                    } catch {
                        Stop-Function -Message "Failure" -ErrorRecord $_
                    }
                }
            }
        }
        process {
            if (Test-FunctionInterrupt) { return }
    
            $getfiles = Get-FileFromXE $InputObject
    
            if ($getfiles) {
                $InputObject += $getfiles
            }
    
            foreach ($file in $InputObject) {
                if ($file -is [System.String]) {
                    $currentfile = $file
                } elseif ($file -is [System.IO.FileInfo]) {
                    $currentfile = $file.FullName
                } elseif ($file -is [Microsoft.SqlServer.Management.XEvent.Session]) {
                    # it was taken care of above
                    continue
                } else {
                    Stop-Function -Message "Unsupported file type."
                    return
                }
    
                $accessible = Test-Path -Path $currentfile
                $whoami = whoami
    
                if (-not $accessible) {
                    if ($file.Status -eq "Stopped") { continue }
                    Stop-Function -Continue -Message "$currentfile cannot be accessed from $($env:COMPUTERNAME). Does $whoami have access?"
                }
    
                if (-not (Test-Path $Path)) {
                    if ([String]::IsNullOrEmpty([IO.Path]::GetExtension($Path))) {
                        New-Item $Path -ItemType directory | Out-Null
                        $outDir = $Path
                        $outFile = [IO.Path]::GetFileNameWithoutExtension($currentfile) + ".csv"
                    } else {
                        $outDir = [IO.Path]::GetDirectoryName($Path)
                        $outFile = [IO.Path]::GetFileName($Path)
                    }
                } else {
                    if ((Get-Item $Path) -is [System.IO.DirectoryInfo]) {
                        $outDir = $Path
                        $outFile = [IO.Path]::GetFileNameWithoutExtension($currentfile) + ".csv"
                    } else {
                        $outDir = [IO.Path]::GetDirectoryName($Path)
                        $outFile = [IO.Path]::GetFileName($Path)
                    }
                }
    
                $adapter = New-Object XESmartTarget.Core.Utils.XELFileCSVAdapter
                $adapter.InputFile = $currentfile
                $adapter.OutputFile = (Join-Path $outDir $outFile)
    
                try {
                    $adapter.Convert()
                    $file = Get-ChildItem -Path $adapter.OutputFile
    
                    if ($file.Length -eq 0) {
                        Remove-Item -Path $adapter.OutputFile
                    } else {
                        $file
                    }
                } catch {
                    Stop-Function -Message "Failure" -ErrorRecord $_ -Target "XESmartTarget" -Continue
                }
            }
        }
    }
    #ValidationTags#Messaging,FlowControl,Pipeline,CodeStyle#
    function Export-DbaXESessionTemplate {
        <#
        .SYNOPSIS
            Exports an XESession XML Template using XE Session(s) output by Get-DbaXESession
    
        .DESCRIPTION
            Exports an XESession XML Template either from the Target SQL Server or XE Session(s) output by Get-DbaXESession. Exports to "$home\Documents\SQL Server Management Studio\Templates\XEventTemplates" by default
    
        .PARAMETER SqlInstance
            The target SQL Server instance or instances. You must have sysadmin access and server version must be SQL Server version 2008 or higher.
    
        .PARAMETER SqlCredential
            Login to the target instance using alternative credentials. Windows and SQL Authentication supported. Accepts credential objects (Get-Credential)
    
        .PARAMETER Session
            The Name of the session(s) to export.
    
        .PARAMETER Path
            The path to export the file into. Can be .xml or directory.
    
        .PARAMETER InputObject
            Specifies an XE Session output by Get-DbaXESession.
    
        .PARAMETER EnableException
            By default, when something goes wrong we try to catch it, interpret it and give you a friendly warning message.
            This avoids overwhelming you with "sea of red" exceptions, but is inconvenient because it basically disables advanced scripting.
            Using this switch turns this "nice by default" feature off and enables you to catch exceptions with your own try/catch.
    
        .NOTES
            Tags: ExtendedEvent, XE, XEvent
            Author: Chrissy LeMaire (@cl), netnerds.net
    
            Website: https://dbatools.io
            Copyright: (c) 2018 by dbatools, licensed under MIT
            License: MIT https://opensource.org/licenses/MIT
    
        .LINK
            https://dbatools.io/Export-DbaXESessionTemplate
    
        .EXAMPLE
            PS C:\> Export-DbaXESessionTemplate -SqlInstance sql2017 -Path C:\temp\xe
    
            Exports an XESession XML Template for all Extended Event Sessions on sql2017 to the C:\temp\xe folder.
    
        .EXAMPLE
            PS C:\> Get-DbaXESession -SqlInstance sql2017 -Session system_health | Export-DbaXESessionTemplate -Path C:\temp\xe
    
            Gets the system_health Extended Events Session from sql2017 and then exports as an XESession XML Template to C:\temp\xe
    
        #>
        [CmdletBinding()]
        param (
            [Alias("ServerInstance", "SqlServer")]
            [DbaInstanceParameter[]]$SqlInstance,
            [PSCredential]$SqlCredential,
            [object[]]$Session,
            [string]$Path = "$home\Documents\SQL Server Management Studio\Templates\XEventTemplates",
            [Parameter(ValueFromPipeline)]
            [Microsoft.SqlServer.Management.XEvent.Session[]]$InputObject,
            [switch]$EnableException
        )
        process {
            foreach ($instance in $SqlInstance) {
                try {
                    $InputObject += Get-DbaXESession -SqlInstance $instance -SqlCredential $SqlCredential -Session $Session -EnableException
                } catch {
                    Stop-Function -Message "Failure" -Category ConnectionError -ErrorRecord $_ -Target $instance -Continue
                }
            }
    
            foreach ($xes in $InputObject) {
                $xesname = Remove-InvalidFileNameChars -Name $xes.Name
    
                if (-not (Test-Path -Path $Path)) {
                    Stop-Function -Message "$Path does not exist." -Target $Path
                }
    
                if ($path.EndsWith(".xml")) {
                    $filename = $path
                } else {
                    $filename = "$path\$xesname.xml"
                }
                Write-Message -Level Verbose -Message "Wrote $xesname to $filename"
                [Microsoft.SqlServer.Management.XEvent.XEStore]::SaveSessionToTemplate($xes, $filename, $true)
                Get-ChildItem -Path $filename
            }
        }
    }
    function Find-DbaAgentJob {
        <#
        .SYNOPSIS
            Find-DbaAgentJob finds agent job/s that fit certain search filters.
    
        .DESCRIPTION
            This command filters SQL Agent jobs giving the DBA a list of jobs that may need attention or could possibly be options for removal.
    
        .PARAMETER SqlInstance
            The target SQL Server instance or instances. You must have sysadmin access and server version must be SQL Server version 2000 or higher.
    
        .PARAMETER SqlCredential
            Allows you to login to servers using SQL Logins as opposed to Windows Auth/Integrated/Trusted.
    
        .PARAMETER JobName
            Filter agent jobs to only the name(s) you list.
            Supports regular expression (e.g. MyJob*) being passed in.
    
        .PARAMETER ExcludeJobName
            Allows you to enter an array of agent job names to ignore
    
        .PARAMETER StepName
            Filter based on StepName.
            Supports regular expression (e.g. MyJob*) being passed in.
    
        .PARAMETER LastUsed
            Find all jobs that havent ran in the INT number of previous day(s)
    
        .PARAMETER IsDisabled
            Find all jobs that are disabled
    
        .PARAMETER IsFailed
            Find all jobs that have failed
    
        .PARAMETER IsNotScheduled
            Find all jobs with no schedule assigned
    
        .PARAMETER IsNoEmailNotification
            Find all jobs without email notification configured
    
        .PARAMETER Category
            Filter based on agent job categories
    
        .PARAMETER Owner
            Filter based on owner of the job/s
    
        .PARAMETER Since
            Datetime object used to narrow the results to a date
    
        .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: Agent, Job
            Author: Stephen Bennett (https://sqlnotesfromtheunderground.wordpress.com/)
    
            Website: https://dbatools.io
            Copyright: (c) 2018 by dbatools, licensed under MIT
            License: MIT https://opensource.org/licenses/MIT
    
        .LINK
            https://dbatools.io/Find-DbaAgentJob
    
        .EXAMPLE
            PS C:\> Find-DbaAgentJob -SqlInstance Dev01 -JobName *backup*
    
            Returns all agent job(s) that have backup in the name
    
        .EXAMPLE
            PS C:\> Find-DbaAgentJob -SqlInstance Dev01, Dev02 -JobName Mybackup
    
            Returns all agent job(s) that are named exactly Mybackup
    
        .EXAMPLE
            PS C:\> Find-DbaAgentJob -SqlInstance Dev01 -LastUsed 10
    
            Returns all agent job(s) that have not ran in 10 days
    
        .EXAMPLE
            PS C:\> Find-DbaAgentJob -SqlInstance Dev01 -IsDisabled -IsNoEmailNotification -IsNotScheduled
    
            Returns all agent job(s) that are either disabled, have no email notification or don't have a schedule. returned with detail
    
        .EXAMPLE
            PS C:\> $servers | Find-DbaAgentJob -IsFailed | Start-DbaAgentJob
    
            Finds all failed job then starts them. Consider using a -WhatIf at the end of Start-DbaAgentJob to see what it'll do first
    
        .EXAMPLE
            PS C:\> Find-DbaAgentJob -SqlInstance Dev01 -LastUsed 10 -Exclude "Yearly - RollUp Workload", "SMS - Notification"
    
            Returns all agent jobs that have not ran in the last 10 days ignoring jobs "Yearly - RollUp Workload" and "SMS - Notification"
    
        .EXAMPLE
            PS C:\> Find-DbaAgentJob -SqlInstance Dev01 -Category "REPL-Distribution", "REPL-Snapshot" | Format-Table -AutoSize -Wrap
    
            Returns all job/s on Dev01 that are in either category "REPL-Distribution" or "REPL-Snapshot"
    
        .EXAMPLE
            PS C:\> Find-DbaAgentJob -SqlInstance Dev01, Dev02 -IsFailed -Since '2016-07-01 10:47:00'
    
            Returns all agent job(s) on Dev01 and Dev02 that have failed since July of 2016 (and still have history in msdb)
    
        .EXAMPLE
            PS C:\> Get-DbaCmsRegServer -SqlInstance CMSServer -Group Production | Find-DbaAgentJob -Disabled -IsNotScheduled | Format-Table -AutoSize -Wrap
    
            Queries CMS server to return all SQL instances in the Production folder and then list out all agent jobs that have either been disabled or have no schedule.
    
        #>
        [CmdletBinding()]
        param (
            [parameter(Position = 0, Mandatory, ValueFromPipeline)]
            [Alias("ServerInstance", "SqlServer", "SqlServers")]
            [DbaInstanceParameter[]]$SqlInstance,
            [PSCredential]
            $SqlCredential,
            [Alias("Name")]
            [string[]]$JobName,
            [string[]]$ExcludeJobName,
            [string[]]$StepName,
            [int]$LastUsed,
            [Alias("Disabled")]
            [switch]$IsDisabled,
            [Alias("Failed")]
            [switch]$IsFailed,
            [Alias("NoSchedule")]
            [switch]$IsNotScheduled,
            [Alias("NoEmailNotification")]
            [switch]$IsNoEmailNotification,
            [string[]]$Category,
            [string]$Owner,
            [datetime]$Since,
            [Alias('Silent')]
            [switch]$EnableException
        )
        begin {
            if ($IsFailed, [boolean]$JobName, [boolean]$StepName, [boolean]$LastUsed.ToString(), $IsDisabled, $IsNotScheduled, $IsNoEmailNotification, [boolean]$Category, [boolean]$Owner, [boolean]$ExcludeJobName -notcontains $true) {
                Stop-Function -Message "At least one search term must be specified"
            }
        }
        process {
            if (Test-FunctionInterrupt) { return }
    
            foreach ($instance in $SqlInstance) {
                Write-Message -Level Verbose -Message "Running Scan on: $instance"
    
                try {
                    $server = Connect-SqlInstance -SqlInstance $instance -SqlCredential $sqlcredential
                } catch {
                    Stop-Function -Message "Failure" -Category ConnectionError -ErrorRecord $_ -Target $instance -Continue
                }
    
                $jobs = $server.JobServer.jobs
                $output = @()
    
                if ($IsFailed) {
                    Write-Message -Level Verbose -Message "Checking for failed jobs."
                    $output += $jobs | Where-Object LastRunOutcome -eq "Failed"
                }
    
                if ($JobName) {
                    Write-Message -Level Verbose -Message "Retrieving jobs by their name."
                    $output += Get-JobList -SqlInstance $server -JobFilter $JobName
                }
    
                if ($StepName) {
                    Write-Message -Level Verbose -Message "Retrieving jobs by their step names."
                    $output += Get-JobList -SqlInstance $server -StepFilter $StepName
                }
    
                if ($LastUsed) {
                    $DaysBack = $LastUsed * -1
                    $SinceDate = (Get-date).AddDays($DaysBack)
                    Write-Message -Level Verbose -Message "Finding job/s not ran in last $LastUsed days"
                    $output += $jobs | Where-Object { $_.LastRunDate -le $SinceDate }
                }
    
                if ($IsDisabled) {
                    Write-Message -Level Verbose -Message "Finding job/s that are disabled"
                    $output += $jobs | Where-Object IsEnabled -eq $false
                }
    
                if ($IsNotScheduled) {
                    Write-Message -Level Verbose -Message "Finding job/s that have no schedule defined"
                    $output += $jobs | Where-Object HasSchedule -eq $false
                }
                if ($IsNoEmailNotification) {
                    Write-Message -Level Verbose -Message "Finding job/s that have no email operator defined"
                    $output += $jobs | Where-Object { [string]::IsNullOrEmpty($_.OperatorToEmail) -eq $true }
                }
    
                if ($Category) {
                    Write-Message -Level Verbose -Message "Finding job/s that have the specified category defined"
                    $output += $jobs | Where-Object { $Category -contains $_.Category }
                }
    
                if ($Owner) {
                    Write-Message -Level Verbose -Message "Finding job/s with owner critera"
                    if ($Owner -match "-") {
                        $OwnerMatch = $Owner -replace "-", ""
                        Write-Message -Level Verbose -Message "Checking for jobs that NOT owned by: $OwnerMatch"
                        $output += $server.JobServer.jobs | Where-Object { $OwnerMatch -notcontains $_.OwnerLoginName }
                    } else {
                        Write-Message -Level Verbose -Message "Checking for jobs that are owned by: $owner"
                        $output += $server.JobServer.jobs | Where-Object { $Owner -contains $_.OwnerLoginName }
                    }
                }
    
                if ($Exclude) {
                    Write-Message -Level Verbose -Message "Excluding job/s based on Exclude"
                    $output = $output | Where-Object { $Exclude -notcontains $_.Name }
                }
    
                if ($Since) {
                    #$Since = $Since.ToString("yyyy-MM-dd HH:mm:ss")
                    Write-Message -Level Verbose -Message "Getting only jobs whose LastRunDate is greater than or equal to $since"
                    $output = $output | Where-Object { $_.LastRunDate -ge $since }
                }
    
                $jobs = $output | Select-Object -Unique
    
                foreach ($job in $jobs) {
                    Add-Member -Force -InputObject $job -MemberType NoteProperty -Name ComputerName -value $server.ComputerName
                    Add-Member -Force -InputObject $job -MemberType NoteProperty -Name InstanceName -value $server.ServiceName
                    Add-Member -Force -InputObject $job -MemberType NoteProperty -Name SqlInstance -value $server.DomainInstanceName
                    Add-Member -Force -InputObject $job -MemberType NoteProperty -Name JobName -value $job.Name
    
    
                    Select-DefaultView -InputObject $job -Property ComputerName, InstanceName, SqlInstance, Name, Category, OwnerLoginName, CurrentRunStatus, CurrentRunRetryAttempt, 'IsEnabled as Enabled', LastRunDate, LastRunOutcome, DateCreated, HasSchedule, OperatorToEmail, 'DateCreated as CreateDate'
                }
            }
        }
    }
    #ValidationTags#Messaging,FlowControl,Pipeline,CodeStyle#
    function Find-DbaBackup {
        <#
        .SYNOPSIS
            Finds SQL Server backups on disk.
    
        .DESCRIPTION
            Provides all of the same functionality for finding SQL backups to remove from disk as a standard maintenance plan would.
    
            As an addition you have the ability to check the Archive bit on files before deletion. This will allow you to ensure backups have been archived to your archive location before removal.
    
        .PARAMETER Path
            Specifies the name of the base level folder to search for backup files.
    
        .PARAMETER BackupFileExtension
            Specifies the filename extension of the backup files you wish to find (typically 'bak', 'trn' or 'log'). Do not include the period.
    
        .PARAMETER RetentionPeriod
            Specifies the retention period for backup files. Correct format is ##U.
    
            ## is the retention value and must be an integer value
            U signifies the units where the valid units are:
            h = hours
            d = days
            w = weeks
            m = months
    
            Formatting Examples:
            '48h' = 48 hours
            '7d' = 7 days
            '4w' = 4 weeks
            '1m' = 1 month
    
        .PARAMETER CheckArchiveBit
            If this switch is enabled, the filesystem Archive bit is checked.
            If this bit is set (which translates to "it has not been backed up to another location yet"), the file won't be included.
    
        .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: Backup
            Author: Chris Sommer (@cjsommer), www.cjsommer.com
    
            Website: https://dbatools.io
            Copyright: (c) 2018 by dbatools, licensed under MIT
            License: MIT https://opensource.org/licenses/MIT
    
        .LINK
            https://dbatools.io/Find-DbaBackup
    
        .EXAMPLE
            PS C:\> Find-DbaBackup -Path 'C:\MSSQL\SQL Backup\' -BackupFileExtension trn -RetentionPeriod 48h
    
            Searches for all trn files in C:\MSSQL\SQL Backup\ and all subdirectories that are more than 48 hours old will be included.
    
        .EXAMPLE
            PS C:\> Find-DbaBackup -Path 'C:\MSSQL\Backup\' -BackupFileExtension bak -RetentionPeriod 7d -CheckArchiveBit
    
            Searches for all bak files in C:\MSSQL\Backup\ and all subdirectories that are more than 7 days old will be included, but only if the files have been backed up to another location as verified by checking the Archive bit.
    
    #>
        [CmdletBinding()]
        param (
            [parameter(Mandatory, HelpMessage = "Full path to the root level backup folder (ex. 'C:\SQL\Backups'")]
            [Alias("BackupFolder")]
            [string]$Path,
            [parameter(Mandatory, HelpMessage = "Backup File extension to remove (ex. bak, trn, dif)")]
            [string]$BackupFileExtension ,
            [parameter(Mandatory, HelpMessage = "Backup retention period. (ex. 24h, 7d, 4w, 6m)")]
            [string]$RetentionPeriod ,
            [parameter(Mandatory = $false)]
            [switch]$CheckArchiveBit = $false ,
            [Alias('Silent')]
            [switch]$EnableException
        )
    
        begin {
            ### Local Functions
            function Convert-UserFriendlyRetentionToDatetime {
                [cmdletbinding()]
                param (
                    [string]$UserFriendlyRetention
                )
    
                <#
                Convert a user friendly retention value into a datetime.
                The last character of the string will indicate units (validated)
                Valid units are: (h = hours, d = days, w = weeks, m = months)
    
                The preceeding characters are the value and must be an integer (validated)
    
                Examples:
                    '48h' = 48 hours
                    '7d' = 7 days
                    '4w' = 4 weeks
                    '1m' = 1 month
                           #>
    
                [int]$Length = ($UserFriendlyRetention).Length
                $Value = ($UserFriendlyRetention).Substring(0, $Length - 1)
                $Units = ($UserFriendlyRetention).Substring($Length - 1, 1)
    
                # Validate that $Units is an accepted unit of measure
                if ( $Units -notin @('h', 'd', 'w', 'm') ) {
                    throw "RetentionPeriod '$UserFriendlyRetention' units invalid! See Get-Help for correct formatting and examples."
                }
    
                # Validate that $Value is an INT
                if ( ![int]::TryParse($Value, [ref]"") ) {
                    throw "RetentionPeriod '$UserFriendlyRetention' format invalid! See Get-Help for correct formatting and examples."
                }
    
                switch ($Units) {
                    #Variable marked as unused by PSScriptAnalyzer
                    'h' {<# $UnitString = 'Hours';#> [datetime]$ReturnDatetime = (Get-Date).AddHours( - $Value)  }
                    'd' {<# $UnitString = 'Days';#> [datetime]$ReturnDatetime = (Get-Date).AddDays( - $Value)   }
                    'w' {<# $UnitString = 'Weeks';#> [datetime]$ReturnDatetime = (Get-Date).AddDays( - $Value * 7) }
                    'm' {<# $UnitString = 'Months';#> [datetime]$ReturnDatetime = (Get-Date).AddMonths( - $Value) }
                }
                $ReturnDatetime
            }
    
            # Validations
            # Ensure BackupFileExtension does not begin with a .
            if ($BackupFileExtension -match "^[.]") {
                Write-Message -Level Warning -Message "Parameter -BackupFileExtension begins with a period '$BackupFileExtension'. A period is automatically prepended to -BackupFileExtension and need not be passed in."
            }
            # Ensure Path is a proper path
            if (!(Test-Path $Path -PathType 'Container')) {
                Stop-Function -Message "$Path not found"
            }
    
        }
        process {
            if (Test-FunctionInterrupt) { return }
            # Process stuff
            Write-Message -Message "Finding backups on $Path" -Level Verbose
            # Convert Retention Value to an actual DateTime
            try {
                $RetentionDate = Convert-UserFriendlyRetentionToDatetime -UserFriendlyRetention $RetentionPeriod
                Write-Message -Message "Backup Retention Date set to $RetentionDate" -Level Verbose
            } catch {
                Stop-Function -Message "Failed to interpret retention time!" -ErrorRecord $_
            }
    
            # Filter out unarchived files if -CheckArchiveBit parameter is used
            if ($CheckArchiveBit) {
                Write-Message -Message "Removing only archived files." -Level Verbose
                filter DbaArchiveBitFilter {
                    if ($_.Attributes -notmatch "Archive") {
                        $_
                    }
                }
            } else {
                filter DbaArchiveBitFilter {
                    $_
                }
            }
            # Enumeration may take a while. Without resorting to "esoteric" file listing facilities
            # and given we need to fetch at least the LastWriteTime, let's just use "streaming" processing
            # here to avoid issues like described in #970
            Get-ChildItem $Path -Filter "*.$BackupFileExtension" -File -Recurse -ErrorAction SilentlyContinue -ErrorVariable EnumErrors |
                Where-Object LastWriteTime -lt $RetentionDate | DbaArchiveBitFilter
            if ($EnumErrors) {
                Write-Message "Errors encountered enumerating files." -Level Warning -ErrorRecord $EnumErrors
            }
        }
    }
    #ValidationTags#Messaging,FlowControl,Pipeline,CodeStyle#
    function Find-DbaCommand {
        <#
        .SYNOPSIS
            Finds dbatools commands searching through the inline help text
    
        .DESCRIPTION
            Finds dbatools commands searching through the inline help text, building a consolidated json index and querying it because Get-Help is too slow
    
        .PARAMETER Tag
            Finds all commands tagged with this auto-populated tag
    
        .PARAMETER Author
            Finds all commands tagged with this author
    
        .PARAMETER MinimumVersion
            Finds all commands tagged with this auto-populated minimum version
    
        .PARAMETER MaximumVersion
            Finds all commands tagged with this auto-populated maximum version
    
        .PARAMETER Rebuild
            Rebuilds the index
    
        .PARAMETER Pattern
            Searches help for all commands in dbatools for the specified pattern and displays all results
    
        .PARAMETER Confirm
            Confirms overwrite of index
    
        .PARAMETER WhatIf
            Displays what would happen if the command is run
    
        .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: Find, Help, Command
            Author: Simone Bizzotto (@niphold)
    
            Website: https://dbatools.io
            Copyright: (c) 2018 by dbatools, licensed under MIT
            License: MIT https://opensource.org/licenses/MIT
    
        .LINK
            https://dbatools.io/Find-DbaCommand
    
        .EXAMPLE
            PS C:\> Find-DbaCommand "snapshot"
    
            For lazy typers: finds all commands searching the entire help for "snapshot"
    
        .EXAMPLE
            PS C:\> Find-DbaCommand -Pattern "snapshot"
    
            For rigorous typers: finds all commands searching the entire help for "snapshot"
    
        .EXAMPLE
            PS C:\> Find-DbaCommand -Tag copy
    
            Finds all commands tagged with "copy"
    
        .EXAMPLE
            PS C:\> Find-DbaCommand -Tag copy,user
    
            Finds all commands tagged with BOTH "copy" and "user"
    
        .EXAMPLE
            PS C:\> Find-DbaCommand -Author chrissy
    
            Finds every command whose author contains our beloved "chrissy"
    
        .EXAMPLE
            PS C:\> Find-DbaCommand -Author chrissy -Tag copy
    
            Finds every command whose author contains our beloved "chrissy" and it tagged as "copy"
    
        .EXAMPLE
            PS C:\> Find-DbaCommand -Pattern snapshot -Rebuild
    
            Finds all commands searching the entire help for "snapshot", rebuilding the index (good for developers)
    
        #>
        [CmdletBinding(SupportsShouldProcess = $true)]
        param (
            [String]$Pattern,
            [String[]]$Tag,
            [String]$Author,
            [String]$MinimumVersion,
            [String]$MaximumVersion,
            [switch]$Rebuild,
            [Alias('Silent')]
            [switch]$EnableException
        )
        begin {
            function Get-DbaTrimmedString($Text) {
                return $Text.Trim() -replace '(\r\n){2,}', "`n"
            }
    
            $tagsRex = ([regex]'(?m)^[\s]{0,15}Tags:(.*)$')
            $authorRex = ([regex]'(?m)^[\s]{0,15}Author:(.*)$')
            $minverRex = ([regex]'(?m)^[\s]{0,15}MinimumVersion:(.*)$')
            $maxverRex = ([regex]'(?m)^[\s]{0,15}MaximumVersion:(.*)$')
    
            function Get-DbaHelp([String]$commandName) {
                $thishelp = Get-Help $commandName -Full
                $thebase = @{ }
                $thebase.CommandName = $commandName
                $thebase.Name = $thishelp.Name
    
                $alias = Get-Alias -Definition $commandName -ErrorAction SilentlyContinue
                $thebase.Alias = $alias.Name -Join ','
    
                ## fetch the description
                $thebase.Description = $thishelp.Description.Text
    
                ## fetch examples
                $thebase.Examples = Get-DbaTrimmedString -Text ($thishelp.Examples | Out-String -Width 200)
    
                ## fetch help link
                $thebase.Links = ($thishelp.relatedLinks).NavigationLink.Uri
    
                ## fetch the synopsis
                $thebase.Synopsis = $thishelp.Synopsis
    
                ## fetch the syntax
                $thebase.Syntax = Get-DbaTrimmedString -Text ($thishelp.Syntax | Out-String -Width 600)
    
                ## store notes
                $as = $thishelp.AlertSet | Out-String -Width 600
    
                ## fetch the tags
                $tags = $tagsrex.Match($as).Groups[1].Value
                if ($tags) {
                    $thebase.Tags = $tags.Split(',').Trim()
                }
                ## fetch the author
                $author = $authorRex.Match($as).Groups[1].Value
                if ($author) {
                    $thebase.Author = $author.Trim()
                }
    
                ## fetch MinimumVersion
                $MinimumVersion = $minverRex.Match($as).Groups[1].Value
                if ($MinimumVersion) {
                    $thebase.MinimumVersion = $MinimumVersion.Trim()
                }
    
                ## fetch MaximumVersion
                $MaximumVersion = $maxverRex.Match($as).Groups[1].Value
                if ($MaximumVersion) {
                    $thebase.MaximumVersion = $MaximumVersion.Trim()
                }
    
                ## fetch Parameters
                $parameters = $thishelp.parameters.parameter
                $command = Get-Command $commandName
                $params = @()
                foreach ($p in $parameters) {
                    $paramAlias = $command.parameters[$p.Name].Aliases
                    $paramDescr = Get-DbaTrimmedString -Text ($p.Description | Out-String -Width 200)
                    $params += , @($p.Name, $paramDescr, ($paramAlias -Join ','), ($p.Required -eq $true), $p.PipelineInput, $p.DefaultValue)
                }
    
                $thebase.Params = $params
    
                [pscustomobject]$thebase
            }
    
            function Get-DbaIndex() {
                if ($Pscmdlet.ShouldProcess($dest, "Recreating index")) {
                    $dbamodule = Get-Module -Name dbatools
                    $allCommands = $dbamodule.ExportedCommands.Values | Where-Object CommandType -EQ 'Function'
    
                    $helpcoll = New-Object System.Collections.Generic.List[System.Object]
                    foreach ($command in $allCommands) {
                        $x = Get-DbaHelp "$command"
                        $helpcoll.Add($x)
                    }
                    # $dest = Get-DbatoolsConfigValue -Name 'Path.TagCache' -Fallback "$(Resolve-Path $PSScriptRoot\..)\dbatools-index.json"
                    $dest = "$moduleDirectory\bin\dbatools-index.json"
                    $helpcoll | ConvertTo-Json -Depth 4 | Out-File $dest -Encoding UTF8
                }
            }
    
            $moduleDirectory = (Get-Module -Name dbatools).ModuleBase
        }
        process {
            $Pattern = $Pattern.TrimEnd("s")
            $idxFile = "$moduleDirectory\bin\dbatools-index.json"
            if (!(Test-Path $idxFile) -or $Rebuild) {
                Write-Message -Level Verbose -Message "Rebuilding index into $idxFile"
                $swRebuild = [system.diagnostics.stopwatch]::StartNew()
                Get-DbaIndex
                Write-Message -Level Verbose -Message "Rebuild done in $($swRebuild.ElapsedMilliseconds)ms"
            }
            $consolidated = Get-Content -Raw $idxFile | ConvertFrom-Json
            $result = $consolidated
            if ($Pattern.Length -gt 0) {
                $result = $result | Where-Object { $_.PsObject.Properties.Value -like "*$Pattern*" }
            }
    
            if ($Tag.Length -gt 0) {
                foreach ($t in $Tag) {
                    $result = $result | Where-Object Tags -Contains $t
                }
            }
    
            if ($Author.Length -gt 0) {
                $result = $result | Where-Object Author -Like "*$Author*"
            }
    
            if ($MinimumVersion.Length -gt 0) {
                $result = $result | Where-Object MinimumVersion -GE $MinimumVersion
            }
    
            if ($MaximumVersion.Length -gt 0) {
                $result = $result | Where-Object MaximumVersion -LE $MaximumVersion
            }
    
            Select-DefaultView -InputObject $result -Property CommandName, Synopsis
        }
    }
    #ValidationTags#Messaging#
    function Find-DbaDatabase {
        <#
        .SYNOPSIS
            Find database/s on multiple servers that match criteria you input
    
        .DESCRIPTION
            Allows you to search SQL Server instances for database that have either the same name, owner or service broker guid.
    
            There a several reasons for the service broker guid not matching on a restored database primarily using alter database new broker. or turn off broker to return a guid of 0000-0000-0000-0000.
    
        .PARAMETER SqlInstance
            The target SQL Server instance or instances.
    
        .PARAMETER SqlCredential
            Credential object used to connect to the SQL Server as a different user
    
        .PARAMETER Property
            What you would like to search on. Either Database Name, Owner, or Service Broker GUID. Database name is the default.
    
        .PARAMETER Pattern
            Value that is searched for. This is a regular expression match but you can just use a plain ol string like 'dbareports'
    
        .PARAMETER Exact
            Search for an exact match instead of a pattern
    
        .PARAMETER Detailed
            Output all properties, will be depreciated in 1.0.0 release.
    
        .PARAMETER EnableException
            By default, when something goes wrong we try to catch it, interpret it and give you a friendly warning message.
            This avoids overwhelming you with "sea of red" exceptions, but is inconvenient because it basically disables advanced scripting.
            Using this switch turns this "nice by default" feature off and enables you to catch exceptions with your own try/catch.
    
        .NOTES
            Tags: Database
            Author: Stephen Bennett, https://sqlnotesfromtheunderground.wordpress.com/
    
            Website: https://dbatools.io
            Copyright: (c) 2018 by dbatools, licensed under MIT
            License: MIT https://opensource.org/licenses/MIT
    
        .LINK
            https://dbatools.io/Find-DbaDatabase
    
        .EXAMPLE
            PS C:\> Find-DbaDatabase -SqlInstance "DEV01", "DEV02", "UAT01", "UAT02", "PROD01", "PROD02" -Pattern Report
    
            Returns all database from the SqlInstances that have a database with Report in the name
    
        .EXAMPLE
            PS C:\> Find-DbaDatabase -SqlInstance "DEV01", "DEV02", "UAT01", "UAT02", "PROD01", "PROD02" -Pattern TestDB -Exact | Select-Object *
    
            Returns all database from the SqlInstances that have a database named TestDB with a detailed output.
    
        .EXAMPLE
            PS C:\> Find-DbaDatabase -SqlInstance "DEV01", "DEV02", "UAT01", "UAT02", "PROD01", "PROD02" -Property ServiceBrokerGuid -Pattern '-faeb-495a-9898-f25a782835f5' | Select-Object *
    
            Returns all database from the SqlInstances that have the same Service Broker GUID with a detailed output
    
        #>
        [CmdletBinding()]
        param (
            [Parameter(Mandatory, ValueFromPipeline)]
            [Alias("ServerInstance", "SqlServer")]
            [DbaInstanceParameter[]]$SqlInstance,
            [Alias("Credential")]
            [PSCredential]$SqlCredential,
            [ValidateSet('Name', 'ServiceBrokerGuid', 'Owner')]
            [string]$Property = 'Name',
            [parameter(Mandatory)]
            [string]$Pattern,
            [switch]$Exact,
            [switch]$Detailed,
            [Alias('Silent')]
            [switch]$EnableException
        )
        begin {
            Test-DbaDeprecation -DeprecatedOn 1.0.0 -Parameter Detailed
        }
        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 ($exact -eq $true) {
                    $dbs = $server.Databases | Where-Object IsAccessible | Where-Object { $_.$property -eq $pattern }
                } else {
                    try {
                        $dbs = $server.Databases | Where-Object IsAccessible | Where-Object { $_.$property.ToString() -match $pattern }
                    } catch {
                        # they probably put asterisks thinking it's a like
                        $Pattern = $Pattern -replace '\*', ''
                        $Pattern = $Pattern -replace '\%', ''
                        $dbs = $server.Databases | Where-Object { $_.$property.ToString() -match $pattern }
                    }
                }
    
                foreach ($db in $dbs) {
    
                    $extendedproperties = @()
                    foreach ($xp in $db.ExtendedProperties) {
                        $extendedproperties += [PSCustomObject]@{
                            Name  = $db.ExtendedProperties[$xp.Name].Name
                            Value = $db.ExtendedProperties[$xp.Name].Value
                        }
                    }
    
                    if ($extendedproperties.count -eq 0) { $extendedproperties = 0 }
    
                    [PSCustomObject]@{
                        ComputerName       = $server.ComputerName
                        InstanceName       = $server.ServiceName
                        SqlInstance        = $server.Name
                        Name               = $db.Name
                        SizeMB             = $db.Size
                        Owner              = $db.Owner
                        CreateDate         = $db.CreateDate
                        ServiceBrokerGuid  = $db.ServiceBrokerGuid
                        Tables             = ($db.Tables | Where-Object { $_.IsSystemObject -eq $false }).Count
                        StoredProcedures   = ($db.StoredProcedures | Where-Object { $_.IsSystemObject -eq $false }).Count
                        Views              = ($db.Views | Where-Object { $_.IsSystemObject -eq $false }).Count
                        ExtendedProperties = $extendedproperties
                        Database           = $db
                    } | Select-DefaultView -ExcludeProperty Database, ExtendedProperties, ServiceBrokerGuid, StoredProcedures, Tables, Views
                }
            }
        }
    }
    #ValidationTags#Messaging,FlowControl,Pipeline,CodeStyle#
    function Find-DbaDbGrowthEvent {
        <#
        .SYNOPSIS
            Finds any database AutoGrow events in the Default Trace.
    
        .DESCRIPTION
            Finds any database AutoGrow events in the Default Trace.
    
            The following events are included:
            92 - Data File Auto Grow
            93 - Log File Auto Grow
            94 - Data File Auto Shrink
            95 - Log File Auto Shrink
    
        .PARAMETER SqlInstance
            The target SQL Server instance or instances. This can be a collection and receive pipeline input to allow the function to be executed against multiple SQL Server instances.
    
        .PARAMETER SqlCredential
            Login to the target instance using alternative credentials. Windows and SQL Authentication supported. Accepts credential objects (Get-Credential)
    
        .PARAMETER 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 EventType
            Provide a filter on growth event type to filter the results.
    
            Allowed values: Growth, Shrink
    
        .PARAMETER FileType
            Provide a filter on file type to filter the results.
    
            Allowed values: Data, Log
    
        .PARAMETER UseLocalTime
            Return the local time of the instance instead of converting to UTC.
    
        .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: AutoGrow,Growth,Database
            Author: Aaron Nelson
    
            Website: https://dbatools.io
            Copyright: (c) 2018 by dbatools, licensed under MIT
            License: MIT https://opensource.org/licenses/MIT
    
            Query Extracted from SQL Server Management Studio (SSMS) 2016.
    
        .LINK
            https://dbatools.io/Find-DbaDatabaseGrowthEvent
    
        .EXAMPLE
            PS C:\> Find-DbaDatabaseGrowthEvent -SqlInstance localhost
    
            Returns any database AutoGrow events in the Default Trace with UTC time for the instance for every database on the localhost instance.
    
        .EXAMPLE
            PS C:\> Find-DbaDatabaseGrowthEvent -SqlInstance localhost -UseLocalTime
    
            Returns any database AutoGrow events in the Default Trace with the local time of the instance for every database on the localhost instance.
    
        .EXAMPLE
            PS C:\> Find-DbaDatabaseGrowthEvent -SqlInstance ServerA\SQL2016, ServerA\SQL2014
    
            Returns any database AutoGrow events in the Default Traces for every database on ServerA\sql2016 & ServerA\SQL2014.
    
        .EXAMPLE
            PS C:\> Find-DbaDatabaseGrowthEvent -SqlInstance ServerA\SQL2016 | Format-Table -AutoSize -Wrap
    
            Returns any database AutoGrow events in the Default Trace for every database on the ServerA\SQL2016 instance in a table format.
    
        .EXAMPLE
            PS C:\> Find-DbaDatabaseGrowthEvent -SqlInstance ServerA\SQL2016 -EventType Shrink
    
            Returns any database Auto Shrink events in the Default Trace for every database on the ServerA\SQL2016 instance.
    
        .EXAMPLE
            PS C:\> Find-DbaDatabaseGrowthEvent -SqlInstance ServerA\SQL2016 -EventType Growth -FileType Data
    
            Returns any database Auto Growth events on data files in the Default Trace for every database on the ServerA\SQL2016 instance.
    
        #>
        [CmdletBinding()]
        param (
            [parameter(Mandatory, ValueFromPipeline)]
            [Alias("ServerInstance", "SqlServer")]
            [DbaInstance[]]$SqlInstance,
            [PSCredential]$SqlCredential,
            [Alias("Databases")]
            [object[]]$Database,
            [object[]]$ExcludeDatabase,
            [ValidateSet('Growth', 'Shrink')]
            [string]$EventType,
            [ValidateSet('Data', 'Log')]
            [string]$FileType,
            [switch]$UseLocalTime,
            [Alias('Silent')]
            [switch]$EnableException
        )
    
        begin {
            $eventClass = New-Object System.Collections.ArrayList
            92..95 | ForEach-Object { $null = $eventClass.Add($_) }
    
            if (Test-Bound 'EventType', 'FileType') {
                switch ($FileType) {
                    'Data' {
                        <# should only contain events for data: 92 (grow), 94 (shrink) #>
                        $eventClass.Remove(93)
                        $eventClass.Remove(95)
                    }
                    'Log' {
                        <# should only contain events for log: 93 (grow), 95 (shrink) #>
                        $eventClass.Remove(92)
                        $eventClass.Remove(94)
                    }
                }
                switch ($EventType) {
                    'Growth' {
                        <# should only contain events for growth: 92 (data), 93 (log) #>
                        $eventClass.Remove(94)
                        $eventClass.Remove(95)
                    }
                    'Shrink' {
                        <# should only contain events for shrink: 94 (data), 95 (log) #>
                        $eventClass.Remove(92)
                        $eventClass.Remove(93)
                    }
                }
            }
    
            $eventClassFilter = $eventClass -join ","
    
            $sql = "
                BEGIN TRY
                    IF (SELECT CONVERT(INT,[value_in_use]) FROM sys.configurations WHERE [name] = 'default trace enabled' ) = 1
                        BEGIN
                            DECLARE @curr_tracefilename VARCHAR(500);
                            DECLARE @base_tracefilename VARCHAR(500);
                            DECLARE @indx INT;
    
                            SELECT @curr_tracefilename = [path]
                            FROM sys.traces
                            WHERE is_default = 1 ;
    
                            SET @curr_tracefilename = REVERSE(@curr_tracefilename);
                            SELECT @indx  = PATINDEX('%\%', @curr_tracefilename);
                            SET @curr_tracefilename = REVERSE(@curr_tracefilename);
                            SET @base_tracefilename = LEFT( @curr_tracefilename,LEN(@curr_tracefilename) - @indx) + '\log.trc';
    
                            SELECT
                                SERVERPROPERTY('MachineName') AS ComputerName,
                                ISNULL(SERVERPROPERTY('InstanceName'), 'MSSQLSERVER') AS InstanceName,
                                SERVERPROPERTY('ServerName') AS SqlInstance,
                                CONVERT(INT,(DENSE_RANK() OVER (ORDER BY [StartTime] DESC))%2) AS OrderRank,
                                    CONVERT(INT, [EventClass]) AS EventClass,
                                [DatabaseName],
                                [Filename],
                                CONVERT(INT,(Duration/1000)) AS Duration,
                                $(if (-not $UseLocalTime) { "
                                DATEADD (MINUTE, DATEDIFF(MINUTE, GETDATE(), GETUTCDATE()), [StartTime]) AS StartTime,  -- Convert to UTC time
                                DATEADD (MINUTE, DATEDIFF(MINUTE, GETDATE(), GETUTCDATE()), [EndTime]) AS EndTime,  -- Convert to UTC time"
                                }
                                else { "
                                [StartTime] AS StartTime,
                                [EndTime] AS EndTime,"
                                })
                                ([IntegerData]*8.0/1024) AS ChangeInSize,
                                ApplicationName,
                                HostName,
                                SessionLoginName,
                                SPID
                            FROM::fn_trace_gettable( @base_tracefilename, DEFAULT )
                            WHERE
                                [EventClass] IN ($eventClassFilter)
                                AND [ServerName] = @@SERVERNAME
                                AND [DatabaseName] IN (_DatabaseList_)
                            ORDER BY [StartTime] DESC;
                        END
                    ELSE
                        SELECT
                            SERVERPROPERTY('MachineName') AS ComputerName,
                            ISNULL(SERVERPROPERTY('InstanceName'), 'MSSQLSERVER') AS InstanceName,
                            SERVERPROPERTY('ServerName') AS SqlInstance,
                            -100 AS [OrderRank],
                            -1 AS [OrderRank],
                            0 AS [EventClass],
                            0 [DatabaseName],
                            0 AS [Filename],
                            0 AS [Duration],
                            0 AS [StartTime],
                            0 AS [EndTime],
                            0 AS ChangeInSize,
                            0 AS [ApplicationName],
                            0 AS [HostName],
                            0 AS [SessionLoginName],
                            0 AS [SPID]
                END	TRY
                BEGIN CATCH
                    SELECT
                        SERVERPROPERTY('MachineName') AS ComputerName,
                        ISNULL(SERVERPROPERTY('InstanceName'), 'MSSQLSERVER') AS InstanceName,
                        SERVERPROPERTY('ServerName') AS SqlInstance,
                        -100 AS [OrderRank],
                        -100 AS [OrderRank],
                        ERROR_NUMBER() AS [EventClass],
                        ERROR_SEVERITY() AS [DatabaseName],
                        ERROR_STATE() AS [Filename],
                        ERROR_MESSAGE() AS [Duration],
                        1 AS [StartTime],
                        1 AS [EndTime],
                        1 AS [ChangeInSize],
                        1 AS [ApplicationName],
                        1 AS [HostName],
                        1 AS [SessionLoginName],
                        1 AS [SPID]
                END CATCH"
    
            Test-DbaDeprecation -DeprecatedOn "1.0.0" -Alias Find-DbaDatabaseGrowthEvent
        }
        process {
            foreach ($instance in $SqlInstance) {
                try {
                    $server = Connect-SqlInstance -SqlInstance $instance -SqlCredential $SqlCredential
                } catch {
                    Stop-Function -Message "Failure" -Category ConnectionError -ErrorRecord $_ -Target $instance -Continue
                }
    
                $dbs = $server.Databases
    
                if ($Database) {
                    $dbs = $dbs | Where-Object Name -In $Database
                }
    
                if ($ExcludeDatabase) {
                    $dbs = $dbs | Where-Object Name -NotIn $ExcludeDatabase
                }
    
                #Create dblist name in 'bd1', 'db2' format
                $dbsList = "'$($($dbs | ForEach-Object {$_.Name}) -join "','")'"
                Write-Message -Level Verbose -Message "Executing query against $dbsList on $instance"
    
                $sql = $sql -replace '_DatabaseList_', $dbsList
                Write-Message -Level Debug -Message "Executing SQL Statement:`n $sql"
    
                $defaults = 'ComputerName', 'InstanceName', 'SqlInstance', 'EventClass', 'DatabaseName', 'Filename', 'Duration', 'StartTime', 'EndTime', 'ChangeInSize', 'ApplicationName', 'HostName'
    
                try {
                    Select-DefaultView -InputObject $server.Query($sql) -Property $defaults
                } catch {
                    Stop-Function -Message "Issue collecting data on $server" -Target $server -ErrorRecord $_ -Exception $_.Exception.InnerException.InnerException.InnerException -Continue
                }
            }
        }
    }
    #ValidationTags#CodeStyle,Messaging,FlowControl,Pipeline#
    function Find-DbaDbUnusedIndex {
        <#
        .SYNOPSIS
            Find unused indexes
    
        .DESCRIPTION
            This command will help you to find Unused indexes on a database or a list of databases
    
            For now only supported for CLUSTERED and NONCLUSTERED indexes
    
        .PARAMETER SqlInstance
            The SQL Server you want to check for unused indexes.
    
        .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 process. Options for this list are auto-populated from the server. If unspecified, all databases will be processed.
    
        .PARAMETER ExcludeDatabase
            Specifies the database(s) to exclude from processing. Options for this list are auto-populated from the server.
    
        .PARAMETER IgnoreUptime
            Less than 7 days uptime can mean that analysis of unused indexes is unreliable, and normally no results will be returned. By setting this option results will be returned even if the Instance has been running for less that 7 days.
    
        .PARAMETER InputObject
            Enables piping from Get-DbaDatabase
    
        .PARAMETER EnableException
            By default, when something goes wrong we try to catch it, interpret it and give you a friendly warning message.
            This avoids overwhelming you with "sea of red" exceptions, but is inconvenient because it basically disables advanced scripting.
            Using this switch turns this "nice by default" feature off and enables you to catch exceptions with your own try/catch.
    
        .NOTES
            Tags: Index
            Author: Aaron Nelson (@SQLvariant), SQLvariant.com
    
            Website: https://dbatools.io
            Copyright: (c) 2018 by dbatools, licensed under MIT
            License: MIT https://opensource.org/licenses/MIT
    
        .LINK
            https://dbatools.io/Find-DbaDbUnusedIndex
    
        .EXAMPLE
            PS C:\> Find-DbaDbUnusedIndex -SqlInstance sql2016 -Database db1, db2
    
            Finds unused databases on db1 and db2 on sql2016
    
        .EXAMPLE
            PS C:\> Find-DbaDbUnusedIndex -SqlInstance sql2016 -SqlCredential $cred
    
            Finds unused databases on db1 and db2 on sql2016 using SQL Authentication to connect to the server
    
        .EXAMPLE
            PS C:\> Get-DbaDatabase -SqlInstance sql2016 | Find-DbaDbUnusedIndex
    
            Finds unused databases on all databases on sql2016
    
           #>
        [CmdletBinding()]
        param (
            [parameter(ValueFromPipeline)]
            [DbaInstanceParameter[]]$SqlInstance,
            [PSCredential]$SqlCredential,
            [object[]]$Database,
            [object[]]$ExcludeDatabase,
            [switch]$IgnoreUptime,
            [Parameter(ValueFromPipeline)]
            [Microsoft.SqlServer.Management.Smo.Database[]]$InputObject,
            [switch]$EnableException
        )
        begin {
            # Support Compression 2008+
            $sql = "SELECT  SERVERPROPERTY('MachineName') AS ComputerName,
            ISNULL(SERVERPROPERTY('InstanceName'), 'MSSQLSERVER') AS InstanceName,
            SERVERPROPERTY('ServerName') AS SqlInstance, DB_NAME(database_id) AS 'Database'
            ,s.name AS 'Schema'
            ,t.name AS 'Table'
            ,i.object_id AS ObjectId
            ,i.name AS 'IndexName'
            ,i.index_id as 'IndexId'
            ,i.type_desc as 'TypeDesc'
            ,user_seeks as 'UserSeeks'
            ,user_scans as 'UserScans'
            ,user_lookups  as 'UserLookups'
            ,user_updates  as 'UserUpdates'
            ,last_user_seek  as 'LastUserSeek'
            ,last_user_scan  as 'LastUserScan'
            ,last_user_lookup  as 'LastUserLookup'
            ,last_user_update  as 'LastUserUpdate'
            ,system_seeks  as 'SystemSeeks'
            ,system_scans  as 'SystemScans'
            ,system_lookups  as 'SystemLookup'
            ,system_updates  as 'SystemUpdates'
            ,last_system_seek  as 'LastSystemSeek'
            ,last_system_scan  as 'LastSystemScan'
            ,last_system_lookup  as 'LastSystemLookup'
            ,last_system_update as 'LastSystemUpdate'
            FROM sys.tables t
            JOIN sys.schemas s
                ON t.schema_id = s.schema_id
            JOIN sys.indexes i
                ON i.object_id = t.object_id LEFT OUTER
            JOIN sys.dm_db_index_usage_stats iu
                ON iu.object_id = i.object_id
                    AND iu.index_id = i.index_id
            WHERE iu.database_id = DB_ID()
                    AND OBJECTPROPERTY(i.[object_id], 'IsMSShipped') = 0
                    AND user_seeks = 0
                    AND user_scans = 0
                    AND user_lookups = 0
                    AND i.type_desc NOT IN ('HEAP', 'CLUSTERED COLUMNSTORE')"
        }
    
        process {
            if ($SqlInstance) {
                $InputObject += Get-DbaDatabase -SqlInstance $SqlInstance -SqlCredential $SqlCredential -Database $Database -ExcludeDatabase $ExcludeDatabase
            }
    
            foreach ($db in $InputObject) {
                if ($db.Parent.Databases[$db].IsAccessible -eq $false) {
                    Write-Message -Level Warning -Message "Database [$db] is not accessible."
                    continue
                }
    
                $server = $db.Parent
                $instance = $server.Name
    
                if ($server.VersionMajor -lt 9) {
                    Stop-Function -Message "This function does not support versions lower than SQL Server 2005 (v9)." -Continue
                }
    
                $lastRestart = $server.Databases['tempdb'].CreateDate
                $endDate = Get-Date -Date $lastRestart
                $diffDays = (New-TimeSpan -Start $endDate -End (Get-Date)).Days
    
                if ($diffDays -le 6) {
                    if ($IgnoreUptime) {
                        Write-Message -Level Verbose -Message "The SQL Service was restarted on $lastRestart, which is not long enough for a solid evaluation."
                    } else {
                        Stop-Function -Message "The SQL Service on $instance was restarted on $lastRestart, which is not long enough for a solid evaluation." -Continue
                    }
                }
    
                <#
                    Validate if server version is:
                        - sql 2012 and if have SP3 CU3 (Build 6537) or higher
                        - sql 2014 and if have SP2 (Build 5000) or higher
                    If the major version is the same but the build is lower, throws the message
                #>
    
                if (($server.VersionMajor -eq 11 -and $server.BuildNumber -lt 6537) -or ($server.VersionMajor -eq 12 -and $server.BuildNumber -lt 5000)) {
                    Stop-Function -Message "This SQL version has a known issue. Rebuilding an index clears any existing row entry from sys.dm_db_index_usage_stats for that index.`r`nPlease refer to connect item: https://support.microsoft.com/en-us/help/3160407/fix-sys-dm-db-index-usage-stats-missing-information-after-index-rebuil" -Continue
                }
    
                if ($diffDays -le 33) {
                    Write-Message -Level Verbose -Message "The SQL Service on $instance was restarted on $lastRestart, which may not be long enough for a solid evaluation."
                }
    
                try {
                    $db.Query($sql)
                } catch {
                    Stop-Function -Message "Issue gathering indexes" -Category InvalidOperation -ErrorRecord $_ -Target $db
                }
            }
        }
        end {
            Test-DbaDeprecation -DeprecatedOn "1.0.0" -Alias Get-SqlUnusedIndex
        }
    }
    function Find-DbaDisabledIndex {
        <#
        .SYNOPSIS
            Find Disabled indexes
    
        .DESCRIPTION
            This command will help you to find disabled indexes on a database or a list of databases.
    
        .PARAMETER SqlInstance
            The target SQL Server instance or instances.
    
        .PARAMETER SqlCredential
            Login to the target instance using alternative credentials. Windows and SQL Authentication supported. Accepts credential objects (Get-Credential)
    
        .PARAMETER Database
            The database(s) to process. Options for this list are auto-populated from the server. If unspecified, all databases will be processed.
    
        .PARAMETER ExcludeDatabase
            Specifies the database(s) to exclude from processing. Options for this list are auto-populated from the server.
    
        .PARAMETER NoClobber
            If this switch is enabled, the output file will not be overwritten.
    
        .PARAMETER Append
            If this switch is enabled, content will be appended to the output file.
    
        .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: Index
            Author: Jason Squires, sqlnotnull.com
    
            Website: https://dbatools.io
            Copyright: (c) 2018 by dbatools, licensed under MIT
            License: MIT https://opensource.org/licenses/MIT
    
        .LINK
            https://dbatools.io/Find-DbadisabledIndex
    
        .EXAMPLE
            PS C:\> Find-DbaDisabledIndex -SqlInstance sql2005
    
            Generates the SQL statements to drop the selected disabled indexes on server "sql2005".
    
        .EXAMPLE
            PS C:\> Find-DbaDisabledIndex -SqlInstance sqlserver2016 -SqlCredential $cred
    
            Generates the SQL statements to drop the selected disabled indexes on server "sqlserver2016", using SQL Authentication to connect to the database.
    
        .EXAMPLE
            PS C:\> Find-DbaDisabledIndex -SqlInstance sqlserver2016 -Database db1, db2
    
            Generates the SQL Statement to drop selected indexes in databases db1 & db2 on server "sqlserver2016".
    
        .EXAMPLE
            PS C:\> Find-DbaDisabledIndex -SqlInstance sqlserver2016
    
            Generates the SQL statements to drop selected indexes on all user databases.
    
        #>
        [CmdletBinding(SupportsShouldProcess = $true)]
        param (
            [parameter(Mandatory, ValueFromPipeline)]
            [Alias("ServerInstance", "SqlServer")]
            [DbaInstanceParameter[]]$SqlInstance,
            [PSCredential]
            $SqlCredential,
            [Alias("Databases")]
            [object[]]$Database,
            [object[]]$ExcludeDatabase,
            [switch]$NoClobber,
            [switch]$Append,
            [Alias('Silent')]
            [switch]$EnableException
        )
    
        begin {
            $sql = "
            SELECT DB_NAME() AS 'DatabaseName'
            ,s.name AS 'SchemaName'
            ,t.name AS 'TableName'
            ,i.object_id AS ObjectId
            ,i.name AS 'IndexName'
            ,i.index_id as 'IndexId'
            ,i.type_desc as 'TypeDesc'
            FROM sys.tables t
            JOIN sys.schemas s
                ON t.schema_id = s.schema_id
            JOIN sys.indexes i
                ON i.object_id = t.object_id
            WHERE i.is_disabled = 1"
        }
        process {
            foreach ($instance in $SqlInstance) {
                try {
                    $server = Connect-SqlInstance -SqlInstance $instance -SqlCredential $SqlCredential  -MinimumVersion 9
                } catch {
                    Stop-Function -Message "Failure" -Category ConnectionError -ErrorRecord $_ -Target $instance -Continue
                }
    
                if ($Database) {
                    $databases = $server.Databases | Where-Object Name -in $database
                } else {
                    $databases = $server.Databases | Where-Object IsAccessible -eq $true
                }
    
                if ($databases.Count -gt 0) {
                    foreach ($db in $databases.name) {
    
                        if ($ExcludeDatabase -contains $db -or $null -eq $server.Databases[$db]) {
                            continue
                        }
    
                        try {
                            if ($PSCmdlet.ShouldProcess($db, "Getting disabled indexes")) {
                                Write-Message -Level Verbose -Message "Getting indexes from database '$db'."
                                Write-Message -Level Debug -Message "SQL Statement: $sql"
                                $disabledIndex = $server.Databases[$db].ExecuteWithResults($sql)
    
                                if ($disabledIndex.Tables[0].Rows.Count -gt 0) {
                                    $results = $disabledIndex.Tables[0];
                                    if ($results.Count -gt 0 -or !([string]::IsNullOrEmpty($results))) {
                                        foreach ($index in $results) {
                                            $index
                                        }
                                    }
                                } else {
                                    Write-Message -Level Verbose -Message "No Disabled indexes found!"
                                }
                            }
                        } catch {
                            Stop-Function -Message "Issue gathering indexes" -Category InvalidOperation -InnerErrorRecord $_ -Target $db
                        }
                    }
                } else {
                    Write-Message -Level Verbose -Message "There are no databases to analyse."
                }
            }
        }
        end {
            Test-DbaDeprecation -DeprecatedOn "1.0.0" -EnableException:$false -Alias Get-SqlDisabledIndex
        }
    }
    function Find-DbaDuplicateIndex {
        <#
        .SYNOPSIS
            Find duplicate and overlapping indexes.
    
        .DESCRIPTION
            This command will help you to find duplicate and overlapping indexes on a database or a list of databases.
    
            On SQL Server 2008 and higher, the IsFiltered property will also be checked
    
            Only supports CLUSTERED and NONCLUSTERED indexes.
    
            Output:
            TableName
            IndexName
            KeyColumns
            IncludedColumns
            IndexSizeMB
            IndexType
            CompressionDescription (When 2008+)
            [RowCount]
            IsDisabled
            IsFiltered (When 2008+)
    
        .PARAMETER SqlInstance
            The target SQL Server instance or instances.
    
        .PARAMETER SqlCredential
            Login to the target instance using alternative credentials. Windows and SQL Authentication supported. Accepts credential objects (Get-Credential)
    
        .PARAMETER Database
            The database(s) to process. Options for this list are auto-populated from the server. If unspecified, all databases will be processed.
    
        .PARAMETER IncludeOverlapping
            If this switch is enabled, indexes which are partially duplicated will be returned.
    
            Example: If the first key column is the same between two indexes, but one has included columns and the other not, this will be shown.
    
        .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: Index
            Author: Claudio Silva (@ClaudioESSilva)
    
            Website: https://dbatools.io
            Copyright: (c) 2018 by dbatools, licensed under MIT
            License: MIT https://opensource.org/licenses/MIT
    
        .LINK
            https://dbatools.io/Find-DbaDuplicateIndex
    
        .EXAMPLE
            PS C:\> Find-DbaDuplicateIndex -SqlInstance sql2005
    
            Returns duplicate indexes found on sql2005
    
        .EXAMPLE
            PS C:\> Find-DbaDuplicateIndex -SqlInstance sql2017 -SqlCredential sqladmin
    
            Finds exact duplicate indexes on all user databases present on sql2017, using SQL authentication.
    
        .EXAMPLE
            PS C:\> Find-DbaDuplicateIndex -SqlInstance sql2017 -Database db1, db2
    
            Finds exact duplicate indexes on the db1 and