Public/Core.ps1

<#
.SYNOPSIS
    Starts a Pode server with the supplied script block or file containing the server logic.
 
.DESCRIPTION
    This function initializes and starts a Pode server based on the provided configuration.
    It supports both inline script blocks and external files for defining server logic.
    The server's behavior, console output, and various features can be customized using parameters.
    Additionally, it manages server termination, cancellation, and cleanup processes.
 
.PARAMETER ScriptBlock
    The main logic for the server, provided as a script block.
 
.PARAMETER FilePath
    A literal or relative path to a file containing the server's logic.
    The directory of this file will be used as the server's root path unless a specific -RootPath is supplied.
 
.PARAMETER Interval
    Specifies the interval in seconds for invoking the script block in 'Service' type servers.
 
.PARAMETER Name
    An optional name for the server, useful for identification in logs and future extensions.
 
.PARAMETER Threads
    The number of threads to allocate for Web, SMTP, and TCP servers. Defaults to 1.
 
.PARAMETER RootPath
    Overrides the server's root path. If not provided, the root path will be derived from the file path or the current working directory.
 
.PARAMETER Request
    Provides request details for serverless environments that Pode can parse and use.
 
.PARAMETER ServerlessType
    Specifies the serverless type for Pode. Valid values are:
    - AzureFunctions
    - AwsLambda
 
.PARAMETER StatusPageExceptions
    Controls the visibility of stack traces on status pages. Valid values are:
    - Show
    - Hide
 
.PARAMETER ListenerType
    Specifies a custom socket listener. Defaults to Pode's inbuilt listener.
 
.PARAMETER EnablePool
    Configures specific runspace pools (e.g., Timers, Schedules, Tasks, WebSockets, Files) for ad-hoc usage.
 
.PARAMETER Browse
    Opens the default web endpoint in the browser upon server start.
 
.PARAMETER CurrentPath
    Sets the server's root path to the current working directory. Only applicable when -FilePath is used.
 
.PARAMETER EnableBreakpoints
    Enables breakpoints created using `Wait-PodeDebugger`.
 
.PARAMETER DisableTermination
    Prevents termination, suspension, or resumption of the server via console commands.
 
.PARAMETER DisableConsoleInput
    Disables all console interactions for the server.
 
.PARAMETER ClearHost
    Clears the console screen whenever the server state changes (e.g., running → suspend → resume).
 
.PARAMETER Quiet
    Suppresses all output from the server.
 
.PARAMETER HideOpenAPI
    Hides OpenAPI details such as specification and documentation URLs from the console output.
 
.PARAMETER HideEndpoints
    Hides the list of active endpoints from the console output.
 
.PARAMETER ShowHelp
    Displays a help menu in the console with available control commands.
 
.PARAMETER IgnoreServerConfig
    Prevents the server from loading settings from the server.psd1 configuration file.
 
.PARAMETER ConfigFile
    Specifies a custom configuration file instead of using the default `server.psd1`.
 
.PARAMETER Daemon
    Configures the server to run as a daemon with minimal console interaction and output.
 
.EXAMPLE
    Start-PodeServer { /* server logic */ }
    Starts a Pode server using the supplied script block.
 
.EXAMPLE
    Start-PodeServer -FilePath './server.ps1' -Browse
    Starts a Pode server using the logic defined in an external file and opens the default endpoint in the browser.
 
.EXAMPLE
    Start-PodeServer -ServerlessType AwsLambda -Request $LambdaInput { /* server logic */ }
    Starts a Pode server in a serverless environment, using AWS Lambda input.
 
.EXAMPLE
    Start-PodeServer -HideOpenAPI -ClearHost { /* server logic */ }
    Starts a Pode server with console output configured to hide OpenAPI details and clear the console on state changes.
 
.NOTES
    This function is part of the Pode framework and is responsible for server initialization, configuration,
    request handling, and cleanup. It supports both standalone and serverless deployments, and provides
    extensive customization options for developers.
#>

function Start-PodeServer {
    [CmdletBinding(DefaultParameterSetName = 'Script')]
    param(
        [Parameter(Mandatory = $true, ValueFromPipeline = $true, Position = 0, ParameterSetName = 'Script')]
        [Parameter(Mandatory = $true, ParameterSetName = 'ScriptDaemon')]
        [scriptblock]
        $ScriptBlock,

        [Parameter(Mandatory = $true, ParameterSetName = 'File')]
        [Parameter(Mandatory = $true, ParameterSetName = 'FileDaemon')]
        [string]
        $FilePath,

        [Parameter()]
        [int]
        $Interval = 0,

        [Parameter()]
        [string]
        $Name,

        [Parameter()]
        [int]
        $Threads = 1,

        [Parameter()]
        [string]
        $RootPath,

        [Parameter()]
        $Request,

        [Parameter()]
        [ValidateSet('', 'AzureFunctions', 'AwsLambda')]
        [string]
        $ServerlessType = [string]::Empty,

        [Parameter()]
        [ValidateSet('', 'Hide', 'Show')]
        [string]
        $StatusPageExceptions = [string]::Empty,

        [Parameter()]
        [string]
        $ListenerType = [string]::Empty,

        [Parameter()]
        [ValidateSet('Timers', 'Schedules', 'Tasks', 'WebSockets', 'Files')]
        [string[]]
        $EnablePool,

        [Parameter(ParameterSetName = 'File')]
        [Parameter(ParameterSetName = 'Script')]
        [switch]
        $Browse,

        [Parameter(Mandatory = $true, ParameterSetName = 'FileDaemon')]
        [Parameter(ParameterSetName = 'File')]
        [switch]
        $CurrentPath,

        [Parameter(ParameterSetName = 'File')]
        [Parameter(ParameterSetName = 'Script')]
        [switch]
        $EnableBreakpoints,

        [Parameter(ParameterSetName = 'File')]
        [Parameter(ParameterSetName = 'Script')]
        [switch]
        $DisableTermination,

        [Parameter(ParameterSetName = 'File')]
        [Parameter(ParameterSetName = 'Script')]
        [switch]
        $Quiet,

        [Parameter(ParameterSetName = 'File')]
        [Parameter(ParameterSetName = 'Script')]
        [switch]
        $DisableConsoleInput,

        [switch]
        $ClearHost,

        [switch]
        $HideOpenAPI,

        [switch]
        $HideEndpoints,

        [switch]
        $ShowHelp,

        [switch]
        $IgnoreServerConfig,

        [string]
        $ConfigFile,

        [Parameter(Mandatory = $true, ParameterSetName = 'FileDaemon')]
        [Parameter(Mandatory = $true, ParameterSetName = 'ScriptDaemon')]
        [switch]
        $Daemon
    )

    begin {
        $pipelineItemCount = 0
    }

    process {
        $pipelineItemCount++
    }

    end {
        if ($pipelineItemCount -gt 1) {
            throw ($PodeLocale.fnDoesNotAcceptArrayAsPipelineInputExceptionMessage -f $($MyInvocation.MyCommand.Name))
        }    # Store the name of the current runspace
        $previousRunspaceName = Get-PodeCurrentRunspaceName
        # Sets the name of the current runspace
        Set-PodeCurrentRunspaceName -Name 'PodeServer'

        # ensure the session is clean
        $Script:PodeContext = $null
        $ShowDoneMessage = $true

        try {
            # if we have a filepath, resolve it - and extract a root path from it
            if ($PSCmdlet.ParameterSetName -ieq 'file') {
                $FilePath = Get-PodeRelativePath -Path $FilePath -Resolve -TestPath -JoinRoot -RootPath $MyInvocation.PSScriptRoot

                # if not already supplied, set root path
                if ([string]::IsNullOrWhiteSpace($RootPath)) {
                    if ($CurrentPath) {
                        $RootPath = $PWD.Path
                    }
                    else {
                        $RootPath = Split-Path -Parent -Path $FilePath
                    }
                }
            }

            # configure the server's root path
            if (!(Test-PodeIsEmpty $RootPath)) {
                $RootPath = Get-PodeRelativePath -Path $RootPath -RootPath $MyInvocation.PSScriptRoot -JoinRoot -Resolve -TestPath
            }


            # Define parameters for the context creation
            $ContextParams = @{
                ScriptBlock          = $ScriptBlock
                FilePath             = $FilePath
                Threads              = $Threads
                Interval             = $Interval
                ServerRoot           = Protect-PodeValue -Value $RootPath -Default $MyInvocation.PSScriptRoot
                ServerlessType       = $ServerlessType
                ListenerType         = $ListenerType
                EnablePool           = $EnablePool
                StatusPageExceptions = $StatusPageExceptions
                Console              = Get-PodeDefaultConsole
                EnableBreakpoints    = $EnableBreakpoints
                IgnoreServerConfig   = $IgnoreServerConfig
                ConfigFile           = $ConfigFile
            }


            # Create main context object
            $PodeContext = New-PodeContext @ContextParams

            # Define parameter values with comments explaining each one
            $ConfigParameters = @{
                DisableTermination  = $DisableTermination   # Disable termination of the Pode server from the console
                DisableConsoleInput = $DisableConsoleInput  # Disable input from the console for the Pode server
                Quiet               = $Quiet                # Enable quiet mode, suppressing console output
                ClearHost           = $ClearHost            # Clear the host on startup
                HideOpenAPI         = $HideOpenAPI          # Hide the OpenAPI documentation display
                HideEndpoints       = $HideEndpoints        # Hide the endpoints list display
                ShowHelp            = $ShowHelp             # Show help information in the console
                Daemon              = $Daemon               # Enable daemon mode, combining multiple configurations
            }

            # Call the function using splatting
            Set-PodeConsoleOverrideConfiguration @ConfigParameters

            # start the file monitor for interally restarting
            Start-PodeFileMonitor

            # start the server
            Start-PodeInternalServer -Request $Request -Browse:$Browse

            # at this point, if it's just a one-one off script, return
            if (!(Test-PodeServerKeepOpen)) {
                return
            }

            # Sit in a loop waiting for server termination/cancellation or a restart request.
            while (!(Test-PodeCancellationTokenRequest -Type Terminate)) {

                # If console input is not disabled, invoke any actions based on console commands.
                if (!$PodeContext.Server.Console.DisableConsoleInput) {
                    Invoke-PodeConsoleAction
                }

                # Resolve cancellation token requests (e.g., Restart, Enable/Disable, Suspend/Resume).
                Resolve-PodeCancellationToken

                # Pause for 1 second before re-checking the state and processing the next action.
                Start-Sleep -Seconds 1
            }

            if ($PodeContext.Server.IsIIS -and $PodeContext.Server.IIS.Shutdown) {
                # (IIS Shutdown)
                Write-PodeHost $PodeLocale.iisShutdownMessage -NoNewLine -ForegroundColor Yellow
                Write-PodeHost ' ' -NoNewLine
            }

            # Terminating...
            Invoke-PodeEvent -Type Terminate
            Close-PodeServer
            Show-PodeConsoleInfo
        }
        catch {
            $_ | Write-PodeErrorLog

            Invoke-PodeEvent -Type Crash
            $ShowDoneMessage = $false
            throw
        }
        finally {
            Invoke-PodeEvent -Type Stop

            # set output values
            Set-PodeOutputVariable

            # unregister secret vaults
            Unregister-PodeSecretVaultsInternal

            # clean the runspaces and tokens
            Close-PodeServerInternal

            Show-PodeConsoleInfo

            # Restore the name of the current runspace
            Set-PodeCurrentRunspaceName -Name $previousRunspaceName

            if (($ShowDoneMessage -and ($PodeContext.Server.Types.Length -gt 0) -and !$PodeContext.Server.IsServerless)) {
                Write-PodeHost $PodeLocale.doneMessage -ForegroundColor Green
            }

            # clean the session
            $PodeContext = $null
            $PodeLocale = $null
        }
    }
}

<#
.SYNOPSIS
    Closes the Pode server.
 
.DESCRIPTION
    Closes the Pode server.
 
.EXAMPLE
    Close-PodeServer
#>

function Close-PodeServer {
    [CmdletBinding()]
    param()

    Close-PodeCancellationTokenRequest -Type Cancellation, Terminate
}

<#
.SYNOPSIS
Restarts the Pode server.
 
.DESCRIPTION
Restarts the Pode server.
 
.EXAMPLE
Restart-PodeServer
#>

function Restart-PodeServer {
    [CmdletBinding()]
    param()

    # Only if the Restart feature is anabled
    if ($PodeContext.Server.AllowedActions.Restart) {
        Close-PodeCancellationTokenRequest -Type Restart
    }
}


<#
.SYNOPSIS
    Resumes the Pode server from a suspended state.
 
.DESCRIPTION
    This function resumes the Pode server, ensuring all associated runspaces are restored to their normal execution state.
    It triggers the 'Resume' event, updates the server's suspended status, and clears the host for a refreshed console view.
 
.PARAMETER Timeout
    The maximum time, in seconds, to wait for each runspace to be recovered before timing out. Default is 30 seconds.
 
.EXAMPLE
    Resume-PodeServer
    # Resumes the Pode server after a suspension.
 
#>

function Resume-PodeServer {
    [CmdletBinding()]
    param(
        [int]
        $Timeout
    )
    # Only if the Suspend feature is anabled
    if ($PodeContext.Server.AllowedActions.Suspend) {
        if ($Timeout) {
            $PodeContext.Server.AllowedActions.Timeout.Resume = $Timeout
        }

        if ((Test-PodeServerState -State Suspended)) {
            Set-PodeResumeToken
        }
    }
}


<#
.SYNOPSIS
    Suspends the Pode server and its runspaces.
 
.DESCRIPTION
    This function suspends the Pode server by pausing all associated runspaces and ensuring they enter a debug state.
    It triggers the 'Suspend' event, updates the server's suspended status, and provides feedback during the suspension process.
 
.PARAMETER Timeout
    The maximum time, in seconds, to wait for each runspace to be suspended before timing out. Default is 30 seconds.
 
.EXAMPLE
    Suspend-PodeServer
    # Suspends the Pode server with a timeout of 60 seconds.
 
#>

function Suspend-PodeServer {
    [CmdletBinding()]
    param(
        [int]
        $Timeout
    )
    # Only if the Suspend feature is anabled
    if ($PodeContext.Server.AllowedActions.Suspend) {
        if ($Timeout) {
            $PodeContext.Server.AllowedActions.Timeout.Suspend = $Timeout
        }
        if (!(Test-PodeServerState -State Suspended)) {
            Set-PodeSuspendToken
        }
    }
}

<#
.SYNOPSIS
Helper wrapper function to start a Pode web server for a static website at the current directory.
 
.DESCRIPTION
Helper wrapper function to start a Pode web server for a static website at the current directory.
 
.PARAMETER Threads
The numbers of threads to use for requests.
 
.PARAMETER RootPath
An override for the Server's root path.
 
.PARAMETER Address
The IP/Hostname of the endpoint.
 
.PARAMETER Port
The Port number of the endpoint.
 
.PARAMETER Https
Start the server using HTTPS, if no certificate details are supplied a self-signed certificate will be generated.
 
.PARAMETER Certificate
The path to a certificate that can be use to enable HTTPS.
 
.PARAMETER CertificatePassword
The password for the certificate referenced in CertificateFile.
 
.PARAMETER CertificateKey
A key file to be paired with a PEM certificate referenced in CertificateFile
 
.PARAMETER X509Certificate
The raw X509 certificate that can be use to enable HTTPS.
 
.PARAMETER Path
The URI path for the static Route.
 
.PARAMETER Defaults
An array of default pages to display, such as 'index.html'.
 
.PARAMETER DownloadOnly
When supplied, all static content on this Route will be attached as downloads - rather than rendered.
 
.PARAMETER FileBrowser
When supplied, If the path is a folder, instead of returning 404, will return A browsable content of the directory.
 
.PARAMETER Browse
Open the web server's default endpoint in your default browser.
 
.EXAMPLE
Start-PodeStaticServer
 
.EXAMPLE
Start-PodeStaticServer -Address '127.0.0.3' -Port 8000
 
.EXAMPLE
Start-PodeStaticServer -Path '/installers' -DownloadOnly
#>

function Start-PodeStaticServer {
    [CmdletBinding()]
    param(
        [Parameter()]
        [int]
        $Threads = 3,

        [Parameter()]
        [string]
        $RootPath = $PWD,

        [Parameter()]
        [string]
        $Address = 'localhost',

        [Parameter()]
        [int]
        $Port = 0,

        [Parameter()]
        [switch]
        $Https,

        [Parameter()]
        [string]
        $Certificate = $null,

        [Parameter()]
        [string]
        $CertificatePassword = $null,

        [Parameter()]
        [string]
        $CertificateKey = $null,

        [Parameter()]
        [X509Certificate]
        $X509Certificate = $null,

        [Parameter()]
        [string]
        $Path = '/',

        [Parameter()]
        [string[]]
        $Defaults,

        [switch]
        $DownloadOnly,

        [switch]
        $FileBrowser,

        [switch]
        $Browse
    )

    Start-PodeServer -RootPath $RootPath -Threads $Threads -Browse:$Browse -ScriptBlock {
        # add either an http or https endpoint
        if ($Https) {
            if ($null -ne $X509Certificate) {
                Add-PodeEndpoint -Address $Address -Port $Port -Protocol Https -X509Certificate $X509Certificate
            }
            elseif (![string]::IsNullOrWhiteSpace($Certificate)) {
                Add-PodeEndpoint -Address $Address -Port $Port -Protocol Https -Certificate $Certificate -CertificatePassword $CertificatePassword -CertificateKey $CertificateKey
            }
            else {
                Add-PodeEndpoint -Address $Address -Port $Port -Protocol Https -SelfSigned
            }
        }
        else {
            Add-PodeEndpoint -Address $Address -Port $Port -Protocol Http
        }

        # add the static route
        Add-PodeStaticRoute -Path $Path -Source (Get-PodeServerPath) -Defaults $Defaults -DownloadOnly:$DownloadOnly -FileBrowser:$FileBrowser
    }
}

<#
.SYNOPSIS
A default server secret that can be for signing values like Session, Cookies, or SSE IDs.
 
.DESCRIPTION
A default server secret that can be for signing values like Session, Cookies, or SSE IDs. This secret is regenerated
on every server start and restart.
 
.EXAMPLE
$secret = Get-PodeServerDefaultSecret
#>

function Get-PodeServerDefaultSecret {
    [CmdletBinding()]
    param()

    return $PodeContext.Server.DefaultSecret
}

<#
.SYNOPSIS
The CLI for Pode, to initialise, build and start your Server.
 
.DESCRIPTION
The CLI for Pode, to initialise, build and start your Server.
 
.PARAMETER Action
The action to invoke on your Server.
 
.PARAMETER Dev
Supply when running "pode install", this will install any dev packages defined in your package.json.
 
.EXAMPLE
pode install -dev
 
.EXAMPLE
pode build
 
.EXAMPLE
pode start
#>

function Pode {
    [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidUsingWriteHost', '')]
    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $true)]
        [ValidateSet('init', 'test', 'start', 'install', 'build')]
        [Alias('a')]
        [string]
        $Action,

        [switch]
        [Alias('d')]
        $Dev
    )

    # default config file name and content
    $file = './package.json'
    $name = Split-Path -Leaf -Path $pwd
    $data = $null

    # default config data that's used to populate on init
    $map = @{
        'name'        = $name
        'version'     = '1.0.0'
        'description' = ''
        'main'        = './server.ps1'
        'scripts'     = @{
            'start'   = './server.ps1'
            'install' = 'yarn install --force --ignore-scripts --modules-folder pode_modules'
            'build'   = 'psake'
            'test'    = 'invoke-pester ./tests/*.ps1'
        }
        'author'      = ''
        'license'     = 'MIT'
    }

    # check and load config if already exists
    if (Test-Path $file) {
        $data = (Get-Content $file | ConvertFrom-Json)
    }

    # quick check to see if the data is required
    if ($Action -ine 'init') {
        if ($null -eq $data) {
            Write-PodeHost 'package.json file not found' -ForegroundColor Red
            return
        }
        else {
            $actionScript = $data.scripts.$Action

            if ([string]::IsNullOrWhiteSpace($actionScript) -and $Action -ieq 'start') {
                $actionScript = $data.main
            }

            if ([string]::IsNullOrWhiteSpace($actionScript) -and $Action -ine 'install') {
                Write-PodeHost "package.json does not contain a script for the $($Action) action" -ForegroundColor Yellow
                return
            }
        }
    }
    else {
        if ($null -ne $data) {
            Write-PodeHost 'package.json already exists' -ForegroundColor Yellow
            return
        }
    }

    switch ($Action.ToLowerInvariant()) {
        'init' {
            $v = Read-Host -Prompt "name ($($map.name))"
            if (![string]::IsNullOrWhiteSpace($v)) { $map.name = $v }

            $v = Read-Host -Prompt "version ($($map.version))"
            if (![string]::IsNullOrWhiteSpace($v)) { $map.version = $v }

            $map.description = Read-Host -Prompt 'description'

            $v = Read-Host -Prompt "entry point ($($map.main))"
            if (![string]::IsNullOrWhiteSpace($v)) { $map.main = $v; $map.scripts.start = $v }

            $map.author = Read-Host -Prompt 'author'

            $v = Read-Host -Prompt "license ($($map.license))"
            if (![string]::IsNullOrWhiteSpace($v)) { $map.license = $v }

            $map | ConvertTo-Json -Depth 10 | Out-File -FilePath $file -Encoding utf8 -Force
            Write-PodeHost 'Success, saved package.json' -ForegroundColor Green
        }

        'test' {
            Invoke-PodePackageScript -ActionScript $actionScript
        }

        'start' {
            Invoke-PodePackageScript -ActionScript $actionScript
        }

        'install' {
            if ($Dev) {
                Install-PodeLocalModule -Module $data.devModules
            }

            Install-PodeLocalModule -Module $data.modules
            Invoke-PodePackageScript -ActionScript $actionScript
        }

        'build' {
            Invoke-PodePackageScript -ActionScript $actionScript
        }
    }
}

<#
.SYNOPSIS
Opens a Web Server up as a Desktop Application.
 
.DESCRIPTION
Opens a Web Server up as a Desktop Application.
 
.PARAMETER Title
The title of the Application's window.
 
.PARAMETER Icon
A path to an icon image for the Application.
 
.PARAMETER WindowState
The state the Application's window starts, such as Minimized.
 
.PARAMETER WindowStyle
The border style of the Application's window.
 
.PARAMETER ResizeMode
Specifies if the Application's window is resizable.
 
.PARAMETER Height
The height of the window.
 
.PARAMETER Width
The width of the window.
 
.PARAMETER EndpointName
The specific endpoint name to use, if you are listening on multiple endpoints.
 
.PARAMETER HideFromTaskbar
Stops the Application from appearing on the taskbar.
 
.EXAMPLE
Show-PodeGui -Title 'MyApplication' -WindowState 'Maximized'
#>

function Show-PodeGui {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $true, ValueFromPipeline = $true)]
        [string]
        $Title,

        [Parameter()]
        [string]
        $Icon,

        [Parameter()]
        [ValidateSet('Normal', 'Maximized', 'Minimized')]
        [string]
        $WindowState = 'Normal',

        [Parameter()]
        [ValidateSet('None', 'SingleBorderWindow', 'ThreeDBorderWindow', 'ToolWindow')]
        [string]
        $WindowStyle = 'SingleBorderWindow',

        [Parameter()]
        [ValidateSet('CanResize', 'CanMinimize', 'NoResize')]
        [string]
        $ResizeMode = 'CanResize',

        [Parameter()]
        [int]
        $Height = 0,

        [Parameter()]
        [int]
        $Width = 0,

        [Parameter()]
        [string]
        $EndpointName,

        [switch]
        $HideFromTaskbar
    )
    begin {
        $pipelineItemCount = 0
    }

    process {

        $pipelineItemCount++
    }

    end {
        if ($pipelineItemCount -gt 1) {
            throw ($PodeLocale.fnDoesNotAcceptArrayAsPipelineInputExceptionMessage -f $($MyInvocation.MyCommand.Name))
        }
        # error if serverless
        Test-PodeIsServerless -FunctionName 'Show-PodeGui' -ThrowError

        # only valid for Windows PowerShell
        if ((Test-PodeIsPSCore) -and ($PSVersionTable.PSVersion.Major -eq 6)) {
            # Show-PodeGui is currently only available for Windows PowerShell and PowerShell 7+ on Windows
            throw ($PodeLocale.showPodeGuiOnlyAvailableOnWindowsExceptionMessage)
        }

        # enable the gui and set general settings
        $PodeContext.Server.Gui.Enabled = $true
        $PodeContext.Server.Gui.Title = $Title
        $PodeContext.Server.Gui.ShowInTaskbar = !$HideFromTaskbar
        $PodeContext.Server.Gui.WindowState = $WindowState
        $PodeContext.Server.Gui.WindowStyle = $WindowStyle
        $PodeContext.Server.Gui.ResizeMode = $ResizeMode

        # set the window's icon path
        if (![string]::IsNullOrWhiteSpace($Icon)) {
            $PodeContext.Server.Gui.Icon = Get-PodeRelativePath -Path $Icon -JoinRoot -Resolve
            if (!(Test-Path $PodeContext.Server.Gui.Icon)) {
                # Path to icon for GUI does not exist
                throw ($PodeLocale.pathToIconForGuiDoesNotExistExceptionMessage -f $PodeContext.Server.Gui.Icon)
            }
        }

        # set the height of the window
        $PodeContext.Server.Gui.Height = $Height
        if ($PodeContext.Server.Gui.Height -le 0) {
            $PodeContext.Server.Gui.Height = 'auto'
        }

        # set the width of the window
        $PodeContext.Server.Gui.Width = $Width
        if ($PodeContext.Server.Gui.Width -le 0) {
            $PodeContext.Server.Gui.Width = 'auto'
        }

        # set the gui to use a specific listener
        $PodeContext.Server.Gui.EndpointName = $EndpointName

        if (![string]::IsNullOrWhiteSpace($EndpointName)) {
            if (!$PodeContext.Server.Endpoints.ContainsKey($EndpointName)) {
                # Endpoint with name '$EndpointName' does not exist.
                throw ($PodeLocale.endpointNameNotExistExceptionMessage -f $EndpointName)
            }

            $PodeContext.Server.Gui.Endpoint = $PodeContext.Server.Endpoints[$EndpointName]
        }
    }
}

<#
.SYNOPSIS
Sets the path for a specified default folder type in the Pode server context.
 
.DESCRIPTION
This function configures the path for one of the Pode server's default folder types: Views, Public, or Errors.
It updates the server's configuration to reflect the new path for the specified folder type.
The function first checks if the provided path exists and is a directory;
if so, it updates the `Server.DefaultFolders` dictionary with the new path.
If the path does not exist or is not a directory, the function throws an error.
 
The purpose of this function is to allow dynamic configuration of the server's folder paths, which can be useful during server setup or when altering the server's directory structure at runtime.
 
.PARAMETER Type
The type of the default folder to set the path for. Must be one of 'Views', 'Public', or 'Errors'.
This parameter determines which default folder's path is being set.
 
.PARAMETER Path
The new file system path for the specified default folder type. This path must exist and be a directory; otherwise, an exception is thrown.
 
.EXAMPLE
Set-PodeDefaultFolder -Type 'Views' -Path 'C:\Pode\Views'
 
This example sets the path for the server's default 'Views' folder to 'C:\Pode\Views', assuming this path exists and is a directory.
 
.EXAMPLE
Set-PodeDefaultFolder -Type 'Public' -Path 'C:\Pode\Public'
 
This example sets the path for the server's default 'Public' folder to 'C:\Pode\Public'.
 
#>

function Set-PodeDefaultFolder {

    [CmdletBinding()]
    param (
        [Parameter()]
        [ValidateSet('Views', 'Public', 'Errors')]
        [string]
        $Type,

        [Parameter()]
        [string]
        $Path
    )
    if (Test-Path -Path $Path -PathType Container) {
        $PodeContext.Server.DefaultFolders[$Type] = $Path
    }
    else {
        # Path does not exist
        throw ($PodeLocale.pathNotExistExceptionMessage -f $Path)
    }
}

<#
.SYNOPSIS
Retrieves the path of a specified default folder type from the Pode server context.
 
.DESCRIPTION
This function returns the path for one of the Pode server's default folder types: Views, Public, or Errors. It accesses the server's configuration stored in the `$PodeContext` variable and retrieves the path for the specified folder type from the `DefaultFolders` dictionary. This function is useful for scripts or modules that need to dynamically access server resources based on the server's current configuration.
 
.PARAMETER Type
The type of the default folder for which to retrieve the path. The valid options are 'Views', 'Public', or 'Errors'. This parameter determines which folder's path will be returned by the function.
 
.EXAMPLE
$path = Get-PodeDefaultFolder -Type 'Views'
 
This example retrieves the current path configured for the server's 'Views' folder and stores it in the `$path` variable.
 
.EXAMPLE
$path = Get-PodeDefaultFolder -Type 'Public'
 
This example retrieves the current path configured for the server's 'Public' folder.
 
.OUTPUTS
String. The file system path of the specified default folder.
#>

function Get-PodeDefaultFolder {
    [CmdletBinding()]
    [OutputType([string])]
    param (
        [Parameter()]
        [ValidateSet('Views', 'Public', 'Errors')]
        [string]
        $Type
    )

    return $PodeContext.Server.DefaultFolders[$Type]
}

<#
.SYNOPSIS
    Attaches a breakpoint which can be used for debugging.
 
.DESCRIPTION
    Attaches a breakpoint which can be used for debugging.
 
.EXAMPLE
    Wait-PodeDebugger
#>

function Wait-PodeDebugger {
    [CmdletBinding()]
    param()

    if (!$PodeContext.Server.Debug.Breakpoints.Enabled) {
        return
    }

    Wait-Debugger
}


<#
.SYNOPSIS
    Retrieves the current state of the Pode server.
 
.DESCRIPTION
    The Get-PodeServerState function evaluates the internal state of the Pode server based on the cancellation tokens available
    in the $PodeContext. The function determines if the server is running, terminating, restarting, suspending, resuming, or
    in any other predefined state.
 
.OUTPUTS
    [string] - The state of the Pode server as one of the following values:
               'Terminated', 'Terminating', 'Resuming', 'Suspending', 'Suspended', 'Restarting', 'Starting', 'Running'.
 
.EXAMPLE
    Get-PodeServerState
 
    Retrieves the current state of the Pode server and returns it as a string.
#>

function Get-PodeServerState {
    [CmdletBinding()]
    [OutputType([Pode.PodeServerState])]
    param()
    # Check if PodeContext or its Tokens property is null; if so, consider the server terminated
    if ($null -eq $PodeContext -or $null -eq $PodeContext.Tokens) {
        return [Pode.PodeServerState]::Terminated
    }

    # Check if the server is in the process of terminating
    if (Test-PodeCancellationTokenRequest -Type Terminate) {
        return [Pode.PodeServerState]::Terminating
    }

    # Check if the server is resuming from a suspended state
    if (Test-PodeCancellationTokenRequest -Type Resume) {
        return [Pode.PodeServerState]::Resuming
    }

    # Check if the server is in the process of restarting
    if (Test-PodeCancellationTokenRequest -Type Restart) {
        return [Pode.PodeServerState]::Restarting
    }

    # Check if the server is suspending or already suspended
    if (Test-PodeCancellationTokenRequest -Type Suspend) {
        if (Test-PodeCancellationTokenRequest -Type Cancellation) {
            return [Pode.PodeServerState]::Suspending
        }
        return [Pode.PodeServerState]::Suspended
    }

    # Check if the server is starting
    if (!(Test-PodeCancellationTokenRequest -Type Start)) {
        return [Pode.PodeServerState]::Starting
    }

    # If none of the above, assume the server is running
    return [Pode.PodeServerState]::Running
}

<#
.SYNOPSIS
    Tests whether the Pode server is in a specified state.
 
.DESCRIPTION
    The `Test-PodeServerState` function checks the current state of the Pode server
    by calling `Get-PodeServerState` and comparing the result to the specified state.
    The function returns `$true` if the server is in the specified state and `$false` otherwise.
 
.PARAMETER State
    Specifies the server state to test. Allowed values are:
    - `Terminated`: The server is not running, and the context is null.
    - `Terminating`: The server is in the process of shutting down.
    - `Resuming`: The server is resuming from a suspended state.
    - `Suspending`: The server is in the process of entering a suspended state.
    - `Suspended`: The server is fully suspended.
    - `Restarting`: The server is restarting.
    - `Starting`: The server is in the process of starting up.
    - `Running`: The server is actively running.
 
.EXAMPLE
    Test-PodeServerState -State 'Running'
 
    Returns `$true` if the server is currently running, otherwise `$false`.
 
.EXAMPLE
    Test-PodeServerState -State 'Suspended'
 
    Returns `$true` if the server is fully suspended, otherwise `$false`.
 
.NOTES
    This function is part of Pode's server state management utilities.
    It relies on the `Get-PodeServerState` function to determine the current state.
#>

function Test-PodeServerState {
    param(
        [Parameter(Mandatory = $true)]
        [Pode.PodeServerState]
        $State
    )

    # Call Get-PodeServerState to retrieve the current server state
    $currentState = Get-PodeServerState

    # Return true if the current state matches the provided state, otherwise false
    return $currentState -eq $State
}

<#
.SYNOPSIS
    Enables new incoming requests by removing the middleware that blocks requests when the Pode Watchdog client is active.
 
.DESCRIPTION
    This function resets the cancellation token for the Disable action, allowing the Pode server to accept new incoming requests.
#>

function Enable-PodeServer {
    if (Test-PodeCancellationTokenRequest -Type Disable) {
        Reset-PodeCancellationToken -Type Disable
    }
}

<#
.SYNOPSIS
    Blocks new incoming requests by adding middleware that returns a 503 Service Unavailable status when the Pode Watchdog client is active.
 
.DESCRIPTION
    This function integrates middleware into the Pode server, preventing new incoming requests while the Pode Watchdog client is active.
    All requests receive a 503 Service Unavailable response, including a 'Retry-After' header that specifies when the service will become available.
 
.PARAMETER RetryAfter
    Specifies the time in seconds clients should wait before retrying their requests. Default is 3600 seconds (1 hour).
#>

function Disable-PodeServer {
    param (
        [Parameter(Mandatory = $false)]
        [int]$RetryAfter = 3600
    )

    $PodeContext.Server.AllowedActions.DisableSettings.RetryAfter = $RetryAfter
    if (! (Test-PodeCancellationTokenRequest -Type Disable)) {
        Close-PodeCancellationTokenRequest -Type Disable
    }
}