Datto.DBPool.Refresh.psm1



#Region
function Set-DBPoolSecurityProtocol {
<#
    .SYNOPSIS
        The Set-DBPoolSecurityProtocol function is used to set the Security Protocol in the current context.
 
    .DESCRIPTION
        Sets the Security Protocol for a .NET application to use TLS 1.2 by default.
        This function is useful for ensuring secure communication in .NET applications.
 
    .PARAMETER Protocol
        The security protocol to use. Can be set to 'Ssl3', 'SystemDefault', 'Tls', 'Tls11', 'Tls12', and 'Tls13'.
 
    .EXAMPLE
        Set-DBPoolSecurityProtocol -Protocol Tls12
 
        Sets the Security Protocol to use TLS 1.2.
 
    .INPUTS
        [string] - The security protocol to use.
 
    .OUTPUTS
        N/A
 
    .NOTES
        Make sure to run this function in the appropriate context, as it affects .NET-wide security settings.
 
    .LINK
        N/A
#>

    [CmdletBinding(SupportsShouldProcess = $true, ConfirmImpact = 'Low')]
    param (
        [Parameter(Position = 0, Mandatory = $False, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)]
        [ValidateSet('Ssl3', 'SystemDefault', 'Tls', 'Tls11', 'Tls12', 'Tls13')]
        [string]$Protocol = 'Tls12'
    )

    Process{

        if ($PSCmdlet.ShouldProcess($Protocol, "Set Security Protocol")) {
            try {
                [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::$Protocol
                Write-Verbose "Security Protocol set to: $Protocol"
            } catch {
                Write-Error "Failed to set Security Protocol. $_"
            }
        }

    }
}
#EndRegion

#Region
function Get-RefreshDBPoolApiKey {
<#
    .SYNOPSIS
        This function gets the DBPool API key from the default PowerShell SecretManagement vault and sets the global variable.
 
    .DESCRIPTION
        This function gets the DBPool API key from the default PowerShell SecretManagement vault and sets the global variable.
        If the global variable is already set, confirm with the user before overwriting the value or set the value without confirmation using the -Force switch.
 
    .PARAMETER SecretName
        The name to use for the secret in the SecretManagement vault. Defaults to 'DBPool_ApiKey'.
 
    .PARAMETER SecretStoreName
        The name of the SecretManagement vault where the secret will be stored. Defaults to the value of the environment variable 'Datto_SecretStore'.
 
    .PARAMETER AsPlainText
        If specified, the function will return the API key as a plaintext string.
 
    .PARAMETER Force
        If specified, forces the function to overwrite the existing secret if it already exists in the vault.
 
    .EXAMPLE
        Get-RefreshDBPoolApiKey
 
        Retrieves the DBPool API key from the default SecretManagement vault with the default name 'DBPool_ApiKey' as a secure string.
 
    .EXAMPLE
        Get-RefreshDBPoolApiKey -AsPlainText
 
        Retrieves the DBPool API key from the default SecretManagement vault with the default name 'DBPool_ApiKey' as a plaintext string.
 
    .EXAMPLE
        Get-RefreshDBPoolApiKey -SecretName 'Different_SecretName' -SecretStoreName 'Custom_SecretsVault' -Force
 
        Retrieves the DBPool API key and adds it to the 'Custom_SecretsVault' SecretManagement vault with the name 'Different_SecretName'.
        If the secret already exists, it will be overwritten.
 
    .INPUTS
        N/A
 
    .OUTPUTS
        [securestring] - The DBPool API key as a secure string.
        [string] - The DBPool API key as a plaintext string.
 
    .NOTES
        This function is designed to work with the default SecretManagement vault. Ensure the vault is installed and configured before using this function.
 
    .LINK
        N/A
#>


    [CmdletBinding(SupportsShouldProcess = $true, ConfirmImpact = 'Medium')]
    param (
        [Parameter(Mandatory = $false)]
        [ValidateNotNullOrEmpty()]
        [string]$SecretName = 'DBPool_ApiKey',

        [Parameter(Mandatory = $false)]
        [string]$SecretStoreName = 'Datto_SecretStore',

        [switch]$AsPlainText,

        [switch]$Force

    )

    begin {

        $secretExists = Get-SecretInfo -Vault $SecretStoreName -WarningAction SilentlyContinue -ErrorAction SilentlyContinue -Verbose:$false | Where-Object { $_.Name -eq $SecretName }

    }

    process {

        if ( !(Test-SecretVault -Name $SecretStoreName -ErrorAction SilentlyContinue -Verbose:$false) -or !($secretExists) ) {
            Write-Error "Ensure the default SecretManagement Vault is installed and configured. Use 'Set-RefreshDBPoolApiKey' first!"
            return
        } else {
            try {

                if (!$DBPool_ApiKey) {
                    Add-DBPoolApiKey -apiKey $( Get-Secret -Name $SecretName -Vault $SecretStoreName -ErrorAction Stop ) -Verbose:$VerbosePreference
                } elseif (Get-Variable -Name 'DBPool_ApiKey' -ErrorAction SilentlyContinue) {
                    if ($Force -or $PSCmdlet.ShouldProcess('$DBPool_ApiKey', 'Set DBPool API Key')) {
                        Add-DBPoolApiKey -apiKey $( Get-Secret -Name $SecretName -Vault $SecretStoreName -ErrorAction Stop ) -Verbose:$VerbosePreference
                    }
                }

            } catch {
                Write-Error $_
            }
        }

    }

    end {
        (Get-DBPoolApiKey -AsPlainText:$AsPlainText -WarningAction SilentlyContinue -ErrorAction SilentlyContinue).ApiKey
    }

}
#EndRegion

#Region
function Remove-RefreshDBPoolApiKey {
<#
    .SYNOPSIS
        This function removes the DBPool API key to the default PowerShell SecretManagement vault.
 
    .DESCRIPTION
        This function removes the DBPool API key from the specified SecretManagement vault.
 
    .PARAMETER SecretName
        The name to use for the secret in the SecretManagement vault. Defaults to 'DBPool_ApiKey'.
 
    .PARAMETER SecretStoreName
        The name of the SecretManagement vault where the secret is stored. Defaults to the value of the environment variable 'Datto_SecretStore'.
 
    .PARAMETER Force
        If specified, forces the function to remove the secret from the vault.
 
    .EXAMPLE
        Remove-RefreshDBPoolApiKey
 
        Removes the API key from the SecretManagement vault.
 
    .EXAMPLE
        Remove-RefreshDBPoolApiKey -SecretName 'Different_SecretName' -SecretStoreName 'Custom_SecretsVault' -Force
 
        Removes the API key from the 'Custom_SecretsVault' SecretManagement vault with the name 'Different_SecretName'.
 
    .INPUTS
        N/A
 
    .OUTPUTS
        N/A
 
    .NOTES
        N/A
 
    .LINK
        N/A
#>

    [CmdletBinding(SupportsShouldProcess=$true, ConfirmImpact = 'Medium')]
    param (
        [Parameter(Mandatory = $false)]
        [ValidateNotNullOrEmpty()]
        [string]$SecretName = 'DBPool_ApiKey',

        [Parameter(Mandatory = $false)]
        [string]$SecretStoreName = 'Datto_SecretStore',

        [switch]$Force
    )

    begin {

        if ( !(Test-SecretVault -Name $SecretStoreName -ErrorAction Stop) ) {
            Write-Error "Ensure the default SecretManagement Vault is installed and configured. Use 'Register-SecretVault' -Name $SecretStoreName -DefaultVault' first!" -ErrorAction Stop
        }

    }

    process {

        $secretExists = Get-Secret -Name $SecretName -Vault $SecretStoreName -ErrorAction SilentlyContinue
        if ($secretExists) {
            if ($Force -or $PSCmdlet.ShouldProcess("Secret name [ $SecretName ] from vault [ $SecretStoreName ]")) {
                Remove-Secret -Name $SecretName -Vault $SecretStoreName
            }
        } else {
            Write-Warning "The secret '$SecretName' does not exist in the vault '$SecretStoreName'."
        }

    }

    end {}

}
#EndRegion

#Region
function Set-RefreshDBPoolApiKey {
<#
    .SYNOPSIS
        This function adds the DBPool API key to the default PowerShell SecretManagement vault.
 
    .DESCRIPTION
        This function securely stores the DBPool API key in the specified SecretManagement vault.
        It can be used to add or update the API key for later use in scripts and automation tasks.
        If the secret already exists, the function can overwrite it if the -Force switch is used.
 
    .PARAMETER SecretName
        The name to use for the secret in the SecretManagement vault. Defaults to 'DBPool_ApiKey'.
 
    .PARAMETER DBPool_ApiKey
        The secure string containing the DBPool API key. This parameter is mandatory.
        DBPool API key can be retrieved from the web interface at "$DBPool_Base_URI/web/self".
 
    .PARAMETER SecretStoreName
        The name of the SecretManagement vault where the secret will be stored.
        Default value is 'Datto_SecretStore'.
 
    .PARAMETER Force
        If specified, forces the function to overwrite the existing secret if it already exists in the vault.
 
    .EXAMPLE
        Set-RefreshDBPoolApiKey -DBPool_ApiKey $secureApiKey -Verbose
 
        Adds the DBPool API key to the default SecretManagement vault with the name 'DBPool_ApiKey'.
 
    .EXAMPLE
        Set-RefreshDBPoolApiKey -DBPool_ApiKey $secureApiKey -SecretName 'Custom_ApiKey' -SecretStoreName 'MySecretStore' -Force
 
        Adds the DBPool API key to the 'MySecretStore' SecretManagement vault with the name 'Custom_ApiKey'.
        If the secret already exists, it will be overwritten.
 
    .INPUTS
        [securestring] - The secure string containing the DBPool API key.
 
    .OUTPUTS
        N/A
 
    .NOTES
        Ensure that the PowerShell SecretManagement module is installed and configured before using this function.
 
    .LINK
        N/A
#>

    [CmdletBinding(SupportsShouldProcess = $true, ConfirmImpact = 'Medium')]
    [Alias('Add-RefreshDBPoolApiKey')]
    param (
        [Parameter(Mandatory = $false)]
        [ValidateNotNullOrEmpty()]
        [string]$SecretName = 'DBPool_ApiKey',

        [Parameter(Mandatory = $true, Position = 0, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true, HelpMessage = { "Get API key from '$(Get-DBPoolBaseURI)/web/self'" })]
        [ValidateNotNullOrEmpty()]
        [securestring]$DBPool_ApiKey,

        [Parameter(Mandatory = $false)]
        [string]$SecretStoreName = 'Datto_SecretStore',

        [Parameter()]
        [switch]$Force
    )

    begin {

        # Pass the InformationAction parameter if bound, default to 'Continue'
        if ($PSBoundParameters.ContainsKey('InformationAction')) { $InformationPreference = $PSBoundParameters['InformationAction'] } else { $InformationPreference = 'Continue' }

        if ( !(Test-SecretVault -Name $SecretStoreName -ErrorAction Stop) ) {
            Write-Error "Ensure the default SecretManagement Vault is installed and configured. Use 'Register-SecretVault' -Name $SecretStoreName -DefaultVault' first!" -ErrorAction Stop
        }

    }

    process {

        $secretExists = Get-Secret -Name $SecretName -Vault $SecretStoreName -ErrorAction SilentlyContinue
        if ($secretExists) {
            $confirmValue = -not $Force

            try {
                if ($Force) { Write-Verbose "Overwriting secret [ $SecretName ]" }
                Set-Secret -Name $SecretName -Secret $DBPool_ApiKey -Vault $SecretStoreName -Confirm:$confirmValue -ErrorAction Stop
            } catch {
                Write-Error $_
            }
        } else {
            if ($PSCmdlet.ShouldProcess("Secret [ $SecretName ]", "Set secret in vault [ $SecretStoreName ]")) {
                try {
                    Set-Secret -Name $SecretName -Secret $DBPool_ApiKey -Vault $SecretStoreName -ErrorAction Stop
                    Write-Information "Secret [ $SecretName ] has been successfully set."
                } catch {
                    Write-Error $_
                }
            }
        }

    }

    end {

        Add-DBPoolApiKey -apiKey $DBPool_ApiKey -Verbose:$false -Force

    }

}
#EndRegion

#Region
function Add-DattoSecretStore {
<#
    .SYNOPSIS
        Adds a local secret store using the Microsoft.PowerShell.SecretStore module.
 
    .DESCRIPTION
        This function adds a local secret store using the Microsoft.PowerShell.SecretStore module. Checks if the secret store is installed and install if not found.
        The function also sets the secret store configuration for the default vault.
 
    .PARAMETER Name
        The name of the secret store to add. Defaults to 'Datto_SecretStore'.
 
    .PARAMETER ModuleName
        The name of the module to use for the secret store. Defaults to 'Microsoft.PowerShell.SecretStore'.
 
    .EXAMPLE
        Add-DattoSecretStore
 
        Adds a local secret store named 'Datto_SecretStore' using the Microsoft.PowerShell.SecretStore module.
 
    .EXAMPLE
        Add-DattoSecretStore -Name 'Custom_SecretsVault' -ModuleName 'Custom.SecretStore'
 
        Adds a local secret store named 'Custom_SecretsVault' using the 'Custom.SecretStore' module.
 
    .INPUTS
        N/A
 
    .OUTPUTS
        N/A
 
    .NOTES
        N/A
 
    .LINK
        N/A
#>

    [CmdletBinding()]
    param (
       [Parameter(Mandatory = $false)]
        [string]$Name = 'Datto_SecretStore',

        [Parameter(Mandatory = $false)]
        [string]$ModuleName = 'Microsoft.PowerShell.SecretStore'
    )

    begin {

        # Pass the InformationAction parameter if bound, default to 'Continue'
        if ($PSBoundParameters.ContainsKey('InformationAction')) { $InformationPreference = $PSBoundParameters['InformationAction'] } else { $InformationPreference = 'Continue' }

        # Check if the SecretManagement module is installed
        $installedModule = Get-InstalledModule -Name $ModuleName -ErrorAction SilentlyContinue
        if ($null -eq $installedModule) {
            try {
                # Use PSResourceGet to install the module
                if (Get-InstalledModule -Name 'PSResourceGet' -ErrorAction SilentlyContinue) {
                    try {
                        Install-PSResource -Name $ModuleName -Scope CurrentUser -TrustRepository -Reinstall -NoClobber -ErrorAction Stop
                    } catch {
                        Write-Error "Failed to install $ModuleName module using 'PSResourceGet': $_"
                        return
                    }
                } else {
                    # Fall back to using Install-Module
                    try {
                        Install-Module -Name $ModuleName -Scope CurrentUser -Force -ErrorAction Stop -AllowClobber
                    } catch {
                        Write-Error "Failed to install $ModuleName module using 'Install-Module': $_"
                        return
                    }
                }
            } catch {
                Write-Error $_
                return
            }
        }

    }

    process {

        if ($ModuleName -eq 'Microsoft.PowerShell.SecretStore') {
            $storeConfiguration = @{
                Authentication  = 'None'
                PasswordTimeout = 600 # 10 minutes
                Interaction     = 'None'
                #Password = $password
                Confirm         = $False
            }
            Set-SecretStoreConfiguration @storeConfiguration
        }

        $secretStore = Get-SecretVault -ErrorAction SilentlyContinue | Where-Object { $_.Name -eq $Name }
        if ($null -eq $secretStore) {
            # Add a local secret store if the specified one is not found
            try {
                Register-SecretVault -Name $Name -ModuleName $ModuleName -DefaultVault -ErrorAction Stop
                Write-Information "Local secret store [ $Name ] has been added and set as the default vault."
            } catch {
                Write-Error "Failed to register the local secret store: $_"
            }
        } else {
            Write-Information "The secret store [ $Name ] is already set."
        }

    }

    end {
        #$Env:Datto_SecretStore = $Name
    }
}
#EndRegion

#Region
function Update-RefreshDBPoolModule {
<#
    .SYNOPSIS
        Updates the Datto.DBPool.Refresh module if a newer version is available online.
 
    .DESCRIPTION
        This function checks for updates to the Datto.DBPool.Refresh module and updates it if a newer version is available online.
        The auto-update feature can be disabled by setting the AutoUpdate parameter to $false otherwise, it will default to $true.
 
    .PARAMETER ModuleName
        The name of the module to update. Defaults to 'Datto.DBPool.Refresh'.
 
    .PARAMETER AutoUpdate
        If specified, the module will be updated if a newer version is available online. Defaults to $RefreshDBPool_Enable_AutoUpdate variable.
 
    .PARAMETER AllowPrerelease
        If specified, the module will be updated to the latest prerelease version if available. Defaults to $false.
 
    .INPUTS
        [string] - ModuleName
 
    .OUTPUTS
        N/A
 
    .EXAMPLE
        Update-RefreshDBPoolModule -ModuleName 'Datto.DBPool.Refresh' -AutoUpdate:$true -AllowPrerelease:$false
 
        Updates the Datto.DBPool.Refresh module if a newer version is available online.
 
    .NOTES
        N/A
 
    .LINK
        N/A
#>

    [CmdletBinding(SupportsShouldProcess = $true, ConfirmImpact = 'Low')]
    param (
        [Parameter( Position = 0, Mandatory = $False, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True )]
        [String]$ModuleName = 'Datto.DBPool.Refresh',

        [Parameter(Position = 1, Mandatory = $False)]
        [switch]$AutoUpdate = $RefreshDBPool_Enable_AutoUpdate,

        [Parameter(Position = 2, Mandatory = $False)]
        [switch]$AllowPrerelease = $False
    )

    begin {

        # Pass the InformationAction parameter if bound, default to 'Continue'
        if ($PSBoundParameters.ContainsKey('InformationAction')) { $InformationPreference = $PSBoundParameters['InformationAction'] } else { $InformationPreference = 'Continue' }

        if ($null -eq $PSBoundParameters['AutoUpdate'] -and $null -eq $RefreshDBPool_Enable_AutoUpdate) {
            $AutoUpdate = $true
            Write-Warning "[ RefreshDBPool_Enable_AutoUpdate ] variable not set, defaulting to $AutoUpdate."
        }
    }

    process {

        switch ($AutoUpdate) {
            $True {
                # Check to update the module if the online version seen is higher than the installed version
                Write-Verbose "Module AutoUpdate is enabled, checking for updates to the module [ $ModuleName ]..."
                try {

                    $installedModule = if (Get-Command -Name Get-InstalledPSResource -ErrorAction SilentlyContinue) {
                        Get-InstalledPSResource -Name $ModuleName -ErrorAction SilentlyContinue -Verbose:$false
                    } else {
                        Get-InstalledModule -Name $ModuleName -AllowPrerelease:$AllowPrerelease -ErrorAction SilentlyContinue -Verbose:$false
                    }
                    $onlineModule = if (Get-Command -Name Find-PSResource -ErrorAction SilentlyContinue) {
                        Find-PSResource -Name $ModuleName -Prerelease:$AllowPrerelease -ErrorAction SilentlyContinue -Verbose:$false
                    } else {
                        Find-Module -Name $ModuleName -AllowPrerelease:$AllowPrerelease -ErrorAction SilentlyContinue -Verbose:$false
                    }
                    $installedModule = $installedModule | Sort-Object -Property { [version]$_.Version } -Descending | Select-Object -First 1
                    $onlineModule = $onlineModule | Sort-Object -Property { [version]$_.Version } -Descending | Select-Object -First 1

                    if (!$installedModule) {
                        try {
                            Write-Warning "Module [ $ModuleName ] does not appear to be installed, attempting to install."
                            if (Get-Command -Name Install-PSResource -ErrorAction SilentlyContinue) {
                                Install-PSResource -Name $ModuleName -Scope 'CurrentUser' -TrustRepository -Prerelease:$AllowPrerelease -ErrorAction Stop -Verbose:$false
                            } else {
                                Install-Module $ModuleName -Scope 'CurrentUser' -Force -AllowPrerelease:$AllowPrerelease -SkipPublisherCheck -ErrorAction Stop -Verbose:$false
                            }
                            Write-Information "Module [ $ModuleName ] successfully installed."
                            Import-Module -Name $ModuleName -Force -Verbose:$false
                        } catch {
                            throw "Error installing module $ModuleName`: $_"
                        }
                    } else {
                        Write-Verbose "Module [ $($installedModule.Name) ] is already installed on the local system."

                        $installedVersion = [version]$installedModule.Version
                        $onlineVersion = [version]$onlineModule.Version

                        Write-Debug "Installed version: [ $installedVersion ] and Online version: [ $onlineVersion ]"

                        if ($installedVersion -eq $onlineVersion) {
                            Write-Host "$ModuleName version installed is [ $installedVersion ] which matches the online version [ $onlineVersion ]" -ForegroundColor Green
                        } elseif ($installedVersion -gt $onlineVersion) {
                            Write-Host "$ModuleName version installed is [ $installedVersion ] which is greater than the online version [ $onlineVersion ]`nStrange, but okay I guess?`n" -ForegroundColor Gray
                        } elseif ($installedVersion -lt $onlineVersion) {
                            Write-Warning "$ModuleName version installed is [ $installedVersion ] which is less than the online version [ $onlineVersion ]"

                            Write-Information "Updating [ $ModuleName ] from version [ $installedVersion ] to [ $onlineVersion ]."
                            if (Get-Command -Name Update-PSResource -ErrorAction SilentlyContinue) {
                                Update-PSResource -Name $ModuleName -Force -Prerelease:$AllowPrerelease -ErrorAction Stop -Verbose:$false
                            } else {
                                Update-Module -Name $ModuleName -Force -TrustRepository -AllowPrerelease:$AllowPrerelease -ErrorAction Stop -Verbose:$false
                            }

                            Import-Module -Name $ModuleName -Force -Verbose:$false
                        }

                    }
                } catch {
                    Write-Error $_
                }

            } Default {
                Write-Information "Module AutoUpdate is disabled, skipping update for module '$ModuleName'."
            }

        }

    }

    end {}

}
#EndRegion

#Region
function Remove-RefreshDBPoolLog {
<#
    .SYNOPSIS
        Remove log files older than a specified number of days.
 
    .DESCRIPTION
        The Remove-RefreshDBPoolLog cmdlet removes log files older than a specified number of days.
 
        By default, log files are stored in the following location and will be removed:
            $env:USERPROFILE\RefreshDBPool\Logs
 
    .PARAMETER LogPath
        Define the location of the log files.
 
        By default, log files are stored in the following location:
            $env:USERPROFILE\RefreshDBPool\Logs
 
    .PARAMETER LogFileName
        Define the name of the log files.
 
        By default, log files are named:
            RefreshDBPool_*.log
 
    .PARAMETER LogRotationDays
        Define the number of days to keep log files.
        By default, log files older than 90 days will be removed.
 
    .PARAMETER Force
        If specified, the function will not prompt for confirmation before removing the log files.
 
    .EXAMPLE
        Remove-RefreshDBPoolLog
 
        Remove log files older than 90 days.
 
    .EXAMPLE
        Remove-RefreshDBPoolLog -LogPath C:\RefreshDBPool\Logs -LogFileName "RefreshDBPool_*.log" -LogRotationDays 7 -Force
 
        Remove log files older than 7 days from the specified location.
 
    .INPUTS
        N/A
 
    .OUTPUTS
        N/A
 
    .NOTES
        N/A
 
    .LINK
        N/A
#>

    [CmdletBinding(SupportsShouldProcess, ConfirmImpact = 'High')]
    param (
        [string]$LogPath = $RefreshDBPool_LogPath,

        [string]$LogFileName = $RefreshDBPool_LogFileName,

        [int]$LogRotationDays = $RefreshDBPool_LogRotationDays,

        [switch]$Force
    )

    begin {

        if (-not (Test-Path -Path $LogPath -ErrorAction SilentlyContinue)) {
            throw "Log path does not exist. Run 'Export-RefreshDBPoolModuleSetting' first."
        }

    }

    process {

        $logFiles = Get-ChildItem -Path $LogPath -Filter "*$LogFileName" -File -ErrorAction SilentlyContinue
        if (-not $logFiles) {
            Write-Warning "No log files matching '*$LogFileName' found in '$LogPath'."
            return
        }

        $logFilesToRemove = $logFiles | Where-Object { $_.LastWriteTime -lt (Get-Date).AddDays(-$LogRotationDays) }
        if (-not $logFilesToRemove) {
            Write-Warning "No log files found in [ $LogPath ] older than '$LogRotationDays' days."
            return
        }

        foreach ($log in $logFilesToRemove) {

            if ($Force -or $PSCmdlet.ShouldProcess("[ $log ]", 'Remove Log file')) {
                try {
                    Remove-Item -Path $log.FullName -Force -ErrorAction Stop
                    Write-Verbose -Message "Removed log file: [ $($log.FullName) ]"
                }
                catch {
                    Write-Verbose -Message "Failed to remove log file: [ $($log.FullName) ]"
                    Write-Error -Message "$_"
                }
            }

        }

    }

    end {}

}
#EndRegion

#Region
function Export-RefreshDBPoolModuleSetting {
<#
    .SYNOPSIS
        Exports various module settings to a configuration file.
 
    .DESCRIPTION
        The Export-RefreshDBPoolSettings cmdlet exports various module settings to a configuration file which can be used to override default settings.
 
    .PARAMETER RefreshDBPoolConfPath
        Define the location to store the Refresh DBPool configuration file.
 
        By default the configuration file is stored in the following location:
            $env:USERPROFILE\RefreshDBPool
 
    .PARAMETER RefreshDBPoolConfFile
        Define the name of the refresh DBPool configuration file.
 
        By default the configuration file is named:
            config.psd1
 
    .EXAMPLE
        Export-RefreshDBPoolSettings
 
        Validates that the BaseURI, and JSON depth are set then exports their values
        to the current user's DBPool configuration file located at:
            $env:USERPROFILE\RefreshDBPool\config.psd1
 
    .EXAMPLE
        Export-RefreshDBPoolSettings -DBPoolConfPath C:\RefreshDBPool -DBPoolConfFile MyConfig.psd1
 
        Validates that the BaseURI, and JSON depth are set then exports their values
        to the current user's DBPool configuration file located at:
            C:\RefreshDBPool\MyConfig.psd1
 
    .INPUTS
        N/A
 
    .OUTPUTS
        N/A
 
    .NOTES
        N/A
 
    .LINK
        N/A
#>


    [CmdletBinding(DefaultParameterSetName = 'set')]
    Param (
        [Parameter(ParameterSetName = 'set')]
        [string]$RefreshDBPoolConfPath = $(Join-Path -Path $home -ChildPath $(if ($IsWindows -or $PSEdition -eq 'Desktop'){"RefreshDBPool"}else{".RefreshDBPool"}) ),

        [Parameter(ParameterSetName = 'set')]
        [string]$RefreshDBPoolConfFile = 'config.psd1'
    )

    begin {}

    process {

        $RefreshDBPoolConfig = Join-Path -Path $RefreshDBPoolConfPath -ChildPath $RefreshDBPoolConfFile
        Write-Verbose "Exporting 'Refresh DBPool Module' settings to [ $RefreshDBPoolConfig ]"

        # Confirm variables exist and are not null before exporting
        if ($DBPool_Base_URI -and $DBPool_JSON_Conversion_Depth) {

            if ($IsWindows -or $PSEdition -eq 'Desktop') {
                New-Item -Path $RefreshDBPoolConfPath -ItemType Directory -Force | ForEach-Object { $_.Attributes = $_.Attributes -bor "Hidden" }
            }
            else{
                New-Item -Path $RefreshDBPoolConfPath -ItemType Directory -Force
            }
@"
    @{
        ### DBPOOL REFRESH OVERRIDE CONFIG VARIABLES ###
        ## This config file is used to override variables for the DBPool Refresh module.
        ## Variables can be set below and uncommented as required.
 
 
        # Container IDs to refresh, by default all containers will be refreshed.
 
# RefreshDBPool_Container_Ids = @( 123, 456, 789 )
 
 
        # URL of the API to be checked.
        # Defaulted to "$DBPool_Base_URI" in the script already and should not need to be changed or uncommented.
 
# DBPool_Base_URI = 'https://dbpool.domain.tld'
 
 
        # Enable / Disable Auto-Update of the Refresh DBPool Module and its dependencies.
 
        RefreshDBPool_Enable_AutoUpdate = "True"
 
 
        # Enable / Disable Logging for the Refresh DBPool Module.
 
        RefreshDBPool_Logging_Enabled = "True"
        RefreshDBPool_LogPath = "$(Join-Path -Path $RefreshDBPoolConfPath -ChildPath 'Logs')"
        RefreshDBPool_LogFileName = 'RefreshDBPool.log'
        RefreshDBPool_LogRotationEnabled = "True"
        RefreshDBPool_LogRotationDays = 90
 
 
        # Timeout for the script to wait for child process jobs to "complete" and return a response (success or failure error) before exiting.
        # Default in the script is set to 3600 seconds (60 minutes).
 
# RefreshDBPool_TimeoutSeconds = 300
 
 
        ## END OF CONFIG FILE
    }
"@
 | Out-File -FilePath $RefreshDBPoolConfig -Force
        }
        else {
            Write-Error "Failed to export DBPool Module settings to [ $RefreshDBPoolConfig ]"
            Write-Error $_ -ErrorAction Stop
        }

    }

    end {}

}
#EndRegion

#Region
function Get-RefreshDBPoolModuleSetting {
<#
    .SYNOPSIS
        Gets the saved DBPool configuration settings
 
    .DESCRIPTION
        The Get-RefreshDBPoolModuleSetting cmdlet gets the saved DBPool refresh configuration settings
        from the local system.
 
        By default the configuration file is stored in the following location:
            $env:USERPROFILE\RefreshDBPool
 
    .PARAMETER RefreshDBPoolConfPath
        Define the location to store the DBPool configuration file.
 
        By default the configuration file is stored in the following location:
            $env:USERPROFILE\RefreshDBPool
 
    .PARAMETER RefreshDBPoolConfFile
        Define the name of the DBPool configuration file.
 
        By default the configuration file is named:
            config.psd1
 
    .PARAMETER openConfFile
        Opens the DBPool configuration file
 
    .EXAMPLE
        Get-RefreshDBPoolModuleSetting
 
        Gets the contents of the configuration file that was created with the
        Export-RefreshDBPoolModuleSettings
 
        The default location of the DBPool configuration file is:
            $env:USERPROFILE\RefreshDBPool\config.psd1
 
    .EXAMPLE
        Get-RefreshDBPoolModuleSetting -RefreshDBPoolConfig C:\RefreshDBPool -DBPoolConfFile MyConfig.psd1 -openConfFile
 
        Opens the configuration file from the defined location in the default editor
 
        The location of the DBPool configuration file in this example is:
            C:\RefreshDBPool\MyConfig.psd1
 
    .INPUTS
        N/A
 
    .OUTPUTS
        N/A
 
    .NOTES
        N/A
 
    .LINK
        N/A
#>


    [CmdletBinding(DefaultParameterSetName = 'index')]
    Param (
        [Parameter(Mandatory = $false, ParameterSetName = 'index')]
        [string]$RefreshDBPoolConfPath = $(Join-Path -Path $home -ChildPath $(if ($IsWindows -or $PSEdition -eq 'Desktop'){"RefreshDBPool"}else{".RefreshDBPool"}) ),

        [Parameter(Mandatory = $false, ParameterSetName = 'index')]
        [String]$RefreshDBPoolConfFile = 'config.psd1',

        [Parameter(Mandatory = $false, ParameterSetName = 'show')]
        [Switch]$openConfFile
    )

    begin {
        $RefreshDBPoolConfig = Join-Path -Path $RefreshDBPoolConfPath -ChildPath $RefreshDBPoolConfFile
    }

    process {

        if ( Test-Path -Path $RefreshDBPoolConfig ){

            if($openConfFile){
                Invoke-Item -Path $RefreshDBPoolConfig
            }
            else{
                Import-LocalizedData -BaseDirectory $RefreshDBPoolConfPath -FileName $RefreshDBPoolConfFile
            }

        }
        else{
            Write-Verbose "No configuration file found at [ $RefreshDBPoolConfig ] run 'Export-RefreshDBPoolModuleSetting' to create one."
        }

    }

    end {}

}
#EndRegion

#Region
function Import-RefreshDBPoolModuleSetting {
<#
    .SYNOPSIS
        Imports the DBPool BaseURI, API, & JSON configuration information to the current session.
 
    .DESCRIPTION
        The Import-RefreshDBPoolModuleSetting cmdlet imports the DBPool BaseURI, API, & JSON configuration
        information stored in the DBPool refresh configuration file to the users current session.
 
        By default the configuration file is stored in the following location:
            $env:USERPROFILE\RefreshDBPool
 
    .PARAMETER RefreshDBPoolConfPath
        Define the location to store the DBPool configuration file.
 
        By default the configuration file is stored in the following location:
            $env:USERPROFILE\RefreshDBPool
 
    .PARAMETER RefreshDBPoolConfFile
        Define the name of the DBPool configuration file.
 
        By default the configuration file is named:
            config.psd1
 
    .EXAMPLE
        Import-RefreshDBPoolModuleSetting
 
        Validates that the configuration file created with the Export-RefreshDBPoolModuleSettings cmdlet exists
        then imports the stored data into the current users session.
 
        The default location of the DBPool configuration file is:
            $env:USERPROFILE\RefreshDBPool\config.psd1
 
    .EXAMPLE
        Import-RefreshDBPoolModuleSetting -RefreshDBPoolConfPath C:\RefreshDBPool -RefreshDBPoolConfFile MyConfig.psd1
 
        Validates that the configuration file created with the Export-RefreshDBPoolModuleSettings cmdlet exists
        then imports the stored data into the current users session.
 
        The location of the DBPool configuration file in this example is:
            C:\RefreshDBPool\MyConfig.psd1
 
    .INPUTS
        N/A
 
    .OUTPUTS
        N/A
 
    .NOTES
        N/A
 
    .LINK
        N/A
#>


    [CmdletBinding(DefaultParameterSetName = 'set')]
    Param (
        [Parameter(ParameterSetName = 'set')]
        [string]$RefreshDBPoolConfPath = $(Join-Path -Path $home -ChildPath $(if ($IsWindows -or $PSEdition -eq 'Desktop'){"RefreshDBPool"}else{".RefreshDBPool"}) ),

        [Parameter(ParameterSetName = 'set')]
        [string]$RefreshDBPoolConfFile = 'config.psd1'
    )

    begin {
        $RefreshDBPoolConfig = Join-Path -Path $RefreshDBPoolConfPath -ChildPath $RefreshDBPoolConfFile
    }

    process {

        if ( Test-Path $RefreshDBPoolConfig ) {
            Import-LocalizedData -BaseDirectory $RefreshDBPoolConfPath -FileName $RefreshDBPoolConfFile -BindingVariable tmp_config

            foreach ($key in $tmp_config.Keys) {
                #Write-Verbose "Setting variable [ $key ] to [ $($tmp_config[$key]) ]"
                $value = $tmp_config[$key]
                if ($value -eq 'True') { $value = $true } elseif ($value -eq 'False') { $value = $false }
                if (-not [string]::IsNullOrEmpty($value)) {
                    Set-Variable -Name $key -Value $value -Scope Global -Force -Verbose:$VerbosePreference
                }
            }

            if ($tmp_config.DBPool_Base_URI) {
                # Send to function to strip potentially superfluous slash (/)
                Add-DBPoolBaseURI $tmp_config.DBPool_Base_URI -Verbose:$VerbosePreference
            } else {
                Add-DBPoolBaseURI -Verbose:$VerbosePreference
            }

            Write-Verbose "RefreshDBPool Module configuration loaded successfully from [ $RefreshDBPoolConfig ]"

            # Clean things up
            Remove-Variable "tmp_config" -Force
        }
        else {
            Write-Verbose "No configuration file found at [ $RefreshDBPoolConfig ] run 'Set-RefreshDBPoolApiKey' to get started."

            Add-DBPoolBaseURI -Verbose:$VerbosePreference

            Set-Variable -Name 'RefreshDBPool_Enable_AutoUpdate' -Value $true -Option ReadOnly -Scope Global -Force -Verbose:$VerbosePreference
        }

    }

    end {}

}
#EndRegion

#Region
# Used to auto load either baseline settings or saved configurations when the module is imported
Import-RefreshDBPoolModuleSetting -Verbose:$VerbosePreference

if (Test-SecretVault -Name 'Datto_SecretStore' -WarningAction SilentlyContinue -ErrorAction SilentlyContinue) {
    try {
        Get-RefreshDBPoolApiKey -Force -ErrorAction SilentlyContinue | Out-Null
    }
    catch {
        Write-Warning $_
    }
}
#EndRegion

#Region
function Remove-RefreshDBPoolModuleSetting {
<#
    .SYNOPSIS
        Removes the stored Refresh DBPool configuration folder.
 
    .DESCRIPTION
        The Remove-RefreshDBPoolModuleSetting cmdlet removes the Refresh DBPool folder and its files.
        This cmdlet also has the option to remove sensitive Refresh DBPool variables as well.
 
        By default configuration files are stored in the following location and will be removed:
            $env:USERPROFILE\RefreshDBPool
 
    .PARAMETER RefreshDBPoolConfPath
        Define the location of the Refresh DBPool configuration folder.
 
        By default the configuration folder is located at:
            $env:USERPROFILE\RefreshDBPool
 
    .PARAMETER andVariables
        Define if sensitive Refresh DBPool variables should be removed as well.
 
        By default the variables are not removed.
 
    .EXAMPLE
        Remove-RefreshDBPoolModuleSetting
 
        Checks to see if the default configuration folder exists and removes it if it does.
 
        The default location of the Refresh DBPool configuration folder is:
            $env:USERPROFILE\RefreshDBPool
 
    .EXAMPLE
        Remove-RefreshDBPoolModuleSetting -RefreshDBPoolConfPath C:\RefreshDBPool -andVariables
 
        Checks to see if the defined configuration folder exists and removes it if it does.
        If sensitive Refresh DBPool variables exist then they are removed as well.
 
        The location of the Refresh DBPool configuration folder in this example is:
            C:\RefreshDBPool
 
    .INPUTS
        N/A
 
    .OUTPUTS
        N/A
 
    .NOTES
        N/A
 
    .LINK
        N/A
#>


    [CmdletBinding(SupportsShouldProcess, DefaultParameterSetName = 'set')]
    Param (
        [Parameter(ParameterSetName = 'set')]
        [string]$RefreshDBPoolConfPath = $(Join-Path -Path $home -ChildPath $(if ($IsWindows -or $PSEdition -eq 'Desktop'){"RefreshDBPool"}else{".RefreshDBPool"}) ),

        [Parameter(ParameterSetName = 'set')]
        [switch]$andVariables
    )

    begin {

        # Pass the InformationAction parameter if bound, default to 'Continue'
        if ($PSBoundParameters.ContainsKey('InformationAction')) { $InformationPreference = $PSBoundParameters['InformationAction'] } else { $InformationPreference = 'Continue' }

    }

    process {

        if (Test-Path $RefreshDBPoolConfPath) {

            Remove-Item -Path $RefreshDBPoolConfPath -Recurse -Force -WhatIf:$WhatIfPreference

            If ($andVariables) {
                Remove-RefreshDBPoolAPIKey -Force -Confirm:$ConfirmPreference -WhatIf:$WhatIfPreference
                Remove-DBPoolBaseURI
            }

            if (!(Test-Path $RefreshDBPoolConfPath)) {
                Write-Information "The RefreshDBPool configuration folder has been removed successfully from [ $RefreshDBPoolConfPath ]"
            }
            else {
                Write-Error "The RefreshDBPool configuration folder could not be removed from [ $RefreshDBPoolConfPath ]"
            }

        }
        else {
            Write-Warning "No configuration folder found at [ $RefreshDBPoolConfPath ]"
        }

    }

    end {}

}
#EndRegion

#Region
function Register-RefreshDBPoolTask {
<#
    .SYNOPSIS
        Creates a scheduled task to automate the refresh of Datto DBPool containers.
 
    .DESCRIPTION
        This function sets up a scheduled task that runs a PowerShell script to refresh Datto DBPool containers.
        The task can be configured to run on specific days of the week and at a specified time.
 
    .PARAMETER TriggerTime
        Specifies the time of day at which the scheduled task should run.
        This should be set to roughly ~1 hour before shift start, so that all containers are refreshed and ready for use.
 
    .PARAMETER ExcludeDaysOfWeek
        Specifies the days of the week on which the scheduled task should NOT be run.
        This will generally be days off work, by default the task will not run on Sundays and Saturdays.
 
    .INPUTS
        N/A
 
    .OUTPUTS
        N/A
 
    .EXAMPLE
        Register-RefreshDBPoolTask -TriggerTime "7AM"
 
        This example creates a scheduled task that runs every day at 7:00 AM, except on Sundays and Saturdays.
 
    .EXAMPLE
        Register-RefreshDBPoolTask -TriggerTime "15:00"
 
        This example creates a scheduled task that runs every day at 3:00 PM, except on Sundays and Saturdays.
 
    .EXAMPLE
        Register-RefreshDBPoolTask -ExcludeDaysOfWeek 'Sunday','Monday' -TriggerTime "4:30PM"
 
        This example creates a scheduled task that runs every day at 4:30 PM, except on Sunday and Monday.
 
    .NOTES
        This function is currently designed to work only on Windows systems. It uses the Task Scheduler to create and manage the scheduled task.
        Will look to add support for Linux/MacOS using cron jobs or similar such as anacron in the future.
 
    .LINK
        https://docs.microsoft.com/en-us/powershell/module/scheduledtasks/new-scheduledtask
#>


    [CmdletBinding()]
    [Alias('New-RefreshDBPoolTask')]
    [OutputType([System.Void])]
    param (
        [Parameter(Mandatory = $true, HelpMessage = "The time of day at which the scheduled task should run.")]
        [DateTime]$TriggerTime,

        [Parameter(Mandatory = $false, HelpMessage = "The days of the week on which the scheduled task should NOT be run.")]
        [ValidateSet('Sunday','Monday','Tuesday','Wednesday','Thursday','Friday','Saturday')]
        [string[]]$ExcludeDaysOfWeek = @('Sunday','Saturday')
    )

    begin {

        # Days of the week to run the task
        $daysToRun = $( [System.DayOfWeek].GetEnumValues() ) | Where-Object { $ExcludeDaysOfWeek -notcontains [System.DayOfWeek]::$_ }

        if ($PSEdition -eq 'Desktop') {
            #$PSExecutable = Join-Path -Path $PSHOME -ChildPath 'powershell.exe'
            $PSExecutable = if (Get-Command -Name 'pwsh' -ErrorAction SilentlyContinue) {
                (Get-Command pwsh).Source
            } else {
                (Get-Command powershell).Source
            }
        } elseif ($PSEdition -eq 'Core') {
            if ($IsWindows) {
                $PSExecutable = Join-Path -Path $PSHOME -ChildPath 'pwsh.exe'
            } elseif ($IsLinux) {
            } elseif ($IsMacOS) {
            }
        }

    }

    process {

        $moduleBasePath = $( Split-Path -Path $((Get-Command Register-RefreshDBPoolTask).Module).path )
        $scriptDir = $( Join-Path -Path $moduleBasePath -ChildPath 'scripts' )
        $scriptFile = $( Join-Path -Path $scriptDir -ChildPath 'Invoke-RefreshDBPoolContainer.ps1' )

        if ($IsWindows -or $PSEdition -eq 'Desktop') {

            $taskPath = 'Datto'
            $taskName = 'DBPool-Refresh'
            $taskDescription = 'Scheduled task to automate refresh of Datto DBPool containers.'

            # Task trigger
            $triggerParams = @{
                Weekly     = $true
                DaysOfWeek = $daysToRun
                At         = $TriggerTime
            }
            $taskTrigger = New-ScheduledTaskTrigger @triggerParams

            # Task Action
            $actionParams = @{
                Execute          = "`"$PSExecutable`""
                Argument         = "-WindowStyle Minimized -NoProfile -ExecutionPolicy Bypass -File `"$scriptFile`" -Bootstrap"
                WorkingDirectory = "$moduleBasePath"
            }
            $taskAction = New-ScheduledTaskAction @actionParams

            # Task Settings
            $settingsParams = @{
                AllowStartIfOnBatteries = $true
                Compatibility           = 'Win8'
                ExecutionTimeLimit      = (New-TimeSpan -Hours 2)
                RestartCount            = 3
                RestartInterval         = (New-TimeSpan -Minutes 5)
                StartWhenAvailable      = $true
                WakeToRun               = $true
            }
            $taskSettings = New-ScheduledTaskSettingsSet @settingsParams
            # 3 corresponds to 'Stop the existing instance' https://stackoverflow.com/questions/59113643/stop-existing-instance-option-when-creating-windows-scheduled-task-using-powersh/59117015#59117015
            $taskSettings.CimInstanceProperties.Item('MultipleInstances').Value = 3

            # Task
            $taskParams = @{
                Action   = $taskAction
                Description = $taskDescription
                Settings = $taskSettings
                Trigger  = $taskTrigger
            }
            $task = New-ScheduledTask @taskParams
            $task.Author = "Kent Sapp (@cksapp)"

            $registerParams = @{
                InputObject = $task
                TaskName    = $taskName
                TaskPath    = $taskPath
                User        = $env:USERNAME
                Force       = $true
                ErrorAction = 'Stop'
            }
            try {
                $scheduledTask = Register-ScheduledTask @registerParams

                try {
                    $scheduledTask.Date = '2023-08-30T12:34:56.7890000'
                    Set-ScheduledTask -InputObject $scheduledTask -Verbose:$VerbosePreference -ErrorAction Stop | Out-Null
                }
                catch {
                    Write-Warning "Error updating 'Created Date' for scheduled task [ $taskName ]: $_"
                }
            }
            catch {
                Write-Error $_.Exception.Message
            }
        }
        else {
            Write-Warning "This function is currently only supported on Windows."
            #TODO: Add support for Linux/MacOS using cron jobs or similar such as anacron
        }

    }

    end {}

}
#EndRegion

#Region
function Update-RefreshDBPoolTask {
<#
    .SYNOPSIS
        Updates the refresh DBPool scheduled task.
 
    .DESCRIPTION
        This function updates the scheduled task that runs the refresh DBPool script by updating path and arguments.
 
    .PARAMETER Force
        Forces the update of the scheduled task.
 
    .INPUTS
        N/A
 
    .OUTPUTS
        N/A
 
    .EXAMPLE
        Update-RefreshDBPoolTask
 
        This example updates the scheduled task that runs the refresh DBPool script.
 
    .NOTES
        This function is currently only supported on Windows systems.
 
    .LINK
        N/A
#>

    [CmdletBinding(SupportsShouldProcess, ConfirmImpact = 'Low')]
    param (
        [switch]$Force
    )

    begin {

        if ($PSEdition -eq 'Desktop') {
            $PSExecutable = if (Get-Command -Name 'pwsh' -ErrorAction SilentlyContinue) {
                (Get-Command pwsh).Source
            } else {
                (Get-Command powershell).Source
            }
        } elseif ($PSEdition -eq 'Core') {
            if ($IsWindows) {
                $PSExecutable = Join-Path -Path $PSHOME -ChildPath 'pwsh.exe'
            } elseif ($IsLinux) {
            } elseif ($IsMacOS) {
            }
        }

    }

    process {

        $moduleBasePath = $( Split-Path -Path $((Get-Command Register-RefreshDBPoolTask).Module).path )
        $scriptDir = $( Join-Path -Path $moduleBasePath -ChildPath 'scripts' )
        $scriptFile = $( Join-Path -Path $scriptDir -ChildPath 'Invoke-RefreshDBPoolContainer.ps1' )

        if ($IsWindows -or $PSEdition -eq 'Desktop') {

            $taskPath = 'Datto'
            $taskName = 'DBPool-Refresh'
            try {
                $task = Get-ScheduledTask -TaskPath "*$taskPath*" -TaskName $taskName -ErrorAction SilentlyContinue

                if (-not $task) {
                    Write-Warning "Scheduled task [ $taskName ] not found. Run 'Register-RefreshDBPoolTask' first."
                    return
                }

                if ($Force -or $PSCmdlet.ShouldProcess("Scheduled task [ $taskName ]", 'Update')) {
                    $actionParams = @{
                        Execute          = "`"$PSExecutable`""
                        Argument         = "-WindowStyle Minimized -NoProfile -ExecutionPolicy Bypass -File `"$scriptFile`""
                        WorkingDirectory = "$moduleBasePath"
                    }
                    $task.Actions = New-ScheduledTaskAction @actionParams

                    Set-ScheduledTask -InputObject $task -Verbose:$VerbosePreference
                }

            }
            catch {
                Write-Error $_
            }
        }
        else {
            Write-Warning "This function is currently only supported on Windows."
            #TODO: Add support for Linux/MacOS using cron jobs or similar such as anacron
        }

    }

    end {}

}
#EndRegion

#Region
function Copy-DBPoolParentContainer {
<#
    .SYNOPSIS
        Clones the specified DBPool parent container(s) using the DBPool API.
 
    .DESCRIPTION
        This function clones the specified DBPool parent container(s) using the DBPool API. By default, this function will clone all containers if no IDs or DefaultDatabase values are provided.
 
    .PARAMETER Id
        The ID(s) of the parent container(s) to clone.
 
    .PARAMETER DefaultDatabase
        The DefaultDatabase(s) of the parent container(s) to clone.
 
    .PARAMETER ContainerName_Append
        The string to append to the cloned container name. The default value is 'clone'.
 
    .PARAMETER Duplicate
        If specified, the function will clone the parent container(s) even if a similar container already exists.
 
    .INPUTS
        [int] - Array of ID(s) of the parent container(s) to clone.
        [string] - Array of DefaultDatabase(s) of the parent container(s) to clone.
 
    .OUTPUTS
        [PSCustomObject] - Object containing the cloned container(s) information.
 
    .EXAMPLE
        Copy-DBPoolParentContainer -Id 1234
 
        Clones the DBPool parent container with the ID 1234.
 
    .EXAMPLE
        Copy-DBPoolParentContainer -DefaultDatabase 'exampleParentA'
 
        Clones the DBPool parent container with the DefaultDatabase 'exampleParentA'.
 
    .EXAMPLE
        Copy-DBPoolParentContainer -Id 1234, 5678 -ContainerName_Append 'copy'
 
        Clones the DBPool parent containers with the IDs 1234 and 5678 and appends 'copy' to the cloned container name.
 
    .EXAMPLE
        Copy-DBPoolParentContainer -DefaultDatabase 'exampleParentA', 'exampleParentB' -Duplicate
 
        Clones the DBPool parent containers with the DefaultDatabase 'exampleParentA' and 'exampleParentB' even if similar containers already exist.
 
    .NOTES
        N/A
 
    .LINK
        N/A
#>

    [CmdletBinding(DefaultParameterSetName = 'byId')]
    [Alias('Clone-DBPoolParentContainer')]
    param (
        [Parameter(Mandatory = $true, ValueFromPipeline, ValueFromPipelineByPropertyName, ParameterSetName = 'byId')]
        [int[]]$Id,

        [Parameter(Mandatory = $true, ParameterSetName = 'byDefaultDatabase')]
        [string[]]$DefaultDatabase,

        [Parameter()]
        [string]$ContainerName_Append = 'clone',

        [switch]$Duplicate
    )

    begin {

        # Pass the InformationAction parameter if bound, default to 'Continue'
        if ($PSBoundParameters.ContainsKey('InformationAction')) { $InformationPreference = $PSBoundParameters['InformationAction'] } else { $InformationPreference = 'Continue' }

        if (-not $DBPool_ApiKey) {
            Write-Warning "DBPool_ApiKey is not set. Please run 'Get-RefreshDBPoolAPIKey' to set the API key."
            return
        }

    }

    process {

        # Retrieve current parent containers
        $parentContainer = Get-DBPoolContainer -ParentContainer

        switch ($PSCmdlet.ParameterSetName) {
            'byId' {
                $myContainers = Get-DBPoolContainer
                # Filter containers based on Id and exclude those with 'BETA' in the name
                $filteredParentContainer = $parentContainer | Where-Object {
                    $_.Id -in $Id -and $_.Name -notmatch 'BETA'
                }
            }

            'byDefaultDatabase' {
                $myContainers = Get-DBPoolContainer -DefaultDatabase $DefaultDatabase
                # Filter containers based on DefaultDatabase and exclude those with 'BETA' in the name
                $filteredParentContainer = $parentContainer | Where-Object {
                    $_.defaultDatabase -in $DefaultDatabase -and $_.Name -notmatch 'BETA'
                }
            }
        }
        if ($filteredParentContainer.Count -eq 0) {
            Write-Error 'No parent container found to clone.'
            return
        }

        # Create clones of the matched containers
        $maxRunspaces = [Math]::Min($filteredParentContainer.Count, ([Environment]::ProcessorCount * 2))
        $runspacePool = [runspacefactory]::CreateRunspacePool(1, $maxRunspaces)
        $runspacePool.Open()
        $runspaces = New-Object System.Collections.ArrayList
        foreach ($parent in $filteredParentContainer) {
            # Extract the first part of the container name before 'on'; i.e. 'Parent Container Name on Database v1.2.3'
            $baseContainerName = $parent.Name -split ' on ' | Select-Object -First 1

            Write-Verbose "Checking DBPool for any container matching Parent: $($parent | Select-Object -Property 'id','name','defaultDatabase')"
            # Check if similar container already exists based on the parent container
            $existingContainerClone = $myContainers | Where-Object { $_.parent -match $parent }

            if ($existingContainerClone -and -not $Duplicate) {
                $existingContainerCloneInfo = ($existingContainerClone | ForEach-Object { "Id: $($_.Id), Name: $($_.Name)" }) -join '; '
                Write-Warning "Container with parent [ $($parent | Select-Object -Property 'id','name','defaultDatabase') ] already exists for container(s) [ $existingContainerCloneInfo ] - Skipping clone."
                Write-Debug "Use '-Duplicate' switch to force clone of existing containers."
                continue
            }

            # Determine the starting index for the clone name
            $existingCloneCount = ($existingContainerClone | Measure-Object).Count + 1

            # Clone the parent container as many times as it appears in the Id or DefaultDatabase parameter
            $cloneCount = switch ($PSCmdlet.ParameterSetName) {
                'byId' { ($Id | Where-Object { $_ -eq $parent.Id }).Count }
                'byDefaultDatabase' { ($DefaultDatabase | Where-Object { $_ -eq $parent.defaultDatabase }).Count }
            }

            for ($i = 0; $i -lt $cloneCount; $i++) {
                try {
                    if ($existingCloneCount + $i -eq 1 -and $cloneCount -eq 1) {
                        $newContainerName = "$baseContainerName($ContainerName_Append)"
                    } else {
                        $newContainerName = "$baseContainerName($ContainerName_Append-$($existingCloneCount + $i))"
                    }
                    $runspace = [powershell]::Create().AddScript({
                            param ($containerName, $parentId, $apiKey)
                            try {
                                Import-Module 'Datto.DBPool.API'
                                Add-DBPoolApiKey -apiKey $apiKey
                                New-DBPoolContainer -ParentId $parentId -ContainerName $containerName -Force
                            } catch {
                                Write-Error "Error in runspace execution: $_"
                            }
                        }).AddArgument($newContainerName).AddArgument($parent.Id).AddArgument($DBPool_ApiKey)

                    $runspace.RunspacePool = $runspacePool
                    $runspaces.Add(@{Runspace = $runspace; Handle = $runspace.BeginInvoke(); ContainerName = $newContainerName }) | Out-Null
                    Write-Information "Parent Container [ Id: $($parent.Id), Name: $($parent.Name) ] 'create' command sent for new Container [ $newContainerName ]"
                } catch {
                    Write-Error "Error sending 'create' command for new Container [ $newContainerName ]: $_"
                }
            }
            Start-Sleep -Milliseconds 500

        }

        while ($runspaces.Count -gt 0) {
            for ($i = 0; $i -lt $runspaces.Count; $i++) {
                $runspace = $runspaces[$i].Runspace
                $handle = $runspaces[$i].Handle
                if ($handle.IsCompleted) {
                    Write-Information "Success: Created DBPool container [ $($runspaces[$i].ContainerName) ]"
                    $runspace.EndInvoke($handle)
                    $runspace.Dispose()
                    $runspaces.RemoveAt($i)
                    $i--
                }
            }
            Start-Sleep -Milliseconds 500
        }

    }

    end {

        $runspacePool.Close()
        $runspacePool.Dispose()

    }

}
#EndRegion

#Region
function Sync-DBPoolContainer {
<#
    .SYNOPSIS
        Refreshes the specified DBPool container(s) using the DBPool API. By default, this function will refresh all containers if no IDs are provided.
 
    .DESCRIPTION
        This function refreshes the specified DBPool container(s) using the DBPool API. By default, this function will refresh all containers if no IDs are provided.
 
    .PARAMETER Id
        The ID(s) of the container(s) to refresh. If no IDs are provided, all containers will be refreshed.
 
    .PARAMETER TimeoutSeconds
        The maximum time in seconds to wait for the container(s) to refresh. The default value is 3600 seconds (1 hour).
 
    .PARAMETER Force
        If specified, the function will not prompt for confirmation before refreshing the container(s).
 
    .INPUTS
        [int] - Array of ID(s) of the container(s) to perform the refresh action on.
 
    .OUTPUTS
        [void] - No output is returned.
 
    .EXAMPLE
        Sync-DBPoolContainer
 
        Refreshes all DBPool containers.
 
    .EXAMPLE
        Sync-DBPoolContainer -Id 1234
 
        Refreshes the DBPool container with the ID 1234.
 
    .EXAMPLE
        Sync-DBPoolContainer -Id 1234, 5678
 
        Refreshes the DBPool containers with the IDs 1234 and 5678.
 
    .EXAMPLE
        Sync-DBPoolContainer -Id $(Get-DBPoolContainer -DefaultDatabase "Database_Name").Id
 
        Refreshes all DBPool containers matching the specified database name.
 
    .EXAMPLE
        Sync-DBPoolContainer -Id $(Get-DBPoolContainer -NotLike -Name "*Container_Name*").Id -Force
 
        Refreshes all DBPool containers not matching the specified container name.
 
    .NOTES
        N/A
 
    .LINK
        N/A
#>

    [CmdletBinding(SupportsShouldProcess = $true, ConfirmImpact = 'Medium')]
    param (
        [Parameter(Mandatory = $false, Position = 0, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true)]
        [AllowNull()]
        [AllowEmptyCollection()]
        [Alias('ContainerId')]
        [int[]]$Id = $RefreshDBPool_Container_Ids,

        [Parameter(DontShow = $true)]
        [ValidateRange(0, [int]::MaxValue)]
        [int]$TimeoutSeconds = $RefreshDBPool_TimeoutSeconds,

        [switch]$Force
    )

    begin {

        if (!(Get-Variable -Name 'DBPool_ApiKey' -Scope Global -ErrorAction SilentlyContinue)) {
            try {
                Get-RefreshDBPoolApiKey -Force -Verbose:$false -ErrorAction Stop
            }
            catch {
                throw $_
            }
        }

        if (-not $PSBoundParameters['TimeoutSeconds']) {
            $TimeoutSeconds = 3600
        }
    }

    process {

        if (!$Id) {
            Write-Warning 'No container IDs provided. Retrieving all container IDs.'
            try {
                $Id = Get-DBPoolContainer -ListContainer -ErrorAction Stop | Select-Object -ExpandProperty Id
            } catch {
                Write-Error $_
            }
        }

        $IdsToRefresh = [System.Collections.ArrayList]::new()
        foreach ($n in $Id) {
            if ($Force -or $PSCmdlet.ShouldProcess("Container [ ID: $n ]", '[ Refresh ]')) {
                $IdsToRefresh.Add($n) | Out-Null
            }
        }

        if ($IdsToRefresh.Count -gt 0) {
            try {
                Invoke-DBPoolContainerAction -Action refresh -Id $IdsToRefresh -Force -Verbose:$VerbosePreference -ThrottleLimit $IdsToRefresh.Count -TimeoutSeconds $TimeoutSeconds -ErrorAction Continue
            }
            catch {
                Write-Error $_
            }
        } elseif ($IdsToRefresh.Count -eq 0) {
            Write-Warning 'No containers refreshed.'
        }

    }

    end {}

}
#EndRegion
<#
 
    .SYNOPSIS
    A PowerShell module that connects to the Datto DBPool API to refresh containers.
 
    .DESCRIPTION
    This module is used to refresh all child containers in Datto (Kaseya) DBPool, by default all containers will be refreshed.
    Several parameters can be set with the use of an environment override file to specify the container IDs to refresh, and more.
 
    .COPYRIGHT
    Copyright (c) Kent Sapp. All rights reserved. Licensed under the MIT license.
    See https://github.com/cksapp/Datto.DBPool.Refresh/blob/main/LICENSE for license information.
 
#>


# Root Module Parameters
# This section is used to dot source all the module functions for development
if (Test-Path -Path $(Join-Path -Path $PSScriptRoot -ChildPath 'Public')) {
    # Directories to import from
    $directory = 'Public', 'Private'

    # Import functions
    $functionsToExport = @()
    $aliasesToExport = @()

    foreach ($dir in $directory) {
        $Functions = @( Get-ChildItem -Path (Join-Path -Path $PSScriptRoot -ChildPath "$dir") -Filter '*.ps1' -Recurse -ErrorAction SilentlyContinue)
        foreach ($Import in @($Functions)) {
            try {
                . $Import.fullname
                $functionsToExport += $Import.BaseName
            } catch {
                throw "Could not import function [$($Import.fullname)]: $_"
                continue
            }
        }
    }

    if ($functionsToExport.Count -gt 0) {
        Export-ModuleMember -Function $functionsToExport
    }

    foreach ($alias in Get-Alias) {
        if ($functionsToExport -contains $alias.Definition) {
            $aliasesToExport += $alias.Name
        }
    }
    if ($aliasesToExport.Count -gt 0) {
        Export-ModuleMember -Alias $aliasesToExport
    }

}