diff --git a/docs/Getting-Started/pwshsupportpolicy.md b/docs/Getting-Started/pwshsupportpolicy.md new file mode 100644 index 000000000..d2060cbe0 --- /dev/null +++ b/docs/Getting-Started/pwshsupportpolicy.md @@ -0,0 +1,30 @@ +# PowerShell Support Policy + +## Overview +This document details the support policy for PowerShell versions as they relate to Pode releases. Our aim is to provide clarity on which versions of PowerShell are supported by each release of Pode, ensuring a secure, efficient, and compatible development environment. + +## Policy Statement +Pode commits to supporting PowerShell versions that are not end of life (EOL) at the moment of each Pode release. This dynamic approach allows Pode to adapt to the evolving PowerShell ecosystem, ensuring compatibility with recent and supported PowerShell versions while maintaining a high security standard. + +### Support Lifecycle +- For each Pode release, support is extended to versions of PowerShell that are not EOL at the time of release. +- Subsequent Pode releases may not support previously compatible PowerShell versions if those versions have reached EOL in the interim. +- This policy applies to all versions of PowerShell, including PowerShell (version 7.x), PowerShell Core (versions 6.x) and Windows PowerShell 5.1. +- Windows Powershell 5.1 will be supported by any Pode version released prior the Jan 7 2027 + +### Example +- **Pode 2.10 Release (April 2024)**: Supports PowerShell 7.2, 7.3, 7.4, and Windows PowerShell 5.1, assuming none are EOL at the time of release. +- **Pode 2.11 Release (Late 2024)**: With PowerShell 7.2 and 7.3 reaching EOL, support for these versions would be discontinued. Pode 2.11 would then support PowerShell Core 7.4 and any newer, non-EOL versions, along with Windows PowerShell 5.1. + +## Testing Strategy +Pode is tested against the PowerShell versions it supports at the time of each release. Testing focuses on ensuring compatibility with non-EOL versions of PowerShell, reflecting the policy stated above. + +## Warning Mechanism +A warning mechanism within Pode detects the PowerShell version in use at runtime. If a version no longer supported by the current release of Pode is detected—due to reaching EOL—a warning will be issued, advising on the potential risks and recommending an update to a supported version. + +## Version Updates and Communication +Updates to the PowerShell version support policy will be documented in Pode’s release notes and official documentation. Users are encouraged to review these sources to stay informed about which PowerShell versions are supported by their version of Pode. + +## Feedback and Contributions +Feedback on Pode's PowerShell version support policy is invaluable. We welcome suggestions and contributions from our community to help refine and improve our approach. Please share your thoughts through our GitHub repository or community forums. + diff --git a/examples/web-auth-apikey-jwt.ps1 b/examples/web-auth-apikey-jwt.ps1 index 9c39f2aa8..0b94b8dcd 100644 --- a/examples/web-auth-apikey-jwt.ps1 +++ b/examples/web-auth-apikey-jwt.ps1 @@ -27,7 +27,7 @@ Import-Module "$($path)/src/Pode.psm1" -Force -ErrorAction Stop Start-PodeServer -Threads 2 { # listen on localhost:8085 - Add-PodeEndpoint -Address * -Port 8085 -Protocol Http + Add-PodeEndpoint -Address localhost -Port 8085 -Protocol Http New-PodeLoggingMethod -File -Name 'requests' | Enable-PodeRequestLogging New-PodeLoggingMethod -Terminal | Enable-PodeErrorLogging diff --git a/pode.build.ps1 b/pode.build.ps1 index 930714a38..9e9bc00c1 100644 --- a/pode.build.ps1 +++ b/pode.build.ps1 @@ -118,10 +118,6 @@ function Invoke-PodeBuildDotnetBuild($target ) { else { $AssemblyVersion = '' } - dotnet build --configuration Release --self-contained --framework $target $AssemblyVersion - if (!$?) { - throw "dotnet build failed for $($target)" - } dotnet publish --configuration Release --self-contained --framework $target $AssemblyVersion --output ../Libs/$target if (!$?) { @@ -130,6 +126,13 @@ function Invoke-PodeBuildDotnetBuild($target ) { } +function Get-PodeBuildPwshEOL { + $eol = invoke-restmethod -Uri 'https://endoflife.date/api/powershell.json' -Headers @{Accept = 'application/json' } + return @{ + eol = ($eol | Where-Object { [datetime]$_.eol -lt [datetime]::Now }).cycle -join ',' + supported = ($eol | Where-Object { [datetime]$_.eol -ge [datetime]::Now }).cycle -join ',' + } +} <# # Helper Tasks @@ -137,7 +140,8 @@ function Invoke-PodeBuildDotnetBuild($target ) { # Synopsis: Stamps the version onto the Module Task StampVersion { - (Get-Content ./pkg/Pode.psd1) | ForEach-Object { $_ -replace '\$version\$', $Version } | Set-Content ./pkg/Pode.psd1 + $pwshVersions = Get-PodeBuildPwshEOL + (Get-Content ./pkg/Pode.psd1) | ForEach-Object { $_ -replace '\$version\$', $Version -replace '\$versionsUntested\$', $pwshVersions.eol -replace '\$versionsSupported\$', $pwshVersions.supported -replace '\$buildyear\$', ((get-date).Year) } | Set-Content ./pkg/Pode.psd1 (Get-Content ./pkg/Pode.Internal.psd1) | ForEach-Object { $_ -replace '\$version\$', $Version } | Set-Content ./pkg/Pode.Internal.psd1 (Get-Content ./packers/choco/pode_template.nuspec) | ForEach-Object { $_ -replace '\$version\$', $Version } | Set-Content ./packers/choco/pode.nuspec (Get-Content ./packers/choco/tools/ChocolateyInstall_template.ps1) | ForEach-Object { $_ -replace '\$version\$', $Version } | Set-Content ./packers/choco/tools/ChocolateyInstall.ps1 @@ -434,13 +438,13 @@ Task DocsBuild DocsDeps, DocsHelpBuild, { } # Synopsis: Clean the build enviroment -Task Clean CleanPkg,CleanDeliverable,CleanLibs,CleanListener +Task Clean CleanPkg, CleanDeliverable, CleanLibs, CleanListener # Synopsis: Clean the Deliverable folder Task CleanDeliverable { $path = './deliverable' if (Test-Path -Path $path -PathType Container) { - Write-Host "Removing ./deliverable folder" + Write-Host 'Removing ./deliverable folder' Remove-Item -Path $path -Recurse -Force | Out-Null } Write-Host "Cleanup $path done" @@ -450,16 +454,16 @@ Task CleanDeliverable { Task CleanPkg { $path = './pkg' if ((Test-Path -Path $path -PathType Container )) { - Write-Host "Removing ./pkg folder" + Write-Host 'Removing ./pkg folder' Remove-Item -Path $path -Recurse -Force | Out-Null } if ((Test-Path -Path .\packers\choco\tools\ChocolateyInstall.ps1 -PathType Leaf )) { - Write-Host "Removing .\packers\choco\tools\ChocolateyInstall.ps1" + Write-Host 'Removing .\packers\choco\tools\ChocolateyInstall.ps1' Remove-Item -Path .\packers\choco\tools\ChocolateyInstall.ps1 } if ((Test-Path -Path .\packers\choco\pode.nuspec -PathType Leaf )) { - Write-Host "Removing .\packers\choco\pode.nuspec" + Write-Host 'Removing .\packers\choco\pode.nuspec' Remove-Item -Path .\packers\choco\pode.nuspec } Write-Host "Cleanup $path done" @@ -470,7 +474,7 @@ Task CleanLibs { $path = './src/Libs' if (Test-Path -Path $path -PathType Container) { Write-Host "Removing $path contents" - Remove-Item -Path $path -Recurse -Force | Out-Null + Remove-Item -Path $path -Recurse -Force | Out-Null } Write-Host "Cleanup $path done" } @@ -480,7 +484,7 @@ Task CleanListener { $path = './src/Listener/bin' if (Test-Path -Path $path -PathType Container) { Write-Host "Removing $path contents" - Remove-Item -Path $path -Recurse -Force | Out-Null + Remove-Item -Path $path -Recurse -Force | Out-Null } Write-Host "Cleanup $path done" } diff --git a/src/Pode.psd1 b/src/Pode.psd1 index c558cb9c2..0118bb0b5 100644 --- a/src/Pode.psd1 +++ b/src/Pode.psd1 @@ -20,13 +20,13 @@ Author = 'Matthew Kelly (Badgerati)' # Copyright statement for this module - Copyright = 'Copyright (c) 2017-2023 Matthew Kelly (Badgerati), licensed under the MIT License.' + Copyright = 'Copyright (c) 2017-$buildyear$ Matthew Kelly (Badgerati), licensed under the MIT License.' # Description of the functionality provided by this module Description = 'A Cross-Platform PowerShell framework for creating web servers to host REST APIs and Websites. Pode also has support for being used in Azure Functions and AWS Lambda.' # Minimum version of the Windows PowerShell engine required by this module - PowerShellVersion = '5.0' + PowerShellVersion = '5.1' # Functions to export from this Module FunctionsToExport = @( @@ -294,6 +294,7 @@ 'Get-PodeEndpoint', 'Pode', 'Get-PodeServerDefaultSecret', + 'Get-PodeVersion', # openapi 'Enable-PodeOpenApi', @@ -457,14 +458,16 @@ # Private data to pass to the module specified in RootModule/ModuleToProcess. This may also contain a PSData hashtable with additional module metadata used by PowerShell. PrivateData = @{ - PSData = @{ + PSData = @{ # Tags applied to this module. These help with module discovery in online galleries. - Tags = @('powershell', 'web', 'server', 'http', 'listener', 'rest', 'api', 'tcp', 'smtp', 'websites', - 'powershell-core', 'windows', 'unix', 'linux', 'pode', 'PSEdition_Core', 'cross-platform', - 'file-monitoring', 'multithreaded', 'schedule', 'middleware', 'session', - 'authentication', 'authorisation', 'arm', 'raspberry-pi', 'aws-lambda', - 'azure-functions', 'websockets', 'swagger', 'openapi', 'webserver', 'secrets', 'fim') + Tags = @( + 'powershell', 'web', 'server', 'http', 'https', 'listener', 'rest', 'api', 'tcp', + 'smtp', 'websites', 'powershell-core', 'windows', 'unix', 'linux', 'pode', 'PSEdition_Core', + 'cross-platform', 'file-monitoring', 'multithreaded', 'schedule', 'middleware', 'session', + 'authentication', 'authorisation', 'authorization', 'arm', 'raspberry-pi', 'aws-lambda', + 'azure-functions', 'websockets', 'swagger', 'openapi', 'webserver', 'secrets', 'fim' + ) # A URL to the license for this module. LicenseUri = 'https://raw.githubusercontent.com/Badgerati/Pode/master/LICENSE.txt' @@ -479,5 +482,9 @@ ReleaseNotes = 'https://github.com/Badgerati/Pode/releases/tag/v$version$' } + PwshVersions = @{ + Untested = '$versionsUntested$' + Supported = '$versionsSupported$' + } } } \ No newline at end of file diff --git a/src/Pode.psm1 b/src/Pode.psm1 index 8d2aea415..d1a1d315b 100644 --- a/src/Pode.psm1 +++ b/src/Pode.psm1 @@ -5,17 +5,35 @@ $root = Split-Path -Parent -Path $MyInvocation.MyCommand.Path Add-Type -AssemblyName System.Web Add-Type -AssemblyName System.Net.Http -# netstandard2 for <7.2 -if ($PSVersionTable.PSVersion -lt [version]'7.2.0') { - Add-Type -LiteralPath "$($root)/Libs/netstandard2.0/Pode.dll" -ErrorAction Stop -} -# net6 for =7.2 -elseif ($PSVersionTable.PSVersion -lt [version]'7.3.0') { - Add-Type -LiteralPath "$($root)/Libs/net6.0/Pode.dll" -ErrorAction Stop +# Construct the path to the module manifest (.psd1 file) +$moduleManifestPath = Join-Path -Path $root -ChildPath 'Pode.psd1' + +# Import the module manifest to access its properties +$moduleManifest = Import-PowerShellDataFile -Path $moduleManifestPath + +$podeDll = [AppDomain]::CurrentDomain.GetAssemblies() | Where-Object { $_.GetName().Name -eq 'Pode' } + +if ($podeDll) { + if ( $moduleManifest.ModuleVersion -ne '$version$') { + $moduleVersion = ([version]::new($moduleManifest.ModuleVersion + '.0')) + if ($podeDll.GetName().Version -ne $moduleVersion) { + throw "An existing incompatible Pode.DLL version $($podeDll.GetName().Version) is loaded. Version $moduleVersion is required. Open a new Powershell/pwsh session and retry." + } + } } -# net7 for >7.2 else { - Add-Type -LiteralPath "$($root)/Libs/net7.0/Pode.dll" -ErrorAction Stop + if ($PSVersionTable.PSVersion -ge [version]'7.4.0') { + Add-Type -LiteralPath "$($root)/Libs/net8.0/Pode.dll" -ErrorAction Stop + } + elseif ($PSVersionTable.PSVersion -ge [version]'7.3.0') { + Add-Type -LiteralPath "$($root)/Libs/net7.0/Pode.dll" -ErrorAction Stop + } + elseif ($PSVersionTable.PSVersion -ge [version]'7.2.0') { + Add-Type -LiteralPath "$($root)/Libs/net6.0/Pode.dll" -ErrorAction Stop + } + else { + Add-Type -LiteralPath "$($root)/Libs/netstandard2.0/Pode.dll" -ErrorAction Stop + } } # load private functions @@ -24,11 +42,21 @@ Get-ChildItem "$($root)/Private/*.ps1" | ForEach-Object { . ([System.IO.Path]::G # only import public functions $sysfuncs = Get-ChildItem Function: +# only import public alias +$sysaliases = Get-ChildItem Alias: + # load public functions Get-ChildItem "$($root)/Public/*.ps1" | ForEach-Object { . ([System.IO.Path]::GetFullPath($_)) } # get functions from memory and compare to existing to find new functions added $funcs = Get-ChildItem Function: | Where-Object { $sysfuncs -notcontains $_ } - +$aliases = Get-ChildItem Alias: | Where-Object { $sysaliases -notcontains $_ } # export the module's public functions -Export-ModuleMember -Function ($funcs.Name) \ No newline at end of file +if ($funcs) { + if ($aliases) { + Export-ModuleMember -Function ($funcs.Name) -Alias $aliases.Name + } + else { + Export-ModuleMember -Function ($funcs.Name) + } +} diff --git a/src/Private/Helpers.ps1 b/src/Private/Helpers.ps1 index a6896d155..7deb3edbc 100644 --- a/src/Private/Helpers.ps1 +++ b/src/Private/Helpers.ps1 @@ -2936,4 +2936,126 @@ function Test-PodePlaceholders { } return ($Path -imatch $Placeholder) -} \ No newline at end of file +} + + +<# +.SYNOPSIS +Retrieves the PowerShell module manifest object for the specified module. + +.DESCRIPTION +This function constructs the path to a PowerShell module manifest file (.psd1) located in the parent directory of the script root. It then imports the module manifest file to access its properties and returns the manifest object. This can be useful for scripts that need to dynamically discover and utilize module metadata, such as version, dependencies, and exported functions. + +.PARAMETERS +This function does not accept any parameters. + +.EXAMPLE +$manifest = Get-PodeModuleManifest +This example calls the `Get-PodeModuleManifest` function to retrieve the module manifest object and stores it in the variable `$manifest`. + +#> +function Get-PodeModuleManifest { + # Construct the path to the module manifest (.psd1 file) + $moduleManifestPath = Join-Path -Path (Split-Path -Path $PSScriptRoot -Parent) -ChildPath 'Pode.psd1' + + # Import the module manifest to access its properties + $moduleManifest = Import-PowerShellDataFile -Path $moduleManifestPath + return $moduleManifest +} + + +<# +.SYNOPSIS +Tests if the Pode module is from the development branch. + +.DESCRIPTION +The Test-PodeVersionDev function checks if the Pode module's version matches the placeholder value ('$version$'), which is used to indicate the development branch of the module. It returns $true if the version matches, indicating the module is from the development branch, and $false otherwise. + +.PARAMETER None +This function does not accept any parameters. + +.OUTPUTS +System.Boolean +Returns $true if the Pode module version is '$version$', indicating the development branch. Returns $false for any other version. + +.EXAMPLE +PS> $moduleManifest = @{ ModuleVersion = '$version$' } +PS> Test-PodeVersionDev + +Returns $true, indicating the development branch. + +.EXAMPLE +PS> $moduleManifest = @{ ModuleVersion = '1.2.3' } +PS> Test-PodeVersionDev + +Returns $false, indicating a specific release version. + +.NOTES +This function assumes that $moduleManifest is a hashtable representing the loaded module manifest, with a key of ModuleVersion. + +#> +function Test-PodeVersionDev { + return (Get-PodeModuleManifest).ModuleVersion -eq '$version$' +} + +<# +.SYNOPSIS +Tests the running PowerShell version for compatibility with Pode, identifying end-of-life (EOL) and untested versions. + +.DESCRIPTION +The `Test-PodeVersionPwshEOL` function checks the current PowerShell version against a list of versions that were either supported or EOL at the time of the Pode release. It uses the module manifest to determine which PowerShell versions are considered EOL and which are officially supported. If the current version is EOL or was not tested with the current release of Pode, the function generates a warning. This function aids in maintaining best practices for using supported PowerShell versions with Pode. + +.PARAMETER ReportUntested +If specified, the function will report if the current PowerShell version was not available and thus untested at the time of the Pode release. This is useful for identifying potential compatibility issues with newer versions of PowerShell. + +.OUTPUTS +A hashtable containing two keys: +- `eol`: A boolean indicating if the current PowerShell version was EOL at the time of the Pode release. +- `supported`: A boolean indicating if the current PowerShell version was officially supported by Pode at the time of the release. + +.EXAMPLE +Test-PodeVersionPwshEOL + +Checks the current PowerShell version against Pode's supported and EOL versions list. Outputs a warning if the version is EOL or untested, and returns a hashtable indicating the compatibility status. + +.EXAMPLE +Test-PodeVersionPwshEOL -ReportUntested + +Similar to the basic usage, but also reports if the current PowerShell version was untested because it was not available at the time of the Pode release. + +.NOTES +This function is part of the Pode module's utilities to ensure compatibility and encourage the use of supported PowerShell versions. + +#> +function Test-PodeVersionPwshEOL { + param( + [switch] $ReportUntested + ) + $moduleManifest = Get-PodeModuleManifest + if ($moduleManifest.ModuleVersion -eq '$version$') { + return @{ + eol = $false + supported = $true + } + } + + $psVersion = $PSVersionTable.PSVersion + $eolVersions = $moduleManifest.PrivateData.PwshVersions.Untested -split ',' + $isEol = "$($psVersion.Major).$($psVersion.Minor)" -in $eolVersions + + if ($isEol) { + Write-PodeHost "[WARNING] Pode $(Get-PodeVersion) has not been tested on PowerShell $($PSVersionTable.PSVersion), as it is EOL." -ForegroundColor Yellow + } + + $SupportedVersions = $moduleManifest.PrivateData.PwshVersions.Supported -split ',' + $isSupported = "$($psVersion.Major).$($psVersion.Minor)" -in $SupportedVersions + + if ((! $isSupported) -and (! $isEol) -and $ReportUntested) { + Write-PodeHost "[WARNING] Pode $(Get-PodeVersion) has not been tested on PowerShell $($PSVersionTable.PSVersion), as it was not available when Pode was released." -ForegroundColor Yellow + } + + return @{ + eol = $isEol + supported = $isSupported + } +} diff --git a/src/Private/Server.ps1 b/src/Private/Server.ps1 index 261c8ddb5..3627c734a 100644 --- a/src/Private/Server.ps1 +++ b/src/Private/Server.ps1 @@ -8,6 +8,10 @@ function Start-PodeInternalServer { ) try { + # Check if the running version of Powershell is EOL + Write-PodeHost "Pode $(Get-PodeVersion) (PID: $($PID))" -ForegroundColor Cyan + $null = Test-PodeVersionPwshEOL -ReportUntested + # setup temp drives for internal dirs Add-PodePSInbuiltDrives diff --git a/src/Public/Utilities.ps1 b/src/Public/Utilities.ps1 index 5ba286b9b..5b7f9395f 100644 --- a/src/Public/Utilities.ps1 +++ b/src/Public/Utilities.ps1 @@ -988,6 +988,7 @@ New-PodeCron -Every Quarter # every 1st #> function New-PodeCron { [CmdletBinding()] + [OutputType([String])] param( [Parameter()] [ValidateRange(0, 59)] @@ -1165,4 +1166,46 @@ function New-PodeCron { # build and return return "$($cron.Minute) $($cron.Hour) $($cron.Date) $($cron.Month) $($cron.Day)" +} + + + +<# +.SYNOPSIS +Gets the version of the Pode module. + +.DESCRIPTION +The Get-PodeVersion function checks the version of the Pode module specified in the module manifest. If the module version is not a placeholder value ('$version$'), it returns the actual version prefixed with 'v.'. If the module version is the placeholder value, indicating the development branch, it returns '[develop branch]'. + +.PARAMETER None +This function does not accept any parameters. + +.OUTPUTS +System.String +Returns a string indicating the version of the Pode module or '[dev]' if on a development version. + +.EXAMPLE +PS> $moduleManifest = @{ ModuleVersion = '1.2.3' } +PS> Get-PodeVersion + +Returns 'v1.2.3'. + +.EXAMPLE +PS> $moduleManifest = @{ ModuleVersion = '$version$' } +PS> Get-PodeVersion + +Returns '[dev]'. + +.NOTES +This function assumes that $moduleManifest is a hashtable representing the loaded module manifest, with a key of ModuleVersion. + +#> +function Get-PodeVersion { + $moduleManifest = Get-PodeModuleManifest + if ($moduleManifest.ModuleVersion -ne '$version$') { + return "v$($moduleManifest.ModuleVersion)" + } + else { + return '[dev]' + } } \ No newline at end of file