Functions/GenXdev.Windows.WireGuard/EnsureWireGuard.ps1

###############################################################################
<#
.SYNOPSIS
Ensures WireGuard VPN service is installed and running via Docker container.
 
.DESCRIPTION
This function sets up and manages the WireGuard VPN service using Docker
Desktop. It automatically ensures Docker Desktop is running, pulls the latest
WireGuard Docker image, creates persistent storage volumes, and manages the
container lifecycle including health monitoring and restart capabilities.
 
WireGuard is a simple, fast, and modern VPN that utilizes state-of-the-art
cryptography. It offers superior performance and simplicity compared to
traditional VPN solutions like OpenVPN, with minimal configuration overhead
and excellent cross-platform support.
 
.PARAMETER ContainerName
The name for the Docker container. Default: "wireguard"
 
.PARAMETER VolumeName
The name for the Docker volume for persistent storage of configuration files
and client certificates. Default: "wireguard_data"
 
.PARAMETER ServicePort
The UDP port number for the WireGuard service to listen on. Must be between
1-65535. Default: 51839
 
.PARAMETER HealthCheckTimeout
Maximum time in seconds to wait for service health check before timing out.
Must be between 10-300 seconds. Default: 60
 
.PARAMETER HealthCheckInterval
Interval in seconds between health check attempts during startup validation.
Must be between 1-10 seconds. Default: 3
 
.PARAMETER ImageName
Custom Docker image name to use instead of the default. If not specified,
uses the official "linuxserver/wireguard" image from Docker Hub.
 
.PARAMETER PUID
User ID for file permissions inside the container. Should match your host
system user ID for proper file access. Default: "1000"
 
.PARAMETER PGID
Group ID for file permissions inside the container. Should match your host
system group ID for proper file access. Default: "1000"
 
.PARAMETER TimeZone
Timezone identifier to use for container logging and timestamps. Uses standard
timezone database format. Default: "Etc/UTC"
 
.PARAMETER Force
Forces complete rebuilding of Docker container and removes all existing data.
This will stop and remove existing containers and volumes, pull the latest
WireGuard image, and create a fresh container with clean configuration.
 
.EXAMPLE
EnsureWireGuard
 
.EXAMPLE
EnsureWireGuard -ContainerName "my_wireguard" -ServicePort 51821
 
.EXAMPLE
EnsureWireGuard -VolumeName "custom_vpn_data" -HealthCheckTimeout 120
 
.EXAMPLE
EnsureWireGuard -PUID 1001 -PGID 1001 -TimeZone "America/New_York"
 
.EXAMPLE
EnsureWireGuard -Force
 
.NOTES
To generate client configurations after setup:
- Run: docker exec -it wireguard /app/show-peer 1
 
For Android 10 and above:
- Install the official WireGuard app from Google Play Store
- Scan the QR code or import the config file to connect
 
For more information, see: https://www.wireguard.com/
###############################################################################>

function EnsureWireGuard {

    [CmdletBinding()]
    [OutputType([System.Boolean])]

    param(
        #######################################################################
        [Parameter(
            Position = 0,
            Mandatory = $false,
            HelpMessage = 'The name for the Docker container'
        )]
        [ValidateNotNullOrEmpty()]
        [string] $ContainerName = 'wireguard',
        #######################################################################
        [Parameter(
            Position = 1,
            Mandatory = $false,
            HelpMessage = ('The name for the Docker volume for persistent ' +
                'storage')
        )]
        [ValidateNotNullOrEmpty()]
        [string] $VolumeName = 'wireguard_data',
        #######################################################################
        [Parameter(
            Position = 2,
            Mandatory = $false,
            HelpMessage = 'The port number for the WireGuard service'
        )]
        [ValidateRange(1, 65535)]
        [int] $ServicePort = 51839,
        #######################################################################
        [Parameter(
            Position = 3,
            Mandatory = $false,
            HelpMessage = ('Maximum time in seconds to wait for service ' +
                'health check')
        )]
        [ValidateRange(10, 300)]
        [int] $HealthCheckTimeout = 60,
        #######################################################################
        [Parameter(
            Position = 4,
            Mandatory = $false,
            HelpMessage = 'Interval in seconds between health check attempts'
        )]
        [ValidateRange(1, 10)]
        [int] $HealthCheckInterval = 3,
        #######################################################################
        [Parameter(
            Position = 5,
            Mandatory = $false,
            HelpMessage = 'Custom Docker image name to use'
        )]
        [ValidateNotNullOrEmpty()]
        [string] $ImageName = 'linuxserver/wireguard',
        #######################################################################
        [Parameter(
            Position = 6,
            Mandatory = $false,
            HelpMessage = 'User ID for permissions in the container'
        )]
        [ValidateNotNullOrEmpty()]
        [string] $PUID = '1000',
        #######################################################################
        [Parameter(
            Position = 7,
            Mandatory = $false,
            HelpMessage = 'Group ID for permissions in the container'
        )]
        [ValidateNotNullOrEmpty()]
        [string] $PGID = '1000',
        #######################################################################
        [Parameter(
            Position = 8,
            Mandatory = $false,
            HelpMessage = 'Timezone to use for the container'
        )]
        [ValidateNotNullOrEmpty()]
        [string] $TimeZone = 'Etc/UTC',
        #######################################################################
        [Parameter(
            Mandatory = $false,
            HelpMessage = 'Show Docker Desktop window during initialization'
        )]
        [switch] $ShowWindow,
        #######################################################################
        [Parameter(
            Mandatory = $false,
            HelpMessage = 'Removes the borders of the window'
        )]
        [Alias('nb')]
        [switch] $NoBorders,
        #######################################################################
        [Parameter(
            Mandatory = $false,
            HelpMessage = 'The initial width of the window'
        )]
        [int] $Width,
        #######################################################################
        [Parameter(
            Mandatory = $false,
            HelpMessage = 'The initial height of the window'
        )]
        [int] $Height,
        #######################################################################
        [Parameter(
            Mandatory = $false,
            HelpMessage = 'Place window on the left side of the screen'
        )]
        [switch] $Left,
        #######################################################################
        [Parameter(
            Mandatory = $false,
            HelpMessage = 'Place window on the right side of the screen'
        )]
        [switch] $Right,
        #######################################################################
        [Parameter(
            Mandatory = $false,
            HelpMessage = 'Place window on the bottom side of the screen'
        )]
        [switch] $Bottom,
        #######################################################################
        [Parameter(
            Mandatory = $false,
            HelpMessage = 'Place window in the center of the screen'
        )]
        [switch] $Centered,
        #######################################################################
        [Parameter(
            Mandatory = $false,
            HelpMessage = 'Maximize the window'
        )]
        [Alias('fs')]
        [switch] $Fullscreen,
        #######################################################################
        [Parameter(
            Mandatory = $false,
            HelpMessage = 'Restore PowerShell window focus'
        )]
        [Alias('rf', 'bg')]
        [switch] $RestoreFocus,
        #######################################################################
        [Parameter(
            Mandatory = $false,
            HelpMessage = ('Will either set the window fullscreen on a ' +
                'different monitor than Powershell, or side by side with ' +
                'Powershell on the same monitor')
        )]
        [Alias('sbs')]
        [switch] $SideBySide,
        #######################################################################
        [Parameter(
            Mandatory = $false,
            HelpMessage = 'Focus the window after opening'
        )]
        [Alias('fw','focus')]
        [switch] $FocusWindow,
        #######################################################################
        [Parameter(
            Mandatory = $false,
            HelpMessage = 'Set the window to foreground after opening'
        )]
        [Alias('fg')]
        [switch] $SetForeground,
        #######################################################################
        [Parameter(
            Mandatory = $false,
            HelpMessage = 'Escape control characters and modifiers when sending keys'
        )]
        [Alias('Escape')]
        [switch] $SendKeyEscape,
        #######################################################################
        [Parameter(
            Mandatory = $false,
            HelpMessage = 'Hold keyboard focus on target window when sending keys'
        )]
        [Alias('HoldKeyboardFocus')]
        [switch] $SendKeyHoldKeyboardFocus,
        #######################################################################
        [Parameter(
            Mandatory = $false,
            HelpMessage = 'Use Shift+Enter instead of Enter when sending keys'
        )]
        [Alias('UseShiftEnter')]
        [switch] $SendKeyUseShiftEnter,
        #######################################################################
        [Parameter(
            Mandatory = $false,
            HelpMessage = ('Delay between different input strings in ' +
                'milliseconds when sending keys')
        )]
        [Alias('DelayMilliSeconds')]
        [int] $SendKeyDelayMilliSeconds,
        #######################################################################
        [Parameter(
            Mandatory = $false,
            HelpMessage = ('Use alternative settings stored in session for AI ' +
                'preferences')
        )]
        [switch] $SessionOnly,
        #######################################################################
        [Parameter(
            Mandatory = $false,
            HelpMessage = ('Clear alternative settings stored in session for ' +
                'AI preferences')
        )]
        [switch] $ClearSession,
        #######################################################################
        [Parameter(
            Mandatory = $false,
            HelpMessage = ('Store settings only in persistent preferences ' +
                'without affecting session')
        )]
        [Alias('FromPreferences')]
        [switch] $SkipSession,
        #######################################################################
        [Parameter(
            Mandatory = $false,
            HelpMessage = ('Force rebuild of Docker container and remove ' +
                'existing data')
        )]
        [Alias('ForceRebuild')]
        [switch] $Force
        #######################################################################
    )    begin {

        # set script-scoped variables from parameters for container management
    $script:wireguardContainerName = $ContainerName

    # set docker image name for pulling and container creation
    $script:wireguardImageName = $ImageName

    # set script-scoped variables for docker volume and networking
    $script:wireguardVolumeName = $VolumeName

    # set script-scoped variables for service configuration
    $script:wireguardServicePort = $ServicePort

    # set script-scoped variables for health monitoring timeouts
    $script:wireguardHealthCheckTimeout = $HealthCheckTimeout

    # set script-scoped variable for health check retry intervals
    $script:wireguardHealthCheckInterval = $HealthCheckInterval

    # set script-scoped variables for container environment settings
    $script:wireguardPuid = $PUID

    $script:wireguardPgid = $PGID

    $script:wireguardTimezone = $TimeZone

    # store original location for cleanup at the end of the function
    $script:wireguardOriginalLocation = \
    (Microsoft.PowerShell.Management\Get-Location).Path

        #######################################################################
        <#
        .SYNOPSIS
        Tests if Docker is available and responsive to commands.
 
        .DESCRIPTION
        Verifies Docker Desktop is running by attempting to query the Docker
        version. Returns true if Docker responds successfully, false otherwise.
        #>

        function Test-DockerAvailability {

            try {

                # attempt to get docker version to verify docker is running
                $null = docker version --format '{{.Server.Version}}' 2>$null

                return $LASTEXITCODE -eq 0
            }
            catch {

                return $false
            }
        }
        #######################################################################
        <#
        .SYNOPSIS
        Tests if a Docker image exists locally.
 
        .DESCRIPTION
        Queries Docker for the existence of a specific image by name. Returns
        true if the image is found locally, false if not found or on error.
 
        .PARAMETER ImageName
        The Docker image name to search for in the local image repository.
        #>

        function Test-DockerImage {

            param([string]$ImageName)

            try {

                # query docker for existing images matching the specified name
                $images = docker images $ImageName --format '{{.Repository}}' `
                    2>$null

                return -not [string]::IsNullOrWhiteSpace($images)
            }
            catch {

                return $false
            }
        }
        #######################################################################
        <#
        .SYNOPSIS
        Tests if a Docker container exists (running or stopped).
 
        .DESCRIPTION
        Searches for a Docker container by exact name match, including both
        running and stopped containers. Returns true if found, false otherwise.
 
        .PARAMETER ContainerName
        The exact container name to search for in Docker container list.
        #>

        function Test-DockerContainer {

            param([string]$ContainerName)

            try {

                # search for containers with exact name match including stopped ones
                $containers = docker ps -a --filter "name=^${ContainerName}$" `
                    --format '{{.ID}}' 2>$null

                return -not [string]::IsNullOrWhiteSpace($containers)
            }
            catch {

                return $false
            }
        }
        #######################################################################
        <#
        .SYNOPSIS
        Tests if a Docker container is currently running.
 
        .DESCRIPTION
        Checks specifically for running containers with the given name. Returns
        true if the container exists and is actively running, false otherwise.
 
        .PARAMETER ContainerName
        The exact container name to check for running status.
        #>

        function Test-DockerContainerRunning {

            param([string]$ContainerName)

            try {

                # search for running containers with exact name match
                $containers = docker ps --filter "name=^${ContainerName}$" `
                    --format '{{.ID}}' 2>$null

                return -not [string]::IsNullOrWhiteSpace($containers)
            }
            catch {

                return $false
            }
        }
        #######################################################################
        <#
        .SYNOPSIS
        Safely removes a Docker container with proper error handling.
 
        .DESCRIPTION
        Stops and removes a Docker container if it exists. Uses ShouldProcess
        for confirmation and provides verbose logging. Handles errors gracefully
        without throwing exceptions.
 
        .PARAMETER ContainerName
        The name of the container to stop and remove from Docker.
        #>

        function Remove-DockerContainer {

            [CmdletBinding(SupportsShouldProcess)]
            param([string]$ContainerName)

            try {

                # check if container exists before attempting removal
                if (Test-DockerContainer $ContainerName) {

                    if ($PSCmdlet.ShouldProcess($ContainerName,
                            'Stop and remove Docker container')) {

                        # output verbose information about container removal
                        Microsoft.PowerShell.Utility\Write-Verbose `
                            "Stopping and removing container: $ContainerName"

                        # stop the container gracefully before removal
                        $null = docker stop $ContainerName 2>$null

                        # remove the container completely from docker
                        $null = docker rm $ContainerName 2>$null
                    }
                }
            }
            catch {

                # warn about container removal failures without throwing
                Microsoft.PowerShell.Utility\Write-Warning `
                    "Failed to remove container ${ContainerName}: $_"
            }
        }
        #######################################################################
        <#
        .SYNOPSIS
        Safely removes a Docker volume with proper error handling.
 
        .DESCRIPTION
        Removes a Docker volume if it exists. Uses ShouldProcess for
        confirmation and provides verbose logging. Handles errors gracefully
        without throwing exceptions.
 
        .PARAMETER VolumeName
        The name of the Docker volume to remove from the system.
        #>

        function Remove-DockerVolume {

            [CmdletBinding(SupportsShouldProcess)]

            param([string]$VolumeName)

            try {

                if ($PSCmdlet.ShouldProcess($VolumeName,
                        'Remove Docker volume')) {

                    # output verbose information about volume removal
                    Microsoft.PowerShell.Utility\Write-Verbose `
                        "Removing Docker volume: $VolumeName"

                    # remove the docker volume and discard output
                    $null = docker volume rm $VolumeName 2>$null
                }
            }
            catch {

                # warn about volume removal failures without throwing
                Microsoft.PowerShell.Utility\Write-Warning `
                    "Failed to remove volume ${VolumeName}: $_"
            }
        }
        #######################################################################
        <#
        .SYNOPSIS
        Tests if the WireGuard service is healthy and responding.
 
        .DESCRIPTION
        Performs health checks on the WireGuard container by examining container
        logs for startup indicators and checking if the service port is
        listening. Returns true if healthy, false otherwise.
        #>

        function Test-ServiceHealth {
            [CmdletBinding()]
            [OutputType([System.Boolean])]
            param()
            try {

                # first check if container is running before health tests
                $containerRunning = Test-DockerContainerRunning `
                    $script:wireguardContainerName

                if (-not $containerRunning) {

                    return $false
                }

                # check container logs for successful startup messages
                $logs = docker logs $script:wireguardContainerName 2>&1

                # look for indications that wireguard is running properly
                # linuxserver/wireguard outputs these specific messages when ready
                if ($logs -match 'All tunnels are now active' -or
                    $logs -match '\[ls\.io-init\] done\.' -or
                    $logs -match 'ip link add wg0 type wireguard') {

                    # log successful health check for debugging
                    Microsoft.PowerShell.Utility\Write-Verbose `
                        'WireGuard service health check passed'

                    return $true
                }

                # alternatively check if the port is listening using netstat
                $netstatOutput = & netstat -an |
                    Microsoft.PowerShell.Utility\Select-String `
                        -Pattern ":$($script:wireguardServicePort) "

                if (-not [string]::IsNullOrEmpty($netstatOutput)) {

                    # log successful health check for debugging
                    Microsoft.PowerShell.Utility\Write-Verbose `
                    ('WireGuard service health check passed ' +
                        '(port is listening)')

                    return $true
                }

                return $false
            }
            catch {

                # log failed health check with error details for debugging
                Microsoft.PowerShell.Utility\Write-Verbose `
                    "WireGuard service health check failed: $_"

                return $false
            }
        }
        #######################################################################
        <#
        .SYNOPSIS
        Waits for the WireGuard service to become healthy and ready.
 
        .DESCRIPTION
        Repeatedly checks service health until it becomes ready or timeout is
        reached. Uses configurable timeout and interval settings for retry
        logic. Returns true if service becomes ready, false on timeout.
        #>

        function Wait-ServiceReady {
            [CmdletBinding()]
            [OutputType([System.Boolean])]
            param()
            # output verbose information about waiting for service readiness
            Microsoft.PowerShell.Utility\Write-Verbose `
                'Waiting for WireGuard service to become ready...'

            # initialize retry counter for health check attempts
            $retryCount = 0

            # calculate maximum retry attempts based on timeout and interval
        $maxRetries = [math]::Floor($script:wireguardHealthCheckTimeout / `
            $script:wireguardHealthCheckInterval)

            while ($retryCount -lt $maxRetries) {

                # test service health and return success if ready
                if (Test-ServiceHealth) {

                    # log successful service readiness
                    Microsoft.PowerShell.Utility\Write-Verbose `
                        'WireGuard service is ready and responding'

                    return $true
                }

                # increment retry counter for next attempt
                $retryCount++

                # output verbose information about retry attempt progress
                Microsoft.PowerShell.Utility\Write-Verbose `
                    "Service not ready yet, attempt $retryCount/$maxRetries..."

                # wait between health check attempts as configured
                Microsoft.PowerShell.Utility\Start-Sleep `
                    -Seconds $script:wireguardHealthCheckInterval
            }

            # warn about service readiness timeout after all retries
            Microsoft.PowerShell.Utility\Write-Warning `
            ('WireGuard service did not become ready within ' +
                "$script:wireguardHealthCheckTimeout seconds")

            return $false
        }
        #######################################################################
        <#
        .SYNOPSIS
        Gets a list of existing WireGuard peer from the container.
 
        .DESCRIPTION
        Queries the WireGuard container for existing peer configurations by
        examining the /config directory structure. Returns an array of peer
        names found in the container.
        #>

        function Get-ExistingPeer {
            try {
                # list peer directories in the container's config folder
                $peerDirs = docker exec $script:wireguardContainerName sh -c "ls -d /config/peer_* 2>/dev/null | grep -o 'peer_[^/]*' | sed 's/peer_//'" 2>$null
                if ($LASTEXITCODE -eq 0 -and -not [string]::IsNullOrWhiteSpace($peerDirs)) {
                    # split the output into individual peer names
                    $peers = $peerDirs -split "`n" |
                        Microsoft.PowerShell.Core\Where-Object { -not [string]::IsNullOrWhiteSpace($_) } |
                        Microsoft.PowerShell.Core\ForEach-Object { $_.Trim() }
                    Microsoft.PowerShell.Utility\Write-Verbose `
                        "Found existing peer: $($peers -join ', ')"
                    return $peers
                }
                Microsoft.PowerShell.Utility\Write-Verbose `
                    'No existing peer found'
                return @()
            }
            catch {
                Microsoft.PowerShell.Utility\Write-Warning `
                    "Failed to get existing peer: $_"
                return @()
            }
        }
        #######################################################################
        <#
        .SYNOPSIS
        Checks the status of a WireGuard peer and activates it if inactive.
 
        .DESCRIPTION
        Uses the container's /app/show-peer script to check peer status and
        attempts to activate inactive peers by restarting the WireGuard service
        or reloading the configuration files.
 
        .PARAMETER PeerName
        The name of the peer to check and potentially activate.
        #>

        function Enable-PeerIfNeeded {
            [CmdletBinding()]
            [OutputType([System.Boolean])]

            param([string]$PeerName)

            try {

                Microsoft.PowerShell.Utility\Write-Verbose `
                    "Checking status of peer: $PeerName"

                # check peer status using container's show-peer script
                $peerStatus = docker exec $script:wireguardContainerName /app/show-peer $PeerName 2>&1

                # check if peer is marked as inactive
                if ($peerStatus -match 'is not active') {

                    Microsoft.PowerShell.Utility\Write-Verbose `
                        "Peer '$PeerName' is inactive, attempting to activate..."

                    # try multiple approaches to activate the peer

                    # approach 1: restart the entire container to reload all configs
                    Microsoft.PowerShell.Utility\Write-Verbose `
                        "Restarting WireGuard container to reload configurations..."

                    docker restart $script:wireguardContainerName 2>&1

                    if ($LASTEXITCODE -eq 0) {

                        # wait for container to fully restart
                        Microsoft.PowerShell.Utility\Start-Sleep -Seconds 15

                        # wait for service to become ready after restart
                        $serviceReady = Wait-ServiceReady

                        if ($serviceReady) {

                            # check peer status again after container restart
                            $newStatus = docker exec $script:wireguardContainerName /app/show-peer $PeerName 2>&1

                            if ($newStatus -notmatch 'is not active') {

                                Microsoft.PowerShell.Utility\Write-Verbose `
                                    "✅ Peer '$PeerName' is now active after container restart"

                                return $true
                            }
                        }
                    }

                    Microsoft.PowerShell.Utility\Write-Warning `
                        ("Peer '$PeerName' appears to be configured but inactive. " +
                         "This may be normal for peers that haven't connected yet, " +
                         "or the peer configuration may need to be regenerated.")

                    return $false
                }
                else {

                    Microsoft.PowerShell.Utility\Write-Verbose `
                        "✅ Peer '$PeerName' status check completed"

                    return $true
                }
            }
            catch {

                Microsoft.PowerShell.Utility\Write-Warning `
                    "Failed to check/activate peer '${PeerName}': $_"

                return $false
            }
        }
        #######################################################################
        <#
        .SYNOPSIS
        Resets the WireGuard server configuration to include all existing peer.
 
        .DESCRIPTION
        This function resets the main WireGuard server configuration file
        (/config/wg_confs/wg0.conf) to include all peer configurations found
        in the persistent storage, then restarts the WireGuard interface.
        #>

        function Reset-WireGuardConfiguration {
            [CmdletBinding(SupportsShouldProcess)]
            [OutputType([System.Boolean])]
            param()
            try {
                Microsoft.PowerShell.Utility\Write-Verbose `
                    "Resetting WireGuard configuration to include all existing peer..."
                # get list of existing peer
                $existingPeer = Get-ExistingPeer
                if ($existingPeer.Count -eq 0) {
                    Microsoft.PowerShell.Utility\Write-Verbose `
                        'No peer to add to configuration'
                    return $true
                }
                Microsoft.PowerShell.Utility\Write-Verbose `
                    "Found $($existingPeer.Count) peer(s) to include in configuration"
                # read the current server configuration
                $serverConfig = docker exec $script:wireguardContainerName cat /config/wg_confs/wg0.conf 2>$null
                if ($LASTEXITCODE -ne 0) {
                    Microsoft.PowerShell.Utility\Write-Warning "Could not read server configuration"
                    return $false
                }
                # split into server section and peer sections
                $configLines = $serverConfig -split "`n"
                $serverSection = @()
                foreach ($line in $configLines) {
                    if ($line.Trim() -match '^\[Peer\]') {
                        break
                    }
                    $serverSection += $line
                }
                # build new configuration with all existing peer
                $newConfig = $serverSection -join "`n"
                foreach ($peerName in $existingPeer) {
                    # read peer configuration
                    $peerConfigPath = "/config/peer_$peerName/peer_$peerName.conf"
                    $peerConfig = docker exec $script:wireguardContainerName cat $peerConfigPath 2>$null
                    if ($LASTEXITCODE -eq 0 -and -not [string]::IsNullOrWhiteSpace($peerConfig)) {
                        # extract peer public key and allowed IPs for server config
                        $publicKey = $null
                        $allowedIPs = $null
                        foreach ($line in $peerConfig -split "`n") {
                            if ($line -match '^PublicKey\s*=\s*(.+)$') {
                                $publicKey = $matches[1].Trim()
                            }
                            elseif ($line -match '^Address\s*=\s*(.+)$') {
                                $allowedIPs = $matches[1].Trim()
                            }
                        }
                        if ($publicKey -and $allowedIPs) {
                            Microsoft.PowerShell.Utility\Write-Verbose `
                                "Adding peer '$peerName' to server configuration"
                            $newConfig += "`n`n[Peer]"
                            $newConfig += "`nPublicKey = $publicKey"
                            $newConfig += "`nAllowedIPs = $allowedIPs"
                        }
                    }
                }
                # write the new configuration
                $tempConfigPath = "/tmp/wg0_rebuilt.conf"
                docker exec $script:wireguardContainerName sh -c "cat > $tempConfigPath << 'EOL'`n$newConfig`nEOL"
                if ($LASTEXITCODE -eq 0) {
                    # backup current config and replace with new one
                    docker exec $script:wireguardContainerName cp /config/wg_confs/wg0.conf /config/wg_confs/wg0.conf.backup 2>$null
                    docker exec $script:wireguardContainerName cp $tempConfigPath /config/wg_confs/wg0.conf
                    if ($LASTEXITCODE -eq 0) {
                        Microsoft.PowerShell.Utility\Write-Verbose `
                            "Successfully reset WireGuard configuration"
                        # restart WireGuard interface
                        docker exec $script:wireguardContainerName wg-quick down wg0 2>$null
                        docker exec $script:wireguardContainerName wg-quick up wg0 2>$null
                        if ($LASTEXITCODE -eq 0) {
                            Microsoft.PowerShell.Utility\Write-Verbose `
                                "✅ WireGuard interface restarted with new configuration"
                            return $true
                        }
                    }
                }
                Microsoft.PowerShell.Utility\Write-Warning `
                    "Failed to reset WireGuard configuration"
                return $false
            }
            catch {
                Microsoft.PowerShell.Utility\Write-Warning `
                    "Error resetting WireGuard configuration: $_"
                return $false
            }
        }
        #######################################################################
        <#
        .SYNOPSIS
        Ensures all existing peer are active and operational.
 
        .DESCRIPTION
        Scans for existing peer configurations and ensures they are all
        properly loaded and active in the WireGuard interface. If inactive
        peer are found, resets the WireGuard configuration to include them.
        #>

        function Confirm-AllPeerActive {
             [CmdletBinding()]
            [OutputType([System.Boolean])]
            param()
            try {
                # get list of existing peer from container
                $existingPeer = Get-ExistingPeer
                if ($existingPeer.Count -eq 0) {
                    Microsoft.PowerShell.Utility\Write-Verbose `
                        'No existing peer to validate'
                    return $true
                }
                Microsoft.PowerShell.Utility\Write-Verbose `
                    "Validating $($existingPeer.Count) existing peer(s)..."
                $inactivePeerFound = $false
                # check each peer's status
                foreach ($peer in $existingPeer) {
                    $peerActive = Enable-PeerIfNeeded -PeerName $peer
                    if (-not $peerActive) {
                        $inactivePeerFound = $true
                    }
                }
                # if inactive peer were found, reset the configuration
                if ($inactivePeerFound) {
                    Microsoft.PowerShell.Utility\Write-Verbose `
                        'Inactive peer detected - resetting WireGuard configuration...'
                    $resetSuccessful = GenXdev.Windows\Reset-WireGuardConfiguration
                    if ($resetSuccessful) {
                        Microsoft.PowerShell.Utility\Write-Verbose `
                            '✅ WireGuard configuration reset successfully'
                        # wait for interface to stabilize
                        Microsoft.PowerShell.Utility\Start-Sleep -Seconds 5
                        # verify peer are now active
                        foreach ($peer in $existingPeer) {
                            $peerStatus = docker exec $script:wireguardContainerName /app/show-peer $peer 2>&1
                            if ($peerStatus -match 'is not active') {
                                Microsoft.PowerShell.Utility\Write-Host -ForegroundColor Yellow `
                                    "ℹ️ Peer '$peer' still shows as inactive (normal for clients that haven't connected)"
                            }
                            else {
                                Microsoft.PowerShell.Utility\Write-Verbose `
                                    "✅ Peer '$peer' is now properly configured"
                            }
                        }
                        Microsoft.PowerShell.Utility\Write-Host -ForegroundColor Green `
                            '✅ All existing peer configurations have been restored to WireGuard interface'
                    }
                    else {
                        Microsoft.PowerShell.Utility\Write-Warning `
                            'Failed to reset WireGuard configuration - some peer may remain inactive'
                    }
                }
                else {
                    Microsoft.PowerShell.Utility\Write-Verbose `
                        '✅ All existing peer have been validated'
                }
                # always return true since the configuration has been reset
                return $true
            }
            catch {
                Microsoft.PowerShell.Utility\Write-Warning `
                    "Failed to validate peer: $_"
                return $false
            }
        }
        #######################################################################
        <#
        .SYNOPSIS
        Pulls the latest WireGuard Docker image from registry.
 
        .DESCRIPTION
        Downloads the specified WireGuard Docker image from Docker Hub or
        configured registry. Provides verbose logging and error handling.
        Returns true on success, false on failure.
        #>

        function Get-WireGuardImage {
            [CmdletBinding()]
            [OutputType([System.Boolean])]
            param()
            try {

                # output verbose information about docker image pull operation
                Microsoft.PowerShell.Utility\Write-Verbose `
                    "Pulling WireGuard image: $script:wireguardImageName"

                # pull the specified docker image from registry
                $pullResult = docker pull $script:wireguardImageName 2>&1

                # check if docker pull command failed
                if ($LASTEXITCODE -ne 0) {

                    throw "Failed to pull WireGuard image: $pullResult"
                }

                # log successful image pull completion
                Microsoft.PowerShell.Utility\Write-Verbose `
                    '✅ WireGuard image pulled successfully'

                return $true
            }
            catch {

                # log error details for image pull failure
                Microsoft.PowerShell.Utility\Write-Error `
                    "Failed to pull WireGuard image: $_"

                return $false
            }
        }
        #######################################################################
        <#
        .SYNOPSIS
        Creates and starts a new WireGuard Docker container.
 
        .DESCRIPTION
        Creates a new WireGuard container with proper configuration including
        networking capabilities, persistent volume mounting, environment
        variables, and restart policies. Uses ShouldProcess for confirmation.
        #>

        function New-WireGuardContainer {

            [CmdletBinding(SupportsShouldProcess)]
            [OutputType([System.Boolean])]
            param()

            try {

                # output verbose information about container creation process
                Microsoft.PowerShell.Utility\Write-Verbose `
                    'Creating WireGuard container...'

                # check if docker volume already exists in the system
                $volumeExists = docker volume ls `
                    --filter "name=^${script:wireguardVolumeName}$" `
                    --format '{{.Name}}' 2>$null

                # create docker volume if it doesn't exist yet
                if ([string]::IsNullOrWhiteSpace($volumeExists)) {

                    # use shouldprocess to confirm volume creation
            if ($PSCmdlet.ShouldProcess("$script:wireguardVolumeName",
                'Create Docker volume')) {

                        # output verbose information about volume creation
                        Microsoft.PowerShell.Utility\Write-Verbose `
                            "Creating Docker volume: $script:wireguardVolumeName"

                        # create the docker volume for persistent storage
                        $volumeResult = docker volume create $script:wireguardVolumeName `
                            2>&1

                        # check if volume creation failed
                        if ($LASTEXITCODE -ne 0) {

                            throw ('Failed to create Docker volume ' +
                                "$script:volumeName`: $volumeResult")
                        }
                    }
                }

                # prepare docker run arguments for container creation
                $dockerArgs = @(
                    'run', '-d'
                    '--name', $script:wireguardContainerName
                    '--cap-add', 'NET_ADMIN'
                    '--cap-add', 'SYS_MODULE'
                    '-e', "PUID=$($script:wireguardPuid)"
                    '-e', "PGID=$($script:wireguardPgid)"
                    '-e', "TZ=$($script:wireguardTimezone)"
                    '-e', "SERVERURL=auto"
                    '-e', "SERVERPORT=$($script:wireguardServicePort)"
                    '-e', "PEERS=1"
                    '-e', "PEERDNS=auto"
                    '-e', "INTERNAL_SUBNET=10.13.13.0"
                    '-e', "ALLOWEDIPS=0.0.0.0/0",
                    '-e', "UPNP=on",
                    '-p', "$($script:wireguardServicePort):51839/udp"
                    '-v', "$($script:wireguardVolumeName):/config"
                    '--restart', 'unless-stopped'
                )

                # add the docker image name as final argument
                $dockerArgs += $script:wireguardImageName

                # output verbose information about docker command execution
                Microsoft.PowerShell.Utility\Write-Verbose `
                    "Docker command: docker $($dockerArgs -join ' ')"

                # use shouldprocess to confirm container creation
        if ($PSCmdlet.ShouldProcess("$script:wireguardContainerName",
            'Create WireGuard container')) {

                    # execute docker run command to create container
                    $result = & docker @dockerArgs 2>&1

                    # check if container creation failed
                    if ($LASTEXITCODE -ne 0) {

                        throw "Failed to create container: $result"
                    }
                }

                # wait for container to initialize properly after creation
                Microsoft.PowerShell.Utility\Start-Sleep -Seconds 5

                # log successful container creation
                Microsoft.PowerShell.Utility\Write-Verbose `
                    '✅ WireGuard container created successfully'

                return $true
            }
            catch {

                # log error details for container creation failure
                Microsoft.PowerShell.Utility\Write-Error `
                    "Failed to create WireGuard container: $_"

                return $false
            }
        }
        #######################################################################
    }

    process {

        try {

            # ensure docker desktop is available and running properly
            Microsoft.PowerShell.Utility\Write-Verbose `
                'Ensuring Docker Desktop is available...'

            # Copy identical parameters between functions
            $params = GenXdev.Helpers\Copy-IdenticalParamValues `
                -FunctionName 'GenXdev.Windows\EnsureDockerDesktop' `
                -BoundParameters $PSBoundParameters `
                -DefaultValues (Microsoft.PowerShell.Utility\Get-Variable -Scope Local -ErrorAction SilentlyContinue)

            GenXdev.Windows\EnsureDockerDesktop @params

            # verify docker is responding to commands after ensuring it's running
            if (-not (Test-DockerAvailability)) {

                throw 'Docker is not available or not responding'
            }

            # handle force cleanup if requested by user for fresh installation
            if ($Force) {

                # output verbose information about forced cleanup process
                Microsoft.PowerShell.Utility\Write-Verbose `
                    'Force flag specified - cleaning up existing resources...'

                # remove existing container and volume for clean slate
                Remove-DockerContainer $script:wireguardContainerName

                Remove-DockerVolume $script:wireguardVolumeName
            }

            # ensure we have the latest wireguard image available locally
            if (-not (Test-DockerImage $script:wireguardImageName) -or $Force) {

                # pull the docker image if not present or force specified
                if (-not (Get-WireGuardImage)) {

                    throw 'Failed to obtain WireGuard Docker image'
                }
            }
            else {

                # log that image is already available locally
                Microsoft.PowerShell.Utility\Write-Verbose `
                    '✅ WireGuard image already available'
            }

            # check current container state for appropriate action
            $containerExists = Test-DockerContainer $script:wireguardContainerName

            $containerRunning = Test-DockerContainerRunning $script:wireguardContainerName

            # handle existing container scenarios based on current state
            if ($containerExists) {

                # check if container is currently running
                if ($containerRunning) {

                    # verify container health for running container
                    if (Test-ServiceHealth) {

                        # log successful health check result
                        Microsoft.PowerShell.Utility\Write-Verbose `
                            '✅ WireGuard container is healthy and responding'
                    }
                    else {

                        # restart unhealthy running container to fix issues
                        Microsoft.PowerShell.Utility\Write-Verbose `
                        ('Container is running but not responding - ' +
                            'restarting...')

                        # restart the container to fix health issues
                        $null = docker restart $script:wireguardContainerName 2>$null

                        # wait for container to restart properly
                        Microsoft.PowerShell.Utility\Start-Sleep -Seconds 10

                        # wait for service to become ready after restart
                        $serviceReady = Wait-ServiceReady

                        # warn if service is not ready after restart attempt
                        if (-not $serviceReady) {

                            Microsoft.PowerShell.Utility\Write-Warning `
                            ('WireGuard service may not be fully ready ' +
                                'after restart')
                        }
                    }
                }
                else {

                    # start existing stopped container
                    Microsoft.PowerShell.Utility\Write-Verbose `
                        'Starting existing container...'

                    # start the stopped container
                    $null = docker start $script:wireguardContainerName 2>$null

                    # wait for container to start properly
                    Microsoft.PowerShell.Utility\Start-Sleep -Seconds 10

                    # wait for service to become ready after start
                    $serviceReady = Wait-ServiceReady

                    # log or warn about service readiness after start
                    if ($serviceReady) {

                        Microsoft.PowerShell.Utility\Write-Verbose `
                            '✅ WireGuard service is ready after container start'
                    }
                    else {

                        Microsoft.PowerShell.Utility\Write-Warning `
                        ('WireGuard service may not be fully ready ' +
                            'after start')
                    }
                }
            }
            else {

                # create and start new container when none exists
                Microsoft.PowerShell.Utility\Write-Verbose `
                    'No existing container found - creating fresh installation...'

                # clean up any existing volume for a fresh start
                Microsoft.PowerShell.Utility\Write-Verbose `
                    'Removing any existing volumes for clean installation...'

                Remove-DockerVolume $script:wireguardVolumeName

                # attempt to create new wireguard container
                if (-not (New-WireGuardContainer)) {

                    throw 'Failed to create WireGuard container'
                }

                # wait for service to be ready after creation
                Microsoft.PowerShell.Utility\Write-Verbose `
                    'Waiting for WireGuard service to be ready...'

                $serviceReady = Wait-ServiceReady

                # log or warn about service readiness after creation
                if ($serviceReady) {

                    Microsoft.PowerShell.Utility\Write-Verbose `
                        '✅ WireGuard service is ready'
                }
                else {

                    Microsoft.PowerShell.Utility\Write-Warning `
                    ('WireGuard service may not be fully ready ' +
                        'after creation')
                }
            }

            # perform final validation of service state before returning
            Microsoft.PowerShell.Utility\Write-Verbose `
                'Performing final validation...'

            # check if container is running and service is healthy
            if ((Test-DockerContainerRunning $script:wireguardContainerName) -and `
                (Test-ServiceHealth)) {

                # only validate peers if container already existed (not fresh install)
                if ($containerExists) {
                    Microsoft.PowerShell.Utility\Write-Verbose `
                        'Validating existing peer configurations...'
                    Confirm-AllPeerActive
                }

                # log successful service operation
                Microsoft.PowerShell.Utility\Write-Verbose `
                ('✅ WireGuard VPN service is fully ' +
                    "operational on port $script:wireguardServicePort")

                # display instructions for client configuration to user
                Microsoft.PowerShell.Utility\Write-Host -ForegroundColor Green @"
 
To generate client configurations:
- Run: docker exec -it $script:wireguardContainerName /app/show-peer 1
This will display a QR code or config file for the client.
 
For Android 10 and above:
1. Install the official WireGuard app from Google Play Store
2. Scan the QR code or import the config file to connect
"@


                return $true
            }
            else {

                # warn about potential service issues
                Microsoft.PowerShell.Utility\Write-Warning `
                    'WireGuard service may not be fully operational'

                return $false
            }
        }
        catch {

            # log error details for any failures in the process
            Microsoft.PowerShell.Utility\Write-Error `
                "Failed to ensure WireGuard service: $_"

            throw
        }
    }    end {

        # restore original location for cleanup after function execution
    Microsoft.PowerShell.Management\Set-Location $script:wireguardOriginalLocation
    }
}