Skip to content

Conversation

@cksapp
Copy link
Owner

@cksapp cksapp commented Jan 30, 2026

Overview

Mirrors Datto-DBPool_PSWrapper v0.2.3 CI/CD implementation with comprehensive automation infrastructure.

Changes Implemented

New Workflows

  • ci.yml: Reusable CI workflow with test, PSScriptAnalyzer analyze, and quality-gate jobs
  • build-and-release.yml: Automated GitHub release creation on version tags
  • publish-psgallery.yml: PowerShell Gallery publishing with artifact handling
  • codeql.yml: CodeQL security scanning on push, PR, and scheduled weekly

Configuration

  • dependabot.yml: Automated dependency updates for github-actions ecosystem (weekly schedule)

Updated Workflows

  • Build_Tests.yaml: Updated to UpdatePWSHAction@v1.0.3, checkout@v6, removed PowerShell version pin
  • Build_DocsSite.yml: Updated actions versions, added GitHub environment variables (GITHUB_TOKEN, GITHUB_REPOSITORY, GITHUB_ACTOR)
  • PSScriptAnalyzer.yml: Upgraded to PSScriptAnalyzer action@v1.1, updated checkout@v6

Build Process Enhancements

  • psakeFile.ps1: Added AppendInitialization task to append Initialize-RefreshDBPool.ps1 to compiled module
  • PublishDocs task: Enhanced with GitHub environment variable passing to Docker container

Version Updates

  • Module version: Updated to 0.2.3
  • Datto.DBPool.API dependency: Pinned to minimum version 0.2.3 (for KB5074596 compatibility)

Documentation

  • CHANGELOG.md: Added comprehensive v0.2.3 release notes documenting all CI/CD improvements

Benefits

  • ✅ Automated testing across Windows, Linux, and macOS
  • ✅ Automated releases and PowerShell Gallery publishing
  • ✅ Security scanning with CodeQL
  • ✅ Automated dependency updates via Dependabot
  • ✅ Consistent build and documentation processes
  • ✅ Quality gates ensuring all tests pass before releases

cksapp added 11 commits June 30, 2025 15:41
Resolves MS KB5074596 in PS5.1
…omation, Dependabot, and CodeQL security scanning

- Restructured CI/CD with reusable workflows for build, test, and deployment
- Implemented automated GitHub release creation and PowerShell Gallery publishing
- Added Dependabot configuration for automated dependency updates (github-actions ecosystem)
- Added CodeQL security scanning workflow for vulnerability detection
- Updated GitHub Actions to latest versions: checkout@v6, cache@v5, UpdatePWSHAction@v1.0.3
- Removed fixed PowerShell version pins to use latest stable 7.x
- Enhanced documentation publishing with proper GitHub environment variables
- Added build process improvements with initialization script appending
- Updated module version to 0.2.3 and Datto.DBPool.API dependency to 0.2.3 minimum
Copilot AI review requested due to automatic review settings January 30, 2026 06:44
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This pull request implements comprehensive CI/CD automation infrastructure for the Datto-DBPool_Refresh PowerShell module, mirroring the v0.2.3 implementation from a related project. The changes focus on automating build, test, and release processes while updating the module version from 0.1.6.1 to 0.2.3.

Changes:

  • Added reusable GitHub Actions workflows for CI/CD (ci.yml, build-and-release.yml, publish-psgallery.yml, codeql.yml) with automated testing, security scanning, and publishing
  • Enhanced build process with PowerShellBuild 0.7.3, added initialization script appending, and function export management
  • Updated module dependencies (Datto.DBPool.API to 0.2.3, PSScriptAnalyzer 1.24.0, psake 4.9.1) and added Dependabot for automated dependency updates

Reviewed changes

Copilot reviewed 23 out of 24 changed files in this pull request and generated 7 comments.

Show a summary per file
File Description
.github/workflows/ci.yml New reusable CI workflow with test, analyze, and quality-gate jobs
.github/workflows/build-and-release.yml Automated GitHub release creation workflow on version tags
.github/workflows/publish-psgallery.yml PowerShell Gallery publishing with artifact handling
.github/workflows/codeql.yml CodeQL security scanning on push, PR, and weekly schedule
.github/dependabot.yml Automated dependency updates for github-actions ecosystem
.github/workflows/Build_Tests.yaml Updated to UpdatePWSHAction@v1.0.3 and checkout@v6
.github/workflows/Build_DocsSite.yml Updated action versions and added GitHub environment variables
.github/workflows/PSScriptAnalyzer.yml Upgraded to PSScriptAnalyzer action@v1.1 and checkout@v6
psakeFile.ps1 Added AppendInitialization task and enhanced PublishDocs with environment variables
build.ps1 Added PowerShellGet v2 enforcement to avoid PSDepend compatibility issues
requirements.psd1 Updated build dependencies (PowerShellBuild 0.7.3, PSScriptAnalyzer 1.24.0, psake 4.9.1)
src/Datto.DBPool.Refresh.psd1 Updated module version to 0.2.3, Datto.DBPool.API dependency to 0.2.3, added NestedModules list
src/Datto.DBPool.Refresh.psm1 Removed module file (now uses compiled monolithic PSM1)
src/Public/Sync-DBPoolContainer.ps1 Added aliases and verbose output with container name during confirmation
src/Public/Copy-DBPoolParentContainer.ps1 Added AllowBeta parameter, improved error messaging, fixed runspace disposal
src/Private/scheduledTask/Register-RefreshDBPoolTask.ps1 Added Documentation property, changed non-Windows message to Write-Error
src/Initialize-RefreshDBPool.ps1 Updated PowerShell installer to 7.5.4, improved execution policy check, added script download handling
README.md Updated installation script command to use shortened URL
CHANGELOG.md Added v0.2.3 release notes
docs/* Updated documentation for Copy-DBPoolParentContainer with AllowBeta parameter
Datto.DBPool.Refresh/0.2.1/* Built module artifacts (version 0.2.1)

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

README.md Outdated
@@ -11,13 +11,15 @@ The recommendation is ~30 - 60 minutes prior to the start of your shift.

Use the following script to easily install and handle **all** dependancies.
Copy link

Copilot AI Jan 30, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Typo in 'dependancies' should be 'dependencies'.

Suggested change
Use the following script to easily install and handle **all** dependancies.
Use the following script to easily install and handle **all** dependencies.

Copilot uses AI. Check for mistakes.

- name: Trigger docs publish
run: |
gh workflow run publish-docs.yml
Copy link

Copilot AI Jan 30, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The workflow references 'publish-docs.yml' which doesn't exist in the repository. The existing documentation workflow is named 'Build_DocsSite.yml'. Either create a 'publish-docs.yml' workflow or update this reference to match the existing workflow name.

Suggested change
gh workflow run publish-docs.yml
gh workflow run Build_DocsSite.yml

Copilot uses AI. Check for mistakes.
Comment on lines +1 to +1797

#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
https://datto-dbpool-refresh.kentsapp.com/Internal/Set-DBPoolSecurityProtocol/
#>
[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
https://datto-dbpool-refresh.kentsapp.com/Internal/apiKey/Get-RefreshDBPoolApiKey/
#>

[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
https://datto-dbpool-refresh.kentsapp.com/Internal/apiKey/Remove-RefreshDBPoolApiKey/
#>
[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
https://datto-dbpool-refresh.kentsapp.com/Internal/apiKey/Set-RefreshDBPoolApiKey/
#>
[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
https://datto-dbpool-refresh.kentsapp.com/Internal/apiKey/Add-DattoSecretStore/
#>
[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
https://datto-dbpool-refresh.kentsapp.com/Internal/autoUpdate/Update-RefreshDBPoolModule/
#>
[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 -Debug:$false
} else {
Get-InstalledModule -Name $ModuleName -AllowPrerelease:$AllowPrerelease -ErrorAction SilentlyContinue -Verbose:$false -Debug:$false
}
$onlineModule = if (Get-Command -Name Find-PSResource -ErrorAction SilentlyContinue) {
Find-PSResource -Name $ModuleName -Prerelease:$AllowPrerelease -ErrorAction SilentlyContinue -Verbose:$false -Debug:$false
} else {
Find-Module -Name $ModuleName -AllowPrerelease:$AllowPrerelease -ErrorAction SilentlyContinue -Verbose:$false -Debug:$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
Write-Debug "Installed module: [ $($installedModule.Name) ] and Online module: [ $($onlineModule.Name) ]"

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 -Debug:$false
} else {
Install-Module $ModuleName -Scope 'CurrentUser' -Force -AllowPrerelease:$AllowPrerelease -SkipPublisherCheck -ErrorAction Stop -Verbose:$false -Debug:$false
}
Write-Information "Module [ $ModuleName ] successfully installed."
Import-Module -Name $ModuleName -Force -Verbose:$false -Debug:$false
} catch {
throw "Error installing module $ModuleName`: $_"
}
} else {
Write-Verbose "Module [ $($installedModule.Name) ] is already installed on the local system."

$installedVersion = [version]$installedModule.Version

if ($null -ne $onlineModule -and $onlineModule.Version) {
$onlineVersion = [version]$onlineModule.Version

Write-Debug "Installed version: [ $( $installedVersion.ToString() ) ] and Online version: [ $( $onlineVersion.ToString() ) ]"

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

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

Import-Module -Name $ModuleName -Force -Verbose:$false -Debug:$false
}
} else {
Write-Warning "Failed to retrieve the online version of $ModuleName. Skipping update."
}
}
} 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
https://datto-dbpool-refresh.kentsapp.com/Internal/logging/Remove-RefreshDBPoolLog/
#>
[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
https://datto-dbpool-refresh.kentsapp.com/Internal/moduleSettings/Export-RefreshDBPoolModuleSetting/
#>

[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


# Script timeout wait for API reachable, and child process jobs to "complete" and return a response (success or failure error) before exit.
# Default in the script is set to 3600 seconds (60 minutes).

# RefreshDBPool_TimeoutSeconds = 300

# Refresh DBPool Script Verbose Preference
# RefreshDBPool_VerbosePreference = "True"


## 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
https://datto-dbpool-refresh.kentsapp.com/Internal/moduleSettings/Get-RefreshDBPoolModuleSetting/
#>

[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
https://datto-dbpool-refresh.kentsapp.com/Internal/moduleSettings/Import-RefreshDBPoolModuleSetting/
#>

[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
https://datto-dbpool-refresh.kentsapp.com/Internal/moduleSettings/Remove-RefreshDBPoolModuleSetting/
#>

[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://datto-dbpool-refresh.kentsapp.com/Internal/scheduledTask/Register-RefreshDBPoolTask/

.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)"
$task.Documentation = 'https://datto-dbpool-refresh.kentsapp.com'

$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-Error "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
https://datto-dbpool-refresh.kentsapp.com/Internal/scheduledTask/Update-RefreshDBPoolTask/
#>
[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. The cloned container(s) will have the same parent container as the original container(s) and will be appended with the specified string.

.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.

.PARAMETER AllowBeta
If specified, the function will allow cloning of parent containers with 'BETA' in the name.
By default, BETA containers are excluded from cloning.

.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.

.EXAMPLE
Copy-DBPoolParentContainer -DefaultDatabase 'exampleParentA', 'exampleParentB', 'exampleParentA

Clones the DBPool parent containers with the DefaultDatabase 'exampleParentA' and 'exampleParentB' and appends a number to any duplicate clones.

----------------------------------------------------------------

Parent Container [ Id: 7, Name: exampleParentB staging ] 'create' command sent for new Container [ exampleB staging(clone) ]
Parent Container [ Id: 3, Name: exampleParentA on SQL 1.2.3 ] 'create' command sent for new Container [ exampleA(clone-1) ]
Parent Container [ Id: 3, Name: exampleParentA on SQL 1.2.3 ] 'create' command sent for new Container [ exampleA(clone-2) ]
Parent Container [ Id: 4, Name: exampleParentB on 4.5.6 ] 'create' command sent for new Container [ exampleB(clone) ]

.NOTES
Does not clone any parent containers with 'BETA' in the name, unless specified with '-AllowBeta' switch.
Also removes parent name suffixes like 'on Database v1.2.3' before appending the ContainerName_Append string.
Appends a number to the cloned container name if multiple matching clones are created with same parent at once, or for any matching clones that already exist when using the '-Duplicate' switch.

.LINK
https://datto-dbpool-refresh.kentsapp.com/Copy-DBPoolParentContainer/
#>
[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,

[Parameter(DontShow = $true)]
[switch]$AllowBeta
)

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
$filteredParentContainer = if ($AllowBeta) {
$parentContainer | Where-Object { $_.Id -in $Id }
} else {
$parentContainer | Where-Object { $_.Id -in $Id -and $_.Name -notmatch 'BETA' }
}
}

'byDefaultDatabase' {
$myContainers = Get-DBPoolContainer -DefaultDatabase $DefaultDatabase -WarningAction SilentlyContinue
$filteredParentContainer = if ($AllowBeta) {
$parentContainer | Where-Object { $_.defaultDatabase -in $DefaultDatabase }
} else {
$parentContainer | Where-Object { $_.defaultDatabase -in $DefaultDatabase -and $_.Name -notmatch 'BETA' }
}
}
}
if ($filteredParentContainer.Count -eq 0) {
Write-Error 'No matching parent container(s) 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 {

if ($filteredParentContainer.Count -ne 0) {
$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
https://datto-dbpool-refresh.kentsapp.com/Sync-DBPoolContainer/
#>
[CmdletBinding(SupportsShouldProcess = $true, ConfirmImpact = 'Medium')]
[Alias('Refresh-DBPoolContainer', 'Refresh-DBPool', 'Sync-DBPool')]
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) {
# Try to get the container name for the ID to output when using Verbose
if ( ($PSCmdlet.MyInvocation.BoundParameters['Verbose'].IsPresent) -and ( (-not $Force) -and ($ConfirmPreference -in @('Low', 'Medium')) ) ) {
try {
$containerName = (Get-DBPoolContainer -Id $n -WarningAction SilentlyContinue -ErrorAction Stop -Verbose:$false).name
} catch {
#Write-Warning "Failed to get the container name for ID $n. $_"
$containerName = '## FailedToGetContainerName ##'
}
Write-Verbose "Confirm action [ Refresh ] for Container [ ID: $n, Name: $containerName ]"
}
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
Copy link

Copilot AI Jan 30, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Build artifacts (compiled module files) are being committed to the repository. The Datto.DBPool.Refresh/0.2.1/ directory appears to be a build output artifact and should not be included in the repository. Consider adding build output directories to .gitignore to prevent committing compiled modules. Typically, only source files should be committed, and build artifacts should be generated during the build process.

Copilot uses AI. Check for mistakes.

- uses: microsoft/psscriptanalyzer-action@v1.1
with:
path: .\
Copy link

Copilot AI Jan 30, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The path separator is incorrect for Linux runners. On line 33, Windows-style backslash .\ is used, but this workflow runs on ubuntu-latest. This should be a forward slash ./ or just . to work correctly on Linux.

Suggested change
path: .\
path: .

Copilot uses AI. Check for mistakes.
$pwshInstallerUrl = 'https://github.com/PowerShell/PowerShell/releases/download/v7.5.0/PowerShell-7.5.0-win-x64.msi'
$pwshInstallerHash = '6B988B7E236A8E1CF1166D3BE289D3A20AA344499153BDAADD2F9FEDFFC6EDA9'
$pwshInstallerUrl = 'https://github.com/PowerShell/PowerShell/releases/download/v7.5.4/PowerShell-7.5.4-win-x64.msi'
$pwshInstallerHash = 'sha256:84a39d39f113f884333686c4df70bc6c517f5b5d3982d88b4a0139f10ebb3fcb'
Copy link

Copilot AI Jan 30, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The hash format has changed from a plain hash string to include a 'sha256:' prefix. This change may cause issues with hash validation. The previous version used a plain uppercase hash, while the new version includes 'sha256:' prefix with lowercase hex. Verify that the hash validation logic in the download code can handle this format change, or update the hash to match the expected format without the 'sha256:' prefix.

Suggested change
$pwshInstallerHash = 'sha256:84a39d39f113f884333686c4df70bc6c517f5b5d3982d88b4a0139f10ebb3fcb'
$pwshInstallerHash = '84A39D39F113F884333686C4DF70BC6C517F5B5D3982D88B4A0139F10EBB3FCB'

Copilot uses AI. Check for mistakes.
- name: Create release archive
shell: pwsh
run: |
$modulePath = "out/Datto.DBPool.Refresh/${{ steps.version.outputs.version }}"
Copy link

Copilot AI Jan 30, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The module path may be incorrect. The psakeFile.ps1 sets the output directory to the project root ($PSBPreference.Build.OutDir = "$projectRoot"), which would typically create the module at Datto.DBPool.Refresh/$version in the root directory. However, this workflow is looking for the built module at out/Datto.DBPool.Refresh/$version. Verify that PowerShellBuild's actual output path matches the path used here, or update the path to match the actual build output location.

Suggested change
$modulePath = "out/Datto.DBPool.Refresh/${{ steps.version.outputs.version }}"
$modulePath = "Datto.DBPool.Refresh/${{ steps.version.outputs.version }}"

Copilot uses AI. Check for mistakes.
psakeFile.ps1 Outdated
Comment on lines 81 to 92
Task PublishDocs -Depends Build {
$env:GITHUB_TOKEN = $env:GITHUB_TOKEN ?? ''
$env:GITHUB_REPOSITORY = $env:GITHUB_REPOSITORY ?? ''
$env:GITHUB_ACTOR = $env:GITHUB_ACTOR ?? ''

Exec {
docker run -v "$($psake.build_script_dir)`:/docs" `
-e 'CI=true' `
-e "GITHUB_TOKEN=$env:GITHUB_TOKEN" `
-e "GITHUB_REPOSITORY=$env:GITHUB_REPOSITORY" `
-e "GITHUB_ACTOR=$env:GITHUB_ACTOR" `
--entrypoint 'sh' squidfunk/mkdocs-material:9 -c 'pip install -r requirements.txt && mkdocs gh-deploy --force'
Copy link

Copilot AI Jan 30, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The PublishDocs task runs a third-party Docker image squidfunk/mkdocs-material:9 via docker run while injecting GitHub secrets (GITHUB_TOKEN, GITHUB_REPOSITORY, GITHUB_ACTOR) as environment variables, and the image is only pinned by a mutable tag. If that upstream image or its registry is compromised, attacker-controlled code inside the container can exfiltrate these secrets and gain control over the repository or CI context. Prefer using an internally built or officially maintained image pinned to an immutable digest, and avoid passing GitHub tokens or other secrets into untrusted containers unless strictly necessary and tightly scoped.

Copilot uses AI. Check for mistakes.
cksapp and others added 2 commits January 30, 2026 01:54
Removed Build.Exclude and AppendInitialization task additions.
Bootstrap helper scripts (Initialize-RefreshDBPool.ps1, Invoke-RefreshDBPoolInstall.ps1)
are copied via CopyDirectories configuration and remain as standalone tools.
Build process only compiles Public and Private directory functions.
Address all 6 Copilot review comments for PR #7:

Workflow Fixes:
- Fix ci.yml path separator for Linux compatibility (. instead of .\\)
- Correct module output path in build-and-release.yml (removed 'out/' prefix)
- Update publish-psgallery.yml workflow reference (publish-docs.yml → Build_DocsSite.yml)
- Pin Docker image to immutable digest in psakeFile.ps1 for security
  * squidfunk/mkdocs-material:9 → squidfunk/mkdocs-material:9@sha256:3bba0a99bc6e635bb8e53f379d32ab9cecb554adee9cc8f59a347f93ecf82f3b

Documentation:
- Fix README.md typo: dependancies → dependencies

Module Updates:
- Normalize PowerShell installer hash format in Initialize-RefreshDBPool.ps1
  * Remove 'sha256:' prefix, convert to uppercase for consistency

Workflow Consolidation:
- Remove deprecated workflows (Build_Tests.yaml, PSScriptAnalyzer.yml, codeql.yml)
- Functionality consolidated into comprehensive ci.yml workflow

These changes implement PSWrapper workflow patterns with enhanced:
- Version validation and duplicate checking
- CHANGELOG extraction and release notes generation
- Quality gates and comprehensive testing
- Security improvements with immutable Docker digests

Co-authored-by: GitHub Copilot <noreply@github.com>
@github-advanced-security
Copy link

This pull request sets up GitHub code scanning for this repository. Once the scans have completed and the checks have passed, the analysis results for this pull request branch will appear on this overview. Once you merge this pull request, the 'Security' tab will show more code scanning analysis results (for example, for the default branch). Depending on your configuration and choice of analysis tool, future pull requests will be annotated with code scanning analysis results. For more information about GitHub code scanning, check out the documentation.

Code scanning alert #2: Remove hardcoded credential in SecretStore initialization

Replace hardcoded 'HardCodedPassword' with randomly generated 32-character password.
This temporary password is only used for initial SecretStore configuration and
is immediately replaced with no-authentication mode.

The Microsoft.PowerShell.SecretStore module requires a password for initial
configuration even when switching to authentication-free mode, so a temporary
password is necessary but should not be hardcoded.

Resolves: https://github.com/cksapp/Datto-DBPool_Refresh/security/code-scanning/2
… password usage

Add SuppressMessageAttribute to ConvertTo-SecureString usage in Initialize-RefreshDBPool.ps1.
The randomly generated temporary password is safe as it:
- Is never stored or logged
- Is only used to satisfy SecretStore's initial setup requirement
- Is immediately discarded after switching to no authentication

Resolves PSScriptAnalyzer code scanning alert about plaintext secure strings.
… PSWrapper

Apply the same SuppressMessageAttribute pattern used in Datto-DBPool_PSWrapper's
Get-DBPoolUser.ps1 function, which includes:
- Justification parameter with clear explanation
- Inline comment after the attribute

The temporary password in Initialize-DattoSecretStoreVault is safe because:
- It's randomly generated (32 characters)
- It's never stored or logged
- It's only used to satisfy SecretStore's initial setup requirement
- It's immediately replaced with no authentication
@cksapp cksapp merged commit 387fb59 into main Jan 30, 2026
6 checks passed
@cksapp cksapp deleted the Verbose-ConfirmOutput branch January 30, 2026 07:50
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants