Unpacking Software Livestream

Join our monthly Unpacking Software livestream to hear about the latest news, chat and opinion on packaging, software deployment and lifecycle management!

Learn More

Chocolatey Product Spotlight

Join the Chocolatey Team on our regular monthly stream where we put a spotlight on the most recent Chocolatey product releases. You'll have a chance to have your questions answered in a live Ask Me Anything format.

Learn More

Chocolatey Coding Livestream

Join us for the Chocolatey Coding Livestream, where members of our team dive into the heart of open source development by coding live on various Chocolatey projects. Tune in to witness real-time coding, ask questions, and gain insights into the world of package management. Don't miss this opportunity to engage with our team and contribute to the future of Chocolatey!

Learn More

Calling All Chocolatiers! Whipping Up Windows Automation with Chocolatey Central Management

Webinar from
Wednesday, 17 January 2024

We are delighted to announce the release of Chocolatey Central Management v0.12.0, featuring seamless Deployment Plan creation, time-saving duplications, insightful Group Details, an upgraded Dashboard, bug fixes, user interface polishing, and refined documentation. As an added bonus we'll have members of our Solutions Engineering team on-hand to dive into some interesting ways you can leverage the new features available!

Watch On-Demand
Chocolatey Community Coffee Break

Join the Chocolatey Team as we discuss all things Community, what we do, how you can get involved and answer your Chocolatey questions.

Watch The Replays
Chocolatey and Intune Overview

Webinar Replay from
Wednesday, 30 March 2022

At Chocolatey Software we strive for simple, and teaching others. Let us teach you just how simple it could be to keep your 3rd party applications updated across your devices, all with Intune!

Watch On-Demand
Chocolatey For Business. In Azure. In One Click.

Livestream from
Thursday, 9 June 2022

Join James and Josh to show you how you can get the Chocolatey For Business recommended infrastructure and workflow, created, in Azure, in around 20 minutes.

Watch On-Demand
The Future of Chocolatey CLI

Livestream from
Thursday, 04 August 2022

Join Paul and Gary to hear more about the plans for the Chocolatey CLI in the not so distant future. We'll talk about some cool new features, long term asks from Customers and Community and how you can get involved!

Watch On-Demand
Hacktoberfest Tuesdays 2022

Livestreams from
October 2022

For Hacktoberfest, Chocolatey ran a livestream every Tuesday! Re-watch Cory, James, Gary, and Rain as they share knowledge on how to contribute to open-source projects such as Chocolatey CLI.

Watch On-Demand

Pode 2.11.0

Legal Disclaimer: Neither this package nor Chocolatey Software, Inc. are affiliated with or endorsed by Badgerati. The inclusion of Badgerati trademark(s), if any, upon this webpage is solely to identify Badgerati goods or services and not for commercial purposes.

  • 1
  • 2
  • 3

This Package Contains an Exempted Check

Not All Tests Have Passed


Validation Testing Passed


Verification Testing Passed

Details

Scan Testing Exemption for this package version only:

Streams.ps1 does appear to be false positive

Details
Learn More

Deployment Method: Individual Install, Upgrade, & Uninstall

To install Pode, run the following command from the command line or from PowerShell:

>

To upgrade Pode, run the following command from the command line or from PowerShell:

>

To uninstall Pode, run the following command from the command line or from PowerShell:

>

Deployment Method:

NOTE

This applies to both open source and commercial editions of Chocolatey.

1. Enter Your Internal Repository Url

(this should look similar to https://community.chocolatey.org/api/v2/)


2. Setup Your Environment

1. Ensure you are set for organizational deployment

Please see the organizational deployment guide

2. Get the package into your environment

  • Open Source or Commercial:
    • Proxy Repository - Create a proxy nuget repository on Nexus, Artifactory Pro, or a proxy Chocolatey repository on ProGet. Point your upstream to https://community.chocolatey.org/api/v2/. Packages cache on first access automatically. Make sure your choco clients are using your proxy repository as a source and NOT the default community repository. See source command for more information.
    • You can also just download the package and push it to a repository Download

3. Copy Your Script

choco upgrade pode -y --source="'INTERNAL REPO URL'" [other options]

See options you can pass to upgrade.

See best practices for scripting.

Add this to a PowerShell script or use a Batch script with tools and in places where you are calling directly to Chocolatey. If you are integrating, keep in mind enhanced exit codes.

If you do use a PowerShell script, use the following to ensure bad exit codes are shown as failures:


choco upgrade pode -y --source="'INTERNAL REPO URL'" 
$exitCode = $LASTEXITCODE

Write-Verbose "Exit code was $exitCode"
$validExitCodes = @(0, 1605, 1614, 1641, 3010)
if ($validExitCodes -contains $exitCode) {
  Exit 0
}

Exit $exitCode

- name: Install pode
  win_chocolatey:
    name: pode
    version: '2.11.0'
    source: INTERNAL REPO URL
    state: present

See docs at https://docs.ansible.com/ansible/latest/modules/win_chocolatey_module.html.


chocolatey_package 'pode' do
  action    :install
  source   'INTERNAL REPO URL'
  version  '2.11.0'
end

See docs at https://docs.chef.io/resource_chocolatey_package.html.


cChocoPackageInstaller pode
{
    Name     = "pode"
    Version  = "2.11.0"
    Source   = "INTERNAL REPO URL"
}

Requires cChoco DSC Resource. See docs at https://github.com/chocolatey/cChoco.


package { 'pode':
  ensure   => '2.11.0',
  provider => 'chocolatey',
  source   => 'INTERNAL REPO URL',
}

Requires Puppet Chocolatey Provider module. See docs at https://forge.puppet.com/puppetlabs/chocolatey.


4. If applicable - Chocolatey configuration/installation

See infrastructure management matrix for Chocolatey configuration elements and examples.

WARNING

There are versions of this package awaiting moderation . See the Version History section below.

Package Approved

This package was approved by moderator flcdrg on 20 Dec 2024.

Description

Pode is a Cross-Platform framework for creating web servers to host REST APIs and Websites. Pode also has support for being used in Azure Functions and AWS Lambda.

Features

  • Cross-platform using PowerShell Core (with support for PS5)
  • Docker support, including images for ARM/Raspberry Pi
  • Azure Functions, AWS Lambda, and IIS support
  • OpenAPI, Swagger, and ReDoc support
  • Listen on a single or multiple IP address/hostnames
  • Cross-platform support for HTTP, HTTPS, TCP and SMTP
  • Cross-platform support for WebSockets, including secure WebSockets
  • Host REST APIs, Web Pages, and Static Content (with caching)
  • Support for custom error pages
  • Request and Response compression using GZip/Deflate
  • Multi-thread support for incoming requests
  • Inbuilt template engine, with support for third-parties
  • Async timers for short-running repeatable processes
  • Async scheduled tasks using cron expressions for short/long-running processes
  • Supports logging to CLI, Files, and custom logic for other services like LogStash
  • Cross-state variable access across multiple runspaces
  • Restart the server via file monitoring, or defined periods/times
  • Ability to allow/deny requests from certain IP addresses and subnets
  • Basic rate limiting for IP addresses and subnets
  • Middleware and Sessions on web servers, with Flash message and CSRF support
  • Authentication on requests, such as Basic, Windows and Azure AD
  • Support for dynamically building Routes from Functions and Modules
  • Generate/bind self-signed certificates
  • Secret management support to load secrets from vaults
  • Support for File Watchers
  • (Windows) Open the hosted server as a desktop application

src\Libs\net6.0\Pode.deps.json
{
  "runtimeTarget": {
    "name": ".NETCoreApp,Version=v6.0",
    "signature": ""
  },
  "compilationOptions": {},
  "targets": {
    ".NETCoreApp,Version=v6.0": {
      "Pode/2.11.0": {
        "runtime": {
          "Pode.dll": {}
        }
      }
    }
  },
  "libraries": {
    "Pode/2.11.0": {
      "type": "project",
      "serviceable": false,
      "sha512": ""
    }
  }
}
src\Libs\net6.0\Pode.dll
md5: 00FF2BACDD84541E15C023F1D2279F2E | sha1: FF78C7C64A5DC7AFE65FBA428E62A420BBEDF732 | sha256: 3F87FB4E99ECA955B3D3BD34D7853062FE7B8C85A674FC3106802CC087FBD565 | sha512: 4AE8C7390ED4FCFBD01AF3413877F0D99798832F23BB65B20409121057073AC191789D5D6DC05E940C8F741C95EADD0EDB555F0E00908DDAB065F4A4A235F383
src\Libs\net6.0\Pode.pdb
 
src\Libs\net8.0\Pode.deps.json
{
  "runtimeTarget": {
    "name": ".NETCoreApp,Version=v8.0",
    "signature": ""
  },
  "compilationOptions": {},
  "targets": {
    ".NETCoreApp,Version=v8.0": {
      "Pode/2.11.0": {
        "runtime": {
          "Pode.dll": {}
        }
      }
    }
  },
  "libraries": {
    "Pode/2.11.0": {
      "type": "project",
      "serviceable": false,
      "sha512": ""
    }
  }
}
src\Libs\net8.0\Pode.dll
md5: E05242701496E268DD679BC79713105B | sha1: C2427FE828DEED186ACE088EB149E53F5110AC40 | sha256: 4DE21CFF64FA309C9C0BABA7AEF0BBD06B162AFE4220A4E4E0A7BB02CA0441CD | sha512: 7A53E60A2FFF75BB2F6FB8AB89588CFB401FAF0859022CAB6B87D416612494CF8BFD99AC1A3F25F20DBD353065A7346547736F2785632B1EEF3F36CBFE65B686
src\Libs\net8.0\Pode.pdb
 
src\Libs\netstandard2.0\Pode.deps.json
{
  "runtimeTarget": {
    "name": ".NETStandard,Version=v2.0/",
    "signature": ""
  },
  "compilationOptions": {},
  "targets": {
    ".NETStandard,Version=v2.0": {},
    ".NETStandard,Version=v2.0/": {
      "Pode/2.11.0": {
        "dependencies": {
          "NETStandard.Library": "2.0.3"
        },
        "runtime": {
          "Pode.dll": {}
        }
      },
      "Microsoft.NETCore.Platforms/1.1.0": {},
      "NETStandard.Library/2.0.3": {
        "dependencies": {
          "Microsoft.NETCore.Platforms": "1.1.0"
        }
      }
    }
  },
  "libraries": {
    "Pode/2.11.0": {
      "type": "project",
      "serviceable": false,
      "sha512": ""
    },
    "Microsoft.NETCore.Platforms/1.1.0": {
      "type": "package",
      "serviceable": true,
      "sha512": "sha512-kz0PEW2lhqygehI/d6XsPCQzD7ff7gUJaVGPVETX611eadGsA3A877GdSlU0LRVMCTH/+P3o2iDTak+S08V2+A==",
      "path": "microsoft.netcore.platforms/1.1.0",
      "hashPath": "microsoft.netcore.platforms.1.1.0.nupkg.sha512"
    },
    "NETStandard.Library/2.0.3": {
      "type": "package",
      "serviceable": true,
      "sha512": "sha512-st47PosZSHrjECdjeIzZQbzivYBJFv6P2nv4cj2ypdI204DO+vZ7l5raGMiX4eXMJ53RfOIg+/s4DHVZ54Nu2A==",
      "path": "netstandard.library/2.0.3",
      "hashPath": "netstandard.library.2.0.3.nupkg.sha512"
    }
  }
}
src\Libs\netstandard2.0\Pode.dll
md5: 75650334613BD22BB3637324A433E438 | sha1: 6B6188C1EC4F378DF367D7FB651F6C2DB6DAD075 | sha256: FC89C7112959616915F84B57DC622F52C007832F0DE11682B229607B302C647C | sha512: 60F0E2925433EA055EC683D7AC9BF594E846E26A8D942E7C8B3F9663FFA2462481D292682986DA7B672E0069F95876D3C4059F79CCF4E0853CDB7A332FDFEEB7
src\Libs\netstandard2.0\Pode.pdb
 
src\LICENSE.txt
The MIT License (MIT)

Copyright (c) [2017-2024] [Matthew Kelly (Badgerati)]

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
src\licenses\LICENSE.bootstrap.txt
Project URL: https://github.com/twbs/bootstrap

The MIT License (MIT)

Copyright (c) 2011-2024 The Bootstrap Authors

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
src\licenses\LICENSE.Font-Awesome.txt
Project URL: https://github.com/FortAwesome/Font-Awesome

Fonticons, Inc. (https://fontawesome.com)

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

Font Awesome Free License

Font Awesome Free is free, open source, and GPL friendly. You can use it for
commercial projects, open source projects, or really almost whatever you want.
Full Font Awesome Free license: https://fontawesome.com/license/free.

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

# Icons: CC BY 4.0 License (https://creativecommons.org/licenses/by/4.0/)

The Font Awesome Free download is licensed under a Creative Commons
Attribution 4.0 International License and applies to all icons packaged
as SVG and JS file types.

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

# Fonts: SIL OFL 1.1 License

In the Font Awesome Free download, the SIL OFL license applies to all icons
packaged as web and desktop font files.

Copyright (c) 2024 Fonticons, Inc. (https://fontawesome.com)
with Reserved Font Name: "Font Awesome".

This Font Software is licensed under the SIL Open Font License, Version 1.1.
This license is copied below, and is also available with a FAQ at:
http://scripts.sil.org/OFL

SIL OPEN FONT LICENSE
Version 1.1 - 26 February 2007

PREAMBLE
The goals of the Open Font License (OFL) are to stimulate worldwide
development of collaborative font projects, to support the font creation
efforts of academic and linguistic communities, and to provide a free and
open framework in which fonts may be shared and improved in partnership
with others.

The OFL allows the licensed fonts to be used, studied, modified and
redistributed freely as long as they are not sold by themselves. The
fonts, including any derivative works, can be bundled, embedded,
redistributed and/or sold with any software provided that any reserved
names are not used by derivative works. The fonts and derivatives,
however, cannot be released under any other type of license. The
requirement for fonts to remain under this license does not apply
to any document created using the fonts or their derivatives.

DEFINITIONS
"Font Software" refers to the set of files released by the Copyright
Holder(s) under this license and clearly marked as such. This may
include source files, build scripts and documentation.

"Reserved Font Name" refers to any names specified as such after the
copyright statement(s).

"Original Version" refers to the collection of Font Software components as
distributed by the Copyright Holder(s).

"Modified Version" refers to any derivative made by adding to, deleting,
or substituting — in part or in whole — any of the components of the
Original Version, by changing formats or by porting the Font Software to a
new environment.

"Author" refers to any designer, engineer, programmer, technical
writer or other person who contributed to the Font Software.

PERMISSION & CONDITIONS
Permission is hereby granted, free of charge, to any person obtaining
a copy of the Font Software, to use, study, copy, merge, embed, modify,
redistribute, and sell modified and unmodified copies of the Font
Software, subject to the following conditions:

1) Neither the Font Software nor any of its individual components,
in Original or Modified Versions, may be sold by itself.

2) Original or Modified Versions of the Font Software may be bundled,
redistributed and/or sold with any software, provided that each copy
contains the above copyright notice and this license. These can be
included either as stand-alone text files, human-readable headers or
in the appropriate machine-readable metadata fields within text or
binary files as long as those fields can be easily viewed by the user.

3) No Modified Version of the Font Software may use the Reserved Font
Name(s) unless explicit written permission is granted by the corresponding
Copyright Holder. This restriction only applies to the primary font name as
presented to the users.

4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
Software shall not be used to promote, endorse or advertise any
Modified Version, except to acknowledge the contribution(s) of the
Copyright Holder(s) and the Author(s) or with their explicit written
permission.

5) The Font Software, modified or unmodified, in part or in whole,
must be distributed entirely under this license, and must not be
distributed under any other license. The requirement for fonts to
remain under this license does not apply to any document created
using the Font Software.

TERMINATION
This license becomes null and void if any of the above conditions are
not met.

DISCLAIMER
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
OTHER DEALINGS IN THE FONT SOFTWARE.

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

# Code: MIT License (https://opensource.org/licenses/MIT)

In the Font Awesome Free download, the MIT license applies to all non-font and
non-icon files.

Copyright 2024 Fonticons, Inc.

Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in the
Software without restriction, including without limitation the rights to use, copy,
modify, merge, publish, distribute, sublicense, and/or sell copies of the Software,
and to permit persons to whom the Software is furnished to do so, subject to the
following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

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

# Attribution

Attribution is required by MIT, SIL OFL, and CC BY licenses. Downloaded Font
Awesome Free files already contain embedded comments with sufficient
attribution, so you shouldn't need to do anything additional when using these
files normally.

We've kept attribution comments terse, so we ask that you do not actively work
to remove them from files, especially code. They're a great way for folks to
learn about Font Awesome.

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

# Brand Icons

All brand icons are trademarks of their respective owners. The use of these
trademarks does not indicate endorsement of the trademark holder by Font
Awesome, nor vice versa. **Please do not use brand logos for any purpose except
to represent the company, product, or service to which they refer.**
src\licenses\LICENSE.highlightjs.txt
Project URL: https://github.com/highlightjs/highlight.js/

BSD 3-Clause License

Copyright (c) 2006, Ivan Sagalaev.
All rights reserved.

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:

* Redistributions of source code must retain the above copyright notice, this
  list of conditions and the following disclaimer.

* Redistributions in binary form must reproduce the above copyright notice,
  this list of conditions and the following disclaimer in the documentation
  and/or other materials provided with the distribution.

* Neither the name of the copyright holder nor the names of its
  contributors may be used to endorse or promote products derived from
  this software without specific prior written permission.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
src\licenses\LICENSE.openapi-explorer.txt
Project URL: https://github.com/Authress-Engineering/openapi-explorer

                                 Apache License
                           Version 2.0, January 2004
                        http://www.apache.org/licenses/

   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION

   1. Definitions.

      "License" shall mean the terms and conditions for use, reproduction,
      and distribution as defined by Sections 1 through 9 of this document.

      "Licensor" shall mean the copyright owner or entity authorized by
      the copyright owner that is granting the License.

      "Legal Entity" shall mean the union of the acting entity and all
      other entities that control, are controlled by, or are under common
      control with that entity. For the purposes of this definition,
      "control" means (i) the power, direct or indirect, to cause the
      direction or management of such entity, whether by contract or
      otherwise, or (ii) ownership of fifty percent (50%) or more of the
      outstanding shares, or (iii) beneficial ownership of such entity.

      "You" (or "Your") shall mean an individual or Legal Entity
      exercising permissions granted by this License.

      "Source" form shall mean the preferred form for making modifications,
      including but not limited to software source code, documentation
      source, and configuration files.

      "Object" form shall mean any form resulting from mechanical
      transformation or translation of a Source form, including but
      not limited to compiled object code, generated documentation,
      and conversions to other media types.

      "Work" shall mean the work of authorship, whether in Source or
      Object form, made available under the License, as indicated by a
      copyright notice that is included in or attached to the work
      (an example is provided in the Appendix below).

      "Derivative Works" shall mean any work, whether in Source or Object
      form, that is based on (or derived from) the Work and for which the
      editorial revisions, annotations, elaborations, or other modifications
      represent, as a whole, an original work of authorship. For the purposes
      of this License, Derivative Works shall not include works that remain
      separable from, or merely link (or bind by name) to the interfaces of,
      the Work and Derivative Works thereof.

      "Contribution" shall mean any work of authorship, including
      the original version of the Work and any modifications or additions
      to that Work or Derivative Works thereof, that is intentionally
      submitted to Licensor for inclusion in the Work by the copyright owner
      or by an individual or Legal Entity authorized to submit on behalf of
      the copyright owner. For the purposes of this definition, "submitted"
      means any form of electronic, verbal, or written communication sent
      to the Licensor or its representatives, including but not limited to
      communication on electronic mailing lists, source code control systems,
      and issue tracking systems that are managed by, or on behalf of, the
      Licensor for the purpose of discussing and improving the Work, but
      excluding communication that is conspicuously marked or otherwise
      designated in writing by the copyright owner as "Not a Contribution."

      "Contributor" shall mean Licensor and any individual or Legal Entity
      on behalf of whom a Contribution has been received by Licensor and
      subsequently incorporated within the Work.

   2. Grant of Copyright License. Subject to the terms and conditions of
      this License, each Contributor hereby grants to You a perpetual,
      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
      copyright license to reproduce, prepare Derivative Works of,
      publicly display, publicly perform, sublicense, and distribute the
      Work and such Derivative Works in Source or Object form.

   3. Grant of Patent License. Subject to the terms and conditions of
      this License, each Contributor hereby grants to You a perpetual,
      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
      (except as stated in this section) patent license to make, have made,
      use, offer to sell, sell, import, and otherwise transfer the Work,
      where such license applies only to those patent claims licensable
      by such Contributor that are necessarily infringed by their
      Contribution(s) alone or by combination of their Contribution(s)
      with the Work to which such Contribution(s) was submitted. If You
      institute patent litigation against any entity (including a
      cross-claim or counterclaim in a lawsuit) alleging that the Work
      or a Contribution incorporated within the Work constitutes direct
      or contributory patent infringement, then any patent licenses
      granted to You under this License for that Work shall terminate
      as of the date such litigation is filed.

   4. Redistribution. You may reproduce and distribute copies of the
      Work or Derivative Works thereof in any medium, with or without
      modifications, and in Source or Object form, provided that You
      meet the following conditions:

      (a) You must give any other recipients of the Work or
          Derivative Works a copy of this License; and

      (b) You must cause any modified files to carry prominent notices
          stating that You changed the files; and

      (c) You must retain, in the Source form of any Derivative Works
          that You distribute, all copyright, patent, trademark, and
          attribution notices from the Source form of the Work,
          excluding those notices that do not pertain to any part of
          the Derivative Works; and

      (d) If the Work includes a "NOTICE" text file as part of its
          distribution, then any Derivative Works that You distribute must
          include a readable copy of the attribution notices contained
          within such NOTICE file, excluding those notices that do not
          pertain to any part of the Derivative Works, in at least one
          of the following places: within a NOTICE text file distributed
          as part of the Derivative Works; within the Source form or
          documentation, if provided along with the Derivative Works; or,
          within a display generated by the Derivative Works, if and
          wherever such third-party notices normally appear. The contents
          of the NOTICE file are for informational purposes only and
          do not modify the License. You may add Your own attribution
          notices within Derivative Works that You distribute, alongside
          or as an addendum to the NOTICE text from the Work, provided
          that such additional attribution notices cannot be construed
          as modifying the License.

      You may add Your own copyright statement to Your modifications and
      may provide additional or different license terms and conditions
      for use, reproduction, or distribution of Your modifications, or
      for any such Derivative Works as a whole, provided Your use,
      reproduction, and distribution of the Work otherwise complies with
      the conditions stated in this License.

   5. Submission of Contributions. Unless You explicitly state otherwise,
      any Contribution intentionally submitted for inclusion in the Work
      by You to the Licensor shall be under the terms and conditions of
      this License, without any additional terms or conditions.
      Notwithstanding the above, nothing herein shall supersede or modify
      the terms of any separate license agreement you may have executed
      with Licensor regarding such Contributions.

   6. Trademarks. This License does not grant permission to use the trade
      names, trademarks, service marks, or product names of the Licensor,
      except as required for reasonable and customary use in describing the
      origin of the Work and reproducing the content of the NOTICE file.

   7. Disclaimer of Warranty. Unless required by applicable law or
      agreed to in writing, Licensor provides the Work (and each
      Contributor provides its Contributions) on an "AS IS" BASIS,
      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
      implied, including, without limitation, any warranties or conditions
      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
      PARTICULAR PURPOSE. You are solely responsible for determining the
      appropriateness of using or redistributing the Work and assume any
      risks associated with Your exercise of permissions under this License.

   8. Limitation of Liability. In no event and under no legal theory,
      whether in tort (including negligence), contract, or otherwise,
      unless required by applicable law (such as deliberate and grossly
      negligent acts) or agreed to in writing, shall any Contributor be
      liable to You for damages, including any direct, indirect, special,
      incidental, or consequential damages of any character arising as a
      result of this License or out of the use or inability to use the
      Work (including but not limited to damages for loss of goodwill,
      work stoppage, computer failure or malfunction, or any and all
      other commercial damages or losses), even if such Contributor
      has been advised of the possibility of such damages.

   9. Accepting Warranty or Additional Liability. While redistributing
      the Work or Derivative Works thereof, You may choose to offer,
      and charge a fee for, acceptance of support, warranty, indemnity,
      or other liability obligations and/or rights consistent with this
      License. However, in accepting such obligations, You may act only
      on Your own behalf and on Your sole responsibility, not on behalf
      of any other Contributor, and only if You agree to indemnify,
      defend, and hold each Contributor harmless for any liability
      incurred by, or claims asserted against, such Contributor by reason
      of your accepting any such warranty or additional liability.

   END OF TERMS AND CONDITIONS

   APPENDIX: How to apply the Apache License to your work.

      To apply the Apache License to your work, attach the following
      boilerplate notice, with the fields enclosed by brackets "[]"
      replaced with your own identifying information. (Don't include
      the brackets!)  The text should be enclosed in the appropriate
      comment syntax for the file format. We also recommend that a
      file or class name and description of purpose be included on the
      same "printed page" as the copyright notice for easier
      identification within third-party archives.

   Copyright [yyyy] [name of copyright owner]

   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at

       http://www.apache.org/licenses/LICENSE-2.0

   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.
src\licenses\LICENSE.powershell-yaml.txt
Project URL: https://github.com/cloudbase/powershell-yaml

                                 Apache License
                           Version 2.0, January 2004
                        http://www.apache.org/licenses/

   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION

   1. Definitions.

      "License" shall mean the terms and conditions for use, reproduction,
      and distribution as defined by Sections 1 through 9 of this document.

      "Licensor" shall mean the copyright owner or entity authorized by
      the copyright owner that is granting the License.

      "Legal Entity" shall mean the union of the acting entity and all
      other entities that control, are controlled by, or are under common
      control with that entity. For the purposes of this definition,
      "control" means (i) the power, direct or indirect, to cause the
      direction or management of such entity, whether by contract or
      otherwise, or (ii) ownership of fifty percent (50%) or more of the
      outstanding shares, or (iii) beneficial ownership of such entity.

      "You" (or "Your") shall mean an individual or Legal Entity
      exercising permissions granted by this License.

      "Source" form shall mean the preferred form for making modifications,
      including but not limited to software source code, documentation
      source, and configuration files.

      "Object" form shall mean any form resulting from mechanical
      transformation or translation of a Source form, including but
      not limited to compiled object code, generated documentation,
      and conversions to other media types.

      "Work" shall mean the work of authorship, whether in Source or
      Object form, made available under the License, as indicated by a
      copyright notice that is included in or attached to the work
      (an example is provided in the Appendix below).

      "Derivative Works" shall mean any work, whether in Source or Object
      form, that is based on (or derived from) the Work and for which the
      editorial revisions, annotations, elaborations, or other modifications
      represent, as a whole, an original work of authorship. For the purposes
      of this License, Derivative Works shall not include works that remain
      separable from, or merely link (or bind by name) to the interfaces of,
      the Work and Derivative Works thereof.

      "Contribution" shall mean any work of authorship, including
      the original version of the Work and any modifications or additions
      to that Work or Derivative Works thereof, that is intentionally
      submitted to Licensor for inclusion in the Work by the copyright owner
      or by an individual or Legal Entity authorized to submit on behalf of
      the copyright owner. For the purposes of this definition, "submitted"
      means any form of electronic, verbal, or written communication sent
      to the Licensor or its representatives, including but not limited to
      communication on electronic mailing lists, source code control systems,
      and issue tracking systems that are managed by, or on behalf of, the
      Licensor for the purpose of discussing and improving the Work, but
      excluding communication that is conspicuously marked or otherwise
      designated in writing by the copyright owner as "Not a Contribution."

      "Contributor" shall mean Licensor and any individual or Legal Entity
      on behalf of whom a Contribution has been received by Licensor and
      subsequently incorporated within the Work.

   2. Grant of Copyright License. Subject to the terms and conditions of
      this License, each Contributor hereby grants to You a perpetual,
      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
      copyright license to reproduce, prepare Derivative Works of,
      publicly display, publicly perform, sublicense, and distribute the
      Work and such Derivative Works in Source or Object form.

   3. Grant of Patent License. Subject to the terms and conditions of
      this License, each Contributor hereby grants to You a perpetual,
      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
      (except as stated in this section) patent license to make, have made,
      use, offer to sell, sell, import, and otherwise transfer the Work,
      where such license applies only to those patent claims licensable
      by such Contributor that are necessarily infringed by their
      Contribution(s) alone or by combination of their Contribution(s)
      with the Work to which such Contribution(s) was submitted. If You
      institute patent litigation against any entity (including a
      cross-claim or counterclaim in a lawsuit) alleging that the Work
      or a Contribution incorporated within the Work constitutes direct
      or contributory patent infringement, then any patent licenses
      granted to You under this License for that Work shall terminate
      as of the date such litigation is filed.

   4. Redistribution. You may reproduce and distribute copies of the
      Work or Derivative Works thereof in any medium, with or without
      modifications, and in Source or Object form, provided that You
      meet the following conditions:

      (a) You must give any other recipients of the Work or
          Derivative Works a copy of this License; and

      (b) You must cause any modified files to carry prominent notices
          stating that You changed the files; and

      (c) You must retain, in the Source form of any Derivative Works
          that You distribute, all copyright, patent, trademark, and
          attribution notices from the Source form of the Work,
          excluding those notices that do not pertain to any part of
          the Derivative Works; and

      (d) If the Work includes a "NOTICE" text file as part of its
          distribution, then any Derivative Works that You distribute must
          include a readable copy of the attribution notices contained
          within such NOTICE file, excluding those notices that do not
          pertain to any part of the Derivative Works, in at least one
          of the following places: within a NOTICE text file distributed
          as part of the Derivative Works; within the Source form or
          documentation, if provided along with the Derivative Works; or,
          within a display generated by the Derivative Works, if and
          wherever such third-party notices normally appear. The contents
          of the NOTICE file are for informational purposes only and
          do not modify the License. You may add Your own attribution
          notices within Derivative Works that You distribute, alongside
          or as an addendum to the NOTICE text from the Work, provided
          that such additional attribution notices cannot be construed
          as modifying the License.

      You may add Your own copyright statement to Your modifications and
      may provide additional or different license terms and conditions
      for use, reproduction, or distribution of Your modifications, or
      for any such Derivative Works as a whole, provided Your use,
      reproduction, and distribution of the Work otherwise complies with
      the conditions stated in this License.

   5. Submission of Contributions. Unless You explicitly state otherwise,
      any Contribution intentionally submitted for inclusion in the Work
      by You to the Licensor shall be under the terms and conditions of
      this License, without any additional terms or conditions.
      Notwithstanding the above, nothing herein shall supersede or modify
      the terms of any separate license agreement you may have executed
      with Licensor regarding such Contributions.

   6. Trademarks. This License does not grant permission to use the trade
      names, trademarks, service marks, or product names of the Licensor,
      except as required for reasonable and customary use in describing the
      origin of the Work and reproducing the content of the NOTICE file.

   7. Disclaimer of Warranty. Unless required by applicable law or
      agreed to in writing, Licensor provides the Work (and each
      Contributor provides its Contributions) on an "AS IS" BASIS,
      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
      implied, including, without limitation, any warranties or conditions
      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
      PARTICULAR PURPOSE. You are solely responsible for determining the
      appropriateness of using or redistributing the Work and assume any
      risks associated with Your exercise of permissions under this License.

   8. Limitation of Liability. In no event and under no legal theory,
      whether in tort (including negligence), contract, or otherwise,
      unless required by applicable law (such as deliberate and grossly
      negligent acts) or agreed to in writing, shall any Contributor be
      liable to You for damages, including any direct, indirect, special,
      incidental, or consequential damages of any character arising as a
      result of this License or out of the use or inability to use the
      Work (including but not limited to damages for loss of goodwill,
      work stoppage, computer failure or malfunction, or any and all
      other commercial damages or losses), even if such Contributor
      has been advised of the possibility of such damages.

   9. Accepting Warranty or Additional Liability. While redistributing
      the Work or Derivative Works thereof, You may choose to offer,
      and charge a fee for, acceptance of support, warranty, indemnity,
      or other liability obligations and/or rights consistent with this
      License. However, in accepting such obligations, You may act only
      on Your own behalf and on Your sole responsibility, not on behalf
      of any other Contributor, and only if You agree to indemnify,
      defend, and hold each Contributor harmless for any liability
      incurred by, or claims asserted against, such Contributor by reason
      of your accepting any such warranty or additional liability.

   END OF TERMS AND CONDITIONS

   APPENDIX: How to apply the Apache License to your work.

      To apply the Apache License to your work, attach the following
      boilerplate notice, with the fields enclosed by brackets "{}"
      replaced with your own identifying information. (Don't include
      the brackets!)  The text should be enclosed in the appropriate
      comment syntax for the file format. We also recommend that a
      file or class name and description of purpose be included on the
      same "printed page" as the copyright notice for easier
      identification within third-party archives.

   Copyright 2016-2023 Cloudbase Solutions SRL

   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at

       http://www.apache.org/licenses/LICENSE-2.0

   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.
src\licenses\LICENSE.PSYaml.txt
Project URL: https://github.com/Phil-Factor/PSYaml

The MIT License (MIT)

Copyright (c) 2016 Jakku Labs

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
src\licenses\LICENSE.RapiDoc.txt
Project URL: https://github.com/rapi-doc/RapiDoc

MIT License

Copyright (c) 2022 Mrinmoy Majumdar

Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:

The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
src\licenses\LICENSE.RapiPdf.txt
Project URL: https://github.com/mrin9/RapiPdf

MIT License

Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:

The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
src\licenses\LICENSE.redoc.txt
Project URL: https://github.com/Redocly/redoc

The MIT License (MIT)

Copyright (c) 2015-present, Rebilly, Inc.

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
src\licenses\LICENSE.SecretManagement.txt
Project URL: https://github.com/PowerShell/SecretManagement

Copyright (c) Microsoft Corporation.

MIT License

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
src\licenses\LICENSE.stoplight.txt
Project URL: https://github.com/stoplightio/elements

                                 Apache License
                           Version 2.0, January 2004
                        http://www.apache.org/licenses/

   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION

   1. Definitions.

      "License" shall mean the terms and conditions for use, reproduction,
      and distribution as defined by Sections 1 through 9 of this document.

      "Licensor" shall mean the copyright owner or entity authorized by
      the copyright owner that is granting the License.

      "Legal Entity" shall mean the union of the acting entity and all
      other entities that control, are controlled by, or are under common
      control with that entity. For the purposes of this definition,
      "control" means (i) the power, direct or indirect, to cause the
      direction or management of such entity, whether by contract or
      otherwise, or (ii) ownership of fifty percent (50%) or more of the
      outstanding shares, or (iii) beneficial ownership of such entity.

      "You" (or "Your") shall mean an individual or Legal Entity
      exercising permissions granted by this License.

      "Source" form shall mean the preferred form for making modifications,
      including but not limited to software source code, documentation
      source, and configuration files.

      "Object" form shall mean any form resulting from mechanical
      transformation or translation of a Source form, including but
      not limited to compiled object code, generated documentation,
      and conversions to other media types.

      "Work" shall mean the work of authorship, whether in Source or
      Object form, made available under the License, as indicated by a
      copyright notice that is included in or attached to the work
      (an example is provided in the Appendix below).

      "Derivative Works" shall mean any work, whether in Source or Object
      form, that is based on (or derived from) the Work and for which the
      editorial revisions, annotations, elaborations, or other modifications
      represent, as a whole, an original work of authorship. For the purposes
      of this License, Derivative Works shall not include works that remain
      separable from, or merely link (or bind by name) to the interfaces of,
      the Work and Derivative Works thereof.

      "Contribution" shall mean any work of authorship, including
      the original version of the Work and any modifications or additions
      to that Work or Derivative Works thereof, that is intentionally
      submitted to Licensor for inclusion in the Work by the copyright owner
      or by an individual or Legal Entity authorized to submit on behalf of
      the copyright owner. For the purposes of this definition, "submitted"
      means any form of electronic, verbal, or written communication sent
      to the Licensor or its representatives, including but not limited to
      communication on electronic mailing lists, source code control systems,
      and issue tracking systems that are managed by, or on behalf of, the
      Licensor for the purpose of discussing and improving the Work, but
      excluding communication that is conspicuously marked or otherwise
      designated in writing by the copyright owner as "Not a Contribution."

      "Contributor" shall mean Licensor and any individual or Legal Entity
      on behalf of whom a Contribution has been received by Licensor and
      subsequently incorporated within the Work.

   2. Grant of Copyright License. Subject to the terms and conditions of
      this License, each Contributor hereby grants to You a perpetual,
      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
      copyright license to reproduce, prepare Derivative Works of,
      publicly display, publicly perform, sublicense, and distribute the
      Work and such Derivative Works in Source or Object form.

   3. Grant of Patent License. Subject to the terms and conditions of
      this License, each Contributor hereby grants to You a perpetual,
      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
      (except as stated in this section) patent license to make, have made,
      use, offer to sell, sell, import, and otherwise transfer the Work,
      where such license applies only to those patent claims licensable
      by such Contributor that are necessarily infringed by their
      Contribution(s) alone or by combination of their Contribution(s)
      with the Work to which such Contribution(s) was submitted. If You
      institute patent litigation against any entity (including a
      cross-claim or counterclaim in a lawsuit) alleging that the Work
      or a Contribution incorporated within the Work constitutes direct
      or contributory patent infringement, then any patent licenses
      granted to You under this License for that Work shall terminate
      as of the date such litigation is filed.

   4. Redistribution. You may reproduce and distribute copies of the
      Work or Derivative Works thereof in any medium, with or without
      modifications, and in Source or Object form, provided that You
      meet the following conditions:

      (a) You must give any other recipients of the Work or
          Derivative Works a copy of this License; and

      (b) You must cause any modified files to carry prominent notices
          stating that You changed the files; and

      (c) You must retain, in the Source form of any Derivative Works
          that You distribute, all copyright, patent, trademark, and
          attribution notices from the Source form of the Work,
          excluding those notices that do not pertain to any part of
          the Derivative Works; and

      (d) If the Work includes a "NOTICE" text file as part of its
          distribution, then any Derivative Works that You distribute must
          include a readable copy of the attribution notices contained
          within such NOTICE file, excluding those notices that do not
          pertain to any part of the Derivative Works, in at least one
          of the following places: within a NOTICE text file distributed
          as part of the Derivative Works; within the Source form or
          documentation, if provided along with the Derivative Works; or,
          within a display generated by the Derivative Works, if and
          wherever such third-party notices normally appear. The contents
          of the NOTICE file are for informational purposes only and
          do not modify the License. You may add Your own attribution
          notices within Derivative Works that You distribute, alongside
          or as an addendum to the NOTICE text from the Work, provided
          that such additional attribution notices cannot be construed
          as modifying the License.

      You may add Your own copyright statement to Your modifications and
      may provide additional or different license terms and conditions
      for use, reproduction, or distribution of Your modifications, or
      for any such Derivative Works as a whole, provided Your use,
      reproduction, and distribution of the Work otherwise complies with
      the conditions stated in this License.

   5. Submission of Contributions. Unless You explicitly state otherwise,
      any Contribution intentionally submitted for inclusion in the Work
      by You to the Licensor shall be under the terms and conditions of
      this License, without any additional terms or conditions.
      Notwithstanding the above, nothing herein shall supersede or modify
      the terms of any separate license agreement you may have executed
      with Licensor regarding such Contributions.

   6. Trademarks. This License does not grant permission to use the trade
      names, trademarks, service marks, or product names of the Licensor,
      except as required for reasonable and customary use in describing the
      origin of the Work and reproducing the content of the NOTICE file.

   7. Disclaimer of Warranty. Unless required by applicable law or
      agreed to in writing, Licensor provides the Work (and each
      Contributor provides its Contributions) on an "AS IS" BASIS,
      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
      implied, including, without limitation, any warranties or conditions
      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
      PARTICULAR PURPOSE. You are solely responsible for determining the
      appropriateness of using or redistributing the Work and assume any
      risks associated with Your exercise of permissions under this License.

   8. Limitation of Liability. In no event and under no legal theory,
      whether in tort (including negligence), contract, or otherwise,
      unless required by applicable law (such as deliberate and grossly
      negligent acts) or agreed to in writing, shall any Contributor be
      liable to You for damages, including any direct, indirect, special,
      incidental, or consequential damages of any character arising as a
      result of this License or out of the use or inability to use the
      Work (including but not limited to damages for loss of goodwill,
      work stoppage, computer failure or malfunction, or any and all
      other commercial damages or losses), even if such Contributor
      has been advised of the possibility of such damages.

   9. Accepting Warranty or Additional Liability. While redistributing
      the Work or Derivative Works thereof, You may choose to offer,
      and charge a fee for, acceptance of support, warranty, indemnity,
      or other liability obligations and/or rights consistent with this
      License. However, in accepting such obligations, You may act only
      on Your own behalf and on Your sole responsibility, not on behalf
      of any other Contributor, and only if You agree to indemnify,
      defend, and hold each Contributor harmless for any liability
      incurred by, or claims asserted against, such Contributor by reason
      of your accepting any such warranty or additional liability.

   END OF TERMS AND CONDITIONS

   Copyright 2018 Stoplight, Inc.

   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at

       http://www.apache.org/licenses/LICENSE-2.0

   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.
src\licenses\LICENSE.swagger-editor.txt
Project URL: https://github.com/swagger-api/swagger-editor


                                 Apache License
                           Version 2.0, January 2004
                        http://www.apache.org/licenses/

   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION

   1. Definitions.

      "License" shall mean the terms and conditions for use, reproduction,
      and distribution as defined by Sections 1 through 9 of this document.

      "Licensor" shall mean the copyright owner or entity authorized by
      the copyright owner that is granting the License.

      "Legal Entity" shall mean the union of the acting entity and all
      other entities that control, are controlled by, or are under common
      control with that entity. For the purposes of this definition,
      "control" means (i) the power, direct or indirect, to cause the
      direction or management of such entity, whether by contract or
      otherwise, or (ii) ownership of fifty percent (50%) or more of the
      outstanding shares, or (iii) beneficial ownership of such entity.

      "You" (or "Your") shall mean an individual or Legal Entity
      exercising permissions granted by this License.

      "Source" form shall mean the preferred form for making modifications,
      including but not limited to software source code, documentation
      source, and configuration files.

      "Object" form shall mean any form resulting from mechanical
      transformation or translation of a Source form, including but
      not limited to compiled object code, generated documentation,
      and conversions to other media types.

      "Work" shall mean the work of authorship, whether in Source or
      Object form, made available under the License, as indicated by a
      copyright notice that is included in or attached to the work
      (an example is provided in the Appendix below).

      "Derivative Works" shall mean any work, whether in Source or Object
      form, that is based on (or derived from) the Work and for which the
      editorial revisions, annotations, elaborations, or other modifications
      represent, as a whole, an original work of authorship. For the purposes
      of this License, Derivative Works shall not include works that remain
      separable from, or merely link (or bind by name) to the interfaces of,
      the Work and Derivative Works thereof.

      "Contribution" shall mean any work of authorship, including
      the original version of the Work and any modifications or additions
      to that Work or Derivative Works thereof, that is intentionally
      submitted to Licensor for inclusion in the Work by the copyright owner
      or by an individual or Legal Entity authorized to submit on behalf of
      the copyright owner. For the purposes of this definition, "submitted"
      means any form of electronic, verbal, or written communication sent
      to the Licensor or its representatives, including but not limited to
      communication on electronic mailing lists, source code control systems,
      and issue tracking systems that are managed by, or on behalf of, the
      Licensor for the purpose of discussing and improving the Work, but
      excluding communication that is conspicuously marked or otherwise
      designated in writing by the copyright owner as "Not a Contribution."

      "Contributor" shall mean Licensor and any individual or Legal Entity
      on behalf of whom a Contribution has been received by Licensor and
      subsequently incorporated within the Work.

   2. Grant of Copyright License. Subject to the terms and conditions of
      this License, each Contributor hereby grants to You a perpetual,
      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
      copyright license to reproduce, prepare Derivative Works of,
      publicly display, publicly perform, sublicense, and distribute the
      Work and such Derivative Works in Source or Object form.

   3. Grant of Patent License. Subject to the terms and conditions of
      this License, each Contributor hereby grants to You a perpetual,
      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
      (except as stated in this section) patent license to make, have made,
      use, offer to sell, sell, import, and otherwise transfer the Work,
      where such license applies only to those patent claims licensable
      by such Contributor that are necessarily infringed by their
      Contribution(s) alone or by combination of their Contribution(s)
      with the Work to which such Contribution(s) was submitted. If You
      institute patent litigation against any entity (including a
      cross-claim or counterclaim in a lawsuit) alleging that the Work
      or a Contribution incorporated within the Work constitutes direct
      or contributory patent infringement, then any patent licenses
      granted to You under this License for that Work shall terminate
      as of the date such litigation is filed.

   4. Redistribution. You may reproduce and distribute copies of the
      Work or Derivative Works thereof in any medium, with or without
      modifications, and in Source or Object form, provided that You
      meet the following conditions:

      (a) You must give any other recipients of the Work or
          Derivative Works a copy of this License; and

      (b) You must cause any modified files to carry prominent notices
          stating that You changed the files; and

      (c) You must retain, in the Source form of any Derivative Works
          that You distribute, all copyright, patent, trademark, and
          attribution notices from the Source form of the Work,
          excluding those notices that do not pertain to any part of
          the Derivative Works; and

      (d) If the Work includes a "NOTICE" text file as part of its
          distribution, then any Derivative Works that You distribute must
          include a readable copy of the attribution notices contained
          within such NOTICE file, excluding those notices that do not
          pertain to any part of the Derivative Works, in at least one
          of the following places: within a NOTICE text file distributed
          as part of the Derivative Works; within the Source form or
          documentation, if provided along with the Derivative Works; or,
          within a display generated by the Derivative Works, if and
          wherever such third-party notices normally appear. The contents
          of the NOTICE file are for informational purposes only and
          do not modify the License. You may add Your own attribution
          notices within Derivative Works that You distribute, alongside
          or as an addendum to the NOTICE text from the Work, provided
          that such additional attribution notices cannot be construed
          as modifying the License.

      You may add Your own copyright statement to Your modifications and
      may provide additional or different license terms and conditions
      for use, reproduction, or distribution of Your modifications, or
      for any such Derivative Works as a whole, provided Your use,
      reproduction, and distribution of the Work otherwise complies with
      the conditions stated in this License.

   5. Submission of Contributions. Unless You explicitly state otherwise,
      any Contribution intentionally submitted for inclusion in the Work
      by You to the Licensor shall be under the terms and conditions of
      this License, without any additional terms or conditions.
      Notwithstanding the above, nothing herein shall supersede or modify
      the terms of any separate license agreement you may have executed
      with Licensor regarding such Contributions.

   6. Trademarks. This License does not grant permission to use the trade
      names, trademarks, service marks, or product names of the Licensor,
      except as required for reasonable and customary use in describing the
      origin of the Work and reproducing the content of the NOTICE file.

   7. Disclaimer of Warranty. Unless required by applicable law or
      agreed to in writing, Licensor provides the Work (and each
      Contributor provides its Contributions) on an "AS IS" BASIS,
      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
      implied, including, without limitation, any warranties or conditions
      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
      PARTICULAR PURPOSE. You are solely responsible for determining the
      appropriateness of using or redistributing the Work and assume any
      risks associated with Your exercise of permissions under this License.

   8. Limitation of Liability. In no event and under no legal theory,
      whether in tort (including negligence), contract, or otherwise,
      unless required by applicable law (such as deliberate and grossly
      negligent acts) or agreed to in writing, shall any Contributor be
      liable to You for damages, including any direct, indirect, special,
      incidental, or consequential damages of any character arising as a
      result of this License or out of the use or inability to use the
      Work (including but not limited to damages for loss of goodwill,
      work stoppage, computer failure or malfunction, or any and all
      other commercial damages or losses), even if such Contributor
      has been advised of the possibility of such damages.

   9. Accepting Warranty or Additional Liability. While redistributing
      the Work or Derivative Works thereof, You may choose to offer,
      and charge a fee for, acceptance of support, warranty, indemnity,
      or other liability obligations and/or rights consistent with this
      License. However, in accepting such obligations, You may act only
      on Your own behalf and on Your sole responsibility, not on behalf
      of any other Contributor, and only if You agree to indemnify,
      defend, and hold each Contributor harmless for any liability
      incurred by, or claims asserted against, such Contributor by reason
      of your accepting any such warranty or additional liability.

   END OF TERMS AND CONDITIONS

   APPENDIX: How to apply the Apache License to your work.

      To apply the Apache License to your work, attach the following
      boilerplate notice, with the fields enclosed by brackets "[]"
      replaced with your own identifying information. (Don't include
      the brackets!)  The text should be enclosed in the appropriate
      comment syntax for the file format. We also recommend that a
      file or class name and description of purpose be included on the
      same "printed page" as the copyright notice for easier
      identification within third-party archives.

   Copyright [yyyy] [name of copyright owner]

   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at

       http://www.apache.org/licenses/LICENSE-2.0

   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.
src\licenses\LICENSE.swagger-ui.txt
Project URL: https://github.com/swagger-api/swagger-ui


                                 Apache License
                           Version 2.0, January 2004
                        http://www.apache.org/licenses/

   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION

   1. Definitions.

      "License" shall mean the terms and conditions for use, reproduction,
      and distribution as defined by Sections 1 through 9 of this document.

      "Licensor" shall mean the copyright owner or entity authorized by
      the copyright owner that is granting the License.

      "Legal Entity" shall mean the union of the acting entity and all
      other entities that control, are controlled by, or are under common
      control with that entity. For the purposes of this definition,
      "control" means (i) the power, direct or indirect, to cause the
      direction or management of such entity, whether by contract or
      otherwise, or (ii) ownership of fifty percent (50%) or more of the
      outstanding shares, or (iii) beneficial ownership of such entity.

      "You" (or "Your") shall mean an individual or Legal Entity
      exercising permissions granted by this License.

      "Source" form shall mean the preferred form for making modifications,
      including but not limited to software source code, documentation
      source, and configuration files.

      "Object" form shall mean any form resulting from mechanical
      transformation or translation of a Source form, including but
      not limited to compiled object code, generated documentation,
      and conversions to other media types.

      "Work" shall mean the work of authorship, whether in Source or
      Object form, made available under the License, as indicated by a
      copyright notice that is included in or attached to the work
      (an example is provided in the Appendix below).

      "Derivative Works" shall mean any work, whether in Source or Object
      form, that is based on (or derived from) the Work and for which the
      editorial revisions, annotations, elaborations, or other modifications
      represent, as a whole, an original work of authorship. For the purposes
      of this License, Derivative Works shall not include works that remain
      separable from, or merely link (or bind by name) to the interfaces of,
      the Work and Derivative Works thereof.

      "Contribution" shall mean any work of authorship, including
      the original version of the Work and any modifications or additions
      to that Work or Derivative Works thereof, that is intentionally
      submitted to Licensor for inclusion in the Work by the copyright owner
      or by an individual or Legal Entity authorized to submit on behalf of
      the copyright owner. For the purposes of this definition, "submitted"
      means any form of electronic, verbal, or written communication sent
      to the Licensor or its representatives, including but not limited to
      communication on electronic mailing lists, source code control systems,
      and issue tracking systems that are managed by, or on behalf of, the
      Licensor for the purpose of discussing and improving the Work, but
      excluding communication that is conspicuously marked or otherwise
      designated in writing by the copyright owner as "Not a Contribution."

      "Contributor" shall mean Licensor and any individual or Legal Entity
      on behalf of whom a Contribution has been received by Licensor and
      subsequently incorporated within the Work.

   2. Grant of Copyright License. Subject to the terms and conditions of
      this License, each Contributor hereby grants to You a perpetual,
      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
      copyright license to reproduce, prepare Derivative Works of,
      publicly display, publicly perform, sublicense, and distribute the
      Work and such Derivative Works in Source or Object form.

   3. Grant of Patent License. Subject to the terms and conditions of
      this License, each Contributor hereby grants to You a perpetual,
      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
      (except as stated in this section) patent license to make, have made,
      use, offer to sell, sell, import, and otherwise transfer the Work,
      where such license applies only to those patent claims licensable
      by such Contributor that are necessarily infringed by their
      Contribution(s) alone or by combination of their Contribution(s)
      with the Work to which such Contribution(s) was submitted. If You
      institute patent litigation against any entity (including a
      cross-claim or counterclaim in a lawsuit) alleging that the Work
      or a Contribution incorporated within the Work constitutes direct
      or contributory patent infringement, then any patent licenses
      granted to You under this License for that Work shall terminate
      as of the date such litigation is filed.

   4. Redistribution. You may reproduce and distribute copies of the
      Work or Derivative Works thereof in any medium, with or without
      modifications, and in Source or Object form, provided that You
      meet the following conditions:

      (a) You must give any other recipients of the Work or
          Derivative Works a copy of this License; and

      (b) You must cause any modified files to carry prominent notices
          stating that You changed the files; and

      (c) You must retain, in the Source form of any Derivative Works
          that You distribute, all copyright, patent, trademark, and
          attribution notices from the Source form of the Work,
          excluding those notices that do not pertain to any part of
          the Derivative Works; and

      (d) If the Work includes a "NOTICE" text file as part of its
          distribution, then any Derivative Works that You distribute must
          include a readable copy of the attribution notices contained
          within such NOTICE file, excluding those notices that do not
          pertain to any part of the Derivative Works, in at least one
          of the following places: within a NOTICE text file distributed
          as part of the Derivative Works; within the Source form or
          documentation, if provided along with the Derivative Works; or,
          within a display generated by the Derivative Works, if and
          wherever such third-party notices normally appear. The contents
          of the NOTICE file are for informational purposes only and
          do not modify the License. You may add Your own attribution
          notices within Derivative Works that You distribute, alongside
          or as an addendum to the NOTICE text from the Work, provided
          that such additional attribution notices cannot be construed
          as modifying the License.

      You may add Your own copyright statement to Your modifications and
      may provide additional or different license terms and conditions
      for use, reproduction, or distribution of Your modifications, or
      for any such Derivative Works as a whole, provided Your use,
      reproduction, and distribution of the Work otherwise complies with
      the conditions stated in this License.

   5. Submission of Contributions. Unless You explicitly state otherwise,
      any Contribution intentionally submitted for inclusion in the Work
      by You to the Licensor shall be under the terms and conditions of
      this License, without any additional terms or conditions.
      Notwithstanding the above, nothing herein shall supersede or modify
      the terms of any separate license agreement you may have executed
      with Licensor regarding such Contributions.

   6. Trademarks. This License does not grant permission to use the trade
      names, trademarks, service marks, or product names of the Licensor,
      except as required for reasonable and customary use in describing the
      origin of the Work and reproducing the content of the NOTICE file.

   7. Disclaimer of Warranty. Unless required by applicable law or
      agreed to in writing, Licensor provides the Work (and each
      Contributor provides its Contributions) on an "AS IS" BASIS,
      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
      implied, including, without limitation, any warranties or conditions
      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
      PARTICULAR PURPOSE. You are solely responsible for determining the
      appropriateness of using or redistributing the Work and assume any
      risks associated with Your exercise of permissions under this License.

   8. Limitation of Liability. In no event and under no legal theory,
      whether in tort (including negligence), contract, or otherwise,
      unless required by applicable law (such as deliberate and grossly
      negligent acts) or agreed to in writing, shall any Contributor be
      liable to You for damages, including any direct, indirect, special,
      incidental, or consequential damages of any character arising as a
      result of this License or out of the use or inability to use the
      Work (including but not limited to damages for loss of goodwill,
      work stoppage, computer failure or malfunction, or any and all
      other commercial damages or losses), even if such Contributor
      has been advised of the possibility of such damages.

   9. Accepting Warranty or Additional Liability. While redistributing
      the Work or Derivative Works thereof, You may choose to offer,
      and charge a fee for, acceptance of support, warranty, indemnity,
      or other liability obligations and/or rights consistent with this
      License. However, in accepting such obligations, You may act only
      on Your own behalf and on Your sole responsibility, not on behalf
      of any other Contributor, and only if You agree to indemnify,
      defend, and hold each Contributor harmless for any liability
      incurred by, or claims asserted against, such Contributor by reason
      of your accepting any such warranty or additional liability.

   END OF TERMS AND CONDITIONS

   APPENDIX: How to apply the Apache License to your work.

      To apply the Apache License to your work, attach the following
      boilerplate notice, with the fields enclosed by brackets "[]"
      replaced with your own identifying information. (Don't include
      the brackets!)  The text should be enclosed in the appropriate
      comment syntax for the file format. We also recommend that a
      file or class name and description of purpose be included on the
      same "printed page" as the copyright notice for easier
      identification within third-party archives.

   Copyright [yyyy] [name of copyright owner]

   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at

       http://www.apache.org/licenses/LICENSE-2.0

   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.
src\Locales\ar\Pode.psd1
@{
    schemaValidationRequiresPowerShell610ExceptionMessage             = 'يتطلب التحقق من صحة المخطط إصدار PowerShell 6.1.0 أو أحدث.'
    customAccessPathOrScriptBlockRequiredExceptionMessage             = 'مطلوب مسار أو ScriptBlock للحصول على قيم الوصول المخصصة.'
    operationIdMustBeUniqueForArrayExceptionMessage                   = 'يجب أن يكون OperationID: {0} فريدًا ولا يمكن تطبيقه على مصفوفة.'
    endpointNotDefinedForRedirectingExceptionMessage                  = "لم يتم تعريف نقطة نهاية باسم '{0}' لإعادة التوجيه."
    filesHaveChangedMessage                                           = 'تم تغيير الملفات التالية:'
    iisAspnetcoreTokenMissingExceptionMessage                         = 'IIS ASPNETCORE_TOKEN مفقود.'
    minValueGreaterThanMaxExceptionMessage                            = 'يجب ألا تكون القيمة الدنيا {0} أكبر من القيمة القصوى.'
    noLogicPassedForRouteExceptionMessage                             = 'لم يتم تمرير منطق للمسار: {0}'
    scriptPathDoesNotExistExceptionMessage                            = 'مسار البرنامج النصي غير موجود: {0}'
    mutexAlreadyExistsExceptionMessage                                = 'يوجد بالفعل Mutex بالاسم التالي: {0}'
    listeningOnEndpointsMessage                                       = 'الاستماع على {0} نقطة(نقاط) النهاية التالية [{1} خيط(خيوط)]:'
    unsupportedFunctionInServerlessContextExceptionMessage            = 'الدالة {0} غير مدعومة في سياق بدون خادم.'
    expectedNoJwtSignatureSuppliedExceptionMessage                    = 'لم يكن من المتوقع توفير توقيع JWT.'
    secretAlreadyMountedExceptionMessage                              = "تم تثبيت سر بالاسم '{0}' بالفعل."
    failedToAcquireLockExceptionMessage                               = 'فشل في الحصول على قفل على الكائن.'
    noPathSuppliedForStaticRouteExceptionMessage                      = '[{0}]: لم يتم توفير مسار للمسار الثابت.'
    invalidHostnameSuppliedExceptionMessage                           = 'اسم المضيف المقدم غير صالح: {0}'
    authMethodAlreadyDefinedExceptionMessage                          = 'طريقة المصادقة محددة بالفعل: {0}'
    csrfCookieRequiresSecretExceptionMessage                          = "عند استخدام ملفات تعريف الارتباط لـ CSRF، يكون السر مطلوبًا. يمكنك تقديم سر أو تعيين السر العالمي لملف تعريف الارتباط - (Set-PodeCookieSecret '<value>' -Global)"
    nonEmptyScriptBlockRequiredForPageRouteExceptionMessage           = 'مطلوب ScriptBlock غير فارغ لإنشاء مسار الصفحة.'
    noPropertiesMutuallyExclusiveExceptionMessage                     = "المعامل 'NoProperties' يتعارض مع 'Properties' و 'MinProperties' و 'MaxProperties'."
    incompatiblePodeDllExceptionMessage                               = 'يتم تحميل إصدار غير متوافق من Pode.DLL {0}. الإصدار {1} مطلوب. افتح جلسة Powershell/pwsh جديدة وأعد المحاولة.'
    accessMethodDoesNotExistExceptionMessage                          = 'طريقة الوصول غير موجودة: {0}.'
    scheduleAlreadyDefinedExceptionMessage                            = '[الجدول الزمني] {0}: الجدول الزمني معرف بالفعل.'
    secondsValueCannotBeZeroOrLessExceptionMessage                    = 'لا يمكن أن تكون قيمة الثواني 0 أو أقل لـ {0}'
    pathToLoadNotFoundExceptionMessage                                = 'لم يتم العثور على المسار لتحميل {0}: {1}'
    failedToImportModuleExceptionMessage                              = 'فشل في استيراد الوحدة: {0}'
    endpointNotExistExceptionMessage                                  = "نقطة النهاية مع البروتوكول '{0}' والعنوان '{1}' أو العنوان المحلي '{2}' غير موجودة."
    terminatingMessage                                                = 'إنهاء...'
    noCommandsSuppliedToConvertToRoutesExceptionMessage               = 'لم يتم توفير أي أوامر لتحويلها إلى طرق.'
    invalidTaskTypeExceptionMessage                                   = 'نوع المهمة غير صالح، المتوقع إما [System.Threading.Tasks.Task] أو [hashtable].'
    alreadyConnectedToWebSocketExceptionMessage                       = "متصل بالفعل بـ WebSocket بالاسم '{0}'"
    crlfMessageEndCheckOnlySupportedOnTcpEndpointsExceptionMessage    = 'فحص نهاية الرسالة CRLF مدعوم فقط على نقاط النهاية TCP.'
    testPodeOAComponentSchemaNeedToBeEnabledExceptionMessage          = "يجب تمكين 'Test-PodeOAComponentSchema' باستخدام 'Enable-PodeOpenApi -EnableSchemaValidation'"
    adModuleNotInstalledExceptionMessage                              = 'وحدة Active Directory غير مثبتة.'
    cronExpressionInvalidExceptionMessage                             = 'يجب أن تتكون تعبير Cron من 5 أجزاء فقط: {0}'
    noSessionToSetOnResponseExceptionMessage                          = 'لا توجد جلسة متاحة لتعيينها على الاستجابة.'
    valueOutOfRangeExceptionMessage                                   = "القيمة '{0}' لـ {1} غير صالحة، يجب أن تكون بين {2} و {3}"
    loggingMethodAlreadyDefinedExceptionMessage                       = 'تم تعريف طريقة التسجيل بالفعل: {0}'
    noSecretForHmac256ExceptionMessage                                = 'لم يتم تقديم أي سر لتجزئة HMAC256.'
    eolPowerShellWarningMessage                                       = '[تحذير] لم يتم اختبار Pode {0} على PowerShell {1}، حيث أنه نهاية العمر.'
    runspacePoolFailedToLoadExceptionMessage                          = 'فشل تحميل RunspacePool لـ {0}.'
    noEventRegisteredExceptionMessage                                 = 'لا يوجد حدث {0} مسجل: {1}'
    scheduleCannotHaveNegativeLimitExceptionMessage                   = '[الجدول الزمني] {0}: لا يمكن أن يكون له حد سلبي.'
    openApiRequestStyleInvalidForParameterExceptionMessage            = 'لا يمكن أن يكون نمط الطلب OpenApi {0} لمعلمة {1}.'
    openApiDocumentNotCompliantExceptionMessage                       = 'مستند OpenAPI غير متوافق.'
    taskDoesNotExistExceptionMessage                                  = "المهمة '{0}' غير موجودة."
    scopedVariableNotFoundExceptionMessage                            = 'لم يتم العثور على المتغير المحدد: {0}'
    sessionsRequiredForCsrfExceptionMessage                           = 'الجلسات مطلوبة لاستخدام CSRF إلا إذا كنت ترغب في استخدام ملفات تعريف الارتباط.'
    nonEmptyScriptBlockRequiredForLoggingMethodExceptionMessage       = 'مطلوب ScriptBlock غير فارغ لطريقة التسجيل.'
    credentialsPassedWildcardForHeadersLiteralExceptionMessage        = 'عند تمرير بيانات الاعتماد، سيتم اعتبار العلامة * للعنوان كـ سلسلة نصية حرفية وليس كعلامة.'
    podeNotInitializedExceptionMessage                                = 'لم يتم تهيئة Pode.'
    multipleEndpointsForGuiMessage                                    = 'تم تعريف نقاط نهاية متعددة، سيتم استخدام الأولى فقط للواجهة الرسومية.'
    operationIdMustBeUniqueExceptionMessage                           = 'يجب أن يكون OperationID: {0} فريدًا.'
    invalidJsonJwtExceptionMessage                                    = 'تم العثور على قيمة JSON غير صالحة في JWT'
    noAlgorithmInJwtHeaderExceptionMessage                            = 'لم يتم توفير أي خوارزمية في رأس JWT.'
    openApiVersionPropertyMandatoryExceptionMessage                   = 'خاصية إصدار OpenApi إلزامية.'
    limitValueCannotBeZeroOrLessExceptionMessage                      = 'لا يمكن أن تكون القيمة الحدية 0 أو أقل لـ {0}'
    timerDoesNotExistExceptionMessage                                 = "المؤقت '{0}' غير موجود."
    openApiGenerationDocumentErrorMessage                             = 'خطأ في مستند إنشاء OpenAPI:'
    routeAlreadyContainsCustomAccessExceptionMessage                  = "المسار '[{0}] {1}' يحتوي بالفعل على وصول مخصص باسم '{2}'"
    maximumConcurrentWebSocketThreadsLessThanMinimumExceptionMessage  = 'لا يمكن أن يكون الحد الأقصى لمؤشرات ترابط WebSocket المتزامنة أقل من الحد الأدنى {0}، ولكن تم الحصول عليه: {1}'
    middlewareAlreadyDefinedExceptionMessage                          = '[Middleware] {0}: تم تعريف الوسيط بالفعل.'
    invalidAtomCharacterExceptionMessage                              = 'حرف الذرة غير صالح: {0}'
    invalidCronAtomFormatExceptionMessage                             = 'تم العثور على تنسيق cron غير صالح: {0}'
    cacheStorageNotFoundForRetrieveExceptionMessage                   = "لم يتم العثور على مخزن ذاكرة التخزين المؤقت بالاسم '{0}' عند محاولة استرجاع العنصر المخزن مؤقتًا '{1}'"
    headerMustHaveNameInEncodingContextExceptionMessage               = 'يجب أن يحتوي الرأس على اسم عند استخدامه في سياق الترميز.'
    moduleDoesNotContainFunctionExceptionMessage                      = 'الوحدة {0} لا تحتوي على الوظيفة {1} لتحويلها إلى مسار.'
    pathToIconForGuiDoesNotExistExceptionMessage                      = 'المسار إلى الأيقونة للواجهة الرسومية غير موجود: {0}'
    noTitleSuppliedForPageExceptionMessage                            = 'لم يتم توفير عنوان للصفحة {0}.'
    certificateSuppliedForNonHttpsWssEndpointExceptionMessage         = 'تم توفير شهادة لنقطة نهاية غير HTTPS/WSS.'
    cannotLockNullObjectExceptionMessage                              = 'لا يمكن قفل كائن فارغ.'
    showPodeGuiOnlyAvailableOnWindowsExceptionMessage                 = 'Show-PodeGui متاح حاليًا فقط لـ Windows PowerShell و PowerShell 7+ على Windows.'
    unlockSecretButNoScriptBlockExceptionMessage                      = 'تم تقديم سر الفتح لنوع خزنة سرية مخصصة، ولكن لم يتم تقديم ScriptBlock الفتح.'
    invalidIpAddressExceptionMessage                                  = 'عنوان IP المقدم غير صالح: {0}'
    maxDaysInvalidExceptionMessage                                    = 'يجب أن يكون MaxDays 0 أو أكبر، ولكن تم الحصول على: {0}'
    noRemoveScriptBlockForVaultExceptionMessage                       = "لم يتم تقديم ScriptBlock الإزالة لإزالة الأسرار من الخزنة '{0}'"
    noSecretExpectedForNoSignatureExceptionMessage                    = 'لم يكن من المتوقع تقديم أي سر لعدم وجود توقيع.'
    noCertificateFoundExceptionMessage                                = "لم يتم العثور على شهادة في {0}{1} لـ '{2}'"
    minValueInvalidExceptionMessage                                   = "القيمة الدنيا '{0}' لـ {1} غير صالحة، يجب أن تكون أكبر من/أو تساوي {2}"
    accessRequiresAuthenticationOnRoutesExceptionMessage              = 'يتطلب الوصول توفير المصادقة على الطرق.'
    noSecretForHmac384ExceptionMessage                                = 'لم يتم تقديم أي سر لتجزئة HMAC384.'
    windowsLocalAuthSupportIsForWindowsOnlyExceptionMessage           = 'دعم المصادقة المحلية لـ Windows هو فقط لنظام Windows.'
    definitionTagNotDefinedExceptionMessage                           = 'لم يتم تعريف علامة التعريف {0}.'
    noComponentInDefinitionExceptionMessage                           = 'لا توجد مكون من نوع {0} باسم {1} متاح في تعريف {2}.'
    noSmtpHandlersDefinedExceptionMessage                             = 'لم يتم تعريف أي معالجات SMTP.'
    sessionMiddlewareAlreadyInitializedExceptionMessage               = 'تم تهيئة Session Middleware بالفعل.'
    reusableComponentPathItemsNotAvailableInOpenApi30ExceptionMessage = "ميزة المكون القابل لإعادة الاستخدام 'pathItems' غير متوفرة في OpenAPI v3.0."
    wildcardHeadersIncompatibleWithAutoHeadersExceptionMessage        = 'العلامة * للعنوان غير متوافقة مع مفتاح AutoHeaders.'
    noDataForFileUploadedExceptionMessage                             = "لا توجد بيانات للملف '{0}' الذي تم تحميله في الطلب."
    sseOnlyConfiguredOnEventStreamAcceptHeaderExceptionMessage        = 'يمكن تكوين SSE فقط على الطلبات التي تحتوي على قيمة رأس Accept النص/تيار الأحداث.'
    noSessionAvailableToSaveExceptionMessage                          = 'لا توجد جلسة متاحة للحفظ.'
    pathParameterRequiresRequiredSwitchExceptionMessage               = "إذا كانت موقع المعلمة هو 'Path'، فإن المعلمة التبديل 'Required' إلزامية."
    noOpenApiUrlSuppliedExceptionMessage                              = 'لم يتم توفير عنوان URL OpenAPI لـ {0}.'
    maximumConcurrentSchedulesInvalidExceptionMessage                 = 'يجب أن تكون الجداول الزمنية المتزامنة القصوى >=1 ولكن تم الحصول على: {0}'
    snapinsSupportedOnWindowsPowershellOnlyExceptionMessage           = 'Snapins مدعومة فقط في Windows PowerShell.'
    eventViewerLoggingSupportedOnWindowsOnlyExceptionMessage          = 'تسجيل عارض الأحداث مدعوم فقط على Windows.'
    parametersMutuallyExclusiveExceptionMessage                       = "المعاملات '{0}' و '{1}' متعارضة."
    pathItemsFeatureNotSupportedInOpenApi30ExceptionMessage           = 'ميزة PathItems غير مدعومة في OpenAPI v3.0.x'
    openApiParameterRequiresNameExceptionMessage                      = 'يتطلب معلمة OpenApi اسمًا محددًا.'
    maximumConcurrentTasksLessThanMinimumExceptionMessage             = 'لا يمكن أن يكون الحد الأقصى للمهام المتزامنة أقل من الحد الأدنى {0}، ولكن تم الحصول عليه: {1}'
    noSemaphoreFoundExceptionMessage                                  = "لم يتم العثور على Semaphore باسم '{0}'"
    singleValueForIntervalExceptionMessage                            = 'يمكنك تقديم قيمة {0} واحدة فقط عند استخدام الفواصل الزمنية.'
    jwtNotYetValidExceptionMessage                                    = 'JWT غير صالح للاستخدام بعد.'
    verbAlreadyDefinedForUrlExceptionMessage                          = '[الفعل] {0}: تم التعريف بالفعل لـ {1}'
    noSecretNamedMountedExceptionMessage                              = "لم يتم تثبيت أي سر بالاسم '{0}'."
    moduleOrVersionNotFoundExceptionMessage                           = 'لم يتم العثور على الوحدة أو الإصدار على {0}: {1}@{2}'
    noScriptBlockSuppliedExceptionMessage                             = 'لم يتم تقديم أي ScriptBlock.'
    noSecretVaultRegisteredExceptionMessage                           = "لم يتم تسجيل خزينة سرية بالاسم '{0}'."
    nameRequiredForEndpointIfRedirectToSuppliedExceptionMessage       = 'مطلوب اسم لنقطة النهاية إذا تم توفير معامل RedirectTo.'
    openApiLicenseObjectRequiresNameExceptionMessage                  = "يتطلب كائن OpenAPI 'license' الخاصية 'name'. استخدم المعامل -LicenseName."
    sourcePathDoesNotExistForStaticRouteExceptionMessage              = '{0}: مسار المصدر المقدم للمسار الثابت غير موجود: {1}'
    noNameForWebSocketDisconnectExceptionMessage                      = 'لا يوجد اسم لفصل WebSocket من المزود.'
    certificateExpiredExceptionMessage                                = "الشهادة '{0}' منتهية الصلاحية: {1}"
    secretVaultUnlockExpiryDateInPastExceptionMessage                 = 'تاريخ انتهاء صلاحية فتح مخزن الأسرار في الماضي (UTC): {0}'
    invalidWebExceptionTypeExceptionMessage                           = 'الاستثناء من نوع غير صالح، يجب أن يكون إما WebException أو HttpRequestException، ولكن تم الحصول عليه: {0}'
    invalidSecretValueTypeExceptionMessage                            = 'قيمة السر من نوع غير صالح. الأنواع المتوقعة: String، SecureString، HashTable، Byte[]، أو PSCredential. ولكن تم الحصول عليه: {0}'
    explicitTlsModeOnlySupportedOnSmtpsTcpsEndpointsExceptionMessage  = 'وضع TLS الصريح مدعوم فقط على نقاط النهاية SMTPS و TCPS.'
    discriminatorMappingRequiresDiscriminatorPropertyExceptionMessage = "يمكن استخدام المعامل 'DiscriminatorMapping' فقط عندما تكون خاصية 'DiscriminatorProperty' موجودة."
    scriptErrorExceptionMessage                                       = "خطأ '{0}' في البرنامج النصي {1} {2} (السطر {3}) الحرف {4} أثناء تنفيذ {5} على الكائن {6} 'الصنف: {7} الصنف الأساسي: {8}"
    cannotSupplyIntervalForQuarterExceptionMessage                    = 'لا يمكن توفير قيمة الفاصل الزمني لكل ربع.'
    scheduleEndTimeMustBeInFutureExceptionMessage                     = '[الجدول الزمني] {0}: يجب أن تكون قيمة EndTime في المستقبل.'
    invalidJwtSignatureSuppliedExceptionMessage                       = 'توقيع JWT المقدم غير صالح.'
    noSetScriptBlockForVaultExceptionMessage                          = "لم يتم تقديم ScriptBlock الإعداد لتحديث/إنشاء الأسرار في الخزنة '{0}'"
    accessMethodNotExistForMergingExceptionMessage                    = 'طريقة الوصول غير موجودة للدمج: {0}'
    defaultAuthNotInListExceptionMessage                              = "المصادقة الافتراضية '{0}' غير موجودة في قائمة المصادقة المقدمة."
    parameterHasNoNameExceptionMessage                                = "لا يحتوي المعامل على اسم. يرجى إعطاء هذا المكون اسمًا باستخدام معامل 'Name'."
    methodPathAlreadyDefinedForUrlExceptionMessage                    = '[{0}] {1}: تم التعريف بالفعل لـ {2}'
    fileWatcherAlreadyDefinedExceptionMessage                         = "تم تعريف مراقب الملفات باسم '{0}' بالفعل."
    noServiceHandlersDefinedExceptionMessage                          = 'لم يتم تعريف أي معالجات خدمة.'
    secretRequiredForCustomSessionStorageExceptionMessage             = 'مطلوب سر عند استخدام تخزين الجلسة المخصص.'
    secretManagementModuleNotInstalledExceptionMessage                = 'وحدة Microsoft.PowerShell.SecretManagement غير مثبتة.'
    noPathSuppliedForRouteExceptionMessage                            = 'لم يتم توفير مسار للطريق.'
    validationOfAnyOfSchemaNotSupportedExceptionMessage               = "التحقق من مخطط يتضمن 'أي منها' غير مدعوم."
    iisAuthSupportIsForWindowsOnlyExceptionMessage                    = 'دعم مصادقة IIS هو فقط لنظام Windows.'
    oauth2InnerSchemeInvalidExceptionMessage                          = 'يمكن أن تكون OAuth2 InnerScheme إما مصادقة Basic أو Form فقط، ولكن تم الحصول على: {0}'
    noRoutePathSuppliedForPageExceptionMessage                        = 'لم يتم توفير مسار للصفحة {0}.'
    cacheStorageNotFoundForExistsExceptionMessage                     = "لم يتم العثور على مخزن ذاكرة التخزين المؤقت بالاسم '{0}' عند محاولة التحقق مما إذا كان العنصر المخزن مؤقتًا '{1}' موجودًا."
    handlerAlreadyDefinedExceptionMessage                             = '[{0}] {1}: تم تعريف المعالج بالفعل.'
    sessionsNotConfiguredExceptionMessage                             = 'لم يتم تكوين الجلسات.'
    propertiesTypeObjectAssociationExceptionMessage                   = 'يمكن ربط خصائص النوع Object فقط بـ {0}.'
    sessionsRequiredForSessionPersistentAuthExceptionMessage          = 'تتطلب المصادقة المستمرة للجلسة جلسات.'
    invalidPathWildcardOrDirectoryExceptionMessage                    = 'لا يمكن أن يكون المسار المقدم عبارة عن حرف بدل أو دليل: {0}'
    accessMethodAlreadyDefinedExceptionMessage                        = 'طريقة الوصول معرفة بالفعل: {0}'
    parametersValueOrExternalValueMandatoryExceptionMessage           = "المعاملات 'Value' أو 'ExternalValue' إلزامية."
    maximumConcurrentTasksInvalidExceptionMessage                     = 'يجب أن يكون الحد الأقصى للمهام المتزامنة >=1، ولكن تم الحصول عليه: {0}'
    cannotCreatePropertyWithoutTypeExceptionMessage                   = 'لا يمكن إنشاء الخاصية لأنه لم يتم تعريف نوع.'
    authMethodNotExistForMergingExceptionMessage                      = 'طريقة المصادقة غير موجودة للدمج: {0}'
    maxValueInvalidExceptionMessage                                   = "القيمة القصوى '{0}' لـ {1} غير صالحة، يجب أن تكون أقل من/أو تساوي {2}"
    endpointAlreadyDefinedExceptionMessage                            = "تم تعريف نقطة نهاية باسم '{0}' بالفعل."
    eventAlreadyRegisteredExceptionMessage                            = 'الحدث {0} مسجل بالفعل: {1}'
    parameterNotSuppliedInRequestExceptionMessage                     = "لم يتم توفير معلمة باسم '{0}' في الطلب أو لا توجد بيانات متاحة."
    cacheStorageNotFoundForSetExceptionMessage                        = "لم يتم العثور على مخزن ذاكرة التخزين المؤقت بالاسم '{0}' عند محاولة تعيين العنصر المخزن مؤقتًا '{1}'"
    methodPathAlreadyDefinedExceptionMessage                          = '[{0}] {1}: تم التعريف بالفعل.'
    errorLoggingAlreadyEnabledExceptionMessage                        = 'تم تمكين تسجيل الأخطاء بالفعل.'
    valueForUsingVariableNotFoundExceptionMessage                     = "لم يتم العثور على قيمة لـ '`$using:{0}'."
    rapidPdfDoesNotSupportOpenApi31ExceptionMessage                   = 'أداة الوثائق RapidPdf لا تدعم OpenAPI 3.1'
    oauth2ClientSecretRequiredExceptionMessage                        = 'تتطلب OAuth2 سر العميل عند عدم استخدام PKCE.'
    invalidBase64JwtExceptionMessage                                  = 'تم العثور على قيمة مشفرة بتنسيق Base64 غير صالحة في JWT'
    noSessionToCalculateDataHashExceptionMessage                      = 'لا توجد جلسة متاحة لحساب تجزئة البيانات.'
    cacheStorageNotFoundForRemoveExceptionMessage                     = "لم يتم العثور على مخزن ذاكرة التخزين المؤقت بالاسم '{0}' عند محاولة إزالة العنصر المخزن مؤقتًا '{1}'"
    csrfMiddlewareNotInitializedExceptionMessage                      = 'لم يتم تهيئة CSRF Middleware.'
    infoTitleMandatoryMessage                                         = 'info.title إلزامي.'
    typeCanOnlyBeAssociatedWithObjectExceptionMessage                 = 'النوع {0} يمكن ربطه فقط بجسم.'
    userFileDoesNotExistExceptionMessage                              = 'ملف المستخدم غير موجود: {0}'
    routeParameterNeedsValidScriptblockExceptionMessage               = 'المعامل Route يتطلب ScriptBlock صالح وغير فارغ.'
    nextTriggerCalculationErrorExceptionMessage                       = 'يبدو أن هناك خطأ ما أثناء محاولة حساب تاريخ المشغل التالي: {0}'
    cannotLockValueTypeExceptionMessage                               = 'لا يمكن قفل [ValueType].'
    failedToCreateOpenSslCertExceptionMessage                         = 'فشل في إنشاء شهادة OpenSSL: {0}'
    jwtExpiredExceptionMessage                                        = 'انتهت صلاحية JWT.'
    openingGuiMessage                                                 = 'جارٍ فتح الواجهة الرسومية.'
    multiTypePropertiesRequireOpenApi31ExceptionMessage               = 'تتطلب خصائص الأنواع المتعددة إصدار OpenApi 3.1 أو أعلى.'
    noNameForWebSocketRemoveExceptionMessage                          = 'لا يوجد اسم لإزالة WebSocket من المزود.'
    maxSizeInvalidExceptionMessage                                    = 'يجب أن يكون MaxSize 0 أو أكبر، ولكن تم الحصول على: {0}'
    iisShutdownMessage                                                = '(إيقاف تشغيل IIS)'
    cannotUnlockValueTypeExceptionMessage                             = 'لا يمكن فتح [ValueType].'
    noJwtSignatureForAlgorithmExceptionMessage                        = 'لم يتم توفير توقيع JWT لـ {0}.'
    maximumConcurrentWebSocketThreadsInvalidExceptionMessage          = 'يجب أن يكون الحد الأقصى لمؤشرات ترابط WebSocket المتزامنة >=1، ولكن تم الحصول عليه: {0}'
    acknowledgeMessageOnlySupportedOnSmtpTcpEndpointsExceptionMessage = 'رسالة الإقرار مدعومة فقط على نقاط النهاية SMTP و TCP.'
    failedToConnectToUrlExceptionMessage                              = 'فشل الاتصال بعنوان URL: {0}'
    failedToAcquireMutexOwnershipExceptionMessage                     = 'فشل في الحصول على ملكية Mutex. اسم Mutex: {0}'
    sessionsRequiredForOAuth2WithPKCEExceptionMessage                 = 'تتطلب OAuth2 مع PKCE جلسات.'
    failedToConnectToWebSocketExceptionMessage                        = 'فشل الاتصال بـ WebSocket: {0}'
    unsupportedObjectExceptionMessage                                 = 'الكائن غير مدعوم'
    failedToParseAddressExceptionMessage                              = "فشل في تحليل '{0}' كعنوان IP/مضيف:منفذ صالح"
    mustBeRunningWithAdminPrivilegesExceptionMessage                  = 'يجب التشغيل بامتيازات المسؤول للاستماع إلى العناوين غير المحلية.'
    specificationMessage                                              = 'مواصفات'
    cacheStorageNotFoundForClearExceptionMessage                      = "لم يتم العثور على مخزن ذاكرة التخزين المؤقت بالاسم '{0}' عند محاولة مسح الذاكرة المؤقتة."
    restartingServerMessage                                           = 'إعادة تشغيل الخادم...'
    cannotSupplyIntervalWhenEveryIsNoneExceptionMessage               = "لا يمكن توفير فترة زمنية عندما يكون المعامل 'Every' مضبوطًا على None."
    unsupportedJwtAlgorithmExceptionMessage                           = 'خوارزمية JWT غير مدعومة حاليًا: {0}'
    websocketsNotConfiguredForSignalMessagesExceptionMessage          = 'لم يتم تهيئة WebSockets لإرسال رسائل الإشارة.'
    invalidLogicTypeInHashtableMiddlewareExceptionMessage             = 'مكون Middleware من نوع Hashtable المقدم يحتوي على نوع منطق غير صالح. كان المتوقع ScriptBlock، ولكن تم الحصول عليه: {0}'
    maximumConcurrentSchedulesLessThanMinimumExceptionMessage         = 'لا يمكن أن تكون الجداول الزمنية المتزامنة القصوى أقل من الحد الأدنى {0} ولكن تم الحصول على: {1}'
    failedToAcquireSemaphoreOwnershipExceptionMessage                 = 'فشل في الحصول على ملكية Semaphore. اسم Semaphore: {0}'
    propertiesParameterWithoutNameExceptionMessage                    = 'لا يمكن استخدام معلمات الخصائص إذا لم يكن لدى الخاصية اسم.'
    customSessionStorageMethodNotImplementedExceptionMessage          = "تخزين الجلسة المخصص لا ينفذ الطريقة المطلوبة '{0}()'."
    authenticationMethodDoesNotExistExceptionMessage                  = 'طريقة المصادقة غير موجودة: {0}'
    webhooksFeatureNotSupportedInOpenApi30ExceptionMessage            = 'ميزة Webhooks غير مدعومة في OpenAPI v3.0.x'
    invalidContentTypeForSchemaExceptionMessage                       = "'content-type' غير صالح في المخطط: {0}"
    noUnlockScriptBlockForVaultExceptionMessage                       = "لم يتم تقديم ScriptBlock الفتح لفتح الخزنة '{0}'"
    definitionTagMessage                                              = 'تعريف {0}:'
    failedToOpenRunspacePoolExceptionMessage                          = 'فشل في فتح RunspacePool: {0}'
    failedToCloseRunspacePoolExceptionMessage                         = 'فشل في إغلاق RunspacePool: {0}'
    verbNoLogicPassedExceptionMessage                                 = '[الفعل] {0}: لم يتم تمرير أي منطق'
    noMutexFoundExceptionMessage                                      = "لم يتم العثور على Mutex باسم '{0}'"
    documentationMessage                                              = 'توثيق'
    timerAlreadyDefinedExceptionMessage                               = '[المؤقت] {0}: المؤقت معرف بالفعل.'
    invalidPortExceptionMessage                                       = 'لا يمكن أن يكون المنفذ سالبًا: {0}'
    viewsFolderNameAlreadyExistsExceptionMessage                      = 'اسم مجلد العرض موجود بالفعل: {0}'
    noNameForWebSocketResetExceptionMessage                           = 'لا يوجد اسم لإعادة تعيين WebSocket من المزود.'
    mergeDefaultAuthNotInListExceptionMessage                         = "المصادقة MergeDefault '{0}' غير موجودة في قائمة المصادقة المقدمة."
    descriptionRequiredExceptionMessage                               = 'مطلوب وصف للمسار: {0} الاستجابة: {1}'
    pageNameShouldBeAlphaNumericExceptionMessage                      = 'يجب أن يكون اسم الصفحة قيمة أبجدية رقمية صالحة: {0}'
    defaultValueNotBooleanOrEnumExceptionMessage                      = 'القيمة الافتراضية ليست من نوع boolean وليست جزءًا من التعداد.'
    openApiComponentSchemaDoesNotExistExceptionMessage                = 'مخطط مكون OpenApi {0} غير موجود.'
    timerParameterMustBeGreaterThanZeroExceptionMessage               = '[المؤقت] {0}: {1} يجب أن يكون أكبر من 0.'
    taskTimedOutExceptionMessage                                      = 'انتهت المهلة الزمنية للمهمة بعد {0}ms.'
    scheduleStartTimeAfterEndTimeExceptionMessage                     = "[الجدول الزمني] {0}: لا يمكن أن يكون 'StartTime' بعد 'EndTime'"
    infoVersionMandatoryMessage                                       = 'info.version إلزامي.'
    cannotUnlockNullObjectExceptionMessage                            = 'لا يمكن فتح كائن فارغ.'
    nonEmptyScriptBlockRequiredForCustomAuthExceptionMessage          = 'مطلوب ScriptBlock غير فارغ لخطة المصادقة المخصصة.'
    nonEmptyScriptBlockRequiredForAuthMethodExceptionMessage          = 'مطلوب ScriptBlock غير فارغ لطريقة المصادقة.'
    validationOfOneOfSchemaNotSupportedExceptionMessage               = "التحقق من مخطط يتضمن 'واحد منها' غير مدعوم."
    routeParameterCannotBeNullExceptionMessage                        = "لا يمكن أن يكون المعامل 'Route' فارغًا."
    cacheStorageAlreadyExistsExceptionMessage                         = "مخزن ذاكرة التخزين المؤقت بالاسم '{0}' موجود بالفعل."
    loggingMethodRequiresValidScriptBlockExceptionMessage             = "تتطلب طريقة الإخراج المقدمة لطريقة التسجيل '{0}' ScriptBlock صالح."
    scopedVariableAlreadyDefinedExceptionMessage                      = 'المتغير المحدد بالفعل معرف: {0}'
    oauth2RequiresAuthorizeUrlExceptionMessage                        = 'تتطلب OAuth2 توفير عنوان URL للتفويض.'
    pathNotExistExceptionMessage                                      = 'المسار غير موجود: {0}'
    noDomainServerNameForWindowsAdAuthExceptionMessage                = 'لم يتم توفير اسم خادم المجال لمصادقة Windows AD.'
    suppliedDateAfterScheduleEndTimeExceptionMessage                  = 'التاريخ المقدم بعد وقت انتهاء الجدول الزمني في {0}'
    wildcardMethodsIncompatibleWithAutoMethodsExceptionMessage        = 'العلامة * للطرق غير متوافقة مع مفتاح AutoMethods.'
    cannotSupplyIntervalForYearExceptionMessage                       = 'لا يمكن توفير قيمة الفاصل الزمني لكل سنة.'
    missingComponentsMessage                                          = 'المكون (المكونات) المفقود'
    invalidStrictTransportSecurityDurationExceptionMessage            = 'تم توفير مدة Strict-Transport-Security غير صالحة: {0}. يجب أن تكون أكبر من 0.'
    noSecretForHmac512ExceptionMessage                                = 'لم يتم تقديم أي سر لتجزئة HMAC512.'
    daysInMonthExceededExceptionMessage                               = 'يحتوي {0} على {1} أيام فقط، ولكن تم توفير {2}.'
    nonEmptyScriptBlockRequiredForCustomLoggingExceptionMessage       = 'مطلوب ScriptBlock غير فارغ لطريقة إخراج السجل المخصصة.'
    encodingAttributeOnlyAppliesToMultipartExceptionMessage           = 'ينطبق سمة الترميز فقط على نصوص الطلبات multipart و application/x-www-form-urlencoded.'
    suppliedDateBeforeScheduleStartTimeExceptionMessage               = 'التاريخ المقدم قبل وقت بدء الجدول الزمني في {0}'
    unlockSecretRequiredExceptionMessage                              = "خاصية 'UnlockSecret' مطلوبة عند استخدام Microsoft.PowerShell.SecretStore"
    noLogicPassedForMethodRouteExceptionMessage                       = '[{0}] {1}: لم يتم تمرير منطق.'
    bodyParserAlreadyDefinedForContentTypeExceptionMessage            = 'تم تعريف محلل الجسم لنوع المحتوى {0} بالفعل.'
    invalidJwtSuppliedExceptionMessage                                = 'JWT المقدم غير صالح.'
    sessionsRequiredForFlashMessagesExceptionMessage                  = 'الجلسات مطلوبة لاستخدام رسائل الفلاش.'
    semaphoreAlreadyExistsExceptionMessage                            = 'يوجد بالفعل Semaphore بالاسم التالي: {0}'
    invalidJwtHeaderAlgorithmSuppliedExceptionMessage                 = 'خوارزمية رأس JWT المقدمة غير صالحة.'
    oauth2ProviderDoesNotSupportPasswordGrantTypeExceptionMessage     = "مزود OAuth2 لا يدعم نوع المنحة 'password' المطلوبة لاستخدام InnerScheme."
    invalidAliasFoundExceptionMessage                                 = 'تم العثور على اسم مستعار غير صالح {0}: {1}'
    scheduleDoesNotExistExceptionMessage                              = "الجدول الزمني '{0}' غير موجود."
    accessMethodNotExistExceptionMessage                              = 'طريقة الوصول غير موجودة: {0}'
    oauth2ProviderDoesNotSupportCodeResponseTypeExceptionMessage      = "مزود OAuth2 لا يدعم نوع الاستجابة 'code'."
    untestedPowerShellVersionWarningMessage                           = '[تحذير] لم يتم اختبار Pode {0} على PowerShell {1}، حيث لم يكن متاحًا عند إصدار Pode.'
    secretVaultAlreadyRegisteredAutoImportExceptionMessage            = "تم تسجيل خزنة سرية باسم '{0}' بالفعل أثناء استيراد الخزن السرية تلقائيًا."
    schemeRequiresValidScriptBlockExceptionMessage                    = "تتطلب الخطة المقدمة لمحقق المصادقة '{0}' ScriptBlock صالح."
    serverLoopingMessage                                              = 'تكرار الخادم كل {0} ثانية'
    certificateThumbprintsNameSupportedOnWindowsExceptionMessage      = 'بصمات الإبهام/الاسم للشهادة مدعومة فقط على Windows.'
    sseConnectionNameRequiredExceptionMessage                         = "مطلوب اسم اتصال SSE، إما من -Name أو `$WebEvent.Sse.Name"
    invalidMiddlewareTypeExceptionMessage                             = 'أحد مكونات Middleware المقدمة من نوع غير صالح. كان المتوقع إما ScriptBlock أو Hashtable، ولكن تم الحصول عليه: {0}'
    noSecretForJwtSignatureExceptionMessage                           = 'لم يتم تقديم أي سر لتوقيع JWT.'
    modulePathDoesNotExistExceptionMessage                            = 'مسار الوحدة غير موجود: {0}'
    taskAlreadyDefinedExceptionMessage                                = '[المهمة] {0}: المهمة معرفة بالفعل.'
    verbAlreadyDefinedExceptionMessage                                = '[الفعل] {0}: تم التعريف بالفعل'
    clientCertificatesOnlySupportedOnHttpsEndpointsExceptionMessage   = 'الشهادات العميلة مدعومة فقط على نقاط النهاية HTTPS.'
    endpointNameNotExistExceptionMessage                              = "نقطة النهاية بالاسم '{0}' غير موجودة."
    middlewareNoLogicSuppliedExceptionMessage                         = '[Middleware]: لم يتم توفير أي منطق في ScriptBlock.'
    scriptBlockRequiredForMergingUsersExceptionMessage                = 'مطلوب ScriptBlock لدمج عدة مستخدمين مصادق عليهم في كائن واحد عندما تكون Valid هي All.'
    secretVaultAlreadyRegisteredExceptionMessage                      = "تم تسجيل مخزن الأسرار بالاسم '{0}' بالفعل{1}."
    deprecatedTitleVersionDescriptionWarningMessage                   = "تحذير: العنوان، الإصدار والوصف في 'Enable-PodeOpenApi' مهمل. يرجى استخدام 'Add-PodeOAInfo' بدلاً من ذلك."
    undefinedOpenApiReferencesMessage                                 = 'مراجع OpenAPI غير معرّفة:'
    doneMessage                                                       = 'تم'
    swaggerEditorDoesNotSupportOpenApi31ExceptionMessage              = 'هذا الإصدار من Swagger-Editor لا يدعم OpenAPI 3.1'
    durationMustBeZeroOrGreaterExceptionMessage                       = 'يجب أن تكون المدة 0 أو أكبر، ولكن تم الحصول عليها: {0}s'
    viewsPathDoesNotExistExceptionMessage                             = 'مسار العرض غير موجود: {0}'
    discriminatorIncompatibleWithAllOfExceptionMessage                = "المعامل 'Discriminator' غير متوافق مع 'allOf'."
    noNameForWebSocketSendMessageExceptionMessage                     = 'لا يوجد اسم لإرسال رسالة إلى WebSocket المزود.'
    hashtableMiddlewareNoLogicExceptionMessage                        = 'مكون Middleware من نوع Hashtable المقدم لا يحتوي على منطق معرف.'
    openApiInfoMessage                                                = 'معلومات OpenAPI:'
    invalidSchemeForAuthValidatorExceptionMessage                     = "تتطلب الخطة '{0}' المقدمة لمحقق المصادقة '{1}' ScriptBlock صالح."
    sseFailedToBroadcastExceptionMessage                              = 'فشل بث SSE بسبب مستوى البث SSE المحدد لـ {0}: {1}'
    adModuleWindowsOnlyExceptionMessage                               = 'وحدة Active Directory متاحة فقط على نظام Windows.'
    requestLoggingAlreadyEnabledExceptionMessage                      = 'تم تمكين تسجيل الطلبات بالفعل.'
    invalidAccessControlMaxAgeDurationExceptionMessage                = 'مدة Access-Control-Max-Age غير صالحة المقدمة: {0}. يجب أن تكون أكبر من 0.'
    openApiDefinitionAlreadyExistsExceptionMessage                    = 'تعريف OpenAPI باسم {0} موجود بالفعل.'
    renamePodeOADefinitionTagExceptionMessage                         = "لا يمكن استخدام Rename-PodeOADefinitionTag داخل Select-PodeOADefinition 'ScriptBlock'."
    taskProcessDoesNotExistExceptionMessage                           = 'عملية المهمة غير موجودة: {0}'
    scheduleProcessDoesNotExistExceptionMessage                       = 'عملية الجدول الزمني غير موجودة: {0}'
    definitionTagChangeNotAllowedExceptionMessage                     = 'لا يمكن تغيير علامة التعريف لمسار.'
    getRequestBodyNotAllowedExceptionMessage                          = 'لا يمكن أن تحتوي عمليات {0} على محتوى الطلب.'
    fnDoesNotAcceptArrayAsPipelineInputExceptionMessage               = "الدالة '{0}' لا تقبل مصفوفة كمدخل لأنبوب البيانات."
    unsupportedStreamCompressionEncodingExceptionMessage              = 'تشفير الضغط غير مدعوم للتشفير {0}'
}
src\Locales\de\Pode.psd1
@{
    schemaValidationRequiresPowerShell610ExceptionMessage             = 'Die Schema-Validierung erfordert PowerShell Version 6.1.0 oder höher.'
    customAccessPathOrScriptBlockRequiredExceptionMessage             = 'Ein Pfad oder ScriptBlock ist erforderlich, um die benutzerdefinierten Zugriffswerte zu beziehen.'
    operationIdMustBeUniqueForArrayExceptionMessage                   = 'OperationID: {0} muss eindeutig sein und kann nicht auf ein Array angewendet werden.'
    endpointNotDefinedForRedirectingExceptionMessage                  = "Ein Endpunkt mit dem Namen '{0}' wurde nicht für die Weiterleitung definiert."
    filesHaveChangedMessage                                           = 'Die folgenden Dateien wurden geändert:'
    iisAspnetcoreTokenMissingExceptionMessage                         = 'Das IIS-ASPNETCORE_TOKEN fehlt.'
    minValueGreaterThanMaxExceptionMessage                            = 'Der Mindestwert für {0} darf nicht größer als der Maximalwert sein.'
    noLogicPassedForRouteExceptionMessage                             = 'Keine Logik für Route übergeben: {0}'
    scriptPathDoesNotExistExceptionMessage                            = 'Der Skriptpfad existiert nicht: {0}'
    mutexAlreadyExistsExceptionMessage                                = 'Ein Mutex mit folgendem Namen existiert bereits: {0}'
    listeningOnEndpointsMessage                                       = 'Lauschen auf den folgenden {0} Endpunkt(en) [{1} Thread(s)]:'
    unsupportedFunctionInServerlessContextExceptionMessage            = 'Die Funktion {0} wird in einem serverlosen Kontext nicht unterstützt.'
    expectedNoJwtSignatureSuppliedExceptionMessage                    = 'Es wurde keine JWT-Signatur erwartet.'
    secretAlreadyMountedExceptionMessage                              = "Ein Geheimnis mit dem Namen '{0}' wurde bereits eingebunden."
    failedToAcquireLockExceptionMessage                               = 'Sperre des Objekts konnte nicht erworben werden.'
    noPathSuppliedForStaticRouteExceptionMessage                      = '[{0}]: Kein Pfad für statische Route angegeben.'
    invalidHostnameSuppliedExceptionMessage                           = 'Der angegebene Hostname ist ungültig: {0}'
    authMethodAlreadyDefinedExceptionMessage                          = 'Authentifizierungsmethode bereits definiert: {0}'
    csrfCookieRequiresSecretExceptionMessage                          = "Beim Verwenden von Cookies für CSRF ist ein Geheimnis erforderlich. Sie können ein Geheimnis angeben oder das globale Cookie-Geheimnis festlegen - (Set-PodeCookieSecret '<value>' -Global)"
    nonEmptyScriptBlockRequiredForPageRouteExceptionMessage           = 'Ein nicht leerer ScriptBlock ist erforderlich, um eine Seitenroute zu erstellen.'
    noPropertiesMutuallyExclusiveExceptionMessage                     = "Der Parameter 'NoProperties' schließt 'Properties', 'MinProperties' und 'MaxProperties' gegenseitig aus."
    incompatiblePodeDllExceptionMessage                               = 'Eine vorhandene inkompatible Pode.DLL-Version {0} ist geladen. Version {1} wird benötigt. Öffnen Sie eine neue PowerShell/pwsh-Sitzung und versuchen Sie es erneut.'
    accessMethodDoesNotExistExceptionMessage                          = 'Zugriffsmethode existiert nicht: {0}.'
    scheduleAlreadyDefinedExceptionMessage                            = '[Aufgabenplaner] {0}: Aufgabenplaner bereits definiert.'
    secondsValueCannotBeZeroOrLessExceptionMessage                    = 'Der Sekundenwert darf für {0} nicht 0 oder weniger sein.'
    pathToLoadNotFoundExceptionMessage                                = 'Pfad zum Laden von {0} nicht gefunden: {1}'
    failedToImportModuleExceptionMessage                              = 'Modulimport fehlgeschlagen: {0}'
    endpointNotExistExceptionMessage                                  = "Der Endpunkt mit dem Protokoll '{0}' und der Adresse '{1}' oder der lokalen Adresse '{2}' existiert nicht"
    terminatingMessage                                                = 'Beenden...'
    noCommandsSuppliedToConvertToRoutesExceptionMessage               = 'Keine Befehle zur Umwandlung in Routen bereitgestellt.'
    invalidTaskTypeExceptionMessage                                   = 'Aufgabentyp ist ungültig, erwartet entweder [System.Threading.Tasks.Task] oder [hashtable]'
    alreadyConnectedToWebSocketExceptionMessage                       = "Bereits mit dem WebSocket mit dem Namen '{0}' verbunden"
    crlfMessageEndCheckOnlySupportedOnTcpEndpointsExceptionMessage    = 'Die CRLF-Nachrichtenendprüfung wird nur auf TCP-Endpunkten unterstützt.'
    testPodeOAComponentSchemaNeedToBeEnabledExceptionMessage          = "'Test-PodeOAComponentSchema' muss mit 'Enable-PodeOpenApi -EnableSchemaValidation' aktiviert werden."
    adModuleNotInstalledExceptionMessage                              = 'Das Active Directory-Modul ist nicht installiert.'
    cronExpressionInvalidExceptionMessage                             = 'Die Cron-Ausdruck sollte nur aus 5 Teilen bestehen: {0}'
    noSessionToSetOnResponseExceptionMessage                          = 'Keine Sitzung verfügbar, die auf die Antwort gesetzt werden kann.'
    valueOutOfRangeExceptionMessage                                   = "Wert '{0}' für {1} ist ungültig, sollte zwischen {2} und {3} liegen"
    loggingMethodAlreadyDefinedExceptionMessage                       = 'Logging-Methode bereits definiert: {0}'
    noSecretForHmac256ExceptionMessage                                = 'Es wurde kein Geheimnis für den HMAC256-Hash angegeben.'
    eolPowerShellWarningMessage                                       = '[WARNUNG] Pode {0} wurde nicht auf PowerShell {1} getestet, da es das Ende des Lebenszyklus erreicht hat.'
    runspacePoolFailedToLoadExceptionMessage                          = '{0} RunspacePool konnte nicht geladen werden.'
    noEventRegisteredExceptionMessage                                 = 'Kein Ereignis {0} registriert: {1}'
    scheduleCannotHaveNegativeLimitExceptionMessage                   = '[Aufgabenplaner] {0}: Kann kein negatives Limit haben.'
    openApiRequestStyleInvalidForParameterExceptionMessage            = 'Der OpenApi-Anfragestil kann für einen {1}-Parameter nicht {0} sein.'
    openApiDocumentNotCompliantExceptionMessage                       = 'Das OpenAPI-Dokument ist nicht konform.'
    taskDoesNotExistExceptionMessage                                  = "Aufgabe '{0}' existiert nicht."
    scopedVariableNotFoundExceptionMessage                            = 'Bereichsvariable nicht gefunden: {0}'
    sessionsRequiredForCsrfExceptionMessage                           = 'Sitzungen sind erforderlich, um CSRF zu verwenden, es sei denn, Sie möchten Cookies verwenden.'
    nonEmptyScriptBlockRequiredForLoggingMethodExceptionMessage       = 'Ein nicht leerer ScriptBlock ist für die Protokollierungsmethode erforderlich.'
    credentialsPassedWildcardForHeadersLiteralExceptionMessage        = 'Wenn Anmeldeinformationen übergeben werden, wird das *-Wildcard für Header als Literalzeichenfolge und nicht als Platzhalter verwendet.'
    podeNotInitializedExceptionMessage                                = 'Pode wurde nicht initialisiert.'
    multipleEndpointsForGuiMessage                                    = 'Mehrere Endpunkte definiert, es wird nur der erste für die GUI verwendet.'
    operationIdMustBeUniqueExceptionMessage                           = 'OperationID: {0} muss eindeutig sein.'
    invalidJsonJwtExceptionMessage                                    = 'Ungültiger JSON-Wert in JWT gefunden'
    noAlgorithmInJwtHeaderExceptionMessage                            = 'Kein Algorithmus im JWT-Header angegeben.'
    openApiVersionPropertyMandatoryExceptionMessage                   = 'Die Eigenschaft OpenApi-Version ist obligatorisch.'
    limitValueCannotBeZeroOrLessExceptionMessage                      = 'Der Grenzwert darf für {0} nicht 0 oder weniger sein.'
    timerDoesNotExistExceptionMessage                                 = "Timer '{0}' existiert nicht."
    openApiGenerationDocumentErrorMessage                             = 'Fehler beim Generieren des OpenAPI-Dokuments:'
    routeAlreadyContainsCustomAccessExceptionMessage                  = "Die Route '[{0}] {1}' enthält bereits einen benutzerdefinierten Zugriff mit dem Namen '{2}'."
    maximumConcurrentWebSocketThreadsLessThanMinimumExceptionMessage  = 'Die maximale Anzahl gleichzeitiger WebSocket-Threads darf nicht kleiner als das Minimum von {0} sein, aber erhalten: {1}'
    middlewareAlreadyDefinedExceptionMessage                          = '[Middleware] {0}: Middleware bereits definiert.'
    invalidAtomCharacterExceptionMessage                              = 'Ungültiges Atomzeichen: {0}'
    invalidCronAtomFormatExceptionMessage                             = 'Ungültiges Cron-Atom-Format gefunden: {0}'
    cacheStorageNotFoundForRetrieveExceptionMessage                   = "Der Cache-Speicher mit dem Namen '{0}' wurde nicht gefunden, als versucht wurde, das zwischengespeicherte Element '{1}' abzurufen."
    headerMustHaveNameInEncodingContextExceptionMessage               = 'Ein Header muss einen Namen haben, wenn er im Codierungskontext verwendet wird.'
    moduleDoesNotContainFunctionExceptionMessage                      = 'Modul {0} enthält keine Funktion {1} zur Umwandlung in eine Route.'
    pathToIconForGuiDoesNotExistExceptionMessage                      = 'Der Pfad zum Symbol für die GUI existiert nicht: {0}'
    noTitleSuppliedForPageExceptionMessage                            = 'Kein Titel für die Seite {0} angegeben.'
    certificateSuppliedForNonHttpsWssEndpointExceptionMessage         = 'Zertifikat für Nicht-HTTPS/WSS-Endpunkt bereitgestellt.'
    cannotLockNullObjectExceptionMessage                              = 'Kann ein null-Objekt nicht sperren.'
    showPodeGuiOnlyAvailableOnWindowsExceptionMessage                 = 'Show-PodeGui ist derzeit nur für Windows PowerShell und PowerShell 7+ unter Windows verfügbar.'
    unlockSecretButNoScriptBlockExceptionMessage                      = 'Unlock secret für benutzerdefinierten Secret Vault-Typ angegeben, aber kein Unlock ScriptBlock bereitgestellt.'
    invalidIpAddressExceptionMessage                                  = 'Die angegebene IP-Adresse ist ungültig: {0}'
    maxDaysInvalidExceptionMessage                                    = 'MaxDays muss 0 oder größer sein, aber erhalten: {0}'
    noRemoveScriptBlockForVaultExceptionMessage                       = "Kein Remove ScriptBlock für das Entfernen von Geheimnissen im Tresor '{0}' bereitgestellt."
    noSecretExpectedForNoSignatureExceptionMessage                    = 'Es wurde erwartet, dass kein Geheimnis für keine Signatur angegeben wird.'
    noCertificateFoundExceptionMessage                                = "Es wurde kein Zertifikat in {0}{1} für '{2}' gefunden."
    minValueInvalidExceptionMessage                                   = "Der Mindestwert '{0}' für {1} ist ungültig, sollte größer oder gleich {2} sein"
    accessRequiresAuthenticationOnRoutesExceptionMessage              = 'Der Zugriff erfordert eine Authentifizierung auf den Routen.'
    noSecretForHmac384ExceptionMessage                                = 'Es wurde kein Geheimnis für den HMAC384-Hash angegeben.'
    windowsLocalAuthSupportIsForWindowsOnlyExceptionMessage           = 'Die Unterstützung der lokalen Windows-Authentifizierung gilt nur für Windows.'
    definitionTagNotDefinedExceptionMessage                           = 'Definitionstag {0} ist nicht definiert.'
    noComponentInDefinitionExceptionMessage                           = 'Es ist keine Komponente des Typs {0} mit dem Namen {1} in der Definition {2} verfügbar.'
    noSmtpHandlersDefinedExceptionMessage                             = 'Es wurden keine SMTP-Handler definiert.'
    sessionMiddlewareAlreadyInitializedExceptionMessage               = 'Session Middleware wurde bereits initialisiert.'
    reusableComponentPathItemsNotAvailableInOpenApi30ExceptionMessage = "Die wiederverwendbare Komponente 'pathItems' ist in OpenAPI v3.0 nicht verfügbar."
    wildcardHeadersIncompatibleWithAutoHeadersExceptionMessage        = 'Das *-Wildcard für Header ist nicht mit dem AutoHeaders-Schalter kompatibel.'
    noDataForFileUploadedExceptionMessage                             = "Keine Daten für die Datei '{0}' wurden in der Anfrage hochgeladen."
    sseOnlyConfiguredOnEventStreamAcceptHeaderExceptionMessage        = 'SSE kann nur auf Anfragen mit einem Accept-Header-Wert von text/event-stream konfiguriert werden.'
    noSessionAvailableToSaveExceptionMessage                          = 'Keine Sitzung verfügbar zum Speichern.'
    pathParameterRequiresRequiredSwitchExceptionMessage               = "Wenn der Parameterstandort 'Path' ist, ist der Schalterparameter 'Required' erforderlich."
    noOpenApiUrlSuppliedExceptionMessage                              = 'Keine OpenAPI-URL für {0} angegeben.'
    maximumConcurrentSchedulesInvalidExceptionMessage                 = 'Maximale gleichzeitige Zeitpläne müssen >=1 sein, aber erhalten: {0}'
    snapinsSupportedOnWindowsPowershellOnlyExceptionMessage           = 'Snapins werden nur in Windows PowerShell unterstützt.'
    eventViewerLoggingSupportedOnWindowsOnlyExceptionMessage          = 'Das Protokollieren im Ereignisanzeige wird nur auf Windows unterstützt.'
    parametersMutuallyExclusiveExceptionMessage                       = "Die Parameter '{0}' und '{1}' schließen sich gegenseitig aus."
    pathItemsFeatureNotSupportedInOpenApi30ExceptionMessage           = 'Das PathItems-Feature wird in OpenAPI v3.0.x nicht unterstützt.'
    openApiParameterRequiresNameExceptionMessage                      = 'Der OpenApi-Parameter erfordert einen angegebenen Namen.'
    maximumConcurrentTasksLessThanMinimumExceptionMessage             = 'Die maximale Anzahl gleichzeitiger Aufgaben darf nicht kleiner als das Minimum von {0} sein, aber erhalten: {1}'
    noSemaphoreFoundExceptionMessage                                  = "Kein Semaphor mit dem Namen '{0}' gefunden."
    singleValueForIntervalExceptionMessage                            = 'Sie können nur einen einzelnen {0}-Wert angeben, wenn Sie Intervalle verwenden.'
    jwtNotYetValidExceptionMessage                                    = 'Der JWT ist noch nicht gültig.'
    verbAlreadyDefinedForUrlExceptionMessage                          = '[Verb] {0}: Bereits für {1} definiert.'
    noSecretNamedMountedExceptionMessage                              = "Kein Geheimnis mit dem Namen '{0}' wurde eingebunden."
    moduleOrVersionNotFoundExceptionMessage                           = 'Modul oder Version nicht gefunden auf {0}: {1}@{2}'
    noScriptBlockSuppliedExceptionMessage                             = 'Kein Skriptblock angegeben.'
    noSecretVaultRegisteredExceptionMessage                           = "Kein Geheimnistresor mit dem Namen '{0}' registriert."
    nameRequiredForEndpointIfRedirectToSuppliedExceptionMessage       = 'Ein Name ist für den Endpunkt erforderlich, wenn der RedirectTo-Parameter angegeben ist.'
    openApiLicenseObjectRequiresNameExceptionMessage                  = "Das OpenAPI-Objekt 'license' erfordert die Eigenschaft 'name'. Verwenden Sie den Parameter -LicenseName."
    sourcePathDoesNotExistForStaticRouteExceptionMessage              = '{0}: Der angegebene Quellpfad für die statische Route existiert nicht: {1}'
    noNameForWebSocketDisconnectExceptionMessage                      = 'Kein Name für die Trennung vom WebSocket angegeben.'
    certificateExpiredExceptionMessage                                = "Das Zertifikat '{0}' ist abgelaufen: {1}"
    secretVaultUnlockExpiryDateInPastExceptionMessage                 = 'Das Ablaufdatum zum Entsperren des Geheimnis-Tresors liegt in der Vergangenheit (UTC): {0}'
    invalidWebExceptionTypeExceptionMessage                           = 'Die Ausnahme hat einen ungültigen Typ. Er sollte entweder WebException oder HttpRequestException sein, aber es wurde {0} erhalten'
    invalidSecretValueTypeExceptionMessage                            = 'Der Geheimniswert hat einen ungültigen Typ. Erwartete Typen: String, SecureString, HashTable, Byte[] oder PSCredential. Aber erhalten wurde: {0}.'
    explicitTlsModeOnlySupportedOnSmtpsTcpsEndpointsExceptionMessage  = 'Der explizite TLS-Modus wird nur auf SMTPS- und TCPS-Endpunkten unterstützt.'
    discriminatorMappingRequiresDiscriminatorPropertyExceptionMessage = "Der Parameter 'DiscriminatorMapping' kann nur verwendet werden, wenn 'DiscriminatorProperty' vorhanden ist."
    scriptErrorExceptionMessage                                       = "Fehler '{0}' im Skript {1} {2} (Zeile {3}) Zeichen {4} beim Ausführen von {5} auf {6} Objekt '{7}' Klasse: {8} Basisklasse: {9}"
    cannotSupplyIntervalForQuarterExceptionMessage                    = 'Ein Intervallwert kann nicht für jedes Quartal angegeben werden.'
    scheduleEndTimeMustBeInFutureExceptionMessage                     = '[Aufgabenplaner] {0}: Der Wert für EndTime muss in der Zukunft liegen.'
    invalidJwtSignatureSuppliedExceptionMessage                       = 'Ungültige JWT-Signatur angegeben.'
    noSetScriptBlockForVaultExceptionMessage                          = "Kein Set ScriptBlock für das Aktualisieren/Erstellen von Geheimnissen im Tresor '{0}' bereitgestellt."
    accessMethodNotExistForMergingExceptionMessage                    = 'Zugriffsmethode zum Zusammenführen nicht vorhanden: {0}.'
    defaultAuthNotInListExceptionMessage                              = "Die Standardauthentifizierung '{0}' befindet sich nicht in der angegebenen Authentifizierungsliste."
    parameterHasNoNameExceptionMessage                                = "Der Parameter hat keinen Namen. Bitte geben Sie dieser Komponente einen Namen mit dem 'Name'-Parameter."
    methodPathAlreadyDefinedForUrlExceptionMessage                    = '[{0}] {1}: Bereits für {2} definiert.'
    fileWatcherAlreadyDefinedExceptionMessage                         = "Ein Dateiwächter mit dem Namen '{0}' wurde bereits definiert."
    noServiceHandlersDefinedExceptionMessage                          = 'Es wurden keine Service-Handler definiert.'
    secretRequiredForCustomSessionStorageExceptionMessage             = 'Ein Geheimnis ist erforderlich, wenn benutzerdefinierter Sitzungspeicher verwendet wird.'
    secretManagementModuleNotInstalledExceptionMessage                = 'Das Modul Microsoft.PowerShell.SecretManagement ist nicht installiert.'
    noPathSuppliedForRouteExceptionMessage                            = 'Kein Pfad für die Route bereitgestellt.'
    validationOfAnyOfSchemaNotSupportedExceptionMessage               = "Die Validierung eines Schemas, das 'anyof' enthält, wird nicht unterstützt."
    iisAuthSupportIsForWindowsOnlyExceptionMessage                    = 'Die IIS-Authentifizierungsunterstützung gilt nur für Windows.'
    oauth2InnerSchemeInvalidExceptionMessage                          = 'OAuth2 InnerScheme kann nur entweder Basic oder Form-Authentifizierung sein, aber erhalten: {0}'
    noRoutePathSuppliedForPageExceptionMessage                        = 'Kein Routenpfad für die Seite {0} angegeben.'
    cacheStorageNotFoundForExistsExceptionMessage                     = "Der Cache-Speicher mit dem Namen '{0}' wurde nicht gefunden, als versucht wurde zu überprüfen, ob das zwischengespeicherte Element '{1}' existiert."
    handlerAlreadyDefinedExceptionMessage                             = '[{0}] {1}: Handler bereits definiert.'
    sessionsNotConfiguredExceptionMessage                             = 'Sitzungen wurden nicht konfiguriert.'
    propertiesTypeObjectAssociationExceptionMessage                   = 'Nur Eigenschaften vom Typ Object können mit {0} verknüpft werden.'
    sessionsRequiredForSessionPersistentAuthExceptionMessage          = 'Sitzungen sind erforderlich, um die sitzungsbeständige Authentifizierung zu verwenden.'
    invalidPathWildcardOrDirectoryExceptionMessage                    = 'Der angegebene Pfad darf kein Platzhalter oder Verzeichnis sein: {0}'
    accessMethodAlreadyDefinedExceptionMessage                        = 'Zugriffsmethode bereits definiert: {0}.'
    parametersValueOrExternalValueMandatoryExceptionMessage           = "Die Parameter 'Value' oder 'ExternalValue' sind obligatorisch."
    maximumConcurrentTasksInvalidExceptionMessage                     = 'Die maximale Anzahl gleichzeitiger Aufgaben muss >=1 sein, aber erhalten: {0}'
    cannotCreatePropertyWithoutTypeExceptionMessage                   = 'Die Eigenschaft kann nicht erstellt werden, weil kein Typ definiert ist.'
    authMethodNotExistForMergingExceptionMessage                      = 'Die Authentifizierungsmethode existiert nicht zum Zusammenführen: {0}'
    maxValueInvalidExceptionMessage                                   = "Der Maximalwert '{0}' für {1} ist ungültig, sollte kleiner oder gleich {2} sein"
    endpointAlreadyDefinedExceptionMessage                            = "Ein Endpunkt mit dem Namen '{0}' wurde bereits definiert."
    eventAlreadyRegisteredExceptionMessage                            = 'Ereignis {0} bereits registriert: {1}'
    parameterNotSuppliedInRequestExceptionMessage                     = "Ein Parameter namens '{0}' wurde in der Anfrage nicht angegeben oder es sind keine Daten verfügbar."
    cacheStorageNotFoundForSetExceptionMessage                        = "Der Cache-Speicher mit dem Namen '{0}' wurde nicht gefunden, als versucht wurde, das zwischengespeicherte Element '{1}' zu setzen."
    methodPathAlreadyDefinedExceptionMessage                          = '[{0}] {1}: Bereits definiert.'
    errorLoggingAlreadyEnabledExceptionMessage                        = 'Die Fehlerprotokollierung wurde bereits aktiviert.'
    valueForUsingVariableNotFoundExceptionMessage                     = "Der Wert für '`$using:{0}' konnte nicht gefunden werden."
    rapidPdfDoesNotSupportOpenApi31ExceptionMessage                   = 'Das Dokumentationstool RapidPdf unterstützt OpenAPI 3.1 nicht.'
    oauth2ClientSecretRequiredExceptionMessage                        = 'OAuth2 erfordert ein Client Secret, wenn PKCE nicht verwendet wird.'
    invalidBase64JwtExceptionMessage                                  = 'Ungültiger Base64-codierter Wert in JWT gefunden'
    noSessionToCalculateDataHashExceptionMessage                      = 'Keine Sitzung verfügbar, um den Datenhash zu berechnen.'
    cacheStorageNotFoundForRemoveExceptionMessage                     = "Der Cache-Speicher mit dem Namen '{0}' wurde nicht gefunden, als versucht wurde, das zwischengespeicherte Element '{1}' zu entfernen."
    csrfMiddlewareNotInitializedExceptionMessage                      = 'CSRF Middleware wurde nicht initialisiert.'
    infoTitleMandatoryMessage                                         = 'info.title ist obligatorisch.'
    typeCanOnlyBeAssociatedWithObjectExceptionMessage                 = 'Der Typ {0} kann nur einem Objekt zugeordnet werden.'
    userFileDoesNotExistExceptionMessage                              = 'Die Benutzerdaten-Datei existiert nicht: {0}'
    routeParameterNeedsValidScriptblockExceptionMessage               = 'Der Route-Parameter benötigt einen gültigen, nicht leeren ScriptBlock.'
    nextTriggerCalculationErrorExceptionMessage                       = 'Es scheint, als ob beim Berechnen des nächsten Trigger-Datums und der nächsten Triggerzeit etwas schief gelaufen wäre: {0}'
    cannotLockValueTypeExceptionMessage                               = 'Kann [ValueType] nicht sperren.'
    failedToCreateOpenSslCertExceptionMessage                         = 'Erstellung des OpenSSL-Zertifikats fehlgeschlagen: {0}.'
    jwtExpiredExceptionMessage                                        = 'Der JWT ist abgelaufen.'
    openingGuiMessage                                                 = 'Die GUI wird geöffnet.'
    multiTypePropertiesRequireOpenApi31ExceptionMessage               = 'Mehrfachtyp-Eigenschaften erfordern OpenApi-Version 3.1 oder höher.'
    noNameForWebSocketRemoveExceptionMessage                          = 'Kein Name für das Entfernen des WebSocket angegeben.'
    maxSizeInvalidExceptionMessage                                    = 'MaxSize muss 0 oder größer sein, aber erhalten: {0}'
    iisShutdownMessage                                                = '(IIS Herunterfahren)'
    cannotUnlockValueTypeExceptionMessage                             = 'Kann [ValueType] nicht entsperren.'
    noJwtSignatureForAlgorithmExceptionMessage                        = 'Keine JWT-Signatur für {0} angegeben.'
    maximumConcurrentWebSocketThreadsInvalidExceptionMessage          = 'Die maximale Anzahl gleichzeitiger WebSocket-Threads muss >=1 sein, aber erhalten: {0}'
    acknowledgeMessageOnlySupportedOnSmtpTcpEndpointsExceptionMessage = 'Die Bestätigungsnachricht wird nur auf SMTP- und TCP-Endpunkten unterstützt.'
    failedToConnectToUrlExceptionMessage                              = 'Verbindung mit der URL fehlgeschlagen: {0}'
    failedToAcquireMutexOwnershipExceptionMessage                     = 'Fehler beim Erwerb des Mutex-Besitzes. Mutex-Name: {0}'
    sessionsRequiredForOAuth2WithPKCEExceptionMessage                 = 'Sitzungen sind erforderlich, um OAuth2 mit PKCE zu verwenden.'
    failedToConnectToWebSocketExceptionMessage                        = 'Verbindung zum WebSocket fehlgeschlagen: {0}'
    unsupportedObjectExceptionMessage                                 = 'Nicht unterstütztes Objekt'
    failedToParseAddressExceptionMessage                              = "Konnte '{0}' nicht als gültige IP/Host:Port-Adresse analysieren"
    mustBeRunningWithAdminPrivilegesExceptionMessage                  = 'Muss mit Administratorrechten ausgeführt werden, um auf Nicht-Localhost-Adressen zu lauschen.'
    specificationMessage                                              = 'Spezifikation'
    cacheStorageNotFoundForClearExceptionMessage                      = "Der Cache-Speicher mit dem Namen '{0}' wurde nicht gefunden, als versucht wurde, den Cache zu leeren."
    restartingServerMessage                                           = 'Server wird neu gestartet...'
    cannotSupplyIntervalWhenEveryIsNoneExceptionMessage               = "Ein Intervall kann nicht angegeben werden, wenn der Parameter 'Every' auf None gesetzt ist."
    unsupportedJwtAlgorithmExceptionMessage                           = 'Der JWT-Algorithmus wird derzeit nicht unterstützt: {0}'
    websocketsNotConfiguredForSignalMessagesExceptionMessage          = 'WebSockets wurden nicht konfiguriert, um Signalnachrichten zu senden.'
    invalidLogicTypeInHashtableMiddlewareExceptionMessage             = 'Eine angegebene Hashtable-Middleware enthält einen ungültigen Logik-Typ. Erwartet wurde ein ScriptBlock, aber erhalten wurde: {0}.'
    maximumConcurrentSchedulesLessThanMinimumExceptionMessage         = 'Maximale gleichzeitige Zeitpläne dürfen nicht kleiner als das Minimum von {0} sein, aber erhalten: {1}'
    failedToAcquireSemaphoreOwnershipExceptionMessage                 = 'Fehler beim Erwerb des Semaphor-Besitzes. Semaphor-Name: {0}'
    propertiesParameterWithoutNameExceptionMessage                    = 'Die Eigenschaftsparameter können nicht verwendet werden, wenn die Eigenschaft keinen Namen hat.'
    customSessionStorageMethodNotImplementedExceptionMessage          = "Der benutzerdefinierte Sitzungspeicher implementiert die erforderliche Methode '{0}()' nicht."
    authenticationMethodDoesNotExistExceptionMessage                  = 'Authentifizierungsmethode existiert nicht: {0}'
    webhooksFeatureNotSupportedInOpenApi30ExceptionMessage            = 'Das Webhooks-Feature wird in OpenAPI v3.0.x nicht unterstützt.'
    invalidContentTypeForSchemaExceptionMessage                       = "Ungültiger 'content-type' im Schema gefunden: {0}"
    noUnlockScriptBlockForVaultExceptionMessage                       = "Kein Unlock ScriptBlock für das Entsperren des Tresors '{0}' bereitgestellt."
    definitionTagMessage                                              = 'Definition {0}:'
    failedToOpenRunspacePoolExceptionMessage                          = 'Fehler beim Öffnen des Runspace-Pools: {0}'
    failedToCloseRunspacePoolExceptionMessage                         = 'Fehler beim Schließen des RunspacePools: {0}'
    verbNoLogicPassedExceptionMessage                                 = '[Verb] {0}: Keine Logik übergeben'
    noMutexFoundExceptionMessage                                      = "Kein Mutex mit dem Namen '{0}' gefunden."
    documentationMessage                                              = 'Dokumentation'
    timerAlreadyDefinedExceptionMessage                               = '[Timer] {0}: Timer bereits definiert.'
    invalidPortExceptionMessage                                       = 'Der Port kann nicht negativ sein: {0}'
    viewsFolderNameAlreadyExistsExceptionMessage                      = 'Der Name des Ansichtsordners existiert bereits: {0}'
    noNameForWebSocketResetExceptionMessage                           = 'Kein Name für das Zurücksetzen des WebSocket angegeben.'
    mergeDefaultAuthNotInListExceptionMessage                         = "Die MergeDefault-Authentifizierung '{0}' befindet sich nicht in der angegebenen Authentifizierungsliste."
    descriptionRequiredExceptionMessage                               = 'Eine Beschreibung ist erforderlich für Pfad:{0} Antwort:{1}'
    pageNameShouldBeAlphaNumericExceptionMessage                      = 'Der Seitenname sollte einen gültigen alphanumerischen Wert haben: {0}'
    defaultValueNotBooleanOrEnumExceptionMessage                      = 'Der Standardwert ist kein Boolean und gehört nicht zum Enum.'
    openApiComponentSchemaDoesNotExistExceptionMessage                = 'Das OpenApi-Komponentenschema {0} existiert nicht.'
    timerParameterMustBeGreaterThanZeroExceptionMessage               = '[Timer] {0}: {1} muss größer als 0 sein.'
    taskTimedOutExceptionMessage                                      = 'Aufgabe ist nach {0}ms abgelaufen.'
    scheduleStartTimeAfterEndTimeExceptionMessage                     = '[Aufgabenplaner] {0}: StartTime kann nicht nach EndTime liegen.'
    infoVersionMandatoryMessage                                       = 'info.version ist obligatorisch.'
    cannotUnlockNullObjectExceptionMessage                            = 'Kann ein null-Objekt nicht entsperren.'
    nonEmptyScriptBlockRequiredForCustomAuthExceptionMessage          = 'Ein nicht leerer ScriptBlock ist für das benutzerdefinierte Authentifizierungsschema erforderlich.'
    nonEmptyScriptBlockRequiredForAuthMethodExceptionMessage          = 'Für die Authentifizierungsmethode ist ein nicht leerer ScriptBlock erforderlich.'
    validationOfOneOfSchemaNotSupportedExceptionMessage               = "Die Validierung eines Schemas, das 'oneof' enthält, wird nicht unterstützt."
    routeParameterCannotBeNullExceptionMessage                        = "Der Parameter 'Route' darf nicht null sein."
    cacheStorageAlreadyExistsExceptionMessage                         = "Ein Cache-Speicher mit dem Namen '{0}' existiert bereits."
    loggingMethodRequiresValidScriptBlockExceptionMessage             = "Die angegebene Ausgabemethode für die Logging-Methode '{0}' erfordert einen gültigen ScriptBlock."
    scopedVariableAlreadyDefinedExceptionMessage                      = 'Die Bereichsvariable ist bereits definiert: {0}.'
    oauth2RequiresAuthorizeUrlExceptionMessage                        = 'OAuth2 erfordert die Angabe einer Autorisierungs-URL.'
    pathNotExistExceptionMessage                                      = 'Pfad existiert nicht: {0}'
    noDomainServerNameForWindowsAdAuthExceptionMessage                = 'Es wurde kein Domänenservername für die Windows-AD-Authentifizierung angegeben.'
    suppliedDateAfterScheduleEndTimeExceptionMessage                  = 'Das angegebene Datum liegt nach der Endzeit des Aufgabenplaners bei {0}'
    wildcardMethodsIncompatibleWithAutoMethodsExceptionMessage        = 'Das *-Wildcard für Methoden ist nicht mit dem AutoMethods-Schalter kompatibel.'
    cannotSupplyIntervalForYearExceptionMessage                       = 'Ein Intervallwert kann nicht für jedes Jahr angegeben werden.'
    missingComponentsMessage                                          = 'Fehlende Komponente(n)'
    invalidStrictTransportSecurityDurationExceptionMessage            = 'Ungültige Strict-Transport-Security-Dauer angegeben: {0}. Sie sollte größer als 0 sein.'
    noSecretForHmac512ExceptionMessage                                = 'Es wurde kein Geheimnis für den HMAC512-Hash angegeben.'
    daysInMonthExceededExceptionMessage                               = '{0} hat nur {1} Tage, aber {2} wurden angegeben'
    nonEmptyScriptBlockRequiredForCustomLoggingExceptionMessage       = 'Ein nicht leerer ScriptBlock ist für die benutzerdefinierte Protokollierungsmethode erforderlich.'
    encodingAttributeOnlyAppliesToMultipartExceptionMessage           = 'Das Encoding-Attribut gilt nur für multipart und application/x-www-form-urlencoded Anfragekörper.'
    suppliedDateBeforeScheduleStartTimeExceptionMessage               = 'Das angegebene Datum liegt vor der Startzeit des Aufgabenplaners bei {0}'
    unlockSecretRequiredExceptionMessage                              = "Eine 'UnlockSecret'-Eigenschaft ist erforderlich, wenn Microsoft.PowerShell.SecretStore verwendet wird."
    noLogicPassedForMethodRouteExceptionMessage                       = '[{0}] {1}: Keine Logik übergeben.'
    bodyParserAlreadyDefinedForContentTypeExceptionMessage            = 'Für den Inhaltstyp {0} ist bereits ein Body-Parser definiert.'
    invalidJwtSuppliedExceptionMessage                                = 'Ungültiger JWT angegeben.'
    sessionsRequiredForFlashMessagesExceptionMessage                  = 'Sitzungen sind erforderlich, um Flash-Nachrichten zu verwenden.'
    semaphoreAlreadyExistsExceptionMessage                            = 'Ein Semaphor mit folgendem Namen existiert bereits: {0}'
    invalidJwtHeaderAlgorithmSuppliedExceptionMessage                 = 'Ungültiger JWT-Header-Algorithmus angegeben.'
    oauth2ProviderDoesNotSupportPasswordGrantTypeExceptionMessage     = "Der OAuth2-Anbieter unterstützt den für die Verwendung eines InnerScheme erforderlichen 'password'-Grant-Typ nicht."
    invalidAliasFoundExceptionMessage                                 = 'Ungültiges {0}-Alias gefunden: {1}'
    scheduleDoesNotExistExceptionMessage                              = "Aufgabenplaner '{0}' existiert nicht."
    accessMethodNotExistExceptionMessage                              = 'Zugriffsmethode nicht vorhanden: {0}.'
    oauth2ProviderDoesNotSupportCodeResponseTypeExceptionMessage      = "Der OAuth2-Anbieter unterstützt den 'code'-Antworttyp nicht."
    untestedPowerShellVersionWarningMessage                           = '[WARNUNG] Pode {0} wurde nicht auf PowerShell {1} getestet, da diese Version bei der Veröffentlichung von Pode nicht verfügbar war.'
    secretVaultAlreadyRegisteredAutoImportExceptionMessage            = "Ein Geheimtresor mit dem Namen '{0}' wurde bereits beim automatischen Importieren von Geheimtresoren registriert."
    schemeRequiresValidScriptBlockExceptionMessage                    = "Das bereitgestellte Schema für den Authentifizierungsvalidator '{0}' erfordert einen gültigen ScriptBlock."
    serverLoopingMessage                                              = 'Server-Schleife alle {0} Sekunden'
    certificateThumbprintsNameSupportedOnWindowsExceptionMessage      = 'Zertifikat-Thumbprints/Name werden nur unter Windows unterstützt.'
    sseConnectionNameRequiredExceptionMessage                         = "Ein SSE-Verbindungsname ist erforderlich, entweder von -Name oder `$WebEvent.Sse.Namee"
    invalidMiddlewareTypeExceptionMessage                             = 'Eines der angegebenen Middleware-Objekte ist ein ungültiger Typ. Erwartet wurde entweder ein ScriptBlock oder ein Hashtable, aber erhalten wurde: {0}.'
    noSecretForJwtSignatureExceptionMessage                           = 'Es wurde kein Geheimnis für die JWT-Signatur angegeben.'
    modulePathDoesNotExistExceptionMessage                            = 'Der Modulpfad existiert nicht: {0}'
    taskAlreadyDefinedExceptionMessage                                = '[Aufgabe] {0}: Aufgabe bereits definiert.'
    verbAlreadyDefinedExceptionMessage                                = '[Verb] {0}: Bereits definiert.'
    clientCertificatesOnlySupportedOnHttpsEndpointsExceptionMessage   = 'Clientzertifikate werden nur auf HTTPS-Endpunkten unterstützt.'
    endpointNameNotExistExceptionMessage                              = "Der Endpunkt mit dem Namen '{0}' existiert nicht"
    middlewareNoLogicSuppliedExceptionMessage                         = '[Middleware]: Kein Logik-ScriptBlock bereitgestellt.'
    scriptBlockRequiredForMergingUsersExceptionMessage                = 'Ein ScriptBlock ist erforderlich, um mehrere authentifizierte Benutzer zu einem Objekt zusammenzuführen, wenn Valid All ist.'
    secretVaultAlreadyRegisteredExceptionMessage                      = "Ein Geheimnis-Tresor mit dem Namen '{0}' wurde bereits registriert{1}."
    deprecatedTitleVersionDescriptionWarningMessage                   = "WARNUNG: Titel, Version und Beschreibung in 'Enable-PodeOpenApi' sind veraltet. Bitte verwenden Sie stattdessen 'Add-PodeOAInfo'."
    undefinedOpenApiReferencesMessage                                 = 'Nicht definierte OpenAPI-Referenzen:'
    doneMessage                                                       = 'Fertig'
    swaggerEditorDoesNotSupportOpenApi31ExceptionMessage              = 'Diese Version des Swagger-Editors unterstützt OpenAPI 3.1 nicht.'
    durationMustBeZeroOrGreaterExceptionMessage                       = 'Die Dauer muss 0 oder größer sein, aber erhalten: {0}s'
    viewsPathDoesNotExistExceptionMessage                             = 'Der Ansichtsordnerpfad existiert nicht: {0}'
    discriminatorIncompatibleWithAllOfExceptionMessage                = "Der Parameter 'Discriminator' ist nicht mit 'allOf' kompatibel."
    noNameForWebSocketSendMessageExceptionMessage                     = 'Kein Name für das Senden einer Nachricht an den WebSocket angegeben.'
    hashtableMiddlewareNoLogicExceptionMessage                        = 'Eine angegebene Hashtable-Middleware enthält keine definierte Logik.'
    openApiInfoMessage                                                = 'OpenAPI-Informationen:'
    invalidSchemeForAuthValidatorExceptionMessage                     = "Das bereitgestellte '{0}'-Schema für den Authentifizierungsvalidator '{1}' erfordert einen gültigen ScriptBlock."
    sseFailedToBroadcastExceptionMessage                              = 'SSE konnte aufgrund des definierten SSE-Broadcast-Levels für {0}: {1} nicht übertragen werden.'
    adModuleWindowsOnlyExceptionMessage                               = 'Active Directory-Modul nur unter Windows verfügbar.'
    requestLoggingAlreadyEnabledExceptionMessage                      = 'Die Anforderungsprotokollierung wurde bereits aktiviert.'
    invalidAccessControlMaxAgeDurationExceptionMessage                = 'Ungültige Access-Control-Max-Age-Dauer angegeben: {0}. Sollte größer als 0 sein.'
    openApiDefinitionAlreadyExistsExceptionMessage                    = 'Die OpenAPI-Definition mit dem Namen {0} existiert bereits.'
    renamePodeOADefinitionTagExceptionMessage                         = "Rename-PodeOADefinitionTag kann nicht innerhalb eines 'ScriptBlock' von Select-PodeOADefinition verwendet werden."
    taskProcessDoesNotExistExceptionMessage                           = "Der Aufgabenprozess '{0}' existiert nicht."
    scheduleProcessDoesNotExistExceptionMessage                       = "Der Aufgabenplanerprozess '{0}' existiert nicht."
    definitionTagChangeNotAllowedExceptionMessage                     = 'Definitionstag für eine Route kann nicht geändert werden.'
    getRequestBodyNotAllowedExceptionMessage                          = '{0}-Operationen können keinen Anforderungstext haben.'
    fnDoesNotAcceptArrayAsPipelineInputExceptionMessage               = "Die Funktion '{0}' akzeptiert kein Array als Pipeline-Eingabe."
    unsupportedStreamCompressionEncodingExceptionMessage              = 'Die Stream-Komprimierungskodierung wird nicht unterstützt: {0}'
}
src\Locales\en-us\Pode.psd1
@{
    schemaValidationRequiresPowerShell610ExceptionMessage             = 'Schema validation requires PowerShell version 6.1.0 or greater.'
    customAccessPathOrScriptBlockRequiredExceptionMessage             = 'A Path or ScriptBlock is required for sourcing the Custom access values.'
    operationIdMustBeUniqueForArrayExceptionMessage                   = 'OperationID: {0} has to be unique and cannot be applied to an array.'
    endpointNotDefinedForRedirectingExceptionMessage                  = "An endpoint named '{0}' has not been defined for redirecting."
    filesHaveChangedMessage                                           = 'The following files have changed:'
    iisAspnetcoreTokenMissingExceptionMessage                         = 'IIS ASPNETCORE_TOKEN is missing.'
    minValueGreaterThanMaxExceptionMessage                            = 'Min value for {0} should not be greater than the max value.'
    noLogicPassedForRouteExceptionMessage                             = 'No logic passed for Route: {0}'
    scriptPathDoesNotExistExceptionMessage                            = 'The script path does not exist: {0}'
    mutexAlreadyExistsExceptionMessage                                = 'A mutex with the following name already exists: {0}'
    listeningOnEndpointsMessage                                       = 'Listening on the following {0} endpoint(s) [{1} thread(s)]:'
    unsupportedFunctionInServerlessContextExceptionMessage            = 'The {0} function is not supported in a serverless context.'
    expectedNoJwtSignatureSuppliedExceptionMessage                    = 'Expected no JWT signature to be supplied.'
    secretAlreadyMountedExceptionMessage                              = "A Secret with the name '{0}' has already been mounted."
    failedToAcquireLockExceptionMessage                               = 'Failed to acquire a lock on the object.'
    noPathSuppliedForStaticRouteExceptionMessage                      = '[{0}]: No Path supplied for Static Route.'
    invalidHostnameSuppliedExceptionMessage                           = 'Invalid hostname supplied: {0}'
    authMethodAlreadyDefinedExceptionMessage                          = 'Authentication method already defined: {0}'
    csrfCookieRequiresSecretExceptionMessage                          = "When using cookies for CSRF, a Secret is required. You can either supply a Secret or set the Cookie global secret - (Set-PodeCookieSecret '<value>' -Global)"
    nonEmptyScriptBlockRequiredForPageRouteExceptionMessage           = 'A non-empty ScriptBlock is required to create a Page Route.'
    noPropertiesMutuallyExclusiveExceptionMessage                     = "The parameter 'NoProperties' is mutually exclusive with 'Properties', 'MinProperties' and 'MaxProperties'"
    incompatiblePodeDllExceptionMessage                               = 'An existing incompatible Pode.DLL version {0} is loaded. Version {1} is required. Open a new Powershell/pwsh session and retry.'
    accessMethodDoesNotExistExceptionMessage                          = 'Access method does not exist: {0}.'
    scheduleAlreadyDefinedExceptionMessage                            = '[Schedule] {0}: Schedule already defined.'
    secondsValueCannotBeZeroOrLessExceptionMessage                    = 'Seconds value cannot be 0 or less for {0}'
    pathToLoadNotFoundExceptionMessage                                = 'Path to load {0} not found: {1}'
    failedToImportModuleExceptionMessage                              = 'Failed to import module: {0}'
    endpointNotExistExceptionMessage                                  = "Endpoint with protocol '{0}' and address '{1}' or local address '{2}' does not exist."
    terminatingMessage                                                = 'Terminating...'
    noCommandsSuppliedToConvertToRoutesExceptionMessage               = 'No commands supplied to convert to Routes.'
    invalidTaskTypeExceptionMessage                                   = 'Task type is invalid, expected either [System.Threading.Tasks.Task] or [hashtable]'
    alreadyConnectedToWebSocketExceptionMessage                       = "Already connected to WebSocket with name '{0}'"
    crlfMessageEndCheckOnlySupportedOnTcpEndpointsExceptionMessage    = 'The CRLF message end check is only supported on TCP endpoints.'
    testPodeOAComponentSchemaNeedToBeEnabledExceptionMessage          = "'Test-PodeOAComponentschema' need to be enabled using 'Enable-PodeOpenApi -EnableSchemaValidation'"
    adModuleNotInstalledExceptionMessage                              = 'Active Directory module is not installed.'
    cronExpressionInvalidExceptionMessage                             = 'Cron expression should only consist of 5 parts: {0}'
    noSessionToSetOnResponseExceptionMessage                          = 'There is no session available to set on the response.'
    valueOutOfRangeExceptionMessage                                   = "Value '{0}' for {1} is invalid, should be between {2} and {3}"
    loggingMethodAlreadyDefinedExceptionMessage                       = 'Logging method already defined: {0}'
    noSecretForHmac256ExceptionMessage                                = 'No secret supplied for HMAC256 hash.'
    eolPowerShellWarningMessage                                       = '[WARNING] Pode {0} has not been tested on PowerShell {1}, as it is EOL.'
    runspacePoolFailedToLoadExceptionMessage                          = '{0} RunspacePool failed to load.'
    noEventRegisteredExceptionMessage                                 = 'No {0} event registered: {1}'
    scheduleCannotHaveNegativeLimitExceptionMessage                   = '[Schedule] {0}: Cannot have a negative limit.'
    openApiRequestStyleInvalidForParameterExceptionMessage            = 'OpenApi request Style cannot be {0} for a {1} parameter.'
    openApiDocumentNotCompliantExceptionMessage                       = 'OpenAPI document is not compliant.'
    taskDoesNotExistExceptionMessage                                  = "Task '{0}' does not exist."
    scopedVariableNotFoundExceptionMessage                            = 'Scoped Variable not found: {0}'
    sessionsRequiredForCsrfExceptionMessage                           = 'Sessions are required to use CSRF unless you want to use cookies.'
    nonEmptyScriptBlockRequiredForLoggingMethodExceptionMessage       = 'A non-empty ScriptBlock is required for the logging method.'
    credentialsPassedWildcardForHeadersLiteralExceptionMessage        = 'When Credentials is passed, The * wildcard for Headers will be taken as a literal string and not a wildcard.'
    podeNotInitializedExceptionMessage                                = 'Pode has not been initialized.'
    multipleEndpointsForGuiMessage                                    = 'Multiple endpoints defined, only the first will be used for the GUI.'
    operationIdMustBeUniqueExceptionMessage                           = 'OperationID: {0} has to be unique.'
    invalidJsonJwtExceptionMessage                                    = 'Invalid JSON value found in JWT'
    noAlgorithmInJwtHeaderExceptionMessage                            = 'No algorithm supplied in JWT Header.'
    openApiVersionPropertyMandatoryExceptionMessage                   = 'OpenApi Version property is mandatory.'
    limitValueCannotBeZeroOrLessExceptionMessage                      = 'Limit value cannot be 0 or less for {0}'
    timerDoesNotExistExceptionMessage                                 = "Timer '{0}' does not exist."
    openApiGenerationDocumentErrorMessage                             = 'OpenAPI generation document error:'
    routeAlreadyContainsCustomAccessExceptionMessage                  = "Route '[{0}] {1}' already contains Custom Access with name '{2}'"
    maximumConcurrentWebSocketThreadsLessThanMinimumExceptionMessage  = 'Maximum concurrent WebSocket threads cannot be less than the minimum of {0} but got: {1}'
    middlewareAlreadyDefinedExceptionMessage                          = '[Middleware] {0}: Middleware already defined.'
    invalidAtomCharacterExceptionMessage                              = 'Invalid atom character: {0}'
    invalidCronAtomFormatExceptionMessage                             = 'Invalid cron atom format found: {0}'
    cacheStorageNotFoundForRetrieveExceptionMessage                   = "Cache storage with name '{0}' not found when attempting to retrieve cached item '{1}'"
    headerMustHaveNameInEncodingContextExceptionMessage               = 'Header must have a name when used in an encoding context.'
    moduleDoesNotContainFunctionExceptionMessage                      = 'Module {0} does not contain function {1} to convert to a Route.'
    pathToIconForGuiDoesNotExistExceptionMessage                      = 'Path to the icon for GUI does not exist: {0}'
    noTitleSuppliedForPageExceptionMessage                            = 'No title supplied for {0} page.'
    certificateSuppliedForNonHttpsWssEndpointExceptionMessage         = 'Certificate supplied for non-HTTPS/WSS endpoint.'
    cannotLockNullObjectExceptionMessage                              = 'Cannot lock an object that is null.'
    showPodeGuiOnlyAvailableOnWindowsExceptionMessage                 = 'Show-PodeGui is currently only available for Windows PowerShell and PowerShell 7+ on Windows OS.'
    unlockSecretButNoScriptBlockExceptionMessage                      = 'Unlock secret supplied for custom Secret Vault type, but not Unlock ScriptBlock supplied.'
    invalidIpAddressExceptionMessage                                  = 'The IP address supplied is invalid: {0}'
    maxDaysInvalidExceptionMessage                                    = 'MaxDays must be 0 or greater, but got: {0}'
    noRemoveScriptBlockForVaultExceptionMessage                       = "No Remove ScriptBlock supplied for removing secrets from the vault '{0}'"
    noSecretExpectedForNoSignatureExceptionMessage                    = 'Expected no secret to be supplied for no signature.'
    noCertificateFoundExceptionMessage                                = "No certificate could be found in {0}{1} for '{2}'"
    minValueInvalidExceptionMessage                                   = "Min value '{0}' for {1} is invalid, should be greater than/equal to {2}"
    accessRequiresAuthenticationOnRoutesExceptionMessage              = 'Access requires Authentication to be supplied on Routes.'
    noSecretForHmac384ExceptionMessage                                = 'No secret supplied for HMAC384 hash.'
    windowsLocalAuthSupportIsForWindowsOnlyExceptionMessage           = 'Windows Local Authentication support is for Windows OS only.'
    definitionTagNotDefinedExceptionMessage                           = 'DefinitionTag {0} does not exist.'
    noComponentInDefinitionExceptionMessage                           = 'No component of type {0} named {1} is available in the {2} definition.'
    noSmtpHandlersDefinedExceptionMessage                             = 'No SMTP handlers have been defined.'
    sessionMiddlewareAlreadyInitializedExceptionMessage               = 'Session Middleware has already been initialized.'
    reusableComponentPathItemsNotAvailableInOpenApi30ExceptionMessage = "The 'pathItems' reusable component feature is not available in OpenAPI v3.0."
    wildcardHeadersIncompatibleWithAutoHeadersExceptionMessage        = 'The * wildcard for Headers is incompatible with the AutoHeaders switch.'
    noDataForFileUploadedExceptionMessage                             = "No data for file '{0}' was uploaded in the request."
    sseOnlyConfiguredOnEventStreamAcceptHeaderExceptionMessage        = 'SSE can only be configured on requests with an Accept header value of text/event-stream'
    noSessionAvailableToSaveExceptionMessage                          = 'There is no session available to save.'
    pathParameterRequiresRequiredSwitchExceptionMessage               = "If the parameter location is 'Path', the switch parameter 'Required' is mandatory."
    noOpenApiUrlSuppliedExceptionMessage                              = 'No OpenAPI URL supplied for {0}.'
    maximumConcurrentSchedulesInvalidExceptionMessage                 = 'Maximum concurrent schedules must be >=1 but got: {0}'
    snapinsSupportedOnWindowsPowershellOnlyExceptionMessage           = 'Snapins are only supported on Windows PowerShell.'
    eventViewerLoggingSupportedOnWindowsOnlyExceptionMessage          = 'Event Viewer logging only supported on Windows OS.'
    parametersMutuallyExclusiveExceptionMessage                       = "Parameters '{0}' and '{1}' are mutually exclusive."
    pathItemsFeatureNotSupportedInOpenApi30ExceptionMessage           = 'The PathItems feature is not supported in OpenAPI v3.0.x'
    openApiParameterRequiresNameExceptionMessage                      = 'The OpenApi parameter requires a name to be specified.'
    maximumConcurrentTasksLessThanMinimumExceptionMessage             = 'Maximum concurrent tasks cannot be less than the minimum of {0} but got: {1}'
    noSemaphoreFoundExceptionMessage                                  = "No semaphore found called '{0}'"
    singleValueForIntervalExceptionMessage                            = 'You can only supply a single {0} value when using intervals.'
    jwtNotYetValidExceptionMessage                                    = 'The JWT is not yet valid for use.'
    verbAlreadyDefinedForUrlExceptionMessage                          = '[Verb] {0}: Already defined for {1}'
    noSecretNamedMountedExceptionMessage                              = "No Secret named '{0}' has been mounted."
    moduleOrVersionNotFoundExceptionMessage                           = 'Module or version not found on {0}: {1}@{2}'
    noScriptBlockSuppliedExceptionMessage                             = 'No ScriptBlock supplied.'
    noSecretVaultRegisteredExceptionMessage                           = "No Secret Vault with the name '{0}' has been registered."
    nameRequiredForEndpointIfRedirectToSuppliedExceptionMessage       = 'A Name is required for the endpoint if the RedirectTo parameter is supplied.'
    openApiLicenseObjectRequiresNameExceptionMessage                  = "The OpenAPI object 'license' required the property 'name'. Use -LicenseName parameter."
    sourcePathDoesNotExistForStaticRouteExceptionMessage              = '{0}: The Source path supplied for Static Route does not exist: {1}'
    noNameForWebSocketDisconnectExceptionMessage                      = 'No Name for a WebSocket to disconnect from supplied.'
    certificateExpiredExceptionMessage                                = "The certificate '{0}' has expired: {1}"
    secretVaultUnlockExpiryDateInPastExceptionMessage                 = 'Secret Vault unlock expiry date is in the past (UTC): {0}'
    invalidWebExceptionTypeExceptionMessage                           = 'Exception is of an invalid type, should be either WebException or HttpRequestException, but got: {0}'
    invalidSecretValueTypeExceptionMessage                            = 'Secret value is of an invalid type. Expected types: String, SecureString, HashTable, Byte[], or PSCredential. But got: {0}'
    explicitTlsModeOnlySupportedOnSmtpsTcpsEndpointsExceptionMessage  = 'The Explicit TLS mode is only supported on SMTPS and TCPS endpoints.'
    discriminatorMappingRequiresDiscriminatorPropertyExceptionMessage = "The parameter 'DiscriminatorMapping' can only be used when 'DiscriminatorProperty' is present."
    scriptErrorExceptionMessage                                       = "Error '{0}' in script {1} {2} (line {3}) char {4} executing {5} on {6} object '{7}' Class: {8} BaseClass: {9}"
    cannotSupplyIntervalForQuarterExceptionMessage                    = 'Cannot supply interval value for every quarter.'
    scheduleEndTimeMustBeInFutureExceptionMessage                     = '[Schedule] {0}: The EndTime value must be in the future.'
    invalidJwtSignatureSuppliedExceptionMessage                       = 'Invalid JWT signature supplied.'
    noSetScriptBlockForVaultExceptionMessage                          = "No Set ScriptBlock supplied for updating/creating secrets in the vault '{0}'"
    accessMethodNotExistForMergingExceptionMessage                    = 'Access method does not exist for merging: {0}'
    defaultAuthNotInListExceptionMessage                              = "The Default Authentication '{0}' is not in the Authentication list supplied."
    parameterHasNoNameExceptionMessage                                = "The Parameter has no name. Please give this component a name using the 'Name' parameter."
    methodPathAlreadyDefinedForUrlExceptionMessage                    = '[{0}] {1}: Already defined for {2}'
    fileWatcherAlreadyDefinedExceptionMessage                         = "A File Watcher named '{0}' has already been defined."
    noServiceHandlersDefinedExceptionMessage                          = 'No Service handlers have been defined.'
    secretRequiredForCustomSessionStorageExceptionMessage             = 'A Secret is required when using custom session storage.'
    secretManagementModuleNotInstalledExceptionMessage                = 'Microsoft.PowerShell.SecretManagement module not installed.'
    noPathSuppliedForRouteExceptionMessage                            = 'No Path supplied for the Route.'
    validationOfAnyOfSchemaNotSupportedExceptionMessage               = "Validation of a schema that includes 'anyof' is not supported."
    iisAuthSupportIsForWindowsOnlyExceptionMessage                    = 'IIS Authentication support is for Windows OS only.'
    oauth2InnerSchemeInvalidExceptionMessage                          = 'OAuth2 InnerScheme can only be one of either Basic or Form authentication, but got: {0}'
    noRoutePathSuppliedForPageExceptionMessage                        = 'No route path supplied for {0} page.'
    cacheStorageNotFoundForExistsExceptionMessage                     = "Cache storage with name '{0}' not found when attempting to check if cached item '{1}' exists."
    handlerAlreadyDefinedExceptionMessage                             = '[{0}] {1}: Handler already defined.'
    sessionsNotConfiguredExceptionMessage                             = 'Sessions have not been configured.'
    propertiesTypeObjectAssociationExceptionMessage                   = 'Only properties of type Object can be associated with {0}.'
    sessionsRequiredForSessionPersistentAuthExceptionMessage          = 'Sessions are required to use session persistent authentication.'
    invalidPathWildcardOrDirectoryExceptionMessage                    = 'The Path supplied cannot be a wildcard or a directory: {0}'
    accessMethodAlreadyDefinedExceptionMessage                        = 'Access method already defined: {0}'
    parametersValueOrExternalValueMandatoryExceptionMessage           = "Parameters 'Value' or 'ExternalValue' are mandatory"
    maximumConcurrentTasksInvalidExceptionMessage                     = 'Maximum concurrent tasks must be >=1 but got: {0}'
    cannotCreatePropertyWithoutTypeExceptionMessage                   = 'Cannot create the property because no type is defined.'
    authMethodNotExistForMergingExceptionMessage                      = 'Authentication method does not exist for merging: {0}'
    maxValueInvalidExceptionMessage                                   = "Max value '{0}' for {1} is invalid, should be less than/equal to {2}"
    endpointAlreadyDefinedExceptionMessage                            = "An endpoint named '{0}' has already been defined."
    eventAlreadyRegisteredExceptionMessage                            = '{0} event already registered: {1}'
    parameterNotSuppliedInRequestExceptionMessage                     = "A parameter called '{0}' was not supplied in the request or has no data available."
    cacheStorageNotFoundForSetExceptionMessage                        = "Cache storage with name '{0}' not found when attempting to set cached item '{1}'"
    methodPathAlreadyDefinedExceptionMessage                          = '[{0}] {1}: Already defined.'
    errorLoggingAlreadyEnabledExceptionMessage                        = 'Error Logging has already been enabled.'
    valueForUsingVariableNotFoundExceptionMessage                     = "Value for '`$using:{0}' could not be found."
    rapidPdfDoesNotSupportOpenApi31ExceptionMessage                   = "The Document tool RapidPdf doesn't support OpenAPI 3.1"
    oauth2ClientSecretRequiredExceptionMessage                        = 'OAuth2 requires a Client Secret when not using PKCE.'
    invalidBase64JwtExceptionMessage                                  = 'Invalid Base64 encoded value found in JWT'
    noSessionToCalculateDataHashExceptionMessage                      = 'No session available to calculate data hash.'
    cacheStorageNotFoundForRemoveExceptionMessage                     = "Cache storage with name '{0}' not found when attempting to remove cached item '{1}'"
    csrfMiddlewareNotInitializedExceptionMessage                      = 'CSRF Middleware has not been initialized.'
    infoTitleMandatoryMessage                                         = 'info.title is mandatory.'
    typeCanOnlyBeAssociatedWithObjectExceptionMessage                 = 'Type {0} can only be associated with an Object.'
    userFileDoesNotExistExceptionMessage                              = 'The user file does not exist: {0}'
    routeParameterNeedsValidScriptblockExceptionMessage               = 'The Route parameter needs a valid, not empty, scriptblock.'
    nextTriggerCalculationErrorExceptionMessage                       = 'Looks like something went wrong trying to calculate the next trigger datetime: {0}'
    cannotLockValueTypeExceptionMessage                               = 'Cannot lock a [ValueType]'
    failedToCreateOpenSslCertExceptionMessage                         = 'Failed to create OpenSSL cert: {0}'
    jwtExpiredExceptionMessage                                        = 'The JWT has expired.'
    openingGuiMessage                                                 = 'Opening the GUI.'
    multiTypePropertiesRequireOpenApi31ExceptionMessage               = 'Multi-type properties require OpenApi Version 3.1 or above.'
    noNameForWebSocketRemoveExceptionMessage                          = 'No Name for a WebSocket to remove supplied.'
    maxSizeInvalidExceptionMessage                                    = 'MaxSize must be 0 or greater, but got: {0}'
    iisShutdownMessage                                                = '(IIS Shutdown)'
    cannotUnlockValueTypeExceptionMessage                             = 'Cannot unlock a [ValueType]'
    noJwtSignatureForAlgorithmExceptionMessage                        = 'No JWT signature supplied for {0}.'
    maximumConcurrentWebSocketThreadsInvalidExceptionMessage          = 'Maximum concurrent WebSocket threads must be >=1 but got: {0}'
    acknowledgeMessageOnlySupportedOnSmtpTcpEndpointsExceptionMessage = 'The Acknowledge message is only supported on SMTP and TCP endpoints.'
    failedToConnectToUrlExceptionMessage                              = 'Failed to connect to URL: {0}'
    failedToAcquireMutexOwnershipExceptionMessage                     = 'Failed to acquire mutex ownership. Mutex name: {0}'
    sessionsRequiredForOAuth2WithPKCEExceptionMessage                 = 'Sessions are required to use OAuth2 with PKCE'
    failedToConnectToWebSocketExceptionMessage                        = 'Failed to connect to WebSocket: {0}'
    unsupportedObjectExceptionMessage                                 = 'Unsupported object'
    failedToParseAddressExceptionMessage                              = "Failed to parse '{0}' as a valid IP/Host:Port address"
    mustBeRunningWithAdminPrivilegesExceptionMessage                  = 'Must be running with administrator privileges to listen on non-localhost addresses.'
    specificationMessage                                              = 'Specification'
    cacheStorageNotFoundForClearExceptionMessage                      = "Cache storage with name '{0}' not found when attempting to clear the cache."
    restartingServerMessage                                           = 'Restarting server...'
    cannotSupplyIntervalWhenEveryIsNoneExceptionMessage               = "Cannot supply an interval when the parameter 'Every' is set to None."
    unsupportedJwtAlgorithmExceptionMessage                           = 'The JWT algorithm is not currently supported: {0}'
    websocketsNotConfiguredForSignalMessagesExceptionMessage          = 'WebSockets have not been configured to send signal messages.'
    invalidLogicTypeInHashtableMiddlewareExceptionMessage             = 'A Hashtable Middleware supplied has an invalid Logic type. Expected ScriptBlock, but got: {0}'
    maximumConcurrentSchedulesLessThanMinimumExceptionMessage         = 'Maximum concurrent schedules cannot be less than the minimum of {0} but got: {1}'
    failedToAcquireSemaphoreOwnershipExceptionMessage                 = 'Failed to acquire semaphore ownership. Semaphore name: {0}'
    propertiesParameterWithoutNameExceptionMessage                    = 'The Properties parameters cannot be used if the Property has no name.'
    customSessionStorageMethodNotImplementedExceptionMessage          = "The custom session storage does not implement the required '{0}()' method."
    authenticationMethodDoesNotExistExceptionMessage                  = 'Authentication method does not exist: {0}'
    webhooksFeatureNotSupportedInOpenApi30ExceptionMessage            = 'The Webhooks feature is not supported in OpenAPI v3.0.x'
    invalidContentTypeForSchemaExceptionMessage                       = "Invalid 'content-type' found for schema: {0}"
    noUnlockScriptBlockForVaultExceptionMessage                       = "No Unlock ScriptBlock supplied for unlocking the vault '{0}'"
    definitionTagMessage                                              = 'Definition {0}:'
    failedToOpenRunspacePoolExceptionMessage                          = 'Failed to open RunspacePool: {0}'
    failedToCloseRunspacePoolExceptionMessage                         = 'Failed to close RunspacePool: {0}'
    verbNoLogicPassedExceptionMessage                                 = '[Verb] {0}: No logic passed'
    noMutexFoundExceptionMessage                                      = "No mutex found called '{0}'"
    documentationMessage                                              = 'Documentation'
    timerAlreadyDefinedExceptionMessage                               = '[Timer] {0}: Timer already defined.'
    invalidPortExceptionMessage                                       = 'The port cannot be negative: {0}'
    viewsFolderNameAlreadyExistsExceptionMessage                      = 'The Views folder name already exists: {0}'
    noNameForWebSocketResetExceptionMessage                           = 'No Name for a WebSocket to reset supplied.'
    mergeDefaultAuthNotInListExceptionMessage                         = "The MergeDefault Authentication '{0}' is not in the Authentication list supplied."
    descriptionRequiredExceptionMessage                               = 'A Description is required for Path:{0} Response:{1}'
    pageNameShouldBeAlphaNumericExceptionMessage                      = 'The Page name should be a valid Alphanumeric value: {0}'
    defaultValueNotBooleanOrEnumExceptionMessage                      = 'The default value is not a boolean and is not part of the enum.'
    openApiComponentSchemaDoesNotExistExceptionMessage                = "The OpenApi component schema {0} doesn't exist."
    timerParameterMustBeGreaterThanZeroExceptionMessage               = '[Timer] {0}: {1} must be greater than 0.'
    taskTimedOutExceptionMessage                                      = 'Task has timed out after {0}ms.'
    scheduleStartTimeAfterEndTimeExceptionMessage                     = '[Schedule] {0}: Cannot have a StartTime after the EndTime'
    infoVersionMandatoryMessage                                       = 'info.version is mandatory.'
    cannotUnlockNullObjectExceptionMessage                            = 'Cannot unlock an object that is null.'
    nonEmptyScriptBlockRequiredForCustomAuthExceptionMessage          = 'A non-empty ScriptBlock is required for the Custom authentication scheme.'
    nonEmptyScriptBlockRequiredForAuthMethodExceptionMessage          = 'A non-empty ScriptBlock is required for the authentication method.'
    validationOfOneOfSchemaNotSupportedExceptionMessage               = "Validation of a schema that includes 'oneof' is not supported."
    routeParameterCannotBeNullExceptionMessage                        = "The parameter 'Route' cannot be null."
    cacheStorageAlreadyExistsExceptionMessage                         = "Cache Storage with name '{0}' already exists."
    loggingMethodRequiresValidScriptBlockExceptionMessage             = "The supplied output Method for the '{0}' Logging method requires a valid ScriptBlock."
    scopedVariableAlreadyDefinedExceptionMessage                      = 'Scoped Variable already defined: {0}'
    oauth2RequiresAuthorizeUrlExceptionMessage                        = "OAuth2 requires an 'AuthoriseUrl' property to be supplied."
    pathNotExistExceptionMessage                                      = 'Path does not exist: {0}'
    noDomainServerNameForWindowsAdAuthExceptionMessage                = 'No domain server name has been supplied for Windows AD authentication'
    suppliedDateAfterScheduleEndTimeExceptionMessage                  = 'Supplied date is after the end time of the schedule at {0}'
    wildcardMethodsIncompatibleWithAutoMethodsExceptionMessage        = 'The * wildcard for Methods is incompatible with the AutoMethods switch.'
    cannotSupplyIntervalForYearExceptionMessage                       = 'Cannot supply interval value for every year.'
    missingComponentsMessage                                          = 'Missing component(s)'
    invalidStrictTransportSecurityDurationExceptionMessage            = 'Invalid Strict-Transport-Security duration supplied: {0}. It should be greater than 0.'
    noSecretForHmac512ExceptionMessage                                = 'No secret supplied for HMAC512 hash.'
    daysInMonthExceededExceptionMessage                               = '{0} only has {1} days, but {2} was supplied.'
    nonEmptyScriptBlockRequiredForCustomLoggingExceptionMessage       = 'A non-empty ScriptBlock is required for the Custom logging output method.'
    encodingAttributeOnlyAppliesToMultipartExceptionMessage           = 'The encoding attribute only applies to multipart and application/x-www-form-urlencoded request bodies.'
    suppliedDateBeforeScheduleStartTimeExceptionMessage               = 'Supplied date is before the start time of the schedule at {0}'
    unlockSecretRequiredExceptionMessage                              = "An 'UnlockSecret' property is required when using Microsoft.PowerShell.SecretStore"
    noLogicPassedForMethodRouteExceptionMessage                       = '[{0}] {1}: No logic passed.'
    bodyParserAlreadyDefinedForContentTypeExceptionMessage            = 'A body-parser is already defined for the {0} content-type.'
    invalidJwtSuppliedExceptionMessage                                = 'Invalid JWT supplied.'
    sessionsRequiredForFlashMessagesExceptionMessage                  = 'Sessions are required to use Flash messages.'
    semaphoreAlreadyExistsExceptionMessage                            = 'A semaphore with the following name already exists: {0}'
    invalidJwtHeaderAlgorithmSuppliedExceptionMessage                 = 'Invalid JWT header algorithm supplied.'
    oauth2ProviderDoesNotSupportPasswordGrantTypeExceptionMessage     = "The OAuth2 provider does not support the 'password' grant_type required by using an InnerScheme."
    invalidAliasFoundExceptionMessage                                 = 'Invalid {0} alias found: {1}'
    scheduleDoesNotExistExceptionMessage                              = "Schedule '{0}' does not exist."
    accessMethodNotExistExceptionMessage                              = 'Access method does not exist: {0}'
    oauth2ProviderDoesNotSupportCodeResponseTypeExceptionMessage      = "The OAuth2 provider does not support the 'code' response_type."
    untestedPowerShellVersionWarningMessage                           = '[WARNING] Pode {0} has not been tested on PowerShell {1}, as it was not available when Pode was released.'
    secretVaultAlreadyRegisteredAutoImportExceptionMessage            = "A Secret Vault with the name '{0}' has already been registered while auto-importing Secret Vaults."
    schemeRequiresValidScriptBlockExceptionMessage                    = "The supplied scheme for the '{0}' authentication validator requires a valid ScriptBlock."
    serverLoopingMessage                                              = 'Server looping every {0}secs'
    certificateThumbprintsNameSupportedOnWindowsExceptionMessage      = 'Certificate Thumbprints/Name are only supported on Windows OS.'
    sseConnectionNameRequiredExceptionMessage                         = "An SSE connection Name is required, either from -Name or `$WebEvent.Sse.Name"
    invalidMiddlewareTypeExceptionMessage                             = 'One of the Middlewares supplied is an invalid type. Expected either a ScriptBlock or Hashtable, but got: {0}'
    noSecretForJwtSignatureExceptionMessage                           = 'No secret supplied for JWT signature.'
    modulePathDoesNotExistExceptionMessage                            = 'The module path does not exist: {0}'
    taskAlreadyDefinedExceptionMessage                                = '[Task] {0}: Task already defined.'
    verbAlreadyDefinedExceptionMessage                                = '[Verb] {0}: Already defined'
    clientCertificatesOnlySupportedOnHttpsEndpointsExceptionMessage   = 'Client certificates are only supported on HTTPS endpoints.'
    endpointNameNotExistExceptionMessage                              = "Endpoint with name '{0}' does not exist."
    middlewareNoLogicSuppliedExceptionMessage                         = '[Middleware]: No logic supplied in ScriptBlock.'
    scriptBlockRequiredForMergingUsersExceptionMessage                = 'A Scriptblock for merging multiple authenticated users into 1 object is required When Valid is All.'
    secretVaultAlreadyRegisteredExceptionMessage                      = "A Secret Vault with the name '{0}' has already been registered{1}."
    deprecatedTitleVersionDescriptionWarningMessage                   = "WARNING: Title, Version, and Description on 'Enable-PodeOpenApi' are deprecated. Please use 'Add-PodeOAInfo' instead."
    undefinedOpenApiReferencesMessage                                 = 'Undefined OpenAPI References:'
    doneMessage                                                       = 'Done'
    swaggerEditorDoesNotSupportOpenApi31ExceptionMessage              = "This version on Swagger-Editor doesn't support OpenAPI 3.1"
    durationMustBeZeroOrGreaterExceptionMessage                       = 'Duration must be 0 or greater, but got: {0}s'
    viewsPathDoesNotExistExceptionMessage                             = 'The Views path does not exist: {0}'
    discriminatorIncompatibleWithAllOfExceptionMessage                = "The parameter 'Discriminator' is incompatible with 'allOf'."
    noNameForWebSocketSendMessageExceptionMessage                     = 'No Name for a WebSocket to send message to supplied.'
    hashtableMiddlewareNoLogicExceptionMessage                        = 'A Hashtable Middleware supplied has no Logic defined.'
    openApiInfoMessage                                                = 'OpenAPI Info:'
    invalidSchemeForAuthValidatorExceptionMessage                     = "The supplied '{0}' Scheme for the '{1}' authentication validator requires a valid ScriptBlock."
    sseFailedToBroadcastExceptionMessage                              = 'SSE failed to broadcast due to defined SSE broadcast level for {0}: {1}'
    adModuleWindowsOnlyExceptionMessage                               = 'Active Directory module only available on Windows OS.'
    requestLoggingAlreadyEnabledExceptionMessage                      = 'Request Logging has already been enabled.'
    invalidAccessControlMaxAgeDurationExceptionMessage                = 'Invalid Access-Control-Max-Age duration supplied: {0}. Should be greater than 0.'
    openApiDefinitionAlreadyExistsExceptionMessage                    = 'OpenAPI definition named {0} already exists.'
    renamePodeOADefinitionTagExceptionMessage                         = "Rename-PodeOADefinitionTag cannot be used inside a Select-PodeOADefinition 'ScriptBlock'."
    taskProcessDoesNotExistExceptionMessage                           = 'Task process does not exist: {0}'
    scheduleProcessDoesNotExistExceptionMessage                       = 'Schedule process does not exist: {0}'
    definitionTagChangeNotAllowedExceptionMessage                     = 'Definition Tag for a Route cannot be changed.'
    getRequestBodyNotAllowedExceptionMessage                          = '{0} operations cannot have a Request Body.'
    fnDoesNotAcceptArrayAsPipelineInputExceptionMessage               = "The function '{0}' does not accept an array as pipeline input."
    unsupportedStreamCompressionEncodingExceptionMessage              = 'Unsupported stream compression encoding: {0}'
}
src\Locales\en\Pode.psd1
@{
    schemaValidationRequiresPowerShell610ExceptionMessage             = 'Schema validation requires PowerShell version 6.1.0 or greater.'
    customAccessPathOrScriptBlockRequiredExceptionMessage             = 'A Path or ScriptBlock is required for sourcing the Custom access values.'
    operationIdMustBeUniqueForArrayExceptionMessage                   = 'OperationID: {0} has to be unique and cannot be applied to an array.'
    endpointNotDefinedForRedirectingExceptionMessage                  = "An endpoint named '{0}' has not been defined for redirecting."
    filesHaveChangedMessage                                           = 'The following files have changed:'
    iisAspnetcoreTokenMissingExceptionMessage                         = 'IIS ASPNETCORE_TOKEN is missing.'
    minValueGreaterThanMaxExceptionMessage                            = 'Min value for {0} should not be greater than the max value.'
    noLogicPassedForRouteExceptionMessage                             = 'No logic passed for Route: {0}'
    scriptPathDoesNotExistExceptionMessage                            = 'The script path does not exist: {0}'
    mutexAlreadyExistsExceptionMessage                                = 'A mutex with the following name already exists: {0}'
    listeningOnEndpointsMessage                                       = 'Listening on the following {0} endpoint(s) [{1} thread(s)]:'
    unsupportedFunctionInServerlessContextExceptionMessage            = 'The {0} function is not supported in a serverless context.'
    expectedNoJwtSignatureSuppliedExceptionMessage                    = 'Expected no JWT signature to be supplied.'
    secretAlreadyMountedExceptionMessage                              = "A Secret with the name '{0}' has already been mounted."
    failedToAcquireLockExceptionMessage                               = 'Failed to acquire a lock on the object.'
    noPathSuppliedForStaticRouteExceptionMessage                      = '[{0}]: No Path supplied for Static Route.'
    invalidHostnameSuppliedExceptionMessage                           = 'Invalid hostname supplied: {0}'
    authMethodAlreadyDefinedExceptionMessage                          = 'Authentication method already defined: {0}'
    csrfCookieRequiresSecretExceptionMessage                          = "When using cookies for CSRF, a Secret is required. You can either supply a Secret or set the Cookie global secret - (Set-PodeCookieSecret '<value>' -Global)"
    nonEmptyScriptBlockRequiredForAuthMethodExceptionMessage          = 'A non-empty ScriptBlock is required for the authentication method.'
    nonEmptyScriptBlockRequiredForPageRouteExceptionMessage           = 'A non-empty ScriptBlock is required to create a Page Route.'
    noPropertiesMutuallyExclusiveExceptionMessage                     = "The parameter 'NoProperties' is mutually exclusive with 'Properties', 'MinProperties' and 'MaxProperties'"
    incompatiblePodeDllExceptionMessage                               = 'An existing incompatible Pode.DLL version {0} is loaded. Version {1} is required. Open a new PowerShell/pwsh session and retry.'
    accessMethodDoesNotExistExceptionMessage                          = 'Access method does not exist: {0}.'
    scheduleAlreadyDefinedExceptionMessage                            = '[Schedule] {0}: Schedule already defined.'
    secondsValueCannotBeZeroOrLessExceptionMessage                    = 'Seconds value cannot be 0 or less for {0}'
    pathToLoadNotFoundExceptionMessage                                = 'Path to load {0} not found: {1}'
    failedToImportModuleExceptionMessage                              = 'Failed to import module: {0}'
    endpointNotExistExceptionMessage                                  = "Endpoint with protocol '{0}' and address '{1}' or local address '{2}' does not exist."
    terminatingMessage                                                = 'Terminating...'
    noCommandsSuppliedToConvertToRoutesExceptionMessage               = 'No commands supplied to convert to Routes.'
    invalidTaskTypeExceptionMessage                                   = 'Task type is invalid, expected either [System.Threading.Tasks.Task] or [hashtable]'
    alreadyConnectedToWebSocketExceptionMessage                       = "Already connected to WebSocket with name '{0}'"
    crlfMessageEndCheckOnlySupportedOnTcpEndpointsExceptionMessage    = 'The CRLF message end check is only supported on TCP endpoints.'
    testPodeOAComponentSchemaNeedToBeEnabledExceptionMessage          = "'Test-PodeOAComponentSchema' need to be enabled using 'Enable-PodeOpenApi -EnableSchemaValidation'"
    adModuleNotInstalledExceptionMessage                              = 'Active Directory module is not installed.'
    cronExpressionInvalidExceptionMessage                             = 'Cron expression should only consist of 5 parts: {0}'
    noSessionToSetOnResponseExceptionMessage                          = 'There is no session available to set on the response.'
    valueOutOfRangeExceptionMessage                                   = "Value '{0}' for {1} is invalid, should be between {2} and {3}"
    loggingMethodAlreadyDefinedExceptionMessage                       = 'Logging method already defined: {0}'
    noSecretForHmac256ExceptionMessage                                = 'No secret supplied for HMAC256 hash.'
    eolPowerShellWarningMessage                                       = '[WARNING] Pode {0} has not been tested on PowerShell {1}, as it is EOL.'
    runspacePoolFailedToLoadExceptionMessage                          = '{0} RunspacePool failed to load.'
    noEventRegisteredExceptionMessage                                 = 'No {0} event registered: {1}'
    scheduleCannotHaveNegativeLimitExceptionMessage                   = '[Schedule] {0}: Cannot have a negative limit.'
    openApiRequestStyleInvalidForParameterExceptionMessage            = 'OpenApi request Style cannot be {0} for a {1} parameter.'
    openApiDocumentNotCompliantExceptionMessage                       = 'OpenAPI document is not compliant.'
    taskDoesNotExistExceptionMessage                                  = "Task '{0}' does not exist."
    scopedVariableNotFoundExceptionMessage                            = 'Scoped Variable not found: {0}'
    sessionsRequiredForCsrfExceptionMessage                           = 'Sessions are required to use CSRF unless you want to use cookies.'
    nonEmptyScriptBlockRequiredForLoggingMethodExceptionMessage       = 'A non-empty ScriptBlock is required for the logging method.'
    credentialsPassedWildcardForHeadersLiteralExceptionMessage        = 'When Credentials is passed, The * wildcard for Headers will be taken as a literal string and not a wildcard.'
    podeNotInitializedExceptionMessage                                = 'Pode has not been initialised.'
    multipleEndpointsForGuiMessage                                    = 'Multiple endpoints defined, only the first will be used for the GUI.'
    operationIdMustBeUniqueExceptionMessage                           = 'OperationID: {0} has to be unique.'
    invalidJsonJwtExceptionMessage                                    = 'Invalid JSON value found in JWT'
    noAlgorithmInJwtHeaderExceptionMessage                            = 'No algorithm supplied in JWT Header.'
    openApiVersionPropertyMandatoryExceptionMessage                   = 'OpenApi Version property is mandatory.'
    limitValueCannotBeZeroOrLessExceptionMessage                      = 'Limit value cannot be 0 or less for {0}'
    timerDoesNotExistExceptionMessage                                 = "Timer '{0}' does not exist."
    openApiGenerationDocumentErrorMessage                             = 'OpenAPI generation document error:'
    routeAlreadyContainsCustomAccessExceptionMessage                  = "Route '[{0}] {1}' already contains Custom Access with name '{2}'"
    maximumConcurrentWebSocketThreadsLessThanMinimumExceptionMessage  = 'Maximum concurrent WebSocket threads cannot be less than the minimum of {0} but got: {1}'
    middlewareAlreadyDefinedExceptionMessage                          = '[Middleware] {0}: Middleware already defined.'
    invalidAtomCharacterExceptionMessage                              = 'Invalid atom character: {0}'
    invalidCronAtomFormatExceptionMessage                             = 'Invalid cron atom format found: {0}'
    cacheStorageNotFoundForRetrieveExceptionMessage                   = "Cache storage with name '{0}' not found when attempting to retrieve cached item '{1}'"
    headerMustHaveNameInEncodingContextExceptionMessage               = 'Header must have a name when used in an encoding context.'
    moduleDoesNotContainFunctionExceptionMessage                      = 'Module {0} does not contain function {1} to convert to a Route.'
    pathToIconForGuiDoesNotExistExceptionMessage                      = 'Path to the icon for GUI does not exist: {0}'
    noTitleSuppliedForPageExceptionMessage                            = 'No title supplied for {0} page.'
    certificateSuppliedForNonHttpsWssEndpointExceptionMessage         = 'Certificate supplied for non-HTTPS/WSS endpoint.'
    cannotLockNullObjectExceptionMessage                              = 'Cannot lock an object that is null.'
    showPodeGuiOnlyAvailableOnWindowsExceptionMessage                 = 'Show-PodeGui is currently only available for Windows PowerShell and PowerShell 7+ on Windows OS.'
    unlockSecretButNoScriptBlockExceptionMessage                      = 'Unlock secret supplied for custom Secret Vault type, but not Unlock ScriptBlock supplied.'
    invalidIpAddressExceptionMessage                                  = 'The IP address supplied is invalid: {0}'
    maxDaysInvalidExceptionMessage                                    = 'MaxDays must be 0 or greater, but got: {0}'
    noRemoveScriptBlockForVaultExceptionMessage                       = "No Remove ScriptBlock supplied for removing secrets from the vault '{0}'"
    noSecretExpectedForNoSignatureExceptionMessage                    = 'Expected no secret to be supplied for no signature.'
    noCertificateFoundExceptionMessage                                = "No certificate could be found in {0}{1} for '{2}'"
    minValueInvalidExceptionMessage                                   = "Min value '{0}' for {1} is invalid, should be greater than/equal to {2}"
    accessRequiresAuthenticationOnRoutesExceptionMessage              = 'Access requires Authentication to be supplied on Routes.'
    noSecretForHmac384ExceptionMessage                                = 'No secret supplied for HMAC384 hash.'
    windowsLocalAuthSupportIsForWindowsOnlyExceptionMessage           = 'Windows Local Authentication support is for Windows OS only.'
    definitionTagNotDefinedExceptionMessage                           = 'DefinitionTag {0} does not exist.'
    noComponentInDefinitionExceptionMessage                           = 'No component of type {0} named {1} is available in the {2} definition.'
    noSmtpHandlersDefinedExceptionMessage                             = 'No SMTP handlers have been defined.'
    sessionMiddlewareAlreadyInitializedExceptionMessage               = 'Session Middleware has already been initialised.'
    reusableComponentPathItemsNotAvailableInOpenApi30ExceptionMessage = "The 'pathItems' reusable component feature is not available in OpenAPI v3.0."
    wildcardHeadersIncompatibleWithAutoHeadersExceptionMessage        = 'The * wildcard for Headers is incompatible with the AutoHeaders switch.'
    noDataForFileUploadedExceptionMessage                             = "No data for file '{0}' was uploaded in the request."
    sseOnlyConfiguredOnEventStreamAcceptHeaderExceptionMessage        = 'SSE can only be configured on requests with an Accept header value of text/event-stream'
    noSessionAvailableToSaveExceptionMessage                          = 'There is no session available to save.'
    pathParameterRequiresRequiredSwitchExceptionMessage               = "If the parameter location is 'Path', the switch parameter 'Required' is mandatory."
    noOpenApiUrlSuppliedExceptionMessage                              = 'No OpenAPI URL supplied for {0}.'
    maximumConcurrentSchedulesInvalidExceptionMessage                 = 'Maximum concurrent schedules must be >=1 but got: {0}'
    snapinsSupportedOnWindowsPowershellOnlyExceptionMessage           = 'Snapins are only supported on Windows PowerShell.'
    eventViewerLoggingSupportedOnWindowsOnlyExceptionMessage          = 'Event Viewer logging only supported on Windows OS.'
    parametersMutuallyExclusiveExceptionMessage                       = "Parameters '{0}' and '{1}' are mutually exclusive."
    pathItemsFeatureNotSupportedInOpenApi30ExceptionMessage           = 'The PathItems feature is not supported in OpenAPI v3.0.x'
    openApiParameterRequiresNameExceptionMessage                      = 'The OpenApi parameter requires a name to be specified.'
    maximumConcurrentTasksLessThanMinimumExceptionMessage             = 'Maximum concurrent tasks cannot be less than the minimum of {0} but got: {1}'
    noSemaphoreFoundExceptionMessage                                  = "No semaphore found called '{0}'"
    singleValueForIntervalExceptionMessage                            = 'You can only supply a single {0} value when using intervals.'
    jwtNotYetValidExceptionMessage                                    = 'The JWT is not yet valid for use.'
    verbAlreadyDefinedForUrlExceptionMessage                          = '[Verb] {0}: Already defined for {1}'
    noSecretNamedMountedExceptionMessage                              = "No Secret named '{0}' has been mounted."
    moduleOrVersionNotFoundExceptionMessage                           = 'Module or version not found on {0}: {1}@{2}'
    noScriptBlockSuppliedExceptionMessage                             = 'No ScriptBlock supplied.'
    noSecretVaultRegisteredExceptionMessage                           = "No Secret Vault with the name '{0}' has been registered."
    nameRequiredForEndpointIfRedirectToSuppliedExceptionMessage       = 'A Name is required for the endpoint if the RedirectTo parameter is supplied.'
    openApiLicenseObjectRequiresNameExceptionMessage                  = "The OpenAPI object 'license' required the property 'name'. Use -LicenseName parameter."
    sourcePathDoesNotExistForStaticRouteExceptionMessage              = '{0}: The Source path supplied for Static Route does not exist: {1}'
    noNameForWebSocketDisconnectExceptionMessage                      = 'No Name for a WebSocket to disconnect from supplied.'
    certificateExpiredExceptionMessage                                = "The certificate '{0}' has expired: {1}"
    secretVaultUnlockExpiryDateInPastExceptionMessage                 = 'Secret Vault unlock expiry date is in the past (UTC): {0}'
    invalidWebExceptionTypeExceptionMessage                           = 'Exception is of an invalid type, should be either WebException or HttpRequestException, but got: {0}'
    invalidSecretValueTypeExceptionMessage                            = 'Secret value is of an invalid type. Expected types: String, SecureString, HashTable, Byte[], or PSCredential. But got: {0}'
    explicitTlsModeOnlySupportedOnSmtpsTcpsEndpointsExceptionMessage  = 'The Explicit TLS mode is only supported on SMTPS and TCPS endpoints.'
    discriminatorMappingRequiresDiscriminatorPropertyExceptionMessage = "The parameter 'DiscriminatorMapping' can only be used when 'DiscriminatorProperty' is present."
    scriptErrorExceptionMessage                                       = "Error '{0}' in script {1} {2} (line {3}) char {4} executing {5} on {6} object '{7}' Class: {8} BaseClass: {9}"
    cannotSupplyIntervalForQuarterExceptionMessage                    = 'Cannot supply interval value for every quarter.'
    scheduleEndTimeMustBeInFutureExceptionMessage                     = '[Schedule] {0}: The EndTime value must be in the future.'
    invalidJwtSignatureSuppliedExceptionMessage                       = 'Invalid JWT signature supplied.'
    noSetScriptBlockForVaultExceptionMessage                          = "No Set ScriptBlock supplied for updating/creating secrets in the vault '{0}'"
    accessMethodNotExistForMergingExceptionMessage                    = 'Access method does not exist for merging: {0}'
    defaultAuthNotInListExceptionMessage                              = "The Default Authentication '{0}' is not in the Authentication list supplied."
    parameterHasNoNameExceptionMessage                                = "The Parameter has no name. Please give this component a name using the 'Name' parameter."
    methodPathAlreadyDefinedForUrlExceptionMessage                    = '[{0}] {1}: Already defined for {2}'
    fileWatcherAlreadyDefinedExceptionMessage                         = "A File Watcher named '{0}' has already been defined."
    noServiceHandlersDefinedExceptionMessage                          = 'No Service handlers have been defined.'
    secretRequiredForCustomSessionStorageExceptionMessage             = 'A Secret is required when using custom session storage.'
    secretManagementModuleNotInstalledExceptionMessage                = 'Microsoft.PowerShell.SecretManagement module not installed.'
    noPathSuppliedForRouteExceptionMessage                            = 'No Path supplied for the Route.'
    validationOfAnyOfSchemaNotSupportedExceptionMessage               = "Validation of a schema that includes 'anyof' is not supported."
    iisAuthSupportIsForWindowsOnlyExceptionMessage                    = 'IIS Authentication support is for Windows OS only.'
    oauth2InnerSchemeInvalidExceptionMessage                          = 'OAuth2 InnerScheme can only be one of either Basic or Form authentication, but got: {0}'
    noRoutePathSuppliedForPageExceptionMessage                        = 'No route path supplied for {0} page.'
    cacheStorageNotFoundForExistsExceptionMessage                     = "Cache storage with name '{0}' not found when attempting to check if cached item '{1}' exists."
    handlerAlreadyDefinedExceptionMessage                             = '[{0}] {1}: Handler already defined.'
    sessionsNotConfiguredExceptionMessage                             = 'Sessions have not been configured.'
    propertiesTypeObjectAssociationExceptionMessage                   = 'Only properties of type Object can be associated with {0}.'
    sessionsRequiredForSessionPersistentAuthExceptionMessage          = 'Sessions are required to use session persistent authentication.'
    invalidPathWildcardOrDirectoryExceptionMessage                    = 'The Path supplied cannot be a wildcard or a directory: {0}'
    accessMethodAlreadyDefinedExceptionMessage                        = 'Access method already defined: {0}'
    parametersValueOrExternalValueMandatoryExceptionMessage           = "Parameters 'Value' or 'ExternalValue' are mandatory"
    maximumConcurrentTasksInvalidExceptionMessage                     = 'Maximum concurrent tasks must be >=1 but got: {0}'
    cannotCreatePropertyWithoutTypeExceptionMessage                   = 'Cannot create the property because no type is defined.'
    authMethodNotExistForMergingExceptionMessage                      = 'Authentication method does not exist for merging: {0}'
    maxValueInvalidExceptionMessage                                   = "Max value '{0}' for {1} is invalid, should be less than/equal to {2}"
    endpointAlreadyDefinedExceptionMessage                            = "An endpoint named '{0}' has already been defined."
    eventAlreadyRegisteredExceptionMessage                            = '{0} event already registered: {1}'
    parameterNotSuppliedInRequestExceptionMessage                     = "A parameter called '{0}' was not supplied in the request or has no data available."
    cacheStorageNotFoundForSetExceptionMessage                        = "Cache storage with name '{0}' not found when attempting to set cached item '{1}'"
    methodPathAlreadyDefinedExceptionMessage                          = '[{0}] {1}: Already defined.'
    errorLoggingAlreadyEnabledExceptionMessage                        = 'Error Logging has already been enabled.'
    valueForUsingVariableNotFoundExceptionMessage                     = "Value for '`$using:{0}' could not be found."
    rapidPdfDoesNotSupportOpenApi31ExceptionMessage                   = "The Document tool RapidPdf doesn't support OpenAPI 3.1"
    oauth2ClientSecretRequiredExceptionMessage                        = 'OAuth2 requires a Client Secret when not using PKCE.'
    invalidBase64JwtExceptionMessage                                  = 'Invalid Base64 encoded value found in JWT'
    noSessionToCalculateDataHashExceptionMessage                      = 'No session available to calculate data hash.'
    cacheStorageNotFoundForRemoveExceptionMessage                     = "Cache storage with name '{0}' not found when attempting to remove cached item '{1}'"
    csrfMiddlewareNotInitializedExceptionMessage                      = 'CSRF Middleware has not been initialised.'
    infoTitleMandatoryMessage                                         = 'info.title is mandatory.'
    typeCanOnlyBeAssociatedWithObjectExceptionMessage                 = 'Type {0} can only be associated with an Object.'
    userFileDoesNotExistExceptionMessage                              = 'The user file does not exist: {0}'
    routeParameterNeedsValidScriptblockExceptionMessage               = 'The Route parameter needs a valid, not empty, scriptblock.'
    nextTriggerCalculationErrorExceptionMessage                       = 'Looks like something went wrong trying to calculate the next trigger datetime: {0}'
    cannotLockValueTypeExceptionMessage                               = 'Cannot lock a [ValueType]'
    failedToCreateOpenSslCertExceptionMessage                         = 'Failed to create OpenSSL cert: {0}'
    jwtExpiredExceptionMessage                                        = 'The JWT has expired.'
    openingGuiMessage                                                 = 'Opening the GUI.'
    multiTypePropertiesRequireOpenApi31ExceptionMessage               = 'Multi-type properties require OpenApi Version 3.1 or above.'
    noNameForWebSocketRemoveExceptionMessage                          = 'No Name for a WebSocket to remove supplied.'
    maxSizeInvalidExceptionMessage                                    = 'MaxSize must be 0 or greater, but got: {0}'
    iisShutdownMessage                                                = '(IIS Shutdown)'
    cannotUnlockValueTypeExceptionMessage                             = 'Cannot unlock a [ValueType]'
    noJwtSignatureForAlgorithmExceptionMessage                        = 'No JWT signature supplied for {0}.'
    maximumConcurrentWebSocketThreadsInvalidExceptionMessage          = 'Maximum concurrent WebSocket threads must be >=1 but got: {0}'
    acknowledgeMessageOnlySupportedOnSmtpTcpEndpointsExceptionMessage = 'The Acknowledge message is only supported on SMTP and TCP endpoints.'
    failedToConnectToUrlExceptionMessage                              = 'Failed to connect to URL: {0}'
    failedToAcquireMutexOwnershipExceptionMessage                     = 'Failed to acquire mutex ownership. Mutex name: {0}'
    sessionsRequiredForOAuth2WithPKCEExceptionMessage                 = 'Sessions are required to use OAuth2 with PKCE'
    failedToConnectToWebSocketExceptionMessage                        = 'Failed to connect to WebSocket: {0}'
    unsupportedObjectExceptionMessage                                 = 'Unsupported object'
    failedToParseAddressExceptionMessage                              = "Failed to parse '{0}' as a valid IP/Host:Port address"
    mustBeRunningWithAdminPrivilegesExceptionMessage                  = 'Must be running with administrator privileges to listen on non-localhost addresses.'
    specificationMessage                                              = 'Specification'
    cacheStorageNotFoundForClearExceptionMessage                      = "Cache storage with name '{0}' not found when attempting to clear the cache."
    restartingServerMessage                                           = 'Restarting server...'
    cannotSupplyIntervalWhenEveryIsNoneExceptionMessage               = "Cannot supply an interval when the parameter 'Every' is set to None."
    unsupportedJwtAlgorithmExceptionMessage                           = 'The JWT algorithm is not currently supported: {0}'
    websocketsNotConfiguredForSignalMessagesExceptionMessage          = 'WebSockets have not been configured to send signal messages.'
    invalidLogicTypeInHashtableMiddlewareExceptionMessage             = 'A Hashtable Middleware supplied has an invalid Logic type. Expected ScriptBlock, but got: {0}'
    maximumConcurrentSchedulesLessThanMinimumExceptionMessage         = 'Maximum concurrent schedules cannot be less than the minimum of {0} but got: {1}'
    failedToAcquireSemaphoreOwnershipExceptionMessage                 = 'Failed to acquire semaphore ownership. Semaphore name: {0}'
    propertiesParameterWithoutNameExceptionMessage                    = 'The Properties parameters cannot be used if the Property has no name.'
    customSessionStorageMethodNotImplementedExceptionMessage          = "The custom session storage does not implement the required '{0}()' method."
    authenticationMethodDoesNotExistExceptionMessage                  = 'Authentication method does not exist: {0}'
    webhooksFeatureNotSupportedInOpenApi30ExceptionMessage            = 'The Webhooks feature is not supported in OpenAPI v3.0.x'
    invalidContentTypeForSchemaExceptionMessage                       = "Invalid 'content-type' found for schema: {0}"
    noUnlockScriptBlockForVaultExceptionMessage                       = "No Unlock ScriptBlock supplied for unlocking the vault '{0}'"
    definitionTagMessage                                              = 'Definition {0}:'
    failedToOpenRunspacePoolExceptionMessage                          = 'Failed to open RunspacePool: {0}'
    failedToCloseRunspacePoolExceptionMessage                         = 'Failed to close RunspacePool: {0}'
    verbNoLogicPassedExceptionMessage                                 = '[Verb] {0}: No logic passed'
    noMutexFoundExceptionMessage                                      = "No mutex found called '{0}'"
    documentationMessage                                              = 'Documentation'
    timerAlreadyDefinedExceptionMessage                               = '[Timer] {0}: Timer already defined.'
    invalidPortExceptionMessage                                       = 'The port cannot be negative: {0}'
    viewsFolderNameAlreadyExistsExceptionMessage                      = 'The Views folder name already exists: {0}'
    noNameForWebSocketResetExceptionMessage                           = 'No Name for a WebSocket to reset supplied.'
    mergeDefaultAuthNotInListExceptionMessage                         = "The MergeDefault Authentication '{0}' is not in the Authentication list supplied."
    descriptionRequiredExceptionMessage                               = 'A Description is required for Path:{0} Response:{1}'
    pageNameShouldBeAlphaNumericExceptionMessage                      = 'The Page name should be a valid Alphanumeric value: {0}'
    defaultValueNotBooleanOrEnumExceptionMessage                      = 'The default value is not a boolean and is not part of the enum.'
    openApiComponentSchemaDoesNotExistExceptionMessage                = "The OpenApi component schema {0} doesn't exist."
    timerParameterMustBeGreaterThanZeroExceptionMessage               = '[Timer] {0}: {1} must be greater than 0.'
    taskTimedOutExceptionMessage                                      = 'Task has timed out after {0}ms.'
    scheduleStartTimeAfterEndTimeExceptionMessage                     = '[Schedule] {0}: Cannot have a StartTime after the EndTime'
    infoVersionMandatoryMessage                                       = 'info.version is mandatory.'
    cannotUnlockNullObjectExceptionMessage                            = 'Cannot unlock an object that is null.'
    nonEmptyScriptBlockRequiredForCustomAuthExceptionMessage          = 'A non-empty ScriptBlock is required for the Custom authentication scheme.'
    validationOfOneOfSchemaNotSupportedExceptionMessage               = "Validation of a schema that includes 'oneof' is not supported."
    routeParameterCannotBeNullExceptionMessage                        = "The parameter 'Route' cannot be null."
    cacheStorageAlreadyExistsExceptionMessage                         = "Cache Storage with name '{0}' already exists."
    loggingMethodRequiresValidScriptBlockExceptionMessage             = "The supplied output Method for the '{0}' Logging method requires a valid ScriptBlock."
    scopedVariableAlreadyDefinedExceptionMessage                      = 'Scoped Variable already defined: {0}'
    oauth2RequiresAuthorizeUrlExceptionMessage                        = "OAuth2 requires an 'AuthoriseUrl' property to be supplied."
    pathNotExistExceptionMessage                                      = 'Path does not exist: {0}'
    noDomainServerNameForWindowsAdAuthExceptionMessage                = 'No domain server name has been supplied for Windows AD authentication'
    suppliedDateAfterScheduleEndTimeExceptionMessage                  = 'Supplied date is after the end time of the schedule at {0}'
    wildcardMethodsIncompatibleWithAutoMethodsExceptionMessage        = 'The * wildcard for Methods is incompatible with the AutoMethods switch.'
    cannotSupplyIntervalForYearExceptionMessage                       = 'Cannot supply interval value for every year.'
    missingComponentsMessage                                          = 'Missing component(s)'
    invalidStrictTransportSecurityDurationExceptionMessage            = 'Invalid Strict-Transport-Security duration supplied: {0}. It should be greater than 0.'
    noSecretForHmac512ExceptionMessage                                = 'No secret supplied for HMAC512 hash.'
    daysInMonthExceededExceptionMessage                               = '{0} only has {1} days, but {2} was supplied.'
    nonEmptyScriptBlockRequiredForCustomLoggingExceptionMessage       = 'A non-empty ScriptBlock is required for the Custom logging output method.'
    encodingAttributeOnlyAppliesToMultipartExceptionMessage           = 'The encoding attribute only applies to multipart and application/x-www-form-urlencoded request bodies.'
    suppliedDateBeforeScheduleStartTimeExceptionMessage               = 'Supplied date is before the start time of the schedule at {0}'
    unlockSecretRequiredExceptionMessage                              = "An 'UnlockSecret' property is required when using Microsoft.PowerShell.SecretStore"
    noLogicPassedForMethodRouteExceptionMessage                       = '[{0}] {1}: No logic passed.'
    bodyParserAlreadyDefinedForContentTypeExceptionMessage            = 'A body-parser is already defined for the {0} content-type.'
    invalidJwtSuppliedExceptionMessage                                = 'Invalid JWT supplied.'
    sessionsRequiredForFlashMessagesExceptionMessage                  = 'Sessions are required to use Flash messages.'
    semaphoreAlreadyExistsExceptionMessage                            = 'A semaphore with the following name already exists: {0}'
    invalidJwtHeaderAlgorithmSuppliedExceptionMessage                 = 'Invalid JWT header algorithm supplied.'
    oauth2ProviderDoesNotSupportPasswordGrantTypeExceptionMessage     = "The OAuth2 provider does not support the 'password' grant_type required by using an InnerScheme."
    invalidAliasFoundExceptionMessage                                 = 'Invalid {0} alias found: {1}'
    scheduleDoesNotExistExceptionMessage                              = "Schedule '{0}' does not exist."
    accessMethodNotExistExceptionMessage                              = 'Access method does not exist: {0}'
    oauth2ProviderDoesNotSupportCodeResponseTypeExceptionMessage      = "The OAuth2 provider does not support the 'code' response_type."
    untestedPowerShellVersionWarningMessage                           = '[WARNING] Pode {0} has not been tested on PowerShell {1}, as it was not available when Pode was released.'
    secretVaultAlreadyRegisteredAutoImportExceptionMessage            = "A Secret Vault with the name '{0}' has already been registered while auto-importing Secret Vaults."
    schemeRequiresValidScriptBlockExceptionMessage                    = "The supplied scheme for the '{0}' authentication validator requires a valid ScriptBlock."
    serverLoopingMessage                                              = 'Server looping every {0}secs'
    certificateThumbprintsNameSupportedOnWindowsExceptionMessage      = 'Certificate Thumbprints/Name are only supported on Windows OS.'
    sseConnectionNameRequiredExceptionMessage                         = "An SSE connection Name is required, either from -Name or `$WebEvent.Sse.Name"
    invalidMiddlewareTypeExceptionMessage                             = 'One of the Middlewares supplied is an invalid type. Expected either a ScriptBlock or Hashtable, but got: {0}'
    noSecretForJwtSignatureExceptionMessage                           = 'No secret supplied for JWT signature.'
    modulePathDoesNotExistExceptionMessage                            = 'The module path does not exist: {0}'
    taskAlreadyDefinedExceptionMessage                                = '[Task] {0}: Task already defined.'
    verbAlreadyDefinedExceptionMessage                                = '[Verb] {0}: Already defined'
    clientCertificatesOnlySupportedOnHttpsEndpointsExceptionMessage   = 'Client certificates are only supported on HTTPS endpoints.'
    endpointNameNotExistExceptionMessage                              = "Endpoint with name '{0}' does not exist."
    middlewareNoLogicSuppliedExceptionMessage                         = '[Middleware]: No logic supplied in ScriptBlock.'
    scriptBlockRequiredForMergingUsersExceptionMessage                = 'A Scriptblock for merging multiple authenticated users into 1 object is required When Valid is All.'
    secretVaultAlreadyRegisteredExceptionMessage                      = "A Secret Vault with the name '{0}' has already been registered{1}."
    deprecatedTitleVersionDescriptionWarningMessage                   = "WARNING: Title, Version, and Description on 'Enable-PodeOpenApi' are deprecated. Please use 'Add-PodeOAInfo' instead."
    undefinedOpenApiReferencesMessage                                 = 'Undefined OpenAPI References:'
    doneMessage                                                       = 'Done'
    swaggerEditorDoesNotSupportOpenApi31ExceptionMessage              = "This version on Swagger-Editor doesn't support OpenAPI 3.1"
    durationMustBeZeroOrGreaterExceptionMessage                       = 'Duration must be 0 or greater, but got: {0}s'
    viewsPathDoesNotExistExceptionMessage                             = 'The Views path does not exist: {0}'
    discriminatorIncompatibleWithAllOfExceptionMessage                = "The parameter 'Discriminator' is incompatible with 'allOf'."
    noNameForWebSocketSendMessageExceptionMessage                     = 'No Name for a WebSocket to send message to supplied.'
    hashtableMiddlewareNoLogicExceptionMessage                        = 'A Hashtable Middleware supplied has no Logic defined.'
    openApiInfoMessage                                                = 'OpenAPI Info:'
    invalidSchemeForAuthValidatorExceptionMessage                     = "The supplied '{0}' Scheme for the '{1}' authentication validator requires a valid ScriptBlock."
    sseFailedToBroadcastExceptionMessage                              = 'SSE failed to broadcast due to defined SSE broadcast level for {0}: {1}'
    adModuleWindowsOnlyExceptionMessage                               = 'Active Directory module only available on Windows OS.'
    requestLoggingAlreadyEnabledExceptionMessage                      = 'Request Logging has already been enabled.'
    invalidAccessControlMaxAgeDurationExceptionMessage                = 'Invalid Access-Control-Max-Age duration supplied: {0}. Should be greater than 0.'
    openApiDefinitionAlreadyExistsExceptionMessage                    = 'OpenAPI definition named {0} already exists.'
    renamePodeOADefinitionTagExceptionMessage                         = "Rename-PodeOADefinitionTag cannot be used inside a Select-PodeOADefinition 'ScriptBlock'."
    taskProcessDoesNotExistExceptionMessage                           = 'Task process does not exist: {0}'
    scheduleProcessDoesNotExistExceptionMessage                       = 'Schedule process does not exist: {0}'
    definitionTagChangeNotAllowedExceptionMessage                     = 'Definition Tag for a Route cannot be changed.'
    getRequestBodyNotAllowedExceptionMessage                          = '{0} operations cannot have a Request Body.'
    fnDoesNotAcceptArrayAsPipelineInputExceptionMessage               = "The function '{0}' does not accept an array as pipeline input."
    unsupportedStreamCompressionEncodingExceptionMessage              = 'Unsupported stream compression encoding: {0}'
}
src\Locales\es\Pode.psd1
@{
    schemaValidationRequiresPowerShell610ExceptionMessage             = 'La validación del esquema requiere PowerShell versión 6.1.0 o superior.'
    customAccessPathOrScriptBlockRequiredExceptionMessage             = 'Se requiere una ruta o un ScriptBlock para obtener los valores de acceso personalizados.'
    operationIdMustBeUniqueForArrayExceptionMessage                   = 'OperationID: {0} debe ser único y no puede aplicarse a un array.'
    endpointNotDefinedForRedirectingExceptionMessage                  = "No se ha definido un punto de conexión llamado '{0}' para la redirección."
    filesHaveChangedMessage                                           = 'Los siguientes archivos han cambiado:'
    iisAspnetcoreTokenMissingExceptionMessage                         = 'Falta el token IIS ASPNETCORE_TOKEN.'
    minValueGreaterThanMaxExceptionMessage                            = 'El valor mínimo para {0} no debe ser mayor que el valor máximo.'
    noLogicPassedForRouteExceptionMessage                             = 'No se pasó lógica para la Ruta: {0}'
    scriptPathDoesNotExistExceptionMessage                            = 'La ruta del script no existe: {0}'
    mutexAlreadyExistsExceptionMessage                                = 'Ya existe un mutex con el siguiente nombre: {0}'
    listeningOnEndpointsMessage                                       = 'Escuchando en los siguientes {0} punto(s) de conexión [{1} hilo(s)]:'
    unsupportedFunctionInServerlessContextExceptionMessage            = 'La función {0} no es compatible en un contexto sin servidor.'
    expectedNoJwtSignatureSuppliedExceptionMessage                    = 'No se esperaba que se proporcionara una firma JWT.'
    secretAlreadyMountedExceptionMessage                              = "Un Secreto con el nombre '{0}' ya ha sido montado."
    failedToAcquireLockExceptionMessage                               = 'No se pudo adquirir un bloqueo en el objeto.'
    noPathSuppliedForStaticRouteExceptionMessage                      = '[{0}]: No se proporcionó una ruta para la Ruta estática.'
    invalidHostnameSuppliedExceptionMessage                           = 'Nombre de host no válido proporcionado: {0}'
    authMethodAlreadyDefinedExceptionMessage                          = 'Método de autenticación ya definido: {0}'
    csrfCookieRequiresSecretExceptionMessage                          = "Al usar cookies para CSRF, se requiere un Secreto. Puedes proporcionar un Secreto o establecer el secreto global de la Cookie - (Set-PodeCookieSecret '<value>' -Global)"
    nonEmptyScriptBlockRequiredForPageRouteExceptionMessage           = 'Se requiere un ScriptBlock no vacío para crear una Ruta de Página.'
    noPropertiesMutuallyExclusiveExceptionMessage                     = "El parámetro 'NoProperties' es mutuamente excluyente con 'Properties', 'MinProperties' y 'MaxProperties'."
    incompatiblePodeDllExceptionMessage                               = 'Se ha cargado una versión incompatible existente de Pode.DLL {0}. Se requiere la versión {1}. Abra una nueva sesión de Powershell/pwsh e intente de nuevo.'
    accessMethodDoesNotExistExceptionMessage                          = 'El método de acceso no existe: {0}.'
    scheduleAlreadyDefinedExceptionMessage                            = '[Programador] {0}: Programador ya definido.'
    secondsValueCannotBeZeroOrLessExceptionMessage                    = 'El valor en segundos no puede ser 0 o menor para {0}'
    pathToLoadNotFoundExceptionMessage                                = 'No se encontró la ruta para cargar {0}: {1}'
    failedToImportModuleExceptionMessage                              = 'Error al importar el módulo: {0}'
    endpointNotExistExceptionMessage                                  = "No existe un punto de conexión con el protocolo '{0}' y la dirección '{1}' o la dirección local '{2}'."
    terminatingMessage                                                = 'Terminando...'
    noCommandsSuppliedToConvertToRoutesExceptionMessage               = 'No se proporcionaron comandos para convertir a Rutas.'
    invalidTaskTypeExceptionMessage                                   = 'El tipo de tarea no es válido, se esperaba [System.Threading.Tasks.Task] o [hashtable].'
    alreadyConnectedToWebSocketExceptionMessage                       = "Ya conectado al WebSocket con el nombre '{0}'"
    crlfMessageEndCheckOnlySupportedOnTcpEndpointsExceptionMessage    = 'La verificación de final de mensaje CRLF solo es compatible con endpoints TCP.'
    testPodeOAComponentSchemaNeedToBeEnabledExceptionMessage          = "'Test-PodeOAComponentSchema' necesita ser habilitado usando 'Enable-PodeOpenApi -EnableSchemaValidation'"
    adModuleNotInstalledExceptionMessage                              = 'El módulo de Active Directory no está instalado.'
    cronExpressionInvalidExceptionMessage                             = 'La expresión Cron solo debe consistir en 5 partes: {0}'
    noSessionToSetOnResponseExceptionMessage                          = 'No hay ninguna sesión disponible para configurar en la respuesta.'
    valueOutOfRangeExceptionMessage                                   = "El valor '{0}' para {1} no es válido, debe estar entre {2} y {3}"
    loggingMethodAlreadyDefinedExceptionMessage                       = 'Método de registro ya definido: {0}'
    noSecretForHmac256ExceptionMessage                                = 'No se suministró ningún secreto para el hash HMAC256.'
    eolPowerShellWarningMessage                                       = '[ADVERTENCIA] Pode {0} no se ha probado en PowerShell {1}, ya que está en fin de vida.'
    runspacePoolFailedToLoadExceptionMessage                          = '{0} RunspacePool no se pudo cargar.'
    noEventRegisteredExceptionMessage                                 = 'No hay evento {0} registrado: {1}'
    scheduleCannotHaveNegativeLimitExceptionMessage                   = '[Programador] {0}: No puede tener un límite negativo.'
    openApiRequestStyleInvalidForParameterExceptionMessage            = 'El estilo de la solicitud OpenApi no puede ser {0} para un parámetro {1}.'
    openApiDocumentNotCompliantExceptionMessage                       = 'El documento OpenAPI no cumple con las normas.'
    taskDoesNotExistExceptionMessage                                  = "La tarea '{0}' no existe."
    scopedVariableNotFoundExceptionMessage                            = 'Variable de alcance no encontrada: {0}'
    sessionsRequiredForCsrfExceptionMessage                           = 'Se requieren sesiones para usar CSRF a menos que desee usar cookies.'
    nonEmptyScriptBlockRequiredForLoggingMethodExceptionMessage       = 'Se requiere un ScriptBlock no vacío para el método de registro.'
    credentialsPassedWildcardForHeadersLiteralExceptionMessage        = 'Cuando se pasan las Credenciales, el comodín * para los Encabezados se tomará como una cadena literal y no como un comodín.'
    podeNotInitializedExceptionMessage                                = 'Pode no se ha inicializado.'
    multipleEndpointsForGuiMessage                                    = 'Se han definido múltiples puntos de conexión, solo se usará el primero para la GUI.'
    operationIdMustBeUniqueExceptionMessage                           = 'OperationID: {0} debe ser único.'
    invalidJsonJwtExceptionMessage                                    = 'Valor JSON no válido encontrado en JWT'
    noAlgorithmInJwtHeaderExceptionMessage                            = 'No se proporcionó un algoritmo en el encabezado JWT.'
    openApiVersionPropertyMandatoryExceptionMessage                   = 'La propiedad de versión OpenApi es obligatoria.'
    limitValueCannotBeZeroOrLessExceptionMessage                      = 'El valor del límite no puede ser 0 o menor para {0}'
    timerDoesNotExistExceptionMessage                                 = "El temporizador '{0}' no existe."
    openApiGenerationDocumentErrorMessage                             = 'Error en el documento de generación de OpenAPI:'
    routeAlreadyContainsCustomAccessExceptionMessage                  = "La ruta '[{0}] {1}' ya contiene acceso personalizado con el nombre '{2}'"
    maximumConcurrentWebSocketThreadsLessThanMinimumExceptionMessage  = 'El número máximo de hilos concurrentes de WebSocket no puede ser menor que el mínimo de {0}, pero se obtuvo: {1}'
    middlewareAlreadyDefinedExceptionMessage                          = '[Middleware] {0}: Middleware ya definido.'
    invalidAtomCharacterExceptionMessage                              = 'Carácter de átomo cron no válido: {0}'
    invalidCronAtomFormatExceptionMessage                             = 'Formato de átomo cron inválido encontrado: {0}'
    cacheStorageNotFoundForRetrieveExceptionMessage                   = "No se encontró el almacenamiento en caché con el nombre '{0}' al intentar recuperar el elemento en caché '{1}'."
    headerMustHaveNameInEncodingContextExceptionMessage               = 'El encabezado debe tener un nombre cuando se usa en un contexto de codificación.'
    moduleDoesNotContainFunctionExceptionMessage                      = 'El módulo {0} no contiene la función {1} para convertir en una Ruta.'
    pathToIconForGuiDoesNotExistExceptionMessage                      = 'La ruta del icono para la GUI no existe: {0}'
    noTitleSuppliedForPageExceptionMessage                            = 'No se proporcionó título para la página {0}.'
    certificateSuppliedForNonHttpsWssEndpointExceptionMessage         = 'Certificado proporcionado para un endpoint que no es HTTPS/WSS.'
    cannotLockNullObjectExceptionMessage                              = 'No se puede bloquear un objeto nulo.'
    showPodeGuiOnlyAvailableOnWindowsExceptionMessage                 = 'Show-PodeGui actualmente solo está disponible para Windows PowerShell y PowerShell 7+ en Windows.'
    unlockSecretButNoScriptBlockExceptionMessage                      = 'Se suministró un secreto de desbloqueo para el tipo de bóveda secreta personalizada, pero no se suministró ningún ScriptBlock de desbloqueo.'
    invalidIpAddressExceptionMessage                                  = 'La dirección IP suministrada no es válida: {0}'
    maxDaysInvalidExceptionMessage                                    = 'MaxDays debe ser igual o mayor que 0, pero se obtuvo: {0}'
    noRemoveScriptBlockForVaultExceptionMessage                       = "No se suministró ningún ScriptBlock de eliminación para eliminar secretos de la bóveda '{0}'"
    noSecretExpectedForNoSignatureExceptionMessage                    = 'Se esperaba que no se suministrara ningún secreto para ninguna firma.'
    noCertificateFoundExceptionMessage                                = "No se encontró ningún certificado en {0}{1} para '{2}'"
    minValueInvalidExceptionMessage                                   = "El valor mínimo '{0}' para {1} no es válido, debe ser mayor o igual a {2}"
    accessRequiresAuthenticationOnRoutesExceptionMessage              = 'El acceso requiere autenticación en las rutas.'
    noSecretForHmac384ExceptionMessage                                = 'No se suministró ningún secreto para el hash HMAC384.'
    windowsLocalAuthSupportIsForWindowsOnlyExceptionMessage           = 'El soporte de autenticación local de Windows es solo para Windows.'
    definitionTagNotDefinedExceptionMessage                           = 'La etiqueta de definición {0} no está definida.'
    noComponentInDefinitionExceptionMessage                           = 'No hay componente del tipo {0} llamado {1} disponible en la definición de {2}.'
    noSmtpHandlersDefinedExceptionMessage                             = 'No se han definido controladores SMTP.'
    sessionMiddlewareAlreadyInitializedExceptionMessage               = 'El Middleware de Sesión ya se ha inicializado.'
    reusableComponentPathItemsNotAvailableInOpenApi30ExceptionMessage = "La característica del componente reutilizable 'pathItems' no está disponible en OpenAPI v3.0."
    wildcardHeadersIncompatibleWithAutoHeadersExceptionMessage        = 'El comodín * para los Encabezados es incompatible con el interruptor AutoHeaders.'
    noDataForFileUploadedExceptionMessage                             = "No se han subido datos para el archivo '{0}' en la solicitud."
    sseOnlyConfiguredOnEventStreamAcceptHeaderExceptionMessage        = 'SSE solo se puede configurar en solicitudes con un valor de encabezado Accept de text/event-stream.'
    noSessionAvailableToSaveExceptionMessage                          = 'No hay sesión disponible para guardar.'
    pathParameterRequiresRequiredSwitchExceptionMessage               = "Si la ubicación del parámetro es 'Path', el parámetro switch 'Required' es obligatorio."
    noOpenApiUrlSuppliedExceptionMessage                              = 'No se proporcionó URL de OpenAPI para {0}.'
    maximumConcurrentSchedulesInvalidExceptionMessage                 = 'Las programaciones simultáneos máximos deben ser >=1 pero se obtuvo: {0}'
    snapinsSupportedOnWindowsPowershellOnlyExceptionMessage           = 'Los Snapins solo son compatibles con Windows PowerShell.'
    eventViewerLoggingSupportedOnWindowsOnlyExceptionMessage          = 'El registro en el Visor de Eventos solo se admite en Windows.'
    parametersMutuallyExclusiveExceptionMessage                       = "Los parámetros '{0}' y '{1}' son mutuamente excluyentes."
    pathItemsFeatureNotSupportedInOpenApi30ExceptionMessage           = 'La función de elementos de ruta no es compatible con OpenAPI v3.0.x'
    openApiParameterRequiresNameExceptionMessage                      = 'El parámetro OpenApi requiere un nombre especificado.'
    maximumConcurrentTasksLessThanMinimumExceptionMessage             = 'El número máximo de tareas concurrentes no puede ser menor que el mínimo de {0}, pero se obtuvo: {1}'
    noSemaphoreFoundExceptionMessage                                  = "No se encontró ningún semáforo llamado '{0}'"
    singleValueForIntervalExceptionMessage                            = 'Solo puede suministrar un único valor {0} cuando utiliza intervalos.'
    jwtNotYetValidExceptionMessage                                    = 'El JWT aún no es válido.'
    verbAlreadyDefinedForUrlExceptionMessage                          = '[Verbo] {0}: Ya está definido para {1}'
    noSecretNamedMountedExceptionMessage                              = "No se ha montado ningún Secreto con el nombre '{0}'."
    moduleOrVersionNotFoundExceptionMessage                           = 'No se encontró el módulo o la versión en {0}: {1}@{2}'
    noScriptBlockSuppliedExceptionMessage                             = 'No se suministró ningún ScriptBlock.'
    noSecretVaultRegisteredExceptionMessage                           = "No se ha registrado un Cofre de Secretos con el nombre '{0}'."
    nameRequiredForEndpointIfRedirectToSuppliedExceptionMessage       = 'Se requiere un nombre para el endpoint si se proporciona el parámetro RedirectTo.'
    openApiLicenseObjectRequiresNameExceptionMessage                  = "El objeto OpenAPI 'license' requiere la propiedad 'name'. Use el parámetro -LicenseName."
    sourcePathDoesNotExistForStaticRouteExceptionMessage              = '{0}: La ruta de origen proporcionada para la Ruta estática no existe: {1}'
    noNameForWebSocketDisconnectExceptionMessage                      = 'No se proporcionó ningún nombre para desconectar el WebSocket.'
    certificateExpiredExceptionMessage                                = "El certificado '{0}' ha expirado: {1}"
    secretVaultUnlockExpiryDateInPastExceptionMessage                 = 'La fecha de expiración para desbloquear el Cofre de Secretos está en el pasado (UTC): {0}'
    invalidWebExceptionTypeExceptionMessage                           = 'La excepción es de un tipo no válido, debe ser WebException o HttpRequestException, pero se obtuvo: {0}'
    invalidSecretValueTypeExceptionMessage                            = 'El valor del secreto es de un tipo no válido. Tipos esperados: String, SecureString, HashTable, Byte[], o PSCredential. Pero se obtuvo: {0}'
    explicitTlsModeOnlySupportedOnSmtpsTcpsEndpointsExceptionMessage  = 'El modo TLS explícito solo es compatible con endpoints SMTPS y TCPS.'
    discriminatorMappingRequiresDiscriminatorPropertyExceptionMessage = "El parámetro 'DiscriminatorMapping' solo se puede usar cuando está presente la propiedad 'DiscriminatorProperty'."
    scriptErrorExceptionMessage                                       = "Error '{0}' en el script {1} {2} (línea {3}) carácter {4} al ejecutar {5} en el objeto {6} '{7}' Clase: {8} ClaseBase: {9}"
    cannotSupplyIntervalForQuarterExceptionMessage                    = 'No se puede proporcionar un valor de intervalo para cada trimestre.'
    scheduleEndTimeMustBeInFutureExceptionMessage                     = '[Programador] {0}: El valor de EndTime debe estar en el futuro.'
    invalidJwtSignatureSuppliedExceptionMessage                       = 'Firma JWT proporcionada no válida.'
    noSetScriptBlockForVaultExceptionMessage                          = "No se suministró ningún ScriptBlock de configuración para actualizar/crear secretos en la bóveda '{0}'"
    accessMethodNotExistForMergingExceptionMessage                    = 'El método de acceso no existe para fusionarse: {0}'
    defaultAuthNotInListExceptionMessage                              = "La autenticación predeterminada '{0}' no está en la lista de autenticación proporcionada."
    parameterHasNoNameExceptionMessage                                = "El parámetro no tiene nombre. Asigne un nombre a este componente usando el parámetro 'Name'."
    methodPathAlreadyDefinedForUrlExceptionMessage                    = '[{0}] {1}: Ya está definido para {2}'
    fileWatcherAlreadyDefinedExceptionMessage                         = "Un Observador de Archivos llamado '{0}' ya ha sido definido."
    noServiceHandlersDefinedExceptionMessage                          = 'No se han definido controladores de servicio.'
    secretRequiredForCustomSessionStorageExceptionMessage             = 'Se requiere un secreto cuando se utiliza el almacenamiento de sesión personalizado.'
    secretManagementModuleNotInstalledExceptionMessage                = 'El módulo Microsoft.PowerShell.SecretManagement no está instalado.'
    noPathSuppliedForRouteExceptionMessage                            = 'No se proporcionó una ruta para la Ruta.'
    validationOfAnyOfSchemaNotSupportedExceptionMessage               = "La validación de un esquema que incluye 'anyof' no es compatible."
    iisAuthSupportIsForWindowsOnlyExceptionMessage                    = 'El soporte de autenticación IIS es solo para Windows.'
    oauth2InnerSchemeInvalidExceptionMessage                          = 'OAuth2 InnerScheme solo puede ser Basic o Form, pero se obtuvo: {0}'
    noRoutePathSuppliedForPageExceptionMessage                        = 'No se proporcionó ruta de acceso para la página {0}.'
    cacheStorageNotFoundForExistsExceptionMessage                     = "No se encontró el almacenamiento en caché con el nombre '{0}' al intentar comprobar si el elemento en caché '{1}' existe."
    handlerAlreadyDefinedExceptionMessage                             = '[{0}] {1}: Manejador ya definido.'
    sessionsNotConfiguredExceptionMessage                             = 'Las sesiones no se han configurado.'
    propertiesTypeObjectAssociationExceptionMessage                   = 'Solo las propiedades de tipo Objeto pueden estar asociadas con {0}.'
    sessionsRequiredForSessionPersistentAuthExceptionMessage          = 'Se requieren sesiones para usar la autenticación persistente de sesión.'
    invalidPathWildcardOrDirectoryExceptionMessage                    = 'La ruta suministrada no puede ser un comodín o un directorio: {0}'
    accessMethodAlreadyDefinedExceptionMessage                        = 'Método de acceso ya definido: {0}'
    parametersValueOrExternalValueMandatoryExceptionMessage           = "Los parámetros 'Value' o 'ExternalValue' son obligatorios."
    maximumConcurrentTasksInvalidExceptionMessage                     = 'El número máximo de tareas concurrentes debe ser >=1, pero se obtuvo: {0}'
    cannotCreatePropertyWithoutTypeExceptionMessage                   = 'No se puede crear la propiedad porque no se ha definido ningún tipo.'
    authMethodNotExistForMergingExceptionMessage                      = 'El método de autenticación no existe para la fusión: {0}'
    maxValueInvalidExceptionMessage                                   = "El valor máximo '{0}' para {1} no es válido, debe ser menor o igual a {2}"
    endpointAlreadyDefinedExceptionMessage                            = "Ya se ha definido un punto de conexión llamado '{0}'."
    eventAlreadyRegisteredExceptionMessage                            = 'Evento {0} ya registrado: {1}'
    parameterNotSuppliedInRequestExceptionMessage                     = "No se ha proporcionado un parámetro llamado '{0}' en la solicitud o no hay datos disponibles."
    cacheStorageNotFoundForSetExceptionMessage                        = "No se encontró el almacenamiento en caché con el nombre '{0}' al intentar establecer el elemento en caché '{1}'."
    methodPathAlreadyDefinedExceptionMessage                          = '[{0}] {1}: Ya está definido.'
    errorLoggingAlreadyEnabledExceptionMessage                        = 'El registro de errores ya está habilitado.'
    valueForUsingVariableNotFoundExceptionMessage                     = "No se pudo encontrar el valor para '`$using:{0}'."
    rapidPdfDoesNotSupportOpenApi31ExceptionMessage                   = 'La herramienta de documentación RapidPdf no admite OpenAPI 3.1'
    oauth2ClientSecretRequiredExceptionMessage                        = 'OAuth2 requiere un Client Secret cuando no se usa PKCE.'
    invalidBase64JwtExceptionMessage                                  = 'Valor Base64 no válido encontrado en JWT'
    noSessionToCalculateDataHashExceptionMessage                      = 'No hay ninguna sesión disponible para calcular el hash de datos.'
    cacheStorageNotFoundForRemoveExceptionMessage                     = "No se encontró el almacenamiento en caché con el nombre '{0}' al intentar eliminar el elemento en caché '{1}'."
    csrfMiddlewareNotInitializedExceptionMessage                      = 'El Middleware CSRF no se ha inicializado.'
    infoTitleMandatoryMessage                                         = 'info.title es obligatorio.'
    typeCanOnlyBeAssociatedWithObjectExceptionMessage                 = 'El tipo {0} solo se puede asociar con un Objeto.'
    userFileDoesNotExistExceptionMessage                              = 'El archivo de usuario no existe: {0}'
    routeParameterNeedsValidScriptblockExceptionMessage               = 'El parámetro Route necesita un ScriptBlock válido y no vacío.'
    nextTriggerCalculationErrorExceptionMessage                       = 'Parece que algo salió mal al intentar calcular la siguiente fecha y hora del disparador: {0}'
    cannotLockValueTypeExceptionMessage                               = 'No se puede bloquear un [ValueType].'
    failedToCreateOpenSslCertExceptionMessage                         = 'Error al crear el certificado OpenSSL: {0}'
    jwtExpiredExceptionMessage                                        = 'El JWT ha expirado.'
    openingGuiMessage                                                 = 'Abriendo la GUI.'
    multiTypePropertiesRequireOpenApi31ExceptionMessage               = 'Las propiedades de tipo múltiple requieren OpenApi versión 3.1 o superior.'
    noNameForWebSocketRemoveExceptionMessage                          = 'No se proporcionó ningún nombre para eliminar el WebSocket.'
    maxSizeInvalidExceptionMessage                                    = 'MaxSize debe ser igual o mayor que 0, pero se obtuvo: {0}'
    iisShutdownMessage                                                = '(Apagado de IIS)'
    cannotUnlockValueTypeExceptionMessage                             = 'No se puede desbloquear un [ValueType].'
    noJwtSignatureForAlgorithmExceptionMessage                        = 'No se proporcionó una firma JWT para {0}.'
    maximumConcurrentWebSocketThreadsInvalidExceptionMessage          = 'El número máximo de hilos concurrentes de WebSocket debe ser >=1, pero se obtuvo: {0}'
    acknowledgeMessageOnlySupportedOnSmtpTcpEndpointsExceptionMessage = 'El mensaje de reconocimiento solo es compatible con endpoints SMTP y TCP.'
    failedToConnectToUrlExceptionMessage                              = 'Error al conectar con la URL: {0}'
    failedToAcquireMutexOwnershipExceptionMessage                     = 'No se pudo adquirir la propiedad del mutex. Nombre del mutex: {0}'
    sessionsRequiredForOAuth2WithPKCEExceptionMessage                 = 'Se requieren sesiones para usar OAuth2 con PKCE.'
    failedToConnectToWebSocketExceptionMessage                        = 'Error al conectar con el WebSocket: {0}'
    unsupportedObjectExceptionMessage                                 = 'Objeto no compatible'
    failedToParseAddressExceptionMessage                              = "Error al analizar '{0}' como una dirección IP/Host:Puerto válida"
    mustBeRunningWithAdminPrivilegesExceptionMessage                  = 'Debe estar ejecutándose con privilegios de administrador para escuchar en direcciones que no sean localhost.'
    specificationMessage                                              = 'Especificación'
    cacheStorageNotFoundForClearExceptionMessage                      = "No se encontró el almacenamiento en caché con el nombre '{0}' al intentar vaciar la caché."
    restartingServerMessage                                           = 'Reiniciando el servidor...'
    cannotSupplyIntervalWhenEveryIsNoneExceptionMessage               = "No se puede proporcionar un intervalo cuando el parámetro 'Every' está configurado en None."
    unsupportedJwtAlgorithmExceptionMessage                           = 'El algoritmo JWT actualmente no es compatible: {0}'
    websocketsNotConfiguredForSignalMessagesExceptionMessage          = 'WebSockets no están configurados para enviar mensajes de señal.'
    invalidLogicTypeInHashtableMiddlewareExceptionMessage             = 'Un Middleware Hashtable suministrado tiene un tipo de lógica no válido. Se esperaba ScriptBlock, pero se obtuvo: {0}'
    maximumConcurrentSchedulesLessThanMinimumExceptionMessage         = 'Las programaciones simultáneos máximos no pueden ser inferiores al mínimo de {0} pero se obtuvo: {1}'
    failedToAcquireSemaphoreOwnershipExceptionMessage                 = 'No se pudo adquirir la propiedad del semáforo. Nombre del semáforo: {0}'
    propertiesParameterWithoutNameExceptionMessage                    = 'Los parámetros de propiedades no se pueden usar si la propiedad no tiene nombre.'
    customSessionStorageMethodNotImplementedExceptionMessage          = "El almacenamiento de sesión personalizado no implementa el método requerido '{0}()'."
    authenticationMethodDoesNotExistExceptionMessage                  = 'El método de autenticación no existe: {0}'
    webhooksFeatureNotSupportedInOpenApi30ExceptionMessage            = 'La función de Webhooks no es compatible con OpenAPI v3.0.x'
    invalidContentTypeForSchemaExceptionMessage                       = "'content-type' inválido encontrado para el esquema: {0}"
    noUnlockScriptBlockForVaultExceptionMessage                       = "No se suministró ningún ScriptBlock de desbloqueo para desbloquear la bóveda '{0}'"
    definitionTagMessage                                              = 'Definición {0}:'
    failedToOpenRunspacePoolExceptionMessage                          = 'Error al abrir RunspacePool: {0}'
    failedToCloseRunspacePoolExceptionMessage                         = 'No se pudo cerrar el RunspacePool: {0}'
    verbNoLogicPassedExceptionMessage                                 = '[Verbo] {0}: No se pasó ninguna lógica'
    noMutexFoundExceptionMessage                                      = "No se encontró ningún mutex llamado '{0}'"
    documentationMessage                                              = 'Documentación'
    timerAlreadyDefinedExceptionMessage                               = '[Temporizador] {0}: Temporizador ya definido.'
    invalidPortExceptionMessage                                       = 'El puerto no puede ser negativo: {0}'
    viewsFolderNameAlreadyExistsExceptionMessage                      = 'El nombre de la carpeta Views ya existe: {0}'
    noNameForWebSocketResetExceptionMessage                           = 'No se proporcionó ningún nombre para restablecer el WebSocket.'
    mergeDefaultAuthNotInListExceptionMessage                         = "La autenticación MergeDefault '{0}' no está en la lista de autenticación proporcionada."
    descriptionRequiredExceptionMessage                               = 'Se requiere una descripción para la Ruta:{0} Respuesta:{1}'
    pageNameShouldBeAlphaNumericExceptionMessage                      = 'El nombre de la página debe ser un valor alfanumérico válido: {0}'
    defaultValueNotBooleanOrEnumExceptionMessage                      = 'El valor predeterminado no es un booleano y no forma parte del enum.'
    openApiComponentSchemaDoesNotExistExceptionMessage                = 'El esquema del componente OpenApi {0} no existe.'
    timerParameterMustBeGreaterThanZeroExceptionMessage               = '[Temporizador] {0}: {1} debe ser mayor que 0.'
    taskTimedOutExceptionMessage                                      = 'La tarea ha agotado el tiempo después de {0}ms.'
    scheduleStartTimeAfterEndTimeExceptionMessage                     = "[Programador] {0}: No puede tener un 'StartTime' después del 'EndTime'"
    infoVersionMandatoryMessage                                       = 'info.version es obligatorio.'
    cannotUnlockNullObjectExceptionMessage                            = 'No se puede desbloquear un objeto nulo.'
    nonEmptyScriptBlockRequiredForCustomAuthExceptionMessage          = 'Se requiere un ScriptBlock no vacío para el esquema de autenticación personalizado.'
    nonEmptyScriptBlockRequiredForAuthMethodExceptionMessage          = 'Se requiere un ScriptBlock no vacío para el método de autenticación.'
    validationOfOneOfSchemaNotSupportedExceptionMessage               = "La validación de un esquema que incluye 'oneof' no es compatible."
    routeParameterCannotBeNullExceptionMessage                        = "El parámetro 'Route' no puede ser nulo."
    cacheStorageAlreadyExistsExceptionMessage                         = "Ya existe un almacenamiento en caché con el nombre '{0}'."
    loggingMethodRequiresValidScriptBlockExceptionMessage             = "El método de salida proporcionado para el método de registro '{0}' requiere un ScriptBlock válido."
    scopedVariableAlreadyDefinedExceptionMessage                      = 'La variable con alcance ya está definida: {0}'
    oauth2RequiresAuthorizeUrlExceptionMessage                        = 'OAuth2 requiere que se proporcione una URL de autorización.'
    pathNotExistExceptionMessage                                      = 'La ruta no existe: {0}'
    noDomainServerNameForWindowsAdAuthExceptionMessage                = 'No se ha proporcionado un nombre de servidor de dominio para la autenticación AD de Windows.'
    suppliedDateAfterScheduleEndTimeExceptionMessage                  = 'La fecha proporcionada es posterior a la hora de finalización del programación en {0}'
    wildcardMethodsIncompatibleWithAutoMethodsExceptionMessage        = 'El comodín * para los Métodos es incompatible con el interruptor AutoMethods.'
    cannotSupplyIntervalForYearExceptionMessage                       = 'No se puede proporcionar un valor de intervalo para cada año.'
    missingComponentsMessage                                          = 'Componente(s) faltante(s)'
    invalidStrictTransportSecurityDurationExceptionMessage            = 'Duración de Strict-Transport-Security no válida proporcionada: {0}. Debe ser mayor que 0.'
    noSecretForHmac512ExceptionMessage                                = 'No se suministró ningún secreto para el hash HMAC512.'
    daysInMonthExceededExceptionMessage                               = '{0} solo tiene {1} días, pero se suministró {2}.'
    nonEmptyScriptBlockRequiredForCustomLoggingExceptionMessage       = 'Se requiere un ScriptBlock no vacío para el método de salida de registro personalizado.'
    encodingAttributeOnlyAppliesToMultipartExceptionMessage           = 'El atributo de codificación solo se aplica a cuerpos de solicitud multipart y application/x-www-form-urlencoded.'
    suppliedDateBeforeScheduleStartTimeExceptionMessage               = 'La fecha proporcionada es anterior a la hora de inicio del programación en {0}'
    unlockSecretRequiredExceptionMessage                              = "Se requiere una propiedad 'UnlockSecret' al usar Microsoft.PowerShell.SecretStore"
    noLogicPassedForMethodRouteExceptionMessage                       = '[{0}] {1}: No se pasó lógica.'
    bodyParserAlreadyDefinedForContentTypeExceptionMessage            = 'Un analizador de cuerpo ya está definido para el tipo de contenido {0}.'
    invalidJwtSuppliedExceptionMessage                                = 'JWT proporcionado no válido.'
    sessionsRequiredForFlashMessagesExceptionMessage                  = 'Se requieren sesiones para usar mensajes Flash.'
    semaphoreAlreadyExistsExceptionMessage                            = 'Ya existe un semáforo con el siguiente nombre: {0}'
    invalidJwtHeaderAlgorithmSuppliedExceptionMessage                 = 'Algoritmo del encabezado JWT proporcionado no válido.'
    oauth2ProviderDoesNotSupportPasswordGrantTypeExceptionMessage     = "El proveedor de OAuth2 no admite el tipo de concesión 'password' requerido al usar un InnerScheme."
    invalidAliasFoundExceptionMessage                                 = 'Se encontró un alias {0} no válido: {1}'
    scheduleDoesNotExistExceptionMessage                              = "El programación '{0}' no existe."
    accessMethodNotExistExceptionMessage                              = 'El método de acceso no existe: {0}'
    oauth2ProviderDoesNotSupportCodeResponseTypeExceptionMessage      = "El proveedor de OAuth2 no admite el tipo de respuesta 'code'."
    untestedPowerShellVersionWarningMessage                           = '[ADVERTENCIA] Pode {0} no se ha probado en PowerShell {1}, ya que no estaba disponible cuando se lanzó Pode.'
    secretVaultAlreadyRegisteredAutoImportExceptionMessage            = "Ya se ha registrado un Bóveda Secreta con el nombre '{0}' al importar automáticamente Bóvedas Secretas."
    schemeRequiresValidScriptBlockExceptionMessage                    = "El esquema proporcionado para el validador de autenticación '{0}' requiere un ScriptBlock válido."
    serverLoopingMessage                                              = 'Bucle del servidor cada {0} segundos'
    certificateThumbprintsNameSupportedOnWindowsExceptionMessage      = 'Las huellas digitales/nombres de certificados solo son compatibles con Windows.'
    sseConnectionNameRequiredExceptionMessage                         = "Se requiere un nombre de conexión SSE, ya sea de -Name o `$WebEvent.Sse.Name"
    invalidMiddlewareTypeExceptionMessage                             = 'Uno de los Middlewares suministrados es de un tipo no válido. Se esperaba ScriptBlock o Hashtable, pero se obtuvo: {0}'
    noSecretForJwtSignatureExceptionMessage                           = 'No se suministró ningún secreto para la firma JWT.'
    modulePathDoesNotExistExceptionMessage                            = 'La ruta del módulo no existe: {0}'
    taskAlreadyDefinedExceptionMessage                                = '[Tarea] {0}: Tarea ya definida.'
    verbAlreadyDefinedExceptionMessage                                = '[Verbo] {0}: Ya está definido'
    clientCertificatesOnlySupportedOnHttpsEndpointsExceptionMessage   = 'Los certificados de cliente solo son compatibles con endpoints HTTPS.'
    endpointNameNotExistExceptionMessage                              = "No existe un punto de conexión con el nombre '{0}'."
    middlewareNoLogicSuppliedExceptionMessage                         = '[Middleware]: No se suministró lógica en el ScriptBlock.'
    scriptBlockRequiredForMergingUsersExceptionMessage                = 'Se requiere un ScriptBlock para fusionar múltiples usuarios autenticados en un solo objeto cuando Valid es All.'
    secretVaultAlreadyRegisteredExceptionMessage                      = "Un Cofre de Secretos con el nombre '{0}' ya ha sido registrado{1}."
    deprecatedTitleVersionDescriptionWarningMessage                   = "ADVERTENCIA: Título, Versión y Descripción en 'Enable-PodeOpenApi' están obsoletos. Utilice 'Add-PodeOAInfo' en su lugar."
    undefinedOpenApiReferencesMessage                                 = 'Referencias OpenAPI indefinidas:'
    doneMessage                                                       = 'Hecho'
    swaggerEditorDoesNotSupportOpenApi31ExceptionMessage              = 'Esta versión de Swagger-Editor no admite OpenAPI 3.1'
    durationMustBeZeroOrGreaterExceptionMessage                       = 'La duración debe ser igual o mayor a 0, pero se obtuvo: {0}s'
    viewsPathDoesNotExistExceptionMessage                             = 'La ruta de las Views no existe: {0}'
    discriminatorIncompatibleWithAllOfExceptionMessage                = "El parámetro 'Discriminator' es incompatible con 'allOf'."
    noNameForWebSocketSendMessageExceptionMessage                     = 'No se proporcionó ningún nombre para enviar un mensaje al WebSocket.'
    hashtableMiddlewareNoLogicExceptionMessage                        = 'Un Middleware Hashtable suministrado no tiene lógica definida.'
    openApiInfoMessage                                                = 'Información OpenAPI:'
    invalidSchemeForAuthValidatorExceptionMessage                     = "El esquema '{0}' proporcionado para el validador de autenticación '{1}' requiere un ScriptBlock válido."
    sseFailedToBroadcastExceptionMessage                              = 'SSE no pudo transmitir debido al nivel de transmisión SSE definido para {0}: {1}.'
    adModuleWindowsOnlyExceptionMessage                               = 'El módulo de Active Directory solo está disponible en Windows.'
    requestLoggingAlreadyEnabledExceptionMessage                      = 'El registro de solicitudes ya está habilitado.'
    invalidAccessControlMaxAgeDurationExceptionMessage                = 'Duración inválida para Access-Control-Max-Age proporcionada: {0}. Debe ser mayor que 0.'
    openApiDefinitionAlreadyExistsExceptionMessage                    = 'La definición de OpenAPI con el nombre {0} ya existe.'
    renamePodeOADefinitionTagExceptionMessage                         = "Rename-PodeOADefinitionTag no se puede usar dentro de un 'ScriptBlock' de Select-PodeOADefinition."
    taskProcessDoesNotExistExceptionMessage                           = "El proceso de la tarea '{0}' no existe."
    scheduleProcessDoesNotExistExceptionMessage                       = "El proceso del programación '{0}' no existe."
    definitionTagChangeNotAllowedExceptionMessage                     = 'La etiqueta de definición para una Route no se puede cambiar.'
    getRequestBodyNotAllowedExceptionMessage                          = 'Las operaciones {0} no pueden tener un cuerpo de solicitud.'
    fnDoesNotAcceptArrayAsPipelineInputExceptionMessage               = "La función '{0}' no acepta una matriz como entrada de canalización."
    unsupportedStreamCompressionEncodingExceptionMessage              = 'La codificación de compresión de transmisión no es compatible: {0}'
}
src\Locales\fr\Pode.psd1
@{
    schemaValidationRequiresPowerShell610ExceptionMessage             = 'La validation du schéma nécessite PowerShell version 6.1.0 ou supérieure.'
    customAccessPathOrScriptBlockRequiredExceptionMessage             = "Un chemin ou un ScriptBlock est requis pour obtenir les valeurs d'accès personnalisées."
    operationIdMustBeUniqueForArrayExceptionMessage                   = 'OperationID : {0} doit être unique et ne peut pas être appliqué à un tableau.'
    endpointNotDefinedForRedirectingExceptionMessage                  = "Un point de terminaison nommé '{0}' n'a pas été défini pour la redirection."
    filesHaveChangedMessage                                           = 'Les fichiers suivants ont été modifiés :'
    iisAspnetcoreTokenMissingExceptionMessage                         = 'Le jeton IIS ASPNETCORE_TOKEN est manquant.'
    minValueGreaterThanMaxExceptionMessage                            = 'La valeur minimale pour {0} ne doit pas être supérieure à la valeur maximale.'
    noLogicPassedForRouteExceptionMessage                             = 'Aucune logique passée pour la Route: {0}'
    scriptPathDoesNotExistExceptionMessage                            = "Le chemin du script n'existe pas : {0}"
    mutexAlreadyExistsExceptionMessage                                = 'Un mutex avec le nom suivant existe déjà: {0}'
    listeningOnEndpointsMessage                                       = 'Écoute sur les {0} point(s) de terminaison suivant(s) [{1} thread(s)] :'
    unsupportedFunctionInServerlessContextExceptionMessage            = "La fonction {0} n'est pas prise en charge dans un contexte sans serveur."
    expectedNoJwtSignatureSuppliedExceptionMessage                    = "Aucune signature JWT n'était attendue."
    secretAlreadyMountedExceptionMessage                              = "Un Secret avec le nom '{0}' a déjà été monté."
    failedToAcquireLockExceptionMessage                               = "Impossible d'acquérir un verrou sur l'objet."
    noPathSuppliedForStaticRouteExceptionMessage                      = '[{0}]: Aucun chemin fourni pour la Route statique.'
    invalidHostnameSuppliedExceptionMessage                           = "Nom d'hôte fourni invalide: {0}"
    authMethodAlreadyDefinedExceptionMessage                          = "Méthode d'authentification déjà définie : {0}"
    csrfCookieRequiresSecretExceptionMessage                          = "Lors de l'utilisation de cookies pour CSRF, un Secret est requis. Vous pouvez soit fournir un Secret, soit définir le Secret global du Cookie - (Set-PodeCookieSecret '<value>' -Global)"
    nonEmptyScriptBlockRequiredForPageRouteExceptionMessage           = 'Un ScriptBlock non vide est requis pour créer une route de page.'
    noPropertiesMutuallyExclusiveExceptionMessage                     = "Le paramètre 'NoProperties' est mutuellement exclusif avec 'Properties', 'MinProperties' et 'MaxProperties'."
    incompatiblePodeDllExceptionMessage                               = 'Une version incompatible existante de Pode.DLL {0} est chargée. La version {1} est requise. Ouvrez une nouvelle session Powershell/pwsh et réessayez.'
    accessMethodDoesNotExistExceptionMessage                          = "La méthode d'accès n'existe pas : {0}."
    scheduleAlreadyDefinedExceptionMessage                            = '[Horaire] {0}: Horaire déjà défini.'
    secondsValueCannotBeZeroOrLessExceptionMessage                    = 'La valeur en secondes ne peut pas être 0 ou inférieure pour {0}'
    pathToLoadNotFoundExceptionMessage                                = 'Chemin à charger {0} non trouvé : {1}'
    failedToImportModuleExceptionMessage                              = "Échec de l'importation du module : {0}"
    endpointNotExistExceptionMessage                                  = "Un point de terminaison avec le protocole '{0}' et l'adresse '{1}' ou l'adresse locale '{2}' n'existe pas."
    terminatingMessage                                                = 'Terminaison...'
    noCommandsSuppliedToConvertToRoutesExceptionMessage               = 'Aucune commande fournie pour convertir en routes.'
    invalidTaskTypeExceptionMessage                                   = "Le type de tâche n'est pas valide, attendu [System.Threading.Tasks.Task] ou [hashtable]."
    alreadyConnectedToWebSocketExceptionMessage                       = "Déjà connecté au WebSocket avec le nom '{0}'"
    crlfMessageEndCheckOnlySupportedOnTcpEndpointsExceptionMessage    = "La vérification de fin de message CRLF n'est prise en charge que sur les points de terminaison TCP."
    testPodeOAComponentSchemaNeedToBeEnabledExceptionMessage          = "'Test-PodeOAComponentSchema' doit être activé en utilisant 'Enable-PodeOpenApi -EnableSchemaValidation'"
    adModuleNotInstalledExceptionMessage                              = "Le module Active Directory n'est pas installé."
    cronExpressionInvalidExceptionMessage                             = "L'expression Cron doit uniquement comporter 5 parties : {0}"
    noSessionToSetOnResponseExceptionMessage                          = 'Aucune session disponible pour être définie sur la réponse.'
    valueOutOfRangeExceptionMessage                                   = "La valeur '{0}' pour {1} n'est pas valide, elle doit être comprise entre {2} et {3}"
    loggingMethodAlreadyDefinedExceptionMessage                       = 'Méthode de journalisation déjà définie: {0}'
    noSecretForHmac256ExceptionMessage                                = 'Aucun secret fourni pour le hachage HMAC256.'
    eolPowerShellWarningMessage                                       = "[AVERTISSEMENT] Pode {0} n'a pas été testé sur PowerShell {1}, car il est en fin de vie."
    runspacePoolFailedToLoadExceptionMessage                          = "{0} RunspacePool n'a pas pu être chargé."
    noEventRegisteredExceptionMessage                                 = 'Aucun événement {0} enregistré : {1}'
    scheduleCannotHaveNegativeLimitExceptionMessage                   = '[Horaire] {0}: Ne peut pas avoir de limite négative.'
    openApiRequestStyleInvalidForParameterExceptionMessage            = 'Le style de la requête OpenApi ne peut pas être {0} pour un paramètre {1}.'
    openApiDocumentNotCompliantExceptionMessage                       = "Le document OpenAPI n'est pas conforme."
    taskDoesNotExistExceptionMessage                                  = "La tâche '{0}' n'existe pas."
    scopedVariableNotFoundExceptionMessage                            = "Variable d'étendue non trouvée : {0}"
    sessionsRequiredForCsrfExceptionMessage                           = 'Des sessions sont nécessaires pour utiliser CSRF sauf si vous souhaitez utiliser des cookies.'
    nonEmptyScriptBlockRequiredForLoggingMethodExceptionMessage       = 'Un ScriptBlock non vide est requis pour la méthode de journalisation.'
    credentialsPassedWildcardForHeadersLiteralExceptionMessage        = 'Lorsque des Identifiants sont passés, le caractère générique * pour les En-têtes sera pris comme une chaîne littérale et non comme un caractère générique.'
    podeNotInitializedExceptionMessage                                = "Pode n'a pas été initialisé."
    multipleEndpointsForGuiMessage                                    = "Plusieurs points de terminaison définis, seul le premier sera utilisé pour l'interface graphique."
    operationIdMustBeUniqueExceptionMessage                           = 'OperationID : {0} doit être unique.'
    invalidJsonJwtExceptionMessage                                    = 'Valeur JSON non valide trouvée dans le JWT'
    noAlgorithmInJwtHeaderExceptionMessage                            = "Aucun algorithme fourni dans l'en-tête JWT."
    openApiVersionPropertyMandatoryExceptionMessage                   = 'La propriété Version OpenApi est obligatoire.'
    limitValueCannotBeZeroOrLessExceptionMessage                      = 'La valeur de la limite ne peut pas être 0 ou inférieure pour {0}'
    timerDoesNotExistExceptionMessage                                 = "Minuteur '{0}' n'existe pas."
    openApiGenerationDocumentErrorMessage                             = 'Erreur de génération du document OpenAPI :'
    routeAlreadyContainsCustomAccessExceptionMessage                  = "La route '[{0}] {1}' contient déjà un accès personnalisé avec le nom '{2}'"
    maximumConcurrentWebSocketThreadsLessThanMinimumExceptionMessage  = 'Le nombre maximum de threads WebSocket simultanés ne peut pas être inférieur au minimum de {0}, mais a obtenu : {1}'
    middlewareAlreadyDefinedExceptionMessage                          = '[Middleware] {0}: Middleware déjà défini.'
    invalidAtomCharacterExceptionMessage                              = "Caractère d'atome cron non valide : {0}"
    invalidCronAtomFormatExceptionMessage                             = "Format d'atome cron invalide trouvé: {0}"
    cacheStorageNotFoundForRetrieveExceptionMessage                   = "Le stockage de cache nommé '{0}' est introuvable lors de la tentative de récupération de l'élément mis en cache '{1}'."
    headerMustHaveNameInEncodingContextExceptionMessage               = "L'en-tête doit avoir un nom lorsqu'il est utilisé dans un contexte de codage."
    moduleDoesNotContainFunctionExceptionMessage                      = 'Le module {0} ne contient pas la fonction {1} à convertir en une Route.'
    pathToIconForGuiDoesNotExistExceptionMessage                      = "Le chemin vers l'icône pour l'interface graphique n'existe pas: {0}"
    noTitleSuppliedForPageExceptionMessage                            = 'Aucun titre fourni pour la page {0}.'
    certificateSuppliedForNonHttpsWssEndpointExceptionMessage         = 'Certificat fourni pour un point de terminaison non HTTPS/WSS.'
    cannotLockNullObjectExceptionMessage                              = 'Impossible de verrouiller un objet nul.'
    showPodeGuiOnlyAvailableOnWindowsExceptionMessage                 = 'Show-PodeGui est actuellement disponible uniquement pour Windows PowerShell et PowerShell 7+ sur Windows.'
    unlockSecretButNoScriptBlockExceptionMessage                      = 'Secret de déverrouillage fourni pour le type de coffre-fort personnalisé, mais aucun ScriptBlock de déverrouillage fourni.'
    invalidIpAddressExceptionMessage                                  = "L'adresse IP fournie n'est pas valide : {0}"
    maxDaysInvalidExceptionMessage                                    = 'MaxDays doit être égal ou supérieur à 0, mais a obtenu: {0}'
    noRemoveScriptBlockForVaultExceptionMessage                       = "Aucun ScriptBlock de suppression fourni pour supprimer des secrets du coffre '{0}'"
    noSecretExpectedForNoSignatureExceptionMessage                    = 'Aucun secret attendu pour aucune signature.'
    noCertificateFoundExceptionMessage                                = "Aucun certificat n'a été trouvé dans {0}{1} pour '{2}'"
    minValueInvalidExceptionMessage                                   = "La valeur minimale '{0}' pour {1} n'est pas valide, elle doit être supérieure ou égale à {2}"
    accessRequiresAuthenticationOnRoutesExceptionMessage              = "L'accès nécessite une authentification sur les routes."
    noSecretForHmac384ExceptionMessage                                = 'Aucun secret fourni pour le hachage HMAC384.'
    windowsLocalAuthSupportIsForWindowsOnlyExceptionMessage           = "Le support de l'authentification locale Windows est uniquement pour Windows."
    definitionTagNotDefinedExceptionMessage                           = 'Tag de définition {0} non défini.'
    noComponentInDefinitionExceptionMessage                           = "Aucun composant du type {0} nommé {1} n'est disponible dans la définition {2}."
    noSmtpHandlersDefinedExceptionMessage                             = 'Aucun gestionnaire SMTP défini.'
    sessionMiddlewareAlreadyInitializedExceptionMessage               = 'Le Middleware de session a déjà été initialisé.'
    reusableComponentPathItemsNotAvailableInOpenApi30ExceptionMessage = "La fonctionnalité du composant réutilisable 'pathItems' n'est pas disponible dans OpenAPI v3.0."
    wildcardHeadersIncompatibleWithAutoHeadersExceptionMessage        = 'Le caractère générique * pour les En-têtes est incompatible avec le commutateur AutoHeaders.'
    noDataForFileUploadedExceptionMessage                             = "Aucune donnée pour le fichier '{0}' n'a été téléchargée dans la demande."
    sseOnlyConfiguredOnEventStreamAcceptHeaderExceptionMessage        = "SSE ne peut être configuré que sur les requêtes avec une valeur d'en-tête Accept de text/event-stream."
    noSessionAvailableToSaveExceptionMessage                          = 'Aucune session disponible pour sauvegarder.'
    pathParameterRequiresRequiredSwitchExceptionMessage               = "Si l'emplacement du paramètre est 'Path', le paramètre switch 'Required' est obligatoire."
    noOpenApiUrlSuppliedExceptionMessage                              = 'Aucune URL OpenAPI fournie pour {0}.'
    maximumConcurrentSchedulesInvalidExceptionMessage                 = 'Les horaires simultanés maximum doivent être >=1 mais obtenu: {0}'
    snapinsSupportedOnWindowsPowershellOnlyExceptionMessage           = 'Les Snapins sont uniquement pris en charge sur Windows PowerShell.'
    eventViewerLoggingSupportedOnWindowsOnlyExceptionMessage          = "La journalisation dans le Visualisateur d'événements n'est prise en charge que sous Windows."
    parametersMutuallyExclusiveExceptionMessage                       = "Les paramètres '{0}' et '{1}' sont mutuellement exclusifs."
    pathItemsFeatureNotSupportedInOpenApi30ExceptionMessage           = "La fonction PathItems n'est pas prise en charge dans OpenAPI v3.0.x"
    openApiParameterRequiresNameExceptionMessage                      = 'Le paramètre OpenApi nécessite un nom spécifié.'
    maximumConcurrentTasksLessThanMinimumExceptionMessage             = 'Le nombre maximum de tâches simultanées ne peut pas être inférieur au minimum de {0}, mais a obtenu : {1}'
    noSemaphoreFoundExceptionMessage                                  = "Aucun sémaphore trouvé appelé '{0}'"
    singleValueForIntervalExceptionMessage                            = "Vous ne pouvez fournir qu'une seule valeur {0} lorsque vous utilisez des intervalles."
    jwtNotYetValidExceptionMessage                                    = "Le JWT n'est pas encore valide pour une utilisation."
    verbAlreadyDefinedForUrlExceptionMessage                          = '[Verbe] {0} : Déjà défini pour {1}'
    noSecretNamedMountedExceptionMessage                              = "Aucun Secret nommé '{0}' n'a été monté."
    moduleOrVersionNotFoundExceptionMessage                           = 'Module ou version introuvable sur {0} : {1}@{2}'
    noScriptBlockSuppliedExceptionMessage                             = 'Aucun ScriptBlock fourni.'
    noSecretVaultRegisteredExceptionMessage                           = "Aucun coffre-fort de secrets enregistré sous le nom '{0}'."
    nameRequiredForEndpointIfRedirectToSuppliedExceptionMessage       = 'Un nom est requis pour le point de terminaison si le paramètre RedirectTo est fourni.'
    openApiLicenseObjectRequiresNameExceptionMessage                  = "L'objet OpenAPI 'license' nécessite la propriété 'name'. Utilisez le paramètre -LicenseName."
    sourcePathDoesNotExistForStaticRouteExceptionMessage              = "{0}: Le chemin source fourni pour la Route statique n'existe pas: {1}"
    noNameForWebSocketDisconnectExceptionMessage                      = 'Aucun Nom fourni pour déconnecter le WebSocket.'
    certificateExpiredExceptionMessage                                = "Le certificat '{0}' a expiré: {1}"
    secretVaultUnlockExpiryDateInPastExceptionMessage                 = "La date d'expiration du déverrouillage du Coffre-Fort de Secrets est dans le passé (UTC) : {0}"
    invalidWebExceptionTypeExceptionMessage                           = "L'exception est d'un type non valide, doit être soit WebException soit HttpRequestException, mais a obtenu : {0}"
    invalidSecretValueTypeExceptionMessage                            = "La valeur du secret est d'un type non valide. Types attendus : String, SecureString, HashTable, Byte[], ou PSCredential. Mais a obtenu : {0}"
    explicitTlsModeOnlySupportedOnSmtpsTcpsEndpointsExceptionMessage  = "Le mode TLS explicite n'est pris en charge que sur les points de terminaison SMTPS et TCPS."
    discriminatorMappingRequiresDiscriminatorPropertyExceptionMessage = "Le paramètre 'DiscriminatorMapping' ne peut être utilisé que lorsque 'DiscriminatorProperty' est présent."
    scriptErrorExceptionMessage                                       = "Erreur '{0}' dans le script {1} {2} (ligne {3}) char {4} en exécutant {5} sur l'objet {6} '{7}' Classe : {8} ClasseBase : {9}"
    cannotSupplyIntervalForQuarterExceptionMessage                    = "Impossible de fournir une valeur d'intervalle pour chaque trimestre."
    scheduleEndTimeMustBeInFutureExceptionMessage                     = '[Horaire] {0}: La valeur de EndTime doit être dans le futur.'
    invalidJwtSignatureSuppliedExceptionMessage                       = 'Signature JWT fournie invalide.'
    noSetScriptBlockForVaultExceptionMessage                          = "Aucun ScriptBlock de configuration fourni pour mettre à jour/créer des secrets dans le coffre '{0}'"
    accessMethodNotExistForMergingExceptionMessage                    = "La méthode d'accès n'existe pas pour la fusion : {0}"
    defaultAuthNotInListExceptionMessage                              = "L'authentification par défaut '{0}' n'est pas dans la liste d'authentification fournie."
    parameterHasNoNameExceptionMessage                                = "Le paramètre n'a pas de nom. Veuillez donner un nom à ce composant en utilisant le paramètre 'Name'."
    methodPathAlreadyDefinedForUrlExceptionMessage                    = '[{0}] {1} : Déjà défini pour {2}'
    fileWatcherAlreadyDefinedExceptionMessage                         = "Un Observateur de fichiers nommé '{0}' a déjà été défini."
    noServiceHandlersDefinedExceptionMessage                          = 'Aucun gestionnaire de service défini.'
    secretRequiredForCustomSessionStorageExceptionMessage             = "Un secret est requis lors de l'utilisation d'un stockage de session personnalisé."
    secretManagementModuleNotInstalledExceptionMessage                = "Le module Microsoft.PowerShell.SecretManagement n'est pas installé."
    noPathSuppliedForRouteExceptionMessage                            = 'Aucun chemin fourni pour la route.'
    validationOfAnyOfSchemaNotSupportedExceptionMessage               = "La validation d'un schéma qui inclut 'anyof' n'est pas prise en charge."
    iisAuthSupportIsForWindowsOnlyExceptionMessage                    = "Le support de l'authentification IIS est uniquement pour Windows."
    oauth2InnerSchemeInvalidExceptionMessage                          = 'Le OAuth2 InnerScheme ne peut être que Basic ou Form, mais obtenu : {0}'
    noRoutePathSuppliedForPageExceptionMessage                        = 'Aucun chemin de route fourni pour la page {0}.'
    cacheStorageNotFoundForExistsExceptionMessage                     = "Le stockage de cache nommé '{0}' est introuvable lors de la tentative de vérification de l'existence de l'élément mis en cache '{1}'."
    handlerAlreadyDefinedExceptionMessage                             = '[{0}] {1}: Handler déjà défini.'
    sessionsNotConfiguredExceptionMessage                             = "Les sessions n'ont pas été configurées."
    propertiesTypeObjectAssociationExceptionMessage                   = 'Seules les propriétés de type Objet peuvent être associées à {0}.'
    sessionsRequiredForSessionPersistentAuthExceptionMessage          = "Des sessions sont nécessaires pour utiliser l'authentification persistante par session."
    invalidPathWildcardOrDirectoryExceptionMessage                    = 'Le chemin fourni ne peut pas être un caractère générique ou un répertoire : {0}'
    accessMethodAlreadyDefinedExceptionMessage                        = "Méthode d'accès déjà définie : {0}"
    parametersValueOrExternalValueMandatoryExceptionMessage           = "Les paramètres 'Value' ou 'ExternalValue' sont obligatoires."
    maximumConcurrentTasksInvalidExceptionMessage                     = 'Le nombre maximum de tâches simultanées doit être >=1, mais a obtenu : {0}'
    cannotCreatePropertyWithoutTypeExceptionMessage                   = "Impossible de créer la propriété car aucun type n'est défini."
    authMethodNotExistForMergingExceptionMessage                      = "La méthode d'authentification n'existe pas pour la fusion : {0}"
    maxValueInvalidExceptionMessage                                   = "La valeur maximale '{0}' pour {1} n'est pas valide, elle doit être inférieure ou égale à {2}"
    endpointAlreadyDefinedExceptionMessage                            = "Un point de terminaison nommé '{0}' a déjà été défini."
    eventAlreadyRegisteredExceptionMessage                            = 'Événement {0} déjà enregistré : {1}'
    parameterNotSuppliedInRequestExceptionMessage                     = "Un paramètre nommé '{0}' n'a pas été fourni dans la demande ou aucune donnée n'est disponible."
    cacheStorageNotFoundForSetExceptionMessage                        = "Le stockage de cache nommé '{0}' est introuvable lors de la tentative de définition de l'élément mis en cache '{1}'."
    methodPathAlreadyDefinedExceptionMessage                          = '[{0}] {1} : Déjà défini.'
    errorLoggingAlreadyEnabledExceptionMessage                        = 'La journalisation des erreurs est déjà activée.'
    valueForUsingVariableNotFoundExceptionMessage                     = "Valeur pour '`$using:{0}' introuvable."
    rapidPdfDoesNotSupportOpenApi31ExceptionMessage                   = "L'outil de documentation RapidPdf ne prend pas en charge OpenAPI 3.1"
    oauth2ClientSecretRequiredExceptionMessage                        = "OAuth2 nécessite un Client Secret lorsque PKCE n'est pas utilisé."
    invalidBase64JwtExceptionMessage                                  = 'Valeur encodée en Base64 non valide trouvée dans le JWT'
    noSessionToCalculateDataHashExceptionMessage                      = 'Aucune session disponible pour calculer le hachage de données.'
    cacheStorageNotFoundForRemoveExceptionMessage                     = "Le stockage de cache nommé '{0}' est introuvable lors de la tentative de suppression de l'élément mis en cache '{1}'."
    csrfMiddlewareNotInitializedExceptionMessage                      = "Le Middleware CSRF n'a pas été initialisé."
    infoTitleMandatoryMessage                                         = 'info.title est obligatoire.'
    typeCanOnlyBeAssociatedWithObjectExceptionMessage                 = "Le type {0} ne peut être associé qu'à un Objet."
    userFileDoesNotExistExceptionMessage                              = "Le fichier utilisateur n'existe pas : {0}"
    routeParameterNeedsValidScriptblockExceptionMessage               = 'Le paramètre de la route nécessite un ScriptBlock valide et non vide.'
    nextTriggerCalculationErrorExceptionMessage                       = 'Il semble que quelque chose ait mal tourné lors de la tentative de calcul de la prochaine date et heure de déclenchement : {0}'
    cannotLockValueTypeExceptionMessage                               = 'Impossible de verrouiller un [ValueType].'
    failedToCreateOpenSslCertExceptionMessage                         = 'Échec de la création du certificat OpenSSL : {0}'
    jwtExpiredExceptionMessage                                        = 'Le JWT a expiré.'
    openingGuiMessage                                                 = "Ouverture de l'interface graphique."
    multiTypePropertiesRequireOpenApi31ExceptionMessage               = 'Les propriétés multi-types nécessitent OpenApi Version 3.1 ou supérieure.'
    noNameForWebSocketRemoveExceptionMessage                          = 'Aucun Nom fourni pour supprimer le WebSocket.'
    maxSizeInvalidExceptionMessage                                    = 'MaxSize doit être égal ou supérieur à 0, mais a obtenu: {0}'
    iisShutdownMessage                                                = "(Arrêt de l'IIS)"
    cannotUnlockValueTypeExceptionMessage                             = 'Impossible de déverrouiller un [ValueType].'
    noJwtSignatureForAlgorithmExceptionMessage                        = 'Aucune signature JWT fournie pour {0}.'
    maximumConcurrentWebSocketThreadsInvalidExceptionMessage          = 'Le nombre maximum de threads WebSocket simultanés doit être >=1, mais a obtenu : {0}'
    acknowledgeMessageOnlySupportedOnSmtpTcpEndpointsExceptionMessage = "Le message de reconnaissance n'est pris en charge que sur les points de terminaison SMTP et TCP."
    failedToConnectToUrlExceptionMessage                              = "Échec de la connexion à l'URL : {0}"
    failedToAcquireMutexOwnershipExceptionMessage                     = "Échec de l'acquisition de la propriété du mutex. Nom du mutex: {0}"
    sessionsRequiredForOAuth2WithPKCEExceptionMessage                 = 'Des sessions sont nécessaires pour utiliser OAuth2 avec PKCE.'
    failedToConnectToWebSocketExceptionMessage                        = 'Échec de la connexion au WebSocket : {0}'
    unsupportedObjectExceptionMessage                                 = 'Objet non pris en charge'
    failedToParseAddressExceptionMessage                              = "Échec de l'analyse de '{0}' en tant qu'adresse IP/Hôte:Port valide"
    mustBeRunningWithAdminPrivilegesExceptionMessage                  = 'Doit être exécuté avec des privilèges administratifs pour écouter sur des adresses autres que localhost.'
    specificationMessage                                              = 'Spécification'
    cacheStorageNotFoundForClearExceptionMessage                      = "Le stockage de cache nommé '{0}' est introuvable lors de la tentative de vider le cache."
    restartingServerMessage                                           = 'Redémarrage du serveur...'
    cannotSupplyIntervalWhenEveryIsNoneExceptionMessage               = "Impossible de fournir un intervalle lorsque le paramètre 'Every' est défini sur None."
    unsupportedJwtAlgorithmExceptionMessage                           = "L'algorithme JWT n'est actuellement pas pris en charge : {0}"
    websocketsNotConfiguredForSignalMessagesExceptionMessage          = 'Les WebSockets ne sont pas configurés pour envoyer des messages de signal.'
    invalidLogicTypeInHashtableMiddlewareExceptionMessage             = 'Un Middleware Hashtable fourni a un type de logique non valide. Attendu ScriptBlock, mais a obtenu : {0}'
    maximumConcurrentSchedulesLessThanMinimumExceptionMessage         = 'Les Horaires simultanés maximum ne peuvent pas être inférieurs au minimum de {0} mais obtenu: {1}'
    failedToAcquireSemaphoreOwnershipExceptionMessage                 = "Échec de l'acquisition de la propriété du sémaphore. Nom du sémaphore: {0}"
    propertiesParameterWithoutNameExceptionMessage                    = "Les paramètres Properties ne peuvent pas être utilisés si la propriété n'a pas de nom."
    customSessionStorageMethodNotImplementedExceptionMessage          = "Le stockage de session personnalisé n'implémente pas la méthode requise '{0}()'."
    authenticationMethodDoesNotExistExceptionMessage                  = "La méthode d'authentification n'existe pas : {0}"
    webhooksFeatureNotSupportedInOpenApi30ExceptionMessage            = "La fonction Webhooks n'est pas prise en charge dans OpenAPI v3.0.x"
    invalidContentTypeForSchemaExceptionMessage                       = "'content-type' invalide trouvé pour le schéma : {0}"
    noUnlockScriptBlockForVaultExceptionMessage                       = "Aucun ScriptBlock de déverrouillage fourni pour déverrouiller le coffre '{0}'"
    definitionTagMessage                                              = 'Définition {0} :'
    failedToOpenRunspacePoolExceptionMessage                          = "Échec de l'ouverture de RunspacePool : {0}"
    failedToCloseRunspacePoolExceptionMessage                         = 'Échec de la fermeture du RunspacePool: {0}'
    verbNoLogicPassedExceptionMessage                                 = '[Verbe] {0} : Aucune logique transmise'
    noMutexFoundExceptionMessage                                      = "Aucun mutex trouvé appelé '{0}'"
    documentationMessage                                              = 'Documentation'
    timerAlreadyDefinedExceptionMessage                               = '[Minuteur] {0}: Minuteur déjà défini.'
    invalidPortExceptionMessage                                       = 'Le port ne peut pas être négatif : {0}'
    viewsFolderNameAlreadyExistsExceptionMessage                      = 'Le nom du dossier Views existe déjà: {0}'
    noNameForWebSocketResetExceptionMessage                           = 'Aucun Nom fourni pour réinitialiser le WebSocket.'
    mergeDefaultAuthNotInListExceptionMessage                         = "L'authentification MergeDefault '{0}' n'est pas dans la liste d'authentification fournie."
    descriptionRequiredExceptionMessage                               = 'Une description est requise pour le chemin:{0} Réponse:{1}'
    pageNameShouldBeAlphaNumericExceptionMessage                      = 'Le nom de la page doit être une valeur alphanumérique valide: {0}'
    defaultValueNotBooleanOrEnumExceptionMessage                      = "La valeur par défaut n'est pas un booléen et ne fait pas partie de l'énumération."
    openApiComponentSchemaDoesNotExistExceptionMessage                = "Le schéma du composant OpenApi {0} n'existe pas."
    timerParameterMustBeGreaterThanZeroExceptionMessage               = '[Minuteur] {0}: {1} doit être supérieur à 0.'
    taskTimedOutExceptionMessage                                      = 'La tâche a expiré après {0}ms.'
    scheduleStartTimeAfterEndTimeExceptionMessage                     = "[Horaire] {0}: Ne peut pas avoir un 'StartTime' après 'EndTime'"
    infoVersionMandatoryMessage                                       = 'info.version est obligatoire.'
    cannotUnlockNullObjectExceptionMessage                            = 'Impossible de déverrouiller un objet nul.'
    nonEmptyScriptBlockRequiredForCustomAuthExceptionMessage          = "Un ScriptBlock non vide est requis pour le schéma d'authentification personnalisé."
    nonEmptyScriptBlockRequiredForAuthMethodExceptionMessage          = "Un ScriptBlock non vide est requis pour la méthode d'authentification."
    validationOfOneOfSchemaNotSupportedExceptionMessage               = "La validation d'un schéma qui inclut 'oneof' n'est pas prise en charge."
    routeParameterCannotBeNullExceptionMessage                        = "Le paramètre 'Route' ne peut pas être nul."
    cacheStorageAlreadyExistsExceptionMessage                         = "Un stockage de cache nommé '{0}' existe déjà."
    loggingMethodRequiresValidScriptBlockExceptionMessage             = "La méthode de sortie fournie pour la méthode de journalisation '{0}' nécessite un ScriptBlock valide."
    scopedVariableAlreadyDefinedExceptionMessage                      = 'La variable à portée est déjà définie : {0}'
    oauth2RequiresAuthorizeUrlExceptionMessage                        = "OAuth2 nécessite une URL d'autorisation."
    pathNotExistExceptionMessage                                      = "Le chemin n'existe pas : {0}"
    noDomainServerNameForWindowsAdAuthExceptionMessage                = "Aucun nom de serveur de domaine n'a été fourni pour l'authentification Windows AD."
    suppliedDateAfterScheduleEndTimeExceptionMessage                  = "La date fournie est postérieure à l'heure de fin du Horaire à {0}"
    wildcardMethodsIncompatibleWithAutoMethodsExceptionMessage        = 'Le caractère générique * pour les Méthodes est incompatible avec le commutateur AutoMethods.'
    cannotSupplyIntervalForYearExceptionMessage                       = "Impossible de fournir une valeur d'intervalle pour chaque année."
    missingComponentsMessage                                          = 'Composant(s) manquant(s)'
    invalidStrictTransportSecurityDurationExceptionMessage            = 'Durée Strict-Transport-Security invalide fournie : {0}. Doit être supérieure à 0.'
    noSecretForHmac512ExceptionMessage                                = 'Aucun secret fourni pour le hachage HMAC512.'
    daysInMonthExceededExceptionMessage                               = "{0} n'a que {1} jours, mais {2} a été fourni."
    nonEmptyScriptBlockRequiredForCustomLoggingExceptionMessage       = 'Un ScriptBlock non vide est requis pour la méthode de journalisation personnalisée.'
    encodingAttributeOnlyAppliesToMultipartExceptionMessage           = "L'attribut d'encodage s'applique uniquement aux corps de requête multipart et application/x-www-form-urlencoded."
    suppliedDateBeforeScheduleStartTimeExceptionMessage               = "La date fournie est antérieure à l'heure de début du Horaire à {0}"
    unlockSecretRequiredExceptionMessage                              = "Une propriété 'UnlockSecret' est requise lors de l'utilisation de Microsoft.PowerShell.SecretStore"
    noLogicPassedForMethodRouteExceptionMessage                       = '[{0}] {1}: Aucune logique passée.'
    bodyParserAlreadyDefinedForContentTypeExceptionMessage            = 'Un analyseur de corps est déjà défini pour le type de contenu {0}.'
    invalidJwtSuppliedExceptionMessage                                = 'JWT fourni invalide.'
    sessionsRequiredForFlashMessagesExceptionMessage                  = 'Des sessions sont nécessaires pour utiliser les messages Flash.'
    semaphoreAlreadyExistsExceptionMessage                            = 'Un sémaphore avec le nom suivant existe déjà: {0}'
    invalidJwtHeaderAlgorithmSuppliedExceptionMessage                 = "Algorithme de l'en-tête JWT fourni invalide."
    oauth2ProviderDoesNotSupportPasswordGrantTypeExceptionMessage     = "Le fournisseur OAuth2 ne supporte pas le type de subvention 'password' requis par l'utilisation d'un InnerScheme."
    invalidAliasFoundExceptionMessage                                 = 'Alias {0} non valide trouvé : {1}'
    scheduleDoesNotExistExceptionMessage                              = "Le Horaire '{0}' n'existe pas."
    accessMethodNotExistExceptionMessage                              = "La méthode d'accès n'existe pas : {0}"
    oauth2ProviderDoesNotSupportCodeResponseTypeExceptionMessage      = "Le fournisseur OAuth2 ne supporte pas le type de réponse 'code'."
    untestedPowerShellVersionWarningMessage                           = "[AVERTISSEMENT] Pode {0} n'a pas été testé sur PowerShell {1}, car il n'était pas disponible lors de la sortie de Pode."
    secretVaultAlreadyRegisteredAutoImportExceptionMessage            = "Un coffre-fort secret avec le nom '{0}' a déjà été enregistré lors de l'importation automatique des coffres-forts secrets."
    schemeRequiresValidScriptBlockExceptionMessage                    = "Le schéma fourni pour le validateur d'authentification '{0}' nécessite un ScriptBlock valide."
    serverLoopingMessage                                              = 'Boucle du serveur toutes les {0} secondes'
    certificateThumbprintsNameSupportedOnWindowsExceptionMessage      = 'Les empreintes digitales/Noms de certificat ne sont pris en charge que sous Windows.'
    sseConnectionNameRequiredExceptionMessage                         = "Un nom de connexion SSE est requis, soit de -Name soit de `$WebEvent.Sse.Name"
    invalidMiddlewareTypeExceptionMessage                             = "Un des Middlewares fournis est d'un type non valide. Attendu ScriptBlock ou Hashtable, mais a obtenu : {0}"
    noSecretForJwtSignatureExceptionMessage                           = 'Aucun secret fourni pour la signature JWT.'
    modulePathDoesNotExistExceptionMessage                            = "Le chemin du module n'existe pas : {0}"
    taskAlreadyDefinedExceptionMessage                                = '[Tâche] {0} : Tâche déjà définie.'
    verbAlreadyDefinedExceptionMessage                                = '[Verbe] {0} : Déjà défini'
    clientCertificatesOnlySupportedOnHttpsEndpointsExceptionMessage   = 'Les certificats client ne sont pris en charge que sur les points de terminaison HTTPS.'
    endpointNameNotExistExceptionMessage                              = "Un point de terminaison avec le nom '{0}' n'existe pas."
    middlewareNoLogicSuppliedExceptionMessage                         = '[Middleware] : Aucune logique fournie dans le ScriptBlock.'
    scriptBlockRequiredForMergingUsersExceptionMessage                = 'Un ScriptBlock est requis pour fusionner plusieurs utilisateurs authentifiés en un seul objet lorsque Valid est All.'
    secretVaultAlreadyRegisteredExceptionMessage                      = "Un Coffre-Fort de Secrets avec le nom '{0}' a déjà été enregistré{1}."
    deprecatedTitleVersionDescriptionWarningMessage                   = "AVERTISSEMENT : Titre, Version et Description sur 'Enable-PodeOpenApi' sont obsolètes. Veuillez utiliser 'Add-PodeOAInfo' à la place."
    undefinedOpenApiReferencesMessage                                 = 'Références OpenAPI non définies :'
    doneMessage                                                       = 'Terminé'
    swaggerEditorDoesNotSupportOpenApi31ExceptionMessage              = 'Cette version de Swagger-Editor ne prend pas en charge OpenAPI 3.1'
    durationMustBeZeroOrGreaterExceptionMessage                       = 'La durée doit être égale ou supérieure à 0, mais a obtenu : {0}s'
    viewsPathDoesNotExistExceptionMessage                             = "Le chemin des Views n'existe pas: {0}"
    discriminatorIncompatibleWithAllOfExceptionMessage                = "Le paramètre 'Discriminator' est incompatible avec 'allOf'."
    noNameForWebSocketSendMessageExceptionMessage                     = 'Aucun Nom fourni pour envoyer un message au WebSocket.'
    hashtableMiddlewareNoLogicExceptionMessage                        = "Un Middleware Hashtable fourni n'a aucune logique définie."
    openApiInfoMessage                                                = 'Informations OpenAPI :'
    invalidSchemeForAuthValidatorExceptionMessage                     = "Le schéma '{0}' fourni pour le validateur d'authentification '{1}' nécessite un ScriptBlock valide."
    sseFailedToBroadcastExceptionMessage                              = 'SSE a échoué à diffuser en raison du niveau de diffusion SSE défini pour {0} : {1}.'
    adModuleWindowsOnlyExceptionMessage                               = 'Le module Active Directory est uniquement disponible sur Windows.'
    requestLoggingAlreadyEnabledExceptionMessage                      = 'La journalisation des requêtes est déjà activée.'
    invalidAccessControlMaxAgeDurationExceptionMessage                = 'Durée Access-Control-Max-Age invalide fournie : {0}. Doit être supérieure à 0.'
    openApiDefinitionAlreadyExistsExceptionMessage                    = 'La définition OpenAPI nommée {0} existe déjà.'
    renamePodeOADefinitionTagExceptionMessage                         = "Rename-PodeOADefinitionTag ne peut pas être utilisé à l'intérieur d'un 'ScriptBlock' de Select-PodeOADefinition."
    taskProcessDoesNotExistExceptionMessage                           = "Le processus de la tâche '{0}' n'existe pas."
    scheduleProcessDoesNotExistExceptionMessage                       = "Le processus de l'horaire '{0}' n'existe pas."
    definitionTagChangeNotAllowedExceptionMessage                     = 'Le tag de définition pour une Route ne peut pas être modifié.'
    getRequestBodyNotAllowedExceptionMessage                          = 'Les opérations {0} ne peuvent pas avoir de corps de requête.'
    fnDoesNotAcceptArrayAsPipelineInputExceptionMessage               = "La fonction '{0}' n'accepte pas un tableau en tant qu'entrée de pipeline."
    unsupportedStreamCompressionEncodingExceptionMessage              = "La compression de flux {0} n'est pas prise en charge."
}

src\Locales\it\Pode.psd1
@{
    schemaValidationRequiresPowerShell610ExceptionMessage             = 'La convalida dello schema richiede PowerShell versione 6.1.0 o superiore.'
    customAccessPathOrScriptBlockRequiredExceptionMessage             = 'È necessario un percorso o un ScriptBlock per ottenere i valori di accesso personalizzati.'
    operationIdMustBeUniqueForArrayExceptionMessage                   = 'OperationID: {0} deve essere univoco e non può essere applicato a una matrice.'
    endpointNotDefinedForRedirectingExceptionMessage                  = "Non è stato definito un endpoint denominato '{0}' per il reindirizzamento."
    filesHaveChangedMessage                                           = 'I seguenti file sono stati modificati:'
    iisAspnetcoreTokenMissingExceptionMessage                         = 'IIS ASPNETCORE_TOKEN è mancante.'
    minValueGreaterThanMaxExceptionMessage                            = 'Il valore minimo per {0} non deve essere maggiore del valore massimo.'
    noLogicPassedForRouteExceptionMessage                             = "Nessuna logica passata per la 'route': {0}"
    scriptPathDoesNotExistExceptionMessage                            = 'Il percorso dello script non esiste: {0}'
    mutexAlreadyExistsExceptionMessage                                = 'Un mutex con il seguente nome esiste già: {0}'
    listeningOnEndpointsMessage                                       = 'In ascolto sui seguenti {0} endpoint [{1} thread]:'
    unsupportedFunctionInServerlessContextExceptionMessage            = "La funzione {0} non è supportata in un contesto 'serverless'."
    expectedNoJwtSignatureSuppliedExceptionMessage                    = 'La firma JWT è inaspettata.'
    secretAlreadyMountedExceptionMessage                              = "Un 'Secret' con il nome '{0}' è già stato montato."
    failedToAcquireLockExceptionMessage                               = "Impossibile acquisire un blocco sull'oggetto."
    noPathSuppliedForStaticRouteExceptionMessage                      = "[{0}]: Nessun percorso fornito per la 'route' statica."
    invalidHostnameSuppliedExceptionMessage                           = 'Nome host fornito non valido: {0}'
    authMethodAlreadyDefinedExceptionMessage                          = 'Metodo di autenticazione già definito: {0}'
    csrfCookieRequiresSecretExceptionMessage                          = "Quando si usano i cookie per CSRF, è necessario un 'Secret'. Puoi fornire uno o impostare il 'Secret' a livello globale - (Set-PodeCookieSecret '<value>' -Global)"
    nonEmptyScriptBlockRequiredForPageRouteExceptionMessage           = "È richiesto uno 'ScriptBlock' non vuoto per creare una 'route'."
    noPropertiesMutuallyExclusiveExceptionMessage                     = "Il parametro 'NoProperties' è mutuamente esclusivo con 'Properties', 'MinProperties' e 'MaxProperties'."
    incompatiblePodeDllExceptionMessage                               = "È caricata una versione incompatibile esistente di 'Pode.DLL' {0}. È richiesta la versione {1}. Apri una nuova sessione Powershell/pwsh e riprova."
    accessMethodDoesNotExistExceptionMessage                          = 'Il metodo di accesso non esiste: {0}.'
    scheduleAlreadyDefinedExceptionMessage                            = '[Schedulatore] {0}: Pianificazione già definita.'
    secondsValueCannotBeZeroOrLessExceptionMessage                    = 'Il valore dei secondi non può essere 0 o inferiore per {0}'
    pathToLoadNotFoundExceptionMessage                                = 'Percorso per caricare {0} non trovato: {1}'
    failedToImportModuleExceptionMessage                              = 'Importazione del modulo non riuscita: {0}'
    endpointNotExistExceptionMessage                                  = "'Endpoint' con protocollo '{0}' e indirizzo '{1}' o indirizzo locale '{2}' non esiste."
    terminatingMessage                                                = 'Terminazione...'
    noCommandsSuppliedToConvertToRoutesExceptionMessage               = "Nessun comando fornito per convertirlo in 'route'."
    invalidTaskTypeExceptionMessage                                   = 'Il tipo di attività non è valido, previsto [System.Threading.Tasks.Task] o [hashtable].'
    alreadyConnectedToWebSocketExceptionMessage                       = "Già connesso al WebSocket con il nome '{0}'"
    crlfMessageEndCheckOnlySupportedOnTcpEndpointsExceptionMessage    = 'Il controllo di fine messaggio CRLF è supportato solo sugli endpoint TCP.'
    testPodeOAComponentSchemaNeedToBeEnabledExceptionMessage          = "'Test-PodeOAComponentSchema' deve essere abilitato utilizzando 'Enable-PodeOpenApi -EnableSchemaValidation'"
    adModuleNotInstalledExceptionMessage                              = 'Il modulo Active Directory non è installato.'
    cronExpressionInvalidExceptionMessage                             = "L'espressione Cron dovrebbe essere composta solo da 5 parti: {0}"
    noSessionToSetOnResponseExceptionMessage                          = "Non c'è nessuna sessione disponibile per la risposta."
    valueOutOfRangeExceptionMessage                                   = "Il valore '{0}' per {1} non è valido, dovrebbe essere compreso tra {2} e {3}"
    loggingMethodAlreadyDefinedExceptionMessage                       = 'Metodo di registrazione già definito: {0}'
    noSecretForHmac256ExceptionMessage                                = "Nessun segreto fornito per l'hash HMAC256."
    eolPowerShellWarningMessage                                       = '[ATTENZIONE] Pode {0} non è stato testato su PowerShell {1}, perche è EOL.'
    runspacePoolFailedToLoadExceptionMessage                          = 'Impossibile caricare RunspacePool per {0}.'
    noEventRegisteredExceptionMessage                                 = 'Nessun evento {0} registrato: {1}'
    scheduleCannotHaveNegativeLimitExceptionMessage                   = '[Schedulatore] {0}: Non può avere un limite negativo.'
    openApiRequestStyleInvalidForParameterExceptionMessage            = 'Lo stile della richiesta OpenAPI non può essere {0} per un parametro {1}.'
    openApiDocumentNotCompliantExceptionMessage                       = 'Il documento non è conforme con le specificazioni OpenAPI.'
    taskDoesNotExistExceptionMessage                                  = "L'attività '{0}' non esiste."
    scopedVariableNotFoundExceptionMessage                            = 'Variabile di ambito non trovata: {0}'
    sessionsRequiredForCsrfExceptionMessage                           = 'Le sessioni sono necessarie per utilizzare CSRF a meno che non si vogliano usare i cookie.'
    nonEmptyScriptBlockRequiredForLoggingMethodExceptionMessage       = 'È richiesto uno ScriptBlock non vuoto per il metodo di registrazione.'
    credentialsPassedWildcardForHeadersLiteralExceptionMessage        = 'Quando vengono passate le Credenziali, il carattere jolly * per le Intestazioni sarà considerato come una stringa letterale e non come un carattere jolly.'
    podeNotInitializedExceptionMessage                                = 'Pode non è stato inizializzato.'
    multipleEndpointsForGuiMessage                                    = 'Sono stati definiti più endpoint, solo il primo sarà utilizzato per la GUI.'
    operationIdMustBeUniqueExceptionMessage                           = 'OperationID: {0} deve essere univoco.'
    invalidJsonJwtExceptionMessage                                    = 'Valore JSON non valido trovato in JWT'
    noAlgorithmInJwtHeaderExceptionMessage                            = "Nessun algoritmo fornito nell'header JWT."
    openApiVersionPropertyMandatoryExceptionMessage                   = 'La proprietà versione OpenAPI è obbligatoria.'
    limitValueCannotBeZeroOrLessExceptionMessage                      = 'Il valore limite non può essere 0 o inferiore per {0}'
    timerDoesNotExistExceptionMessage                                 = "Timer '{0}' non esiste."
    openApiGenerationDocumentErrorMessage                             = 'Errore nella generazione del documento OpenAPI:'
    routeAlreadyContainsCustomAccessExceptionMessage                  = "Il percorso '[{0}] {1}' contiene già un accesso personalizzato con nome '{2}'"
    maximumConcurrentWebSocketThreadsLessThanMinimumExceptionMessage  = 'Il numero massimo di thread WebSocket simultanei non può essere inferiore al minimo di {0}, ma è stato ottenuto: {1}'
    middlewareAlreadyDefinedExceptionMessage                          = '[Middleware] {0}: Middleware già definito.'
    invalidAtomCharacterExceptionMessage                              = "Carattere cron 'atom' non valido: {0}"
    invalidCronAtomFormatExceptionMessage                             = "Formato cron 'atom' non valido trovato: {0}"
    cacheStorageNotFoundForRetrieveExceptionMessage                   = "Memoria cache con nome '{0}' non trovata durante il tentativo di recuperare l'elemento memorizzato nella cache '{1}'."
    headerMustHaveNameInEncodingContextExceptionMessage               = "L'intestazione deve avere un nome quando viene utilizzata in un contesto di codifica."
    moduleDoesNotContainFunctionExceptionMessage                      = "Il modulo {0} non contiene la funzione {1} da convertire in una 'route'."
    pathToIconForGuiDoesNotExistExceptionMessage                      = "Il percorso dell'icona per la GUI non esiste: {0}"
    noTitleSuppliedForPageExceptionMessage                            = 'Nessun titolo fornito per la pagina {0}.'
    certificateSuppliedForNonHttpsWssEndpointExceptionMessage         = 'Certificato fornito per un endpoint non HTTPS/WSS.'
    cannotLockNullObjectExceptionMessage                              = 'Non è possibile bloccare un oggetto nullo.'
    showPodeGuiOnlyAvailableOnWindowsExceptionMessage                 = 'Show-PodeGui è attualmente disponibile solo per Windows PowerShell e PowerShell 7+ su Windows OS.'
    unlockSecretButNoScriptBlockExceptionMessage                      = "'Secret' di sblocco fornito per tipo di 'Secret Vault' personalizzata, ma nessun ScriptBlock di sblocco è fornito."
    invalidIpAddressExceptionMessage                                  = "L'indirizzo IP fornito non è valido: {0}"
    maxDaysInvalidExceptionMessage                                    = 'MaxDays deve essere 0 o superiore, ma è stato ottenuto: {0}'
    noRemoveScriptBlockForVaultExceptionMessage                       = "Nessun ScriptBlock fornito per rimuovere 'Secret Vault' '{0}'"
    noSecretExpectedForNoSignatureExceptionMessage                    = "Non era previsto alcun 'Secret' per nessuna firma."
    noCertificateFoundExceptionMessage                                = "Nessun certificato trovato in {0}{1} per '{2}'"
    minValueInvalidExceptionMessage                                   = "Il valore minimo '{0}' per {1} non è valido, dovrebbe essere maggiore o uguale a {2}"
    accessRequiresAuthenticationOnRoutesExceptionMessage              = "L'accesso richiede l'autenticazione sulle rotte."
    noSecretForHmac384ExceptionMessage                                = "Nessun 'Secret' fornito per l'hash HMAC384."
    windowsLocalAuthSupportIsForWindowsOnlyExceptionMessage           = "Il supporto per l'autenticazione locale di Windows è solo per Windows OS."
    definitionTagNotDefinedExceptionMessage                           = 'Tag di definizione {0} non existe.'
    noComponentInDefinitionExceptionMessage                           = 'Nessun componente del tipo {0} chiamato {1} è disponibile nella definizione {2}.'
    noSmtpHandlersDefinedExceptionMessage                             = 'Non sono stati definiti gestori SMTP.'
    sessionMiddlewareAlreadyInitializedExceptionMessage               = 'Il Middleware della sessione è già stato inizializzato.'
    reusableComponentPathItemsNotAvailableInOpenApi30ExceptionMessage = "La funzione del componente riutilizzabile 'pathItems' non è disponibile in OpenAPI v3.0."
    wildcardHeadersIncompatibleWithAutoHeadersExceptionMessage        = "Il carattere jolly * per le Intestazioni è incompatibile con l'opzione AutoHeaders."
    noDataForFileUploadedExceptionMessage                             = "Nessun dato per il file '{0}' è stato caricato nella richiesta."
    sseOnlyConfiguredOnEventStreamAcceptHeaderExceptionMessage        = 'SSE può essere configurato solo su richieste con un valore di intestazione Accept di text/event-stream.'
    noSessionAvailableToSaveExceptionMessage                          = 'Nessuna sessione disponibile per il salvataggio.'
    pathParameterRequiresRequiredSwitchExceptionMessage               = "Se la posizione del parametro è 'Path', il parametro switch 'Required' è obbligatorio."
    noOpenApiUrlSuppliedExceptionMessage                              = 'Nessun URL OpenAPI fornito per {0}.'
    maximumConcurrentSchedulesInvalidExceptionMessage                 = 'Il numero massimo di schedulazioni concorrenti deve essere >=1 ma invece è: {0}'
    snapinsSupportedOnWindowsPowershellOnlyExceptionMessage           = 'Gli Snapin sono supportati solo con Windows PowerShell.'
    eventViewerLoggingSupportedOnWindowsOnlyExceptionMessage          = 'La registrazione nel Visualizzatore eventi è supportata solo su Windows OS.'
    parametersMutuallyExclusiveExceptionMessage                       = "I parametri '{0}' e '{1}' sono mutuamente esclusivi."
    pathItemsFeatureNotSupportedInOpenApi30ExceptionMessage           = "La funzionalità 'PathItems' non è supportata in OpenAPI v3.0.x"
    openApiParameterRequiresNameExceptionMessage                      = 'Il parametro OpenAPI richiede che un nome sia specificato.'
    maximumConcurrentTasksLessThanMinimumExceptionMessage             = 'Il numero massimo di attività simultanee non può essere inferiore al minimo di {0}, ma è stato fornito: {1}'
    noSemaphoreFoundExceptionMessage                                  = "Nessun semaforo trovato chiamato '{0}'"
    singleValueForIntervalExceptionMessage                            = 'Puoi fornire solo un singolo valore {0} quando si utilizzano gli intervalli.'
    jwtNotYetValidExceptionMessage                                    = "JWT non è ancora valido per l'uso."
    verbAlreadyDefinedForUrlExceptionMessage                          = '[Verbo] {0}: Già definito per {1}'
    noSecretNamedMountedExceptionMessage                              = "Nessun 'Secret' con il nome '{0}' è stato montato."
    moduleOrVersionNotFoundExceptionMessage                           = 'Modulo o versione non trovati su {0}: {1}@{2}'
    noScriptBlockSuppliedExceptionMessage                             = "Nessun 'ScriptBlock' fornito."
    noSecretVaultRegisteredExceptionMessage                           = "Nessuna 'Secret Vault' con il nome '{0}' è stato registrata."
    nameRequiredForEndpointIfRedirectToSuppliedExceptionMessage       = "È richiesto un nome per l'endpoint se viene fornito il parametro 'RedirectTo'."
    openApiLicenseObjectRequiresNameExceptionMessage                  = "L'oggetto OpenAPI 'license' richiede la proprietà 'name'. Utilizzare il parametro -LicenseName."
    sourcePathDoesNotExistForStaticRouteExceptionMessage              = "{0}: Il percorso sorgente fornito per la 'route' statica non esiste: {1}"
    noNameForWebSocketDisconnectExceptionMessage                      = 'Nessun nome fornito per disconnettere il WebSocket.'
    certificateExpiredExceptionMessage                                = "Il certificato '{0}' è scaduto: {1}"
    secretVaultUnlockExpiryDateInPastExceptionMessage                 = "La data di scadenza per sbloccare la 'Secret Vault' è nel passato (UTC): {0}"
    invalidWebExceptionTypeExceptionMessage                           = "L'eccezione è di un tipo non valido, dovrebbe essere WebException o HttpRequestException, ma invece è: {0}"
    invalidSecretValueTypeExceptionMessage                            = "Il valore 'Secret' è di un tipo non valido. Tipi previsti: String, SecureString, HashTable, Byte[] o PSCredential. Ma ottenuto: {0}"
    explicitTlsModeOnlySupportedOnSmtpsTcpsEndpointsExceptionMessage  = 'La modalità TLS esplicita è supportata solo sugli endpoint SMTPS e TCPS.'
    discriminatorMappingRequiresDiscriminatorPropertyExceptionMessage = "Il parametro 'DiscriminatorMapping' può essere utilizzato solo quando è presente 'DiscriminatorProperty'."
    scriptErrorExceptionMessage                                       = "Errore '{0}' nello script {1} {2} (riga {3}) carattere {4} eseguendo {5} su {6} oggetto '{7}' Classe: {8} Classe di base: {9}"
    cannotSupplyIntervalForQuarterExceptionMessage                    = 'Impossibile fornire un valore di intervallo per ogni trimestre.'
    scheduleEndTimeMustBeInFutureExceptionMessage                     = '[Schedulatore] {0}: Il valore di EndTime deve essere nel futuro.'
    invalidJwtSignatureSuppliedExceptionMessage                       = 'Firma JWT fornita non valida.'
    noSetScriptBlockForVaultExceptionMessage                          = "Nessun 'ScriptBlock' fornito per aggiornare/creare 'Secret Vault' '{0}'"
    accessMethodNotExistForMergingExceptionMessage                    = "Il metodo di accesso non esiste per l'unione: {0}"
    defaultAuthNotInListExceptionMessage                              = "L'autenticazione predefinita '{0}' non è nella lista di autenticazione fornita."
    parameterHasNoNameExceptionMessage                                = "Il parametro non ha un nome. Assegna un nome a questo componente usando il parametro 'Name'."
    methodPathAlreadyDefinedForUrlExceptionMessage                    = '[{0}] {1}: Già definito per {2}'
    fileWatcherAlreadyDefinedExceptionMessage                         = "Un 'FileWatcher' con il nome '{0}' è già stato definito."
    noServiceHandlersDefinedExceptionMessage                          = 'Non sono stati definiti gestori di servizio.'
    secretRequiredForCustomSessionStorageExceptionMessage             = "Un 'Secret' è riquesto quando si utilizza l'archiviazione delle sessioni personalizzata."
    secretManagementModuleNotInstalledExceptionMessage                = 'Il modulo Microsoft.PowerShell.SecretManagement non è installato.'
    noPathSuppliedForRouteExceptionMessage                            = "Nessun percorso fornito per la 'route'."
    validationOfAnyOfSchemaNotSupportedExceptionMessage               = "La validazione di uno schema che include 'anyof' non è supportata."
    iisAuthSupportIsForWindowsOnlyExceptionMessage                    = "Il supporto per l'autenticazione IIS è solo per Windows OS."
    oauth2InnerSchemeInvalidExceptionMessage                          = 'OAuth2 InnerScheme può essere solo di tipo Basic o Form, ma non di tipo: {0}'
    noRoutePathSuppliedForPageExceptionMessage                        = "Nessun percorso di 'route' fornito per la pagina {0}."
    cacheStorageNotFoundForExistsExceptionMessage                     = "Memoria cache con nome '{0}' non trovata durante il tentativo di verificare se l'elemento memorizzato nella cache '{1}' esiste."
    handlerAlreadyDefinedExceptionMessage                             = '[{0}] {1}: Handler già definito.'
    sessionsNotConfiguredExceptionMessage                             = 'Le sessioni non sono state configurate.'
    propertiesTypeObjectAssociationExceptionMessage                   = "Solo le proprietà di tipo 'Object' possono essere associate a {0}."
    sessionsRequiredForSessionPersistentAuthExceptionMessage          = "Sono necessarie sessioni per utilizzare l'autenticazione persistente della sessione."
    invalidPathWildcardOrDirectoryExceptionMessage                    = 'Il percorso fornito non può essere un carattere jolly o una directory: {0}'
    accessMethodAlreadyDefinedExceptionMessage                        = 'Metodo di accesso già definito: {0}'
    parametersValueOrExternalValueMandatoryExceptionMessage           = "I parametri 'Value' o 'ExternalValue' sono obbligatori."
    maximumConcurrentTasksInvalidExceptionMessage                     = 'Il numero massimo di attività simultanee deve essere >=1, {0} non è valido.'
    cannotCreatePropertyWithoutTypeExceptionMessage                   = 'Impossibile creare la proprietà perché manca la definizione di tipo.'
    authMethodNotExistForMergingExceptionMessage                      = 'Il metodo di autenticazione non esiste per la aggregazione: {0}'
    maxValueInvalidExceptionMessage                                   = "Il valore massimo '{0}' per {1} non è valido, dovrebbe essere minore o uguale a {2}"
    endpointAlreadyDefinedExceptionMessage                            = "Un endpoint denominato '{0}' è già stato definito."
    eventAlreadyRegisteredExceptionMessage                            = 'Evento {0} già registrato: {1}'
    parameterNotSuppliedInRequestExceptionMessage                     = "Un parametro chiamato '{0}' non è stato fornito nella richiesta o non ci sono dati disponibili."
    cacheStorageNotFoundForSetExceptionMessage                        = "Memoria cache con nome '{0}' non trovata durante il tentativo di impostare l'elemento memorizzato nella cache '{1}'."
    methodPathAlreadyDefinedExceptionMessage                          = '[{0}] {1}: Già definito.'
    errorLoggingAlreadyEnabledExceptionMessage                        = 'La registrazione degli errori è già abilitata.'
    valueForUsingVariableNotFoundExceptionMessage                     = "Impossibile trovare il valore per '`$using:{0}'."
    rapidPdfDoesNotSupportOpenApi31ExceptionMessage                   = 'Lo strumento di documentazione RapidPdf non supporta OpenAPI 3.1'
    oauth2ClientSecretRequiredExceptionMessage                        = 'OAuth2 richiede un Client Secret quando non si utilizza PKCE.'
    invalidBase64JwtExceptionMessage                                  = 'Valore codificato Base64 non valido trovato in JWT'
    noSessionToCalculateDataHashExceptionMessage                      = "Nessuna sessione disponibile per calcolare l'hash dei dati."
    cacheStorageNotFoundForRemoveExceptionMessage                     = "Memoria cache con nome '{0}' non trovata durante il tentativo di rimuovere l'elemento memorizzato nella cache '{1}'."
    csrfMiddlewareNotInitializedExceptionMessage                      = 'Il Middleware CSRF non è stato inizializzato.'
    infoTitleMandatoryMessage                                         = 'info.title è obbligatorio.'
    typeCanOnlyBeAssociatedWithObjectExceptionMessage                 = 'Il tipo {0} può essere associato solo a un oggetto.'
    userFileDoesNotExistExceptionMessage                              = 'Il file utente non esiste: {0}'
    routeParameterNeedsValidScriptblockExceptionMessage               = "Il parametro della 'route' richiede uno ScriptBlock valido e non vuoto."
    nextTriggerCalculationErrorExceptionMessage                       = 'Sembra che ci sia stato un errore nel tentativo di calcolare la prossima data e ora del trigger: {0}'
    cannotLockValueTypeExceptionMessage                               = 'Non è possibile bloccare un [ValueType].'
    failedToCreateOpenSslCertExceptionMessage                         = 'Impossibile creare il certificato OpenSSL: {0}'
    jwtExpiredExceptionMessage                                        = 'JWT è scaduto.'
    openingGuiMessage                                                 = 'Apertura della GUI.'
    multiTypePropertiesRequireOpenApi31ExceptionMessage               = 'Le proprietà multi-tipo richiedono OpenAPI versione 3.1 o superiore.'
    noNameForWebSocketRemoveExceptionMessage                          = 'Nessun nome fornito per rimuovere il WebSocket.'
    maxSizeInvalidExceptionMessage                                    = 'MaxSize deve essere 0 o superiore, ma è stato ottenuto: {0}'
    iisShutdownMessage                                                = '(Chiusura IIS)'
    cannotUnlockValueTypeExceptionMessage                             = 'Non è possibile sbloccare un [ValueType].'
    noJwtSignatureForAlgorithmExceptionMessage                        = 'Nessuna firma JWT fornita per {0}.'
    maximumConcurrentWebSocketThreadsInvalidExceptionMessage          = 'Il numero massimo di thread WebSocket simultanei deve essere >=1, ma è stato ottenuto: {0}'
    acknowledgeMessageOnlySupportedOnSmtpTcpEndpointsExceptionMessage = 'Il messaggio di conferma è supportato solo sugli endpoint SMTP e TCP.'
    failedToConnectToUrlExceptionMessage                              = "Impossibile connettersi all'URL: {0}"
    failedToAcquireMutexOwnershipExceptionMessage                     = 'Impossibile acquisire la proprietà del mutex. Nome del mutex: {0}'
    sessionsRequiredForOAuth2WithPKCEExceptionMessage                 = 'Sono necessarie sessioni per utilizzare OAuth2 con PKCE'
    failedToConnectToWebSocketExceptionMessage                        = 'Connessione al WebSocket non riuscita: {0}'
    unsupportedObjectExceptionMessage                                 = 'Oggetto non supportato'
    failedToParseAddressExceptionMessage                              = "Impossibile analizzare '{0}' come indirizzo IP/Host:Port valido"
    mustBeRunningWithAdminPrivilegesExceptionMessage                  = 'Deve essere eseguito con privilegi di amministratore per usare indirizzi non locali.'
    specificationMessage                                              = 'Specifica'
    cacheStorageNotFoundForClearExceptionMessage                      = "Memoria cache con nome '{0}' non trovata durante il tentativo di cancellare la cache."
    restartingServerMessage                                           = 'Riavvio del server...'
    cannotSupplyIntervalWhenEveryIsNoneExceptionMessage               = "Impossibile fornire un intervallo quando il parametro 'Every' è 'None'."
    unsupportedJwtAlgorithmExceptionMessage                           = "L'algoritmo JWT non è attualmente supportato: {0}"
    websocketsNotConfiguredForSignalMessagesExceptionMessage          = 'I WebSockets non sono configurati per inviare messaggi di segnale.'
    invalidLogicTypeInHashtableMiddlewareExceptionMessage             = "Un Middleware di tipo Hashtable fornito ha un tipo di logica non valido. Previsto 'ScriptBlock', invece di: {0}"
    maximumConcurrentSchedulesLessThanMinimumExceptionMessage         = 'Il numero di schedulazioni concorrenti massime non può essere inferiore al minimo di {0}. Valore passato: {1}'
    failedToAcquireSemaphoreOwnershipExceptionMessage                 = 'Impossibile acquisire la proprietà del semaforo. Nome del semaforo: {0}'
    propertiesParameterWithoutNameExceptionMessage                    = "I parametri 'Properties' non possono essere utilizzati se la proprietà non ha un nome."
    customSessionStorageMethodNotImplementedExceptionMessage          = "L'archiviazione delle sessioni personalizzata non implementa il metodo richiesto '{0}()'."
    authenticationMethodDoesNotExistExceptionMessage                  = 'Il metodo di autenticazione non esiste: {0}'
    webhooksFeatureNotSupportedInOpenApi30ExceptionMessage            = 'La funzionalità Webhooks non è supportata in OpenAPI v3.0.x'
    invalidContentTypeForSchemaExceptionMessage                       = "'content-type' non valido trovato per lo schema: {0}"
    noUnlockScriptBlockForVaultExceptionMessage                       = "Nessun 'ScriptBlock' di sblocco fornito per sbloccare la 'Secret Vault' '{0}'"
    definitionTagMessage                                              = 'Definizione {0}:'
    failedToOpenRunspacePoolExceptionMessage                          = 'Impossibile aprire RunspacePool: {0}'
    failedToCloseRunspacePoolExceptionMessage                         = 'Impossibile chiudere RunspacePool: {0}'
    verbNoLogicPassedExceptionMessage                                 = '[Verbo] {0}: Nessuna logica passata'
    noMutexFoundExceptionMessage                                      = "Nessun mutex trovato chiamato '{0}'"
    documentationMessage                                              = 'Documentazione'
    timerAlreadyDefinedExceptionMessage                               = '[Timer] {0}: Timer già definito.'
    invalidPortExceptionMessage                                       = 'La porta non può essere un numero negativo: {0}'
    viewsFolderNameAlreadyExistsExceptionMessage                      = "Il nome della cartella 'Views' esiste già: {0}"
    noNameForWebSocketResetExceptionMessage                           = 'Nessun nome fornito per reimpostare il WebSocket.'
    mergeDefaultAuthNotInListExceptionMessage                         = "L'autenticazione MergeDefault '{0}' non è nella lista di autenticazione fornita."
    descriptionRequiredExceptionMessage                               = 'È necessaria una descrizione per il percorso:{0} Risposta:{1}'
    pageNameShouldBeAlphaNumericExceptionMessage                      = 'Il nome della pagina dovrebbe essere un valore alfanumerico valido: {0}'
    defaultValueNotBooleanOrEnumExceptionMessage                      = "Il valore predefinito non è un booleano e non fa parte dell'enum."
    openApiComponentSchemaDoesNotExistExceptionMessage                = 'Lo schema del componente OpenAPI {0} non esiste.'
    timerParameterMustBeGreaterThanZeroExceptionMessage               = '[Timer] {0}: {1} deve essere maggiore di 0.'
    taskTimedOutExceptionMessage                                      = "Il 'Task' è scaduto dopo {0}ms."
    scheduleStartTimeAfterEndTimeExceptionMessage                     = "[Schedulatore] {0}: Non può avere un 'StartTime' sucessivo a 'EndTime'"
    infoVersionMandatoryMessage                                       = 'info.version è obbligatorio.'
    cannotUnlockNullObjectExceptionMessage                            = 'Non è possibile sbloccare un oggetto nullo.'
    nonEmptyScriptBlockRequiredForCustomAuthExceptionMessage          = 'È richiesto uno ScriptBlock non vuoto per lo schema di autenticazione personalizzato.'
    nonEmptyScriptBlockRequiredForAuthMethodExceptionMessage          = 'È necessario un ScriptBlock non vuoto per il metodo di autenticazione.'
    validationOfOneOfSchemaNotSupportedExceptionMessage               = "La validazione di uno schema che include 'oneof' non è supportata."
    routeParameterCannotBeNullExceptionMessage                        = "Il parametro 'Route' non può essere null."
    cacheStorageAlreadyExistsExceptionMessage                         = "Memoria cache con nome '{0}' esiste già."
    loggingMethodRequiresValidScriptBlockExceptionMessage             = "Il metodo di output fornito per il metodo di registrazione '{0}' richiede un ScriptBlock valido."
    scopedVariableAlreadyDefinedExceptionMessage                      = 'Variabile con ambito già definita: {0}'
    oauth2RequiresAuthorizeUrlExceptionMessage                        = "OAuth2 richiede che venga fornita un'URL di autorizzazione"
    pathNotExistExceptionMessage                                      = 'Il percorso non esiste: {0}'
    noDomainServerNameForWindowsAdAuthExceptionMessage                = "Non è stato fornito alcun nome di server di dominio per l'autenticazione AD di Windows"
    suppliedDateAfterScheduleEndTimeExceptionMessage                  = "La data fornita è successiva all'ora di fine del programma a {0}"
    wildcardMethodsIncompatibleWithAutoMethodsExceptionMessage        = "Il carattere jolly * per i Metodi è incompatibile con l'opzione AutoMethods."
    cannotSupplyIntervalForYearExceptionMessage                       = 'Impossibile fornire un valore di intervallo per ogni anno.'
    missingComponentsMessage                                          = 'Componenti mancanti'
    invalidStrictTransportSecurityDurationExceptionMessage            = 'Durata Strict-Transport-Security non valida fornita: {0}. Deve essere maggiore di 0.'
    noSecretForHmac512ExceptionMessage                                = "Nessun 'secret' fornito per l'hash HMAC512."
    daysInMonthExceededExceptionMessage                               = '{0} ha solo {1} giorni, ma è stato fornito {2}.'
    nonEmptyScriptBlockRequiredForCustomLoggingExceptionMessage       = 'È richiesto uno ScriptBlock non vuoto per il metodo di registrazione personalizzato.'
    encodingAttributeOnlyAppliesToMultipartExceptionMessage           = "L'attributo di codifica si applica solo ai corpi delle richieste multipart e application/x-www-form-urlencoded."
    suppliedDateBeforeScheduleStartTimeExceptionMessage               = "La data fornita è precedente all'ora di inizio del programma a {0}"
    unlockSecretRequiredExceptionMessage                              = "È necessaria una proprietà 'UnlockSecret' quando si utilizza Microsoft.PowerShell.SecretStore"
    noLogicPassedForMethodRouteExceptionMessage                       = '[{0}] {1}: Nessuna logica passata.'
    bodyParserAlreadyDefinedForContentTypeExceptionMessage            = 'Un body-parser è già definito per il tipo di contenuto {0}.'
    invalidJwtSuppliedExceptionMessage                                = 'JWT fornito non valido.'
    sessionsRequiredForFlashMessagesExceptionMessage                  = 'Le sessioni sono necessarie per utilizzare i messaggi di tipo Flash.'
    semaphoreAlreadyExistsExceptionMessage                            = 'Un semaforo con il seguente nome esiste già: {0}'
    invalidJwtHeaderAlgorithmSuppliedExceptionMessage                 = "Algoritmo dell'header JWT fornito non valido."
    oauth2ProviderDoesNotSupportPasswordGrantTypeExceptionMessage     = "Il provider OAuth2 non supporta il tipo di concessione 'password' richiesto dall'utilizzo di un InnerScheme."
    invalidAliasFoundExceptionMessage                                 = 'Alias {0} non valido trovato: {1}'
    scheduleDoesNotExistExceptionMessage                              = "Il programma '{0}' non esiste."
    accessMethodNotExistExceptionMessage                              = 'Il metodo di accesso non esiste: {0}'
    oauth2ProviderDoesNotSupportCodeResponseTypeExceptionMessage      = "Il provider OAuth2 non supporta il tipo di risposta 'code'."
    untestedPowerShellVersionWarningMessage                           = '[ATTENZIONE] Pode {0} non è stato testato su PowerShell {1}, poiché non era disponibile quando Pode è stato rilasciato.'
    secretVaultAlreadyRegisteredAutoImportExceptionMessage            = "Una 'Secret Vault' con il nome '{0}' è già stata registrata durante l'importazione automatica delle 'Secret Vaults'."
    schemeRequiresValidScriptBlockExceptionMessage                    = "Lo schema fornito per il validatore di autenticazione '{0}' richiede uno ScriptBlock valido."
    serverLoopingMessage                                              = 'Ciclo del server ogni {0} secondi'
    certificateThumbprintsNameSupportedOnWindowsExceptionMessage      = 'Impronte digitali/nome del certificato supportati solo su Windows OS.'
    sseConnectionNameRequiredExceptionMessage                         = "È richiesto un nome di connessione SSE, sia da -Name che da `$WebEvent.Sse.Name"
    invalidMiddlewareTypeExceptionMessage                             = 'Uno dei Middleware forniti è di un tipo non valido. Previsto ScriptBlock o Hashtable, ma ottenuto: {0}'
    noSecretForJwtSignatureExceptionMessage                           = "Nessun 'secret' fornito per la firma JWT."
    modulePathDoesNotExistExceptionMessage                            = 'Il percorso del modulo non esiste: {0}'
    taskAlreadyDefinedExceptionMessage                                = '[Attività] {0}: Attività già definita.'
    verbAlreadyDefinedExceptionMessage                                = '[Verbo] {0}: Già definito'
    clientCertificatesOnlySupportedOnHttpsEndpointsExceptionMessage   = 'I certificati client sono supportati solo sugli endpoint HTTPS.'
    endpointNameNotExistExceptionMessage                              = "Endpoint con nome '{0}' non esiste."
    middlewareNoLogicSuppliedExceptionMessage                         = '[Middleware]: Nessuna logica fornita nello ScriptBlock.'
    scriptBlockRequiredForMergingUsersExceptionMessage                = "È richiesto uno ScriptBlock per unire più utenti autenticati in un unico oggetto quando 'Valid' è uguale a 'All'."
    secretVaultAlreadyRegisteredExceptionMessage                      = "Una 'Secret Vault' con il nome '{0}' è già stato registrata{1}."
    deprecatedTitleVersionDescriptionWarningMessage                   = "ATTENZIONE: Titolo, Versione e Descrizione su 'Enable-PodeOpenApi' sono deprecati. Si prega di utilizzare 'Add-PodeOAInfo' invece."
    undefinedOpenApiReferencesMessage                                 = 'Riferimenti OpenAPI non definiti:'
    doneMessage                                                       = 'Fatto'
    swaggerEditorDoesNotSupportOpenApi31ExceptionMessage              = 'Questa versione di Swagger-Editor non supporta OpenAPI 3.1'
    durationMustBeZeroOrGreaterExceptionMessage                       = 'La durata deve essere 0 o superiore, non {0}s'
    viewsPathDoesNotExistExceptionMessage                             = 'Il percorso delle Views non esiste: {0}'
    discriminatorIncompatibleWithAllOfExceptionMessage                = "Il parametro 'Discriminator' è incompatibile con 'allOf'."
    noNameForWebSocketSendMessageExceptionMessage                     = 'Nessun nome fornito per inviare un messaggio al WebSocket.'
    hashtableMiddlewareNoLogicExceptionMessage                        = 'Un Middleware di tipo Hashtable fornito non ha una logica definita.'
    openApiInfoMessage                                                = 'Informazioni OpenAPI:'
    invalidSchemeForAuthValidatorExceptionMessage                     = "Lo schema '{0}' fornito per il validatore di autenticazione '{1}' richiede uno ScriptBlock valido."
    sseFailedToBroadcastExceptionMessage                              = 'SSE non è riuscito a trasmettere a causa del livello di trasmissione SSE definito per {0}: {1}.'
    adModuleWindowsOnlyExceptionMessage                               = 'Il modulo Active Directory è disponibile solo su Windows OS.'
    requestLoggingAlreadyEnabledExceptionMessage                      = 'La registrazione delle richieste è già abilitata.'
    invalidAccessControlMaxAgeDurationExceptionMessage                = 'Durata non valida fornita per Access-Control-Max-Age: {0}. Deve essere maggiore di 0.'
    openApiDefinitionAlreadyExistsExceptionMessage                    = 'La definizione OpenAPI denominata {0} esiste già.'
    renamePodeOADefinitionTagExceptionMessage                         = "Rename-PodeOADefinitionTag non può essere utilizzato all'interno di un 'ScriptBlock' di Select-PodeOADefinition."
    taskProcessDoesNotExistExceptionMessage                           = "Il processo dell'attività '{0}' non esiste."
    scheduleProcessDoesNotExistExceptionMessage                       = "Il processo della programma '{0}' non esiste."
    definitionTagChangeNotAllowedExceptionMessage                     = 'Il tag di definizione per una Route non può essere cambiato.'
    getRequestBodyNotAllowedExceptionMessage                          = 'Le operazioni {0} non possono avere un corpo della richiesta.'
    fnDoesNotAcceptArrayAsPipelineInputExceptionMessage               = "La funzione '{0}' non accetta una matrice come input della pipeline."
    unsupportedStreamCompressionEncodingExceptionMessage              = 'La compressione dello stream non è supportata per la codifica {0}'
}
src\Locales\ja\Pode.psd1
@{
    schemaValidationRequiresPowerShell610ExceptionMessage             = 'スキーマ検証には PowerShell バージョン 6.1.0 以上が必要です。'
    customAccessPathOrScriptBlockRequiredExceptionMessage             = 'カスタムアクセス値のソース化には、パスまたはスクリプトブロックが必要です。'
    operationIdMustBeUniqueForArrayExceptionMessage                   = 'OperationID: {0} は一意でなければならず、配列に適用できません。'
    endpointNotDefinedForRedirectingExceptionMessage                  = "リダイレクトのために名前 '{0}' のエンドポイントが定義されていません。"
    filesHaveChangedMessage                                           = '次のファイルが変更されました:'
    iisAspnetcoreTokenMissingExceptionMessage                         = 'IIS ASPNETCORE_TOKENがありません。'
    minValueGreaterThanMaxExceptionMessage                            = '{0}の最小値は最大値を超えることはできません。'
    noLogicPassedForRouteExceptionMessage                             = 'ルートに対してロジックが渡されませんでした: {0}'
    scriptPathDoesNotExistExceptionMessage                            = 'スクリプトパスが存在しません: {0}'
    mutexAlreadyExistsExceptionMessage                                = '次の名前のミューテックスはすでに存在します: {0}'
    listeningOnEndpointsMessage                                       = '次の {0} エンドポイントでリッスンしています [{1} スレッド]:'
    unsupportedFunctionInServerlessContextExceptionMessage            = 'サーバーレスコンテキストではサポートされていない関数です: {0}'
    expectedNoJwtSignatureSuppliedExceptionMessage                    = '提供されるべきではないJWT署名が予期されました。'
    secretAlreadyMountedExceptionMessage                              = "名前 '{0}' のシークレットは既にマウントされています。"
    failedToAcquireLockExceptionMessage                               = 'オブジェクトのロックを取得できませんでした。'
    noPathSuppliedForStaticRouteExceptionMessage                      = '[{0}]: 静的ルートに対して提供されたパスがありません。'
    invalidHostnameSuppliedExceptionMessage                           = '無効なホスト名が指定されました: {0}'
    authMethodAlreadyDefinedExceptionMessage                          = '認証方法はすでに定義されています:{0}'
    csrfCookieRequiresSecretExceptionMessage                          = "CSRFのためにクッキーを使用する場合、秘密が必要です。秘密を提供するか、クッキーのグローバル秘密を設定してください - (Set-PodeCookieSecret '<value>' -Global)"
    nonEmptyScriptBlockRequiredForPageRouteExceptionMessage           = 'ページルートを作成するには空でないScriptBlockが必要です。'
    noPropertiesMutuallyExclusiveExceptionMessage                     = "パラメーター'NoProperties'は'Properties'、'MinProperties'、および'MaxProperties'と相互排他的です。"
    incompatiblePodeDllExceptionMessage                               = '既存の互換性のないPode.DLLバージョン{0}がロードされています。バージョン{1}が必要です。新しいPowerShell/pwshセッションを開いて再試行してください。'
    accessMethodDoesNotExistExceptionMessage                          = 'アクセスメソッドが存在しません:{0}。'
    scheduleAlreadyDefinedExceptionMessage                            = '[スケジュール] {0}: スケジュールはすでに定義されています。'
    secondsValueCannotBeZeroOrLessExceptionMessage                    = '{0}の秒数値は0またはそれ以下にすることはできません。'
    pathToLoadNotFoundExceptionMessage                                = '読み込むパス{0}が見つかりません: {1}'
    failedToImportModuleExceptionMessage                              = 'モジュールのインポートに失敗しました: {0}'
    endpointNotExistExceptionMessage                                  = "プロトコル'{0}'、アドレス'{1}'またはローカルアドレス'{2}'のエンドポイントが存在しません。"
    terminatingMessage                                                = '終了中...'
    noCommandsSuppliedToConvertToRoutesExceptionMessage               = 'ルートに変換するためのコマンドが提供されていません。'
    invalidTaskTypeExceptionMessage                                   = 'タスクタイプが無効です。予期されるタイプ:[System.Threading.Tasks.Task]または[hashtable]'
    alreadyConnectedToWebSocketExceptionMessage                       = "名前 '{0}' の WebSocket に既に接続されています"
    crlfMessageEndCheckOnlySupportedOnTcpEndpointsExceptionMessage    = 'CRLFメッセージ終了チェックはTCPエンドポイントでのみサポートされています。'
    testPodeOAComponentSchemaNeedToBeEnabledExceptionMessage          = "'Test-PodeOAComponentSchema' は 'Enable-PodeOpenApi -EnableSchemaValidation' を使用して有効にする必要があります。"
    adModuleNotInstalledExceptionMessage                              = 'Active Directoryモジュールがインストールされていません。'
    cronExpressionInvalidExceptionMessage                             = 'Cron式は5つの部分で構成される必要があります: {0}'
    noSessionToSetOnResponseExceptionMessage                          = 'レスポンスに設定するセッションがありません。'
    valueOutOfRangeExceptionMessage                                   = "{1}の値'{0}'は無効です。{2}から{3}の間でなければなりません。"
    loggingMethodAlreadyDefinedExceptionMessage                       = 'ログ記録方法は既に定義されています: {0}'
    noSecretForHmac256ExceptionMessage                                = 'HMAC256ハッシュに対する秘密が提供されていません。'
    eolPowerShellWarningMessage                                       = '[警告] Pode {0} は、EOLであるPowerShell {1} でテストされていません。'
    runspacePoolFailedToLoadExceptionMessage                          = '{0} RunspacePoolの読み込みに失敗しました。'
    noEventRegisteredExceptionMessage                                 = '登録された{0}イベントはありません:{1}'
    scheduleCannotHaveNegativeLimitExceptionMessage                   = '[スケジュール] {0}: 負の制限を持つことはできません。'
    openApiRequestStyleInvalidForParameterExceptionMessage            = 'OpenApi リクエストのスタイルは {1} パラメータに対して {0} であってはなりません。'
    openApiDocumentNotCompliantExceptionMessage                       = 'OpenAPIドキュメントが準拠していません。'
    taskDoesNotExistExceptionMessage                                  = "タスク '{0}' は存在しません。"
    scopedVariableNotFoundExceptionMessage                            = 'スコープ変数が見つかりません: {0}'
    sessionsRequiredForCsrfExceptionMessage                           = 'クッキーを使用しない場合は、CSRFを使用するためにセッションが必要です。'
    nonEmptyScriptBlockRequiredForLoggingMethodExceptionMessage       = 'ロギングメソッドには空でないScriptBlockが必要です。'
    credentialsPassedWildcardForHeadersLiteralExceptionMessage        = '資格情報が渡されると、ヘッダーのワイルドカード * はワイルドカードとしてではなく、リテラル文字列として解釈されます。'
    podeNotInitializedExceptionMessage                                = 'Podeが初期化されていません。'
    multipleEndpointsForGuiMessage                                    = '複数のエンドポイントが定義されていますが、GUIには最初のエンドポイントのみが使用されます。'
    operationIdMustBeUniqueExceptionMessage                           = 'OperationID: {0} は一意でなければなりません。'
    invalidJsonJwtExceptionMessage                                    = 'JWTに無効なJSON値が見つかりました。'
    noAlgorithmInJwtHeaderExceptionMessage                            = 'JWTヘッダーにアルゴリズムが提供されていません。'
    openApiVersionPropertyMandatoryExceptionMessage                   = 'OpenApiバージョンプロパティは必須です。'
    limitValueCannotBeZeroOrLessExceptionMessage                      = '{0}の制限値は0またはそれ以下にすることはできません。'
    timerDoesNotExistExceptionMessage                                 = "タイマー '{0}' は存在しません。"
    openApiGenerationDocumentErrorMessage                             = 'OpenAPI生成ドキュメントエラー:'
    routeAlreadyContainsCustomAccessExceptionMessage                  = "ルート '[{0}] {1}' はすでに名前 '{2}' のカスタムアクセスを含んでいます"
    maximumConcurrentWebSocketThreadsLessThanMinimumExceptionMessage  = '最大同時 WebSocket スレッド数は最小値 {0} より小さくてはいけませんが、取得した値は: {1}'
    middlewareAlreadyDefinedExceptionMessage                          = '[Middleware] {0}: ミドルウェアは既に定義されています。'
    invalidAtomCharacterExceptionMessage                              = '無効なアトム文字: {0}'
    invalidCronAtomFormatExceptionMessage                             = '無効な cron アトム形式が見つかりました: {0}'
    cacheStorageNotFoundForRetrieveExceptionMessage                   = "キャッシュされたアイテム '{1}' を取得しようとしたときに、名前 '{0}' のキャッシュストレージが見つかりません。"
    headerMustHaveNameInEncodingContextExceptionMessage               = 'エンコーディングコンテキストで使用される場合、ヘッダーには名前が必要です。'
    moduleDoesNotContainFunctionExceptionMessage                      = 'モジュール {0} にはルートに変換する関数 {1} が含まれていません。'
    pathToIconForGuiDoesNotExistExceptionMessage                      = 'GUI用アイコンのパスが存在しません: {0}'
    noTitleSuppliedForPageExceptionMessage                            = '{0} ページのタイトルが提供されていません。'
    certificateSuppliedForNonHttpsWssEndpointExceptionMessage         = 'HTTPS/WSS以外のエンドポイントに提供された証明書。'
    cannotLockNullObjectExceptionMessage                              = 'nullオブジェクトをロックできません。'
    showPodeGuiOnlyAvailableOnWindowsExceptionMessage                 = 'Show-PodeGuiは現在、Windows PowerShellおよびWindows上のPowerShell 7+でのみ利用可能です。'
    unlockSecretButNoScriptBlockExceptionMessage                      = 'カスタムシークレットボールトタイプに対してアンロックシークレットが提供されましたが、アンロックスクリプトブロックが提供されていません。'
    invalidIpAddressExceptionMessage                                  = '提供されたIPアドレスは無効です: {0}'
    maxDaysInvalidExceptionMessage                                    = 'MaxDaysは0以上でなければなりませんが、受け取った値は: {0}'
    noRemoveScriptBlockForVaultExceptionMessage                       = "ボールト'{0}'のシークレットを削除するためのスクリプトブロックが提供されていません。"
    noSecretExpectedForNoSignatureExceptionMessage                    = '署名なしのための秘密が提供されることを期待していませんでした。'
    noCertificateFoundExceptionMessage                                = "'{2}'用の{0}{1}に証明書が見つかりませんでした。"
    minValueInvalidExceptionMessage                                   = "{1}の最小値'{0}'は無効です。{2}以上でなければなりません。"
    accessRequiresAuthenticationOnRoutesExceptionMessage              = 'アクセスにはルート上の認証が必要です。'
    noSecretForHmac384ExceptionMessage                                = 'HMAC384ハッシュに対する秘密が提供されていません。'
    windowsLocalAuthSupportIsForWindowsOnlyExceptionMessage           = 'Windowsローカル認証のサポートはWindowsのみです。'
    definitionTagNotDefinedExceptionMessage                           = '定義タグ {0} が定義されていません。'
    noComponentInDefinitionExceptionMessage                           = '{2}定義に{0}タイプの名前{1}コンポーネントが利用できません。'
    noSmtpHandlersDefinedExceptionMessage                             = 'SMTPハンドラが定義されていません。'
    sessionMiddlewareAlreadyInitializedExceptionMessage               = 'セッションミドルウェアは既に初期化されています。'
    reusableComponentPathItemsNotAvailableInOpenApi30ExceptionMessage = "OpenAPI v3.0では再利用可能なコンポーネント機能'pathItems'は使用できません。"
    wildcardHeadersIncompatibleWithAutoHeadersExceptionMessage        = 'ヘッダーのワイルドカード * は AutoHeaders スイッチと互換性がありません。'
    noDataForFileUploadedExceptionMessage                             = "リクエストでアップロードされたファイル '{0}' のデータがありません。"
    sseOnlyConfiguredOnEventStreamAcceptHeaderExceptionMessage        = 'SSEはAcceptヘッダー値がtext/event-streamのリクエストでのみ構成できます。'
    noSessionAvailableToSaveExceptionMessage                          = '保存するためのセッションが利用できません。'
    pathParameterRequiresRequiredSwitchExceptionMessage               = "パラメータの場所が 'Path' の場合、スイッチパラメータ 'Required' は必須です。"
    noOpenApiUrlSuppliedExceptionMessage                              = '{0} 用の OpenAPI URL が提供されていません。'
    maximumConcurrentSchedulesInvalidExceptionMessage                 = '最大同時スケジュール数は 1 以上でなければなりませんが、受け取った値: {0}'
    snapinsSupportedOnWindowsPowershellOnlyExceptionMessage           = 'SnapinsはWindows PowerShellのみでサポートされています。'
    eventViewerLoggingSupportedOnWindowsOnlyExceptionMessage          = 'イベントビューアーロギングはWindowsでのみサポートされています。'
    parametersMutuallyExclusiveExceptionMessage                       = "パラメータ '{0}' と '{1}' は互いに排他的です。"
    pathItemsFeatureNotSupportedInOpenApi30ExceptionMessage           = 'PathItems機能はOpenAPI v3.0.xではサポートされていません。'
    openApiParameterRequiresNameExceptionMessage                      = 'OpenApi パラメータには名前が必要です。'
    maximumConcurrentTasksLessThanMinimumExceptionMessage             = '最大同時タスク数は最小値 {0} より少なくてはいけませんが、取得した値は: {1}'
    noSemaphoreFoundExceptionMessage                                  = "名前 '{0}' のセマフォが見つかりません"
    singleValueForIntervalExceptionMessage                            = 'インターバルを使用する場合、単一の{0}値しか指定できません。'
    jwtNotYetValidExceptionMessage                                    = 'JWTはまだ有効ではありません。'
    verbAlreadyDefinedForUrlExceptionMessage                          = '[動詞] {0}: {1}にすでに定義されています'
    noSecretNamedMountedExceptionMessage                              = "名前 '{0}' のシークレットはマウントされていません。"
    moduleOrVersionNotFoundExceptionMessage                           = '{0}でモジュールまたはバージョンが見つかりません: {1}@{2}'
    noScriptBlockSuppliedExceptionMessage                             = 'ScriptBlockが提供されていません。'
    noSecretVaultRegisteredExceptionMessage                           = "名前 '{0}' のシークレットボールトは登録されていません。"
    nameRequiredForEndpointIfRedirectToSuppliedExceptionMessage       = 'RedirectToパラメーターが提供されている場合、エンドポイントには名前が必要です。'
    openApiLicenseObjectRequiresNameExceptionMessage                  = "OpenAPI オブジェクト 'license' には 'name' プロパティが必要です。-LicenseName パラメータを使用してください。"
    sourcePathDoesNotExistForStaticRouteExceptionMessage              = '{0}: 静的ルートに対して提供されたソースパスが存在しません: {1}'
    noNameForWebSocketDisconnectExceptionMessage                      = '切断する WebSocket の名前が指定されていません。'
    certificateExpiredExceptionMessage                                = "証明書 '{0}' の有効期限が切れています: {1}"
    secretVaultUnlockExpiryDateInPastExceptionMessage                 = 'シークレットボールトのアンロック有効期限が過去に設定されています (UTC) :{0}'
    invalidWebExceptionTypeExceptionMessage                           = '例外が無効な型です。WebExceptionまたはHttpRequestExceptionのいずれかである必要がありますが、次の型を取得しました: {0}'
    invalidSecretValueTypeExceptionMessage                            = 'シークレットの値が無効な型です。期待される型: String、SecureString、HashTable、Byte[]、またはPSCredential。しかし、次を取得しました: {0}'
    explicitTlsModeOnlySupportedOnSmtpsTcpsEndpointsExceptionMessage  = '明示的なTLSモードはSMTPSおよびTCPSエンドポイントでのみサポートされています。'
    discriminatorMappingRequiresDiscriminatorPropertyExceptionMessage = "パラメーター'DiscriminatorMapping'は'DiscriminatorProperty'が存在する場合にのみ使用できます。"
    scriptErrorExceptionMessage                                       = "スクリプト{1} {2}(行{3})のエラー'{0}'(文字{4})が{6}オブジェクト'{7}'の{5}を実行中に発生しました クラス: {8} 基底クラス: {9}"
    cannotSupplyIntervalForQuarterExceptionMessage                    = '四半期ごとの間隔値を提供できません。'
    scheduleEndTimeMustBeInFutureExceptionMessage                     = '[スケジュール] {0}: EndTime 値は未来に設定する必要があります。'
    invalidJwtSignatureSuppliedExceptionMessage                       = '無効なJWT署名が提供されました。'
    noSetScriptBlockForVaultExceptionMessage                          = "ボールト'{0}'のシークレットを更新/作成するためのスクリプトブロックが提供されていません。"
    accessMethodNotExistForMergingExceptionMessage                    = 'マージするアクセス方法が存在しません: {0}'
    defaultAuthNotInListExceptionMessage                              = "デフォルト認証'{0}'は提供された認証リストにありません。"
    parameterHasNoNameExceptionMessage                                = "パラメーターに名前がありません。このコンポーネントに'Name'パラメーターを使用して名前を付けてください。"
    methodPathAlreadyDefinedForUrlExceptionMessage                    = '[{0}] {1}: {2}用に既に定義されています。'
    fileWatcherAlreadyDefinedExceptionMessage                         = "名前 '{0}' のファイルウォッチャーは既に定義されています。"
    noServiceHandlersDefinedExceptionMessage                          = 'サービスハンドラが定義されていません。'
    secretRequiredForCustomSessionStorageExceptionMessage             = 'カスタムセッションストレージを使用する場合、シークレットが必要です。'
    secretManagementModuleNotInstalledExceptionMessage                = 'Microsoft.PowerShell.SecretManagementモジュールがインストールされていません。'
    noPathSuppliedForRouteExceptionMessage                            = 'ルートのパスが提供されていません。'
    validationOfAnyOfSchemaNotSupportedExceptionMessage               = "'anyof'を含むスキーマの検証はサポートされていません。"
    iisAuthSupportIsForWindowsOnlyExceptionMessage                    = 'IIS認証のサポートはWindowsのみです。'
    oauth2InnerSchemeInvalidExceptionMessage                          = 'OAuth2 InnerSchemeはBasicまたはFormのいずれかでなければなりませんが、取得したのは: {0}'
    noRoutePathSuppliedForPageExceptionMessage                        = '{0} ページのルートパスが提供されていません。'
    cacheStorageNotFoundForExistsExceptionMessage                     = "キャッシュされたアイテム '{1}' が存在するかどうかを確認しようとしたときに、名前 '{0}' のキャッシュストレージが見つかりません。"
    handlerAlreadyDefinedExceptionMessage                             = '[{0}] {1}: ハンドラは既に定義されています。'
    sessionsNotConfiguredExceptionMessage                             = 'セッションが構成されていません。'
    propertiesTypeObjectAssociationExceptionMessage                   = 'Object 型のプロパティのみが {0} と関連付けられます。'
    sessionsRequiredForSessionPersistentAuthExceptionMessage          = 'セッション持続認証を使用するにはセッションが必要です。'
    invalidPathWildcardOrDirectoryExceptionMessage                    = '指定されたパスはワイルドカードまたはディレクトリにすることはできません: {0}'
    accessMethodAlreadyDefinedExceptionMessage                        = 'アクセス方法はすでに定義されています: {0}'
    parametersValueOrExternalValueMandatoryExceptionMessage           = "パラメータ 'Value' または 'ExternalValue' は必須です。"
    maximumConcurrentTasksInvalidExceptionMessage                     = '最大同時タスク数は >=1 でなければなりませんが、取得した値は: {0}'
    cannotCreatePropertyWithoutTypeExceptionMessage                   = '型が定義されていないため、プロパティを作成できません。'
    authMethodNotExistForMergingExceptionMessage                      = 'マージするための認証方法は存在しません:{0}'
    maxValueInvalidExceptionMessage                                   = "{1}の最大値'{0}'は無効です。{2}以下でなければなりません。"
    endpointAlreadyDefinedExceptionMessage                            = "名前 '{0}' のエンドポイントは既に定義されています。"
    eventAlreadyRegisteredExceptionMessage                            = '{0}イベントはすでに登録されています:{1}'
    parameterNotSuppliedInRequestExceptionMessage                     = "リクエストに '{0}' という名前のパラメータが提供されていないか、データがありません。"
    cacheStorageNotFoundForSetExceptionMessage                        = "キャッシュされたアイテム '{1}' を設定しようとしたときに、名前 '{0}' のキャッシュストレージが見つかりません。"
    methodPathAlreadyDefinedExceptionMessage                          = '[{0}] {1}: 既に定義されています。'
    errorLoggingAlreadyEnabledExceptionMessage                        = 'エラーロギングは既に有効になっています。'
    valueForUsingVariableNotFoundExceptionMessage                     = "'`$using:{0}'の値が見つかりませんでした。"
    rapidPdfDoesNotSupportOpenApi31ExceptionMessage                   = 'ドキュメントツール RapidPdf は OpenAPI 3.1 をサポートしていません'
    oauth2ClientSecretRequiredExceptionMessage                        = 'PKCEを使用しない場合、OAuth2にはクライアントシークレットが必要です。'
    invalidBase64JwtExceptionMessage                                  = 'JWTに無効なBase64エンコード値が見つかりました。'
    noSessionToCalculateDataHashExceptionMessage                      = 'データハッシュを計算するセッションがありません。'
    cacheStorageNotFoundForRemoveExceptionMessage                     = "キャッシュされたアイテム '{1}' を削除しようとしたときに、名前 '{0}' のキャッシュストレージが見つかりません。"
    csrfMiddlewareNotInitializedExceptionMessage                      = 'CSRFミドルウェアが初期化されていません。'
    infoTitleMandatoryMessage                                         = 'info.title は必須です。'
    typeCanOnlyBeAssociatedWithObjectExceptionMessage                 = 'タイプ{0}はオブジェクトにのみ関連付けることができます。'
    userFileDoesNotExistExceptionMessage                              = 'ユーザーファイルが存在しません:{0}'
    routeParameterNeedsValidScriptblockExceptionMessage               = 'ルートパラメーターには有効で空でないScriptBlockが必要です。'
    nextTriggerCalculationErrorExceptionMessage                       = '次のトリガー日時の計算中に問題が発生したようです: {0}'
    cannotLockValueTypeExceptionMessage                               = '[ValueType]をロックできません。'
    failedToCreateOpenSslCertExceptionMessage                         = 'OpenSSL証明書の作成に失敗しました: {0}'
    jwtExpiredExceptionMessage                                        = 'JWTの有効期限が切れています。'
    openingGuiMessage                                                 = 'GUIを開いています。'
    multiTypePropertiesRequireOpenApi31ExceptionMessage               = '複数タイプのプロパティはOpenApiバージョン3.1以上が必要です。'
    noNameForWebSocketRemoveExceptionMessage                          = '削除する WebSocket の名前が指定されていません。'
    maxSizeInvalidExceptionMessage                                    = 'MaxSizeは0以上でなければなりませんが、受け取った値は: {0}'
    iisShutdownMessage                                                = '(IIS シャットダウン)'
    cannotUnlockValueTypeExceptionMessage                             = '[ValueType]のロックを解除できません。'
    noJwtSignatureForAlgorithmExceptionMessage                        = '{0}のためのJWT署名が提供されていません。'
    maximumConcurrentWebSocketThreadsInvalidExceptionMessage          = '最大同時 WebSocket スレッド数は >=1 でなければなりませんが、取得した値は: {0}'
    acknowledgeMessageOnlySupportedOnSmtpTcpEndpointsExceptionMessage = '確認メッセージはSMTPおよびTCPエンドポイントでのみサポートされています。'
    failedToConnectToUrlExceptionMessage                              = 'URLへの接続に失敗しました: {0}'
    failedToAcquireMutexOwnershipExceptionMessage                     = 'ミューテックスの所有権を取得できませんでした。ミューテックス名: {0}'
    sessionsRequiredForOAuth2WithPKCEExceptionMessage                 = 'PKCEを使用するOAuth2にはセッションが必要です。'
    failedToConnectToWebSocketExceptionMessage                        = 'WebSocket への接続に失敗しました: {0}'
    unsupportedObjectExceptionMessage                                 = 'サポートされていないオブジェクトです。'
    failedToParseAddressExceptionMessage                              = "'{0}'を有効なIP/ホスト:ポートアドレスとして解析できませんでした。"
    mustBeRunningWithAdminPrivilegesExceptionMessage                  = 'ローカルホスト以外のアドレスでリッスンするには管理者権限で実行する必要があります。'
    specificationMessage                                              = '仕様'
    cacheStorageNotFoundForClearExceptionMessage                      = "キャッシュをクリアしようとしたときに、名前 '{0}' のキャッシュストレージが見つかりません。"
    restartingServerMessage                                           = 'サーバーを再起動しています...'
    cannotSupplyIntervalWhenEveryIsNoneExceptionMessage               = "パラメーター'Every'がNoneに設定されている場合、間隔を提供できません。"
    unsupportedJwtAlgorithmExceptionMessage                           = '現在サポートされていないJWTアルゴリズムです: {0}'
    websocketsNotConfiguredForSignalMessagesExceptionMessage          = 'WebSocketsはシグナルメッセージを送信するように構成されていません。'
    invalidLogicTypeInHashtableMiddlewareExceptionMessage             = '提供されたHashtableミドルウェアに無効なロジック型があります。ScriptBlockを期待しましたが、次を取得しました: {0}'
    maximumConcurrentSchedulesLessThanMinimumExceptionMessage         = '最大同時スケジュール数は最小 {0} 未満にすることはできませんが、受け取った値: {1}'
    failedToAcquireSemaphoreOwnershipExceptionMessage                 = 'セマフォの所有権を取得できませんでした。セマフォ名: {0}'
    propertiesParameterWithoutNameExceptionMessage                    = 'プロパティに名前がない場合、プロパティパラメータは使用できません。'
    customSessionStorageMethodNotImplementedExceptionMessage          = "カスタムセッションストレージは必要なメソッド'{0}()'を実装していません。"
    authenticationMethodDoesNotExistExceptionMessage                  = '認証方法が存在しません: {0}'
    webhooksFeatureNotSupportedInOpenApi30ExceptionMessage            = 'Webhooks機能はOpenAPI v3.0.xではサポートされていません。'
    invalidContentTypeForSchemaExceptionMessage                       = "スキーマの 'content-type' が無効です: {0}"
    noUnlockScriptBlockForVaultExceptionMessage                       = "ボールト'{0}'のロック解除に必要なスクリプトブロックが提供されていません。"
    definitionTagMessage                                              = '定義 {0}:'
    failedToOpenRunspacePoolExceptionMessage                          = 'RunspacePoolのオープンに失敗しました: {0}'
    failedToCloseRunspacePoolExceptionMessage                         = 'RunspacePoolのクローズに失敗しました: {0}'
    verbNoLogicPassedExceptionMessage                                 = '[動詞] {0}: ロジックが渡されていません'
    noMutexFoundExceptionMessage                                      = "名前 '{0}' のミューテックスが見つかりません"
    documentationMessage                                              = 'ドキュメント'
    timerAlreadyDefinedExceptionMessage                               = '[タイマー] {0}: タイマーはすでに定義されています。'
    invalidPortExceptionMessage                                       = 'ポートは負であってはなりません: {0}'
    viewsFolderNameAlreadyExistsExceptionMessage                      = 'ビューのフォルダ名は既に存在します: {0}'
    noNameForWebSocketResetExceptionMessage                           = 'リセットする WebSocket の名前が指定されていません。'
    mergeDefaultAuthNotInListExceptionMessage                         = "MergeDefault認証'{0}'は提供された認証リストにありません。"
    descriptionRequiredExceptionMessage                               = 'パス:{0} 応答:{1} に説明が必要です'
    pageNameShouldBeAlphaNumericExceptionMessage                      = 'ページ名は有効な英数字である必要があります: {0}'
    defaultValueNotBooleanOrEnumExceptionMessage                      = 'デフォルト値は boolean ではなく、enum に含まれていません。'
    openApiComponentSchemaDoesNotExistExceptionMessage                = 'OpenApi コンポーネントスキーマ {0} は存在しません。'
    timerParameterMustBeGreaterThanZeroExceptionMessage               = '[タイマー] {0}: {1} は 0 より大きくなければなりません。'
    taskTimedOutExceptionMessage                                      = 'タスクが{0}ミリ秒後にタイムアウトしました。'
    scheduleStartTimeAfterEndTimeExceptionMessage                     = "[スケジュール] {0}: 'StartTime' が 'EndTime' の後であることはできません"
    infoVersionMandatoryMessage                                       = 'info.version は必須です。'
    cannotUnlockNullObjectExceptionMessage                            = 'nullオブジェクトのロックを解除できません。'
    nonEmptyScriptBlockRequiredForCustomAuthExceptionMessage          = 'カスタム認証スキームには空でないScriptBlockが必要です。'
    nonEmptyScriptBlockRequiredForAuthMethodExceptionMessage          = '認証方法には空でない ScriptBlock が必要です。'
    validationOfOneOfSchemaNotSupportedExceptionMessage               = "'oneof'を含むスキーマの検証はサポートされていません。"
    routeParameterCannotBeNullExceptionMessage                        = "パラメータ 'Route' は null ではいけません。"
    cacheStorageAlreadyExistsExceptionMessage                         = "名前 '{0}' のキャッシュストレージは既に存在します。"
    loggingMethodRequiresValidScriptBlockExceptionMessage             = "'{0}' ログ記録方法のために提供された出力方法は、有効なScriptBlockが必要です。"
    scopedVariableAlreadyDefinedExceptionMessage                      = 'スコープ付き変数が既に定義されています: {0}'
    oauth2RequiresAuthorizeUrlExceptionMessage                        = 'OAuth2には認可URLの提供が必要です。'
    pathNotExistExceptionMessage                                      = 'パスが存在しません: {0}'
    noDomainServerNameForWindowsAdAuthExceptionMessage                = 'Windows AD認証用のドメインサーバー名が提供されていません。'
    suppliedDateAfterScheduleEndTimeExceptionMessage                  = '提供された日付はスケジュールの終了時間 {0} の後です'
    wildcardMethodsIncompatibleWithAutoMethodsExceptionMessage        = 'メソッドのワイルドカード * は AutoMethods スイッチと互換性がありません。'
    cannotSupplyIntervalForYearExceptionMessage                       = '毎年の間隔値を提供できません。'
    missingComponentsMessage                                          = '欠落しているコンポーネント'
    invalidStrictTransportSecurityDurationExceptionMessage            = '無効な Strict-Transport-Security 期間が指定されました: {0}。0 より大きい必要があります。'
    noSecretForHmac512ExceptionMessage                                = 'HMAC512ハッシュに対する秘密が提供されていません。'
    daysInMonthExceededExceptionMessage                               = '{0}は{1}日しかありませんが、{2}が指定されました。'
    nonEmptyScriptBlockRequiredForCustomLoggingExceptionMessage       = 'カスタムロギング出力メソッドには空でないScriptBlockが必要です。'
    encodingAttributeOnlyAppliesToMultipartExceptionMessage           = 'エンコーディング属性は、multipart および application/x-www-form-urlencoded リクエストボディにのみ適用されます。'
    suppliedDateBeforeScheduleStartTimeExceptionMessage               = '提供された日付はスケジュールの開始時間 {0} より前です'
    unlockSecretRequiredExceptionMessage                              = "Microsoft.PowerShell.SecretStoreを使用する場合、'UnlockSecret'プロパティが必要です。"
    noLogicPassedForMethodRouteExceptionMessage                       = '[{0}] {1}: ロジックが渡されませんでした。'
    bodyParserAlreadyDefinedForContentTypeExceptionMessage            = '{0} コンテンツタイプ用のボディパーサーは既に定義されています。'
    invalidJwtSuppliedExceptionMessage                                = '無効なJWTが提供されました。'
    sessionsRequiredForFlashMessagesExceptionMessage                  = 'フラッシュメッセージを使用するにはセッションが必要です。'
    semaphoreAlreadyExistsExceptionMessage                            = '次の名前のセマフォはすでに存在します: {0}'
    invalidJwtHeaderAlgorithmSuppliedExceptionMessage                 = '無効なJWTヘッダーアルゴリズムが提供されました。'
    oauth2ProviderDoesNotSupportPasswordGrantTypeExceptionMessage     = "OAuth2プロバイダーはInnerSchemeを使用するために必要な'password' grant_typeをサポートしていません。"
    invalidAliasFoundExceptionMessage                                 = '無効な{0}エイリアスが見つかりました: {1}'
    scheduleDoesNotExistExceptionMessage                              = "スケジュール '{0}' は存在しません。"
    accessMethodNotExistExceptionMessage                              = 'アクセス方法が存在しません: {0}'
    oauth2ProviderDoesNotSupportCodeResponseTypeExceptionMessage      = "OAuth2プロバイダーは'code' response_typeをサポートしていません。"
    untestedPowerShellVersionWarningMessage                           = '[警告] Pode {0} はリリース時に利用可能でなかったため、PowerShell {1} でテストされていません。'
    secretVaultAlreadyRegisteredAutoImportExceptionMessage            = "シークレットボールト'{0}'は既に登録されています(シークレットボールトの自動インポート中)。"
    schemeRequiresValidScriptBlockExceptionMessage                    = "'{0}'認証バリデーターのために提供されたスキームには有効なScriptBlockが必要です。"
    serverLoopingMessage                                              = 'サーバーループ間隔 {0}秒'
    certificateThumbprintsNameSupportedOnWindowsExceptionMessage      = 'Certificate Thumbprints/NameはWindowsでのみサポートされています。'
    sseConnectionNameRequiredExceptionMessage                         = "-Nameまたは`$WebEvent.Sse.NameからSSE接続名が必要です。"
    invalidMiddlewareTypeExceptionMessage                             = '提供されたMiddlewaresの1つが無効な型です。ScriptBlockまたはHashtableのいずれかを期待しましたが、次を取得しました: {0}'
    noSecretForJwtSignatureExceptionMessage                           = 'JWT署名に対する秘密が提供されていません。'
    modulePathDoesNotExistExceptionMessage                            = 'モジュールパスが存在しません: {0}'
    taskAlreadyDefinedExceptionMessage                                = '[タスク] {0}: タスクは既に定義されています。'
    verbAlreadyDefinedExceptionMessage                                = '[動詞] {0}: すでに定義されています'
    clientCertificatesOnlySupportedOnHttpsEndpointsExceptionMessage   = 'クライアント証明書はHTTPSエンドポイントでのみサポートされています。'
    endpointNameNotExistExceptionMessage                              = "名前'{0}'のエンドポイントが存在しません。"
    middlewareNoLogicSuppliedExceptionMessage                         = '[ミドルウェア]: ScriptBlockにロジックが提供されていません。'
    scriptBlockRequiredForMergingUsersExceptionMessage                = 'ValidがAllの場合、複数の認証済みユーザーを1つのオブジェクトにマージするためのScriptBlockが必要です。'
    secretVaultAlreadyRegisteredExceptionMessage                      = "名前 '{0}' のシークレットボールトは既に登録されています{1}。"
    deprecatedTitleVersionDescriptionWarningMessage                   = "警告: 'Enable-PodeOpenApi' のタイトル、バージョン、および説明は非推奨です。代わりに 'Add-PodeOAInfo' を使用してください。"
    undefinedOpenApiReferencesMessage                                 = '未定義のOpenAPI参照:'
    doneMessage                                                       = '完了'
    swaggerEditorDoesNotSupportOpenApi31ExceptionMessage              = 'このバージョンの Swagger-Editor は OpenAPI 3.1 をサポートしていません'
    durationMustBeZeroOrGreaterExceptionMessage                       = '期間は 0 以上でなければなりませんが、取得した値は: {0}s'
    viewsPathDoesNotExistExceptionMessage                             = 'ビューのパスが存在しません: {0}'
    discriminatorIncompatibleWithAllOfExceptionMessage                = "パラメーター'Discriminator'は'allOf'と互換性がありません。"
    noNameForWebSocketSendMessageExceptionMessage                     = 'メッセージを送信する WebSocket の名前が指定されていません。'
    hashtableMiddlewareNoLogicExceptionMessage                        = '提供されたHashtableミドルウェアにロジックが定義されていません。'
    openApiInfoMessage                                                = 'OpenAPI情報:'
    invalidSchemeForAuthValidatorExceptionMessage                     = "'{1}'認証バリデーターのために提供された'{0}'スキームには有効なScriptBlockが必要です。"
    sseFailedToBroadcastExceptionMessage                              = '{0}のSSEブロードキャストレベルが定義されているため、SSEのブロードキャストに失敗しました: {1}'
    adModuleWindowsOnlyExceptionMessage                               = 'Active DirectoryモジュールはWindowsでのみ利用可能です。'
    requestLoggingAlreadyEnabledExceptionMessage                      = 'リクエストロギングは既に有効になっています。'
    invalidAccessControlMaxAgeDurationExceptionMessage                = '無効な Access-Control-Max-Age 期間が提供されました:{0}。0 より大きくする必要があります。'
    openApiDefinitionAlreadyExistsExceptionMessage                    = '名前が {0} の OpenAPI 定義は既に存在します。'
    renamePodeOADefinitionTagExceptionMessage                         = "Rename-PodeOADefinitionTag は Select-PodeOADefinition 'ScriptBlock' 内で使用できません。"
    taskProcessDoesNotExistExceptionMessage                           = 'タスクプロセスが存在しません: {0}'
    scheduleProcessDoesNotExistExceptionMessage                       = 'スケジュールプロセスが存在しません: {0}'
    definitionTagChangeNotAllowedExceptionMessage                     = 'Routeの定義タグは変更できません。'
    getRequestBodyNotAllowedExceptionMessage                          = '{0}操作にはリクエストボディを含めることはできません。'
    fnDoesNotAcceptArrayAsPipelineInputExceptionMessage               = "関数 '{0}' は配列をパイプライン入力として受け付けません。"
    unsupportedStreamCompressionEncodingExceptionMessage              = 'サポートされていないストリーム圧縮エンコーディングが提供されました: {0}'
}
src\Locales\ko\Pode.psd1
@{
    schemaValidationRequiresPowerShell610ExceptionMessage             = '스키마 유효성 검사는 PowerShell 버전 6.1.0 이상이 필요합니다.'
    customAccessPathOrScriptBlockRequiredExceptionMessage             = '사용자 지정 액세스 값을 소싱하기 위해 경로 또는 ScriptBlock이 필요합니다.'
    operationIdMustBeUniqueForArrayExceptionMessage                   = 'OperationID: {0}은(는) 고유해야 하며 배열에 적용될 수 없습니다.'
    endpointNotDefinedForRedirectingExceptionMessage                  = "리디렉션을 위해 이름이 '{0}'인 엔드포인트가 정의되지 않았습니다."
    filesHaveChangedMessage                                           = '다음 파일이 변경되었습니다:'
    iisAspnetcoreTokenMissingExceptionMessage                         = 'IIS ASPNETCORE_TOKEN이 누락되었습니다.'
    minValueGreaterThanMaxExceptionMessage                            = '{0}의 최소 값은 최대 값보다 클 수 없습니다.'
    noLogicPassedForRouteExceptionMessage                             = '경로에 대한 논리가 전달되지 않았습니다: {0}'
    scriptPathDoesNotExistExceptionMessage                            = '스크립트 경로가 존재하지 않습니다: {0}'
    mutexAlreadyExistsExceptionMessage                                = "이름이 '{0}'인 뮤텍스가 이미 존재합니다."
    listeningOnEndpointsMessage                                       = '다음 {0} 엔드포인트에서 수신 중 [{1} 스레드]:'
    unsupportedFunctionInServerlessContextExceptionMessage            = '{0} 함수는 서버리스 컨텍스트에서 지원되지 않습니다.'
    expectedNoJwtSignatureSuppliedExceptionMessage                    = 'JWT 서명이 제공되지 않을 것으로 예상되었습니다.'
    secretAlreadyMountedExceptionMessage                              = "이름이 '{0}'인 시크릿이 이미 마운트되었습니다."
    failedToAcquireLockExceptionMessage                               = '개체에 대한 잠금을 획득하지 못했습니다.'
    noPathSuppliedForStaticRouteExceptionMessage                      = '[{0}]: 정적 경로에 대한 경로가 제공되지 않았습니다.'
    invalidHostnameSuppliedExceptionMessage                           = '제공된 호스트 이름이 잘못되었습니다: {0}'
    authMethodAlreadyDefinedExceptionMessage                          = '인증 방법이 이미 정의되었습니다: {0}'
    csrfCookieRequiresSecretExceptionMessage                          = "CSRF에 대해 쿠키를 사용할 때, 비밀이 필요합니다. 비밀을 제공하거나 전역 비밀 쿠키를 설정하십시오 - (Set-PodeCookieSecret '<value>' -Global)"
    nonEmptyScriptBlockRequiredForPageRouteExceptionMessage           = '페이지 경로를 생성하려면 비어 있지 않은 ScriptBlock이 필요합니다.'
    noPropertiesMutuallyExclusiveExceptionMessage                     = "매개변수 'NoProperties'는 'Properties', 'MinProperties' 및 'MaxProperties'와 상호 배타적입니다."
    incompatiblePodeDllExceptionMessage                               = '기존의 호환되지 않는 Pode.DLL 버전 {0}이 로드되었습니다. 버전 {1}이 필요합니다. 새로운 Powershell/pwsh 세션을 열고 다시 시도하세요.'
    accessMethodDoesNotExistExceptionMessage                          = '접근 방법이 존재하지 않습니다: {0}.'
    scheduleAlreadyDefinedExceptionMessage                            = '[스케줄] {0}: 스케줄이 이미 정의되어 있습니다.'
    secondsValueCannotBeZeroOrLessExceptionMessage                    = '{0}에 대한 초 값은 0 이하일 수 없습니다.'
    pathToLoadNotFoundExceptionMessage                                = '로드할 경로 {0}을(를) 찾을 수 없습니다: {1}'
    failedToImportModuleExceptionMessage                              = '모듈을 가져오지 못했습니다: {0}'
    endpointNotExistExceptionMessage                                  = "프로토콜 '{0}' 및 주소 '{1}' 또는 로컬 주소 '{2}'가 있는 엔드포인트가 존재하지 않습니다."
    terminatingMessage                                                = '종료 중...'
    noCommandsSuppliedToConvertToRoutesExceptionMessage               = '경로로 변환할 명령이 제공되지 않았습니다.'
    invalidTaskTypeExceptionMessage                                   = '작업 유형이 유효하지 않습니다. 예상된 유형: [System.Threading.Tasks.Task] 또는 [hashtable]'
    alreadyConnectedToWebSocketExceptionMessage                       = "이름이 '{0}'인 WebSocket에 이미 연결되어 있습니다."
    crlfMessageEndCheckOnlySupportedOnTcpEndpointsExceptionMessage    = 'CRLF 메시지 끝 검사는 TCP 엔드포인트에서만 지원됩니다.'
    testPodeOAComponentSchemaNeedToBeEnabledExceptionMessage          = "'Test-PodeOAComponentSchema'는 'Enable-PodeOpenApi -EnableSchemaValidation'을 사용하여 활성화해야 합니다."
    adModuleNotInstalledExceptionMessage                              = 'Active Directory 모듈이 설치되지 않았습니다.'
    cronExpressionInvalidExceptionMessage                             = 'Cron 표현식은 5개의 부분으로만 구성되어야 합니다: {0}'
    noSessionToSetOnResponseExceptionMessage                          = '응답에 설정할 세션이 없습니다.'
    valueOutOfRangeExceptionMessage                                   = "{1}의 값 '{0}'이(가) 유효하지 않습니다. {2}와 {3} 사이여야 합니다."
    loggingMethodAlreadyDefinedExceptionMessage                       = '로깅 방법이 이미 정의되었습니다: {0}'
    noSecretForHmac256ExceptionMessage                                = 'HMAC256 해시를 위한 비밀이 제공되지 않았습니다.'
    eolPowerShellWarningMessage                                       = '[경고] Pode {0}은 EOL 상태인 PowerShell {1}에서 테스트되지 않았습니다.'
    runspacePoolFailedToLoadExceptionMessage                          = '{0} RunspacePool 로드 실패.'
    noEventRegisteredExceptionMessage                                 = '등록된 {0} 이벤트가 없습니다: {1}'
    scheduleCannotHaveNegativeLimitExceptionMessage                   = '[스케줄] {0}: 음수 한도를 가질 수 없습니다.'
    openApiRequestStyleInvalidForParameterExceptionMessage            = 'OpenApi 요청 스타일은 {1} 매개변수에 대해 {0}일 수 없습니다.'
    openApiDocumentNotCompliantExceptionMessage                       = 'OpenAPI 문서는 준수하지 않습니다.'
    taskDoesNotExistExceptionMessage                                  = "작업 '{0}'이(가) 존재하지 않습니다."
    scopedVariableNotFoundExceptionMessage                            = '범위 변수 {0}을(를) 찾을 수 없습니다.'
    sessionsRequiredForCsrfExceptionMessage                           = '쿠키를 사용하지 않으려면 CSRF 사용을 위해 세션이 필요합니다.'
    nonEmptyScriptBlockRequiredForLoggingMethodExceptionMessage       = '로깅 방법에는 비어 있지 않은 ScriptBlock이 필요합니다.'
    credentialsPassedWildcardForHeadersLiteralExceptionMessage        = '자격 증명이 전달되면, 헤더에 대한 * 와일드카드는 와일드카드가 아닌 리터럴 문자열로 취급됩니다.'
    podeNotInitializedExceptionMessage                                = 'Pode가 초기화되지 않았습니다.'
    multipleEndpointsForGuiMessage                                    = '여러 엔드포인트가 정의되었으며, GUI에는 첫 번째만 사용됩니다.'
    operationIdMustBeUniqueExceptionMessage                           = 'OperationID: {0}은(는) 고유해야 합니다.'
    invalidJsonJwtExceptionMessage                                    = 'JWT에서 잘못된 JSON 값이 발견되었습니다.'
    noAlgorithmInJwtHeaderExceptionMessage                            = 'JWT 헤더에 제공된 알고리즘이 없습니다.'
    openApiVersionPropertyMandatoryExceptionMessage                   = 'OpenApi 버전 속성은 필수입니다.'
    limitValueCannotBeZeroOrLessExceptionMessage                      = '{0}에 대한 제한 값은 0 이하일 수 없습니다.'
    timerDoesNotExistExceptionMessage                                 = "타이머 '{0}'이(가) 존재하지 않습니다."
    openApiGenerationDocumentErrorMessage                             = 'OpenAPI 생성 문서 오류:'
    routeAlreadyContainsCustomAccessExceptionMessage                  = "경로 '[{0}] {1}'에 '{2}' 이름의 사용자 지정 액세스가 이미 포함되어 있습니다."
    maximumConcurrentWebSocketThreadsLessThanMinimumExceptionMessage  = '최대 동시 WebSocket 스레드는 최소값 {0}보다 작을 수 없지만 받은 값: {1}'
    middlewareAlreadyDefinedExceptionMessage                          = '[Middleware] {0}: 미들웨어가 이미 정의되었습니다.'
    invalidAtomCharacterExceptionMessage                              = '잘못된 원자 문자: {0}'
    invalidCronAtomFormatExceptionMessage                             = '잘못된 크론 원자 형식이 발견되었습니다: {0}'
    cacheStorageNotFoundForRetrieveExceptionMessage                   = "캐시된 항목 '{1}'을(를) 검색하려고 할 때 이름이 '{0}'인 캐시 스토리지를 찾을 수 없습니다."
    headerMustHaveNameInEncodingContextExceptionMessage               = '인코딩 컨텍스트에서 사용될 때 헤더는 이름이 있어야 합니다.'
    moduleDoesNotContainFunctionExceptionMessage                      = '모듈 {0}에 경로로 변환할 함수 {1}이(가) 포함되어 있지 않습니다.'
    pathToIconForGuiDoesNotExistExceptionMessage                      = 'GUI용 아이콘의 경로가 존재하지 않습니다: {0}'
    noTitleSuppliedForPageExceptionMessage                            = '{0} 페이지에 대한 제목이 제공되지 않았습니다.'
    certificateSuppliedForNonHttpsWssEndpointExceptionMessage         = 'HTTPS/WSS가 아닌 엔드포인트에 제공된 인증서입니다.'
    cannotLockNullObjectExceptionMessage                              = 'null 개체를 잠글 수 없습니다.'
    showPodeGuiOnlyAvailableOnWindowsExceptionMessage                 = 'Show-PodeGui는 현재 Windows PowerShell 및 Windows의 PowerShell 7+에서만 사용할 수 있습니다.'
    unlockSecretButNoScriptBlockExceptionMessage                      = '사용자 정의 비밀 금고 유형에 대해 제공된 Unlock 비밀이지만, Unlock ScriptBlock이 제공되지 않았습니다.'
    invalidIpAddressExceptionMessage                                  = '제공된 IP 주소가 유효하지 않습니다: {0}'
    maxDaysInvalidExceptionMessage                                    = 'MaxDays는 0 이상이어야 하지만, 받은 값: {0}'
    noRemoveScriptBlockForVaultExceptionMessage                       = "금고 '{0}'에서 비밀을 제거하기 위한 Remove ScriptBlock이 제공되지 않았습니다."
    noSecretExpectedForNoSignatureExceptionMessage                    = '서명이 없는 경우 비밀이 제공되지 않아야 합니다.'
    noCertificateFoundExceptionMessage                                = "'{2}'에 대한 {0}{1}에서 인증서를 찾을 수 없습니다."
    minValueInvalidExceptionMessage                                   = "{1}의 최소 값 '{0}'이(가) 유효하지 않습니다. {2} 이상이어야 합니다."
    accessRequiresAuthenticationOnRoutesExceptionMessage              = '경로에 대한 접근은 인증이 필요합니다.'
    noSecretForHmac384ExceptionMessage                                = 'HMAC384 해시를 위한 비밀이 제공되지 않았습니다.'
    windowsLocalAuthSupportIsForWindowsOnlyExceptionMessage           = 'Windows 로컬 인증 지원은 Windows 전용입니다.'
    definitionTagNotDefinedExceptionMessage                           = '정의 태그 {0}이(가) 정의되지 않았습니다.'
    noComponentInDefinitionExceptionMessage                           = '{2} 정의에서 {0} 유형의 {1} 이름의 구성 요소가 없습니다.'
    noSmtpHandlersDefinedExceptionMessage                             = '정의된 SMTP 핸들러가 없습니다.'
    sessionMiddlewareAlreadyInitializedExceptionMessage               = '세션 미들웨어가 이미 초기화되었습니다.'
    reusableComponentPathItemsNotAvailableInOpenApi30ExceptionMessage = "OpenAPI v3.0에서는 재사용 가능한 구성 요소 기능 'pathItems'를 사용할 수 없습니다."
    wildcardHeadersIncompatibleWithAutoHeadersExceptionMessage        = '헤더에 대한 * 와일드카드는 AutoHeaders 스위치와 호환되지 않습니다.'
    noDataForFileUploadedExceptionMessage                             = "요청에서 업로드된 파일 '{0}'에 대한 데이터가 없습니다."
    sseOnlyConfiguredOnEventStreamAcceptHeaderExceptionMessage        = 'SSE는 Accept 헤더 값이 text/event-stream인 요청에서만 구성할 수 있습니다.'
    noSessionAvailableToSaveExceptionMessage                          = '저장할 수 있는 세션이 없습니다.'
    pathParameterRequiresRequiredSwitchExceptionMessage               = "매개변수 위치가 'Path'인 경우 'Required' 스위치 매개변수가 필수입니다."
    noOpenApiUrlSuppliedExceptionMessage                              = '{0}에 대한 OpenAPI URL이 제공되지 않았습니다.'
    maximumConcurrentSchedulesInvalidExceptionMessage                 = '최대 동시 스케줄 수는 1 이상이어야 하지만 받은 값: {0}'
    snapinsSupportedOnWindowsPowershellOnlyExceptionMessage           = 'Snapins는 Windows PowerShell에서만 지원됩니다.'
    eventViewerLoggingSupportedOnWindowsOnlyExceptionMessage          = '이벤트 뷰어 로깅은 Windows에서만 지원됩니다.'
    parametersMutuallyExclusiveExceptionMessage                       = "매개변수 '{0}'와(과) '{1}'는 상호 배타적입니다."
    pathItemsFeatureNotSupportedInOpenApi30ExceptionMessage           = 'PathItems 기능은 OpenAPI v3.0.x에서 지원되지 않습니다.'
    openApiParameterRequiresNameExceptionMessage                      = 'OpenApi 매개변수에는 이름이 필요합니다.'
    maximumConcurrentTasksLessThanMinimumExceptionMessage             = '최대 동시 작업 수는 최소값 {0}보다 작을 수 없지만 받은 값: {1}'
    noSemaphoreFoundExceptionMessage                                  = "이름이 '{0}'인 세마포어를 찾을 수 없습니다."
    singleValueForIntervalExceptionMessage                            = '간격을 사용할 때는 단일 {0} 값을 제공할 수 있습니다.'
    jwtNotYetValidExceptionMessage                                    = 'JWT가 아직 유효하지 않습니다.'
    verbAlreadyDefinedForUrlExceptionMessage                          = '[동사] {0}: {1}에 대해 이미 정의되었습니다.'
    noSecretNamedMountedExceptionMessage                              = "이름이 '{0}'인 시크릿이 마운트되지 않았습니다."
    moduleOrVersionNotFoundExceptionMessage                           = '{0}에서 모듈 또는 버전을 찾을 수 없습니다: {1}@{2}'
    noScriptBlockSuppliedExceptionMessage                             = 'ScriptBlock이 제공되지 않았습니다.'
    noSecretVaultRegisteredExceptionMessage                           = "이름이 '{0}'인 비밀 금고가 등록되지 않았습니다."
    nameRequiredForEndpointIfRedirectToSuppliedExceptionMessage       = 'RedirectTo 매개변수가 제공된 경우 엔드포인트에 이름이 필요합니다.'
    openApiLicenseObjectRequiresNameExceptionMessage                  = "OpenAPI 객체 'license'는 'name' 속성이 필요합니다. -LicenseName 매개변수를 사용하십시오."
    sourcePathDoesNotExistForStaticRouteExceptionMessage              = '{0}: 정적 경로에 대한 제공된 소스 경로가 존재하지 않습니다: {1}'
    noNameForWebSocketDisconnectExceptionMessage                      = '연결을 끊을 WebSocket의 이름이 제공되지 않았습니다.'
    certificateExpiredExceptionMessage                                = "인증서 '{0}'이(가) 만료되었습니다: {1}"
    secretVaultUnlockExpiryDateInPastExceptionMessage                 = '시크릿 금고의 잠금 해제 만료 날짜가 과거입니다 (UTC): {0}'
    invalidWebExceptionTypeExceptionMessage                           = '예외가 잘못된 유형입니다. WebException 또는 HttpRequestException이어야 하지만, 얻은 것은: {0}'
    invalidSecretValueTypeExceptionMessage                            = '비밀 값이 잘못된 유형입니다. 예상되는 유형: String, SecureString, HashTable, Byte[] 또는 PSCredential. 그러나 얻은 것은: {0}'
    explicitTlsModeOnlySupportedOnSmtpsTcpsEndpointsExceptionMessage  = '명시적 TLS 모드는 SMTPS 및 TCPS 엔드포인트에서만 지원됩니다.'
    discriminatorMappingRequiresDiscriminatorPropertyExceptionMessage = "매개변수 'DiscriminatorMapping'은 'DiscriminatorProperty'가 있을 때만 사용할 수 있습니다."
    scriptErrorExceptionMessage                                       = "스크립트 {1} {2} (라인 {3}) 문자 {4}에서 {5}을(를) 실행하는 중에 스크립트 {0} 오류가 발생했습니다. 개체 '{7}' 클래스: {8} 기본 클래스: {9}"
    cannotSupplyIntervalForQuarterExceptionMessage                    = '분기별 간격 값을 제공할 수 없습니다.'
    scheduleEndTimeMustBeInFutureExceptionMessage                     = '[스케줄] {0}: 종료 시간 값은 미래에 있어야 합니다.'
    invalidJwtSignatureSuppliedExceptionMessage                       = '제공된 JWT 서명이 유효하지 않습니다.'
    noSetScriptBlockForVaultExceptionMessage                          = "금고 '{0}'에서 비밀을 업데이트/생성하기 위한 Set ScriptBlock이 제공되지 않았습니다."
    accessMethodNotExistForMergingExceptionMessage                    = '병합을 위한 액세스 방법이 존재하지 않습니다: {0}'
    defaultAuthNotInListExceptionMessage                              = "기본 인증 '{0}'이(가) 제공된 인증 목록에 없습니다."
    parameterHasNoNameExceptionMessage                                = "매개변수에 이름이 없습니다. 'Name' 매개변수를 사용하여 이 구성 요소에 이름을 지정하십시오."
    methodPathAlreadyDefinedForUrlExceptionMessage                    = '[{0}] {1}: {2}에 대해 이미 정의되었습니다.'
    fileWatcherAlreadyDefinedExceptionMessage                         = "'{0}'라는 이름의 파일 감시자가 이미 정의되었습니다."
    noServiceHandlersDefinedExceptionMessage                          = '정의된 서비스 핸들러가 없습니다.'
    secretRequiredForCustomSessionStorageExceptionMessage             = '사용자 정의 세션 저장소를 사용할 때는 비밀이 필요합니다.'
    secretManagementModuleNotInstalledExceptionMessage                = 'Microsoft.PowerShell.SecretManagement 모듈이 설치되지 않았습니다.'
    noPathSuppliedForRouteExceptionMessage                            = '경로에 대해 제공된 경로가 없습니다.'
    validationOfAnyOfSchemaNotSupportedExceptionMessage               = "'anyof'을 포함하는 스키마의 유효성 검사는 지원되지 않습니다."
    iisAuthSupportIsForWindowsOnlyExceptionMessage                    = 'IIS 인증 지원은 Windows 전용입니다.'
    oauth2InnerSchemeInvalidExceptionMessage                          = 'OAuth2 InnerScheme은 Basic 또는 Form 인증 중 하나여야 합니다, 그러나 받은 값: {0}'
    noRoutePathSuppliedForPageExceptionMessage                        = '{0} 페이지에 대한 경로가 제공되지 않았습니다.'
    cacheStorageNotFoundForExistsExceptionMessage                     = "캐시된 항목 '{1}'이(가) 존재하는지 확인하려고 할 때 이름이 '{0}'인 캐시 스토리지를 찾을 수 없습니다."
    handlerAlreadyDefinedExceptionMessage                             = '[{0}] {1}: 핸들러가 이미 정의되었습니다.'
    sessionsNotConfiguredExceptionMessage                             = '세션이 구성되지 않았습니다.'
    propertiesTypeObjectAssociationExceptionMessage                   = 'Object 유형의 속성만 {0}와(과) 연결될 수 있습니다.'
    sessionsRequiredForSessionPersistentAuthExceptionMessage          = '세션 지속 인증을 사용하려면 세션이 필요합니다.'
    invalidPathWildcardOrDirectoryExceptionMessage                    = '제공된 경로는 와일드카드 또는 디렉터리가 될 수 없습니다: {0}'
    accessMethodAlreadyDefinedExceptionMessage                        = '액세스 방법이 이미 정의되었습니다: {0}'
    parametersValueOrExternalValueMandatoryExceptionMessage           = "매개변수 'Value' 또는 'ExternalValue'는 필수입니다."
    maximumConcurrentTasksInvalidExceptionMessage                     = '최대 동시 작업 수는 >=1이어야 하지만 받은 값: {0}'
    cannotCreatePropertyWithoutTypeExceptionMessage                   = '유형이 정의되지 않았기 때문에 속성을 생성할 수 없습니다.'
    authMethodNotExistForMergingExceptionMessage                      = '병합을 위한 인증 방법이 존재하지 않습니다: {0}'
    maxValueInvalidExceptionMessage                                   = "{1}의 최대 값 '{0}'이(가) 유효하지 않습니다. {2} 이하여야 합니다."
    endpointAlreadyDefinedExceptionMessage                            = "이름이 '{0}'인 엔드포인트가 이미 정의되어 있습니다."
    eventAlreadyRegisteredExceptionMessage                            = '{0} 이벤트가 이미 등록되었습니다: {1}'
    parameterNotSuppliedInRequestExceptionMessage                     = "요청에 '{0}'라는 이름의 매개변수가 제공되지 않았거나 데이터가 없습니다."
    cacheStorageNotFoundForSetExceptionMessage                        = "캐시된 항목 '{1}'을(를) 설정하려고 할 때 이름이 '{0}'인 캐시 스토리지를 찾을 수 없습니다."
    methodPathAlreadyDefinedExceptionMessage                          = '[{0}] {1}: 이미 정의되었습니다.'
    errorLoggingAlreadyEnabledExceptionMessage                        = '오류 로깅이 이미 활성화되었습니다.'
    valueForUsingVariableNotFoundExceptionMessage                     = "'`$using:{0}'에 대한 값을 찾을 수 없습니다."
    rapidPdfDoesNotSupportOpenApi31ExceptionMessage                   = '문서 도구 RapidPdf는 OpenAPI 3.1을 지원하지 않습니다.'
    oauth2ClientSecretRequiredExceptionMessage                        = 'PKCE를 사용하지 않을 때 OAuth2에는 클라이언트 비밀이 필요합니다.'
    invalidBase64JwtExceptionMessage                                  = 'JWT에서 잘못된 Base64 인코딩 값이 발견되었습니다.'
    noSessionToCalculateDataHashExceptionMessage                      = '데이터 해시를 계산할 세션이 없습니다.'
    cacheStorageNotFoundForRemoveExceptionMessage                     = "캐시된 항목 '{1}'을(를) 제거하려고 할 때 이름이 '{0}'인 캐시 스토리지를 찾을 수 없습니다."
    csrfMiddlewareNotInitializedExceptionMessage                      = 'CSRF 미들웨어가 초기화되지 않았습니다.'
    infoTitleMandatoryMessage                                         = 'info.title은 필수 항목입니다.'
    typeCanOnlyBeAssociatedWithObjectExceptionMessage                 = '유형 {0}는 객체와만 연관될 수 있습니다.'
    userFileDoesNotExistExceptionMessage                              = '사용자 파일이 존재하지 않습니다: {0}'
    routeParameterNeedsValidScriptblockExceptionMessage               = '경로 매개변수에는 유효하고 비어 있지 않은 ScriptBlock이 필요합니다.'
    nextTriggerCalculationErrorExceptionMessage                       = '다음 트리거 날짜 및 시간을 계산하는 중에 문제가 발생한 것 같습니다: {0}'
    cannotLockValueTypeExceptionMessage                               = '[ValueType]를 잠글 수 없습니다.'
    failedToCreateOpenSslCertExceptionMessage                         = 'OpenSSL 인증서 생성 실패: {0}'
    jwtExpiredExceptionMessage                                        = 'JWT가 만료되었습니다.'
    openingGuiMessage                                                 = 'GUI 열기.'
    multiTypePropertiesRequireOpenApi31ExceptionMessage               = '다중 유형 속성은 OpenApi 버전 3.1 이상이 필요합니다.'
    noNameForWebSocketRemoveExceptionMessage                          = '제거할 WebSocket의 이름이 제공되지 않았습니다.'
    maxSizeInvalidExceptionMessage                                    = 'MaxSize는 0 이상이어야 하지만, 받은 값: {0}'
    iisShutdownMessage                                                = '(IIS 종료)'
    cannotUnlockValueTypeExceptionMessage                             = '[ValueType]를 잠금 해제할 수 없습니다.'
    noJwtSignatureForAlgorithmExceptionMessage                        = '{0}에 대한 JWT 서명이 제공되지 않았습니다.'
    maximumConcurrentWebSocketThreadsInvalidExceptionMessage          = '최대 동시 WebSocket 스레드는 >=1이어야 하지만 받은 값: {0}'
    acknowledgeMessageOnlySupportedOnSmtpTcpEndpointsExceptionMessage = '확인 메시지는 SMTP 및 TCP 엔드포인트에서만 지원됩니다.'
    failedToConnectToUrlExceptionMessage                              = 'URL에 연결하지 못했습니다: {0}'
    failedToAcquireMutexOwnershipExceptionMessage                     = '뮤텍스 소유권을 획득하지 못했습니다. 뮤텍스 이름: {0}'
    sessionsRequiredForOAuth2WithPKCEExceptionMessage                 = 'PKCE를 사용하는 OAuth2에는 세션이 필요합니다.'
    failedToConnectToWebSocketExceptionMessage                        = 'WebSocket에 연결하지 못했습니다: {0}'
    unsupportedObjectExceptionMessage                                 = '지원되지 않는 개체'
    failedToParseAddressExceptionMessage                              = "'{0}'을(를) 유효한 IP/호스트:포트 주소로 구문 분석하지 못했습니다."
    mustBeRunningWithAdminPrivilegesExceptionMessage                  = '관리자 권한으로 실행되어야 비로소 로컬호스트 주소가 아닌 주소를 청취할 수 있습니다.'
    specificationMessage                                              = '사양'
    cacheStorageNotFoundForClearExceptionMessage                      = "캐시를 지우려고 할 때 이름이 '{0}'인 캐시 스토리지를 찾을 수 없습니다."
    restartingServerMessage                                           = '서버를 재시작 중...'
    cannotSupplyIntervalWhenEveryIsNoneExceptionMessage               = "매개변수 'Every'가 None으로 설정된 경우 간격을 제공할 수 없습니다."
    unsupportedJwtAlgorithmExceptionMessage                           = 'JWT 알고리즘은 현재 지원되지 않습니다: {0}'
    websocketsNotConfiguredForSignalMessagesExceptionMessage          = 'WebSockets가 신호 메시지를 보내도록 구성되지 않았습니다.'
    invalidLogicTypeInHashtableMiddlewareExceptionMessage             = '제공된 Hashtable 미들웨어에 잘못된 논리 유형이 있습니다. 예상된 유형은 ScriptBlock이지만, 얻은 것은: {0}'
    maximumConcurrentSchedulesLessThanMinimumExceptionMessage         = '최대 동시 스케줄 수는 최소 {0}보다 작을 수 없지만 받은 값: {1}'
    failedToAcquireSemaphoreOwnershipExceptionMessage                 = '세마포어 소유권을 획득하지 못했습니다. 세마포어 이름: {0}'
    propertiesParameterWithoutNameExceptionMessage                    = '속성에 이름이 없으면 Properties 매개변수를 사용할 수 없습니다.'
    customSessionStorageMethodNotImplementedExceptionMessage          = "사용자 정의 세션 저장소가 필요한 메서드 '{0}()'를 구현하지 않았습니다."
    authenticationMethodDoesNotExistExceptionMessage                  = '인증 방법이 존재하지 않습니다: {0}'
    webhooksFeatureNotSupportedInOpenApi30ExceptionMessage            = 'Webhooks 기능은 OpenAPI v3.0.x에서 지원되지 않습니다.'
    invalidContentTypeForSchemaExceptionMessage                       = "스키마에 대해 잘못된 'content-type'이 발견되었습니다: {0}"
    noUnlockScriptBlockForVaultExceptionMessage                       = "금고 '{0}'을(를) 해제하는 Unlock ScriptBlock이 제공되지 않았습니다."
    definitionTagMessage                                              = '정의 {0}:'
    failedToOpenRunspacePoolExceptionMessage                          = 'RunspacePool을 여는 데 실패했습니다: {0}'
    failedToCloseRunspacePoolExceptionMessage                         = 'RunspacePool을(를) 닫지 못했습니다: {0}'
    verbNoLogicPassedExceptionMessage                                 = '[동사] {0}: 전달된 로직 없음'
    noMutexFoundExceptionMessage                                      = "이름이 '{0}'인 뮤텍스를 찾을 수 없습니다."
    documentationMessage                                              = '문서'
    timerAlreadyDefinedExceptionMessage                               = '[타이머] {0}: 타이머가 이미 정의되어 있습니다.'
    invalidPortExceptionMessage                                       = '포트는 음수일 수 없습니다: {0}'
    viewsFolderNameAlreadyExistsExceptionMessage                      = '뷰 폴더 이름이 이미 존재합니다: {0}'
    noNameForWebSocketResetExceptionMessage                           = '재설정할 WebSocket의 이름이 제공되지 않았습니다.'
    mergeDefaultAuthNotInListExceptionMessage                         = "병합 기본 인증 '{0}'이(가) 제공된 인증 목록에 없습니다."
    descriptionRequiredExceptionMessage                               = '경로:{0} 응답:{1} 에 대한 설명이 필요합니다'
    pageNameShouldBeAlphaNumericExceptionMessage                      = '페이지 이름은 유효한 알파벳 숫자 값이어야 합니다: {0}'
    defaultValueNotBooleanOrEnumExceptionMessage                      = '기본값이 boolean이 아니며 enum에 속하지 않습니다.'
    openApiComponentSchemaDoesNotExistExceptionMessage                = 'OpenApi 구성 요소 스키마 {0}이(가) 존재하지 않습니다.'
    timerParameterMustBeGreaterThanZeroExceptionMessage               = '[타이머] {0}: {1}은(는) 0보다 커야 합니다.'
    taskTimedOutExceptionMessage                                      = '작업이 {0}ms 후에 시간 초과되었습니다.'
    scheduleStartTimeAfterEndTimeExceptionMessage                     = "[스케줄] {0}: 'StartTime'이 'EndTime' 이후일 수 없습니다."
    infoVersionMandatoryMessage                                       = 'info.version은 필수 항목입니다.'
    cannotUnlockNullObjectExceptionMessage                            = 'null 개체를 잠금 해제할 수 없습니다.'
    nonEmptyScriptBlockRequiredForCustomAuthExceptionMessage          = '사용자 정의 인증 스킴에는 비어 있지 않은 ScriptBlock이 필요합니다.'
    nonEmptyScriptBlockRequiredForAuthMethodExceptionMessage          = '인증 방법에 대해 비어 있지 않은 ScriptBlock이 필요합니다.'
    validationOfOneOfSchemaNotSupportedExceptionMessage               = "'oneof'을 포함하는 스키마의 유효성 검사는 지원되지 않습니다."
    routeParameterCannotBeNullExceptionMessage                        = "'Route' 매개변수는 null일 수 없습니다."
    cacheStorageAlreadyExistsExceptionMessage                         = "이름이 '{0}'인 캐시 스토리지가 이미 존재합니다."
    loggingMethodRequiresValidScriptBlockExceptionMessage             = "'{0}' 로깅 방법에 대한 제공된 출력 방법은 유효한 ScriptBlock이 필요합니다."
    scopedVariableAlreadyDefinedExceptionMessage                      = '범위 지정 변수가 이미 정의되었습니다: {0}'
    oauth2RequiresAuthorizeUrlExceptionMessage                        = 'OAuth2에는 권한 부여 URL이 필요합니다.'
    pathNotExistExceptionMessage                                      = '경로가 존재하지 않습니다: {0}'
    noDomainServerNameForWindowsAdAuthExceptionMessage                = 'Windows AD 인증을 위한 도메인 서버 이름이 제공되지 않았습니다.'
    suppliedDateAfterScheduleEndTimeExceptionMessage                  = '제공된 날짜가 스케줄 종료 시간 {0} 이후입니다.'
    wildcardMethodsIncompatibleWithAutoMethodsExceptionMessage        = '메서드에 대한 * 와일드카드는 AutoMethods 스위치와 호환되지 않습니다.'
    cannotSupplyIntervalForYearExceptionMessage                       = '매년 간격 값을 제공할 수 없습니다.'
    missingComponentsMessage                                          = '누락된 구성 요소'
    invalidStrictTransportSecurityDurationExceptionMessage            = '잘못된 Strict-Transport-Security 기간이 제공되었습니다: {0}. 0보다 커야 합니다.'
    noSecretForHmac512ExceptionMessage                                = 'HMAC512 해시를 위한 비밀이 제공되지 않았습니다.'
    daysInMonthExceededExceptionMessage                               = '{0}에는 {1}일밖에 없지만 {2}일이 제공되었습니다.'
    nonEmptyScriptBlockRequiredForCustomLoggingExceptionMessage       = '사용자 정의 로깅 출력 방법에는 비어 있지 않은 ScriptBlock이 필요합니다.'
    encodingAttributeOnlyAppliesToMultipartExceptionMessage           = '인코딩 속성은 multipart 및 application/x-www-form-urlencoded 요청 본문에만 적용됩니다.'
    suppliedDateBeforeScheduleStartTimeExceptionMessage               = '제공된 날짜가 스케줄 시작 시간 {0} 이전입니다.'
    unlockSecretRequiredExceptionMessage                              = "Microsoft.PowerShell.SecretStore를 사용할 때 'UnlockSecret' 속성이 필요합니다."
    noLogicPassedForMethodRouteExceptionMessage                       = '[{0}] {1}: 논리가 전달되지 않았습니다.'
    bodyParserAlreadyDefinedForContentTypeExceptionMessage            = '{0} 콘텐츠 유형에 대한 바디 파서가 이미 정의되어 있습니다.'
    invalidJwtSuppliedExceptionMessage                                = '제공된 JWT가 유효하지 않습니다.'
    sessionsRequiredForFlashMessagesExceptionMessage                  = '플래시 메시지를 사용하려면 세션이 필요합니다.'
    semaphoreAlreadyExistsExceptionMessage                            = "이름이 '{0}'인 세마포어가 이미 존재합니다."
    invalidJwtHeaderAlgorithmSuppliedExceptionMessage                 = '제공된 JWT 헤더 알고리즘이 유효하지 않습니다.'
    oauth2ProviderDoesNotSupportPasswordGrantTypeExceptionMessage     = "OAuth2 공급자는 InnerScheme을 사용하는 데 필요한 'password' 부여 유형을 지원하지 않습니다."
    invalidAliasFoundExceptionMessage                                 = '잘못된 {0} 별칭이 발견되었습니다: {1}'
    scheduleDoesNotExistExceptionMessage                              = "스케줄 '{0}'이(가) 존재하지 않습니다."
    accessMethodNotExistExceptionMessage                              = '액세스 방법이 존재하지 않습니다: {0}'
    oauth2ProviderDoesNotSupportCodeResponseTypeExceptionMessage      = "OAuth2 공급자는 'code' 응답 유형을 지원하지 않습니다."
    untestedPowerShellVersionWarningMessage                           = '[경고] Pode {0}은 출시 당시 사용 가능하지 않았기 때문에 PowerShell {1}에서 테스트되지 않았습니다.'
    secretVaultAlreadyRegisteredAutoImportExceptionMessage            = "이름이 '{0}'인 비밀 금고가 이미 자동으로 가져오는 동안 등록되었습니다."
    schemeRequiresValidScriptBlockExceptionMessage                    = "'{0}' 인증 검증기에 제공된 스킴에는 유효한 ScriptBlock이 필요합니다."
    serverLoopingMessage                                              = '서버 루핑 간격 {0}초'
    certificateThumbprintsNameSupportedOnWindowsExceptionMessage      = '인증서 지문/이름은 Windows에서만 지원됩니다.'
    sseConnectionNameRequiredExceptionMessage                         = "-Name 또는 `$WebEvent.Sse.Name에서 SSE 연결 이름이 필요합니다."
    invalidMiddlewareTypeExceptionMessage                             = '제공된 미들웨어 중 하나가 잘못된 유형입니다. 예상된 유형은 ScriptBlock 또는 Hashtable이지만, 얻은 것은: {0}'
    noSecretForJwtSignatureExceptionMessage                           = 'JWT 서명을 위한 비밀이 제공되지 않았습니다.'
    modulePathDoesNotExistExceptionMessage                            = '모듈 경로가 존재하지 않습니다: {0}'
    taskAlreadyDefinedExceptionMessage                                = '[작업] {0}: 작업이 이미 정의되었습니다.'
    verbAlreadyDefinedExceptionMessage                                = '[동사] {0}: 이미 정의되었습니다.'
    clientCertificatesOnlySupportedOnHttpsEndpointsExceptionMessage   = '클라이언트 인증서는 HTTPS 엔드포인트에서만 지원됩니다.'
    endpointNameNotExistExceptionMessage                              = "이름이 '{0}'인 엔드포인트가 존재하지 않습니다."
    middlewareNoLogicSuppliedExceptionMessage                         = '[미들웨어]: ScriptBlock에 로직이 제공되지 않았습니다.'
    scriptBlockRequiredForMergingUsersExceptionMessage                = 'Valid가 All일 때 여러 인증된 사용자를 하나의 객체로 병합하려면 ScriptBlock이 필요합니다.'
    secretVaultAlreadyRegisteredExceptionMessage                      = "이름이 '{0}'인 시크릿 금고가 이미 등록되었습니다{1}."
    deprecatedTitleVersionDescriptionWarningMessage                   = "경고: 'Enable-PodeOpenApi'의 제목, 버전 및 설명이 더 이상 사용되지 않습니다. 대신 'Add-PodeOAInfo'를 사용하십시오."
    undefinedOpenApiReferencesMessage                                 = '정의되지 않은 OpenAPI 참조:'
    doneMessage                                                       = '완료'
    swaggerEditorDoesNotSupportOpenApi31ExceptionMessage              = '이 버전의 Swagger-Editor는 OpenAPI 3.1을 지원하지 않습니다.'
    durationMustBeZeroOrGreaterExceptionMessage                       = '기간은 0 이상이어야 하지만 받은 값: {0}s'
    viewsPathDoesNotExistExceptionMessage                             = '뷰 경로가 존재하지 않습니다: {0}'
    discriminatorIncompatibleWithAllOfExceptionMessage                = "매개변수 'Discriminator'는 'allOf'와 호환되지 않습니다."
    noNameForWebSocketSendMessageExceptionMessage                     = '메시지를 보낼 WebSocket의 이름이 제공되지 않았습니다.'
    hashtableMiddlewareNoLogicExceptionMessage                        = '제공된 Hashtable 미들웨어에는 정의된 논리가 없습니다.'
    openApiInfoMessage                                                = 'OpenAPI 정보:'
    invalidSchemeForAuthValidatorExceptionMessage                     = "'{1}' 인증 검증기에 제공된 '{0}' 스킴에는 유효한 ScriptBlock이 필요합니다."
    sseFailedToBroadcastExceptionMessage                              = '{0}에 대해 정의된 SSE 브로드캐스트 수준으로 인해 SSE 브로드캐스트에 실패했습니다: {1}'
    adModuleWindowsOnlyExceptionMessage                               = 'Active Directory 모듈은 Windows에서만 사용할 수 있습니다.'
    requestLoggingAlreadyEnabledExceptionMessage                      = '요청 로깅이 이미 활성화되었습니다.'
    invalidAccessControlMaxAgeDurationExceptionMessage                = '잘못된 Access-Control-Max-Age 기간이 제공되었습니다: {0}. 0보다 커야 합니다.'
    openApiDefinitionAlreadyExistsExceptionMessage                    = '이름이 {0}인 OpenAPI 정의가 이미 존재합니다.'
    renamePodeOADefinitionTagExceptionMessage                         = "Rename-PodeOADefinitionTag은 Select-PodeOADefinition 'ScriptBlock' 내에서 사용할 수 없습니다."
    taskProcessDoesNotExistExceptionMessage                           = '작업 프로세스가 존재하지 않습니다: {0}'
    scheduleProcessDoesNotExistExceptionMessage                       = '스케줄 프로세스가 존재하지 않습니다: {0}'
    definitionTagChangeNotAllowedExceptionMessage                     = 'Route에 대한 정의 태그는 변경할 수 없습니다.'
    getRequestBodyNotAllowedExceptionMessage                          = '{0} 작업에는 요청 본문이 있을 수 없습니다.'
    fnDoesNotAcceptArrayAsPipelineInputExceptionMessage               = "함수 '{0}'은(는) 배열을 파이프라인 입력으로 받지 않습니다."
    unsupportedStreamCompressionEncodingExceptionMessage              = '지원되지 않는 스트림 압축 인코딩: {0}'
}
src\Locales\nl\Pode.psd1
@{
    schemaValidationRequiresPowerShell610ExceptionMessage             = 'Schema-validatie vereist PowerShell versie 6.1.0 of hoger.'
    customAccessPathOrScriptBlockRequiredExceptionMessage             = 'Een pad of ScriptBlock is vereist voor het verkrijgen van de aangepaste toegangswaarden.'
    operationIdMustBeUniqueForArrayExceptionMessage                   = 'OperationID: {0} moet uniek zijn en kan niet worden toegepast op een array.'
    endpointNotDefinedForRedirectingExceptionMessage                  = "Er is geen eindpunt met de naam '{0}' gedefinieerd voor omleiding."
    filesHaveChangedMessage                                           = 'De volgende bestanden zijn gewijzigd:'
    iisAspnetcoreTokenMissingExceptionMessage                         = 'IIS ASPNETCORE_TOKEN ontbreekt.'
    minValueGreaterThanMaxExceptionMessage                            = 'Min waarde voor {0} mag niet groter zijn dan de max waarde.'
    noLogicPassedForRouteExceptionMessage                             = 'Geen logica doorgegeven voor Route: {0}'
    scriptPathDoesNotExistExceptionMessage                            = 'Het scriptpad bestaat niet: {0}'
    mutexAlreadyExistsExceptionMessage                                = 'Er bestaat al een mutex met de volgende naam: {0}'
    listeningOnEndpointsMessage                                       = 'Luisteren naar de volgende {0} eindpunt(en) [{1} thread(s)]:'
    unsupportedFunctionInServerlessContextExceptionMessage            = 'De functie {0} wordt niet ondersteund in een serverloze context.'
    expectedNoJwtSignatureSuppliedExceptionMessage                    = 'Er werd geen JWT-handtekening verwacht.'
    secretAlreadyMountedExceptionMessage                              = "Er is al een geheim met de naam '{0}' gemonteerd."
    failedToAcquireLockExceptionMessage                               = 'Kan geen lock op het object verkrijgen.'
    noPathSuppliedForStaticRouteExceptionMessage                      = '[{0}]: Geen pad opgegeven voor statische route.'
    invalidHostnameSuppliedExceptionMessage                           = 'Ongeldige hostnaam opgegeven: {0}'
    authMethodAlreadyDefinedExceptionMessage                          = 'Authenticatiemethode al gedefinieerd: {0}'
    csrfCookieRequiresSecretExceptionMessage                          = "Bij gebruik van cookies voor CSRF is een geheim vereist. U kunt een geheim opgeven of het globale cookiesecret instellen - (Set-PodeCookieSecret '<waarde>' -Global)"
    nonEmptyScriptBlockRequiredForAuthMethodExceptionMessage          = 'Een niet-lege ScriptBlock is vereist voor de authenticatiemethode.'
    nonEmptyScriptBlockRequiredForPageRouteExceptionMessage           = 'Een niet-lege ScriptBlock is vereist om een paginaroute te maken.'
    noPropertiesMutuallyExclusiveExceptionMessage                     = "De parameter 'NoProperties' is wederzijds exclusief met 'Properties', 'MinProperties' en 'MaxProperties'"
    incompatiblePodeDllExceptionMessage                               = 'Een bestaande incompatibele Pode.DLL-versie {0} is geladen. Versie {1} is vereist. Open een nieuwe PowerShell/pwsh-sessie en probeer opnieuw.'
    accessMethodDoesNotExistExceptionMessage                          = 'Toegangsmethode bestaat niet: {0}.'
    scheduleAlreadyDefinedExceptionMessage                            = '[Schema] {0}: Schema al gedefinieerd.'
    secondsValueCannotBeZeroOrLessExceptionMessage                    = 'Waarde in seconden kan niet 0 of minder zijn voor {0}'
    pathToLoadNotFoundExceptionMessage                                = 'Pad om te laden {0} niet gevonden: {1}'
    failedToImportModuleExceptionMessage                              = 'Kon module niet importeren: {0}'
    endpointNotExistExceptionMessage                                  = "Eindpunt met protocol '{0}' en adres '{1}' of lokaal adres '{2}' bestaat niet."
    terminatingMessage                                                = 'Beëindigen...'
    noCommandsSuppliedToConvertToRoutesExceptionMessage               = 'Geen opdrachten opgegeven om om te zetten naar routes.'
    invalidTaskTypeExceptionMessage                                   = 'Taaktype is ongeldig, verwacht ofwel [System.Threading.Tasks.Task] of [hashtable]'
    alreadyConnectedToWebSocketExceptionMessage                       = "Al verbonden met WebSocket met naam '{0}'"
    crlfMessageEndCheckOnlySupportedOnTcpEndpointsExceptionMessage    = 'De CRLF-berichteneindcontrole wordt alleen ondersteund op TCP-eindpunten.'
    testPodeOAComponentSchemaNeedToBeEnabledExceptionMessage          = "'Test-PodeOAComponentSchema' moet worden ingeschakeld met 'Enable-PodeOpenApi -EnableSchemaValidation'"
    adModuleNotInstalledExceptionMessage                              = 'Active Directory-module is niet geïnstalleerd.'
    cronExpressionInvalidExceptionMessage                             = 'Cron-expressie mag alleen uit 5 delen bestaan: {0}'
    noSessionToSetOnResponseExceptionMessage                          = 'Er is geen sessie beschikbaar om op de reactie in te stellen.'
    valueOutOfRangeExceptionMessage                                   = "Waarde '{0}' voor {1} is ongeldig, moet tussen {2} en {3} liggen"
    loggingMethodAlreadyDefinedExceptionMessage                       = 'De logboekmethode is al gedefinieerd: {0}'
    noSecretForHmac256ExceptionMessage                                = 'Geen geheim opgegeven voor HMAC256-hash.'
    eolPowerShellWarningMessage                                       = '[WAARSCHUWING] Pode {0} is niet getest op PowerShell {1}, omdat het EOL is.'
    runspacePoolFailedToLoadExceptionMessage                          = '{0} RunspacePool kon niet geladen worden.'
    noEventRegisteredExceptionMessage                                 = 'Geen {0} gebeurtenis geregistreerd: {1}'
    scheduleCannotHaveNegativeLimitExceptionMessage                   = '[Schema] {0}: Kan geen negatieve limiet hebben.'
    openApiRequestStyleInvalidForParameterExceptionMessage            = 'OpenApi-verzoekstijl kan niet {0} zijn voor een {1} parameter.'
    openApiDocumentNotCompliantExceptionMessage                       = 'OpenAPI-document voldoet niet aan de normen.'
    taskDoesNotExistExceptionMessage                                  = "Taak '{0}' bestaat niet."
    scopedVariableNotFoundExceptionMessage                            = 'Gescopede variabele niet gevonden: {0}'
    sessionsRequiredForCsrfExceptionMessage                           = 'Sessies zijn vereist om CSRF te gebruiken, tenzij u cookies wilt gebruiken.'
    nonEmptyScriptBlockRequiredForLoggingMethodExceptionMessage       = 'Een niet-lege ScriptBlock is vereist voor de logboekmethode.'
    credentialsPassedWildcardForHeadersLiteralExceptionMessage        = 'Wanneer referenties worden doorgegeven, wordt het * jokerteken voor headers als een letterlijke tekenreeks en niet als een jokerteken genomen.'
    podeNotInitializedExceptionMessage                                = 'Pode is niet geïnitialiseerd.'
    multipleEndpointsForGuiMessage                                    = 'Meerdere eindpunten gedefinieerd, alleen het eerste wordt gebruikt voor de GUI.'
    operationIdMustBeUniqueExceptionMessage                           = 'OperationID: {0} moet uniek zijn.'
    invalidJsonJwtExceptionMessage                                    = 'Ongeldige JSON-waarde gevonden in JWT'
    noAlgorithmInJwtHeaderExceptionMessage                            = 'Geen algoritme opgegeven in JWT-header.'
    openApiVersionPropertyMandatoryExceptionMessage                   = 'OpenApi-versie-eigenschap is verplicht.'
    limitValueCannotBeZeroOrLessExceptionMessage                      = 'Limietwaarde kan niet 0 of minder zijn voor {0}'
    timerDoesNotExistExceptionMessage                                 = "Timer '{0}' bestaat niet."
    openApiGenerationDocumentErrorMessage                             = 'OpenAPI-generatiedocumentfout:'
    routeAlreadyContainsCustomAccessExceptionMessage                  = "Route '[{0}] {1}' bevat al aangepaste toegang met naam '{2}'"
    maximumConcurrentWebSocketThreadsLessThanMinimumExceptionMessage  = 'Maximaal aantal gelijktijdige WebSocket-threads kan niet minder zijn dan het minimum van {0} maar kreeg: {1}'
    middlewareAlreadyDefinedExceptionMessage                          = '[Middleware] {0}: Middleware al gedefinieerd.'
    invalidAtomCharacterExceptionMessage                              = 'Ongeldig atoomteken: {0}'
    invalidCronAtomFormatExceptionMessage                             = 'Ongeldig cron-atoomformaat gevonden: {0}'
    cacheStorageNotFoundForRetrieveExceptionMessage                   = "Cache-opslag met naam '{0}' niet gevonden bij poging om gecachte item '{1}' op te halen"
    headerMustHaveNameInEncodingContextExceptionMessage               = 'Header moet een naam hebben wanneer deze in een coderingscontext wordt gebruikt.'
    moduleDoesNotContainFunctionExceptionMessage                      = 'Module {0} bevat geen functie {1} om om te zetten naar een route.'
    pathToIconForGuiDoesNotExistExceptionMessage                      = 'Pad naar het pictogram voor GUI bestaat niet: {0}'
    noTitleSuppliedForPageExceptionMessage                            = 'Geen titel opgegeven voor {0} pagina.'
    certificateSuppliedForNonHttpsWssEndpointExceptionMessage         = 'Certificaat opgegeven voor niet-HTTPS/WSS-eindpunt.'
    cannotLockNullObjectExceptionMessage                              = 'Kan geen object vergrendelen dat null is.'
    showPodeGuiOnlyAvailableOnWindowsExceptionMessage                 = 'Show-PodeGui is momenteel alleen beschikbaar voor Windows PowerShell en PowerShell 7+ op Windows OS.'
    unlockSecretButNoScriptBlockExceptionMessage                      = 'Ontgrendel geheim opgegeven voor aangepast geheimenkluis type, maar geen ontgrendel ScriptBlock opgegeven.'
    invalidIpAddressExceptionMessage                                  = 'Het opgegeven IP-adres is ongeldig: {0}'
    maxDaysInvalidExceptionMessage                                    = 'MaxDays moet 0 of groter zijn, maar kreeg: {0}'
    noRemoveScriptBlockForVaultExceptionMessage                       = "Geen verwijder ScriptBlock opgegeven voor het verwijderen van geheimen uit de kluis '{0}'"
    noSecretExpectedForNoSignatureExceptionMessage                    = 'Er werd geen geheim verwacht voor geen handtekening.'
    noCertificateFoundExceptionMessage                                = "Geen certificaat gevonden in {0}{1} voor '{2}'"
    minValueInvalidExceptionMessage                                   = "Min waarde '{0}' voor {1} is ongeldig, moet groter zijn dan/gelijk aan {2}"
    accessRequiresAuthenticationOnRoutesExceptionMessage              = 'Toegang vereist dat authenticatie wordt opgegeven op routes.'
    noSecretForHmac384ExceptionMessage                                = 'Geen geheim opgegeven voor HMAC384-hash.'
    windowsLocalAuthSupportIsForWindowsOnlyExceptionMessage           = 'Windows lokale authenticatie-ondersteuning is alleen voor Windows OS.'
    definitionTagNotDefinedExceptionMessage                           = 'DefinitionTag {0} bestaat niet.'
    noComponentInDefinitionExceptionMessage                           = 'Geen component van het type {0} genaamd {1} is beschikbaar in de {2} definitie.'
    noSmtpHandlersDefinedExceptionMessage                             = 'Er zijn geen SMTP-handlers gedefinieerd.'
    sessionMiddlewareAlreadyInitializedExceptionMessage               = 'Sessie Middleware is al geïnitialiseerd.'
    reusableComponentPathItemsNotAvailableInOpenApi30ExceptionMessage = "De herbruikbare componentfunctie 'pathItems' is niet beschikbaar in OpenAPI v3.0."
    wildcardHeadersIncompatibleWithAutoHeadersExceptionMessage        = 'Het * jokerteken voor headers is niet compatibel met de AutoHeaders-schakelaar.'
    noDataForFileUploadedExceptionMessage                             = "Geen gegevens voor bestand '{0}' zijn geüpload in het verzoek."
    sseOnlyConfiguredOnEventStreamAcceptHeaderExceptionMessage        = 'SSE kan alleen worden geconfigureerd voor verzoeken met een Accept-headerwaarde van text/event-stream'
    noSessionAvailableToSaveExceptionMessage                          = 'Er is geen sessie beschikbaar om op te slaan.'
    pathParameterRequiresRequiredSwitchExceptionMessage               = "Als de parameterlocatie 'Pad' is, is de schakelparameter 'Vereist' verplicht."
    noOpenApiUrlSuppliedExceptionMessage                              = 'Geen OpenAPI-URL opgegeven voor {0}.'
    maximumConcurrentSchedulesInvalidExceptionMessage                 = "Maximaal aantal gelijktijdige schema's moet >=1 zijn, maar kreeg: {0}"
    snapinsSupportedOnWindowsPowershellOnlyExceptionMessage           = 'Snapins worden alleen ondersteund op Windows PowerShell.'
    eventViewerLoggingSupportedOnWindowsOnlyExceptionMessage          = 'Event Viewer-logboekregistratie wordt alleen ondersteund op Windows OS.'
    parametersMutuallyExclusiveExceptionMessage                       = "Parameters '{0}' en '{1}' zijn wederzijds exclusief."
    pathItemsFeatureNotSupportedInOpenApi30ExceptionMessage           = 'De functie PathItems wordt niet ondersteund in OpenAPI v3.0.x'
    openApiParameterRequiresNameExceptionMessage                      = 'De OpenApi-parameter vereist een naam om te worden opgegeven.'
    maximumConcurrentTasksLessThanMinimumExceptionMessage             = 'Maximaal aantal gelijktijdige taken kan niet minder zijn dan het minimum van {0} maar kreeg: {1}'
    noSemaphoreFoundExceptionMessage                                  = "Geen semafoor gevonden genaamd '{0}'"
    singleValueForIntervalExceptionMessage                            = 'U kunt slechts één {0} waarde opgeven bij gebruik van intervallen.'
    jwtNotYetValidExceptionMessage                                    = 'De JWT is nog niet geldig voor gebruik.'
    verbAlreadyDefinedForUrlExceptionMessage                          = '[Werkwoord] {0}: Al gedefinieerd voor {1}'
    noSecretNamedMountedExceptionMessage                              = "Geen geheim genaamd '{0}' is gemonteerd."
    moduleOrVersionNotFoundExceptionMessage                           = 'Module of versie niet gevonden op {0}: {1}@{2}'
    noScriptBlockSuppliedExceptionMessage                             = 'Geen ScriptBlock opgegeven.'
    noSecretVaultRegisteredExceptionMessage                           = "Geen geheime kluis met de naam '{0}' is geregistreerd."
    nameRequiredForEndpointIfRedirectToSuppliedExceptionMessage       = 'Een naam is vereist voor het eindpunt als de parameter RedirectTo is opgegeven.'
    openApiLicenseObjectRequiresNameExceptionMessage                  = "Het OpenAPI-object 'licentie' vereist de eigenschap 'naam'. Gebruik de parameter -LicenseName."
    sourcePathDoesNotExistForStaticRouteExceptionMessage              = '{0}: Het opgegeven bronpad voor statische route bestaat niet: {1}'
    noNameForWebSocketDisconnectExceptionMessage                      = 'Geen naam opgegeven voor een WebSocket om los te koppelen.'
    certificateExpiredExceptionMessage                                = "Het certificaat '{0}' is verlopen: {1}"
    secretVaultUnlockExpiryDateInPastExceptionMessage                 = 'De ontgrendelingsvervaldatum van de geheime kluis ligt in het verleden (UTC): {0}'
    invalidWebExceptionTypeExceptionMessage                           = 'Uitzondering is van een ongeldig type, moet ofwel WebException of HttpRequestException zijn, maar kreeg: {0}'
    invalidSecretValueTypeExceptionMessage                            = 'Geheime waarde is van een ongeldig type. Verwachte types: String, SecureString, HashTable, Byte[], of PSCredential. Maar kreeg: {0}'
    explicitTlsModeOnlySupportedOnSmtpsTcpsEndpointsExceptionMessage  = 'De expliciete TLS-modus wordt alleen ondersteund op SMTPS- en TCPS-eindpunten.'
    discriminatorMappingRequiresDiscriminatorPropertyExceptionMessage = "De parameter 'DiscriminatorMapping' kan alleen worden gebruikt wanneer 'DiscriminatorProperty' aanwezig is."
    scriptErrorExceptionMessage                                       = "Fout '{0}' in script {1} {2} (regel {3}) teken {4} bij uitvoeren {5} op {6} object '{7}' Klasse: {8} BasisKlasse: {9}"
    cannotSupplyIntervalForQuarterExceptionMessage                    = 'Kan geen intervalwaarde opgeven voor elk kwartaal.'
    scheduleEndTimeMustBeInFutureExceptionMessage                     = '[Schema] {0}: De eindtijdwaarde moet in de toekomst liggen.'
    invalidJwtSignatureSuppliedExceptionMessage                       = 'Ongeldige JWT-handtekening opgegeven.'
    noSetScriptBlockForVaultExceptionMessage                          = "Geen Set ScriptBlock opgegeven voor het bijwerken/maken van geheimen in de kluis '{0}'"
    accessMethodNotExistForMergingExceptionMessage                    = 'Toegangsmethode bestaat niet voor samenvoegen: {0}'
    defaultAuthNotInListExceptionMessage                              = "De standaardauthenticatie '{0}' staat niet in de opgegeven authenticatielijst."
    parameterHasNoNameExceptionMessage                                = "De parameter heeft geen naam. Geef dit component een naam met de parameter 'Naam'."
    methodPathAlreadyDefinedForUrlExceptionMessage                    = '[{0}] {1}: Al gedefinieerd voor {2}'
    fileWatcherAlreadyDefinedExceptionMessage                         = "Een bestand bewaker genaamd '{0}' is al gedefinieerd."
    noServiceHandlersDefinedExceptionMessage                          = 'Er zijn geen servicehandlers gedefinieerd.'
    secretRequiredForCustomSessionStorageExceptionMessage             = 'Een geheim is vereist bij gebruik van aangepaste sessieopslag.'
    secretManagementModuleNotInstalledExceptionMessage                = 'Microsoft.PowerShell.SecretManagement module niet geïnstalleerd.'
    noPathSuppliedForRouteExceptionMessage                            = 'Geen pad opgegeven voor de route.'
    validationOfAnyOfSchemaNotSupportedExceptionMessage               = "Validatie van een schema dat 'anyof' bevat, wordt niet ondersteund."
    iisAuthSupportIsForWindowsOnlyExceptionMessage                    = 'IIS-authenticatieondersteuning is alleen voor Windows OS.'
    oauth2InnerSchemeInvalidExceptionMessage                          = 'OAuth2 InnerScheme kan alleen een van de basale of formulierauthenticatie zijn, maar kreeg: {0}'
    noRoutePathSuppliedForPageExceptionMessage                        = 'Geen routepad opgegeven voor {0} pagina.'
    cacheStorageNotFoundForExistsExceptionMessage                     = "Cache-opslag met naam '{0}' niet gevonden bij poging om te controleren of gecachte item '{1}' bestaat."
    handlerAlreadyDefinedExceptionMessage                             = '[{0}] {1}: Handler al gedefinieerd.'
    sessionsNotConfiguredExceptionMessage                             = 'Sessies zijn niet geconfigureerd.'
    propertiesTypeObjectAssociationExceptionMessage                   = 'Alleen eigenschappen van het type Object kunnen worden geassocieerd met {0}.'
    sessionsRequiredForSessionPersistentAuthExceptionMessage          = 'Sessies zijn vereist om sessie-persistente authenticatie te gebruiken.'
    invalidPathWildcardOrDirectoryExceptionMessage                    = 'Het opgegeven pad kan geen wildcard of een directory zijn: {0}'
    accessMethodAlreadyDefinedExceptionMessage                        = 'Toegangsmethode al gedefinieerd: {0}'
    parametersValueOrExternalValueMandatoryExceptionMessage           = "Parameters 'Value' of 'ExternalValue' zijn verplicht"
    maximumConcurrentTasksInvalidExceptionMessage                     = 'Maximaal aantal gelijktijdige taken moet >=1 zijn, maar kreeg: {0}'
    cannotCreatePropertyWithoutTypeExceptionMessage                   = 'Kan de eigenschap niet maken omdat er geen type is gedefinieerd.'
    authMethodNotExistForMergingExceptionMessage                      = 'Authenticatiemethode bestaat niet voor samenvoegen: {0}'
    maxValueInvalidExceptionMessage                                   = "Max waarde '{0}' voor {1} is ongeldig, moet minder dan/gelijk aan {2} zijn"
    endpointAlreadyDefinedExceptionMessage                            = "Er is al een eindpunt met de naam '{0}' gedefinieerd."
    eventAlreadyRegisteredExceptionMessage                            = '{0} gebeurtenis al geregistreerd: {1}'
    parameterNotSuppliedInRequestExceptionMessage                     = "Een parameter genaamd '{0}' is niet opgegeven in het verzoek of heeft geen beschikbare gegevens."
    cacheStorageNotFoundForSetExceptionMessage                        = "Cache-opslag met naam '{0}' niet gevonden bij poging om gecachte item '{1}' in te stellen"
    methodPathAlreadyDefinedExceptionMessage                          = '[{0}] {1}: Al gedefinieerd.'
    errorLoggingAlreadyEnabledExceptionMessage                        = 'Foutlogboekregistratie is al ingeschakeld.'
    valueForUsingVariableNotFoundExceptionMessage                     = "Waarde voor '`$using:{0}' kon niet worden gevonden."
    rapidPdfDoesNotSupportOpenApi31ExceptionMessage                   = 'Het Document-tool RapidPdf ondersteunt OpenAPI 3.1 niet'
    oauth2ClientSecretRequiredExceptionMessage                        = 'OAuth2 vereist een Client Secret wanneer PKCE niet wordt gebruikt.'
    invalidBase64JwtExceptionMessage                                  = 'Ongeldige Base64-gecodeerde waarde gevonden in JWT'
    noSessionToCalculateDataHashExceptionMessage                      = 'Geen sessie beschikbaar om gegevenshash te berekenen.'
    cacheStorageNotFoundForRemoveExceptionMessage                     = "Cache-opslag met naam '{0}' niet gevonden bij poging om gecachte item '{1}' te verwijderen"
    csrfMiddlewareNotInitializedExceptionMessage                      = 'CSRF Middleware is niet geïnitialiseerd.'
    infoTitleMandatoryMessage                                         = 'info.title is verplicht.'
    typeCanOnlyBeAssociatedWithObjectExceptionMessage                 = 'Type {0} kan alleen worden geassocieerd met een Object.'
    userFileDoesNotExistExceptionMessage                              = 'Het gebruikersbestand bestaat niet: {0}'
    routeParameterNeedsValidScriptblockExceptionMessage               = 'De routeparameter heeft een geldige, niet-lege, scriptblock nodig.'
    nextTriggerCalculationErrorExceptionMessage                       = 'Er lijkt iets mis te zijn gegaan bij het berekenen van de volgende triggerdatum: {0}'
    cannotLockValueTypeExceptionMessage                               = 'Kan een [ValueType] niet vergrendelen'
    failedToCreateOpenSslCertExceptionMessage                         = 'Kon OpenSSL-certificaat niet maken: {0}'
    jwtExpiredExceptionMessage                                        = 'De JWT is verlopen.'
    openingGuiMessage                                                 = 'De GUI wordt geopend.'
    multiTypePropertiesRequireOpenApi31ExceptionMessage               = 'Multi-type eigenschappen vereisen OpenApi versie 3.1 of hoger.'
    noNameForWebSocketRemoveExceptionMessage                          = 'Geen naam opgegeven voor een WebSocket om te verwijderen.'
    maxSizeInvalidExceptionMessage                                    = 'MaxSize moet 0 of groter zijn, maar kreeg: {0}'
    iisShutdownMessage                                                = '(IIS Afsluiting)'
    cannotUnlockValueTypeExceptionMessage                             = 'Kan een [ValueType] niet ontgrendelen'
    noJwtSignatureForAlgorithmExceptionMessage                        = 'Geen JWT-handtekening opgegeven voor {0}.'
    maximumConcurrentWebSocketThreadsInvalidExceptionMessage          = 'Maximaal aantal gelijktijdige WebSocket-threads moet >=1 zijn, maar kreeg: {0}'
    acknowledgeMessageOnlySupportedOnSmtpTcpEndpointsExceptionMessage = 'Het Acknowledge-bericht wordt alleen ondersteund op SMTP- en TCP-eindpunten.'
    failedToConnectToUrlExceptionMessage                              = 'Kon geen verbinding maken met URL: {0}'
    failedToAcquireMutexOwnershipExceptionMessage                     = 'Kon geen mutex-eigendom verkrijgen. Mutex-naam: {0}'
    sessionsRequiredForOAuth2WithPKCEExceptionMessage                 = 'Sessies zijn vereist om OAuth2 met PKCE te gebruiken'
    failedToConnectToWebSocketExceptionMessage                        = 'Kon geen verbinding maken met WebSocket: {0}'
    unsupportedObjectExceptionMessage                                 = 'Niet ondersteund object'
    failedToParseAddressExceptionMessage                              = "Kon '{0}' niet parseren als een geldig IP/Host:Port adres"
    mustBeRunningWithAdminPrivilegesExceptionMessage                  = 'Moet worden uitgevoerd met beheerdersrechten om naar niet-lokale adressen te luisteren.'
    specificationMessage                                              = 'Specificatie'
    cacheStorageNotFoundForClearExceptionMessage                      = "Cache-opslag met naam '{0}' niet gevonden bij poging om de cache te wissen."
    restartingServerMessage                                           = 'Server opnieuw starten...'
    cannotSupplyIntervalWhenEveryIsNoneExceptionMessage               = "Kan geen interval opgeven wanneer de parameter 'Every' is ingesteld op None."
    unsupportedJwtAlgorithmExceptionMessage                           = 'Het JWT-algoritme wordt momenteel niet ondersteund: {0}'
    websocketsNotConfiguredForSignalMessagesExceptionMessage          = 'WebSockets zijn niet geconfigureerd om signaalberichten te verzenden.'
    invalidLogicTypeInHashtableMiddlewareExceptionMessage             = 'Een opgegeven Hashtable Middleware heeft een ongeldig logica-type. Verwachte ScriptBlock, maar kreeg: {0}'
    maximumConcurrentSchedulesLessThanMinimumExceptionMessage         = "Maximaal aantal gelijktijdige schema's kan niet minder zijn dan het minimum van {0} maar kreeg: {1}"
    failedToAcquireSemaphoreOwnershipExceptionMessage                 = 'Kon geen semafoor-eigendom verkrijgen. Semafoornaam: {0}'
    propertiesParameterWithoutNameExceptionMessage                    = 'De eigenschappenparameters kunnen niet worden gebruikt als de eigenschap geen naam heeft.'
    customSessionStorageMethodNotImplementedExceptionMessage          = "De aangepaste sessieopslag implementeert de vereiste methode '{0}()' niet."
    authenticationMethodDoesNotExistExceptionMessage                  = 'Authenticatiemethode bestaat niet: {0}'
    webhooksFeatureNotSupportedInOpenApi30ExceptionMessage            = 'De Webhooks-functie wordt niet ondersteund in OpenAPI v3.0.x'
    invalidContentTypeForSchemaExceptionMessage                       = "Ongeldige 'content-type' gevonden voor schema: {0}"
    noUnlockScriptBlockForVaultExceptionMessage                       = "Geen ontgrendel ScriptBlock opgegeven voor het ontgrendelen van de kluis '{0}'"
    definitionTagMessage                                              = 'Definitie {0}:'
    failedToOpenRunspacePoolExceptionMessage                          = 'Kon RunspacePool niet openen: {0}'
    failedToCloseRunspacePoolExceptionMessage                         = 'Kon RunspacePool niet sluiten: {0}'
    verbNoLogicPassedExceptionMessage                                 = '[Werkwoord] {0}: Geen logica doorgegeven'
    noMutexFoundExceptionMessage                                      = "Geen mutex gevonden genaamd '{0}'"
    documentationMessage                                              = 'Documentatie'
    timerAlreadyDefinedExceptionMessage                               = '[Timer] {0}: Timer al gedefinieerd.'
    invalidPortExceptionMessage                                       = 'De poort kan niet negatief zijn: {0}'
    viewsFolderNameAlreadyExistsExceptionMessage                      = 'De mapnaam Views bestaat al: {0}'
    noNameForWebSocketResetExceptionMessage                           = 'Geen naam opgegeven voor een WebSocket om te resetten.'
    mergeDefaultAuthNotInListExceptionMessage                         = "De standaardauthenticatie '{0}' staat niet in de opgegeven authenticatielijst."
    descriptionRequiredExceptionMessage                               = 'Een beschrijving is vereist voor Pad:{0} Antwoord:{1}'
    pageNameShouldBeAlphaNumericExceptionMessage                      = 'De paginanaam moet een geldige alfanumerieke waarde zijn: {0}'
    defaultValueNotBooleanOrEnumExceptionMessage                      = 'De standaardwaarde is geen boolean en maakt geen deel uit van de enum.'
    openApiComponentSchemaDoesNotExistExceptionMessage                = 'Het OpenApi-component schema {0} bestaat niet.'
    timerParameterMustBeGreaterThanZeroExceptionMessage               = '[Timer] {0}: {1} moet groter zijn dan 0.'
    taskTimedOutExceptionMessage                                      = 'Taak is verlopen na {0}ms.'
    scheduleStartTimeAfterEndTimeExceptionMessage                     = '[Schema] {0}: Kan geen StartTime hebben na de EndTime'
    infoVersionMandatoryMessage                                       = 'info.version is verplicht.'
    cannotUnlockNullObjectExceptionMessage                            = 'Kan een object dat null is niet ontgrendelen.'
    nonEmptyScriptBlockRequiredForCustomAuthExceptionMessage          = 'Een niet-lege ScriptBlock is vereist voor het aangepaste authenticatieschema.'
    validationOfOneOfSchemaNotSupportedExceptionMessage               = "Validatie van een schema dat 'oneof' bevat, wordt niet ondersteund."
    routeParameterCannotBeNullExceptionMessage                        = "De parameter 'Route' kan niet null zijn."
    cacheStorageAlreadyExistsExceptionMessage                         = "Cache-opslag met naam '{0}' bestaat al."
    loggingMethodRequiresValidScriptBlockExceptionMessage             = "De opgegeven uitvoeringsmethode voor de '{0}' logboekmethode vereist een geldige ScriptBlock."
    scopedVariableAlreadyDefinedExceptionMessage                      = 'Gescopede variabele al gedefinieerd: {0}'
    oauth2RequiresAuthorizeUrlExceptionMessage                        = "OAuth2 vereist een 'AuthoriseUrl'-eigenschap om te worden opgegeven."
    pathNotExistExceptionMessage                                      = 'Pad bestaat niet: {0}'
    noDomainServerNameForWindowsAdAuthExceptionMessage                = 'Er is geen domeinservernaam opgegeven voor Windows AD-authenticatie'
    suppliedDateAfterScheduleEndTimeExceptionMessage                  = 'Opgegeven datum is na de eindtijd van het schema op {0}'
    wildcardMethodsIncompatibleWithAutoMethodsExceptionMessage        = 'Het * jokerteken voor methoden is niet compatibel met de AutoMethods-schakelaar.'
    cannotSupplyIntervalForYearExceptionMessage                       = 'Kan geen intervalwaarde opgeven voor elk jaar.'
    missingComponentsMessage                                          = 'Ontbrekende component(en)'
    invalidStrictTransportSecurityDurationExceptionMessage            = 'Ongeldige Strict-Transport-Security duur opgegeven: {0}. Het moet groter zijn dan 0.'
    noSecretForHmac512ExceptionMessage                                = 'Geen geheim opgegeven voor HMAC512-hash.'
    daysInMonthExceededExceptionMessage                               = '{0} heeft slechts {1} dagen, maar {2} is opgegeven.'
    nonEmptyScriptBlockRequiredForCustomLoggingExceptionMessage       = 'Een niet-lege ScriptBlock is vereist voor de aangepaste logboekuitvoermethode.'
    encodingAttributeOnlyAppliesToMultipartExceptionMessage           = 'Het coderingsattribuut is alleen van toepassing op multipart en application/x-www-form-urlencoded request bodies.'
    suppliedDateBeforeScheduleStartTimeExceptionMessage               = 'Opgegeven datum is voor de starttijd van het schema op {0}'
    unlockSecretRequiredExceptionMessage                              = "Een 'UnlockSecret' eigenschap is vereist bij gebruik van Microsoft.PowerShell.SecretStore"
    noLogicPassedForMethodRouteExceptionMessage                       = '[{0}] {1}: Geen logica doorgegeven.'
    bodyParserAlreadyDefinedForContentTypeExceptionMessage            = 'Er is al een body-parser gedefinieerd voor de {0} content-type.'
    invalidJwtSuppliedExceptionMessage                                = 'Ongeldige JWT opgegeven.'
    sessionsRequiredForFlashMessagesExceptionMessage                  = 'Sessies zijn vereist om Flash-berichten te gebruiken.'
    semaphoreAlreadyExistsExceptionMessage                            = 'Een semafoor met de volgende naam bestaat al: {0}'
    invalidJwtHeaderAlgorithmSuppliedExceptionMessage                 = 'Ongeldig JWT-headeralgoritme opgegeven.'
    oauth2ProviderDoesNotSupportPasswordGrantTypeExceptionMessage     = "De OAuth2-provider ondersteunt het 'password' grant_type vereist door gebruik van een InnerScheme niet."
    invalidAliasFoundExceptionMessage                                 = 'Ongeldige {0} alias gevonden: {1}'
    scheduleDoesNotExistExceptionMessage                              = "Schema '{0}' bestaat niet."
    accessMethodNotExistExceptionMessage                              = 'Toegangsmethode bestaat niet: {0}'
    oauth2ProviderDoesNotSupportCodeResponseTypeExceptionMessage      = "De OAuth2-provider ondersteunt het 'code' response_type niet."
    untestedPowerShellVersionWarningMessage                           = '[WAARSCHUWING] Pode {0} is niet getest op PowerShell {1}, omdat het niet beschikbaar was toen Pode werd uitgebracht.'
    secretVaultAlreadyRegisteredAutoImportExceptionMessage            = "Een geheime kluis met de naam '{0}' is al geregistreerd tijdens het automatisch importeren van geheime kluizen."
    schemeRequiresValidScriptBlockExceptionMessage                    = "Het opgegeven schema voor de '{0}' authenticatievalidator vereist een geldige ScriptBlock."
    serverLoopingMessage                                              = 'Server loop elke {0} seconden'
    certificateThumbprintsNameSupportedOnWindowsExceptionMessage      = 'Certificaat thumbprints/naam worden alleen ondersteund op Windows OS.'
    sseConnectionNameRequiredExceptionMessage                         = "Een SSE-verbindingnaam is vereist, hetzij van -Naam of `$WebEvent.Sse.Name"
    invalidMiddlewareTypeExceptionMessage                             = 'Een van de opgegeven middlewares is van een ongeldig type. Verwachte ScriptBlock of Hashtable, maar kreeg: {0}'
    noSecretForJwtSignatureExceptionMessage                           = 'Geen geheim opgegeven voor JWT-handtekening.'
    modulePathDoesNotExistExceptionMessage                            = 'Het modulepad bestaat niet: {0}'
    taskAlreadyDefinedExceptionMessage                                = '[Taak] {0}: Taak al gedefinieerd.'
    verbAlreadyDefinedExceptionMessage                                = '[Werkwoord] {0}: Al gedefinieerd'
    clientCertificatesOnlySupportedOnHttpsEndpointsExceptionMessage   = 'Clientcertificaten worden alleen ondersteund op HTTPS-eindpunten.'
    endpointNameNotExistExceptionMessage                              = "Eindpunt met naam '{0}' bestaat niet."
    middlewareNoLogicSuppliedExceptionMessage                         = '[Middleware]: Geen logica opgegeven in ScriptBlock.'
    scriptBlockRequiredForMergingUsersExceptionMessage                = 'Een ScriptBlock voor het samenvoegen van meerdere geauthenticeerde gebruikers in één object is vereist wanneer Valid All is.'
    secretVaultAlreadyRegisteredExceptionMessage                      = "Een geheime kluis met de naam '{0}' is al geregistreerd{1}."
    deprecatedTitleVersionDescriptionWarningMessage                   = "WAARSCHUWING: Titel, versie en beschrijving op 'Enable-PodeOpenApi' zijn verouderd. Gebruik in plaats daarvan 'Add-PodeOAInfo'."
    undefinedOpenApiReferencesMessage                                 = 'Ongedefinieerde OpenAPI-referenties:'
    doneMessage                                                       = 'Klaar'
    swaggerEditorDoesNotSupportOpenApi31ExceptionMessage              = 'Deze versie van Swagger-Editor ondersteunt OpenAPI 3.1 niet'
    durationMustBeZeroOrGreaterExceptionMessage                       = 'Duur moet 0 of groter zijn, maar kreeg: {0}s'
    viewsPathDoesNotExistExceptionMessage                             = 'Het pad voor views bestaat niet: {0}'
    discriminatorIncompatibleWithAllOfExceptionMessage                = "De parameter 'Discriminator' is niet compatibel met 'allOf'."
    noNameForWebSocketSendMessageExceptionMessage                     = 'Geen naam opgegeven voor een WebSocket om een bericht naar te sturen.'
    hashtableMiddlewareNoLogicExceptionMessage                        = 'Een opgegeven Hashtable Middleware heeft geen logica gedefinieerd.'
    openApiInfoMessage                                                = 'OpenAPI Info:'
    invalidSchemeForAuthValidatorExceptionMessage                     = "Het opgegeven '{0}' schema voor de '{1}' authenticatievalidator vereist een geldige ScriptBlock."
    sseFailedToBroadcastExceptionMessage                              = 'SSE kon niet uitzenden vanwege het gedefinieerde SSE-uitzendniveau voor {0}: {1}'
    adModuleWindowsOnlyExceptionMessage                               = 'Active Directory-module alleen beschikbaar op Windows OS.'
    requestLoggingAlreadyEnabledExceptionMessage                      = 'Verzoeklogboekregistratie is al ingeschakeld.'
    invalidAccessControlMaxAgeDurationExceptionMessage                = 'Ongeldige Access-Control-Max-Age duur opgegeven: {0}. Moet groter zijn dan 0.'
    openApiDefinitionAlreadyExistsExceptionMessage                    = 'OpenAPI-definitie met de naam {0} bestaat al.'
    renamePodeOADefinitionTagExceptionMessage                         = "Rename-PodeOADefinitionTag kan niet worden gebruikt binnen een Select-PodeOADefinition 'ScriptBlock'."
    taskProcessDoesNotExistExceptionMessage                           = "Taakproces '{0}' bestaat niet."
    scheduleProcessDoesNotExistExceptionMessage                       = "Schema-proces '{0}' bestaat niet."
    definitionTagChangeNotAllowedExceptionMessage                     = 'Definitietag voor een route kan niet worden gewijzigd.'
    getRequestBodyNotAllowedExceptionMessage                          = '{0}-operaties kunnen geen Request Body hebben.'
    fnDoesNotAcceptArrayAsPipelineInputExceptionMessage               = "De functie '{0}' accepteert geen array als pipeline-invoer."
    unsupportedStreamCompressionEncodingExceptionMessage              = 'Niet-ondersteunde streamcompressie-encodering: {0}'
}
src\Locales\pl\Pode.psd1
@{
    schemaValidationRequiresPowerShell610ExceptionMessage             = 'Walidacja schematu wymaga wersji PowerShell 6.1.0 lub nowszej.'
    customAccessPathOrScriptBlockRequiredExceptionMessage             = 'Ścieżka lub ScriptBlock są wymagane do pozyskiwania wartości dostępu niestandardowego.'
    operationIdMustBeUniqueForArrayExceptionMessage                   = 'OperationID: {0} musi być unikalny i nie może być zastosowany do tablicy.'
    endpointNotDefinedForRedirectingExceptionMessage                  = "Nie zdefiniowano punktu końcowego o nazwie '{0}' do przekierowania."
    filesHaveChangedMessage                                           = 'Następujące pliki zostały zmienione:'
    iisAspnetcoreTokenMissingExceptionMessage                         = 'Brakujący IIS ASPNETCORE_TOKEN.'
    minValueGreaterThanMaxExceptionMessage                            = 'Minimalna wartość dla {0} nie powinna być większa od maksymalnej wartości.'
    noLogicPassedForRouteExceptionMessage                             = 'Brak logiki przekazanej dla trasy: {0}'
    scriptPathDoesNotExistExceptionMessage                            = 'Ścieżka skryptu nie istnieje: {0}'
    mutexAlreadyExistsExceptionMessage                                = "Muteks o nazwie '{0}' już istnieje."
    listeningOnEndpointsMessage                                       = 'Nasłuchiwanie na następujących {0} punktach końcowych [{1} wątków]:'
    unsupportedFunctionInServerlessContextExceptionMessage            = 'Funkcja {0} nie jest obsługiwana w kontekście bezserwerowym.'
    expectedNoJwtSignatureSuppliedExceptionMessage                    = 'Oczekiwano, że nie zostanie dostarczony żaden podpis JWT.'
    secretAlreadyMountedExceptionMessage                              = "Tajemnica o nazwie '{0}' została już zamontowana."
    failedToAcquireLockExceptionMessage                               = 'Nie udało się uzyskać blokady na obiekcie.'
    noPathSuppliedForStaticRouteExceptionMessage                      = '[{0}]: Brak dostarczonej ścieżki dla trasy statycznej.'
    invalidHostnameSuppliedExceptionMessage                           = 'Podano nieprawidłową nazwę hosta: {0}'
    authMethodAlreadyDefinedExceptionMessage                          = 'Metoda uwierzytelniania już zdefiniowana: {0}'
    csrfCookieRequiresSecretExceptionMessage                          = "Podczas używania ciasteczek do CSRF, wymagany jest Sekret. Możesz dostarczyć Sekret lub ustawić globalny sekret dla ciasteczek - (Set-PodeCookieSecret '<value>' -Global)"
    nonEmptyScriptBlockRequiredForPageRouteExceptionMessage           = 'Aby utworzyć trasę strony, wymagany jest niepusty ScriptBlock.'
    noPropertiesMutuallyExclusiveExceptionMessage                     = "Parametr 'NoProperties' jest wzajemnie wykluczający się z 'Properties', 'MinProperties' i 'MaxProperties'."
    incompatiblePodeDllExceptionMessage                               = 'Istnieje niekompatybilna wersja Pode.DLL {0}. Wymagana wersja {1}. Otwórz nową sesję Powershell/pwsh i spróbuj ponownie.'
    accessMethodDoesNotExistExceptionMessage                          = 'Metoda dostępu nie istnieje: {0}.'
    scheduleAlreadyDefinedExceptionMessage                            = '[Harmonogram] {0}: Harmonogram już zdefiniowany.'
    secondsValueCannotBeZeroOrLessExceptionMessage                    = 'Wartość sekund nie może być 0 lub mniejsza dla {0}'
    pathToLoadNotFoundExceptionMessage                                = 'Ścieżka do załadowania {0} nie znaleziona: {1}'
    failedToImportModuleExceptionMessage                              = 'Nie udało się zaimportować modułu: {0}'
    endpointNotExistExceptionMessage                                  = "Punkt końcowy z protokołem '{0}' i adresem '{1}' lub adresem lokalnym '{2}' nie istnieje."
    terminatingMessage                                                = 'Kończenie...'
    noCommandsSuppliedToConvertToRoutesExceptionMessage               = 'Nie dostarczono żadnych poleceń do konwersji na trasy.'
    invalidTaskTypeExceptionMessage                                   = 'Typ zadania jest nieprawidłowy, oczekiwano [System.Threading.Tasks.Task] lub [hashtable]'
    alreadyConnectedToWebSocketExceptionMessage                       = "Już połączono z WebSocket o nazwie '{0}'"
    crlfMessageEndCheckOnlySupportedOnTcpEndpointsExceptionMessage    = 'Sprawdzanie końca wiadomości CRLF jest obsługiwane tylko na punktach końcowych TCP.'
    testPodeOAComponentSchemaNeedToBeEnabledExceptionMessage          = "'Test-PodeOAComponentSchema' musi być włączony przy użyciu 'Enable-PodeOpenApi -EnableSchemaValidation'"
    adModuleNotInstalledExceptionMessage                              = 'Moduł Active Directory nie jest zainstalowany.'
    cronExpressionInvalidExceptionMessage                             = 'Wyrażenie Cron powinno składać się tylko z 5 części: {0}'
    noSessionToSetOnResponseExceptionMessage                          = 'Brak dostępnej sesji do ustawienia odpowiedzi.'
    valueOutOfRangeExceptionMessage                                   = "Wartość '{0}' dla {1} jest nieprawidłowa, powinna być pomiędzy {2} a {3}"
    loggingMethodAlreadyDefinedExceptionMessage                       = 'Metoda logowania już zdefiniowana: {0}'
    noSecretForHmac256ExceptionMessage                                = 'Nie podano tajemnicy dla haszowania HMAC256.'
    eolPowerShellWarningMessage                                       = '[OSTRZEŻENIE] Pode {0} nie był testowany na PowerShell {1}, ponieważ jest to wersja EOL.'
    runspacePoolFailedToLoadExceptionMessage                          = '{0} Nie udało się załadować RunspacePool.'
    noEventRegisteredExceptionMessage                                 = 'Brak zarejestrowanego wydarzenia {0}: {1}'
    scheduleCannotHaveNegativeLimitExceptionMessage                   = '[Harmonogram] {0}: Nie może mieć ujemnego limitu.'
    openApiRequestStyleInvalidForParameterExceptionMessage            = 'Styl żądania OpenApi nie może być {0} dla parametru {1}.'
    openApiDocumentNotCompliantExceptionMessage                       = 'Dokument OpenAPI nie jest zgodny.'
    taskDoesNotExistExceptionMessage                                  = "Zadanie '{0}' nie istnieje."
    scopedVariableNotFoundExceptionMessage                            = 'Nie znaleziono zmiennej zakresu: {0}'
    sessionsRequiredForCsrfExceptionMessage                           = 'Sesje są wymagane do używania CSRF, chyba że chcesz używać ciasteczek.'
    nonEmptyScriptBlockRequiredForLoggingMethodExceptionMessage       = 'Metoda rejestrowania wymaga niepustego ScriptBlock.'
    credentialsPassedWildcardForHeadersLiteralExceptionMessage        = 'Gdy przekazywane są dane uwierzytelniające, symbol wieloznaczny * dla nagłówków będzie traktowany jako dosłowny ciąg znaków, a nie symbol wieloznaczny.'
    podeNotInitializedExceptionMessage                                = 'Pode nie został zainicjowany.'
    multipleEndpointsForGuiMessage                                    = 'Zdefiniowano wiele punktów końcowych, tylko pierwszy będzie używany dla GUI.'
    operationIdMustBeUniqueExceptionMessage                           = 'OperationID: {0} musi być unikalny.'
    invalidJsonJwtExceptionMessage                                    = 'Nieprawidłowa wartość JSON znaleziona w JWT'
    noAlgorithmInJwtHeaderExceptionMessage                            = 'Brak dostarczonego algorytmu w nagłówku JWT.'
    openApiVersionPropertyMandatoryExceptionMessage                   = 'Właściwość wersji OpenApi jest obowiązkowa.'
    limitValueCannotBeZeroOrLessExceptionMessage                      = 'Wartość limitu nie może być 0 lub mniejsza dla {0}'
    timerDoesNotExistExceptionMessage                                 = "Timer '{0}' nie istnieje."
    openApiGenerationDocumentErrorMessage                             = 'Błąd generowania dokumentu OpenAPI:'
    routeAlreadyContainsCustomAccessExceptionMessage                  = "Trasa '[{0}] {1}' już zawiera dostęp niestandardowy z nazwą '{2}'"
    maximumConcurrentWebSocketThreadsLessThanMinimumExceptionMessage  = 'Maksymalna liczba jednoczesnych wątków WebSocket nie może być mniejsza niż minimum {0}, ale otrzymano: {1}'
    middlewareAlreadyDefinedExceptionMessage                          = '[Middleware] {0}: Middleware już zdefiniowany.'
    invalidAtomCharacterExceptionMessage                              = 'Nieprawidłowy znak atomu: {0}'
    invalidCronAtomFormatExceptionMessage                             = 'Znaleziono nieprawidłowy format atomu cron: {0}'
    cacheStorageNotFoundForRetrieveExceptionMessage                   = "Nie znaleziono magazynu pamięci podręcznej o nazwie '{0}' podczas próby pobrania elementu z pamięci podręcznej '{1}'."
    headerMustHaveNameInEncodingContextExceptionMessage               = 'Nagłówek musi mieć nazwę, gdy jest używany w kontekście kodowania.'
    moduleDoesNotContainFunctionExceptionMessage                      = 'Moduł {0} nie zawiera funkcji {1} do konwersji na trasę.'
    pathToIconForGuiDoesNotExistExceptionMessage                      = 'Ścieżka do ikony dla GUI nie istnieje: {0}'
    noTitleSuppliedForPageExceptionMessage                            = 'Nie dostarczono tytułu dla strony {0}.'
    certificateSuppliedForNonHttpsWssEndpointExceptionMessage         = 'Certyfikat dostarczony dla punktu końcowego innego niż HTTPS/WSS.'
    cannotLockNullObjectExceptionMessage                              = 'Nie można zablokować pustego obiektu.'
    showPodeGuiOnlyAvailableOnWindowsExceptionMessage                 = 'Show-PodeGui jest obecnie dostępne tylko dla Windows PowerShell i PowerShell 7+ w Windows.'
    unlockSecretButNoScriptBlockExceptionMessage                      = 'Podano tajemnicę odblokowania dla niestandardowego typu skarbca, ale nie podano ScriptBlock odblokowania.'
    invalidIpAddressExceptionMessage                                  = 'Podany adres IP jest nieprawidłowy: {0}'
    maxDaysInvalidExceptionMessage                                    = 'MaxDays musi wynosić 0 lub więcej, ale otrzymano: {0}'
    noRemoveScriptBlockForVaultExceptionMessage                       = "Nie podano ScriptBlock dla usuwania tajemnic ze skarbca '{0}'"
    noSecretExpectedForNoSignatureExceptionMessage                    = 'Nie oczekiwano podania tajemnicy dla braku podpisu.'
    noCertificateFoundExceptionMessage                                = "Nie znaleziono certyfikatu w {0}{1} dla '{2}'"
    minValueInvalidExceptionMessage                                   = "Minimalna wartość '{0}' dla {1} jest nieprawidłowa, powinna być większa lub równa {2}"
    accessRequiresAuthenticationOnRoutesExceptionMessage              = 'Dostęp wymaga uwierzytelnienia na trasach.'
    noSecretForHmac384ExceptionMessage                                = 'Nie podano tajemnicy dla haszowania HMAC384.'
    windowsLocalAuthSupportIsForWindowsOnlyExceptionMessage           = 'Wsparcie lokalnego uwierzytelniania Windows jest tylko dla Windows.'
    definitionTagNotDefinedExceptionMessage                           = 'Etykieta definicji {0} nie jest zdefiniowana.'
    noComponentInDefinitionExceptionMessage                           = 'Brak komponentu typu {0} o nazwie {1} dostępnego w definicji {2}.'
    noSmtpHandlersDefinedExceptionMessage                             = 'Nie zdefiniowano żadnych obsługujących SMTP.'
    sessionMiddlewareAlreadyInitializedExceptionMessage               = 'Middleware sesji został już zainicjowany.'
    reusableComponentPathItemsNotAvailableInOpenApi30ExceptionMessage = "Funkcja wielokrotnego użytku 'pathItems' nie jest dostępna w OpenAPI v3.0."
    wildcardHeadersIncompatibleWithAutoHeadersExceptionMessage        = 'Symbol wieloznaczny * dla nagłówków jest niezgodny z przełącznikiem AutoHeaders.'
    noDataForFileUploadedExceptionMessage                             = "Brak danych dla pliku '{0}' przesłanego w żądaniu."
    sseOnlyConfiguredOnEventStreamAcceptHeaderExceptionMessage        = 'SSE można skonfigurować tylko na żądaniach z wartością nagłówka Accept równą text/event-stream.'
    noSessionAvailableToSaveExceptionMessage                          = 'Brak dostępnej sesji do zapisania.'
    pathParameterRequiresRequiredSwitchExceptionMessage               = "Jeśli lokalizacja parametru to 'Path', przełącznik 'Required' jest obowiązkowy."
    noOpenApiUrlSuppliedExceptionMessage                              = 'Nie dostarczono adresu URL OpenAPI dla {0}.'
    maximumConcurrentSchedulesInvalidExceptionMessage                 = 'Maksymalna liczba równoczesnych harmonogramów musi wynosić >=1, ale otrzymano: {0}'
    snapinsSupportedOnWindowsPowershellOnlyExceptionMessage           = 'Snapiny są obsługiwane tylko w Windows PowerShell.'
    eventViewerLoggingSupportedOnWindowsOnlyExceptionMessage          = 'Rejestrowanie w Podglądzie zdarzeń jest obsługiwane tylko w systemie Windows.'
    parametersMutuallyExclusiveExceptionMessage                       = "Parametry '{0}' i '{1}' są wzajemnie wykluczające się."
    pathItemsFeatureNotSupportedInOpenApi30ExceptionMessage           = 'Funkcja PathItems nie jest obsługiwana w OpenAPI v3.0.x'
    openApiParameterRequiresNameExceptionMessage                      = 'Parametr OpenApi wymaga podania nazwy.'
    maximumConcurrentTasksLessThanMinimumExceptionMessage             = 'Maksymalna liczba jednoczesnych zadań nie może być mniejsza niż minimum {0}, ale otrzymano: {1}'
    noSemaphoreFoundExceptionMessage                                  = "Nie znaleziono semaforu o nazwie '{0}'"
    singleValueForIntervalExceptionMessage                            = 'Możesz podać tylko jedną wartość {0} podczas korzystania z interwałów.'
    jwtNotYetValidExceptionMessage                                    = 'JWT jeszcze nie jest ważny.'
    verbAlreadyDefinedForUrlExceptionMessage                          = '[Czasownik] {0}: Już zdefiniowane dla {1}'
    noSecretNamedMountedExceptionMessage                              = "Nie zamontowano tajemnicy o nazwie '{0}'."
    moduleOrVersionNotFoundExceptionMessage                           = 'Nie znaleziono modułu lub wersji na {0}: {1}@{2}'
    noScriptBlockSuppliedExceptionMessage                             = 'Nie podano ScriptBlock.'
    noSecretVaultRegisteredExceptionMessage                           = "Nie zarejestrowano Skarbca Tajemnic o nazwie '{0}'."
    nameRequiredForEndpointIfRedirectToSuppliedExceptionMessage       = 'Nazwa jest wymagana dla punktu końcowego, jeśli podano parametr RedirectTo.'
    openApiLicenseObjectRequiresNameExceptionMessage                  = "Obiekt OpenAPI 'license' wymaga właściwości 'name'. Użyj parametru -LicenseName."
    sourcePathDoesNotExistForStaticRouteExceptionMessage              = '{0}: Dostarczona ścieżka źródłowa dla trasy statycznej nie istnieje: {1}'
    noNameForWebSocketDisconnectExceptionMessage                      = 'Nie podano nazwy dla rozłączenia WebSocket.'
    certificateExpiredExceptionMessage                                = "Certyfikat '{0}' wygasł: {1}"
    secretVaultUnlockExpiryDateInPastExceptionMessage                 = 'Data wygaśnięcia odblokowania Skarbca tajemnic jest w przeszłości (UTC): {0}'
    invalidWebExceptionTypeExceptionMessage                           = 'Wyjątek jest nieprawidłowego typu, powinien być WebException lub HttpRequestException, ale otrzymano: {0}'
    invalidSecretValueTypeExceptionMessage                            = 'Wartość tajemnicy jest nieprawidłowego typu. Oczekiwane typy: String, SecureString, HashTable, Byte[] lub PSCredential. Ale otrzymano: {0}'
    explicitTlsModeOnlySupportedOnSmtpsTcpsEndpointsExceptionMessage  = 'Tryb TLS Explicity jest obsługiwany tylko na punktach końcowych SMTPS i TCPS.'
    discriminatorMappingRequiresDiscriminatorPropertyExceptionMessage = "Parametr 'DiscriminatorMapping' może być używany tylko wtedy, gdy jest obecna właściwość 'DiscriminatorProperty'."
    scriptErrorExceptionMessage                                       = "Błąd '{0}' w skrypcie {1} {2} (linia {3}) znak {4} podczas wykonywania {5} na {6} obiekt '{7}' Klasa: {8} Klasa bazowa: {9}"
    cannotSupplyIntervalForQuarterExceptionMessage                    = 'Nie można dostarczyć wartości interwału dla każdego kwartału.'
    scheduleEndTimeMustBeInFutureExceptionMessage                     = '[Harmonogram] {0}: Wartość EndTime musi być w przyszłości.'
    invalidJwtSignatureSuppliedExceptionMessage                       = 'Dostarczono nieprawidłowy podpis JWT.'
    noSetScriptBlockForVaultExceptionMessage                          = "Nie podano ScriptBlock dla aktualizacji/tworzenia tajemnic w skarbcu '{0}'"
    accessMethodNotExistForMergingExceptionMessage                    = 'Metoda dostępu nie istnieje do scalania: {0}'
    defaultAuthNotInListExceptionMessage                              = "Domyślne uwierzytelnianie '{0}' nie znajduje się na dostarczonej liście uwierzytelniania."
    parameterHasNoNameExceptionMessage                                = "Parametr nie ma nazwy. Proszę nadać tej części nazwę za pomocą parametru 'Name'."
    methodPathAlreadyDefinedForUrlExceptionMessage                    = '[{0}] {1}: Już zdefiniowane dla {2}'
    fileWatcherAlreadyDefinedExceptionMessage                         = "Obserwator plików o nazwie '{0}' został już zdefiniowany."
    noServiceHandlersDefinedExceptionMessage                          = 'Nie zdefiniowano żadnych obsługujących usług.'
    secretRequiredForCustomSessionStorageExceptionMessage             = 'Podczas korzystania z niestandardowego przechowywania sesji wymagany jest sekret.'
    secretManagementModuleNotInstalledExceptionMessage                = 'Moduł Microsoft.PowerShell.SecretManagement nie jest zainstalowany.'
    noPathSuppliedForRouteExceptionMessage                            = 'Nie podano ścieżki dla trasy.'
    validationOfAnyOfSchemaNotSupportedExceptionMessage               = "Walidacja schematu, który zawiera 'anyof', nie jest obsługiwana."
    iisAuthSupportIsForWindowsOnlyExceptionMessage                    = 'Wsparcie uwierzytelniania IIS jest tylko dla Windows.'
    oauth2InnerSchemeInvalidExceptionMessage                          = 'OAuth2 InnerScheme może być tylko jednym z dwóch: Basic lub Form, ale otrzymano: {0}'
    noRoutePathSuppliedForPageExceptionMessage                        = 'Nie dostarczono ścieżki trasy dla strony {0}.'
    cacheStorageNotFoundForExistsExceptionMessage                     = "Nie znaleziono magazynu pamięci podręcznej o nazwie '{0}' podczas próby sprawdzenia, czy element w pamięci podręcznej '{1}' istnieje."
    handlerAlreadyDefinedExceptionMessage                             = '[{0}] {1}: Handler już zdefiniowany.'
    sessionsNotConfiguredExceptionMessage                             = 'Sesje nie zostały skonfigurowane.'
    propertiesTypeObjectAssociationExceptionMessage                   = 'Tylko właściwości typu Object mogą być powiązane z {0}.'
    sessionsRequiredForSessionPersistentAuthExceptionMessage          = 'Sesje są wymagane do używania trwałego uwierzytelniania sesji.'
    invalidPathWildcardOrDirectoryExceptionMessage                    = 'Podana ścieżka nie może być symbolem wieloznacznym ani katalogiem: {0}'
    accessMethodAlreadyDefinedExceptionMessage                        = 'Metoda dostępu już zdefiniowana: {0}'
    parametersValueOrExternalValueMandatoryExceptionMessage           = "Parametry 'Value' lub 'ExternalValue' są obowiązkowe."
    maximumConcurrentTasksInvalidExceptionMessage                     = 'Maksymalna liczba jednoczesnych zadań musi wynosić >=1, ale otrzymano: {0}'
    cannotCreatePropertyWithoutTypeExceptionMessage                   = 'Nie można utworzyć właściwości, ponieważ nie zdefiniowano typu.'
    authMethodNotExistForMergingExceptionMessage                      = 'Metoda uwierzytelniania nie istnieje dla scalania: {0}'
    maxValueInvalidExceptionMessage                                   = "Maksymalna wartość '{0}' dla {1} jest nieprawidłowa, powinna być mniejsza lub równa {2}"
    endpointAlreadyDefinedExceptionMessage                            = "Punkt końcowy o nazwie '{0}' został już zdefiniowany."
    eventAlreadyRegisteredExceptionMessage                            = 'Wydarzenie {0} już zarejestrowane: {1}'
    parameterNotSuppliedInRequestExceptionMessage                     = "Parametr o nazwie '{0}' nie został dostarczony w żądaniu lub nie ma dostępnych danych."
    cacheStorageNotFoundForSetExceptionMessage                        = "Nie znaleziono magazynu pamięci podręcznej o nazwie '{0}' podczas próby ustawienia elementu w pamięci podręcznej '{1}'."
    methodPathAlreadyDefinedExceptionMessage                          = '[{0}] {1}: Już zdefiniowane.'
    errorLoggingAlreadyEnabledExceptionMessage                        = 'Rejestrowanie błędów jest już włączone.'
    valueForUsingVariableNotFoundExceptionMessage                     = "Nie można znaleźć wartości dla '`$using:{0}'."
    rapidPdfDoesNotSupportOpenApi31ExceptionMessage                   = 'Narzędzie do dokumentów RapidPdf nie obsługuje OpenAPI 3.1'
    oauth2ClientSecretRequiredExceptionMessage                        = 'OAuth2 wymaga tajemnicy klienta, gdy nie używa się PKCE.'
    invalidBase64JwtExceptionMessage                                  = 'Nieprawidłowa wartość zakodowana w Base64 znaleziona w JWT'
    noSessionToCalculateDataHashExceptionMessage                      = 'Brak dostępnej sesji do obliczenia skrótu danych.'
    cacheStorageNotFoundForRemoveExceptionMessage                     = "Nie znaleziono magazynu pamięci podręcznej o nazwie '{0}' podczas próby usunięcia elementu z pamięci podręcznej '{1}'."
    csrfMiddlewareNotInitializedExceptionMessage                      = 'Middleware CSRF nie został zainicjowany.'
    infoTitleMandatoryMessage                                         = 'info.title jest obowiązkowe.'
    typeCanOnlyBeAssociatedWithObjectExceptionMessage                 = 'Typ {0} może być powiązany tylko z obiektem.'
    userFileDoesNotExistExceptionMessage                              = 'Plik użytkownika nie istnieje: {0}'
    routeParameterNeedsValidScriptblockExceptionMessage               = 'Parametr trasy wymaga prawidłowego, niepustego ScriptBlock.'
    nextTriggerCalculationErrorExceptionMessage                       = 'Wygląda na to, że coś poszło nie tak przy próbie obliczenia następnej daty i godziny wyzwalacza: {0}'
    cannotLockValueTypeExceptionMessage                               = 'Nie można zablokować [ValueType].'
    failedToCreateOpenSslCertExceptionMessage                         = 'Nie udało się utworzyć certyfikatu OpenSSL: {0}'
    jwtExpiredExceptionMessage                                        = 'JWT wygasł.'
    openingGuiMessage                                                 = 'Otwieranie GUI.'
    multiTypePropertiesRequireOpenApi31ExceptionMessage               = 'Właściwości wielotypowe wymagają wersji OpenApi 3.1 lub wyższej.'
    noNameForWebSocketRemoveExceptionMessage                          = 'Nie podano nazwy dla usunięcia WebSocket.'
    maxSizeInvalidExceptionMessage                                    = 'MaxSize musi wynosić 0 lub więcej, ale otrzymano: {0}'
    iisShutdownMessage                                                = '(Zamykanie IIS)'
    cannotUnlockValueTypeExceptionMessage                             = 'Nie można odblokować [ValueType].'
    noJwtSignatureForAlgorithmExceptionMessage                        = 'Nie dostarczono podpisu JWT dla {0}.'
    maximumConcurrentWebSocketThreadsInvalidExceptionMessage          = 'Maksymalna liczba jednoczesnych wątków WebSocket musi wynosić >=1, ale otrzymano: {0}'
    acknowledgeMessageOnlySupportedOnSmtpTcpEndpointsExceptionMessage = 'Komunikat potwierdzenia jest obsługiwany tylko na punktach końcowych SMTP i TCP.'
    failedToConnectToUrlExceptionMessage                              = 'Nie udało się połączyć z URL: {0}'
    failedToAcquireMutexOwnershipExceptionMessage                     = 'Nie udało się przejąć własności muteksu. Nazwa muteksu: {0}'
    sessionsRequiredForOAuth2WithPKCEExceptionMessage                 = 'Sesje są wymagane do używania OAuth2 z PKCE'
    failedToConnectToWebSocketExceptionMessage                        = 'Nie udało się połączyć z WebSocket: {0}'
    unsupportedObjectExceptionMessage                                 = 'Obiekt nieobsługiwany'
    failedToParseAddressExceptionMessage                              = "Nie udało się przeanalizować '{0}' jako poprawnego adresu IP/Host:Port"
    mustBeRunningWithAdminPrivilegesExceptionMessage                  = 'Musisz mieć uprawnienia administratora, aby nasłuchiwać na adresach innych niż localhost.'
    specificationMessage                                              = 'Specyfikacja'
    cacheStorageNotFoundForClearExceptionMessage                      = "Nie znaleziono magazynu pamięci podręcznej o nazwie '{0}' podczas próby wyczyszczenia pamięci podręcznej."
    restartingServerMessage                                           = 'Restartowanie serwera...'
    cannotSupplyIntervalWhenEveryIsNoneExceptionMessage               = "Nie można dostarczyć interwału, gdy parametr 'Every' jest ustawiony na None."
    unsupportedJwtAlgorithmExceptionMessage                           = 'Algorytm JWT nie jest obecnie obsługiwany: {0}'
    websocketsNotConfiguredForSignalMessagesExceptionMessage          = 'WebSockets nie zostały skonfigurowane do wysyłania wiadomości sygnałowych.'
    invalidLogicTypeInHashtableMiddlewareExceptionMessage             = 'Dostarczone Middleware typu Hashtable ma nieprawidłowy typ logiki. Oczekiwano ScriptBlock, ale otrzymano: {0}'
    maximumConcurrentSchedulesLessThanMinimumExceptionMessage         = 'Maksymalna liczba równoczesnych harmonogramów nie może być mniejsza niż minimalna liczba {0}, ale otrzymano: {1}'
    failedToAcquireSemaphoreOwnershipExceptionMessage                 = 'Nie udało się przejąć własności semaforu. Nazwa semaforu: {0}'
    propertiesParameterWithoutNameExceptionMessage                    = 'Parametry Properties nie mogą być używane, jeśli właściwość nie ma nazwy.'
    customSessionStorageMethodNotImplementedExceptionMessage          = "Niestandardowe przechowywanie sesji nie implementuje wymaganego ''{0}()'' sposobu."
    authenticationMethodDoesNotExistExceptionMessage                  = 'Metoda uwierzytelniania nie istnieje: {0}'
    webhooksFeatureNotSupportedInOpenApi30ExceptionMessage            = 'Funkcja Webhooks nie jest obsługiwana w OpenAPI v3.0.x'
    invalidContentTypeForSchemaExceptionMessage                       = "Nieprawidłowy 'content-type' znaleziony w schemacie: {0}"
    noUnlockScriptBlockForVaultExceptionMessage                       = "Nie podano ScriptBlock odblokowania dla odblokowania skarbca '{0}'"
    definitionTagMessage                                              = 'Definicja {0}:'
    failedToOpenRunspacePoolExceptionMessage                          = 'Nie udało się otworzyć RunspacePool: {0}'
    failedToCloseRunspacePoolExceptionMessage                         = 'Nie udało się zamknąć RunspacePool: {0}'
    verbNoLogicPassedExceptionMessage                                 = '[Czasownik] {0}: Nie przekazano logiki'
    noMutexFoundExceptionMessage                                      = "Nie znaleziono muteksu o nazwie '{0}'."
    documentationMessage                                              = 'Dokumentacja'
    timerAlreadyDefinedExceptionMessage                               = '[Timer] {0}: Timer już zdefiniowany.'
    invalidPortExceptionMessage                                       = 'Port nie może być ujemny: {0}'
    viewsFolderNameAlreadyExistsExceptionMessage                      = 'Nazwa folderu Widoków już istnieje: {0}'
    noNameForWebSocketResetExceptionMessage                           = 'Nie podano nazwy dla resetowania WebSocket.'
    mergeDefaultAuthNotInListExceptionMessage                         = "Uwierzytelnianie MergeDefault '{0}' nie znajduje się na dostarczonej liście uwierzytelniania."
    descriptionRequiredExceptionMessage                               = 'Wymagany jest opis dla ścieżki:{0} Odpowiedź:{1}'
    pageNameShouldBeAlphaNumericExceptionMessage                      = 'Nazwa strony powinna być poprawną wartością alfanumeryczną: {0}'
    defaultValueNotBooleanOrEnumExceptionMessage                      = 'Wartość domyślna nie jest typu boolean i nie należy do enum.'
    openApiComponentSchemaDoesNotExistExceptionMessage                = 'Schemat komponentu OpenApi {0} nie istnieje.'
    timerParameterMustBeGreaterThanZeroExceptionMessage               = '[Timer] {0}: {1} musi być większy od 0.'
    taskTimedOutExceptionMessage                                      = 'Zadanie przekroczyło limit czasu po {0}ms.'
    scheduleStartTimeAfterEndTimeExceptionMessage                     = "[Harmonogram] {0}: Nie może mieć 'StartTime' po 'EndTime'."
    infoVersionMandatoryMessage                                       = 'info.version jest obowiązkowe.'
    cannotUnlockNullObjectExceptionMessage                            = 'Nie można odblokować pustego obiektu.'
    nonEmptyScriptBlockRequiredForCustomAuthExceptionMessage          = 'Dla niestandardowego schematu uwierzytelniania wymagany jest niepusty ScriptBlock.'
    nonEmptyScriptBlockRequiredForAuthMethodExceptionMessage          = 'Wymagany jest niepusty ScriptBlock dla metody uwierzytelniania.'
    validationOfOneOfSchemaNotSupportedExceptionMessage               = "Walidacja schematu, który zawiera 'oneof', nie jest obsługiwana."
    routeParameterCannotBeNullExceptionMessage                        = "Parametr 'Route' nie może być pusty."
    cacheStorageAlreadyExistsExceptionMessage                         = "Magazyn pamięci podręcznej o nazwie '{0}' już istnieje."
    loggingMethodRequiresValidScriptBlockExceptionMessage             = "Dostarczona metoda wyjściowa dla metody logowania '{0}' wymaga poprawnego ScriptBlock."
    scopedVariableAlreadyDefinedExceptionMessage                      = 'Zmienna z zakresem już zdefiniowana: {0}'
    oauth2RequiresAuthorizeUrlExceptionMessage                        = 'OAuth2 wymaga podania URL autoryzacji'
    pathNotExistExceptionMessage                                      = 'Ścieżka nie istnieje: {0}'
    noDomainServerNameForWindowsAdAuthExceptionMessage                = 'Nie podano nazwy serwera domeny dla uwierzytelniania Windows AD'
    suppliedDateAfterScheduleEndTimeExceptionMessage                  = 'Podana data jest późniejsza niż czas zakończenia harmonogramu o {0}'
    wildcardMethodsIncompatibleWithAutoMethodsExceptionMessage        = 'Symbol wieloznaczny * dla metod jest niezgodny z przełącznikiem AutoMethods.'
    cannotSupplyIntervalForYearExceptionMessage                       = 'Nie można dostarczyć wartości interwału dla każdego roku.'
    missingComponentsMessage                                          = 'Brakujące komponenty'
    invalidStrictTransportSecurityDurationExceptionMessage            = 'Nieprawidłowy czas trwania Strict-Transport-Security: {0}. Powinien być większy niż 0.'
    noSecretForHmac512ExceptionMessage                                = 'Nie podano tajemnicy dla haszowania HMAC512.'
    daysInMonthExceededExceptionMessage                               = '{0} ma tylko {1} dni, ale podano {2}.'
    nonEmptyScriptBlockRequiredForCustomLoggingExceptionMessage       = 'Metoda niestandardowego rejestrowania wymaga niepustego ScriptBlock.'
    encodingAttributeOnlyAppliesToMultipartExceptionMessage           = 'Atrybut kodowania dotyczy tylko ciał żądania typu multipart i application/x-www-form-urlencoded.'
    suppliedDateBeforeScheduleStartTimeExceptionMessage               = 'Podana data jest wcześniejsza niż czas rozpoczęcia harmonogramu o {0}'
    unlockSecretRequiredExceptionMessage                              = "Właściwość 'UnlockSecret' jest wymagana przy używaniu Microsoft.PowerShell.SecretStore"
    noLogicPassedForMethodRouteExceptionMessage                       = '[{0}] {1}: Brak logiki przekazanej.'
    bodyParserAlreadyDefinedForContentTypeExceptionMessage            = 'Parser treści dla typu zawartości {0} jest już zdefiniowany.'
    invalidJwtSuppliedExceptionMessage                                = 'Dostarczono nieprawidłowy JWT.'
    sessionsRequiredForFlashMessagesExceptionMessage                  = 'Sesje są wymagane do używania wiadomości Flash.'
    semaphoreAlreadyExistsExceptionMessage                            = "Semafor o nazwie '{0}' już istnieje."
    invalidJwtHeaderAlgorithmSuppliedExceptionMessage                 = 'Dostarczono nieprawidłowy algorytm nagłówka JWT.'
    oauth2ProviderDoesNotSupportPasswordGrantTypeExceptionMessage     = "Dostawca OAuth2 nie obsługuje typu 'password' wymaganego przez InnerScheme."
    invalidAliasFoundExceptionMessage                                 = 'Znaleziono nieprawidłowy alias {0}: {1}'
    scheduleDoesNotExistExceptionMessage                              = "Harmonogram '{0}' nie istnieje."
    accessMethodNotExistExceptionMessage                              = 'Metoda dostępu nie istnieje: {0}'
    oauth2ProviderDoesNotSupportCodeResponseTypeExceptionMessage      = "Dostawca OAuth2 nie obsługuje typu odpowiedzi 'code'."
    untestedPowerShellVersionWarningMessage                           = '[OSTRZEŻENIE] Pode {0} nie był testowany na PowerShell {1}, ponieważ nie był dostępny, gdy Pode został wydany.'
    secretVaultAlreadyRegisteredAutoImportExceptionMessage            = "Skarbiec z nazwą '{0}' został już zarejestrowany podczas automatycznego importowania skarbców."
    schemeRequiresValidScriptBlockExceptionMessage                    = "Dostarczony schemat dla walidatora uwierzytelniania '{0}' wymaga ważnego ScriptBlock."
    serverLoopingMessage                                              = 'Pętla serwera co {0} sekund'
    certificateThumbprintsNameSupportedOnWindowsExceptionMessage      = 'Odciski palców/nazwa certyfikatu są obsługiwane tylko w systemie Windows.'
    sseConnectionNameRequiredExceptionMessage                         = "Wymagana jest nazwa połączenia SSE, z -Name lub `$WebEvent.Sse.Name"
    invalidMiddlewareTypeExceptionMessage                             = 'Jeden z dostarczonych Middleware jest nieprawidłowego typu. Oczekiwano ScriptBlock lub Hashtable, ale otrzymano: {0}'
    noSecretForJwtSignatureExceptionMessage                           = 'Nie podano tajemnicy dla podpisu JWT.'
    modulePathDoesNotExistExceptionMessage                            = 'Ścieżka modułu nie istnieje: {0}'
    taskAlreadyDefinedExceptionMessage                                = '[Zadanie] {0}: Zadanie już zdefiniowane.'
    verbAlreadyDefinedExceptionMessage                                = '[Czasownik] {0}: Już zdefiniowane'
    clientCertificatesOnlySupportedOnHttpsEndpointsExceptionMessage   = 'Certyfikaty klienta są obsługiwane tylko na punktach końcowych HTTPS.'
    endpointNameNotExistExceptionMessage                              = "Punkt końcowy o nazwie '{0}' nie istnieje."
    middlewareNoLogicSuppliedExceptionMessage                         = '[Middleware]: Nie dostarczono logiki w ScriptBlock.'
    scriptBlockRequiredForMergingUsersExceptionMessage                = 'Wymagany jest ScriptBlock do scalania wielu uwierzytelnionych użytkowników w jeden obiekt, gdy opcja Valid to All.'
    secretVaultAlreadyRegisteredExceptionMessage                      = "Skarbiec tajemnic o nazwie '{0}' został już zarejestrowany{1}."
    deprecatedTitleVersionDescriptionWarningMessage                   = "OSTRZEŻENIE: Tytuł, Wersja i Opis w 'Enable-PodeOpenApi' są przestarzałe. Proszę użyć 'Add-PodeOAInfo' zamiast tego."
    undefinedOpenApiReferencesMessage                                 = 'Niezdefiniowane odwołania OpenAPI:'
    doneMessage                                                       = 'Gotowe'
    swaggerEditorDoesNotSupportOpenApi31ExceptionMessage              = 'Ta wersja Swagger-Editor nie obsługuje OpenAPI 3.1'
    durationMustBeZeroOrGreaterExceptionMessage                       = 'Czas trwania musi wynosić 0 lub więcej, ale otrzymano: {0}s'
    viewsPathDoesNotExistExceptionMessage                             = 'Ścieżka do Widoków nie istnieje: {0}'
    discriminatorIncompatibleWithAllOfExceptionMessage                = "Parametr 'Discriminator' jest niezgodny z 'allOf'."
    noNameForWebSocketSendMessageExceptionMessage                     = 'Nie podano nazwy dla wysłania wiadomości do WebSocket.'
    hashtableMiddlewareNoLogicExceptionMessage                        = 'Dostarczone Middleware typu Hashtable nie ma zdefiniowanej logiki.'
    openApiInfoMessage                                                = 'Informacje OpenAPI:'
    invalidSchemeForAuthValidatorExceptionMessage                     = "Dostarczony schemat '{0}' dla walidatora uwierzytelniania '{1}' wymaga ważnego ScriptBlock."
    sseFailedToBroadcastExceptionMessage                              = 'SSE nie udało się przesłać z powodu zdefiniowanego poziomu przesyłania SSE dla {0}: {1}'
    adModuleWindowsOnlyExceptionMessage                               = 'Moduł Active Directory jest dostępny tylko w systemie Windows.'
    requestLoggingAlreadyEnabledExceptionMessage                      = 'Rejestrowanie żądań jest już włączone.'
    invalidAccessControlMaxAgeDurationExceptionMessage                = 'Podano nieprawidłowy czas trwania Access-Control-Max-Age: {0}. Powinien być większy niż 0.'
    openApiDefinitionAlreadyExistsExceptionMessage                    = 'Definicja OpenAPI o nazwie {0} już istnieje.'
    renamePodeOADefinitionTagExceptionMessage                         = "Rename-PodeOADefinitionTag nie może być używany wewnątrz 'ScriptBlock' Select-PodeOADefinition."
    taskProcessDoesNotExistExceptionMessage                           = "Proces zadania '{0}' nie istnieje."
    scheduleProcessDoesNotExistExceptionMessage                       = "Proces harmonogramu '{0}' nie istnieje."
    definitionTagChangeNotAllowedExceptionMessage                     = 'Tag definicji dla Route nie może zostać zmieniony.'
    getRequestBodyNotAllowedExceptionMessage                          = 'Operacje {0} nie mogą mieć treści żądania.'
    fnDoesNotAcceptArrayAsPipelineInputExceptionMessage               = "Funkcja '{0}' nie akceptuje tablicy jako wejścia potoku."
    unsupportedStreamCompressionEncodingExceptionMessage              = 'Kodowanie kompresji strumienia nie jest obsługiwane: {0}'
}
src\Locales\pt\Pode.psd1
@{
    schemaValidationRequiresPowerShell610ExceptionMessage             = 'A validação do esquema requer a versão 6.1.0 ou superior do PowerShell.'
    customAccessPathOrScriptBlockRequiredExceptionMessage             = 'É necessário um Caminho ou ScriptBlock para obter os valores de acesso personalizados.'
    operationIdMustBeUniqueForArrayExceptionMessage                   = 'OperationID: {0} deve ser único e não pode ser aplicado a uma matriz.'
    endpointNotDefinedForRedirectingExceptionMessage                  = "Não foi definido um ponto de extremidade chamado '{0}' para redirecionamento."
    filesHaveChangedMessage                                           = 'Os seguintes arquivos foram alterados:'
    iisAspnetcoreTokenMissingExceptionMessage                         = 'IIS ASPNETCORE_TOKEN está ausente.'
    minValueGreaterThanMaxExceptionMessage                            = 'O valor mínimo para {0} não deve ser maior que o valor máximo.'
    noLogicPassedForRouteExceptionMessage                             = 'Nenhuma lógica passada para a Rota: {0}'
    scriptPathDoesNotExistExceptionMessage                            = 'O caminho do script não existe: {0}'
    mutexAlreadyExistsExceptionMessage                                = 'Já existe um mutex com o seguinte nome: {0}'
    listeningOnEndpointsMessage                                       = 'Ouvindo nos seguintes {0} endpoint(s) [{1} thread(s)]:'
    unsupportedFunctionInServerlessContextExceptionMessage            = 'A função {0} não é suportada em um contexto serverless.'
    expectedNoJwtSignatureSuppliedExceptionMessage                    = 'Esperava-se que nenhuma assinatura JWT fosse fornecida.'
    secretAlreadyMountedExceptionMessage                              = "Um Segredo com o nome '{0}' já foi montado."
    failedToAcquireLockExceptionMessage                               = 'Falha ao adquirir um bloqueio no objeto.'
    noPathSuppliedForStaticRouteExceptionMessage                      = '[{0}]: Nenhum caminho fornecido para a Rota Estática.'
    invalidHostnameSuppliedExceptionMessage                           = 'Nome de host fornecido inválido: {0}'
    authMethodAlreadyDefinedExceptionMessage                          = 'Método de autenticação já definido: {0}'
    csrfCookieRequiresSecretExceptionMessage                          = "Ao usar cookies para CSRF, é necessário um Segredo. Você pode fornecer um Segredo ou definir o segredo global do Cookie - (Set-PodeCookieSecret '<value>' -Global)"
    nonEmptyScriptBlockRequiredForPageRouteExceptionMessage           = 'Um ScriptBlock não vazio é necessário para criar uma Rota de Página.'
    noPropertiesMutuallyExclusiveExceptionMessage                     = "O parâmetro 'NoProperties' é mutuamente exclusivo com 'Properties', 'MinProperties' e 'MaxProperties'."
    incompatiblePodeDllExceptionMessage                               = 'Uma versão incompatível existente do Pode.DLL {0} está carregada. É necessária a versão {1}. Abra uma nova sessão do Powershell/pwsh e tente novamente.'
    accessMethodDoesNotExistExceptionMessage                          = 'O método de acesso não existe: {0}.'
    scheduleAlreadyDefinedExceptionMessage                            = '[Cronograma] {0}: Cronograma já definida.'
    secondsValueCannotBeZeroOrLessExceptionMessage                    = 'O valor dos segundos não pode ser 0 ou inferior para {0}'
    pathToLoadNotFoundExceptionMessage                                = 'Caminho para carregar {0} não encontrado: {1}'
    failedToImportModuleExceptionMessage                              = 'Falha ao importar módulo: {0}'
    endpointNotExistExceptionMessage                                  = "O ponto de extremidade com o protocolo '{0}' e endereço '{1}' ou endereço local '{2}' não existe."
    terminatingMessage                                                = 'Terminando...'
    noCommandsSuppliedToConvertToRoutesExceptionMessage               = 'Nenhum comando fornecido para converter em Rotas.'
    invalidTaskTypeExceptionMessage                                   = 'O tipo de tarefa é inválido, esperado [System.Threading.Tasks.Task] ou [hashtable].'
    alreadyConnectedToWebSocketExceptionMessage                       = "Já conectado ao websocket com o nome '{0}'"
    crlfMessageEndCheckOnlySupportedOnTcpEndpointsExceptionMessage    = 'A verificação de fim de mensagem CRLF é suportada apenas em endpoints TCP.'
    testPodeOAComponentSchemaNeedToBeEnabledExceptionMessage          = "'Test-PodeOAComponentSchema' precisa ser habilitado usando 'Enable-PodeOpenApi -EnableSchemaValidation'"
    adModuleNotInstalledExceptionMessage                              = 'O módulo Active Directory não está instalado.'
    cronExpressionInvalidExceptionMessage                             = 'A expressão Cron deve consistir apenas em 5 partes: {0}'
    noSessionToSetOnResponseExceptionMessage                          = 'Não há sessão disponível para definir na resposta.'
    valueOutOfRangeExceptionMessage                                   = "O valor '{0}' para {1} é inválido, deve estar entre {2} e {3}"
    loggingMethodAlreadyDefinedExceptionMessage                       = 'Método de registro já definido: {0}'
    noSecretForHmac256ExceptionMessage                                = 'Nenhum segredo fornecido para o hash HMAC256.'
    eolPowerShellWarningMessage                                       = '[AVISO] Pode {0} não foi testado no PowerShell {1}, pois está em EOL.'
    runspacePoolFailedToLoadExceptionMessage                          = '{0} Falha ao carregar RunspacePool.'
    noEventRegisteredExceptionMessage                                 = 'Nenhum evento {0} registrado: {1}'
    scheduleCannotHaveNegativeLimitExceptionMessage                   = '[Cronograma] {0}: Não pode ter um limite negativo.'
    openApiRequestStyleInvalidForParameterExceptionMessage            = 'O estilo da solicitação OpenApi não pode ser {0} para um parâmetro {1}.'
    openApiDocumentNotCompliantExceptionMessage                       = 'O documento OpenAPI não está em conformidade.'
    taskDoesNotExistExceptionMessage                                  = "A tarefa '{0}' não existe."
    scopedVariableNotFoundExceptionMessage                            = 'Variável de escopo não encontrada: {0}'
    sessionsRequiredForCsrfExceptionMessage                           = 'Sessões são necessárias para usar CSRF, a menos que você queira usar cookies.'
    nonEmptyScriptBlockRequiredForLoggingMethodExceptionMessage       = 'Um ScriptBlock não vazio é necessário para o método de registro.'
    credentialsPassedWildcardForHeadersLiteralExceptionMessage        = 'Quando as Credenciais são passadas, o caractere curinga * para os Cabeçalhos será interpretado como uma string literal e não como um caractere curinga.'
    podeNotInitializedExceptionMessage                                = 'Pode não foi inicializado.'
    multipleEndpointsForGuiMessage                                    = 'Múltiplos endpoints definidos, apenas o primeiro será usado para a GUI.'
    operationIdMustBeUniqueExceptionMessage                           = 'OperationID: {0} deve ser único.'
    invalidJsonJwtExceptionMessage                                    = 'Valor JSON inválido encontrado no JWT'
    noAlgorithmInJwtHeaderExceptionMessage                            = 'Nenhum algoritmo fornecido no Cabeçalho JWT.'
    openApiVersionPropertyMandatoryExceptionMessage                   = 'A propriedade da versão do OpenApi é obrigatória.'
    limitValueCannotBeZeroOrLessExceptionMessage                      = 'O valor limite não pode ser 0 ou inferior para {0}'
    timerDoesNotExistExceptionMessage                                 = "O temporizador '{0}' não existe."
    openApiGenerationDocumentErrorMessage                             = 'Erro no documento de geração do OpenAPI:'
    routeAlreadyContainsCustomAccessExceptionMessage                  = "A rota '[{0}] {1}' já contém Acesso Personalizado com o nome '{2}'"
    maximumConcurrentWebSocketThreadsLessThanMinimumExceptionMessage  = 'O número máximo de threads concorrentes do WebSocket não pode ser menor que o mínimo de {0}, mas foi obtido: {1}'
    middlewareAlreadyDefinedExceptionMessage                          = '[Middleware] {0}: Middleware já definido.'
    invalidAtomCharacterExceptionMessage                              = 'Caractere atômico inválido: {0}'
    invalidCronAtomFormatExceptionMessage                             = 'Formato de átomo cron inválido encontrado: {0}'
    cacheStorageNotFoundForRetrieveExceptionMessage                   = "Armazenamento em cache com o nome '{0}' não encontrado ao tentar recuperar o item em cache '{1}'."
    headerMustHaveNameInEncodingContextExceptionMessage               = 'O cabeçalho deve ter um nome quando usado em um contexto de codificação.'
    moduleDoesNotContainFunctionExceptionMessage                      = 'O módulo {0} não contém a função {1} para converter em uma Rota.'
    pathToIconForGuiDoesNotExistExceptionMessage                      = 'O caminho para o ícone da interface gráfica não existe: {0}'
    noTitleSuppliedForPageExceptionMessage                            = 'Nenhum título fornecido para a página {0}.'
    certificateSuppliedForNonHttpsWssEndpointExceptionMessage         = 'Certificado fornecido para endpoint que não é HTTPS/WSS.'
    cannotLockNullObjectExceptionMessage                              = 'Não é possível bloquear um objeto nulo.'
    showPodeGuiOnlyAvailableOnWindowsExceptionMessage                 = 'Show-PodeGui está atualmente disponível apenas para Windows PowerShell e PowerShell 7+ no Windows.'
    unlockSecretButNoScriptBlockExceptionMessage                      = 'Segredo de desbloqueio fornecido para tipo de Cofre Secreto personalizado, mas nenhum ScriptBlock de desbloqueio fornecido.'
    invalidIpAddressExceptionMessage                                  = 'O endereço IP fornecido é inválido: {0}'
    maxDaysInvalidExceptionMessage                                    = 'MaxDays deve ser igual ou maior que 0, mas foi obtido: {0}'
    noRemoveScriptBlockForVaultExceptionMessage                       = "Nenhum ScriptBlock fornecido para remover segredos do cofre '{0}'"
    noSecretExpectedForNoSignatureExceptionMessage                    = 'Não era esperado nenhum segredo para nenhuma assinatura.'
    noCertificateFoundExceptionMessage                                = "Nenhum certificado encontrado em {0}{1} para '{2}'"
    minValueInvalidExceptionMessage                                   = "O valor mínimo '{0}' para {1} é inválido, deve ser maior ou igual a {2}"
    accessRequiresAuthenticationOnRoutesExceptionMessage              = 'O acesso requer autenticação nas rotas.'
    noSecretForHmac384ExceptionMessage                                = 'Nenhum segredo fornecido para o hash HMAC384.'
    windowsLocalAuthSupportIsForWindowsOnlyExceptionMessage           = 'O suporte à Autenticação Local do Windows é apenas para Windows.'
    definitionTagNotDefinedExceptionMessage                           = 'A tag de definição {0} não existe.'
    noComponentInDefinitionExceptionMessage                           = 'Nenhum componente do tipo {0} chamado {1} está disponível na definição {2}.'
    noSmtpHandlersDefinedExceptionMessage                             = 'Nenhum manipulador SMTP definido.'
    sessionMiddlewareAlreadyInitializedExceptionMessage               = 'O Middleware de Sessão já foi inicializado.'
    reusableComponentPathItemsNotAvailableInOpenApi30ExceptionMessage = "O recurso de componente reutilizável 'pathItems' não está disponível no OpenAPI v3.0."
    wildcardHeadersIncompatibleWithAutoHeadersExceptionMessage        = 'O caractere curinga * para os Cabeçalhos é incompatível com a chave AutoHeaders.'
    noDataForFileUploadedExceptionMessage                             = "Nenhum dado para o arquivo '{0}' foi enviado na solicitação."
    sseOnlyConfiguredOnEventStreamAcceptHeaderExceptionMessage        = 'SSE só pode ser configurado em solicitações com um valor de cabeçalho Accept de text/event-stream.'
    noSessionAvailableToSaveExceptionMessage                          = 'Não há sessão disponível para salvar.'
    pathParameterRequiresRequiredSwitchExceptionMessage               = "Se a localização do parâmetro for 'Path', o parâmetro de switch 'Required' é obrigatório."
    noOpenApiUrlSuppliedExceptionMessage                              = 'Nenhuma URL do OpenAPI fornecida para {0}.'
    maximumConcurrentSchedulesInvalidExceptionMessage                 = 'As cronogramas simultâneas máximas devem ser >=1, mas obtidas: {0}'
    snapinsSupportedOnWindowsPowershellOnlyExceptionMessage           = 'Os Snapins são suportados apenas no Windows PowerShell.'
    eventViewerLoggingSupportedOnWindowsOnlyExceptionMessage          = 'O registro no Visualizador de Eventos é suportado apenas no Windows.'
    parametersMutuallyExclusiveExceptionMessage                       = "Os parâmetros '{0}' e '{1}' são mutuamente exclusivos."
    pathItemsFeatureNotSupportedInOpenApi30ExceptionMessage           = 'O recurso PathItems não é suportado no OpenAPI v3.0.x'
    openApiParameterRequiresNameExceptionMessage                      = 'O parâmetro OpenApi requer um nome especificado.'
    maximumConcurrentTasksLessThanMinimumExceptionMessage             = 'O número máximo de tarefas concorrentes não pode ser menor que o mínimo de {0}, mas foi obtido: {1}'
    noSemaphoreFoundExceptionMessage                                  = "Nenhum semáforo encontrado chamado '{0}'"
    singleValueForIntervalExceptionMessage                            = 'Você pode fornecer apenas um único valor {0} ao usar intervalos.'
    jwtNotYetValidExceptionMessage                                    = 'O JWT ainda não é válido para uso.'
    verbAlreadyDefinedForUrlExceptionMessage                          = '[Verbo] {0}: Já definido para {1}'
    noSecretNamedMountedExceptionMessage                              = "Nenhum Segredo com o nome '{0}' foi montado."
    moduleOrVersionNotFoundExceptionMessage                           = 'Módulo ou versão não encontrada em {0}: {1}@{2}'
    noScriptBlockSuppliedExceptionMessage                             = 'Nenhum ScriptBlock fornecido.'
    noSecretVaultRegisteredExceptionMessage                           = "Nenhum Cofre de Segredos com o nome '{0}' foi registrado."
    nameRequiredForEndpointIfRedirectToSuppliedExceptionMessage       = 'Um nome é necessário para o endpoint se o parâmetro RedirectTo for fornecido.'
    openApiLicenseObjectRequiresNameExceptionMessage                  = "O objeto OpenAPI 'license' requer a propriedade 'name'. Use o parâmetro -LicenseName."
    sourcePathDoesNotExistForStaticRouteExceptionMessage              = '{0}: O caminho de origem fornecido para a Rota Estática não existe: {1}'
    noNameForWebSocketDisconnectExceptionMessage                      = 'Nenhum nome fornecido para desconectar do WebSocket.'
    certificateExpiredExceptionMessage                                = "O certificado '{0}' expirou: {1}"
    secretVaultUnlockExpiryDateInPastExceptionMessage                 = 'A data de expiração de desbloqueio do Cofre de Segredos está no passado (UTC): {0}'
    invalidWebExceptionTypeExceptionMessage                           = 'A exceção é de um tipo inválido, deve ser WebException ou HttpRequestException, mas foi obtido: {0}'
    invalidSecretValueTypeExceptionMessage                            = 'O valor do segredo é de um tipo inválido. Tipos esperados: String, SecureString, HashTable, Byte[] ou PSCredential. Mas obtido: {0}'
    explicitTlsModeOnlySupportedOnSmtpsTcpsEndpointsExceptionMessage  = 'O modo TLS explícito é suportado apenas em endpoints SMTPS e TCPS.'
    discriminatorMappingRequiresDiscriminatorPropertyExceptionMessage = "O parâmetro 'DiscriminatorMapping' só pode ser usado quando 'DiscriminatorProperty' está presente."
    scriptErrorExceptionMessage                                       = "Erro '{0}' no script {1} {2} (linha {3}) caractere {4} executando {5} em {6} objeto '{7}' Classe: {8} ClasseBase: {9}"
    cannotSupplyIntervalForQuarterExceptionMessage                    = 'Não é possível fornecer um valor de intervalo para cada trimestre.'
    scheduleEndTimeMustBeInFutureExceptionMessage                     = '[Cronograma] {0}: O valor de EndTime deve estar no futuro.'
    invalidJwtSignatureSuppliedExceptionMessage                       = 'Assinatura JWT fornecida inválida.'
    noSetScriptBlockForVaultExceptionMessage                          = "Nenhum ScriptBlock fornecido para atualizar/criar segredos no cofre '{0}'"
    accessMethodNotExistForMergingExceptionMessage                    = 'O método de acesso não existe para a mesclagem: {0}'
    defaultAuthNotInListExceptionMessage                              = "A Autenticação Default '{0}' não está na lista de Autenticação fornecida."
    parameterHasNoNameExceptionMessage                                = "O parâmetro não tem nome. Dê um nome a este componente usando o parâmetro 'Name'."
    methodPathAlreadyDefinedForUrlExceptionMessage                    = '[{0}] {1}: Já definido para {2}'
    fileWatcherAlreadyDefinedExceptionMessage                         = "Um Observador de Arquivos chamado '{0}' já foi definido."
    noServiceHandlersDefinedExceptionMessage                          = 'Nenhum manipulador de serviço definido.'
    secretRequiredForCustomSessionStorageExceptionMessage             = 'Um segredo é necessário ao usar armazenamento de sessão personalizado.'
    secretManagementModuleNotInstalledExceptionMessage                = 'O módulo Microsoft.PowerShell.SecretManagement não está instalado.'
    noPathSuppliedForRouteExceptionMessage                            = 'Nenhum caminho fornecido para a Rota.'
    validationOfAnyOfSchemaNotSupportedExceptionMessage               = "A validação de um esquema que inclui 'anyof' não é suportada."
    iisAuthSupportIsForWindowsOnlyExceptionMessage                    = 'O suporte à Autenticação IIS é apenas para Windows.'
    oauth2InnerSchemeInvalidExceptionMessage                          = 'O OAuth2 InnerScheme só pode ser um de autenticação Basic ou Form, mas foi obtido: {0}'
    noRoutePathSuppliedForPageExceptionMessage                        = 'Nenhum caminho de rota fornecido para a página {0}.'
    cacheStorageNotFoundForExistsExceptionMessage                     = "Armazenamento em cache com o nome '{0}' não encontrado ao tentar verificar se o item em cache '{1}' existe."
    handlerAlreadyDefinedExceptionMessage                             = '[{0}] {1}: Manipulador já definido.'
    sessionsNotConfiguredExceptionMessage                             = 'As sessões não foram configuradas.'
    propertiesTypeObjectAssociationExceptionMessage                   = 'Apenas propriedades do tipo Objeto podem ser associadas com {0}.'
    sessionsRequiredForSessionPersistentAuthExceptionMessage          = 'Sessões são necessárias para usar a autenticação persistente por sessão.'
    invalidPathWildcardOrDirectoryExceptionMessage                    = 'O caminho fornecido não pode ser um curinga ou um diretório: {0}'
    accessMethodAlreadyDefinedExceptionMessage                        = 'Método de acesso já definido: {0}'
    parametersValueOrExternalValueMandatoryExceptionMessage           = "Os parâmetros 'Value' ou 'ExternalValue' são obrigatórios."
    maximumConcurrentTasksInvalidExceptionMessage                     = 'O número máximo de tarefas concorrentes deve ser >=1, mas foi obtido: {0}'
    cannotCreatePropertyWithoutTypeExceptionMessage                   = 'Não é possível criar a propriedade porque nenhum tipo é definido.'
    authMethodNotExistForMergingExceptionMessage                      = 'O método de autenticação não existe para mesclagem: {0}'
    maxValueInvalidExceptionMessage                                   = "O valor máximo '{0}' para {1} é inválido, deve ser menor ou igual a {2}"
    endpointAlreadyDefinedExceptionMessage                            = "Um ponto de extremidade chamado '{0}' já foi definido."
    eventAlreadyRegisteredExceptionMessage                            = 'Evento {0} já registrado: {1}'
    parameterNotSuppliedInRequestExceptionMessage                     = "Um parâmetro chamado '{0}' não foi fornecido na solicitação ou não há dados disponíveis."
    cacheStorageNotFoundForSetExceptionMessage                        = "Armazenamento em cache com o nome '{0}' não encontrado ao tentar definir o item em cache '{1}'."
    methodPathAlreadyDefinedExceptionMessage                          = '[{0}] {1}: Já definido.'
    errorLoggingAlreadyEnabledExceptionMessage                        = 'O registro de erros já está habilitado.'
    valueForUsingVariableNotFoundExceptionMessage                     = "Valor para '`$using:{0}' não pôde ser encontrado."
    rapidPdfDoesNotSupportOpenApi31ExceptionMessage                   = 'A ferramenta de documentos RapidPdf não suporta OpenAPI 3.1'
    oauth2ClientSecretRequiredExceptionMessage                        = 'OAuth2 requer um Client Secret quando não se usa PKCE.'
    invalidBase64JwtExceptionMessage                                  = 'Valor codificado Base64 inválido encontrado no JWT'
    noSessionToCalculateDataHashExceptionMessage                      = 'Nenhuma sessão disponível para calcular o hash dos dados.'
    cacheStorageNotFoundForRemoveExceptionMessage                     = "Armazenamento em cache com o nome '{0}' não encontrado ao tentar remover o item em cache '{1}'."
    csrfMiddlewareNotInitializedExceptionMessage                      = 'O Middleware CSRF não foi inicializado.'
    infoTitleMandatoryMessage                                         = 'info.title é obrigatório.'
    typeCanOnlyBeAssociatedWithObjectExceptionMessage                 = 'O tipo {0} só pode ser associado a um Objeto.'
    userFileDoesNotExistExceptionMessage                              = 'O arquivo do usuário não existe: {0}'
    routeParameterNeedsValidScriptblockExceptionMessage               = 'O parâmetro da Rota precisa de um ScriptBlock válido e não vazio.'
    nextTriggerCalculationErrorExceptionMessage                       = 'Parece que algo deu errado ao tentar calcular a próxima data e hora do gatilho: {0}'
    cannotLockValueTypeExceptionMessage                               = 'Não é possível bloquear um [ValueType].'
    failedToCreateOpenSslCertExceptionMessage                         = 'Falha ao criar o certificado OpenSSL: {0}'
    jwtExpiredExceptionMessage                                        = 'O JWT expirou.'
    openingGuiMessage                                                 = 'Abrindo a GUI.'
    multiTypePropertiesRequireOpenApi31ExceptionMessage               = 'Propriedades de múltiplos tipos requerem a versão 3.1 ou superior do OpenApi.'
    noNameForWebSocketRemoveExceptionMessage                          = 'Nenhum nome fornecido para remover o WebSocket.'
    maxSizeInvalidExceptionMessage                                    = 'MaxSize deve ser igual ou maior que 0, mas foi obtido: {0}'
    iisShutdownMessage                                                = '(Desligamento do IIS)'
    cannotUnlockValueTypeExceptionMessage                             = 'Não é possível desbloquear um [ValueType].'
    noJwtSignatureForAlgorithmExceptionMessage                        = 'Nenhuma assinatura JWT fornecida para {0}.'
    maximumConcurrentWebSocketThreadsInvalidExceptionMessage          = 'O número máximo de threads concorrentes do WebSocket deve ser >=1, mas foi obtido: {0}'
    acknowledgeMessageOnlySupportedOnSmtpTcpEndpointsExceptionMessage = 'A mensagem de reconhecimento é suportada apenas em endpoints SMTP e TCP.'
    failedToConnectToUrlExceptionMessage                              = 'Falha ao conectar ao URL: {0}'
    failedToAcquireMutexOwnershipExceptionMessage                     = 'Falha ao adquirir a propriedade do mutex. Nome do mutex: {0}'
    sessionsRequiredForOAuth2WithPKCEExceptionMessage                 = 'Sessões são necessárias para usar OAuth2 com PKCE'
    failedToConnectToWebSocketExceptionMessage                        = 'Falha ao conectar ao WebSocket: {0}'
    unsupportedObjectExceptionMessage                                 = 'Objeto não suportado'
    failedToParseAddressExceptionMessage                              = "Falha ao analisar '{0}' como um endereço IP/Host:Port válido"
    mustBeRunningWithAdminPrivilegesExceptionMessage                  = 'Deve estar sendo executado com privilégios de administrador para escutar endereços que não sejam localhost.'
    specificationMessage                                              = 'Especificação'
    cacheStorageNotFoundForClearExceptionMessage                      = "Armazenamento em cache com o nome '{0}' não encontrado ao tentar limpar o cache."
    restartingServerMessage                                           = 'Reiniciando o servidor...'
    cannotSupplyIntervalWhenEveryIsNoneExceptionMessage               = "Não é possível fornecer um intervalo quando o parâmetro 'Every' está definido como None."
    unsupportedJwtAlgorithmExceptionMessage                           = 'O algoritmo JWT não é atualmente suportado: {0}'
    websocketsNotConfiguredForSignalMessagesExceptionMessage          = 'WebSockets não estão configurados para enviar mensagens de sinal.'
    invalidLogicTypeInHashtableMiddlewareExceptionMessage             = 'Um Middleware do tipo Hashtable fornecido tem um tipo de lógica inválido. Esperado ScriptBlock, mas obtido: {0}'
    maximumConcurrentSchedulesLessThanMinimumExceptionMessage         = 'As cronogramas simultâneas máximas não podem ser inferiores ao mínimo de {0}, mas obtidas: {1}'
    failedToAcquireSemaphoreOwnershipExceptionMessage                 = 'Falha ao adquirir a propriedade do semáforo. Nome do semáforo: {0}'
    propertiesParameterWithoutNameExceptionMessage                    = 'Os parâmetros Properties não podem ser usados se a propriedade não tiver um nome.'
    customSessionStorageMethodNotImplementedExceptionMessage          = "O armazenamento de sessão personalizado não implementa o método requerido '{0}()'."
    authenticationMethodDoesNotExistExceptionMessage                  = 'O método de autenticação não existe: {0}'
    webhooksFeatureNotSupportedInOpenApi30ExceptionMessage            = 'O recurso Webhooks não é suportado no OpenAPI v3.0.x'
    invalidContentTypeForSchemaExceptionMessage                       = "'content-type' inválido encontrado para o esquema: {0}"
    noUnlockScriptBlockForVaultExceptionMessage                       = "Nenhum ScriptBlock de desbloqueio fornecido para desbloquear o cofre '{0}'"
    definitionTagMessage                                              = 'Definição {0}:'
    failedToOpenRunspacePoolExceptionMessage                          = 'Falha ao abrir o RunspacePool: {0}'
    failedToCloseRunspacePoolExceptionMessage                         = 'Falha ao fechar RunspacePool: {0}'
    verbNoLogicPassedExceptionMessage                                 = '[Verbo] {0}: Nenhuma lógica passada'
    noMutexFoundExceptionMessage                                      = "Nenhum mutex encontrado chamado '{0}'"
    documentationMessage                                              = 'Documentação'
    timerAlreadyDefinedExceptionMessage                               = '[Temporizador] {0}: Temporizador já definido.'
    invalidPortExceptionMessage                                       = 'A porta não pode ser negativa: {0}'
    viewsFolderNameAlreadyExistsExceptionMessage                      = 'O nome da pasta Views já existe: {0}'
    noNameForWebSocketResetExceptionMessage                           = 'Nenhum nome fornecido para redefinir o WebSocket.'
    mergeDefaultAuthNotInListExceptionMessage                         = "A Autenticação MergeDefault '{0}' não está na lista de Autenticação fornecida."
    descriptionRequiredExceptionMessage                               = 'Uma descrição é necessária para o Caminho:{0} Resposta:{1}'
    pageNameShouldBeAlphaNumericExceptionMessage                      = 'O nome da página deve ser um valor alfanumérico válido: {0}'
    defaultValueNotBooleanOrEnumExceptionMessage                      = 'O valor padrão não é booleano e não faz parte do enum.'
    openApiComponentSchemaDoesNotExistExceptionMessage                = 'O esquema do componente OpenApi {0} não existe.'
    timerParameterMustBeGreaterThanZeroExceptionMessage               = '[Temporizador] {0}: {1} deve ser maior que 0.'
    taskTimedOutExceptionMessage                                      = 'A tarefa expirou após {0}ms.'
    scheduleStartTimeAfterEndTimeExceptionMessage                     = "[Cronograma] {0}: Não pode ter um 'StartTime' após o 'EndTime'"
    infoVersionMandatoryMessage                                       = 'info.version é obrigatório.'
    cannotUnlockNullObjectExceptionMessage                            = 'Não é possível desbloquear um objeto nulo.'
    nonEmptyScriptBlockRequiredForCustomAuthExceptionMessage          = 'É necessário um ScriptBlock não vazio para o esquema de autenticação personalizado.'
    nonEmptyScriptBlockRequiredForAuthMethodExceptionMessage          = 'Um ScriptBlock não vazio é necessário para o método de autenticação.'
    validationOfOneOfSchemaNotSupportedExceptionMessage               = "A validação de um esquema que inclui 'oneof' não é suportada."
    routeParameterCannotBeNullExceptionMessage                        = "O parâmetro 'Route' não pode ser nulo."
    cacheStorageAlreadyExistsExceptionMessage                         = "Armazenamento em cache com o nome '{0}' já existe."
    loggingMethodRequiresValidScriptBlockExceptionMessage             = "O método de saída fornecido para o método de registro '{0}' requer um ScriptBlock válido."
    scopedVariableAlreadyDefinedExceptionMessage                      = 'Variável de escopo já definida: {0}'
    oauth2RequiresAuthorizeUrlExceptionMessage                        = 'OAuth2 requer que seja fornecida uma URL de Autorização'
    pathNotExistExceptionMessage                                      = 'O caminho não existe: {0}'
    noDomainServerNameForWindowsAdAuthExceptionMessage                = 'Nenhum nome de servidor de domínio foi fornecido para a autenticação AD do Windows'
    suppliedDateAfterScheduleEndTimeExceptionMessage                  = 'A data fornecida é posterior ao horário de término da cronograma em {0}'
    wildcardMethodsIncompatibleWithAutoMethodsExceptionMessage        = 'O caractere curinga * para os Métodos é incompatível com a chave AutoMethods.'
    cannotSupplyIntervalForYearExceptionMessage                       = 'Não é possível fornecer um valor de intervalo para cada ano.'
    missingComponentsMessage                                          = 'Componente(s) ausente(s)'
    invalidStrictTransportSecurityDurationExceptionMessage            = 'Duração inválida fornecida para Strict-Transport-Security: {0}. Deve ser maior que 0.'
    noSecretForHmac512ExceptionMessage                                = 'Nenhum segredo fornecido para o hash HMAC512.'
    daysInMonthExceededExceptionMessage                               = '{0} tem apenas {1} dias, mas {2} foi fornecido.'
    nonEmptyScriptBlockRequiredForCustomLoggingExceptionMessage       = 'Um ScriptBlock não vazio é necessário para o método de registro personalizado.'
    encodingAttributeOnlyAppliesToMultipartExceptionMessage           = 'O atributo de codificação só se aplica a corpos de solicitação multipart e application/x-www-form-urlencoded.'
    suppliedDateBeforeScheduleStartTimeExceptionMessage               = 'A data fornecida é anterior ao horário de início da cronograma em {0}'
    unlockSecretRequiredExceptionMessage                              = "É necessária uma propriedade 'UnlockSecret' ao usar Microsoft.PowerShell.SecretStore"
    noLogicPassedForMethodRouteExceptionMessage                       = '[{0}] {1}: Nenhuma lógica passada.'
    bodyParserAlreadyDefinedForContentTypeExceptionMessage            = 'Um body-parser já está definido para o tipo de conteúdo {0}.'
    invalidJwtSuppliedExceptionMessage                                = 'JWT fornecido inválido.'
    sessionsRequiredForFlashMessagesExceptionMessage                  = 'Sessões são necessárias para usar mensagens Flash.'
    semaphoreAlreadyExistsExceptionMessage                            = 'Já existe um semáforo com o seguinte nome: {0}'
    invalidJwtHeaderAlgorithmSuppliedExceptionMessage                 = 'Algoritmo de cabeçalho JWT fornecido inválido.'
    oauth2ProviderDoesNotSupportPasswordGrantTypeExceptionMessage     = "O provedor OAuth2 não suporta o grant_type 'password' necessário ao usar um InnerScheme."
    invalidAliasFoundExceptionMessage                                 = 'Alias {0} inválido encontrado: {1}'
    scheduleDoesNotExistExceptionMessage                              = "A cronograma '{0}' não existe."
    accessMethodNotExistExceptionMessage                              = 'O método de acesso não existe: {0}'
    oauth2ProviderDoesNotSupportCodeResponseTypeExceptionMessage      = "O provedor OAuth2 não suporta o response_type 'code'."
    untestedPowerShellVersionWarningMessage                           = '[AVISO] Pode {0} não foi testado no PowerShell {1}, pois não estava disponível quando o Pode foi lançado.'
    secretVaultAlreadyRegisteredAutoImportExceptionMessage            = "Um Cofre de Segredos com o nome '{0}' já foi registrado durante a importação automática de Cofres de Segredos."
    schemeRequiresValidScriptBlockExceptionMessage                    = "O esquema fornecido para o validador de autenticação '{0}' requer um ScriptBlock válido."
    serverLoopingMessage                                              = 'Looping do servidor a cada {0} segundos'
    certificateThumbprintsNameSupportedOnWindowsExceptionMessage      = 'Impressões digitais/nome do certificado são suportados apenas no Windows.'
    sseConnectionNameRequiredExceptionMessage                         = "Um nome de conexão SSE é necessário, seja de -Name ou `$WebEvent.Sse.Name."
    invalidMiddlewareTypeExceptionMessage                             = 'Um dos Middlewares fornecidos é de um tipo inválido. Esperado ScriptBlock ou Hashtable, mas obtido: {0}'
    noSecretForJwtSignatureExceptionMessage                           = 'Nenhum segredo fornecido para a assinatura JWT.'
    modulePathDoesNotExistExceptionMessage                            = 'O caminho do módulo não existe: {0}'
    taskAlreadyDefinedExceptionMessage                                = '[Tarefa] {0}: Tarefa já definida.'
    verbAlreadyDefinedExceptionMessage                                = '[Verbo] {0}: Já definido'
    clientCertificatesOnlySupportedOnHttpsEndpointsExceptionMessage   = 'Certificados de cliente são suportados apenas em endpoints HTTPS.'
    endpointNameNotExistExceptionMessage                              = "O ponto de extremidade com o nome '{0}' não existe."
    middlewareNoLogicSuppliedExceptionMessage                         = '[Middleware]: Nenhuma lógica fornecida no ScriptBlock.'
    scriptBlockRequiredForMergingUsersExceptionMessage                = 'É necessário um ScriptBlock para mesclar vários usuários autenticados em 1 objeto quando Valid é All.'
    secretVaultAlreadyRegisteredExceptionMessage                      = "Um Cofre de Segredos com o nome '{0}' já foi registrado{1}."
    deprecatedTitleVersionDescriptionWarningMessage                   = "AVISO: Título, Versão e Descrição em 'Enable-PodeOpenApi' estão obsoletos. Utilize 'Add-PodeOAInfo' em vez disso."
    undefinedOpenApiReferencesMessage                                 = 'Referências OpenAPI indefinidas:'
    doneMessage                                                       = 'Concluído'
    swaggerEditorDoesNotSupportOpenApi31ExceptionMessage              = 'Esta versão do Swagger-Editor não suporta OpenAPI 3.1'
    durationMustBeZeroOrGreaterExceptionMessage                       = 'A duração deve ser 0 ou maior, mas foi obtido: {0}s'
    viewsPathDoesNotExistExceptionMessage                             = 'O caminho das Views não existe: {0}'
    discriminatorIncompatibleWithAllOfExceptionMessage                = "O parâmetro 'Discriminator' é incompatível com 'allOf'."
    noNameForWebSocketSendMessageExceptionMessage                     = 'Nenhum nome fornecido para enviar mensagem ao WebSocket.'
    hashtableMiddlewareNoLogicExceptionMessage                        = 'Um Middleware do tipo Hashtable fornecido não tem lógica definida.'
    openApiInfoMessage                                                = 'Informações OpenAPI:'
    invalidSchemeForAuthValidatorExceptionMessage                     = "O esquema '{0}' fornecido para o validador de autenticação '{1}' requer um ScriptBlock válido."
    sseFailedToBroadcastExceptionMessage                              = 'SSE falhou em transmitir devido ao nível de transmissão SSE definido para {0}: {1}.'
    adModuleWindowsOnlyExceptionMessage                               = 'O módulo Active Directory está disponível apenas no Windows.'
    requestLoggingAlreadyEnabledExceptionMessage                      = 'O registro de solicitações já está habilitado.'
    invalidAccessControlMaxAgeDurationExceptionMessage                = 'Duração inválida fornecida para Access-Control-Max-Age: {0}. Deve ser maior que 0.'
    openApiDefinitionAlreadyExistsExceptionMessage                    = 'A definição OpenAPI com o nome {0} já existe.'
    renamePodeOADefinitionTagExceptionMessage                         = "Rename-PodeOADefinitionTag não pode ser usado dentro de um 'ScriptBlock' Select-PodeOADefinition."
    taskProcessDoesNotExistExceptionMessage                           = "O processo da tarefa '{0}' não existe."
    scheduleProcessDoesNotExistExceptionMessage                       = "O processo do cronograma '{0}' não existe."
    definitionTagChangeNotAllowedExceptionMessage                     = 'A Tag de definição para uma Route não pode ser alterada.'
    getRequestBodyNotAllowedExceptionMessage                          = 'As operações {0} não podem ter um corpo de solicitação.'
    fnDoesNotAcceptArrayAsPipelineInputExceptionMessage               = "A função '{0}' não aceita uma matriz como entrada de pipeline."
    unsupportedStreamCompressionEncodingExceptionMessage              = 'A codificação de compressão de fluxo não é suportada.'
}
src\Locales\zh\Pode.psd1
@{
    schemaValidationRequiresPowerShell610ExceptionMessage             = '架构验证需要 PowerShell 版本 6.1.0 或更高版本。'
    customAccessPathOrScriptBlockRequiredExceptionMessage             = '对于源自自定义访问值,需要路径或 ScriptBlock。'
    operationIdMustBeUniqueForArrayExceptionMessage                   = '操作ID: {0} 必须唯一,不能应用于数组。'
    endpointNotDefinedForRedirectingExceptionMessage                  = "未定义用于重定向的名为 '{0}' 的端点。"
    filesHaveChangedMessage                                           = '以下文件已更改:'
    iisAspnetcoreTokenMissingExceptionMessage                         = '缺少 IIS ASPNETCORE_TOKEN。'
    minValueGreaterThanMaxExceptionMessage                            = '{0} 的最小值不应大于最大值。'
    noLogicPassedForRouteExceptionMessage                             = '没有为路径传递逻辑: {0}'
    scriptPathDoesNotExistExceptionMessage                            = '脚本路径不存在: {0}'
    mutexAlreadyExistsExceptionMessage                                = "名为 '{0}' 的互斥量已存在。"
    listeningOnEndpointsMessage                                       = '正在监听以下 {0} 个端点 [{1} 个线程]:'
    unsupportedFunctionInServerlessContextExceptionMessage            = '不支持在无服务器上下文中使用 {0} 函数。'
    expectedNoJwtSignatureSuppliedExceptionMessage                    = '预期不提供 JWT 签名。'
    secretAlreadyMountedExceptionMessage                              = "名为'{0}'的秘密已挂载。"
    failedToAcquireLockExceptionMessage                               = '未能获取对象的锁。'
    noPathSuppliedForStaticRouteExceptionMessage                      = '[{0}]: 没有为静态路径提供路径。'
    invalidHostnameSuppliedExceptionMessage                           = '提供的主机名无效: {0}'
    authMethodAlreadyDefinedExceptionMessage                          = '身份验证方法已定义:{0}'
    csrfCookieRequiresSecretExceptionMessage                          = "使用 CSRF 的 Cookie 时,需要一个密钥。您可以提供一个密钥或设置全局 Cookie 密钥 - (Set-PodeCookieSecret '<value>' -Global)"
    nonEmptyScriptBlockRequiredForPageRouteExceptionMessage           = '创建页面路由需要非空的ScriptBlock。'
    noPropertiesMutuallyExclusiveExceptionMessage                     = "参数'NoProperties'与'Properties'、'MinProperties'和'MaxProperties'互斥。"
    incompatiblePodeDllExceptionMessage                               = '已加载存在不兼容的 Pode.DLL 版本 {0}。需要版本 {1}。请打开新的 Powershell/pwsh 会话并重试。'
    accessMethodDoesNotExistExceptionMessage                          = '访问方法不存在:{0}。'
    scheduleAlreadyDefinedExceptionMessage                            = '[计划] {0}: 计划已定义。'
    secondsValueCannotBeZeroOrLessExceptionMessage                    = '{0} 的秒数值不能为 0 或更小。'
    pathToLoadNotFoundExceptionMessage                                = '未找到要加载的路径 {0}: {1}'
    failedToImportModuleExceptionMessage                              = '导入模块失败: {0}'
    endpointNotExistExceptionMessage                                  = "具有协议 '{0}' 和地址 '{1}' 或本地地址 '{2}' 的端点不存在。"
    terminatingMessage                                                = '正在终止...'
    noCommandsSuppliedToConvertToRoutesExceptionMessage               = '未提供要转换为路由的命令。'
    invalidTaskTypeExceptionMessage                                   = '任务类型无效,预期类型为[System.Threading.Tasks.Task]或[hashtable]。'
    alreadyConnectedToWebSocketExceptionMessage                       = "已连接到名为 '{0}' 的 WebSocket"
    crlfMessageEndCheckOnlySupportedOnTcpEndpointsExceptionMessage    = 'CRLF消息结束检查仅支持TCP端点。'
    testPodeOAComponentSchemaNeedToBeEnabledExceptionMessage          = "必须使用 'Enable-PodeOpenApi -EnableSchemaValidation' 启用 'Test-PodeOAComponentSchema'。"
    adModuleNotInstalledExceptionMessage                              = '未安装 Active Directory 模块。'
    cronExpressionInvalidExceptionMessage                             = 'Cron 表达式应仅包含 5 个部分: {0}'
    noSessionToSetOnResponseExceptionMessage                          = '没有可用的会话来设置响应。'
    valueOutOfRangeExceptionMessage                                   = "{1} 的值 '{0}' 无效,应在 {2} 和 {3} 之间"
    loggingMethodAlreadyDefinedExceptionMessage                       = '日志记录方法已定义: {0}'
    noSecretForHmac256ExceptionMessage                                = '未提供 HMAC256 哈希的密钥。'
    eolPowerShellWarningMessage                                       = '[警告] Pode {0} 未在 PowerShell {1} 上测试,因为它已达到 EOL。'
    runspacePoolFailedToLoadExceptionMessage                          = '{0} RunspacePool 加载失败。'
    noEventRegisteredExceptionMessage                                 = '没有注册的 {0} 事件:{1}'
    scheduleCannotHaveNegativeLimitExceptionMessage                   = '[计划] {0}: 不能有负数限制。'
    openApiRequestStyleInvalidForParameterExceptionMessage            = 'OpenApi 请求样式不能为 {0},适用于 {1} 参数。'
    openApiDocumentNotCompliantExceptionMessage                       = 'OpenAPI 文档不符合规范。'
    taskDoesNotExistExceptionMessage                                  = "任务 '{0}' 不存在。"
    scopedVariableNotFoundExceptionMessage                            = '未找到范围变量: {0}'
    sessionsRequiredForCsrfExceptionMessage                           = '使用CSRF需要会话, 除非您想使用Cookie。'
    nonEmptyScriptBlockRequiredForLoggingMethodExceptionMessage       = '日志记录方法需要非空的ScriptBlock。'
    credentialsPassedWildcardForHeadersLiteralExceptionMessage        = '传递凭据时,标头的通配符 * 将被视为文字字符串,而不是通配符。'
    podeNotInitializedExceptionMessage                                = 'Pode未初始化。'
    multipleEndpointsForGuiMessage                                    = '定义了多个端点,仅第一个将用于 GUI。'
    operationIdMustBeUniqueExceptionMessage                           = '操作ID: {0} 必须唯一。'
    invalidJsonJwtExceptionMessage                                    = '在 JWT 中找到无效的 JSON 值'
    noAlgorithmInJwtHeaderExceptionMessage                            = 'JWT 头中未提供算法。'
    openApiVersionPropertyMandatoryExceptionMessage                   = 'OpenApi 版本属性是必需的。'
    limitValueCannotBeZeroOrLessExceptionMessage                      = '{0} 的限制值不能为 0 或更小。'
    timerDoesNotExistExceptionMessage                                 = "计时器 '{0}' 不存在。"
    openApiGenerationDocumentErrorMessage                             = 'OpenAPI 生成文档错误:'
    routeAlreadyContainsCustomAccessExceptionMessage                  = "路由 '[{0}] {1}' 已经包含名称为 '{2}' 的自定义访问。"
    maximumConcurrentWebSocketThreadsLessThanMinimumExceptionMessage  = '最大并发 WebSocket 线程数不能小于最小值 {0},但获得: {1}'
    middlewareAlreadyDefinedExceptionMessage                          = '[Middleware] {0}: 中间件已定义。'
    invalidAtomCharacterExceptionMessage                              = '无效的原子字符: {0}'
    invalidCronAtomFormatExceptionMessage                             = '发现无效的 cron 原子格式: {0}'
    cacheStorageNotFoundForRetrieveExceptionMessage                   = "尝试检索缓存项 '{1}' 时,找不到名为 '{0}' 的缓存存储。"
    headerMustHaveNameInEncodingContextExceptionMessage               = '在编码上下文中使用时,标头必须有名称。'
    moduleDoesNotContainFunctionExceptionMessage                      = '模块 {0} 不包含要转换为路径的函数 {1}。'
    pathToIconForGuiDoesNotExistExceptionMessage                      = 'GUI 图标的路径不存在: {0}'
    noTitleSuppliedForPageExceptionMessage                            = '未提供 {0} 页面的标题。'
    certificateSuppliedForNonHttpsWssEndpointExceptionMessage         = '为非HTTPS/WSS端点提供的证书。'
    cannotLockNullObjectExceptionMessage                              = '无法锁定空对象。'
    showPodeGuiOnlyAvailableOnWindowsExceptionMessage                 = 'Show-PodeGui目前仅适用于Windows PowerShell和Windows上的PowerShell 7+。'
    unlockSecretButNoScriptBlockExceptionMessage                      = '为自定义秘密保险库类型提供了解锁密钥,但未提供解锁 ScriptBlock。'
    invalidIpAddressExceptionMessage                                  = '提供的 IP 地址无效: {0}'
    maxDaysInvalidExceptionMessage                                    = 'MaxDays 必须大于或等于 0, 但得到: {0}'
    noRemoveScriptBlockForVaultExceptionMessage                       = "未为从保险库 '{0}' 中删除秘密提供删除 ScriptBlock。"
    noSecretExpectedForNoSignatureExceptionMessage                    = '预期未提供签名的密钥。'
    noCertificateFoundExceptionMessage                                = "在 {0}{1} 中找不到证书 '{2}'。"
    minValueInvalidExceptionMessage                                   = "{1} 的最小值 '{0}' 无效,应大于或等于 {2}"
    accessRequiresAuthenticationOnRoutesExceptionMessage              = '访问需要在路由上进行身份验证。'
    noSecretForHmac384ExceptionMessage                                = '未提供 HMAC384 哈希的密钥。'
    windowsLocalAuthSupportIsForWindowsOnlyExceptionMessage           = 'Windows 本地身份验证支持仅适用于 Windows。'
    definitionTagNotDefinedExceptionMessage                           = '定义标签 {0} 未定义。'
    noComponentInDefinitionExceptionMessage                           = '定义中没有类型为 {0} 名称为 {1} 的组件。'
    noSmtpHandlersDefinedExceptionMessage                             = '未定义 SMTP 处理程序。'
    sessionMiddlewareAlreadyInitializedExceptionMessage               = '会话中间件已初始化。'
    reusableComponentPathItemsNotAvailableInOpenApi30ExceptionMessage = "OpenAPI v3.0中不支持可重用组件功能'pathItems'。"
    wildcardHeadersIncompatibleWithAutoHeadersExceptionMessage        = '标头的通配符 * 与 AutoHeaders 开关不兼容。'
    noDataForFileUploadedExceptionMessage                             = "请求中未上传文件 '{0}' 的数据。"
    sseOnlyConfiguredOnEventStreamAcceptHeaderExceptionMessage        = 'SSE只能在Accept标头值为text/event-stream的请求上配置。'
    noSessionAvailableToSaveExceptionMessage                          = '没有可保存的会话。'
    pathParameterRequiresRequiredSwitchExceptionMessage               = "如果参数位置是 'Path',则 'Required' 开关参数是必需的。"
    noOpenApiUrlSuppliedExceptionMessage                              = '未提供 {0} 的 OpenAPI URL。'
    maximumConcurrentSchedulesInvalidExceptionMessage                 = '最大并发计划数必须 >=1, 但得到: {0}'
    snapinsSupportedOnWindowsPowershellOnlyExceptionMessage           = 'Snapins 仅支持 Windows PowerShell。'
    eventViewerLoggingSupportedOnWindowsOnlyExceptionMessage          = '事件查看器日志记录仅支持Windows。'
    parametersMutuallyExclusiveExceptionMessage                       = "参数 '{0}' 和 '{1}' 是互斥的。"
    pathItemsFeatureNotSupportedInOpenApi30ExceptionMessage           = '在 OpenAPI v3.0.x 中不支持 PathItems 功能。'
    openApiParameterRequiresNameExceptionMessage                      = 'OpenApi 参数需要指定名称。'
    maximumConcurrentTasksLessThanMinimumExceptionMessage             = '最大并发任务数不能小于最小值 {0},但获得: {1}'
    noSemaphoreFoundExceptionMessage                                  = "找不到名为 '{0}' 的信号量"
    singleValueForIntervalExceptionMessage                            = '当使用间隔时,只能提供单个 {0} 值。'
    jwtNotYetValidExceptionMessage                                    = 'JWT 尚未有效。'
    verbAlreadyDefinedForUrlExceptionMessage                          = '[Verb] {0}: 已经为 {1} 定义'
    noSecretNamedMountedExceptionMessage                              = "没有挂载名为'{0}'的秘密。"
    moduleOrVersionNotFoundExceptionMessage                           = '在 {0} 上找不到模块或版本: {1}@{2}'
    noScriptBlockSuppliedExceptionMessage                             = '未提供脚本块。'
    noSecretVaultRegisteredExceptionMessage                           = "未注册名为 '{0}' 的秘密保险库。"
    nameRequiredForEndpointIfRedirectToSuppliedExceptionMessage       = '如果提供了RedirectTo参数, 则需要为端点指定名称。'
    openApiLicenseObjectRequiresNameExceptionMessage                  = "OpenAPI 对象 'license' 需要属性 'name'。请使用 -LicenseName 参数。"
    sourcePathDoesNotExistForStaticRouteExceptionMessage              = '{0}: 为静态路径提供的源路径不存在: {1}'
    noNameForWebSocketDisconnectExceptionMessage                      = '没有提供断开连接的 WebSocket 的名称。'
    certificateExpiredExceptionMessage                                = "证书 '{0}' 已过期: {1}"
    secretVaultUnlockExpiryDateInPastExceptionMessage                 = '秘密保险库的解锁到期日期已过 (UTC) :{0}'
    invalidWebExceptionTypeExceptionMessage                           = '异常类型无效,应为 WebException 或 HttpRequestException, 但得到了: {0}'
    invalidSecretValueTypeExceptionMessage                            = '密钥值是无效的类型。期望类型: 字符串、SecureString、HashTable、Byte[] 或 PSCredential。但得到了: {0}'
    explicitTlsModeOnlySupportedOnSmtpsTcpsEndpointsExceptionMessage  = '显式TLS模式仅支持SMTPS和TCPS端点。'
    discriminatorMappingRequiresDiscriminatorPropertyExceptionMessage = "参数'DiscriminatorMapping'只能在存在'DiscriminatorProperty'时使用。"
    scriptErrorExceptionMessage                                       = "脚本 '{0}' 在 {1} {2} (第 {3} 行) 第 {4} 个字符处执行 {5} 对象 '{7}' 的错误。类: {8} 基类: {9}"
    cannotSupplyIntervalForQuarterExceptionMessage                    = '无法为每季度提供间隔值。'
    scheduleEndTimeMustBeInFutureExceptionMessage                     = '[计划] {0}: EndTime 值必须在将来。'
    invalidJwtSignatureSuppliedExceptionMessage                       = '提供的 JWT 签名无效。'
    noSetScriptBlockForVaultExceptionMessage                          = "未为更新/创建保险库 '{0}' 中的秘密提供设置 ScriptBlock。"
    accessMethodNotExistForMergingExceptionMessage                    = '合并时访问方法不存在: {0}'
    defaultAuthNotInListExceptionMessage                              = "默认身份验证 '{0}' 不在提供的身份验证列表中。"
    parameterHasNoNameExceptionMessage                                = "参数没有名称。请使用'Name'参数为此组件命名。"
    methodPathAlreadyDefinedForUrlExceptionMessage                    = '[{0}] {1}: 已经为 {2} 定义。'
    fileWatcherAlreadyDefinedExceptionMessage                         = "名为 '{0}' 的文件监视器已定义。"
    noServiceHandlersDefinedExceptionMessage                          = '未定义服务处理程序。'
    secretRequiredForCustomSessionStorageExceptionMessage             = '使用自定义会话存储时需要一个密钥。'
    secretManagementModuleNotInstalledExceptionMessage                = '未安装 Microsoft.PowerShell.SecretManagement 模块。'
    noPathSuppliedForRouteExceptionMessage                            = '未为路由提供路径。'
    validationOfAnyOfSchemaNotSupportedExceptionMessage               = "不支持包含 'anyof' 的模式的验证。"
    iisAuthSupportIsForWindowsOnlyExceptionMessage                    = 'IIS 身份验证支持仅适用于 Windows。'
    oauth2InnerSchemeInvalidExceptionMessage                          = 'OAuth2 InnerScheme 只能是 Basic 或 Form 身份验证,但得到:{0}'
    noRoutePathSuppliedForPageExceptionMessage                        = '未提供 {0} 页面的路由路径。'
    cacheStorageNotFoundForExistsExceptionMessage                     = "尝试检查缓存项 '{1}' 是否存在时,找不到名为 '{0}' 的缓存存储。"
    handlerAlreadyDefinedExceptionMessage                             = '[{0}] {1}: 处理程序已定义。'
    sessionsNotConfiguredExceptionMessage                             = '会话尚未配置。'
    propertiesTypeObjectAssociationExceptionMessage                   = '只有 Object 类型的属性可以与 {0} 关联。'
    sessionsRequiredForSessionPersistentAuthExceptionMessage          = '使用会话持久性身份验证需要会话。'
    invalidPathWildcardOrDirectoryExceptionMessage                    = '提供的路径不能是通配符或目录: {0}'
    accessMethodAlreadyDefinedExceptionMessage                        = '访问方法已经定义: {0}'
    parametersValueOrExternalValueMandatoryExceptionMessage           = "参数 'Value' 或 'ExternalValue' 是必需的。"
    maximumConcurrentTasksInvalidExceptionMessage                     = '最大并发任务数必须 >=1, 但获得: {0}'
    cannotCreatePropertyWithoutTypeExceptionMessage                   = '无法创建属性,因为未定义类型。'
    authMethodNotExistForMergingExceptionMessage                      = '合并时身份验证方法不存在:{0}'
    maxValueInvalidExceptionMessage                                   = "{1} 的最大值 '{0}' 无效,应小于或等于 {2}"
    endpointAlreadyDefinedExceptionMessage                            = "名为 '{0}' 的端点已定义。"
    eventAlreadyRegisteredExceptionMessage                            = '{0} 事件已注册:{1}'
    parameterNotSuppliedInRequestExceptionMessage                     = "请求中未提供名为 '{0}' 的参数或没有可用数据。"
    cacheStorageNotFoundForSetExceptionMessage                        = "尝试设置缓存项 '{1}' 时,找不到名为 '{0}' 的缓存存储。"
    methodPathAlreadyDefinedExceptionMessage                          = '[{0}] {1}: 已经定义。'
    errorLoggingAlreadyEnabledExceptionMessage                        = '错误日志记录已启用。'
    valueForUsingVariableNotFoundExceptionMessage                     = "未找到 '`$using:{0}' 的值。"
    rapidPdfDoesNotSupportOpenApi31ExceptionMessage                   = '文档工具 RapidPdf 不支持 OpenAPI 3.1'
    oauth2ClientSecretRequiredExceptionMessage                        = '不使用 PKCE 时, OAuth2 需要一个客户端密钥。'
    invalidBase64JwtExceptionMessage                                  = '在 JWT 中找到无效的 Base64 编码值'
    noSessionToCalculateDataHashExceptionMessage                      = '没有可用的会话来计算数据哈希。'
    cacheStorageNotFoundForRemoveExceptionMessage                     = "尝试删除缓存项 '{1}' 时,找不到名为 '{0}' 的缓存存储。"
    csrfMiddlewareNotInitializedExceptionMessage                      = 'CSRF中间件未初始化。'
    infoTitleMandatoryMessage                                         = 'info.title 是必填项。'
    typeCanOnlyBeAssociatedWithObjectExceptionMessage                 = '类型{0}只能与对象关联。'
    userFileDoesNotExistExceptionMessage                              = '用户文件不存在:{0}'
    routeParameterNeedsValidScriptblockExceptionMessage               = '路由参数需要有效且非空的ScriptBlock。'
    nextTriggerCalculationErrorExceptionMessage                       = '似乎在尝试计算下一个触发器日期时间时出现了问题: {0}'
    cannotLockValueTypeExceptionMessage                               = '无法锁定[ValueType]。'
    failedToCreateOpenSslCertExceptionMessage                         = '创建 OpenSSL 证书失败: {0}'
    jwtExpiredExceptionMessage                                        = 'JWT 已过期。'
    openingGuiMessage                                                 = '正在打开 GUI。'
    multiTypePropertiesRequireOpenApi31ExceptionMessage               = '多类型属性需要 OpenApi 版本 3.1 或更高版本。'
    noNameForWebSocketRemoveExceptionMessage                          = '没有提供要删除的 WebSocket 的名称。'
    maxSizeInvalidExceptionMessage                                    = 'MaxSize 必须大于或等于 0,但得到: {0}'
    iisShutdownMessage                                                = '(IIS 关闭)'
    cannotUnlockValueTypeExceptionMessage                             = '无法解锁[ValueType]。'
    noJwtSignatureForAlgorithmExceptionMessage                        = '没有为 {0} 提供 JWT 签名。'
    maximumConcurrentWebSocketThreadsInvalidExceptionMessage          = '最大并发 WebSocket 线程数必须 >=1, 但获得: {0}'
    acknowledgeMessageOnlySupportedOnSmtpTcpEndpointsExceptionMessage = '确认消息仅支持SMTP和TCP端点。'
    failedToConnectToUrlExceptionMessage                              = '连接到 URL 失败: {0}'
    failedToAcquireMutexOwnershipExceptionMessage                     = '未能获得互斥量的所有权。互斥量名称: {0}'
    sessionsRequiredForOAuth2WithPKCEExceptionMessage                 = '使用 PKCE 时需要会话来使用 OAuth2'
    failedToConnectToWebSocketExceptionMessage                        = '连接到 WebSocket 失败: {0}'
    unsupportedObjectExceptionMessage                                 = '不支持的对象'
    failedToParseAddressExceptionMessage                              = "无法将 '{0}' 解析为有效的 IP/主机:端口地址"
    mustBeRunningWithAdminPrivilegesExceptionMessage                  = '必须以管理员权限运行才能监听非本地主机地址。'
    specificationMessage                                              = '规格'
    cacheStorageNotFoundForClearExceptionMessage                      = "尝试清除缓存时,找不到名为 '{0}' 的缓存存储。"
    restartingServerMessage                                           = '正在重启服务器...'
    cannotSupplyIntervalWhenEveryIsNoneExceptionMessage               = "当参数'Every'设置为None时, 无法提供间隔。"
    unsupportedJwtAlgorithmExceptionMessage                           = '当前不支持的 JWT 算法: {0}'
    websocketsNotConfiguredForSignalMessagesExceptionMessage          = 'WebSockets未配置为发送信号消息。'
    invalidLogicTypeInHashtableMiddlewareExceptionMessage             = '提供的 Hashtable 中间件具有无效的逻辑类型。期望是 ScriptBlockm, 但得到了: {0}'
    maximumConcurrentSchedulesLessThanMinimumExceptionMessage         = '最大并发计划数不能小于最小值 {0},但得到: {1}'
    failedToAcquireSemaphoreOwnershipExceptionMessage                 = '未能获得信号量的所有权。信号量名称: {0}'
    propertiesParameterWithoutNameExceptionMessage                    = '如果属性没有名称,则不能使用 Properties 参数。'
    customSessionStorageMethodNotImplementedExceptionMessage          = "自定义会话存储未实现所需的方法'{0}()'。"
    authenticationMethodDoesNotExistExceptionMessage                  = '认证方法不存在: {0}'
    webhooksFeatureNotSupportedInOpenApi30ExceptionMessage            = '在 OpenAPI v3.0.x 中不支持 Webhooks 功能'
    invalidContentTypeForSchemaExceptionMessage                       = "架构中发现无效的 'content-type': {0}"
    noUnlockScriptBlockForVaultExceptionMessage                       = "未为解锁保险库 '{0}' 提供解锁 ScriptBlock。"
    definitionTagMessage                                              = '定义 {0}:'
    failedToOpenRunspacePoolExceptionMessage                          = '打开 RunspacePool 失败: {0}'
    failedToCloseRunspacePoolExceptionMessage                         = '无法关闭RunspacePool: {0}'
    verbNoLogicPassedExceptionMessage                                 = '[动词] {0}: 未传递逻辑'
    noMutexFoundExceptionMessage                                      = "找不到名为 '{0}' 的互斥量"
    documentationMessage                                              = '文档'
    timerAlreadyDefinedExceptionMessage                               = '[计时器] {0}: 计时器已定义。'
    invalidPortExceptionMessage                                       = '端口不能为负数: {0}'
    viewsFolderNameAlreadyExistsExceptionMessage                      = '视图文件夹名称已存在: {0}'
    noNameForWebSocketResetExceptionMessage                           = '没有提供要重置的 WebSocket 的名称。'
    mergeDefaultAuthNotInListExceptionMessage                         = "MergeDefault 身份验证 '{0}' 不在提供的身份验证列表中。"
    descriptionRequiredExceptionMessage                               = '路径:{0} 响应:{1} 需要描述'
    pageNameShouldBeAlphaNumericExceptionMessage                      = '页面名称应为有效的字母数字值: {0}'
    defaultValueNotBooleanOrEnumExceptionMessage                      = '默认值不是布尔值且不属于枚举。'
    openApiComponentSchemaDoesNotExistExceptionMessage                = 'OpenApi 组件架构 {0} 不存在。'
    timerParameterMustBeGreaterThanZeroExceptionMessage               = '[计时器] {0}: {1} 必须大于 0。'
    taskTimedOutExceptionMessage                                      = '任务在 {0} 毫秒后超时。'
    scheduleStartTimeAfterEndTimeExceptionMessage                     = "[计划] {0}: 'StartTime' 不能在 'EndTime' 之后"
    infoVersionMandatoryMessage                                       = 'info.version 是必填项。'
    cannotUnlockNullObjectExceptionMessage                            = '无法解锁空对象。'
    nonEmptyScriptBlockRequiredForCustomAuthExceptionMessage          = '自定义身份验证方案需要一个非空的 ScriptBlock。'
    nonEmptyScriptBlockRequiredForAuthMethodExceptionMessage          = '身份验证方法需要非空的 ScriptBlock。'
    validationOfOneOfSchemaNotSupportedExceptionMessage               = "不支持包含 'oneof' 的模式的验证。"
    routeParameterCannotBeNullExceptionMessage                        = "参数 'Route' 不能为空。"
    cacheStorageAlreadyExistsExceptionMessage                         = "名为 '{0}' 的缓存存储已存在。"
    loggingMethodRequiresValidScriptBlockExceptionMessage             = "为 '{0}' 日志记录方法提供的输出方法需要有效的 ScriptBlock。"
    scopedVariableAlreadyDefinedExceptionMessage                      = '已经定义了作用域变量: {0}'
    oauth2RequiresAuthorizeUrlExceptionMessage                        = 'OAuth2 需要提供授权 URL'
    pathNotExistExceptionMessage                                      = '路径不存在: {0}'
    noDomainServerNameForWindowsAdAuthExceptionMessage                = '没有为 Windows AD 身份验证提供域服务器名称'
    suppliedDateAfterScheduleEndTimeExceptionMessage                  = '提供的日期晚于计划的结束时间 {0}'
    wildcardMethodsIncompatibleWithAutoMethodsExceptionMessage        = '方法的通配符 * 与 AutoMethods 开关不兼容。'
    cannotSupplyIntervalForYearExceptionMessage                       = '无法为每年提供间隔值。'
    missingComponentsMessage                                          = '缺少的组件'
    invalidStrictTransportSecurityDurationExceptionMessage            = '提供的严格传输安全持续时间无效: {0}。应大于 0。'
    noSecretForHmac512ExceptionMessage                                = '未提供 HMAC512 哈希的密钥。'
    daysInMonthExceededExceptionMessage                               = '{0} 仅有 {1} 天,但提供了 {2} 天。'
    nonEmptyScriptBlockRequiredForCustomLoggingExceptionMessage       = '自定义日志输出方法需要非空的ScriptBlock。'
    encodingAttributeOnlyAppliesToMultipartExceptionMessage           = '编码属性仅适用于 multipart 和 application/x-www-form-urlencoded 请求体。'
    suppliedDateBeforeScheduleStartTimeExceptionMessage               = '提供的日期早于计划的开始时间 {0}'
    unlockSecretRequiredExceptionMessage                              = "使用 Microsoft.PowerShell.SecretStore 时需要 'UnlockSecret' 属性。"
    noLogicPassedForMethodRouteExceptionMessage                       = '[{0}] {1}: 没有传递逻辑。'
    bodyParserAlreadyDefinedForContentTypeExceptionMessage            = '已为 {0} 内容类型定义了一个 body-parser。'
    invalidJwtSuppliedExceptionMessage                                = '提供的 JWT 无效。'
    sessionsRequiredForFlashMessagesExceptionMessage                  = '使用闪存消息需要会话。'
    semaphoreAlreadyExistsExceptionMessage                            = "名为 '{0}' 的信号量已存在。"
    invalidJwtHeaderAlgorithmSuppliedExceptionMessage                 = '提供的 JWT 头算法无效。'
    oauth2ProviderDoesNotSupportPasswordGrantTypeExceptionMessage     = "OAuth2 提供程序不支持使用 InnerScheme 所需的 'password' grant_type。"
    invalidAliasFoundExceptionMessage                                 = '找到了无效的 {0} 别名: {1}'
    scheduleDoesNotExistExceptionMessage                              = "计划 '{0}' 不存在。"
    accessMethodNotExistExceptionMessage                              = '访问方法不存在: {0}'
    oauth2ProviderDoesNotSupportCodeResponseTypeExceptionMessage      = "OAuth2 提供程序不支持 'code' response_type。"
    untestedPowerShellVersionWarningMessage                           = '[警告] Pode {0} 未在 PowerShell {1} 上测试,因为 Pode 发布时该版本不可用。'
    secretVaultAlreadyRegisteredAutoImportExceptionMessage            = "已经注册了名称为 '{0}' 的秘密保险库,同时正在自动导入秘密保险库。"
    schemeRequiresValidScriptBlockExceptionMessage                    = "提供的方案用于 '{0}' 身份验证验证器,需要一个有效的 ScriptBlock。"
    serverLoopingMessage                                              = '服务器每 {0} 秒循环一次'
    certificateThumbprintsNameSupportedOnWindowsExceptionMessage      = '证书指纹/名称仅在 Windows 上受支持。'
    sseConnectionNameRequiredExceptionMessage                         = "需要SSE连接名称, 可以从-Name或`$WebEvent.Sse.Name获取。"
    invalidMiddlewareTypeExceptionMessage                             = '提供的中间件之一是无效的类型。期望是 ScriptBlock 或 Hashtable, 但得到了: {0}'
    noSecretForJwtSignatureExceptionMessage                           = '未提供 JWT 签名的密钥。'
    modulePathDoesNotExistExceptionMessage                            = '模块路径不存在: {0}'
    taskAlreadyDefinedExceptionMessage                                = '[任务] {0}: 任务已定义。'
    verbAlreadyDefinedExceptionMessage                                = '[Verb] {0}: 已经定义'
    clientCertificatesOnlySupportedOnHttpsEndpointsExceptionMessage   = '客户端证书仅支持HTTPS端点。'
    endpointNameNotExistExceptionMessage                              = "名为 '{0}' 的端点不存在。"
    middlewareNoLogicSuppliedExceptionMessage                         = '[中间件]: ScriptBlock中未提供逻辑。'
    scriptBlockRequiredForMergingUsersExceptionMessage                = '当 Valid 是 All 时,需要一个 ScriptBlock 来将多个经过身份验证的用户合并为一个对象。'
    secretVaultAlreadyRegisteredExceptionMessage                      = "名为'{0}'的秘密保险库已注册{1}。"
    deprecatedTitleVersionDescriptionWarningMessage                   = "警告: 'Enable-PodeOpenApi' 的标题、版本和描述已被弃用。请改用 'Add-PodeOAInfo'。"
    undefinedOpenApiReferencesMessage                                 = '未定义的 OpenAPI 引用:'
    doneMessage                                                       = '完成'
    swaggerEditorDoesNotSupportOpenApi31ExceptionMessage              = '此版本的 Swagger-Editor 不支持 OpenAPI 3.1'
    durationMustBeZeroOrGreaterExceptionMessage                       = '持续时间必须为 0 或更大,但获得: {0}s'
    viewsPathDoesNotExistExceptionMessage                             = '视图路径不存在: {0}'
    discriminatorIncompatibleWithAllOfExceptionMessage                = "参数'Discriminator'与'allOf'不兼容。"
    noNameForWebSocketSendMessageExceptionMessage                     = '没有提供要发送消息的 WebSocket 的名称。'
    hashtableMiddlewareNoLogicExceptionMessage                        = '提供的 Hashtable 中间件没有定义逻辑。'
    openApiInfoMessage                                                = 'OpenAPI 信息:'
    invalidSchemeForAuthValidatorExceptionMessage                     = "提供的 '{0}' 方案用于 '{1}' 身份验证验证器,需要一个有效的 ScriptBlock。"
    sseFailedToBroadcastExceptionMessage                              = '由于为{0}定义的SSE广播级别, SSE广播失败: {1}'
    adModuleWindowsOnlyExceptionMessage                               = '仅支持 Windows 的 Active Directory 模块。'
    requestLoggingAlreadyEnabledExceptionMessage                      = '请求日志记录已启用。'
    invalidAccessControlMaxAgeDurationExceptionMessage                = '提供的 Access-Control-Max-Age 时长无效:{0}。应大于 0。'
    openApiDefinitionAlreadyExistsExceptionMessage                    = '名为 {0} 的 OpenAPI 定义已存在。'
    renamePodeOADefinitionTagExceptionMessage                         = "Rename-PodeOADefinitionTag 不能在 Select-PodeOADefinition 'ScriptBlock' 内使用。"
    taskProcessDoesNotExistExceptionMessage                           = "任务进程 '{0}' 不存在。"
    scheduleProcessDoesNotExistExceptionMessage                       = "计划进程 '{0}' 不存在。"
    definitionTagChangeNotAllowedExceptionMessage                     = 'Route的定义标签无法更改。'
    getRequestBodyNotAllowedExceptionMessage                          = '{0} 操作不能包含请求体。'
    fnDoesNotAcceptArrayAsPipelineInputExceptionMessage               = "函数 '{0}' 不接受数组作为管道输入。"
    unsupportedStreamCompressionEncodingExceptionMessage              = '不支持的流压缩编码: {0}'
}
src\Misc\default-doc-bookmarks.html.pode
 
src\Misc\default-error-page.html.pode
 
src\Misc\default-error-page.json.pode
 
src\Misc\default-error-page.xml.pode
 
src\Misc\default-explorer.html.pode
 
src\Misc\default-file-browsing.html.pode
 
src\Misc\default-rapidoc.html.pode
 
src\Misc\default-rapipdf.html.pode
 
src\Misc\default-redoc.html.pode
 
src\Misc\default-stoplight.html.pode
 
src\Misc\default-swagger-editor.html.pode
 
src\Misc\default-swagger.html.pode
 
src\Pode.Internal.psd1
#
# Internal module manifest for module 'Pode'
#
# Generated by: Matthew Kelly (Badgerati)
#
# Generated on: 24/01/2023
#

@{
    # Script module or binary module file associated with this manifest.
    RootModule        = 'Pode.Internal.psm1'

    # Version number of this module.
    ModuleVersion     = '2.11.0'

    # ID used to uniquely identify this module
    GUID              = '86b48c1c-8b59-4f3c-80bb-936d6b3218f6'

    # Author of this module
    Author            = 'Matthew Kelly (Badgerati)'

    # Minimum version of the Windows PowerShell engine required by this module
    PowerShellVersion = '5.1'

}
src\Pode.Internal.psm1
# root path
$root = Split-Path -Parent -Path $MyInvocation.MyCommand.Path

# import everything
$sysfuncs = Get-ChildItem Function:

# load private functions
Get-ChildItem "$($root)/Private/*.ps1" | ForEach-Object { . ([System.IO.Path]::GetFullPath($_)) }

# 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 $_ }

# export the module's public functions
Export-ModuleMember -Function ($funcs.Name)
src\Pode.psd1
#
# Module manifest for module 'Pode'
#
# Generated by: Matthew Kelly (Badgerati)
#
# Generated on: 28/11/2017
#

@{
    # Script module or binary module file associated with this manifest.
    RootModule        = 'Pode.psm1'

    # Version number of this module.
    ModuleVersion     = '2.11.0'

    # ID used to uniquely identify this module
    GUID              = 'e3ea217c-fc3d-406b-95d5-4304ab06c6af'

    # Author of this module
    Author            = 'Matthew Kelly (Badgerati)'

    # Copyright statement for this module
    Copyright         = 'Copyright (c) 2017-2024 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.1'

    # Functions to export from this Module
    FunctionsToExport = @(
        # cookies
        'Get-PodeCookie',
        'Get-PodeCookieSecret',
        'Remove-PodeCookie',
        'Set-PodeCookie',
        'Set-PodeCookieSecret',
        'Test-PodeCookie',
        'Test-PodeCookieSigned',
        'Update-PodeCookieExpiry',
        'Get-PodeCookieValue',

        # flash
        'Add-PodeFlashMessage',
        'Clear-PodeFlashMessages',
        'Get-PodeFlashMessage',
        'Get-PodeFlashMessageNames',
        'Remove-PodeFlashMessage',
        'Test-PodeFlashMessage',

        # headers
        'Add-PodeHeader',
        'Add-PodeHeaderBulk',
        'Test-PodeHeader',
        'Get-PodeHeader',
        'Set-PodeHeader',
        'Set-PodeHeaderBulk',
        'Test-PodeHeaderSigned',

        # state
        'Set-PodeState',
        'Get-PodeState',
        'Remove-PodeState',
        'Save-PodeState',
        'Restore-PodeState',
        'Test-PodeState',
        'Get-PodeStateNames',

        # response helpers
        'Set-PodeResponseAttachment',
        'Write-PodeTextResponse',
        'Write-PodeFileResponse',
        'Write-PodeCsvResponse',
        'Write-PodeHtmlResponse',
        'Write-PodeMarkdownResponse',
        'Write-PodeJsonResponse',
        'Write-PodeYamlResponse',
        'Write-PodeXmlResponse',
        'Write-PodeViewResponse',
        'Write-PodeDirectoryResponse',
        'Set-PodeResponseStatus',
        'Move-PodeResponseUrl',
        'Write-PodeTcpClient',
        'Read-PodeTcpClient',
        'Close-PodeTcpClient',
        'Save-PodeRequestFile',
        'Test-PodeRequestFile',
        'Set-PodeViewEngine',
        'Use-PodePartialView',
        'Send-PodeSignal',
        'Add-PodeViewFolder',
        'Send-PodeResponse',

        # sse
        'ConvertTo-PodeSseConnection',
        'Send-PodeSseEvent',
        'Close-PodeSseConnection',
        'Test-PodeSseClientIdSigned',
        'Test-PodeSseClientIdValid',
        'New-PodeSseClientId',
        'Enable-PodeSseSigning',
        'Disable-PodeSseSigning',
        'Set-PodeSseBroadcastLevel',
        'Get-PodeSseBroadcastLevel',
        'Test-PodeSseBroadcastLevel',
        'Set-PodeSseDefaultScope',
        'Get-PodeSseDefaultScope',
        'Test-PodeSseName',
        'Test-PodeSseClientId',

        # utility helpers
        'Close-PodeDisposable',
        'Get-PodeServerPath',
        'Start-PodeStopwatch',
        'Use-PodeStream',
        'Use-PodeScript',
        'Get-PodeConfig',
        'Add-PodeEndware',
        'Use-PodeEndware',
        'Import-PodeModule',
        'Import-PodeSnapIn',
        'Protect-PodeValue',
        'Resolve-PodeValue',
        'Invoke-PodeScriptBlock',
        'Merge-PodeScriptblockArguments',
        'Test-PodeIsUnix',
        'Test-PodeIsWindows',
        'Test-PodeIsMacOS',
        'Test-PodeIsPSCore',
        'Test-PodeIsEmpty',
        'Out-PodeHost',
        'Write-PodeHost',
        'Test-PodeIsIIS',
        'Test-PodeIsHeroku',
        'Get-PodeIISApplicationPath',
        'Out-PodeVariable',
        'Test-PodeIsHosted',
        'New-PodeCron',
        'Test-PodeInRunspace',
        'ConvertFrom-PodeXml',
        'Set-PodeDefaultFolder',
        'Get-PodeDefaultFolder',
        'Get-PodeCurrentRunspaceName',
        'Set-PodeCurrentRunspaceName',
        'Invoke-PodeGC',

        # routes
        'Add-PodeRoute',
        'Add-PodeStaticRoute',
        'Add-PodeSignalRoute',
        'Remove-PodeRoute',
        'Remove-PodeStaticRoute',
        'Remove-PodeSignalRoute',
        'Clear-PodeRoutes',
        'Clear-PodeStaticRoutes',
        'Clear-PodeSignalRoutes',
        'ConvertTo-PodeRoute',
        'Add-PodePage',
        'Get-PodeRoute',
        'Get-PodeStaticRoute',
        'Get-PodeSignalRoute',
        'Use-PodeRoutes',
        'Add-PodeRouteGroup',
        'Add-PodeStaticRouteGroup',
        'Add-PodeSignalRouteGroup',
        'Set-PodeRouteIfExistsPreference',
        'Test-PodeRoute',
        'Test-PodeStaticRoute',
        'Test-PodeSignalRoute',

        # handlers
        'Add-PodeHandler',
        'Remove-PodeHandler',
        'Clear-PodeHandlers',
        'Use-PodeHandlers',

        # schedules
        'Add-PodeSchedule',
        'Remove-PodeSchedule',
        'Clear-PodeSchedule',
        'Invoke-PodeSchedule',
        'Edit-PodeSchedule',
        'Set-PodeScheduleConcurrency',
        'Get-PodeSchedule',
        'Get-PodeScheduleNextTrigger',
        'Use-PodeSchedules',
        'Test-PodeSchedule',
        'Clear-PodeSchedules',
        'Get-PodeScheduleProcess',

        # timers
        'Add-PodeTimer',
        'Remove-PodeTimer',
        'Clear-PodeTimers',
        'Invoke-PodeTimer',
        'Edit-PodeTimer',
        'Get-PodeTimer',
        'Use-PodeTimers',
        'Test-PodeTimer',

        # tasks
        'Add-PodeTask',
        'Set-PodeTaskConcurrency',
        'Invoke-PodeTask',
        'Remove-PodeTask',
        'Clear-PodeTasks',
        'Edit-PodeTask',
        'Get-PodeTask',
        'Use-PodeTasks',
        'Close-PodeTask',
        'Test-PodeTaskCompleted',
        'Wait-PodeTask',
        'Get-PodeTaskProcess',

        # middleware
        'Add-PodeMiddleware',
        'Remove-PodeMiddleware',
        'Clear-PodeMiddleware',
        'Add-PodeAccessRule',
        'Add-PodeLimitRule',
        'New-PodeCsrfToken',
        'Get-PodeCsrfMiddleware',
        'Initialize-PodeCsrf',
        'Enable-PodeCsrfMiddleware',
        'Use-PodeMiddleware',
        'New-PodeMiddleware',
        'Add-PodeBodyParser',
        'Remove-PodeBodyParser',

        # sessions
        'Enable-PodeSessionMiddleware',
        'Remove-PodeSession',
        'Save-PodeSession',
        'Get-PodeSessionId',
        'Reset-PodeSessionExpiry',
        'Get-PodeSessionDuration',
        'Get-PodeSessionExpiry',
        'Test-PodeSessionsEnabled',
        'Get-PodeSessionTabId',
        'Get-PodeSessionInfo',
        'Test-PodeSessionScopeIsBrowser',

        # auth
        'New-PodeAuthScheme',
        'New-PodeAuthAzureADScheme',
        'New-PodeAuthTwitterScheme',
        'Add-PodeAuth',
        'Get-PodeAuth',
        'Clear-PodeAuth',
        'Add-PodeAuthWindowsAd',
        'Add-PodeAuthWindowsLocal',
        'Remove-PodeAuth',
        'Add-PodeAuthMiddleware',
        'Add-PodeAuthIIS',
        'Add-PodeAuthUserFile',
        'ConvertTo-PodeJwt',
        'ConvertFrom-PodeJwt',
        'Test-PodeJwt'
        'Use-PodeAuth',
        'ConvertFrom-PodeOIDCDiscovery',
        'Test-PodeAuthUser',
        'Merge-PodeAuth',
        'Test-PodeAuth',
        'Test-PodeAuthExists',
        'Get-PodeAuthUser',
        'Add-PodeAuthSession',

        # access
        'New-PodeAccessScheme',
        'Add-PodeAccess',
        'Add-PodeAccessCustom',
        'Get-PodeAccess',
        'Test-PodeAccessExists',
        'Test-PodeAccess',
        'Test-PodeAccessUser',
        'Test-PodeAccessRoute',
        'Merge-PodeAccess',
        'Remove-PodeAccess',
        'Clear-PodeAccess',
        'Add-PodeAccessMiddleware',
        'Use-PodeAccess',

        # logging
        'New-PodeLoggingMethod',
        'Enable-PodeRequestLogging',
        'Enable-PodeErrorLogging',
        'Disable-PodeRequestLogging',
        'Disable-PodeErrorLogging',
        'Add-PodeLogger',
        'Remove-PodeLogger',
        'Clear-PodeLoggers',
        'Write-PodeErrorLog',
        'Write-PodeLog',
        'Protect-PodeLogItem',
        'Use-PodeLogging',

        # core
        'Start-PodeServer',
        'Close-PodeServer',
        'Restart-PodeServer',
        'Start-PodeStaticServer',
        'Show-PodeGui',
        'Add-PodeEndpoint',
        'Get-PodeEndpoint',
        'Pode',
        'Get-PodeServerDefaultSecret',
        'Wait-PodeDebugger',
        'Get-PodeVersion',

        # openapi
        'Enable-PodeOpenApi',
        'Get-PodeOADefinition',
        'Select-PodeOADefinition',
        'Add-PodeOAResponse',
        'Remove-PodeOAResponse',
        'Set-PodeOARequest',
        'New-PodeOARequestBody',
        'Test-PodeOADefinitionTag',
        'Test-PodeOADefinition',
        'Rename-PodeOADefinitionTag',

        # properties
        'New-PodeOAIntProperty',
        'New-PodeOANumberProperty',
        'New-PodeOAStringProperty',
        'New-PodeOABoolProperty',
        'New-PodeOAObjectProperty',
        'New-PodeOAMultiTypeProperty',
        'Merge-PodeOAProperty',
        'New-PodeOAComponentSchemaProperty',
        'ConvertTo-PodeOAParameter',
        'Set-PodeOARouteInfo',
        'Enable-PodeOAViewer',
        'Test-PodeOAJsonSchemaCompliance',
        'Add-PodeOAInfo',
        'Add-PodeOAExternalDoc',
        'New-PodeOAExternalDoc',
        'Add-PodeOATag',
        'Add-PodeOAServerEndpoint',
        'New-PodeOAExample',
        'New-PodeOAEncodingObject',
        'New-PodeOAResponse',
        'Add-PodeOACallBack',
        'New-PodeOAResponseLink',
        'New-PodeOAContentMediaType',
        'Add-PodeOAExternalRoute',
        'New-PodeOAServerEndpoint',
        'Test-PodeOAVersion',

        # Components
        'Add-PodeOAComponentResponse',
        'Add-PodeOAComponentSchema',
        'Add-PodeOAComponentRequestBody',
        'Add-PodeOAComponentHeader',
        'Add-PodeOAComponentExample',
        'Add-PodeOAComponentParameter',
        'Add-PodeOAComponentResponseLink',
        'Add-PodeOAComponentCallBack',
        'Add-PodeOAComponentPathItem',
        'Add-PodeOAWebhook',
        'Test-PodeOAComponent',
        'Remove-PodeOAComponent',

        # Metrics
        'Get-PodeServerUptime',
        'Get-PodeServerRestartCount',
        'Get-PodeServerRequestMetric',
        'Get-PodeServerSignalMetric',
        'Get-PodeServerActiveRequestMetric',
        'Get-PodeServerActiveSignalMetric',

        # AutoImport
        'Export-PodeModule',
        'Export-PodeSnapin',
        'Export-PodeFunction',
        'Export-PodeSecretVault',

        # Events
        'Register-PodeEvent',
        'Unregister-PodeEvent',
        'Test-PodeEvent',
        'Get-PodeEvent',
        'Clear-PodeEvent',
        'Use-PodeEvents',

        # Security
        'Add-PodeSecurityHeader',
        'Add-PodeSecurityContentSecurityPolicy',
        'Add-PodeSecurityPermissionsPolicy',
        'Remove-PodeSecurity',
        'Remove-PodeSecurityAccessControl',
        'Remove-PodeSecurityContentSecurityPolicy',
        'Remove-PodeSecurityContentTypeOptions',
        'Remove-PodeSecurityCrossOrigin',
        'Remove-PodeSecurityFrameOptions',
        'Remove-PodeSecurityHeader',
        'Remove-PodeSecurityPermissionsPolicy',
        'Remove-PodeSecurityReferrerPolicy',
        'Remove-PodeSecurityStrictTransportSecurity',
        'Set-PodeSecurity',
        'Set-PodeSecurityAccessControl',
        'Set-PodeSecurityContentSecurityPolicy',
        'Set-PodeSecurityContentTypeOptions',
        'Set-PodeSecurityCrossOrigin',
        'Set-PodeSecurityFrameOptions',
        'Set-PodeSecurityPermissionsPolicy',
        'Set-PodeSecurityReferrerPolicy',
        'Set-PodeSecurityStrictTransportSecurity',
        'Hide-PodeSecurityServer',
        'Show-PodeSecurityServer',

        # Verbs
        'Add-PodeVerb',
        'Remove-PodeVerb',
        'Clear-PodeVerbs',
        'Get-PodeVerb',
        'Use-PodeVerbs',

        # WebSockets
        'Set-PodeWebSocketConcurrency',
        'Connect-PodeWebSocket',
        'Disconnect-PodeWebSocket',
        'Remove-PodeWebSocket',
        'Send-PodeWebSocket',
        'Reset-PodeWebSocket',
        'Test-PodeWebSocket'

        # Secrets
        'Register-PodeSecretVault',
        'Unregister-PodeSecretVault',
        'Unlock-PodeSecretVault',
        'Get-PodeSecretVault',
        'Test-PodeSecretVault',
        'Mount-PodeSecret',
        'Dismount-PodeSecret',
        'Get-PodeSecret',
        'Test-PodeSecret',
        'Update-PodeSecret',
        'Remove-PodeSecret',
        'Read-PodeSecret',
        'Set-PodeSecret',

        # File Watchers
        'Add-PodeFileWatcher',
        'Test-PodeFileWatcher',
        'Get-PodeFileWatcher',
        'Remove-PodeFileWatcher',
        'Clear-PodeFileWatchers',
        'Use-PodeFileWatchers',

        # Threading
        'Lock-PodeObject',
        'New-PodeLockable',
        'Remove-PodeLockable',
        'Get-PodeLockable',
        'Test-PodeLockable',
        'Enter-PodeLockable',
        'Exit-PodeLockable',
        'Clear-PodeLockables',
        'New-PodeMutex',
        'Test-PodeMutex',
        'Get-PodeMutex',
        'Remove-PodeMutex',
        'Use-PodeMutex',
        'Enter-PodeMutex',
        'Exit-PodeMutex',
        'Clear-PodeMutexes',
        'New-PodeSemaphore',
        'Test-PodeSemaphore',
        'Get-PodeSemaphore',
        'Remove-PodeSemaphore',
        'Use-PodeSemaphore',
        'Enter-PodeSemaphore',
        'Exit-PodeSemaphore',
        'Clear-PodeSemaphores',

        # caching
        'Get-PodeCache',
        'Set-PodeCache',
        'Test-PodeCache',
        'Remove-PodeCache',
        'Clear-PodeCache',
        'Add-PodeCacheStorage',
        'Remove-PodeCacheStorage',
        'Get-PodeCacheStorage',
        'Test-PodeCacheStorage',
        'Set-PodeCacheDefaultStorage',
        'Get-PodeCacheDefaultStorage',
        'Set-PodeCacheDefaultTtl',
        'Get-PodeCacheDefaultTtl',

        # scoped variables
        'Convert-PodeScopedVariables',
        'Convert-PodeScopedVariable',
        'Add-PodeScopedVariable',
        'Remove-PodeScopedVariable',
        'Test-PodeScopedVariable',
        'Clear-PodeScopedVariables',
        'Get-PodeScopedVariable',
        'Use-PodeScopedVariables'
    )

    # Aliases to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no aliases to export.
    AliasesToExport   = @(
        'Enable-PodeOpenApiViewer',
        'Enable-PodeOA',
        'Get-PodeOpenApiDefinition',
        'New-PodeOASchemaProperty'
    )

    # 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       = @{
            # Tags applied to this module. These help with module discovery in online galleries.
            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'

            # A URL to the main website for this project.
            ProjectUri   = 'https://github.com/Badgerati/Pode'

            # A URL to an icon representing this module.
            IconUri      = 'https://raw.githubusercontent.com/Badgerati/Pode/master/images/icon.png'

            # Release notes for this particular version of the module
            ReleaseNotes = 'https://github.com/Badgerati/Pode/releases/tag/v2.11.0'
        }
        PwshVersions = @{
            Untested  = '7.3,7.1,7.0,6.2,6.1,6.0,5.0,4.0,3.0,2.0,1.0'
            Supported = '7.4,7.2,5.1'
        }
    }
}
src\Pode.psm1
<#
.SYNOPSIS
    Pode PowerShell Module

.DESCRIPTION
    This module sets up the Pode environment, including
    localization and loading necessary assemblies and functions.

.PARAMETER UICulture
    Specifies the culture to be used for localization.

.EXAMPLE
    Import-Module -Name "Pode" -ArgumentList @{ UICulture = 'ko-KR' }
    Sets the culture to Korean.

.EXAMPLE
    Import-Module -Name "Pode"
    Uses the default culture.

.EXAMPLE
    Import-Module -Name "Pode" -ArgumentList 'it-SM'
    Uses the Italian San Marino region culture.

.EXAMPLE
    try {
        Import-Module -Name Pode -MaximumVersion 2.99.99
    } catch {
        Write-Error "Failed to load the Pode module"
        throw
    }
    The import statement is within a try/catch block.
    This way, if the module fails to load, your script won’t proceed, preventing possible errors or unexpected behavior.

    .NOTES
    This is the entry point for the Pode module.

#>

param(
    [string]$UICulture
)

# root path
$root = Split-Path -Parent -Path $MyInvocation.MyCommand.Path
$localesPath = (Join-Path -Path $root -ChildPath 'Locales')

# Import localized messages
if ([string]::IsNullOrEmpty($UICulture)) {
    $UICulture = $PsUICulture
}

try {
    try {
        #The list of all available supported culture is available here https://azuliadesigns.com/c-sharp-tutorials/list-net-culture-country-codes/

        # ErrorAction:SilentlyContinue is not sufficient to avoid Import-LocalizedData to generate an exception when the Culture file is not the right format
        Import-LocalizedData -BindingVariable tmpPodeLocale -BaseDirectory $localesPath -UICulture $UICulture -ErrorAction:SilentlyContinue
        if ($null -eq $tmpPodeLocale) {
            $UICulture = 'en'
            Import-LocalizedData -BindingVariable tmpPodeLocale -BaseDirectory $localesPath -UICulture $UICulture -ErrorAction:Stop
        }
    }
    catch {
        throw ("Failed to Import Localized Data $(Join-Path -Path $localesPath -ChildPath  $UICulture -AdditionalChildPath 'Pode.psd1') $_")
    }

    # Create the global msgTable read-only variable
    New-Variable -Name 'PodeLocale' -Value $tmpPodeLocale -Scope script -Option ReadOnly -Force -Description 'Localization HashTable'

    # load assemblies
    Add-Type -AssemblyName System.Web -ErrorAction Stop
    Add-Type -AssemblyName System.Net.Http -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 -ErrorAction Stop


    $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) {
                # An existing incompatible Pode.DLL version {0} is loaded. Version {1} is required. Open a new Powershell/pwsh session and retry.
                throw ($PodeLocale.incompatiblePodeDllExceptionMessage -f $podeDll.GetName().Version, $moduleVersion)
            }
        }
    }
    else {
        # fetch the .net version and the libs path
        $version = [System.Environment]::Version.Major
        $libsPath = "$($root)/Libs"

        # filter .net dll folders based on version above, and get path for latest version found
        if (![string]::IsNullOrWhiteSpace($version)) {
            $netFolder = Get-ChildItem -Path $libsPath -Directory -Force |
                Where-Object { $_.Name -imatch "net[1-$($version)]" } |
                Sort-Object -Property Name -Descending |
                Select-Object -First 1 -ExpandProperty FullName
        }

        # use netstandard if no folder found
        if ([string]::IsNullOrWhiteSpace($netFolder)) {
            $netFolder = "$($libsPath)/netstandard2.0"
        }

        # append Pode.dll and mount
        Add-Type -LiteralPath "$($netFolder)/Pode.dll" -ErrorAction Stop
    }

    # load private functions
    Get-ChildItem "$($root)/Private/*.ps1" | ForEach-Object { . ([System.IO.Path]::GetFullPath($_)) }

    # 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
    if ($funcs) {
        if ($aliases) {
            Export-ModuleMember -Function ($funcs.Name) -Alias $aliases.Name
        }
        else {
            Export-ModuleMember -Function ($funcs.Name)
        }
    }
}
catch {
    throw ("Failed to load the Pode module. $_")
}

src\Private\Access.ps1
function Get-PodeAccessMiddlewareScript {
    return {
        param($opts)

        if ($null -eq $WebEvent.Auth) {
            Set-PodeResponseStatus -Code 403
            return $false
        }

        # test access
        $WebEvent.Auth.IsAuthorised = Invoke-PodeAccessValidation -Name $opts.Name

        # 403 if unauthorised
        if (!$WebEvent.Auth.IsAuthorised) {
            Set-PodeResponseStatus -Code 403
        }

        # run next middleware or stop?
        return $WebEvent.Auth.IsAuthorised
    }
}

function Invoke-PodeAccessValidation {
    param(
        [Parameter(Mandatory = $true)]
        [string]
        $Name
    )

    # get the access method
    $access = $PodeContext.Server.Authorisations.Methods[$Name]

    # if it's a merged access, re-call this function and check against "succeed" value
    if ($access.Merged) {
        foreach ($accName in $access.Access) {
            $result = Invoke-PodeAccessValidation -Name $accName

            # if the access passed, and we only need one access to pass, return true
            if ($result -and $access.PassOne) {
                return $true
            }

            # if the access failed, but we need all to pass, return false
            if (!$result -and !$access.PassOne) {
                return $false
            }
        }

        # if the last access failed, and we only need one access to pass, return false
        if (!$result -and $access.PassOne) {
            return $false
        }

        # if the last access succeeded, and we need all to pass, return true
        if ($result -and !$access.PassOne) {
            return $true
        }

        # default failure
        return $false
    }

    # main access validation logic
    return (Test-PodeAccessRoute -Name $Name)
}
src\Private\Authentication.ps1
function Get-PodeAuthBasicType {
    return {
        param($options)

        # get the auth header
        $header = (Get-PodeHeader -Name 'Authorization')
        if ($null -eq $header) {
            return @{
                Message = 'No Authorization header found'
                Code    = 401
            }
        }

        # ensure the first atom is basic (or opt override)
        $atoms = $header -isplit '\s+'
        if ($atoms.Length -lt 2) {
            return @{
                Message = 'Invalid Authorization header'
                Code    = 400
            }
        }

        if ($atoms[0] -ine $options.HeaderTag) {
            return @{
                Message = "Header is not for $($options.HeaderTag) Authorization"
                Code    = 400
            }
        }

        # decode the auth header
        try {
            $enc = [System.Text.Encoding]::GetEncoding($options.Encoding)
        }
        catch {
            return @{
                Message = 'Invalid encoding specified for Authorization'
                Code    = 400
            }
        }

        try {
            $decoded = $enc.GetString([System.Convert]::FromBase64String($atoms[1]))
        }
        catch {
            return @{
                Message = 'Invalid Base64 string found in Authorization header'
                Code    = 400
            }
        }

        # validate and return user/result
        $index = $decoded.IndexOf(':')
        $username = $decoded.Substring(0, $index)
        $password = $decoded.Substring($index + 1)

        # build the result
        $result = @($username, $password)

        # convert to credential?
        if ($options.AsCredential) {
            $passSecure = ConvertTo-SecureString -String $password -AsPlainText -Force
            $creds = [pscredential]::new($username, $passSecure)
            $result = @($creds)
        }

        # return data for calling validator
        return $result
    }
}

function Get-PodeAuthOAuth2Type {
    return {
        param($options, $schemes)

        # set default scopes
        if (($null -eq $options.Scopes) -or ($options.Scopes.Length -eq 0)) {
            $options.Scopes = @('openid', 'profile', 'email')
        }

        $scopes = ($options.Scopes -join ' ')

        # if there's an error, fail
        if (![string]::IsNullOrWhiteSpace($WebEvent.Query['error'])) {
            return @{
                Message   = $WebEvent.Query['error']
                Code      = 401
                IsErrored = $true
            }
        }

        # set grant type
        $hasInnerScheme = (($null -ne $schemes) -and ($schemes.Length -gt 0))
        $grantType = 'authorization_code'
        if ($hasInnerScheme) {
            $grantType = 'password'
        }

        # if there's a code query param, or inner scheme, get access token
        if ($hasInnerScheme -or ![string]::IsNullOrWhiteSpace($WebEvent.Query['code'])) {
            try {
                # ensure the state is valid
                if ((Test-PodeSessionsInUse) -and ($WebEvent.Query['state'] -ne $WebEvent.Session.Data['__pode_oauth_state__'])) {
                    return @{
                        Message   = 'OAuth2 state returned is invalid'
                        Code      = 401
                        IsErrored = $true
                    }
                }

                # build tokenUrl query with client info
                $body = "client_id=$($options.Client.ID)"
                $body += "&grant_type=$($grantType)"

                if (![string]::IsNullOrEmpty($options.Client.Secret)) {
                    $body += "&client_secret=$([System.Web.HttpUtility]::UrlEncode($options.Client.Secret))"
                }

                # add PKCE code verifier
                if ($options.PKCE.Enabled) {
                    $body += "&code_verifier=$($WebEvent.Session.Data['__pode_oauth_code_verifier__'])"
                }

                # if there's an inner scheme, get the username/password, and set query
                if ($hasInnerScheme) {
                    $body += "&username=$($schemes[-1][0])"
                    $body += "&password=$($schemes[-1][1])"
                    $body += "&scope=$([System.Web.HttpUtility]::UrlEncode($scopes))"
                }

                # otherwise, set query for auth_code
                else {
                    $redirectUrl = Get-PodeOAuth2RedirectHost -RedirectUrl $options.Urls.Redirect
                    $body += "&code=$($WebEvent.Query['code'])"
                    $body += "&redirect_uri=$([System.Web.HttpUtility]::UrlEncode($redirectUrl))"
                }

                # POST the tokenUrl
                try {
                    $result = Invoke-RestMethod -Method Post -Uri $options.Urls.Token -Body $body -ContentType 'application/x-www-form-urlencoded' -ErrorAction Stop
                }
                catch [System.Net.WebException], [System.Net.Http.HttpRequestException] {
                    $response = Read-PodeWebExceptionInfo -ErrorRecord $_
                    $result = ($response.Body | ConvertFrom-Json)
                }

                # was there an error?
                if (![string]::IsNullOrWhiteSpace($result.error)) {
                    return @{
                        Message   = "$($result.error): $($result.error_description)"
                        Code      = 401
                        IsErrored = $true
                    }
                }

                # get user details - if url supplied
                if (![string]::IsNullOrWhiteSpace($options.Urls.User.Url)) {
                    try {
                        $user = Invoke-RestMethod -Method $options.Urls.User.Method -Uri $options.Urls.User.Url -Headers @{ Authorization = "Bearer $($result.access_token)" }
                    }
                    catch [System.Net.WebException], [System.Net.Http.HttpRequestException] {
                        $response = Read-PodeWebExceptionInfo -ErrorRecord $_
                        $user = ($response.Body | ConvertFrom-Json)
                    }

                    if (![string]::IsNullOrWhiteSpace($user.error)) {
                        return @{
                            Message   = "$($user.error): $($user.error_description)"
                            Code      = 401
                            IsErrored = $true
                        }
                    }
                }
                elseif (![string]::IsNullOrWhiteSpace($result.id_token)) {
                    try {
                        $user = ConvertFrom-PodeJwt -Token $result.id_token -IgnoreSignature
                    }
                    catch {
                        $user = @{ Provider = 'OAuth2' }
                    }
                }
                else {
                    $user = @{ Provider = 'OAuth2' }
                }

                # return the user for the validator
                return @($user, $result.access_token, $result.refresh_token, $result)
            }
            finally {
                if ($null -ne $WebEvent.Session.Data) {
                    # clear state
                    $WebEvent.Session.Data.Remove('__pode_oauth_state__')

                    # clear PKCE
                    if ($options.PKCE.Enabled) {
                        $WebEvent.Session.Data.Remove('__pode_oauth_code_verifier__')
                    }
                }
            }
        }

        # redirect to the authUrl - only if no inner scheme supplied
        if (!$hasInnerScheme) {
            # get the redirectUrl
            $redirectUrl = Get-PodeOAuth2RedirectHost -RedirectUrl $options.Urls.Redirect

            # add authUrl query params
            $query = "client_id=$($options.Client.ID)"
            $query += '&response_type=code'
            $query += "&redirect_uri=$([System.Web.HttpUtility]::UrlEncode($redirectUrl))"
            $query += '&response_mode=query'
            $query += "&scope=$([System.Web.HttpUtility]::UrlEncode($scopes))"

            # add csrf state
            if (Test-PodeSessionsInUse) {
                $guid = New-PodeGuid
                $WebEvent.Session.Data['__pode_oauth_state__'] = $guid
                $query += "&state=$($guid)"
            }

            # build a code verifier for PKCE, and add to query
            if ($options.PKCE.Enabled) {
                $guid = New-PodeGuid
                $codeVerifier = "$($guid)-$($guid)"
                $WebEvent.Session.Data['__pode_oauth_code_verifier__'] = $codeVerifier

                $codeChallenge = $codeVerifier
                if ($options.PKCE.CodeChallenge.Method -ieq 'S256') {
                    $codeChallenge = ConvertTo-PodeBase64UrlValue -Value (Invoke-PodeSHA256Hash -Value $codeChallenge) -NoConvert
                }

                $query += "&code_challenge=$($codeChallenge)"
                $query += "&code_challenge_method=$($options.PKCE.CodeChallenge.Method)"
            }

            # are custom parameters already on the URL?
            $url = $options.Urls.Authorise
            if (!$url.Contains('?')) {
                $url += '?'
            }
            else {
                $url += '&'
            }

            # redirect to OAuth2 endpoint
            Move-PodeResponseUrl -Url "$($url)$($query)"
            return @{ IsRedirected = $true }
        }

        # hmm, this is unexpected
        return @{
            Message   = 'Well, this is awkward...'
            Code      = 500
            IsErrored = $true
        }
    }
}

function Get-PodeOAuth2RedirectHost {
    param(
        [Parameter()]
        [string]
        $RedirectUrl
    )

    if ($RedirectUrl.StartsWith('/')) {
        if ($PodeContext.Server.IsIIS -or $PodeContext.Server.IsHeroku) {
            $protocol = Get-PodeHeader -Name 'X-Forwarded-Proto'
            if ([string]::IsNullOrWhiteSpace($protocol)) {
                $protocol = 'https'
            }

            $domain = "$($protocol)://$($WebEvent.Request.Host)"
        }
        else {
            $domain = Get-PodeEndpointUrl
        }

        $RedirectUrl = "$($domain.TrimEnd('/'))$($RedirectUrl)"
    }

    return $RedirectUrl
}

function Get-PodeAuthClientCertificateType {
    return {
        param($options)
        $cert = $WebEvent.Request.ClientCertificate

        # ensure we have a client cert
        if ($null -eq $cert) {
            return @{
                Message = 'No client certificate supplied'
                Code    = 401
            }
        }

        # ensure the cert has a thumbprint
        if ([string]::IsNullOrWhiteSpace($cert.Thumbprint)) {
            return @{
                Message = 'Invalid client certificate supplied'
                Code    = 401
            }
        }

        # ensure the cert hasn't expired, or has it even started
        $now = [datetime]::Now
        if (($cert.NotAfter -lt $now) -or ($cert.NotBefore -gt $now)) {
            return @{
                Message = 'Invalid client certificate supplied'
                Code    = 401
            }
        }

        # return data for calling validator
        return @($cert, $WebEvent.Request.ClientCertificateErrors)
    }
}

function Get-PodeAuthApiKeyType {
    return {
        param($options)

        # get api key from appropriate location
        $apiKey = [string]::Empty

        switch ($options.Location.ToLowerInvariant()) {
            'header' {
                $apiKey = Get-PodeHeader -Name $options.LocationName
            }

            'query' {
                $apiKey = $WebEvent.Query[$options.LocationName]
            }

            'cookie' {
                $apiKey = Get-PodeCookieValue -Name $options.LocationName
            }
        }

        # 400 if no key
        if ([string]::IsNullOrWhiteSpace($apiKey)) {
            return @{
                Message = "No $($options.LocationName) $($options.Location) found"
                Code    = 400
            }
        }

        # build the result
        $apiKey = $apiKey.Trim()
        $result = @($apiKey)

        # convert as jwt?
        if ($options.AsJWT) {
            try {
                $payload = ConvertFrom-PodeJwt -Token $apiKey -Secret $options.Secret
                Test-PodeJwt -Payload $payload
            }
            catch {
                if ($_.Exception.Message -ilike '*jwt*') {
                    return @{
                        Message = $_.Exception.Message
                        Code    = 400
                    }
                }

                throw
            }

            $result = @($payload)
        }

        # return the result
        return $result
    }
}

function Get-PodeAuthBearerType {
    return {
        param($options)

        # get the auth header
        $header = (Get-PodeHeader -Name 'Authorization')
        if ($null -eq $header) {
            return @{
                Message   = 'No Authorization header found'
                Challenge = (New-PodeAuthBearerChallenge -Scopes $options.Scopes -ErrorType invalid_request)
                Code      = 400
            }
        }

        # ensure the first atom is bearer
        $atoms = $header -isplit '\s+'
        if ($atoms.Length -lt 2) {
            return @{
                Message   = 'Invalid Authorization header'
                Challenge = (New-PodeAuthBearerChallenge -Scopes $options.Scopes -ErrorType invalid_request)
                Code      = 400
            }
        }

        if ($atoms[0] -ine $options.HeaderTag) {
            return @{
                Message   = "Authorization header is not $($options.HeaderTag)"
                Challenge = (New-PodeAuthBearerChallenge -Scopes $options.Scopes -ErrorType invalid_request)
                Code      = 400
            }
        }

        # 400 if no token
        $token = $atoms[1]
        if ([string]::IsNullOrWhiteSpace($token)) {
            return @{
                Message = 'No Bearer token found'
                Code    = 400
            }
        }

        # build the result
        $token = $token.Trim()
        $result = @($token)

        # convert as jwt?
        if ($options.AsJWT) {
            try {
                $payload = ConvertFrom-PodeJwt -Token $token -Secret $options.Secret
                Test-PodeJwt -Payload $payload
            }
            catch {
                if ($_.Exception.Message -ilike '*jwt*') {
                    return @{
                        Message = $_.Exception.Message
                        #https://www.rfc-editor.org/rfc/rfc6750 Bearer token should return 401
                        Code    = 401
                    }
                }

                throw
            }

            $result = @($payload)
        }

        # return the result
        return $result
    }
}

function Get-PodeAuthBearerPostValidator {
    return {
        param($token, $result, $options)

        # if there's no user, fail with challenge
        if (($null -eq $result) -or ($null -eq $result.User)) {
            return @{
                Message   = 'User not found'
                Challenge = (New-PodeAuthBearerChallenge -Scopes $options.Scopes -ErrorType invalid_token)
                Code      = 401
            }
        }

        # check for an error and description
        if (![string]::IsNullOrWhiteSpace($result.Error)) {
            return @{
                Message   = 'Authorization failed'
                Challenge = (New-PodeAuthBearerChallenge -Scopes $options.Scopes -ErrorType $result.Error -ErrorDescription $result.ErrorDescription)
                Code      = 401
            }
        }

        # check the scopes
        $hasAuthScopes = (($null -ne $options.Scopes) -and ($options.Scopes.Length -gt 0))
        $hasTokenScope = ![string]::IsNullOrWhiteSpace($result.Scope)

        # 403 if we have auth scopes but no token scope
        if ($hasAuthScopes -and !$hasTokenScope) {
            return @{
                Message   = 'Invalid Scope'
                Challenge = (New-PodeAuthBearerChallenge -Scopes $options.Scopes -ErrorType insufficient_scope)
                Code      = 403
            }
        }

        # 403 if we have both, but token not in auth scope
        if ($hasAuthScopes -and $hasTokenScope -and ($options.Scopes -notcontains $result.Scope)) {
            return @{
                Message   = 'Invalid Scope'
                Challenge = (New-PodeAuthBearerChallenge -Scopes $options.Scopes -ErrorType insufficient_scope)
                Code      = 403
            }
        }

        # return result
        return $result
    }
}

function New-PodeAuthBearerChallenge {
    param(
        [Parameter()]
        [string[]]
        $Scopes,

        [Parameter()]
        [ValidateSet('', 'invalid_request', 'invalid_token', 'insufficient_scope')]
        [string]
        $ErrorType,

        [Parameter()]
        [string]
        $ErrorDescription
    )

    $items = @()
    if (($null -ne $Scopes) -and ($Scopes.Length -gt 0)) {
        $items += "scope=`"$($Scopes -join ' ')`""
    }

    if (![string]::IsNullOrWhiteSpace($ErrorType)) {
        $items += "error=`"$($ErrorType)`""
    }

    if (![string]::IsNullOrWhiteSpace($ErrorDescription)) {
        $items += "error_description=`"$($ErrorDescription)`""
    }

    return ($items -join ', ')
}

function Get-PodeAuthDigestType {
    return {
        param($options)

        # get the auth header - send challenge if missing
        $header = (Get-PodeHeader -Name 'Authorization')
        if ($null -eq $header) {
            return @{
                Message   = 'No Authorization header found'
                Challenge = (New-PodeAuthDigestChallenge)
                Code      = 401
            }
        }

        # if auth header isn't digest send challenge
        $atoms = $header -isplit '\s+'
        if ($atoms.Length -lt 2) {
            return @{
                Message = 'Invalid Authorization header'
                Code    = 400
            }
        }

        if ($atoms[0] -ine $options.HeaderTag) {
            return @{
                Message   = "Authorization header is not $($options.HeaderTag)"
                Challenge = (New-PodeAuthDigestChallenge)
                Code      = 401
            }
        }

        # parse the other atoms of the header (after the scheme), return 400 if none
        $params = ConvertFrom-PodeAuthDigestHeader -Parts ($atoms[1..$($atoms.Length - 1)])
        if ($params.Count -eq 0) {
            return @{
                Message = 'Invalid Authorization header'
                Code    = 400
            }
        }

        # if no username then 401 and challenge
        if ([string]::IsNullOrWhiteSpace($params.username)) {
            return @{
                Message   = 'Authorization header is missing username'
                Challenge = (New-PodeAuthDigestChallenge)
                Code      = 401
            }
        }

        # return 400 if domain doesnt match request domain
        if ($WebEvent.Path -ine $params.uri) {
            return @{
                Message = 'Invalid Authorization header'
                Code    = 400
            }
        }

        # return data for calling validator
        return @($params.username, $params)
    }
}

function Get-PodeAuthDigestPostValidator {
    return {
        param($username, $params, $result, $options)

        # if there's no user or password, fail with challenge
        if (($null -eq $result) -or ($null -eq $result.User) -or [string]::IsNullOrWhiteSpace($result.Password)) {
            return @{
                Message   = 'User not found'
                Challenge = (New-PodeAuthDigestChallenge)
                Code      = 401
            }
        }

        # generate the first hash
        $hash1 = Invoke-PodeMD5Hash -Value "$($params.username):$($params.realm):$($result.Password)"

        # generate the second hash
        $hash2 = Invoke-PodeMD5Hash -Value "$($WebEvent.Method.ToUpperInvariant()):$($params.uri)"

        # generate final hash
        $final = Invoke-PodeMD5Hash -Value "$($hash1):$($params.nonce):$($params.nc):$($params.cnonce):$($params.qop):$($hash2)"

        # compare final hash to client response
        if ($final -ne $params.response) {
            return @{
                Message   = 'Hashes failed to match'
                Challenge = (New-PodeAuthDigestChallenge)
                Code      = 401
            }
        }

        # hashes are valid, remove password and return result
        $null = $result.Remove('Password')
        return $result
    }
}

function ConvertFrom-PodeAuthDigestHeader {
    param(
        [Parameter()]
        [string[]]
        $Parts
    )

    if (($null -eq $Parts) -or ($Parts.Length -eq 0)) {
        return @{}
    }

    $obj = @{}
    $value = ($Parts -join ' ')

    @($value -isplit ',(?=(?:[^"]|"[^"]*")*$)') | ForEach-Object {
        if ($_ -imatch '(?<name>\w+)=["]?(?<value>[^"]+)["]?$') {
            $obj[$Matches['name']] = $Matches['value']
        }
    }

    return $obj
}

function New-PodeAuthDigestChallenge {
    $items = @('qop="auth"', 'algorithm="MD5"', "nonce=`"$(New-PodeGuid -Secure -NoDashes)`"")
    return ($items -join ', ')
}

function Get-PodeAuthFormType {
    return {
        param($options)

        # get user/pass keys to get from payload
        $userField = $options.Fields.Username
        $passField = $options.Fields.Password

        # get the user/pass
        $username = $WebEvent.Data.$userField
        $password = $WebEvent.Data.$passField

        # if either are empty, fail auth
        if ([string]::IsNullOrWhiteSpace($username) -or [string]::IsNullOrWhiteSpace($password)) {
            return @{
                Message = 'Username or Password not supplied'
                Code    = 401
            }
        }

        # build the result
        $result = @($username, $password)

        # convert to credential?
        if ($options.AsCredential) {
            $passSecure = ConvertTo-SecureString -String $password -AsPlainText -Force
            $creds = [pscredential]::new($username, $passSecure)
            $result = @($creds)
        }

        # return data for calling validator
        return $result
    }
}

<#
.SYNOPSIS
    Authenticates a user based on a username and password provided as parameters.

.DESCRIPTION
    This function finds a user whose username matches the provided username, and checks the user's password.
    If the password is correct, it converts the user into a hashtable and checks if the user is valid for any users/groups specified by the options parameter. If the user is valid, it returns a hashtable containing the user object. If the user is not valid, it returns a hashtable with a message indicating that the user is not authorized to access the website.

.PARAMETER username
    The username of the user to authenticate.

.PARAMETER password
    The password of the user to authenticate.

.PARAMETER options
    A hashtable containing options for the function. It can include the following keys:
    - FilePath: The path to the JSON file containing user data.
    - HmacSecret: The secret key for computing a HMAC-SHA256 hash of the password.
    - Users: A list of valid users.
    - Groups: A list of valid groups.
    - ScriptBlock: A script block for additional validation.

.EXAMPLE
    Get-PodeAuthUserFileMethod -username "admin" -password "password123" -options @{ FilePath = "C:\Users.json"; HmacSecret = "secret"; Users = @("admin"); Groups = @("Administrators"); ScriptBlock = { param($user) $user.Name -eq "admin" } }

    This example authenticates a user with username "admin" and password "password123". It reads user data from the JSON file at "C:\Users.json", computes a HMAC-SHA256 hash of the password using "secret" as the secret key, and checks if the user is in the "admin" user or "Administrators" group. It also performs additional validation using a script block that checks if the user's name is "admin".
#>
function Get-PodeAuthUserFileMethod {
    return {
        param($username, $password, $options)

        # using pscreds?
        if (($null -eq $options) -and ($username -is [pscredential])) {
            $_username = ([pscredential]$username).UserName
            $_password = ([pscredential]$username).GetNetworkCredential().Password
            $_options = [hashtable]$password
        }
        else {
            $_username = $username
            $_password = $password
            $_options = $options
        }

        # load the file
        $users = (Get-Content -Path $_options.FilePath -Raw | ConvertFrom-Json)

        # find the user by username - only use the first one
        $user = @(foreach ($_user in $users) {
                if ($_user.Username -ieq $_username) {
                    $_user
                    break
                }
            })[0]

        # fail if no user
        if ($null -eq $user) {
            return @{ Message = 'You are not authorised to access this website' }
        }

        # check the user's password
        if (![string]::IsNullOrWhiteSpace($_options.HmacSecret)) {
            $hash = Invoke-PodeHMACSHA256Hash -Value $_password -Secret $_options.HmacSecret
        }
        else {
            $hash = Invoke-PodeSHA256Hash -Value $_password
        }

        if ($user.Password -ne $hash) {
            return @{ Message = 'You are not authorised to access this website' }
        }

        # convert the user to a hashtable
        $user = @{
            Name     = $user.Name
            Username = $user.Username
            Email    = $user.Email
            Groups   = $user.Groups
            Metadata = $user.Metadata
        }

        # is the user valid for any users/groups?
        if (!(Test-PodeAuthUserGroup -User $user -Users $_options.Users -Groups $_options.Groups)) {
            return @{ Message = 'You are not authorised to access this website' }
        }

        $result = @{ User = $user }

        # call additional scriptblock if supplied
        if ($null -ne $_options.ScriptBlock.Script) {
            $result = Invoke-PodeAuthInbuiltScriptBlock -User $result.User -ScriptBlock $_options.ScriptBlock.Script -UsingVariables $_options.ScriptBlock.UsingVariables
        }

        # return final result, this could contain a user obj, or an error message from custom scriptblock
        return $result
    }
}

function Get-PodeAuthWindowsADMethod {
    return {
        param($username, $password, $options)

        # using pscreds?
        if (($null -eq $options) -and ($username -is [pscredential])) {
            $_username = ([pscredential]$username).UserName
            $_password = ([pscredential]$username).GetNetworkCredential().Password
            $_options = [hashtable]$password
        }
        else {
            $_username = $username
            $_password = $password
            $_options = $options
        }

        # parse username to remove domains
        $_username = (($_username -split '@')[0] -split '\\')[-1]

        # validate and retrieve the AD user
        $noGroups = $_options.NoGroups
        $directGroups = $_options.DirectGroups
        $keepCredential = $_options.KeepCredential

        $result = Get-PodeAuthADResult `
            -Server $_options.Server `
            -Domain $_options.Domain `
            -SearchBase $_options.SearchBase `
            -Username $_username `
            -Password $_password `
            -Provider $_options.Provider `
            -NoGroups:$noGroups `
            -DirectGroups:$directGroups `
            -KeepCredential:$keepCredential

        # if there's a message, fail and return the message
        if (![string]::IsNullOrWhiteSpace($result.Message)) {
            return $result
        }

        # if there's no user, then, err, oops
        if (Test-PodeIsEmpty $result.User) {
            return @{ Message = 'An unexpected error occured' }
        }

        # is the user valid for any users/groups - if not, error!
        if (!(Test-PodeAuthUserGroup -User $result.User -Users $_options.Users -Groups $_options.Groups)) {
            return @{ Message = 'You are not authorised to access this website' }
        }

        # call additional scriptblock if supplied
        if ($null -ne $_options.ScriptBlock.Script) {
            $result = Invoke-PodeAuthInbuiltScriptBlock -User $result.User -ScriptBlock $_options.ScriptBlock.Script -UsingVariables $_options.ScriptBlock.UsingVariables
        }

        # return final result, this could contain a user obj, or an error message from custom scriptblock
        return $result
    }
}

function Invoke-PodeAuthInbuiltScriptBlock {
    param(
        [Parameter(Mandatory = $true)]
        [hashtable]
        $User,

        [Parameter(Mandatory = $true)]
        [scriptblock]
        $ScriptBlock,

        [Parameter()]
        $UsingVariables
    )

    return (Invoke-PodeScriptBlock -ScriptBlock $ScriptBlock -Arguments $User -UsingVariables $UsingVariables -Return)
}

function Get-PodeAuthWindowsLocalMethod {
    return {
        param($username, $password, $options)

        # using pscreds?
        if (($null -eq $options) -and ($username -is [pscredential])) {
            $_username = ([pscredential]$username).UserName
            $_password = ([pscredential]$username).GetNetworkCredential().Password
            $_options = [hashtable]$password
        }
        else {
            $_username = $username
            $_password = $password
            $_options = $options
        }

        $user = @{
            UserType           = 'Local'
            AuthenticationType = 'WinNT'
            Username           = $_username
            Name               = [string]::Empty
            Fqdn               = $PodeContext.Server.ComputerName
            Domain             = 'localhost'
            Groups             = @()
        }

        Add-Type -AssemblyName System.DirectoryServices.AccountManagement -ErrorAction Stop
        $context = [System.DirectoryServices.AccountManagement.PrincipalContext]::new('Machine', $PodeContext.Server.ComputerName)
        $valid = $context.ValidateCredentials($_username, $_password)

        if (!$valid) {
            return @{ Message = 'Invalid credentials supplied' }
        }

        try {
            $tmpUsername = $_username -replace '\\', '/'
            if ($_username -inotlike "$($PodeContext.Server.ComputerName)*") {
                $tmpUsername = "$($PodeContext.Server.ComputerName)/$($_username)"
            }

            $ad = [adsi]"WinNT://$($tmpUsername)"
            $user.Name = @($ad.FullName)[0]

            if (!$_options.NoGroups) {
                $cmd = "`$ad = [adsi]'WinNT://$($tmpUsername)'; @(`$ad.Groups() | Foreach-Object { `$_.GetType().InvokeMember('Name', 'GetProperty', `$null, `$_, `$null) })"
                $user.Groups = [string[]](powershell -c $cmd)
            }
        }
        finally {
            Close-PodeDisposable -Disposable $ad -Close
        }

        # is the user valid for any users/groups - if not, error!
        if (!(Test-PodeAuthUserGroup -User $user -Users $_options.Users -Groups $_options.Groups)) {
            return @{ Message = 'You are not authorised to access this website' }
        }

        $result = @{ User = $user }

        # call additional scriptblock if supplied
        if ($null -ne $_options.ScriptBlock.Script) {
            $result = Invoke-PodeAuthInbuiltScriptBlock -User $result.User -ScriptBlock $_options.ScriptBlock.Script -UsingVariables $_options.ScriptBlock.UsingVariables
        }

        # return final result, this could contain a user obj, or an error message from custom scriptblock
        return $result
    }
}

function Get-PodeAuthWindowsADIISMethod {
    return {
        param($token, $options)

        # get the close handler
        $win32Handler = Add-Type -Name Win32CloseHandle -PassThru -MemberDefinition @'
            [DllImport("kernel32.dll", SetLastError = true)]
            public static extern bool CloseHandle(IntPtr handle);
'@

        try {
            # parse the auth token and get the user
            $winAuthToken = [System.IntPtr][Int]"0x$($token)"
            $winIdentity = [System.Security.Principal.WindowsIdentity]::new($winAuthToken, 'Windows')

            # get user and domain
            $username = ($winIdentity.Name -split '\\')[-1]
            $domain = ($winIdentity.Name -split '\\')[0]

            # create base user object
            $user = @{
                UserType           = 'Domain'
                Identity           = @{
                    AccessToken = $winIdentity.AccessToken
                }
                AuthenticationType = $winIdentity.AuthenticationType
                DistinguishedName  = [string]::Empty
                Username           = $username
                Name               = [string]::Empty
                Email              = [string]::Empty
                Fqdn               = [string]::Empty
                Domain             = $domain
                Groups             = @()
            }

            # if the domain isn't local, attempt AD user
            if (![string]::IsNullOrWhiteSpace($domain) -and (@('.', $PodeContext.Server.ComputerName) -inotcontains $domain)) {
                # get the server's fdqn (and name/email)
                try {
                    # Open ADSISearcher and change context to given domain
                    $searcher = [adsisearcher]''
                    $searcher.SearchRoot = [adsi]"LDAP://$($domain)"
                    $searcher.Filter = "ObjectSid=$($winIdentity.User.Value.ToString())"

                    # Query the ADSISearcher for the above defined SID
                    $ad = $searcher.FindOne()

                    # Save it to our existing array for later usage
                    $user.DistinguishedName = @($ad.Properties.distinguishedname)[0]
                    $user.Name = @($ad.Properties.name)[0]
                    $user.Email = @($ad.Properties.mail)[0]
                    $user.Fqdn = (Get-PodeADServerFromDistinguishedName -DistinguishedName $user.DistinguishedName)
                }
                finally {
                    Close-PodeDisposable -Disposable $searcher
                }

                try {
                    if (!$options.NoGroups) {

                        # open a new connection
                        $result = (Open-PodeAuthADConnection -Server $user.Fqdn -Domain $domain -Provider $options.Provider)
                        if (!$result.Success) {
                            return @{ Message = "Failed to connect to Domain Server '$($user.Fqdn)' of $domain for $($user.DistinguishedName)." }
                        }

                        # get the connection
                        $connection = $result.Connection

                        # get the users groups
                        $directGroups = $options.DirectGroups
                        $user.Groups = (Get-PodeAuthADGroup -Connection $connection -DistinguishedName $user.DistinguishedName -Username $user.Username -Direct:$directGroups -Provider $options.Provider)
                    }
                }
                finally {
                    if ($null -ne $connection) {
                        Close-PodeDisposable -Disposable $connection.Searcher
                        Close-PodeDisposable -Disposable $connection.Entry -Close
                        $connection.Credential = $null
                    }
                }
            }

            # otherwise, get details of local user
            else {
                # get the user's name and groups
                try {
                    $user.UserType = 'Local'

                    if (!$options.NoLocalCheck) {
                        $localUser = $winIdentity.Name -replace '\\', '/'
                        $ad = [adsi]"WinNT://$($localUser)"
                        $user.Name = @($ad.FullName)[0]

                        # dirty, i know :/ - since IIS runs using pwsh, the InvokeMember part fails
                        # we can safely call windows powershell here, as IIS is only on windows.
                        if (!$options.NoGroups) {
                            $cmd = "`$ad = [adsi]'WinNT://$($localUser)'; @(`$ad.Groups() | Foreach-Object { `$_.GetType().InvokeMember('Name', 'GetProperty', `$null, `$_, `$null) })"
                            $user.Groups = [string[]](powershell -c $cmd)
                        }
                    }
                }
                finally {
                    Close-PodeDisposable -Disposable $ad -Close
                }
            }
        }
        catch {
            $_ | Write-PodeErrorLog
            return @{ Message = 'Failed to retrieve user using Authentication Token' }
        }
        finally {
            $win32Handler::CloseHandle($winAuthToken)
        }

        # is the user valid for any users/groups - if not, error!
        if (!(Test-PodeAuthUserGroup -User $user -Users $options.Users -Groups $options.Groups)) {
            return @{ Message = 'You are not authorised to access this website' }
        }

        $result = @{ User = $user }

        # call additional scriptblock if supplied
        if ($null -ne $options.ScriptBlock.Script) {
            $result = Invoke-PodeAuthInbuiltScriptBlock -User $result.User -ScriptBlock $options.ScriptBlock.Script -UsingVariables $options.ScriptBlock.UsingVariables
        }

        # return final result, this could contain a user obj, or an error message from custom scriptblock
        return $result
    }
}

<#
    .SYNOPSIS
    Authenticates a user based on group membership or specific user authorization.

    .DESCRIPTION
    This function checks if a given user is authorized based on supplied lists of users and groups. The user is considered authorized if their username is directly specified in the list of users, or if they are a member of any of the specified groups.

    .PARAMETER User
    A hashtable representing the user, expected to contain at least the 'Username' and 'Groups' keys.

    .PARAMETER Users
    An optional array of usernames. If specified, the function checks if the user's username exists in this list.

    .PARAMETER Groups
    An optional array of group names. If specified, the function checks if the user belongs to any of these groups.

    .EXAMPLE
    $user = @{ Username = 'john.doe'; Groups = @('Administrators', 'Users') }
    $authorizedUsers = @('john.doe', 'jane.doe')
    $authorizedGroups = @('Administrators')

    Test-PodeAuthUserGroup -User $user -Users $authorizedUsers -Groups $authorizedGroups
    # Returns true if John Doe is either listed as an authorized user or is a member of an authorized group.
#>
function Test-PodeAuthUserGroup {
    param(
        [Parameter(Mandatory = $true)]
        [hashtable]
        $User,

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

        [Parameter()]
        [string[]]
        $Groups
    )

    $haveUsers = (($null -ne $Users) -and ($Users.Length -gt 0))
    $haveGroups = (($null -ne $Groups) -and ($Groups.Length -gt 0))

    # if there are no groups/users supplied, return user is valid
    if (!$haveUsers -and !$haveGroups) {
        return $true
    }

    # before checking supplied groups, is the user in the supplied list of authorised users?
    if ($haveUsers -and (@($Users) -icontains $User.Username)) {
        return $true
    }

    # if there are groups supplied, check the user is a member of one
    if ($haveGroups) {
        foreach ($group in $Groups) {
            if (@($User.Groups) -icontains $group) {
                return $true
            }
        }
    }

    return $false
}

function Invoke-PodeAuthValidation {
    param(
        [Parameter(Mandatory = $true)]
        [string]
        $Name
    )

    # get auth method
    $auth = $PodeContext.Server.Authentications.Methods[$Name]

    # if it's a merged auth, re-call this function and check against "succeed" value
    if ($auth.Merged) {
        $results = @{}
        foreach ($authName in $auth.Authentications) {
            $result = Invoke-PodeAuthValidation -Name $authName

            # if the auth is trying to redirect, we need to bubble the this back now
            if ($result.Redirected) {
                return $result
            }

            # if the auth passed, and we only need one auth to pass, return current result
            if ($result.Success -and $auth.PassOne) {
                return $result
            }

            # if the auth failed, but we need all to pass, return current result
            if (!$result.Success -and !$auth.PassOne) {
                return $result
            }

            # remember result if we need all to pass
            if (!$auth.PassOne) {
                $results[$authName] = $result
            }
        }
        # if the last auth failed, and we only need one auth to pass, set failure and return
        if (!$result.Success -and $auth.PassOne) {
            return $result
        }

        # if the last auth succeeded, and we need all to pass, merge users/headers and return result
        if ($result.Success -and !$auth.PassOne) {
            # invoke scriptblock, or use result of merge default
            if ($null -ne $auth.ScriptBlock.Script) {
                $result = Invoke-PodeAuthInbuiltScriptBlock -User $results -ScriptBlock $auth.ScriptBlock.Script -UsingVariables $auth.ScriptBlock.UsingVariables
            }
            else {
                $result = $results[$auth.MergeDefault]
            }

            # reset default properties and return
            $result.Success = $true
            $result.Auth = $results.Keys
            return $result
        }

        # default failure
        return @{
            Success    = $false
            StatusCode = 500
        }
    }

    # main auth validation logic
    $result = (Test-PodeAuthValidation -Name $Name)
    $result.Auth = $Name
    return $result
}

function Test-PodeAuthValidation {
    param(
        [Parameter(Mandatory = $true)]
        [string]
        $Name
    )

    try {
        # get auth method
        $auth = $PodeContext.Server.Authentications.Methods[$Name]

        # auth result
        $result = $null

        # run pre-auth middleware
        if ($null -ne $auth.Scheme.Middleware) {
            if (!(Invoke-PodeMiddleware -Middleware $auth.Scheme.Middleware)) {
                return @{
                    Success = $false
                }
            }
        }

        # run auth scheme script to parse request for data
        $_args = @(Merge-PodeScriptblockArguments -ArgumentList $auth.Scheme.Arguments -UsingVariables $auth.Scheme.ScriptBlock.UsingVariables)

        # call inner schemes first
        if ($null -ne $auth.Scheme.InnerScheme) {
            $schemes = @()

            $_scheme = $auth.Scheme
            $_inner = @(while ($null -ne $_scheme.InnerScheme) {
                    $_scheme = $_scheme.InnerScheme
                    $_scheme
                })

            for ($i = $_inner.Length - 1; $i -ge 0; $i--) {
                $_tmp_args = @(Merge-PodeScriptblockArguments -ArgumentList $_inner[$i].Arguments -UsingVariables $_inner[$i].ScriptBlock.UsingVariables)

                $_tmp_args += , $schemes
                $result = (Invoke-PodeScriptBlock -ScriptBlock $_inner[$i].ScriptBlock.Script -Arguments $_tmp_args -Return -Splat)
                if ($result -is [hashtable]) {
                    break
                }

                $schemes += , $result
                $result = $null
            }

            $_args += , $schemes
        }

        if ($null -eq $result) {
            $result = (Invoke-PodeScriptBlock -ScriptBlock $auth.Scheme.ScriptBlock.Script -Arguments $_args -Return -Splat)
        }

        # if data is a hashtable, then don't call validator (parser either failed, or forced a success)
        if ($result -isnot [hashtable]) {
            $original = $result

            $_args = @($result) + @($auth.Arguments)
            $result = (Invoke-PodeScriptBlock -ScriptBlock $auth.ScriptBlock -Arguments $_args -UsingVariables $auth.UsingVariables -Return -Splat)

            # if we have user, then run post validator if present
            if ([string]::IsNullOrEmpty($result.Code) -and ($null -ne $auth.Scheme.PostValidator.Script)) {
                $_args = @($original) + @($result) + @($auth.Scheme.Arguments)
                $result = (Invoke-PodeScriptBlock -ScriptBlock $auth.Scheme.PostValidator.Script -Arguments $_args -UsingVariables $auth.Scheme.PostValidator.UsingVariables -Return -Splat)
            }
        }

        # is the auth trying to redirect ie: oauth?
        if ($result.IsRedirected) {
            return @{
                Success    = $false
                Redirected = $true
            }
        }

        # if there's no result, or no user, then the auth failed - but allow auth if anon enabled
        if (($null -eq $result) -or ($result.Count -eq 0) -or (Test-PodeIsEmpty $result.User)) {
            $code = (Protect-PodeValue -Value $result.Code -Default 401)

            # set the www-auth header
            $validCode = (($code -eq 401) -or ![string]::IsNullOrEmpty($result.Challenge))

            if ($validCode) {
                if ($null -eq $result) {
                    $result = @{}
                }

                if ($null -eq $result.Headers) {
                    $result.Headers = @{}
                }

                if (![string]::IsNullOrWhiteSpace($auth.Scheme.Name) -and !$result.Headers.ContainsKey('WWW-Authenticate')) {
                    $authHeader = Get-PodeAuthWwwHeaderValue -Name $auth.Scheme.Name -Realm $auth.Scheme.Realm -Challenge $result.Challenge
                    $result.Headers['WWW-Authenticate'] = $authHeader
                }
            }

            return @{
                Success         = $false
                StatusCode      = $code
                Description     = $result.Message
                Headers         = $result.Headers
                FailureRedirect = [bool]$result.IsErrored
            }
        }

        # authentication was successful
        return @{
            Success = $true
            User    = $result.User
            Headers = $result.Headers
        }
    }
    catch {
        $_ | Write-PodeErrorLog
        return @{
            Success    = $false
            StatusCode = 500
            Exception  = $_
        }
    }
}

function Get-PodeAuthMiddlewareScript {
    return {
        param($opts)

        return Test-PodeAuthInternal `
            -Name $opts.Name `
            -Login:($opts.Login) `
            -Logout:($opts.Logout) `
            -AllowAnon:($opts.Anon)
    }
}

function Test-PodeAuthInternal {
    [CmdletBinding()]
    [OutputType([bool])]
    param(
        [Parameter(Mandatory = $true)]
        [string]
        $Name,

        [switch]
        $Login,

        [switch]
        $Logout,

        [switch]
        $AllowAnon
    )

    # get the auth method
    $auth = $PodeContext.Server.Authentications.Methods[$Name]

    # check for logout command
    if ($Logout) {
        Remove-PodeAuthSession

        if ($PodeContext.Server.Sessions.Info.UseHeaders) {
            return Set-PodeAuthStatus `
                -StatusCode 401 `
                -Name $Name `
                -NoSuccessRedirect
        }
        else {
            $auth.Failure.Url = (Protect-PodeValue -Value $auth.Failure.Url -Default $WebEvent.Request.Url.AbsolutePath)
            return Set-PodeAuthStatus `
                -StatusCode 302 `
                -Name $Name `
                -NoSuccessRedirect
        }
    }

    # if the session already has a user/isAuth'd, then skip auth - or allow anon
    if (Test-PodeSessionsInUse) {
        # existing session auth'd
        if (Test-PodeAuthUser) {
            $WebEvent.Auth = $WebEvent.Session.Data.Auth
            return Set-PodeAuthStatus `
                -Name $Name `
                -LoginRoute:($Login) `
                -NoSuccessRedirect
        }

        # if we're allowing anon access, and using sessions, then stop here - as a session will be created from a login route for auth'ing users
        if ($AllowAnon) {
            if (!(Test-PodeIsEmpty $WebEvent.Session.Data.Auth)) {
                Revoke-PodeSession
            }

            return $true
        }
    }

    # check if the login flag is set, in which case just return and load a login get-page (allowing anon access)
    if ($Login -and !$PodeContext.Server.Sessions.Info.UseHeaders -and ($WebEvent.Method -ieq 'get')) {
        if (!(Test-PodeIsEmpty $WebEvent.Session.Data.Auth)) {
            Revoke-PodeSession
        }

        return $true
    }

    try {
        $result = Invoke-PodeAuthValidation -Name $Name
    }
    catch {
        $_ | Write-PodeErrorLog
        return Set-PodeAuthStatus `
            -StatusCode 500 `
            -Description $_.Exception.Message `
            -Name $Name
    }

    # did the auth force a redirect?
    if ($result.Redirected) {
        $success = Get-PodeAuthSuccessInfo -Name $Name
        Set-PodeAuthRedirectUrl -UseOrigin:($success.UseOrigin)
        return $false
    }

    # if auth failed, are we allowing anon access?
    if (!$result.Success -and $AllowAnon) {
        return $true
    }

    # if auth failed, set appropriate response headers/redirects
    if (!$result.Success) {
        return Set-PodeAuthStatus `
            -StatusCode $result.StatusCode `
            -Description $result.Description `
            -Headers $result.Headers `
            -Name $Name `
            -LoginRoute:$Login `
            -NoFailureRedirect:($result.FailureRedirect)
    }

    # if auth passed, assign the user to the session
    $WebEvent.Auth = [ordered]@{
        User            = $result.User
        IsAuthenticated = $true
        IsAuthorised    = $true
        Store           = !$auth.Sessionless
        Name            = $result.Auth
    }

    # successful auth
    $authName = $null
    if ($auth.Merged -and !$auth.PassOne) {
        $authName = $Name
    }
    else {
        $authName = @($result.Auth)[0]
    }

    return Set-PodeAuthStatus `
        -Headers $result.Headers `
        -Name $authName `
        -LoginRoute:$Login
}

function Get-PodeAuthWwwHeaderValue {
    param(
        [Parameter()]
        [string]
        $Name,

        [Parameter()]
        [string]
        $Realm,

        [Parameter()]
        [string]
        $Challenge
    )

    if ([string]::IsNullOrWhiteSpace($Name)) {
        return [string]::Empty
    }

    $header = $Name
    if (![string]::IsNullOrWhiteSpace($Realm)) {
        $header += " realm=`"$($Realm)`""
    }

    if (![string]::IsNullOrWhiteSpace($Challenge)) {
        $header += ", $($Challenge)"
    }

    return $header
}

function Remove-PodeAuthSession {
    # blank out the auth
    $WebEvent.Auth = @{}

    # if a session auth is found, blank it
    if (!(Test-PodeIsEmpty $WebEvent.Session.Data.Auth)) {
        $WebEvent.Session.Data.Remove('Auth')
    }

    # Delete the current session (remove from store, blank it, and remove from Response)
    Revoke-PodeSession
}

function Get-PodeAuthFailureInfo {
    param(
        [Parameter(Mandatory = $true)]
        [string]
        $Name,

        [Parameter()]
        [hashtable]
        $Info,

        [Parameter()]
        [string]
        $BaseName
    )

    # base name
    if ([string]::IsNullOrEmpty($BaseName)) {
        $BaseName = $Name
    }

    # get auth method
    $auth = $PodeContext.Server.Authentications.Methods[$Name]

    # cached failure?
    if ($null -ne $auth.Cache.Failure) {
        return $auth.Cache.Failure
    }

    # find failure info
    if ($null -eq $Info) {
        $Info = @{
            Url     = $auth.Failure.Url
            Message = $auth.Failure.Message
        }
    }

    if ([string]::IsNullOrEmpty($Info.Url)) {
        $Info.Url = $auth.Failure.Url
    }

    if ([string]::IsNullOrEmpty($Info.Message)) {
        $Info.Message = $auth.Failure.Message
    }

    if ((![string]::IsNullOrEmpty($Info.Url) -and ![string]::IsNullOrEmpty($Info.Message)) -or [string]::IsNullOrEmpty($auth.Parent)) {
        $PodeContext.Server.Authentications.Methods[$BaseName].Cache.Failure = $Info
        return $Info
    }

    return (Get-PodeAuthFailureInfo -Name $auth.Parent -Info $Info -BaseName $BaseName)
}

function Get-PodeAuthSuccessInfo {
    param(
        [Parameter(Mandatory = $true)]
        [string]
        $Name,

        [Parameter()]
        [hashtable]
        $Info,

        [Parameter()]
        [string]
        $BaseName
    )

    # base name
    if ([string]::IsNullOrEmpty($BaseName)) {
        $BaseName = $Name
    }

    # get auth method
    $auth = $PodeContext.Server.Authentications.Methods[$Name]

    # cached success?
    if ($null -ne $auth.Cache.Success) {
        return $auth.Cache.Success
    }

    # find success info
    if ($null -eq $Info) {
        $Info = @{
            Url       = $auth.Success.Url
            UseOrigin = $auth.Success.UseOrigin
        }
    }

    if ([string]::IsNullOrEmpty($Info.Url)) {
        $Info.Url = $auth.Success.Url
    }

    if (!$Info.UseOrigin) {
        $Info.UseOrigin = $auth.Success.UseOrigin
    }

    if ((![string]::IsNullOrEmpty($Info.Url) -and $Info.UseOrigin) -or [string]::IsNullOrEmpty($auth.Parent)) {
        $PodeContext.Server.Authentications.Methods[$BaseName].Cache.Success = $Info
        return $Info
    }

    return (Get-PodeAuthSuccessInfo -Name $auth.Parent -Info $Info -BaseName $BaseName)
}

function Set-PodeAuthStatus {
    param(
        [Parameter(Mandatory = $true)]
        [string]
        $Name,

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

        [Parameter()]
        [string]
        $Description,

        [Parameter()]
        [hashtable]
        $Headers,

        [switch]
        $LoginRoute,

        [switch]
        $NoSuccessRedirect,

        [switch]
        $NoFailureRedirect
    )

    # if we have any headers, set them
    if (($null -ne $Headers) -and ($Headers.Count -gt 0)) {
        foreach ($key in $Headers.Keys) {
            Set-PodeHeader -Name $key -Value $Headers[$key]
        }
    }

    # get auth method
    $auth = $PodeContext.Server.Authentications.Methods[$Name]

    # get Success object from auth
    $success = Get-PodeAuthSuccessInfo -Name $Name

    # if a statuscode supplied, assume failure
    if ($StatusCode -gt 0) {
        # get Failure object from auth
        $failure = Get-PodeAuthFailureInfo -Name $Name

        # override description with the failureMessage if supplied
        $Description = (Protect-PodeValue -Value $failure.Message -Default $Description)

        # add error to flash
        if ($LoginRoute -and !$auth.Sessionless -and ![string]::IsNullOrWhiteSpace($Description)) {
            Add-PodeFlashMessage -Name 'auth-error' -Message $Description
        }

        # check if we have a failure url redirect
        if (!$NoFailureRedirect -and ![string]::IsNullOrWhiteSpace($failure.Url)) {
            Set-PodeAuthRedirectUrl -UseOrigin:($success.UseOrigin)
            Move-PodeResponseUrl -Url $failure.Url
        }
        else {
            Set-PodeResponseStatus -Code $StatusCode -Description $Description
        }

        return $false
    }

    # if no statuscode, success, so check if we have a success url redirect (but only for auto-login routes)
    if (!$NoSuccessRedirect -or $LoginRoute) {
        $url = Get-PodeAuthRedirectUrl -Url $success.Url -UseOrigin:($success.UseOrigin)
        if (![string]::IsNullOrWhiteSpace($url)) {
            Move-PodeResponseUrl -Url $url
            return $false
        }
    }

    return $true
}

function Get-PodeADServerFromDistinguishedName {
    param(
        [Parameter()]
        [string]
        $DistinguishedName
    )

    if ([string]::IsNullOrWhiteSpace($DistinguishedName)) {
        return [string]::Empty
    }

    $parts = @($DistinguishedName -split ',')
    $name = @()

    foreach ($part in $parts) {
        if ($part -imatch '^DC=(?<name>.+)$') {
            $name += $Matches['name']
        }
    }

    return ($name -join '.')
}

function Get-PodeAuthADResult {
    param(
        [Parameter()]
        [string]
        $Server,

        [Parameter()]
        [string]
        $Domain,

        [Parameter()]
        [string]
        $SearchBase,

        [Parameter()]
        [string]
        $Username,

        [Parameter()]
        [string]
        $Password,

        [Parameter()]
        [ValidateSet('DirectoryServices', 'ActiveDirectory', 'OpenLDAP')]
        [string]
        $Provider,

        [switch]
        $NoGroups,

        [switch]
        $DirectGroups,

        [switch]
        $KeepCredential
    )

    try {
        # validate the user's AD creds
        $result = (Open-PodeAuthADConnection -Server $Server -Domain $Domain -Username $Username -Password $Password -Provider $Provider)
        if (!$result.Success) {
            return @{ Message = 'Invalid credentials supplied' }
        }

        # get the connection
        $connection = $result.Connection

        # get the user
        $user = (Get-PodeAuthADUser -Connection $connection -Username $Username -Provider $Provider)
        if ($null -eq $user) {
            return @{ Message = 'User not found in Active Directory' }
        }

        # get the users groups
        $groups = @()
        if (!$NoGroups) {
            $groups = (Get-PodeAuthADGroup -Connection $connection -DistinguishedName $user.DistinguishedName -Username $Username -Direct:$DirectGroups -Provider $Provider)
        }

        # check if we want to keep the credentials in the User object
        if ($KeepCredential) {
            $credential = [pscredential]::new($($Domain + '\' + $Username), (ConvertTo-SecureString -String $Password -AsPlainText -Force))
        }
        else {
            $credential = $null
        }

        # return the user
        return @{
            User = @{
                UserType           = 'Domain'
                AuthenticationType = 'LDAP'
                DistinguishedName  = $user.DistinguishedName
                Username           = ($Username -split '\\')[-1]
                Name               = $user.Name
                Email              = $user.Email
                Fqdn               = $Server
                Domain             = $Domain
                Groups             = $groups
                Credential         = $credential
            }
        }
    }
    finally {
        if ($null -ne $connection) {
            switch ($Provider.ToLowerInvariant()) {
                'openldap' {
                    $connection.Username = $null
                    $connection.Password = $null
                }

                'activedirectory' {
                    $connection.Credential = $null
                }

                'directoryservices' {
                    Close-PodeDisposable -Disposable $connection.Searcher
                    Close-PodeDisposable -Disposable $connection.Entry -Close
                }
            }
        }
    }
}

function Open-PodeAuthADConnection {
    param(
        [Parameter(Mandatory = $true)]
        [string]
        $Server,

        [Parameter()]
        [string]
        $Domain,

        [Parameter()]
        [string]
        $SearchBase,

        [Parameter()]
        [string]
        $Username,

        [Parameter()]
        [string]
        $Password,

        [Parameter()]
        [ValidateSet('LDAP', 'WinNT')]
        [string]
        $Protocol = 'LDAP',

        [Parameter()]
        [ValidateSet('DirectoryServices', 'ActiveDirectory', 'OpenLDAP')]
        [string]
        $Provider
    )

    $result = $true
    $connection = $null

    # validate the user's AD creds
    switch ($Provider.ToLowerInvariant()) {
        'openldap' {
            if (![string]::IsNullOrWhiteSpace($SearchBase)) {
                $baseDn = $SearchBase
            }
            else {
                $baseDn = "DC=$(($Server -split '\.') -join ',DC=')"
            }

            $query = (Get-PodeAuthADQuery -Username $Username)
            $hostname = "$($Protocol)://$($Server)"

            $user = $Username
            if (!$Username.StartsWith($Domain)) {
                $user = "$($Domain)\$($Username)"
            }

            $null = (ldapsearch -x -LLL -H "$($hostname)" -D "$($user)" -w "$($Password)" -b "$($baseDn)" -o ldif-wrap=no "$($query)" dn)
            if (!$? -or ($LASTEXITCODE -ne 0)) {
                $result = $false
            }
            else {
                $connection = @{
                    Hostname = $hostname
                    Username = $user
                    BaseDN   = $baseDn
                    Password = $Password
                }
            }
        }

        'activedirectory' {
            try {
                $creds = [pscredential]::new($Username, (ConvertTo-SecureString -String $Password -AsPlainText -Force))
                $null = Get-ADUser -Identity $Username -Credential $creds -ErrorAction Stop
                $connection = @{
                    Credential = $creds
                }
            }
            catch {
                $result = $false
            }
        }

        'directoryservices' {
            if ([string]::IsNullOrWhiteSpace($Password)) {
                $ad = [System.DirectoryServices.DirectoryEntry]::new("$($Protocol)://$($Server)")
            }
            else {
                $ad = [System.DirectoryServices.DirectoryEntry]::new("$($Protocol)://$($Server)", "$($Username)", "$($Password)")
            }

            if (Test-PodeIsEmpty $ad.distinguishedName) {
                $result = $false
            }
            else {
                $connection = @{
                    Entry = $ad
                }
            }
        }
    }

    return @{
        Success    = $result
        Connection = $connection
    }
}

function Get-PodeAuthADQuery {
    param(
        [Parameter(Mandatory = $true)]
        [string]
        $Username
    )

    return "(&(objectCategory=person)(samaccountname=$($Username)))"
}

function Get-PodeAuthADUser {
    param(
        [Parameter(Mandatory = $true)]
        $Connection,

        [Parameter(Mandatory = $true)]
        [string]
        $Username,

        [Parameter()]
        [ValidateSet('DirectoryServices', 'ActiveDirectory', 'OpenLDAP')]
        [string]
        $Provider
    )

    $query = (Get-PodeAuthADQuery -Username $Username)
    $user = $null

    # generate query to find user
    switch ($Provider.ToLowerInvariant()) {
        'openldap' {
            $result = (ldapsearch -x -LLL -H "$($Connection.Hostname)" -D "$($Connection.Username)" -w "$($Connection.Password)" -b "$($Connection.BaseDN)" -o ldif-wrap=no "$($query)" name mail)
            if (!$? -or ($LASTEXITCODE -ne 0)) {
                return $null
            }

            $user = @{
                DistinguishedName = (Get-PodeOpenLdapValue -Lines $result -Property 'dn')
                Name              = (Get-PodeOpenLdapValue -Lines $result -Property 'name')
                Email             = (Get-PodeOpenLdapValue -Lines $result -Property 'mail')
            }
        }

        'activedirectory' {
            $result = Get-ADUser -LDAPFilter $query -Credential $Connection.Credential -Properties mail
            $user = @{
                DistinguishedName = $result.DistinguishedName
                Name              = $result.Name
                Email             = $result.mail
            }
        }

        'directoryservices' {
            $Connection.Searcher = [System.DirectoryServices.DirectorySearcher]::new($Connection.Entry)
            $Connection.Searcher.filter = $query

            $result = $Connection.Searcher.FindOne().Properties
            if (Test-PodeIsEmpty $result) {
                return $null
            }

            $user = @{
                DistinguishedName = @($result.distinguishedname)[0]
                Name              = @($result.name)[0]
                Email             = @($result.mail)[0]
            }
        }
    }

    return $user
}

function Get-PodeOpenLdapValue {
    param(
        [Parameter()]
        [string[]]
        $Lines,

        [Parameter()]
        [string]
        $Property,

        [switch]
        $All
    )

    foreach ($line in $Lines) {
        if ($line -imatch "^$($Property)\:\s+(?<$($Property)>.+)$") {
            # return the first found
            if (!$All) {
                return $Matches[$Property]
            }

            # return array of all
            $Matches[$Property]
        }
    }
}
<#
.SYNOPSIS
    Retrieves Active Directory (AD) group information for a user.

.DESCRIPTION
    This function retrieves AD group information for a specified user. It supports two modes of operation:
    1. Direct: Retrieves groups directly associated with the user.
    2. All: Retrieves all groups within the specified distinguished name (DN).

.PARAMETER Connection
    The AD connection object or credentials for connecting to the AD server.

.PARAMETER DistinguishedName
    The distinguished name (DN) of the user or group. If not provided, the default DN is used.

.PARAMETER Username
    The username for which to retrieve group information.

.PARAMETER Provider
    The AD provider to use (e.g., 'DirectoryServices', 'ActiveDirectory', 'OpenLDAP').

.PARAMETER Direct
    Switch parameter. If specified, retrieves only direct group memberships for the user.

.OUTPUTS
    Returns AD group information as needed based on the mode of operation.

.EXAMPLE
    Get-PodeAuthADGroup -Connection $adConnection -Username "john.doe"
    # Retrieves all AD groups for the user "john.doe".

    Get-PodeAuthADGroup -Connection $adConnection -Username "jane.smith" -Direct
    # Retrieves only direct group memberships for the user "jane.smith".
#>
function Get-PodeAuthADGroup {
    param(
        [Parameter(Mandatory = $true)]
        $Connection,

        [Parameter()]
        [string]
        $DistinguishedName,

        [Parameter()]
        [string]
        $Username,

        [Parameter()]
        [ValidateSet('DirectoryServices', 'ActiveDirectory', 'OpenLDAP')]
        [string]
        $Provider,

        [switch]
        $Direct
    )

    if ($Direct) {
        return (Get-PodeAuthADGroupDirect -Connection $Connection -Username $Username -Provider $Provider)
    }

    return (Get-PodeAuthADGroupAll -Connection $Connection -DistinguishedName $DistinguishedName -Provider $Provider)
}

function Get-PodeAuthADGroupDirect {
    param(
        [Parameter(Mandatory = $true)]
        $Connection,

        [Parameter()]
        [string]
        $Username,

        [Parameter()]
        [ValidateSet('DirectoryServices', 'ActiveDirectory', 'OpenLDAP')]
        [string]
        $Provider
    )

    # create the query
    $query = "(&(objectCategory=person)(samaccountname=$($Username)))"
    $groups = @()

    # get the groups
    switch ($Provider.ToLowerInvariant()) {
        'openldap' {
            $result = (ldapsearch -x -LLL -H "$($Connection.Hostname)" -D "$($Connection.Username)" -w "$($Connection.Password)" -b "$($Connection.BaseDN)" -o ldif-wrap=no "$($query)" memberof)
            $groups = (Get-PodeOpenLdapValue -Lines $result -Property 'memberof' -All)
        }

        'activedirectory' {
            $groups = (Get-ADPrincipalGroupMembership -Identity $Username -Credential $Connection.Credential).distinguishedName
        }

        'directoryservices' {
            if ($null -eq $Connection.Searcher) {
                $Connection.Searcher = [System.DirectoryServices.DirectorySearcher]::new($Connection.Entry)
            }

            $Connection.Searcher.filter = $query
            $groups = @($Connection.Searcher.FindOne().Properties.memberof)
        }
    }

    $groups = @(foreach ($group in $groups) {
            if ($group -imatch '^CN=(?<group>.+?),') {
                $Matches['group']
            }
        })

    return $groups
}

function Get-PodeAuthADGroupAll {
    param(
        [Parameter(Mandatory = $true)]
        $Connection,

        [Parameter()]
        [string]
        $DistinguishedName,

        [Parameter()]
        [ValidateSet('DirectoryServices', 'ActiveDirectory', 'OpenLDAP')]
        [string]
        $Provider
    )

    # create the query
    $query = "(member:1.2.840.113556.1.4.1941:=$($DistinguishedName))"
    $groups = @()

    # get the groups
    switch ($Provider.ToLowerInvariant()) {
        'openldap' {
            $result = (ldapsearch -x -LLL -H "$($Connection.Hostname)" -D "$($Connection.Username)" -w "$($Connection.Password)" -b "$($Connection.BaseDN)" -o ldif-wrap=no "$($query)" samaccountname)
            $groups = (Get-PodeOpenLdapValue -Lines $result -Property 'sAMAccountName' -All)
        }

        'activedirectory' {
            $groups = (Get-ADObject -LDAPFilter $query -Credential $Connection.Credential).Name
        }

        'directoryservices' {
            if ($null -eq $Connection.Searcher) {
                $Connection.Searcher = [System.DirectoryServices.DirectorySearcher]::new($Connection.Entry)
            }

            $null = $Connection.Searcher.PropertiesToLoad.Add('samaccountname')
            $Connection.Searcher.filter = $query
            $groups = @($Connection.Searcher.FindAll().Properties.samaccountname)
        }
    }

    return $groups
}

function Get-PodeAuthDomainName {
    $domain = $null

    if (Test-PodeIsMacOS) {
        $domain = (scutil --dns | grep -m 1 'search domain\[0\]' | cut -d ':' -f 2)
    }
    elseif (Test-PodeIsUnix) {
        $domain = (dnsdomainname)
        if ([string]::IsNullOrWhiteSpace($domain)) {
            $domain = (/usr/sbin/realm list --name-only)
        }
    }
    else {
        $domain = $env:USERDNSDOMAIN
        if ([string]::IsNullOrWhiteSpace($domain)) {
            $domain = (Get-CimInstance -Class Win32_ComputerSystem -Verbose:$false).Domain
        }
    }

    if (![string]::IsNullOrEmpty($domain)) {
        $domain = $domain.Trim()
    }

    return $domain
}

function Find-PodeAuth {
    param(
        [Parameter(Mandatory = $true)]
        [ValidateNotNullOrEmpty()]
        [string]
        $Name
    )

    return $PodeContext.Server.Authentications.Methods[$Name]
}

<#
.SYNOPSIS
  Expands a list of authentication names, including merged authentication methods.

.DESCRIPTION
  The Expand-PodeAuthMerge function takes an array of authentication names and expands it by resolving any merged authentication methods
  into their individual components. It is particularly useful in scenarios where authentication methods are combined or merged, and there
  is a need to process each individual method separately.

.PARAMETER Names
  An array of authentication method names. These names can include both discrete authentication methods and merged ones.

.EXAMPLE
  $expandedAuthNames = Expand-PodeAuthMerge -Names @('BasicAuth', 'CustomMergedAuth')

  Expands the provided authentication names, resolving 'CustomMergedAuth' into its constituent authentication methods if it's a merged one.
#>
function Expand-PodeAuthMerge {
    param (
        [Parameter(Mandatory = $true)]
        [ValidateNotNullOrEmpty()]
        [string[]]
        $Names
    )

    # Initialize a hashtable to store expanded authentication names
    $authNames = @{}

    # Iterate over each authentication name
    foreach ($authName in $Names) {
        # Handle the special case of anonymous access
        if ($authName -eq '%_allowanon_%') {
            $authNames[$authName] = $true
        }
        else {
            # Retrieve the authentication method from the Pode context
            $_auth = $PodeContext.Server.Authentications.Methods[$authName]

            # Check if the authentication is a merged one and expand it
            if ($_auth.merged) {
                foreach ($key in (Expand-PodeAuthMerge -Names $_auth.Authentications)) {
                    $authNames[$key] = $true
                }
            }
            else {
                # If not merged, add the authentication name to the list
                $authNames[$_auth.Name] = $true
            }
        }
    }

    # Return the keys of the hashtable, which are the expanded authentication names
    return $authNames.Keys
}


function Import-PodeAuthADModule {
    if (!(Test-PodeIsWindows)) {
        # Active Directory module only available on Windows
        throw ($PodeLocale.adModuleWindowsOnlyExceptionMessage)
    }

    if (!(Test-PodeModuleInstalled -Name ActiveDirectory)) {
        # Active Directory module is not installed
        throw ($PodeLocale.adModuleNotInstalledExceptionMessage)
    }

    Import-Module -Name ActiveDirectory -Force -ErrorAction Stop
    Export-PodeModule -Name ActiveDirectory
}

function Get-PodeAuthADProvider {
    param(
        [switch]
        $OpenLDAP,

        [switch]
        $ADModule
    )

    # openldap (literal, or not windows)
    if ($OpenLDAP -or !(Test-PodeIsWindows)) {
        return 'OpenLDAP'
    }

    # ad module
    if ($ADModule) {
        return 'ActiveDirectory'
    }

    # ds
    return 'DirectoryServices'
}

function Set-PodeAuthRedirectUrl {
    param(
        [switch]
        $UseOrigin
    )

    if ($UseOrigin -and ($WebEvent.Method -ieq 'get')) {
        $null = Set-PodeCookie -Name 'pode.redirecturl' -Value $WebEvent.Request.Url.PathAndQuery
    }
}

function Get-PodeAuthRedirectUrl {
    param(
        [Parameter()]
        [string]
        $Url,

        [switch]
        $UseOrigin
    )

    if (!$UseOrigin) {
        return $Url
    }

    $tmpUrl = Get-PodeCookieValue -Name 'pode.redirecturl'
    Remove-PodeCookie -Name 'pode.redirecturl'

    if (![string]::IsNullOrWhiteSpace($tmpUrl)) {
        $Url = $tmpUrl
    }

    return $Url
}
src\Private\AutoImport.ps1
function Import-PodeFunctionsIntoRunspaceState {
    param(
        [Parameter(Mandatory = $true, ParameterSetName = 'Script')]
        [scriptblock]
        $ScriptBlock,

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

    # do nothing if disabled
    if (!$PodeContext.Server.AutoImport.Functions.Enabled) {
        return
    }

    # if export only, and there are none, do nothing
    if ($PodeContext.Server.AutoImport.Functions.ExportOnly -and ($PodeContext.Server.AutoImport.Functions.ExportList.Length -eq 0)) {
        return
    }

    # script or file functions?
    switch ($PSCmdlet.ParameterSetName.ToLowerInvariant()) {
        'script' {
            $funcs = (Get-PodeFunctionsFromScriptBlock -ScriptBlock $ScriptBlock)
        }

        'file' {
            $funcs = (Get-PodeFunctionsFromFile -FilePath $FilePath)
        }
    }

    # looks like we have nothing!
    if (($null -eq $funcs) -or ($funcs.Length -eq 0)) {
        return
    }

    # groups funcs in case there or multiple definitions
    $funcs = ($funcs | Group-Object -Property { $_.Name })

    # import them, but also check if they're exported
    foreach ($func in $funcs) {
        # only exported funcs? is the func exported?
        if ($PodeContext.Server.AutoImport.Functions.ExportOnly -and ($PodeContext.Server.AutoImport.Functions.ExportList -inotcontains $func.Name)) {
            continue
        }

        # load the function
        $funcDef = [System.Management.Automation.Runspaces.SessionStateFunctionEntry]::new($func.Name, $func.Group[-1].Definition)
        $PodeContext.RunspaceState.Commands.Add($funcDef)
    }
}

function Import-PodeModulesIntoRunspaceState {
    # do nothing if disabled
    if (!$PodeContext.Server.AutoImport.Modules.Enabled) {
        return
    }

    # if export only, and there are none, do nothing
    if ($PodeContext.Server.AutoImport.Modules.ExportOnly -and ($PodeContext.Server.AutoImport.Modules.ExportList.Length -eq 0)) {
        return
    }

    # get modules currently loaded in session
    $modules = Get-Module |
        Where-Object {
            ($_.Name -inotin @('pode', 'pode.internal')) -and ($_.Name -inotlike 'microsoft.powershell.*')
        } | Select-Object -Unique

    # work out which order the modules need to be loaded
    $modulesOrder = @(foreach ($module in $modules) {
            Get-PodeModuleDependencyList -Module $module
        }) |
        Where-Object {
            ($_.Name -inotin @('pode', 'pode.internal')) -and ($_.Name -inotlike 'microsoft.powershell.*')
        } | Select-Object -Unique

    # load modules into runspaces, if allowed
    foreach ($module in $modulesOrder) {
        # only exported modules? is the module exported?
        if ($PodeContext.Server.AutoImport.Modules.ExportOnly -and ($PodeContext.Server.AutoImport.Modules.ExportList -inotcontains $module.Name)) {
            continue
        }

        # import the module
        $path = Find-PodeModuleFile -Module $module
        if ([string]::IsNullOrEmpty($path) -or !(Test-Path $path)) {
            continue
        }

        if (($module.ModuleType -ieq 'Manifest') -or ($path.EndsWith('.ps1'))) {
            $PodeContext.RunspaceState.ImportPSModule($path)
        }
        else {
            $PodeContext.Server.Modules[$module.Name] = $path
        }
    }
}

function Import-PodeSnapinsIntoRunspaceState {
    # if non-windows or core, do nothing
    if ((Test-PodeIsPSCore) -or (Test-PodeIsUnix)) {
        return
    }

    # do nothing if disabled
    if (!$PodeContext.Server.AutoImport.Snapins.Enabled) {
        return
    }

    # if export only, and there are none, do nothing
    if ($PodeContext.Server.AutoImport.Snapins.ExportOnly -and ($PodeContext.Server.AutoImport.Snapins.ExportList.Length -eq 0)) {
        return
    }

    # load snapins into runspaces, if allowed
    $snapins = (Get-PSSnapin | Where-Object { !$_.IsDefault }).Name | Sort-Object -Unique

    foreach ($snapin in $snapins) {
        # only exported snapins? is the snapin exported?
        if ($PodeContext.Server.AutoImport.Snapins.ExportOnly -and ($PodeContext.Server.AutoImport.Snapins.ExportList -inotcontains $snapin)) {
            continue
        }

        $PodeContext.RunspaceState.ImportPSSnapIn($snapin, [ref]$null)
    }
}

function Initialize-PodeAutoImportConfiguration {
    return @{
        Modules      = @{
            Enabled    = $true
            ExportList = @()
            ExportOnly = $false
        }
        Snapins      = @{
            Enabled    = $true
            ExportList = @()
            ExportOnly = $false
        }
        Functions    = @{
            Enabled    = $true
            ExportList = @()
            ExportOnly = $false
        }
        SecretVaults = @{
            Enabled          = $true
            SecretManagement = @{
                Enabled    = $false
                ExportList = @()
                ExportOnly = $false
            }
        }
    }
}

function Import-PodeSecretVaultsIntoRegistry {
    # do nothing if disabled
    if (!$PodeContext.Server.AutoImport.SecretVaults.Enabled) {
        return
    }

    Import-PodeSecretManagementVaultsIntoRegistry
}

function Import-PodeSecretManagementVaultsIntoRegistry {
    # do nothing if disabled
    if (!$PodeContext.Server.AutoImport.SecretVaults.SecretManagement.Enabled) {
        return
    }

    # if export only, and there are none, do nothing
    if ($PodeContext.Server.AutoImport.SecretVaults.SecretManagement.ExportOnly -and ($PodeContext.Server.AutoImport.SecretVaults.SecretManagement.ExportList.Length -eq 0)) {
        return
    }

    # error if SecretManagement module not installed
    if (!(Test-PodeModuleInstalled -Name Microsoft.PowerShell.SecretManagement)) {
        # Microsoft.PowerShell.SecretManagement module not installed
        throw ($PodeLocale.secretManagementModuleNotInstalledExceptionMessage)
    }

    # import the module
    $null = Import-Module -Name Microsoft.PowerShell.SecretManagement -Force -DisableNameChecking -Scope Global -ErrorAction Stop -Verbose:$false

    # get the current secret vaults
    $vaults = @(Get-SecretVault -ErrorAction Stop)

    # register the vaults
    foreach ($vault in $vaults) {
        # only exported vaults? is the vault exported?
        if ($PodeContext.Server.AutoImport.SecretVaults.SecretManagement.ExportOnly -and ($PodeContext.Server.AutoImport.SecretVaults.SecretManagement.ExportList -inotcontains $vault.Name)) {
            continue
        }

        # is a vault with this name already registered?
        if (Test-PodeSecretVault -Name $vault.Name) {
            throw ($PodeLocale.secretVaultAlreadyRegisteredExceptionMessage -f $vault.Name,"")
            #"A Secret Vault with the name '$($vault.Name)' has already been registered while auto-importing Secret Vaults"
        }

        # register the vault
        $PodeContext.Server.Secrets.Vaults[$vault.Name] = @{
            Name             = $vault.Name
            Type             = 'secretmanagement'
            Parameters       = $vault.VaultParameters
            AutoImported     = $true
            Unlock           = $null
            Cache            = $null
            SecretManagement = @{
                VaultName  = $vault.Name
                ModuleName = $vault.ModulePath
            }
        }
    }
}

function Read-PodeAutoImportConfiguration {
    param(
        [Parameter()]
        [hashtable]
        $Configuration
    )

    $impModules = $Configuration.AutoImport.Modules
    $impSnapins = $Configuration.AutoImport.Snapins
    $impFuncs = $Configuration.AutoImport.Functions
    $impSecretVaults = $Configuration.AutoImport.SecretVaults

    return @{
        Modules      = @{
            Enabled    = (($null -eq $impModules.Enable) -or [bool]$impModules.Enable)
            ExportList = @()
            ExportOnly = ([bool]$impModules.ExportOnly)
        }
        Snapins      = @{
            Enabled    = (($null -eq $impSnapins.Enable) -or [bool]$impSnapins.Enable)
            ExportList = @()
            ExportOnly = ([bool]$impSnapins.ExportOnly)
        }
        Functions    = @{
            Enabled    = (($null -eq $impFuncs.Enable) -or [bool]$impFuncs.Enable)
            ExportList = @()
            ExportOnly = ([bool]$impFuncs.ExportOnly)
        }
        SecretVaults = @{
            Enabled          = (($null -eq $impSecretVaults.Enable) -or [bool]$impSecretVaults.Enable)
            SecretManagement = @{
                Enabled    = ((($null -eq $impSecretVaults.Enable) -and (Test-PodeModuleInstalled -Name Microsoft.PowerShell.SecretManagement)) -or [bool]$impSecretVaults.Enable)
                ExportList = @()
                ExportOnly = ([bool]$impSecretVaults.SecretManagement.ExportOnly)
            }
        }
    }
}

function Reset-PodeAutoImportConfiguration {
    $PodeContext.Server.AutoImport.Modules.ExportList = @()
    $PodeContext.Server.AutoImport.Snapins.ExportList = @()
    $PodeContext.Server.AutoImport.Functions.ExportList = @()
    $PodeContext.Server.AutoImport.SecretVaults.SecretManagement.ExportList = @()
}
src\Private\Caching.ps1
function Get-PodeCacheInternal {
    param(
        [Parameter(Mandatory = $true)]
        [string]
        $Key,

        [switch]
        $Metadata
    )

    $meta = $PodeContext.Server.Cache.Items[$Key]
    if ($null -eq $meta) {
        return $null
    }

    # check ttl/expiry
    if ($meta.Expiry -lt [datetime]::UtcNow) {
        Remove-PodeCacheInternal -Key $Key
        return $null
    }

    # return value an metadata if required
    if ($Metadata) {
        return $meta
    }

    # return just the value as default
    return $meta.Value
}

function Set-PodeCacheInternal {
    param(
        [Parameter(Mandatory = $true)]
        [string]
        $Key,

        [Parameter(Mandatory = $true)]
        [object]
        $InputObject,

        [Parameter()]
        [int]
        $Ttl = 0
    )

    # crete (or update) value value
    $PodeContext.Server.Cache.Items[$Key] = @{
        Value  = $InputObject
        Ttl    = $Ttl
        Expiry = [datetime]::UtcNow.AddSeconds($Ttl)
    }
}

function Test-PodeCacheInternal {
    param(
        [Parameter(Mandatory = $true)]
        [string]
        $Key
    )

    # if it's not in the cache at all, return false
    if (!$PodeContext.Server.Cache.Items.ContainsKey($Key)) {
        return $false
    }

    # fetch the items metadata, and check expiry. If it's expired return false.
    $meta = $PodeContext.Server.Cache.Items[$Key]

    # check ttl/expiry
    if ($meta.Expiry -lt [datetime]::UtcNow) {
        Remove-PodeCacheInternal -Key $Key
        return $false
    }

    # it exists, and isn't expired
    return $true
}

function Remove-PodeCacheInternal {
    param(
        [Parameter(Mandatory = $true)]
        [string]
        $Key
    )

    Lock-PodeObject -Object $PodeContext.Threading.Lockables.Cache -ScriptBlock {
        $null = $PodeContext.Server.Cache.Items.Remove($Key)
    }
}

function Clear-PodeCacheInternal {
    Lock-PodeObject -Object $PodeContext.Threading.Lockables.Cache -ScriptBlock {
        $null = $PodeContext.Server.Cache.Items.Clear()
    }
}

function Start-PodeCacheHousekeeper {
    if (![string]::IsNullOrEmpty((Get-PodeCacheDefaultStorage))) {
        return
    }

    Add-PodeTimer -Name '__pode_cache_housekeeper__' -Interval 10 -ScriptBlock {
        $keys = Lock-PodeObject -Object $PodeContext.Threading.Lockables.Cache -Return -ScriptBlock {
            if ($PodeContext.Server.Cache.Items.Count -eq 0) {
                return
            }

            return $PodeContext.Server.Cache.Items.Keys.Clone()
        }

        if (Test-PodeIsEmpty $keys) {
            return
        }

        $now = [datetime]::UtcNow

        foreach ($key in $keys) {
            if ($PodeContext.Server.Cache.Items[$key].Expiry -lt $now) {
                Remove-PodeCacheInternal -Key $key
            }
        }
    }
}
src\Private\Context.ps1
using namespace Pode

function New-PodeContext {
    [CmdletBinding()]
    param(
        [Parameter()]
        [scriptblock]
        $ScriptBlock,

        [Parameter()]
        [string]
        $FilePath,

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

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

        [Parameter()]
        [string]
        $ServerRoot,

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

        [Parameter()]
        [string]
        $ServerlessType,

        [Parameter()]
        [string]
        $StatusPageExceptions,

        [Parameter()]
        [string]
        $ListenerType,

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

        [switch]
        $DisableTermination,

        [switch]
        $Quiet,

        [switch]
        $EnableBreakpoints
    )

    # set a random server name if one not supplied
    if (Test-PodeIsEmpty $Name) {
        $Name = Get-PodeRandomName
    }

    # are we running in a serverless context
    $isServerless = ![string]::IsNullOrWhiteSpace($ServerlessType)

    # ensure threads are always >0, for to 1 if we're serverless
    if (($Threads -le 0) -or $isServerless) {
        $Threads = 1
    }

    # basic context object
    $ctx = [PSCustomObject]@{
        Threads       = @{}
        Timers        = @{}
        Schedules     = @{}
        Tasks         = @{}
        RunspacePools = $null
        Runspaces     = $null
        RunspaceState = $null
        Tokens        = @{}
        LogsToProcess = $null
        Threading     = @{}
        Server        = @{}
        Metrics       = @{}
        Listeners     = @()
        Receivers     = @()
        Watchers      = @()
        Fim           = @{}
    }

    # set the server name, logic and root, and other basic properties
    $ctx.Server.Name = $Name
    $ctx.Server.Logic = $ScriptBlock
    $ctx.Server.LogicPath = $FilePath
    $ctx.Server.Interval = $Interval
    $ctx.Server.PodeModule = (Get-PodeModuleInfo)
    $ctx.Server.DisableTermination = $DisableTermination.IsPresent
    $ctx.Server.Quiet = $Quiet.IsPresent
    $ctx.Server.ComputerName = [System.Net.DNS]::GetHostName()

    # list of created listeners/receivers
    $ctx.Listeners = @()
    $ctx.Receivers = @()
    $ctx.Watchers = @()

    # default secret that can used when needed, and a secret isn't supplied
    $ctx.Server.DefaultSecret = New-PodeGuid -Secure

    # list of timers/schedules/tasks/fim
    $ctx.Timers = @{
        Enabled = ($EnablePool -icontains 'timers')
        Items   = @{}
    }

    $ctx.Schedules = @{
        Enabled   = ($EnablePool -icontains 'schedules')
        Items     = @{}
        Processes = @{}
    }

    $ctx.Tasks = @{
        Enabled   = ($EnablePool -icontains 'tasks')
        Items     = @{}
        Processes = @{}
    }

    $ctx.Fim = @{
        Enabled = ($EnablePool -icontains 'files')
        Items   = @{}
    }

    # auto importing (modules, funcs, snap-ins)
    $ctx.Server.AutoImport = Initialize-PodeAutoImportConfiguration

    # basic logging setup
    $ctx.Server.Logging = @{
        Enabled = $true
        Types   = @{}
    }

    # set thread counts
    $ctx.Threads = @{
        General    = $Threads
        Schedules  = 10
        Files      = 1
        Tasks      = 2
        WebSockets = 2
        Timers     = 1
    }

    # set socket details for pode server
    $ctx.Server.Sockets = @{
        Ssl            = @{
            Protocols = Get-PodeDefaultSslProtocol
        }
        ReceiveTimeout = 100
    }

    $ctx.Server.Signals = @{
        Enabled  = $false
        Listener = $null
    }

    $ctx.Server.Http = @{
        Listener = $null
    }

    $ctx.Server.Sse = @{
        Signed         = $false
        Secret         = $null
        Strict         = $false
        DefaultScope   = 'Global'
        BroadcastLevel = @{}
    }

    $ctx.Server.WebSockets = @{
        Enabled     = ($EnablePool -icontains 'websockets')
        Receiver    = $null
        Connections = @{}
    }

    # set default request config
    $ctx.Server.Request = @{
        Timeout  = 30
        BodySize = 100MB
    }

    # default Folders
    $ctx.Server.DefaultFolders = @{
        'Views'  = 'views'
        'Public' = 'public'
        'Errors' = 'errors'
    }

    # check if there is any global configuration
    $ctx.Server.Configuration = Open-PodeConfiguration -ServerRoot $ServerRoot -Context $ctx

    # over status page exceptions
    if (!(Test-PodeIsEmpty $StatusPageExceptions)) {
        if ($null -eq $ctx.Server.Web) {
            $ctx.Server.Web = @{ ErrorPages = @{} }
        }

        $ctx.Server.Web.ErrorPages.ShowExceptions = ($StatusPageExceptions -eq 'show')
    }

    # configure the server's root path
    $ctx.Server.Root = $ServerRoot
    if (!(Test-PodeIsEmpty $ctx.Server.Configuration.Server.Root)) {
        $ctx.Server.Root = Get-PodeRelativePath -Path $ctx.Server.Configuration.Server.Root -RootPath $ctx.Server.Root -JoinRoot -Resolve -TestPath
    }

    if (Test-PodeIsEmpty $ctx.Server.Root) {
        $ctx.Server.Root = $PWD.Path
    }

    # debugging
    if ($EnableBreakpoints) {
        if ($null -eq $ctx.Server.Debug) {
            $ctx.Server.Debug = @{ Breakpoints = @{} }
        }

        $ctx.Server.Debug.Breakpoints.Enabled = $EnableBreakpoints.IsPresent
    }

    # set the server's listener type
    $ctx.Server.ListenerType = $ListenerType

    # set serverless info
    $ctx.Server.ServerlessType = $ServerlessType
    $ctx.Server.IsServerless = $isServerless
    if ($isServerless) {
        $ctx.Server.DisableTermination = $true
    }

    # set the server types
    $ctx.Server.IsService = ($Interval -gt 0)
    $ctx.Server.Types = @()

    # is the server running under IIS? (also, disable termination)
    $ctx.Server.IsIIS = (!$isServerless -and (!(Test-PodeIsEmpty $env:ASPNETCORE_PORT)) -and (!(Test-PodeIsEmpty $env:ASPNETCORE_TOKEN)))
    if ($ctx.Server.IsIIS) {
        $ctx.Server.DisableTermination = $true

        # if under IIS and Azure Web App, force quiet
        if (!(Test-PodeIsEmpty $env:WEBSITE_IIS_SITE_NAME)) {
            $ctx.Server.Quiet = $true
        }

        # set iis token/settings
        $ctx.Server.IIS = @{
            Token    = $env:ASPNETCORE_TOKEN
            Port     = $env:ASPNETCORE_PORT
            Path     = @{
                Raw       = '/'
                Pattern   = '^/'
                IsNonRoot = $false
            }
            Shutdown = $false
        }

        if (![string]::IsNullOrWhiteSpace($env:ASPNETCORE_APPL_PATH) -and ($env:ASPNETCORE_APPL_PATH -ne '/')) {
            $ctx.Server.IIS.Path.Raw = $env:ASPNETCORE_APPL_PATH
            $ctx.Server.IIS.Path.Pattern = "^$($env:ASPNETCORE_APPL_PATH)"
            $ctx.Server.IIS.Path.IsNonRoot = $true
        }
    }

    # is the server running under Heroku?
    $ctx.Server.IsHeroku = (!$isServerless -and (!(Test-PodeIsEmpty $env:PORT)) -and (!(Test-PodeIsEmpty $env:DYNO)))

    # if we're inside a remote host, stop termination
    if ($Host.Name -ieq 'ServerRemoteHost') {
        $ctx.Server.DisableTermination = $true
    }

    # set the IP address details
    $ctx.Server.Endpoints = @{}
    $ctx.Server.EndpointsMap = @{}

    # general encoding for the server
    $ctx.Server.Encoding = [System.Text.UTF8Encoding]::new()

    # setup gui details
    $ctx.Server.Gui = @{}

    # shared temp drives
    $ctx.Server.Drives = @{}
    $ctx.Server.InbuiltDrives = @{}

    # shared state between runspaces
    $ctx.Server.State = @{}

    # setup caching
    $ctx.Server.Cache = @{
        Items          = @{}
        Storage        = @{}
        DefaultStorage = $null
        DefaultTtl     = 3600 # 1hr
    }

    # output details, like variables, to be set once the server stops
    $ctx.Server.Output = @{
        Variables = @{}
    }

    # view engine for rendering pages
    $ctx.Server.ViewEngine = @{
        Type           = 'html'
        Extension      = 'html'
        ScriptBlock    = $null
        UsingVariables = $null
        IsDynamic      = $false
    }

    # pode default preferences
    $ctx.Server.Preferences = @{
        Routes = @{
            IfExists = $null
        }
    }

    # routes for pages and api
    $ctx.Server.Routes = @{
        'connect' = [ordered]@{}
        'delete'  = [ordered]@{}
        'get'     = [ordered]@{}
        'head'    = [ordered]@{}
        'merge'   = [ordered]@{}
        'options' = [ordered]@{}
        'patch'   = [ordered]@{}
        'post'    = [ordered]@{}
        'put'     = [ordered]@{}
        'trace'   = [ordered]@{}
        'static'  = [ordered]@{}
        'signal'  = [ordered]@{}
        '*'       = [ordered]@{}
    }

    # verbs for tcp
    $ctx.Server.Verbs = @{}

    # secrets
    $ctx.Server.Secrets = @{
        Vaults = @{}
        Keys   = @{}
    }

    # custom view paths
    $ctx.Server.Views = @{}

    # handlers for tcp
    $ctx.Server.Handlers = @{
        smtp    = @{}
        service = @{}
    }

    # setup basic access placeholders
    $ctx.Server.Access = @{
        Allow = @{}
        Deny  = @{}
    }

    # setup basic limit rules
    $ctx.Server.Limits = @{
        Rules  = @{}
        Active = @{}
    }

    # cookies and session logic
    $ctx.Server.Cookies = @{
        Csrf    = @{}
        Secrets = @{}
    }

    # sessions
    $ctx.Server.Sessions = @{}

    #OpenApi Definition Tag
    $ctx.Server.OpenAPI = Initialize-PodeOpenApiTable -DefaultDefinitionTag $ctx.Server.Web.OpenApi.DefaultDefinitionTag

    # server metrics
    $ctx.Metrics = @{
        Server   = @{
            InitialLoadTime = [datetime]::UtcNow
            StartTime       = [datetime]::UtcNow
            RestartCount    = 0
        }
        Requests = @{
            Total       = 0
            StatusCodes = @{}
        }
        Signals  = @{
            Total = 0
        }
    }

    # authentication and authorisation methods
    $ctx.Server.Authentications = @{
        Methods = @{}
    }

    $ctx.Server.Authorisations = @{
        Methods = @{}
    }

    # create new cancellation tokens
    $ctx.Tokens = @{
        Cancellation = [System.Threading.CancellationTokenSource]::new()
        Restart      = [System.Threading.CancellationTokenSource]::new()
    }

    # requests that should be logged
    $ctx.LogsToProcess = [System.Collections.ArrayList]::new()

    # middleware that needs to run
    $ctx.Server.Middleware = @()
    $ctx.Server.BodyParsers = @{}

    # common support values
    $ctx.Server.Compression = @{
        Encodings = @('gzip', 'deflate', 'x-gzip')
    }

    # endware that needs to run
    $ctx.Server.Endware = @()

    # runspace pools
    $ctx.RunspacePools = @{
        Main      = $null
        Web       = $null
        Smtp      = $null
        Tcp       = $null
        Signals   = $null
        Schedules = $null
        Gui       = $null
        Tasks     = $null
        Files     = $null
        Timers    = $null
    }

    # threading locks, etc.
    $ctx.Threading.Lockables = @{
        Global = [hashtable]::Synchronized(@{})
        Cache  = [hashtable]::Synchronized(@{})
        Custom = @{}
    }

    $ctx.Threading.Mutexes = @{}
    $ctx.Threading.Semaphores = @{}

    # setup runspaces
    $ctx.Runspaces = @()

    # setup events
    $ctx.Server.Events = @{
        Start     = [ordered]@{}
        Terminate = [ordered]@{}
        Restart   = [ordered]@{}
        Browser   = [ordered]@{}
        Crash     = [ordered]@{}
        Stop      = [ordered]@{}
        Running   = [ordered]@{}
    }

    # modules
    $ctx.Server.Modules = [ordered]@{}

    # setup security
    $ctx.Server.Security = @{
        ServerDetails = $true
        Headers       = @{}
        Cache         = @{
            ContentSecurity   = @{}
            PermissionsPolicy = @{}
        }
    }

    # scoped variables
    $ctx.Server.ScopedVariables = [ordered]@{}

    # an internal cache for adhoc values, such as module importing checks
    $ctx.Server.InternalCache = @{
        YamlModuleImported = $null
    }

    # return the new context
    return $ctx
}

function New-PodeRunspaceState {
    # create the state, and add the pode modules
    $state = [initialsessionstate]::CreateDefault()
    $state.ImportPSModule($PodeContext.Server.PodeModule.DataPath)
    $state.ImportPSModule($PodeContext.Server.PodeModule.InternalPath)

    # load the vars into the share state
    $session = New-PodeStateContext -Context $PodeContext

    $variables = @(
        [System.Management.Automation.Runspaces.SessionStateVariableEntry]::new('PodeLocale', $PodeLocale, $null),
        [System.Management.Automation.Runspaces.SessionStateVariableEntry]::new('PodeContext', $session, $null),
        [System.Management.Automation.Runspaces.SessionStateVariableEntry]::new('Console', $Host, $null),
        [System.Management.Automation.Runspaces.SessionStateVariableEntry]::new('PODE_SCOPE_RUNSPACE', $true, $null)
    )

    foreach ($var in $variables) {
        $state.Variables.Add($var)
    }

    $PodeContext.RunspaceState = $state
}

<#
.SYNOPSIS
    Creates and initializes runspace pools for various Pode components.

.DESCRIPTION
    This function sets up runspace pools for different Pode components, such as timers, schedules, web endpoints, web sockets, SMTP, TCP, and more. It dynamically adjusts the thread counts based on the presence of specific components and their configuration.

.OUTPUTS
    Initializes and configures runspace pools for various Pode components.
#>
function New-PodeRunspacePool {
    if ($PodeContext.Server.IsServerless) {
        return
    }

    # setup main runspace pool
    $threadsCounts = @{
        Default  = 3
        Log      = 1
        Schedule = 1
        Misc     = 1
    }

    if (!(Test-PodeSchedulesExist)) {
        $threadsCounts.Schedule = 0
    }

    if (!(Test-PodeLoggersExist)) {
        $threadsCounts.Log = 0
    }

    # main runspace - for timers, schedules, etc
    $totalThreadCount = ($threadsCounts.Values | Measure-Object -Sum).Sum
    $PodeContext.RunspacePools.Main = @{
        Pool  = [runspacefactory]::CreateRunspacePool(1, $totalThreadCount, $PodeContext.RunspaceState, $Host)
        State = 'Waiting'
    }

    # web runspace - if we have any http/s endpoints
    if (Test-PodeEndpointByProtocolType -Type Http) {
        $PodeContext.RunspacePools.Web = @{
            Pool  = [runspacefactory]::CreateRunspacePool(1, ($PodeContext.Threads.General + 1), $PodeContext.RunspaceState, $Host)
            State = 'Waiting'
        }
    }

    # smtp runspace - if we have any smtp endpoints
    if (Test-PodeEndpointByProtocolType -Type Smtp) {
        $PodeContext.RunspacePools.Smtp = @{
            Pool  = [runspacefactory]::CreateRunspacePool(1, ($PodeContext.Threads.General + 1), $PodeContext.RunspaceState, $Host)
            State = 'Waiting'
        }
    }

    # tcp runspace - if we have any tcp endpoints
    if (Test-PodeEndpointByProtocolType -Type Tcp) {
        $PodeContext.RunspacePools.Tcp = @{
            Pool  = [runspacefactory]::CreateRunspacePool(1, ($PodeContext.Threads.General + 1), $PodeContext.RunspaceState, $Host)
            State = 'Waiting'
        }
    }

    # signals runspace - if we have any ws/s endpoints
    if (Test-PodeEndpointByProtocolType -Type Ws) {
        $PodeContext.RunspacePools.Signals = @{
            Pool  = [runspacefactory]::CreateRunspacePool(1, ($PodeContext.Threads.General + 2), $PodeContext.RunspaceState, $Host)
            State = 'Waiting'
        }
    }

    # web socket connections runspace - for receiving data for external sockets
    if (Test-PodeWebSocketsExist) {
        $PodeContext.RunspacePools.WebSockets = @{
            Pool  = [runspacefactory]::CreateRunspacePool(1, $PodeContext.Threads.WebSockets + 1, $PodeContext.RunspaceState, $Host)
            State = 'Waiting'
        }

        New-PodeWebSocketReceiver
    }

    # setup timer runspace pool -if we have any timers
    if (Test-PodeTimersExist) {
        $PodeContext.RunspacePools.Timers = @{
            Pool  = [runspacefactory]::CreateRunspacePool(1, $PodeContext.Threads.Timers, $PodeContext.RunspaceState, $Host)
            State = 'Waiting'
        }
    }

    # setup schedule runspace pool -if we have any schedules
    if (Test-PodeSchedulesExist) {
        $PodeContext.RunspacePools.Schedules = @{
            Pool  = [runspacefactory]::CreateRunspacePool(1, $PodeContext.Threads.Schedules, $PodeContext.RunspaceState, $Host)
            State = 'Waiting'
        }
    }

    # setup tasks runspace pool -if we have any tasks
    if (Test-PodeTasksExist) {
        $PodeContext.RunspacePools.Tasks = @{
            Pool  = [runspacefactory]::CreateRunspacePool(1, $PodeContext.Threads.Tasks, $PodeContext.RunspaceState, $Host)
            State = 'Waiting'
        }
    }

    # setup files runspace pool -if we have any file watchers
    if (Test-PodeFileWatchersExist) {
        $PodeContext.RunspacePools.Files = @{
            Pool  = [runspacefactory]::CreateRunspacePool(1, $PodeContext.Threads.Files + 1, $PodeContext.RunspaceState, $Host)
            State = 'Waiting'
        }
    }

    # setup gui runspace pool (only for non-ps-core) - if gui enabled
    if (Test-PodeGuiEnabled) {
        $PodeContext.RunspacePools.Gui = @{
            Pool  = [runspacefactory]::CreateRunspacePool(1, 1, $PodeContext.RunspaceState, $Host)
            State = 'Waiting'
        }

        $PodeContext.RunspacePools.Gui.Pool.ApartmentState = 'STA'
    }
}

<#
.SYNOPSIS
    Opens and initializes runspace pools for various Pode components.

.DESCRIPTION
    This function opens and initializes runspace pools for different Pode components, such as timers, schedules, web endpoints, web sockets, SMTP, TCP, and more. It asynchronously opens the pools and waits for them to be in the 'Opened' state. If any pool fails to open, it reports an error.

.OUTPUTS
    Opens and initializes runspace pools for various Pode components.
#>
function Open-PodeRunspacePool {
    if ($PodeContext.Server.IsServerless) {
        return
    }

    $start = [datetime]::Now
    Write-Verbose 'Opening RunspacePools'

    # open pools async
    foreach ($key in $PodeContext.RunspacePools.Keys) {
        $item = $PodeContext.RunspacePools[$key]
        if ($null -eq $item) {
            continue
        }

        $item.Pool.ThreadOptions = [System.Management.Automation.Runspaces.PSThreadOptions]::ReuseThread
        $item.Pool.CleanupInterval = [timespan]::FromMinutes(5)
        $item.Result = $item.Pool.BeginOpen($null, $null)
    }

    # wait for them all to open
    $queue = @($PodeContext.RunspacePools.Keys)

    while ($queue.Length -gt 0) {
        foreach ($key in $queue) {
            $item = $PodeContext.RunspacePools[$key]
            if ($null -eq $item) {
                $queue = ($queue | Where-Object { $_ -ine $key })
                continue
            }

            if ($item.Pool.RunspacePoolStateInfo.State -iin @('Opened', 'Broken')) {
                $queue = ($queue | Where-Object { $_ -ine $key })
                Write-Verbose "RunspacePool for $($key): $($item.Pool.RunspacePoolStateInfo.State) [duration: $(([datetime]::Now - $start).TotalSeconds)s]"
            }
        }

        if ($queue.Length -gt 0) {
            Start-Sleep -Milliseconds 100
        }
    }

    # report errors for failed pools
    foreach ($key in $PodeContext.RunspacePools.Keys) {
        $item = $PodeContext.RunspacePools[$key]
        if ($null -eq $item) {
            continue
        }

        if ($item.Pool.RunspacePoolStateInfo.State -ieq 'broken') {
            $item.Pool.EndOpen($item.Result) | Out-Default
            throw ($PodeLocale.failedToOpenRunspacePoolExceptionMessage -f $key) #"Failed to open RunspacePool: $($key)"
        }
    }

    Write-Verbose "RunspacePools opened [duration: $(([datetime]::Now - $start).TotalSeconds)s]"
}

<#
.SYNOPSIS
    Closes and disposes runspace pools for various Pode components.

.DESCRIPTION
    This function closes and disposes runspace pools for different Pode components, such as timers, schedules, web endpoints, web sockets, SMTP, TCP, and more. It asynchronously closes the pools and waits for them to be in the 'Closed' state. If any pool fails to close, it reports an error.

.OUTPUTS
    Closes and disposes runspace pools for various Pode components.
#>
function Close-PodeRunspacePool {
    if ($PodeContext.Server.IsServerless -or ($null -eq $PodeContext.RunspacePools)) {
        return
    }

    $start = [datetime]::Now
    Write-Verbose 'Closing RunspacePools'

    # close pools async
    foreach ($key in $PodeContext.RunspacePools.Keys) {
        $item = $PodeContext.RunspacePools[$key]
        if (($null -eq $item) -or ($item.Pool.IsDisposed)) {
            continue
        }

        $item.Result = $item.Pool.BeginClose($null, $null)
    }

    # wait for them all to close
    $queue = @($PodeContext.RunspacePools.Keys)

    while ($queue.Length -gt 0) {
        foreach ($key in $queue) {
            $item = $PodeContext.RunspacePools[$key]
            if ($null -eq $item) {
                $queue = ($queue | Where-Object { $_ -ine $key })
                continue
            }

            if ($item.Pool.RunspacePoolStateInfo.State -iin @('Closed', 'Broken')) {
                $queue = ($queue | Where-Object { $_ -ine $key })
                Write-Verbose "RunspacePool for $($key): $($item.Pool.RunspacePoolStateInfo.State) [duration: $(([datetime]::Now - $start).TotalSeconds)s]"
            }
        }

        if ($queue.Length -gt 0) {
            Start-Sleep -Milliseconds 100
        }
    }

    # report errors for failed pools
    foreach ($key in $PodeContext.RunspacePools.Keys) {
        $item = $PodeContext.RunspacePools[$key]
        if ($null -eq $item) {
            continue
        }

        if ($item.Pool.RunspacePoolStateInfo.State -ieq 'broken') {
            $item.Pool.EndClose($item.Result) | Out-Default
            # Failed to close RunspacePool
            throw ($PodeLocale.failedToCloseRunspacePoolExceptionMessage -f $key)
        }
    }

    # dispose pools
    foreach ($key in $PodeContext.RunspacePools.Keys) {
        $item = $PodeContext.RunspacePools[$key]
        if (($null -eq $item) -or ($item.Pool.IsDisposed)) {
            continue
        }

        Close-PodeDisposable -Disposable $item.Pool
    }

    Write-Verbose "RunspacePools closed [duration: $(([datetime]::Now - $start).TotalSeconds)s]"
}

function New-PodeStateContext {
    param(
        [Parameter(Mandatory = $true)]
        [ValidateNotNull()]
        $Context
    )

    return [PSCustomObject]@{
        Threads       = $Context.Threads
        Timers        = $Context.Timers
        Schedules     = $Context.Schedules
        Tasks         = $Context.Tasks
        Fim           = $Context.Fim
        RunspacePools = $Context.RunspacePools
        Tokens        = $Context.Tokens
        Metrics       = $Context.Metrics
        LogsToProcess = $Context.LogsToProcess
        Threading     = $Context.Threading
        Server        = $Context.Server
    }
}

function Open-PodeConfiguration {
    param(
        [Parameter()]
        [string]
        $ServerRoot = $null,

        [Parameter()]
        $Context
    )

    $config = @{}

    # set the path to the root config file
    $configPath = (Join-PodeServerRoot -Folder '.' -FilePath 'server.psd1' -Root $ServerRoot)

    # check to see if an environmental config exists (if the env var is set)
    if (!(Test-PodeIsEmpty $env:PODE_ENVIRONMENT)) {
        $_path = (Join-PodeServerRoot -Folder '.' -FilePath "server.$($env:PODE_ENVIRONMENT).psd1" -Root $ServerRoot)
        if (Test-PodePath -Path $_path -NoStatus) {
            $configPath = $_path
        }
    }

    # check the path exists, and load the config
    if (Test-PodePath -Path $configPath -NoStatus) {
        $config = Import-PowerShellDataFile -Path $configPath -ErrorAction Stop
        Set-PodeServerConfiguration -Configuration $config.Server -Context $Context
        Set-PodeWebConfiguration -Configuration $config.Web -Context $Context
    }

    return $config
}

function Set-PodeServerConfiguration {
    param(
        [Parameter()]
        [hashtable]
        $Configuration,

        [Parameter()]
        $Context
    )

    # file monitoring
    $Context.Server.FileMonitor = @{
        Enabled   = [bool]$Configuration.FileMonitor.Enable
        Exclude   = (Convert-PodePathPatternsToRegex -Paths @($Configuration.FileMonitor.Exclude))
        Include   = (Convert-PodePathPatternsToRegex -Paths @($Configuration.FileMonitor.Include))
        ShowFiles = [bool]$Configuration.FileMonitor.ShowFiles
        Files     = @()
    }

    # logging
    $Context.Server.Logging = @{
        Enabled = (($null -eq $Configuration.Logging.Enable) -or [bool]$Configuration.Logging.Enable)
        Masking = @{
            Patterns = (Remove-PodeEmptyItemsFromArray -Array @($Configuration.Logging.Masking.Patterns))
            Mask     = (Protect-PodeValue -Value $Configuration.Logging.Masking.Mask -Default '********')
        }
        Types   = @{}
    }

    # sockets
    if (!(Test-PodeIsEmpty $Configuration.Ssl.Protocols)) {
        $Context.Server.Sockets.Ssl.Protocols = (ConvertTo-PodeSslProtocol -Protocol $Configuration.Ssl.Protocols)
    }

    if ([int]$Configuration.ReceiveTimeout -gt 0) {
        $Context.Server.Sockets.ReceiveTimeout = (Protect-PodeValue -Value $Configuration.ReceiveTimeout $Context.Server.Sockets.ReceiveTimeout)
    }

    # auto-import
    $Context.Server.AutoImport = Read-PodeAutoImportConfiguration -Configuration $Configuration

    # request
    if ([int]$Configuration.Request.Timeout -gt 0) {
        $Context.Server.Request.Timeout = [int]$Configuration.Request.Timeout
    }

    if ([long]$Configuration.Request.BodySize -gt 0) {
        $Context.Server.Request.BodySize = [long]$Configuration.Request.BodySize
    }

    # default folders
    if ($Configuration.DefaultFolders) {
        if ($Configuration.DefaultFolders.Public) {
            $Context.Server.DefaultFolders.Public = $Configuration.DefaultFolders.Public
        }
        if ($Configuration.DefaultFolders.Views) {
            $Context.Server.DefaultFolders.Views = $Configuration.DefaultFolders.Views
        }
        if ($Configuration.DefaultFolders.Errors) {
            $Context.Server.DefaultFolders.Errors = $Configuration.DefaultFolders.Errors
        }
    }

    # debug
    $Context.Server.Debug = @{
        Breakpoints = @{
            Enabled = [bool]$Configuration.Debug.Breakpoints.Enable
        }
    }
}

function Set-PodeWebConfiguration {
    param(
        [Parameter()]
        [hashtable]
        $Configuration,

        [Parameter()]
        $Context
    )

    # setup the main web config
    $Context.Server.Web = @{
        Static           = @{
            Defaults          = $Configuration.Static.Defaults
            RedirectToDefault = [bool]$Configuration.Static.RedirectToDefault
            Cache             = @{
                Enabled = [bool]$Configuration.Static.Cache.Enable
                MaxAge  = [int](Protect-PodeValue -Value $Configuration.Static.Cache.MaxAge -Default 3600)
                Include = (Convert-PodePathPatternsToRegex -Paths @($Configuration.Static.Cache.Include) -NotSlashes)
                Exclude = (Convert-PodePathPatternsToRegex -Paths @($Configuration.Static.Cache.Exclude) -NotSlashes)
            }
            ValidateLast      = [bool]$Configuration.Static.ValidateLast
        }
        ErrorPages       = @{
            ShowExceptions      = [bool]$Configuration.ErrorPages.ShowExceptions
            StrictContentTyping = [bool]$Configuration.ErrorPages.StrictContentTyping
            Default             = $Configuration.ErrorPages.Default
            Routes              = @{}
        }
        ContentType      = @{
            Default = $Configuration.ContentType.Default
            Routes  = @{}
        }
        TransferEncoding = @{
            Default = $Configuration.TransferEncoding.Default
            Routes  = @{}
        }
        Compression      = @{
            Enabled = [bool]$Configuration.Compression.Enable
        }
        OpenApi          = @{
            DefaultDefinitionTag = [string](Protect-PodeValue -Value $Configuration.OpenApi.DefaultDefinitionTag -Default 'default')
        }
    }

    if ($Configuration.OpenApi -and $Configuration.OpenApi.ContainsKey('UsePodeYamlInternal')) {
        $Context.Server.Web.OpenApi.UsePodeYamlInternal = $Configuration.OpenApi.UsePodeYamlInternal
    }

    # setup content type route patterns for forced content types
    $Configuration.ContentType.Routes.Keys | Where-Object { ![string]::IsNullOrWhiteSpace($_) } | ForEach-Object {
        $_type = $Configuration.ContentType.Routes[$_]
        $_pattern = (Convert-PodePathPatternToRegex -Path $_ -NotSlashes)
        $Context.Server.Web.ContentType.Routes[$_pattern] = $_type
    }

    # setup transfer encoding route patterns for forced transfer encodings
    $Configuration.TransferEncoding.Routes.Keys | Where-Object { ![string]::IsNullOrWhiteSpace($_) } | ForEach-Object {
        $_type = $Configuration.TransferEncoding.Routes[$_]
        $_pattern = (Convert-PodePathPatternToRegex -Path $_ -NotSlashes)
        $Context.Server.Web.TransferEncoding.Routes[$_pattern] = $_type
    }

    # setup content type route patterns for error pages
    $Configuration.ErrorPages.Routes.Keys | Where-Object { ![string]::IsNullOrWhiteSpace($_) } | ForEach-Object {
        $_type = $Configuration.ErrorPages.Routes[$_]
        $_pattern = (Convert-PodePathPatternToRegex -Path $_ -NotSlashes)
        $Context.Server.Web.ErrorPages.Routes[$_pattern] = $_type
    }
}

function New-PodeAutoRestartServer {
    [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSPossibleIncorrectComparisonWithNull', '')]
    [CmdletBinding()]
    param()
    # don't configure if not supplied, or running as serverless
    $config = (Get-PodeConfig)
    if (($null -eq $config) -or ($null -eq $config.Server.Restart) -or $PodeContext.Server.IsServerless) {
        return
    }

    $restart = $config.Server.Restart

    # period - setup a timer
    $period = [int]$restart.period
    if ($period -gt 0) {
        Add-PodeTimer -Name '__pode_restart_period__' -Interval ($period * 60) -ScriptBlock {
            $PodeContext.Tokens.Restart.Cancel()
        }
    }

    # times - convert into cron expressions
    $times = @(@($restart.times) -ne $null)
    if (($times | Measure-Object).Count -gt 0) {
        $crons = @()

        @($times) | ForEach-Object {
            $atoms = $_ -split '\:'
            $crons += "$([int]$atoms[1]) $([int]$atoms[0]) * * *"
        }

        Add-PodeSchedule -Name '__pode_restart_times__' -Cron @($crons) -ScriptBlock {
            $PodeContext.Tokens.Restart.Cancel()
        }
    }

    # crons - setup schedules
    $crons = @(@($restart.crons) -ne $null)
    if (($crons | Measure-Object).Count -gt 0) {
        Add-PodeSchedule -Name '__pode_restart_crons__' -Cron @($crons) -ScriptBlock {
            $PodeContext.Tokens.Restart.Cancel()
        }
    }
}

<#
.SYNOPSIS
    Sets global output variables based on the Pode server context.

.DESCRIPTION
    This function sets global output variables based on the Pode server context. It retrieves output variables from the server context and assigns them as global variables. These output variables can be accessed and used in other parts of your code.

.OUTPUTS
    Sets global output variables based on the Pode server context.

#>
function Set-PodeOutputVariable {
    if (Test-PodeIsEmpty $PodeContext.Server.Output.Variables) {
        return
    }

    foreach ($key in $PodeContext.Server.Output.Variables.Keys) {
        try {
            Set-Variable -Name $key -Value $PodeContext.Server.Output.Variables[$key] -Force -Scope Global
        }
        catch {
            $_ | Write-PodeErrorLog
        }
    }
}
src\Private\Cookies.ps1
function ConvertTo-PodeCookie {
    param(
        [Parameter()]
        [System.Net.Cookie]
        $Cookie
    )

    if ($null -eq $Cookie) {
        return @{}
    }

    return @{
        Name      = $Cookie.Name
        Value     = $Cookie.Value
        Expires   = $Cookie.Expires
        Expired   = $Cookie.Expired
        Discard   = $Cookie.Discard
        HttpOnly  = $Cookie.HttpOnly
        Secure    = $Cookie.Secure
        Path      = $Cookie.Path
        TimeStamp = $Cookie.TimeStamp
        Signed    = $Cookie.Value.StartsWith('s:')
    }
}

function ConvertTo-PodeCookieString {
    param(
        [Parameter(Mandatory = $true)]
        $Cookie
    )

    try {
        $builder = [System.Text.StringBuilder]::new()
        $null = $builder.Append($Cookie.Name)
        $null = $builder.Append('=')
        $null = $builder.Append($Cookie.Value)

        if ($Cookie.Discard) {
            $null = $builder.Append('; Discard')
        }

        if ($Cookie.HttpOnly) {
            $null = $builder.Append('; HttpOnly')
        }

        if ($Cookie.Secure) {
            $null = $builder.Append('; Secure')
        }

        if (![string]::IsNullOrEmpty($Cookie.Domain)) {
            $null = $builder.Append('; Domain=')
            $null = $builder.Append($Cookie.Domain)
        }

        if (![string]::IsNullOrEmpty($Cookie.Path)) {
            $null = $builder.Append('; Path=')
            $null = $builder.Append($Cookie.Path)
        }

        if (($null -ne $Cookie.Expires) -and ($Cookie.Expires.Ticks -ne 0)) {
            $secs = ($Cookie.Expires.Subtract([datetime]::UtcNow)).TotalSeconds
            if ($secs -lt 0) {
                $secs = 0
            }

            $null = $builder.Append('; Max-Age=')
            $null = $builder.Append($secs)
        }

        if ($builder.Length -le 1) {
            return $null
        }

        return $builder.ToString()
    }
    finally {
        $builder = $null
    }
}
src\Private\CronParser.ps1
<#
.SYNOPSIS
    Provides a list of cron expression fields.

.DESCRIPTION
    This function returns an array of strings representing the different fields in a cron expression. These fields include 'Minute', 'Hour', 'DayOfMonth', 'Month', and 'DayOfWeek'.

.OUTPUTS
    Returns an array of strings representing cron expression fields.

.NOTES
    This is an internal function and may change in future releases of Pode.
#>
function Get-PodeCronField {
    [CmdletBinding()]
    [OutputType([string[]])]
    param()
    return [string[]]@(
        'Minute',
        'Hour',
        'DayOfMonth',
        'Month',
        'DayOfWeek'
    )
}

<#
.SYNOPSIS
    Provides constraints and information for cron expression fields.

.DESCRIPTION
    This function returns a hashtable containing constraints and information for various cron expression fields. It includes details such as valid ranges for minutes, hours, days of the month, months, and days of the week.

.OUTPUTS
    Returns a hashtable with constraints and information for cron expression fields.

.NOTES
    This is an internal function and may change in future releases of Pode.
#>
function Get-PodeCronFieldConstraint {
    [CmdletBinding()]
    [OutputType([hashtable])]
    param()
    return @{
        MinMax       = @(
            @(0, 59),
            @(0, 23),
            @(1, 31),
            @(1, 12),
            @(0, 6)
        )
        DaysInMonths = @(
            31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31
        )
        Months       = @(
            'January', 'February', 'March', 'April', 'May', 'June', 'July',
            'August', 'September', 'October', 'November', 'December'
        )
    }
}

function Get-PodeCronPredefined {
    return @{
        # normal
        '@minutely'       = '* * * * *'
        '@hourly'         = '0 * * * *'
        '@daily'          = '0 0 * * *'
        '@weekly'         = '0 0 * * 0'
        '@monthly'        = '0 0 1 * *'
        '@quarterly'      = '0 0 1 1,4,7,10 *'
        '@yearly'         = '0 0 1 1 *'
        '@annually'       = '0 0 1 1 *'

        # twice
        '@twice-hourly'   = '0,30 * * * *'
        '@twice-daily'    = '0 0,12 * * *'
        '@twice-weekly'   = '0 0 * * 0,4'
        '@twice-monthly'  = '0 0 1,15 * *'
        '@twice-yearly'   = '0 0 1 1,6 *'
        '@twice-annually' = '0 0 1 1,6 *'
    }
}

<#
.SYNOPSIS
    Provides aliases for cron expression fields.

.DESCRIPTION
    This function returns a hashtable containing aliases for cron expression fields. It includes mappings for month abbreviations (e.g., 'Jan' to 1) and day of the week abbreviations (e.g., 'Sun' to 0).

.OUTPUTS
    Returns a hashtable with aliases for cron expression fields.

.NOTES
    This is an internal function and may change in future releases of Pode.
#>
function Get-PodeCronFieldAlias {
    [CmdletBinding()]
    [OutputType([hashtable])]
    param()
    return @{
        Month     = @{
            Jan = 1
            Feb = 2
            Mar = 3
            Apr = 4
            May = 5
            Jun = 6
            Jul = 7
            Aug = 8
            Sep = 9
            Oct = 10
            Nov = 11
            Dec = 12
        }
        DayOfWeek = @{
            Sun = 0
            Mon = 1
            Tue = 2
            Wed = 3
            Thu = 4
            Fri = 5
            Sat = 6
        }
    }
}

<#
.SYNOPSIS
    Converts a Pode-style cron expression into a hashtable representation.

.DESCRIPTION
    This function takes an array of Pode-style cron expressions and converts them into a hashtable format. Each hashtable represents a cron expression with its individual components.

.PARAMETER Expression
    An array of Pode-style cron expressions to convert.

.OUTPUTS
    A hashtable representing the cron expression with the following keys:
    - 'Minute'
    - 'Hour'
    - 'DayOfMonth'
    - 'Month'
    - 'DayOfWeek'

.NOTES
    This is an internal function and may change in future releases of Pode.
#>

function ConvertFrom-PodeCronExpression {
    [CmdletBinding()]
    [OutputType([hashtable])]
    param(
        [Parameter(Mandatory = $true)]
        [ValidateNotNullOrEmpty()]
        [string[]]
        $Expression
    )
    $cronList = @()

    foreach ($item in $Expression) {
        if ([string]::IsNullOrEmpty($item)) {
            continue
        }
        $item = $item.Trim()

        # check predefineds
        $predef = Get-PodeCronPredefined
        if (!(Test-PodeIsEmpty $predef[$item])) {
            $item = $predef[$item]
        }

        # split and check atoms length
        $atoms = @($item -isplit '\s+')
        if ($atoms.Length -ne 5) {
            # Cron expression should only consist of 5 parts
            throw ($PodeLocale.cronExpressionInvalidExceptionMessage -f $Expression)
        }

        # basic variables
        $aliasRgx = '(?<tag>[a-z]{3})'

        # get cron obj and validate atoms
        $fields = Get-PodeCronField
        $constraints = Get-PodeCronFieldConstraint
        $aliases = Get-PodeCronFieldAlias
        $cron = @{}

        for ($i = 0; $i -lt $atoms.Length; $i++) {
            $_cronExp = @{
                Range       = $null
                Values      = $null
                Constraints = $null
                Random      = $false
                WildCard    = $false
            }

            $_atom = $atoms[$i]
            $_field = $fields[$i]
            $_constraint = $constraints.MinMax[$i]
            $_aliases = $aliases[$_field]

        # replace day of week and months with numbers
        if (@('month', 'dayofweek') -icontains $_field) {
            while ($_atom -imatch $aliasRgx) {
                $_alias = $_aliases[$Matches['tag']]
                if ($null -eq $_alias) {
                    # Invalid $($_field) alias found: $($Matches['tag'])
                    throw ($PodeLocale.invalidAliasFoundExceptionMessage -f $_field, $Matches['tag'])
                }

                    $_atom = $_atom -ireplace $Matches['tag'], $_alias
                    $null = $_atom -imatch $aliasRgx
                }
            }

        # ensure atom is a valid value
        if (!($_atom -imatch '^[\d|/|*|\-|,r]+$')) {
            # Invalid atom character
            throw ($PodeLocale.invalidAtomCharacterExceptionMessage -f $_atom)
        }

            # replace * with min/max constraint
            if ($_atom -ieq '*') {
                $_cronExp.WildCard = $true
                $_atom = ($_constraint -join '-')
            }

            # parse the atom for either a literal, range, array, or interval
            # literal
            if ($_atom -imatch '^(\d+|r)$') {
                # check if it's random
                if ($_atom -ieq 'r') {
                    $_cronExp.Values = @(Get-Random -Minimum $_constraint[0] -Maximum ($_constraint[1] + 1))
                    $_cronExp.Random = $true
                }
                else {
                    $_cronExp.Values = @([int]$_atom)
                }
            }

            # range
            elseif ($_atom -imatch '^(?<min>\d+)\-(?<max>\d+)$') {
                $_cronExp.Range = @{ 'Min' = [int]($Matches['min'].Trim()); 'Max' = [int]($Matches['max'].Trim()); }
            }

            # array
            elseif ($_atom -imatch '^[\d,]+$') {
                $_cronExp.Values = [int[]](@($_atom -split ',').Trim())
            }

            # interval
            elseif ($_atom -imatch '(?<start>(\d+|\*))\/(?<interval>(\d+|r))$') {
                $start = $Matches['start']
                $interval = $Matches['interval']

                if ($interval -ieq '0') {
                    $interval = '1'
                }

                if ([string]::IsNullOrWhiteSpace($start) -or ($start -ieq '*')) {
                    $start = '0'
                }

                # set the initial trigger value
                $_cronExp.Values = @([int]$start)

                # check if it's random
                if ($interval -ieq 'r') {
                    $_cronExp.Random = $true
                }
                else {
                    # loop to get all next values
                    $next = [int]$start + [int]$interval
                    while ($next -le $_constraint[1]) {
                        $_cronExp.Values += $next
                        $next += [int]$interval
                    }
                }
            }

        # error
        else {
            # Invalid cron atom format found
            throw ($PodeLocale.invalidCronAtomFormatExceptionMessage -f $_atom)
        }

        # ensure cron expression values are valid
        if ($null -ne $_cronExp.Range) {
            if ($_cronExp.Range.Min -gt $_cronExp.Range.Max) {
                # Min value should not be greater than the max value
                throw ($PodeLocale.minValueGreaterThanMaxExceptionMessage -f $_field)
            }

            if ($_cronExp.Range.Min -lt $_constraint[0]) {
                # Min value for $($_field) is invalid, should be greater than/equal
                throw ($PodeLocale.minValueInvalidExceptionMessage -f $_cronExp.Range.Min, $_field, $_constraint[0])
            }

            if ($_cronExp.Range.Max -gt $_constraint[1]) {
                # Max value for $($_field) is invalid, should be greater than/equal
                throw ($PodeLocale.maxValueInvalidExceptionMessage -f $_cronExp.Range.Max, $_field, $_constraint[1])
            }
        }

        if ($null -ne $_cronExp.Values) {
            $_cronExp.Values | ForEach-Object {
                if ($_ -lt $_constraint[0] -or $_ -gt $_constraint[1]) {
                    # Value is invalid, should be between
                    throw ($PodeLocale.valueOutOfRangeExceptionMessage -f $value, $_field, $_constraint[0], $_constraint[1])
                }
            }
        }

            # assign value
            $_cronExp.Constraints = $_constraint
            $cron[$_field] = $_cronExp
        }

    # post validation for month/days in month
    if (($null -ne $cron['Month'].Values) -and ($null -ne $cron['DayOfMonth'].Values)) {
        foreach ($mon in $cron['Month'].Values) {
            foreach ($day in $cron['DayOfMonth'].Values) {
                if ($day -gt $constraints.DaysInMonths[$mon - 1]) {
                    # $($constraints.Months[$mon - 1]) only has $($constraints.DaysInMonths[$mon - 1]) days, but $($day) was supplied
                    throw ($PodeLocale.daysInMonthExceededExceptionMessage -f $constraints.Months[$mon - 1], $constraints.DaysInMonths[$mon - 1], $day)
                }
            }
        }
    }

        # flag if this cron contains a random atom
        $cron['Random'] = (($cron.Values | Where-Object { $_.Random } | Measure-Object).Count -gt 0)

        # add the cron to the list
        $cronList += $cron
    }

    # return the cronlist
    return $cronList
}

function Reset-PodeRandomCronExpressions {
    [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseSingularNouns', '')]
    param(
        [Parameter(Mandatory = $true)]
        [ValidateNotNull()]
        $Expressions
    )

    return @(@($Expressions) | ForEach-Object {
            Reset-PodeRandomCronExpression -Expression $_
        })
}

function Reset-PodeRandomCronExpression {
    param(
        [Parameter(Mandatory = $true)]
        [ValidateNotNull()]
        $Expression
    )

    function Reset-Atom($Atom) {
        if (!$Atom.Random) {
            return $Atom
        }

        if ($Atom.Random) {
            $Atom.Values = @(Get-Random -Minimum $Atom.Constraints[0] -Maximum ($Atom.Constraints[1] + 1))
        }

        return $Atom
    }

    if (!$Expression.Random) {
        return $Expression
    }

    $Expression.Minute = (Reset-Atom -Atom $Expression.Minute)
    $Expression.Hour = (Reset-Atom -Atom $Expression.Hour)
    $Expression.DayOfMonth = (Reset-Atom -Atom $Expression.DayOfMonth)
    $Expression.Month = (Reset-Atom -Atom $Expression.Month)
    $Expression.DayOfWeek = (Reset-Atom -Atom $Expression.DayOfWeek)

    return $Expression
}

function Test-PodeCronExpressions {
    [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseSingularNouns', '')]
    param(
        [Parameter(Mandatory = $true)]
        [ValidateNotNull()]
        $Expressions,

        [Parameter()]
        $DateTime = $null
    )

    return ((@($Expressions) | Where-Object {
                Test-PodeCronExpression -Expression $_ -DateTime $DateTime
            } | Measure-Object).Count -gt 0)
}

function Test-PodeCronExpression {
    param(
        [Parameter(Mandatory = $true)]
        [ValidateNotNull()]
        $Expression,

        [Parameter()]
        $DateTime = $null
    )

    function Test-RangeAndValue($AtomContraint, $NowValue) {
        if ($null -ne $AtomContraint.Range) {
            return (!(($NowValue -lt $AtomContraint.Range.Min) -or ($NowValue -gt $AtomContraint.Range.Max)))
        }

        return ($AtomContraint.Values -icontains $NowValue)
    }

    # current time
    if ($null -eq $DateTime) {
        $DateTime = [datetime]::Now
    }

    # check day of month
    if (!(Test-RangeAndValue -AtomContraint $Expression.DayOfMonth -NowValue $DateTime.Day)) {
        return $false
    }

    # check day of week
    if (!(Test-RangeAndValue -AtomContraint $Expression.DayOfWeek -NowValue ([int]$DateTime.DayOfWeek))) {
        return $false
    }

    # check month
    if (!(Test-RangeAndValue -AtomContraint $Expression.Month -NowValue $DateTime.Month)) {
        return $false
    }

    # check hour
    if (!(Test-RangeAndValue -AtomContraint $Expression.Hour -NowValue $DateTime.Hour)) {
        return $false
    }

    # check minute
    if (!(Test-RangeAndValue -AtomContraint $Expression.Minute -NowValue $DateTime.Minute)) {
        return $false
    }

    # date is valid
    return $true
}

function Get-PodeCronNextEarliestTrigger {
    param(
        [Parameter(Mandatory = $true)]
        [ValidateNotNull()]
        $Expressions,

        [Parameter()]
        $StartTime = $null,

        [Parameter()]
        $EndTime = $null
    )

    return (@($Expressions) | Foreach-Object {
            Get-PodeCronNextTrigger -Expression $_ -StartTime $StartTime -EndTime $EndTime
        } | Where-Object { $null -ne $_ } | Sort-Object | Select-Object -First 1)
}

function Get-PodeCronNextTrigger {
    param(
        [Parameter(Mandatory = $true)]
        [ValidateNotNull()]
        $Expression,

        [Parameter()]
        $StartTime = $null,

        [Parameter()]
        $EndTime = $null
    )

    # start from the current time, if a start time not defined
    if ($null -eq $StartTime) {
        $StartTime = [datetime]::Now
    }
    $StartTime = $StartTime.AddMinutes(1)

    # the next time to trigger
    $NextTime = [datetime]::new($StartTime.Year, $StartTime.Month, $StartTime.Day, $StartTime.Hour, $StartTime.Minute, 0)

    # first, is the current time valid?
    if (Test-PodeCronExpression -Expression $Expression -DateTime $NextTime) {
        return $NextTime
    }

    # functions for getting the closest value
    function Get-ClosestValue($AtomContraint, $NowValue) {
        $_values = $AtomContraint.Values
        if ($null -eq $_values) {
            $_values = ($AtomContraint.Range.Min..$AtomContraint.Range.Max)
        }

        if (($_values.Length -eq 1) -or ($_values[-1] -lt $NowValue) -or ($_values[0] -gt $NowValue)) {
            return $_values[0]
        }

        return ($_values -ge $NowValue)[0]
    }

    # loop until we get a date
    while ($true) {
        # check the minute
        if (!$Expression.Minute.WildCard) {
            $minute = Get-ClosestValue -AtomContraint $Expression.Minute -NowValue $NextTime.Minute
            if ($minute -lt $NextTime.Minute) {
                $NextTime = $NextTime.AddHours(1)
            }

            $NextTime = $NextTime.AddMinutes($minute - $NextTime.Minute)
        }

        # check hour
        if (!$Expression.Hour.WildCard) {
            $hour = Get-ClosestValue -AtomContraint $Expression.Hour -NowValue $NextTime.Hour
            if ($hour -lt $NextTime.Hour) {
                $NextTime = $NextTime.AddDays(1)
            }

            $_hour = $NextTime.Hour
            $NextTime = $NextTime.AddHours($hour - $NextTime.Hour)
            if ($_hour -ne $hour) {
                $NextTime = [datetime]::new($NextTime.Year, $NextTime.Month, $NextTime.Day, $NextTime.Hour, 0, 0)
                continue
            }
        }

        # check day
        if (!$Expression.DayOfMonth.WildCard) {
            $day = Get-ClosestValue -AtomContraint $Expression.DayOfMonth -NowValue $NextTime.Day
            if (($day -lt $NextTime.Day) -or ($day -gt [datetime]::DaysInMonth($NextTime.Year, $NextTime.Month))) {
                $NextTime = $NextTime.AddMonths(1)
            }

            if ($day -gt [datetime]::DaysInMonth($NextTime.Year, $NextTime.Month)) {
                $NextTime = [datetime]::new($NextTime.Year, $NextTime.Month, 1, 0, 0, 0)
                continue
            }

            $_day = $NextTime.Day
            $NextTime = $NextTime.AddDays($day - $NextTime.Day)
            if ($_day -ne $day) {
                $NextTime = [datetime]::new($NextTime.Year, $NextTime.Month, $NextTime.Day, 0, 0, 0)
                continue
            }
        }

        # check month
        if (!$Expression.Month.WildCard) {
            $month = Get-ClosestValue -AtomContraint $Expression.Month -NowValue $NextTime.Month
            if ($month -lt $NextTime.Month) {
                $NextTime = $NextTime.AddYears(1)
            }

            $_month = $NextTime.Month
            $NextTime = $NextTime.AddMonths($month - $NextTime.Month)
            if ($_month -ne $month) {
                $NextTime = [datetime]::new($NextTime.Year, $NextTime.Month, 1, 0, 0, 0)
                continue
            }
        }

        # check day of week
        if (!$Expression.DayOfWeek.WildCard) {
            $doweek = Get-ClosestValue -AtomContraint $Expression.DayOfWeek -NowValue $NextTime.DayOfWeek

            $_doweek = $NextTime.DayOfWeek
            if ($doweek -lt $NextTime.DayOfWeek) {
                $NextTime = $NextTime.AddDays(7 - ($NextTime.DayOfWeek - $doweek))
            }
            elseif ($doweek -gt $NextTime.DayOfWeek) {
                $NextTime = $NextTime.AddDays($doweek - $NextTime.DayOfWeek)
            }

            if ($_doweek -ne $doweek) {
                $NextTime = [datetime]::new($NextTime.Year, $NextTime.Month, $NextTime.Day, 0, 0, 0)
                continue
            }
        }

        break
    }

    # before we return, make sure the time is valid
    if (!(Test-PodeCronExpression -Expression $Expression -DateTime $NextTime)) {
        throw ($PodeLocale.nextTriggerCalculationErrorExceptionMessage -f $NextTime) #"Looks like something went wrong trying to calculate the next trigger datetime: $($NextTime)"
    }

    # if before the start or after end then return null
    if (($NextTime -lt $StartTime) -or (($null -ne $EndTime) -and ($NextTime -gt $EndTime))) {
        return $null
    }

    return $NextTime
}
src\Private\Cryptography.ps1
<#
.SYNOPSIS
    Computes an HMAC-SHA256 hash for a given value using a secret key.

.DESCRIPTION
    This function calculates an HMAC-SHA256 hash for the specified value using either a secret provided as a string or as a byte array. It supports two parameter sets:
    1. String: The secret is provided as a string.
    2. Bytes: The secret is provided as a byte array.

.PARAMETER Value
    The value for which the HMAC-SHA256 hash needs to be computed.

.PARAMETER Secret
    The secret key as a string. If this parameter is provided, it will be converted to a byte array.

.PARAMETER SecretBytes
    The secret key as a byte array. If this parameter is provided, it will be used directly.

.OUTPUTS
    Returns the computed HMAC-SHA256 hash as a base64-encoded string.

.EXAMPLE
    $value = "MySecretValue"
    $secret = "MySecretKey"
    $hash = Invoke-PodeHMACSHA256Hash -Value $value -Secret $secret
    Write-PodeHost "HMAC-SHA256 hash: $hash"

    This example computes the HMAC-SHA256 hash for the value "MySecretValue" using the secret key "MySecretKey".
.NOTES
    - This function is intended for internal use.
#>
function Invoke-PodeHMACSHA256Hash {
    [CmdletBinding(DefaultParameterSetName = 'String')]
    [OutputType([String])]
    param(
        [Parameter(Mandatory = $true)]
        [string]
        $Value,

        [Parameter(Mandatory = $true, ParameterSetName = 'String')]
        [string]
        $Secret,

        [Parameter(Mandatory = $true, ParameterSetName = 'Bytes')]
        [byte[]]
        $SecretBytes
    )

    # Convert secret to byte array if provided as a string
    if (![string]::IsNullOrWhiteSpace($Secret)) {
        $SecretBytes = [System.Text.Encoding]::UTF8.GetBytes($Secret)
    }

    # Validate secret length
    if ($SecretBytes.Length -eq 0) {
        # No secret supplied for HMAC256 hash
        throw ($PodeLocale.noSecretForHmac256ExceptionMessage)
    }

    # Compute HMAC-SHA384 hash
    $crypto = [System.Security.Cryptography.HMACSHA256]::new($SecretBytes)
    return [System.Convert]::ToBase64String($crypto.ComputeHash([System.Text.Encoding]::UTF8.GetBytes($Value)))
}

<#
.SYNOPSIS
    Computes a private HMAC-SHA384 hash for a given value using a secret key.

.DESCRIPTION
    This function calculates a private HMAC-SHA384 hash for the specified value using either a secret provided as a string or as a byte array. It supports two parameter sets:
    1. String: The secret is provided as a string.
    2. Bytes: The secret is provided as a byte array.

.PARAMETER Value
    The value for which the private HMAC-SHA384 hash needs to be computed.

.PARAMETER Secret
    The secret key as a string. If this parameter is provided, it will be converted to a byte array.

.PARAMETER SecretBytes
    The secret key as a byte array. If this parameter is provided, it will be used directly.

.OUTPUTS
    Returns the computed private HMAC-SHA384 hash as a base64-encoded string.

.EXAMPLE
    $value = "MySecretValue"
    $secret = "MySecretKey"
    $hash = Invoke-PodeHMACSHA384Hash -Value $value -Secret $secret
    Write-PodeHost "Private HMAC-SHA384 hash: $hash"

    This example computes the private HMAC-SHA384 hash for the value "MySecretValue" using the secret key "MySecretKey".

.NOTES
    - This function is intended for internal use.
#>
function Invoke-PodeHMACSHA384Hash {
    [CmdletBinding(DefaultParameterSetName = 'String')]
    [OutputType([String])]
    param(
        [Parameter(Mandatory = $true)]
        [string]
        $Value,

        [Parameter(Mandatory = $true, ParameterSetName = 'String')]
        [string]
        $Secret,

        [Parameter(Mandatory = $true, ParameterSetName = 'Bytes')]
        [byte[]]
        $SecretBytes
    )

    # Convert secret to byte array if provided as a string
    if (![string]::IsNullOrWhiteSpace($Secret)) {
        $SecretBytes = [System.Text.Encoding]::UTF8.GetBytes($Secret)
    }

    # Validate secret length
    if ($SecretBytes.Length -eq 0) {
        # No secret supplied for HMAC384 hash
        throw ($PodeLocale.noSecretForHmac384ExceptionMessage)
    }

    # Compute private HMAC-SHA384 hash
    $crypto = [System.Security.Cryptography.HMACSHA384]::new($SecretBytes)
    return [System.Convert]::ToBase64String($crypto.ComputeHash([System.Text.Encoding]::UTF8.GetBytes($Value)))
}

<#
.SYNOPSIS
    Computes a private HMAC-SHA512 hash for a given value using a secret key.

.DESCRIPTION
    This function calculates a private HMAC-SHA512 hash for the specified value using either a secret provided as a string or as a byte array. It supports two parameter sets:
    1. String: The secret is provided as a string.
    2. Bytes: The secret is provided as a byte array.

.PARAMETER Value
    The value for which the private HMAC-SHA512 hash needs to be computed.

.PARAMETER Secret
    The secret key as a string. If this parameter is provided, it will be converted to a byte array.

.PARAMETER SecretBytes
    The secret key as a byte array. If this parameter is provided, it will be used directly.

.OUTPUTS
    Returns the computed private HMAC-SHA512 hash as a base64-encoded string.

.EXAMPLE
    $value = "MySecretValue"
    $secret = "MySecretKey"
    $hash = Invoke-PodeHMACSHA512Hash -Value $value -Secret $secret
    Write-PodeHost "Private HMAC-SHA512 hash: $hash"

    This example computes the private HMAC-SHA512 hash for the value "MySecretValue" using the secret key "MySecretKey".

.NOTES
    - This function is intended for internal use.
#>
function Invoke-PodeHMACSHA512Hash {
    [CmdletBinding(DefaultParameterSetName = 'String')]
    [OutputType([string])]
    param(
        [Parameter(Mandatory = $true)]
        [string]
        $Value,

        [Parameter(Mandatory = $true, ParameterSetName = 'String')]
        [string]
        $Secret,

        [Parameter(Mandatory = $true, ParameterSetName = 'Bytes')]
        [byte[]]
        $SecretBytes
    )

    # Convert secret to byte array if provided as a string
    if (![string]::IsNullOrWhiteSpace($Secret)) {
        $SecretBytes = [System.Text.Encoding]::UTF8.GetBytes($Secret)
    }

    # Validate secret length
    if ($SecretBytes.Length -eq 0) {
        # No secret supplied for HMAC512 hash
        throw ($PodeLocale.noSecretForHmac512ExceptionMessage)
    }

    # Compute private HMAC-SHA512 hash
    $crypto = [System.Security.Cryptography.HMACSHA512]::new($SecretBytes)
    return [System.Convert]::ToBase64String($crypto.ComputeHash([System.Text.Encoding]::UTF8.GetBytes($Value)))
}

function Invoke-PodeSHA256Hash {
    [CmdletBinding()]
    [OutputType([string])]
    param(
        [Parameter(Mandatory = $true)]
        [ValidateNotNullOrEmpty()]
        [string]
        $Value
    )

    $crypto = [System.Security.Cryptography.SHA256]::Create()
    return [System.Convert]::ToBase64String($crypto.ComputeHash([System.Text.Encoding]::UTF8.GetBytes($Value)))
}

function Invoke-PodeSHA1Hash {
    [CmdletBinding()]
    [OutputType([string])]
    param(
        [Parameter(Mandatory = $true)]
        [ValidateNotNullOrEmpty()]
        [string]
        $Value
    )

    $crypto = [System.Security.Cryptography.SHA1]::Create()
    return [System.Convert]::ToBase64String($crypto.ComputeHash([System.Text.Encoding]::UTF8.GetBytes($Value)))
}

function ConvertTo-PodeBase64Auth {
    [CmdletBinding()]
    [OutputType([string])]
    param(
        [Parameter(Mandatory = $true)]
        [string]
        $Username,

        [Parameter(Mandatory = $true)]
        [string]
        $Password
    )

    return [System.Convert]::ToBase64String([System.Text.Encoding]::UTF8.GetBytes("$($Username):$($Password)"))
}

function Invoke-PodeMD5Hash {
    [CmdletBinding()]
    [OutputType([string])]
    param(
        [Parameter(Mandatory = $true)]
        [ValidateNotNullOrEmpty()]
        [string]
        $Value
    )

    $crypto = [System.Security.Cryptography.MD5]::Create()
    return [System.BitConverter]::ToString($crypto.ComputeHash([System.Text.Encoding]::ASCII.GetBytes($Value))).Replace('-', '').ToLowerInvariant()
}

<#
.SYNOPSIS
Generates a random byte array of specified length.

.DESCRIPTION
This function generates a random byte array using the .NET `System.Security.Cryptography.RandomNumberGenerator` class. You can specify the desired length of the byte array.

.PARAMETER Length
The length of the byte array to generate (default is 16).

.OUTPUTS
An array of bytes representing the random byte array.

.NOTES
    This is an internal function and may change in future releases of Pode.
#>
function Get-PodeRandomByte {
    [CmdletBinding()]
    [OutputType([System.Object[]])]
    param(
        [Parameter()]
        [int]
        $Length = 16
    )

    return (Use-PodeStream -Stream ([System.Security.Cryptography.RandomNumberGenerator]::Create()) {
            param($p)
            $bytes = [byte[]]::new($Length)
            $p.GetBytes($bytes)
            return $bytes
        })
}

function New-PodeSalt {
    [CmdletBinding()]
    [OutputType([string])]
    param(
        [Parameter()]
        [int]
        $Length = 8
    )

    $bytes = [byte[]](Get-PodeRandomByte -Length $Length)
    return [System.Convert]::ToBase64String($bytes)
}

function New-PodeGuid {
    [CmdletBinding()]
    [OutputType([string])]
    param(
        [Parameter()]
        [int]
        $Length = 16,

        [switch]
        $Secure,

        [switch]
        $NoDashes
    )

    # generate a cryptographically secure guid
    if ($Secure) {
        $bytes = [byte[]](Get-PodeRandomByte -Length $Length)
        $guid = ([guid]::new($bytes)).ToString()
    }

    # return a normal guid
    else {
        $guid = ([guid]::NewGuid()).ToString()
    }

    if ($NoDashes) {
        $guid = ($guid -ireplace '-', '')
    }

    return $guid
}

function Invoke-PodeValueSign {
    [CmdletBinding()]
    [OutputType([string])]
    param(
        [Parameter(Mandatory = $true, ValueFromPipeline = $true)]
        [ValidateNotNullOrEmpty()]
        [string]
        $Value,

        [Parameter(Mandatory = $true)]
        [ValidateNotNullOrEmpty()]
        [string]
        $Secret,

        [switch]
        $Strict
    )
    process {
        if ($Strict) {
            $Secret = ConvertTo-PodeStrictSecret -Secret $Secret
        }

        return "s:$($Value).$(Invoke-PodeHMACSHA256Hash -Value $Value -Secret $Secret)"
    }
}

function Invoke-PodeValueUnsign {
    [CmdletBinding()]
    [OutputType([string])]
    param(
        [Parameter(Mandatory = $true, ValueFromPipeline = $true)]
        [ValidateNotNullOrEmpty()]
        [string]
        $Value,

        [Parameter(Mandatory = $true)]
        [ValidateNotNullOrEmpty()]
        [string]
        $Secret,

        [switch]
        $Strict
    )
    process {
        # the signed value must start with "s:"
        if (!$Value.StartsWith('s:')) {
            return $null
        }

        # the signed value must contain a dot - splitting value and signature
        $Value = $Value.Substring(2)
        $periodIndex = $Value.LastIndexOf('.')
        if ($periodIndex -eq -1) {
            return $null
        }

        if ($Strict) {
            $Secret = ConvertTo-PodeStrictSecret -Secret $Secret
        }

        # get the raw value and signature
        $raw = $Value.Substring(0, $periodIndex)
        $sig = $Value.Substring($periodIndex + 1)

        if ((Invoke-PodeHMACSHA256Hash -Value $raw -Secret $Secret) -ne $sig) {
            return $null
        }

        return $raw
    }
}

function Test-PodeValueSigned {
    [CmdletBinding()]
    [OutputType([bool])]
    param(
        [Parameter(ValueFromPipeline = $true)]
        [string]
        $Value,

        [Parameter(Mandatory = $true)]
        [ValidateNotNullOrEmpty()]
        [string]
        $Secret,

        [switch]
        $Strict
    )
    process {
        if ([string]::IsNullOrEmpty($Value)) {
            return $false
        }

        $result = Invoke-PodeValueUnsign -Value $Value -Secret $Secret -Strict:$Strict
        return ![string]::IsNullOrEmpty($result)
    }
}

function ConvertTo-PodeStrictSecret {
    param(
        [Parameter(Mandatory = $true)]
        [string]
        $Secret
    )

    return "$($Secret);$($WebEvent.Request.UserAgent);$($WebEvent.Request.RemoteEndPoint.Address.IPAddressToString)"
}

function New-PodeJwtSignature {
    [CmdletBinding()]
    [OutputType([string])]
    param(
        [Parameter(Mandatory = $true)]
        [string]
        $Algorithm,

        [Parameter(Mandatory = $true)]
        [string]
        $Token,

        [Parameter()]
        [byte[]]
        $SecretBytes
    )

    if (($Algorithm -ine 'none') -and (($null -eq $SecretBytes) -or ($SecretBytes.Length -eq 0))) {
        # No secret supplied for JWT signature
        throw ($PodeLocale.noSecretForJwtSignatureExceptionMessage)
    }

    if (($Algorithm -ieq 'none') -and (($null -ne $secretBytes) -and ($SecretBytes.Length -gt 0))) {
        # Expected no secret to be supplied for no signature
        throw ($PodeLocale.noSecretExpectedForNoSignatureExceptionMessage)
    }

    $sig = $null

    switch ($Algorithm.ToUpperInvariant()) {
        'HS256' {
            $sig = Invoke-PodeHMACSHA256Hash -Value $Token -SecretBytes $SecretBytes
            $sig = ConvertTo-PodeBase64UrlValue -Value $sig -NoConvert
        }

        'HS384' {
            $sig = Invoke-PodeHMACSHA384Hash -Value $Token -SecretBytes $SecretBytes
            $sig = ConvertTo-PodeBase64UrlValue -Value $sig -NoConvert
        }

        'HS512' {
            $sig = Invoke-PodeHMACSHA512Hash -Value $Token -SecretBytes $SecretBytes
            $sig = ConvertTo-PodeBase64UrlValue -Value $sig -NoConvert
        }

        'NONE' {
            $sig = [string]::Empty
        }

        default {
            throw ($PodeLocale.unsupportedJwtAlgorithmExceptionMessage -f $Algorithm) #"The JWT algorithm is not currently supported: $($Algorithm)"
        }
    }

    return $sig
}

function ConvertTo-PodeBase64UrlValue {
    [CmdletBinding()]
    [OutputType([string])]
    param(
        [Parameter(Mandatory = $true)]
        [string]
        $Value,

        [switch]
        $NoConvert
    )

    if (!$NoConvert) {
        $Value = [System.Convert]::ToBase64String([System.Text.Encoding]::UTF8.GetBytes($Value))
    }

    $Value = ($Value -ireplace '\+', '-')
    $Value = ($Value -ireplace '/', '_')
    $Value = ($Value -ireplace '=', '')

    return $Value
}

function ConvertFrom-PodeJwtBase64Value {
    [CmdletBinding()]
    [OutputType([pscustomobject])]
    param(
        [Parameter(Mandatory = $true)]
        [string]
        $Value
    )

    # map chars
    $Value = ($Value -ireplace '-', '+')
    $Value = ($Value -ireplace '_', '/')

    # add padding
    switch ($Value.Length % 4) {
        1 {
            $Value = $Value.Substring(0, $Value.Length - 1)
        }

        2 {
            $Value += '=='
        }

        3 {
            $Value += '='
        }
    }

    # convert base64 to string
    try {
        $Value = [System.Text.Encoding]::UTF8.GetString([System.Convert]::FromBase64String($Value))
    }
    catch {
        # Invalid Base64 encoded value found in JWT
        throw ($PodeLocale.invalidBase64JwtExceptionMessage)
    }

    # return json
    try {
        return ($Value | ConvertFrom-Json)
    }
    catch {
        # Invalid JSON value found in JWT
        throw ($PodeLocale.invalidJsonJwtExceptionMessage)
    }
}
src\Private\Endpoints.ps1
<#
.SYNOPSIS
    Finds Pode endpoints based on protocol, address, or endpoint name.

.DESCRIPTION
    This function allows you to search for Pode endpoints based on different criteria. You can specify the protocol (HTTP or HTTPS), the address, or the endpoint name. It returns an array of hashtable objects representing the matching endpoints.

.PARAMETER Protocol
    The protocol of the endpoint (HTTP or HTTPS).

.PARAMETER Address
    The address of the endpoint.

.PARAMETER EndpointName
    The name of the endpoint.

.OUTPUTS
    An array of hashtables representing the matching endpoints, with the following keys:
    - 'Protocol'
    - 'Address'
    - 'Name'

.NOTES
    This is an internal function and may change in future releases of Pode.
#>
function Find-PodeEndpoint {
    [CmdletBinding()]
    [OutputType([hashtable[]])]
    param(
        [Parameter()]
        [ValidateSet('', 'Http', 'Https')]
        [string]
        $Protocol,

        [Parameter()]
        [string]
        $Address,

        [Parameter()]
        [string[]]
        $EndpointName
    )

    $endpoints = @()

    # just use a single endpoint/protocol
    if ([string]::IsNullOrWhiteSpace($EndpointName)) {
        $endpoints += @{
            Protocol = $Protocol
            Address  = $Address
            Name     = [string]::Empty
        }
    }

    # get all defined endpoints by name
    else {
        foreach ($name in @($EndpointName)) {
            $_endpoint = Get-PodeEndpointByName -Name $name -ThrowError
            if ($null -ne $_endpoint) {
                $endpoints += @{
                    Protocol = $_endpoint.Protocol
                    Address  = $_endpoint.RawAddress
                    Name     = $name
                }
            }
        }
    }

    # convert the endpoint's address into host:port format
    foreach ($_endpoint in $endpoints) {
        if (![string]::IsNullOrWhiteSpace($_endpoint.Address)) {
            $_addr = Get-PodeEndpointInfo -Address $_endpoint.Address -AnyPortOnZero
            $_endpoint.Address = "$($_addr.Host):$($_addr.Port)"
        }
    }

    return $endpoints
}

<#
.SYNOPSIS
    Retrieves internal endpoints based on the specified types.

.DESCRIPTION
    The `Get-PodeEndpointByProtocolType` function returns internal endpoints from the PodeContext
    based on the specified types (HTTP, WebSocket, SMTP, or TCP).

.PARAMETER Type
    Specifies the type of endpoints to retrieve. Valid values are 'Http', 'Ws', 'Smtp', and 'Tcp'.
    This parameter is mandatory.

.OUTPUTS
    Returns an array of internal endpoints matching the specified types.

.EXAMPLE
    # Example usage:
    $httpEndpoints = Get-PodeEndpointByProtocolType -Type 'Http'
    $wsEndpoints = Get-PodeEndpointByProtocolType -Type 'Ws'
    # Retrieve HTTP and WebSocket endpoints from the PodeContext.

.NOTES
    This is an internal function and may change in future releases of Pode.
#>
function Get-PodeEndpointByProtocolType {
    [CmdletBinding()]
    [OutputType([object[]])]
    param(
        [Parameter(Mandatory = $true)]
        [ValidateSet('Http', 'Ws', 'Smtp', 'Tcp')]
        [string[]]
        $Type
    )

    $endpoints = @()

    foreach ($t in $Type) {
        switch ($t.ToLowerInvariant()) {
            'http' {
                $endpoints += @($PodeContext.Server.Endpoints.Values | Where-Object { @('http', 'https') -icontains $_.Protocol })
            }

            'ws' {
                $endpoints += @($PodeContext.Server.Endpoints.Values | Where-Object { @('ws', 'wss') -icontains $_.Protocol })
            }

            'smtp' {
                $endpoints += @($PodeContext.Server.Endpoints.Values | Where-Object { @('smtp', 'smtps') -icontains $_.Protocol })
            }

            'tcp' {
                $endpoints += @($PodeContext.Server.Endpoints.Values | Where-Object { @('tcp', 'tcps') -icontains $_.Protocol })
            }
        }
    }

    return $endpoints
}

function Test-PodeEndpointByProtocolTypeProtocol {
    param(
        [Parameter(Mandatory = $true)]
        [ValidateSet('Http', 'Https', 'Ws', 'Wss', 'Smtp', 'Smtps', 'Tcp', 'Tcps')]
        [string]
        $Protocol
    )

    $endpoint = $PodeContext.Server.Endpoints.Values | Where-Object { $_.Protocol -ieq $Protocol }
    return ($null -ne $endpoint)
}

function Get-PodeEndpointType {
    param(
        [Parameter()]
        [ValidateSet('Http', 'Https', 'Smtp', 'Smtps', 'Tcp', 'Tcps', 'Ws', 'Wss')]
        [string]
        $Protocol
    )

    switch ($Protocol) {
        { $_ -iin @('http', 'https') } {
            'Http'
        }

        { $_ -iin @('ws', 'wss') } {
            'Ws'
        }

        { $_ -iin @('smtp', 'smtps') } {
            'Smtp'
        }

        { $_ -iin @('tcp', 'tcps') } {
            'Tcp'
        }

        default {
            $Protocol
        }
    }
}

function Get-PodeEndpointRunspacePoolName {
    param(
        [Parameter()]
        [ValidateSet('Http', 'Https', 'Smtp', 'Smtps', 'Tcp', 'Tcps', 'Ws', 'Wss')]
        [string]
        $Protocol
    )

    switch ($Protocol) {
        { $_ -iin @('http', 'https') } {
            'Web'
        }

        { $_ -iin @('ws', 'wss') } {
            'Signals'
        }

        { $_ -iin @('smtp', 'smtps') } {
            'Smtp'
        }

        { $_ -iin @('tcp', 'tcps') } {
            'Tcp'
        }

        default {
            $Protocol
        }
    }
}

<#
.SYNOPSIS
Tests whether Pode endpoints of a specified type exist.

.DESCRIPTION
This function checks if there are any Pode endpoints of the specified type (HTTP, WebSocket, SMTP, or TCP). It returns a boolean value indicating whether endpoints of that type are available.

.PARAMETER Type
The type of Pode endpoint to test (HTTP, WebSocket, SMTP, or TCP).

.OUTPUTS
A boolean value (True if endpoints exist, False otherwise).

.NOTES
    This is an internal function and may change in future releases of Pode.
#>
function Test-PodeEndpointByProtocolType {
    [CmdletBinding()]
    [OutputType([bool])]
    param(
        [Parameter(Mandatory = $true)]
        [ValidateSet('Http', 'Ws', 'Smtp', 'Tcp')]
        [string]
        $Type
    )

    $endpoints = (Get-PodeEndpointByProtocolType -Type $Type)
    return (($null -ne $endpoints) -and ($endpoints.Length -gt 0))

}

function Find-PodeEndpointName {
    param(
        [Parameter()]
        [string]
        $Protocol,

        [Parameter()]
        [string]
        $Address,

        [Parameter()]
        [System.Net.EndPoint]
        $LocalAddress,

        [switch]
        $Force,

        [switch]
        $ThrowError,

        [switch]
        $Enabled
    )

    if (!$Enabled -and !$Force) {
        return $null
    }

    if ([string]::IsNullOrWhiteSpace($Protocol) -or
        [string]::IsNullOrWhiteSpace($Address) -or
        [string]::IsNullOrWhiteSpace($LocalAddress)) {
        return $null
    }

    <#
       using Host header
    #>

    # add a default port to the address if missing
    if (!$Address.Contains(':')) {
        $port = Get-PodeDefaultPort -Protocol $Protocol -Real -TlsMode Implicit
        $Address = "$($Address):$($port)"
    }

    # change localhost/computer name to ip address
    if (($Address -ilike 'localhost:*') -or ($Address -ilike "$($PodeContext.Server.ComputerName):*")) {
        $Address = ($Address -ireplace "(localhost|$([regex]::Escape($PodeContext.Server.ComputerName)))\:", "(127\.0\.0\.1|0\.0\.0\.0|\:\:ffff\:127\.0\.0\.1|\:\:ffff\:0\:0|\[\:\:\]|\[\:\:1\]|\:\:1|\:\:|localhost|$([regex]::Escape($PodeContext.Server.ComputerName))):")
    }
    else {
        $Address = [regex]::Escape($Address)
    }

    # create the endpoint key for address
    $key = "$($Protocol)\|$($Address)"

    # try and find endpoint for address
    $key = @(foreach ($k in $PodeContext.Server.EndpointsMap.Keys) {
            if ($k -imatch $key) {
                $k
                break
            }
        })[0]

    if (![string]::IsNullOrWhiteSpace($key) -and $PodeContext.Server.EndpointsMap.ContainsKey($key)) {
        return $PodeContext.Server.EndpointsMap[$key]
    }

    <#
       using local endpoint from socket
    #>

    # setup the local address as a string
    $_localAddress = "$($LocalAddress.Address.IPAddressToString):$($LocalAddress.Port)"
    $_localAddress = [regex]::Escape($_localAddress)

    # create the endpoint key for local address
    $key = "$($Protocol)\|$($_localAddress)"

    # try and find endpoint for local address
    $key = @(foreach ($k in $PodeContext.Server.EndpointsMap.Keys) {
            if ($k -imatch $key) {
                $k
                break
            }
        })[0]

    if (![string]::IsNullOrWhiteSpace($key) -and $PodeContext.Server.EndpointsMap.ContainsKey($key)) {
        return $PodeContext.Server.EndpointsMap[$key]
    }

    <#
       check for * address
    #>

    # set * address as string
    $_anyAddress = "(0\.0\.0\.0|\[\:\:\]|\:\:|\:\:ffff\:0\:0):$($LocalAddress.Port)"
    $key = "$($Protocol)\|$($_anyAddress)"

    # try and find endpoint for any address
    $key = @(foreach ($k in $PodeContext.Server.EndpointsMap.Keys) {
            if ($k -imatch $key) {
                $k
                break
            }
        })[0]

    if (![string]::IsNullOrWhiteSpace($key) -and $PodeContext.Server.EndpointsMap.ContainsKey($key)) {
        return $PodeContext.Server.EndpointsMap[$key]
    }

    # error?
    if ($ThrowError) {
        throw ($PodeLocale.endpointNotExistExceptionMessage -f $Protocol, $Address, $_localAddress) #"Endpoint with protocol '$($Protocol)' and address '$($Address)' or local address '$($_localAddress)' does not exist"
    }

    return $null
}

function Get-PodeEndpointByName {
    param(
        [Parameter()]
        [string]
        $Name,

        [switch]
        $ThrowError
    )

    # if an EndpointName was supplied, find it and use it
    if ([string]::IsNullOrWhiteSpace($Name)) {
        return $null
    }

    # ensure it exists
    if ($PodeContext.Server.Endpoints.ContainsKey($Name)) {
        return $PodeContext.Server.Endpoints[$Name]
    }

    # error?
    if ($ThrowError) {
        throw ($PodeLocale.endpointNameNotExistExceptionMessage -f $Name) #"Endpoint with name '$($Name)' does not exist"
    }

    return $null
}
src\Private\Endware.ps1
function Invoke-PodeEndware {
    param(
        [Parameter()]
        $Endware
    )

    # if there's no endware, do nothing
    if (($null -eq $Endware) -or ($Endware.Length -eq 0)) {
        return
    }

    # loop through each of the endware, invoking the next if it returns true
    foreach ($eware in @($Endware)) {
        if (($null -eq $eware) -or ($null -eq $eware.Logic)) {
            continue
        }

        try {
            $null = Invoke-PodeScriptBlock -ScriptBlock $eware.Logic -Arguments $eware.Arguments -UsingVariables $eware.UsingVariables -Scoped -Splat
        }
        catch {
            $_ | Write-PodeErrorLog
        }
    }
}
src\Private\Events.ps1
function Invoke-PodeEvent {
    param(
        [Parameter(Mandatory = $true)]
        [ValidateSet('Start', 'Terminate', 'Restart', 'Browser', 'Crash', 'Stop', 'Running')]
        [string]
        $Type
    )

    # do nothing if no events
    if (($null -eq $PodeContext.Server.Events) -or ($PodeContext.Server.Events[$Type].Count -eq 0)) {
        return
    }

    # invoke each event's scriptblock
    foreach ($evt in $PodeContext.Server.Events[$Type].Values) {
        if (($null -eq $evt) -or ($null -eq $evt.ScriptBlock)) {
            continue
        }

        try {
            $null = Invoke-PodeScriptBlock -ScriptBlock $evt.ScriptBlock -Arguments $evt.Arguments -UsingVariables $evt.UsingVariables -Scoped -Splat
        }
        catch {
            $_ | Write-PodeErrorLog
        }
    }
}
src\Private\FileMonitor.ps1
function Start-PodeFileMonitor {
    # don't configure if not supplied, or we're running as serverless
    if (!$PodeContext.Server.FileMonitor.Enabled -or $PodeContext.Server.IsServerless) {
        return
    }

    # what folder and filter are we moitoring?
    $folder = $PodeContext.Server.Root
    $filter = '*.*'

    # setup the file monitor
    $watcher = [System.IO.FileSystemWatcher]::new($folder, $filter)
    $watcher.IncludeSubdirectories = $true
    $watcher.NotifyFilter = [System.IO.NotifyFilters]'FileName,LastWrite,CreationTime'
    $watcher.EnableRaisingEvents = $true

    # setup the monitor timer - only restart server after changes + 2s of no changes
    $timer = [System.Timers.Timer]::new()
    $timer.AutoReset = $false
    $timer.Interval = 2000

    # setup the message data for the events
    $msgData = @{
        Timer    = $timer
        Settings = $PodeContext.Server.FileMonitor
    }

    # setup the events script logic
    $action = {
        # if there are exclusions, and one matches, return
        if (($null -ne $Event.MessageData.Settings.Exclude) -and ($Event.SourceEventArgs.Name -imatch $Event.MessageData.Settings.Exclude)) {
            return
        }

        # if there are inclusions, and none match, return
        if (($null -ne $Event.MessageData.Settings.Include) -and ($Event.SourceEventArgs.Name -inotmatch $Event.MessageData.Settings.Include)) {
            return
        }

        # if enabled, add the file to the list of files that trigggered the restart
        if ($Event.MessageData.Settings.ShowFiles) {
            $name = "[$($Event.SourceEventArgs.ChangeType)] $($Event.SourceEventArgs.Name)"

            if ($Event.MessageData.Settings.Files -inotcontains $name) {
                $Event.MessageData.Settings.Files += $name
            }
        }

        # restart the timer
        $Event.MessageData.Timer.Stop()
        $Event.MessageData.Timer.Start()
    }

    # listen out of file created, changed, deleted events
    Register-ObjectEvent -InputObject $watcher -EventName 'Created' `
        -SourceIdentifier (Get-PodeFileMonitorName Create) -Action $action -MessageData $msgData -SupportEvent

    Register-ObjectEvent -InputObject $watcher -EventName 'Changed' `
        -SourceIdentifier (Get-PodeFileMonitorName Update) -Action $action -MessageData $msgData -SupportEvent

    Register-ObjectEvent -InputObject $watcher -EventName 'Deleted' `
        -SourceIdentifier (Get-PodeFileMonitorName Delete) -Action $action -MessageData $msgData -SupportEvent

    # listen out for timer ticks to reset server
    Register-ObjectEvent -InputObject $timer -EventName 'Elapsed' -SourceIdentifier (Get-PodeFileMonitorTimerName) -Action {
        # if enabled, show the files that triggered the restart
        if ($Event.MessageData.FileSettings.ShowFiles) {
            if (!$Event.MessageData.Quiet) {
                # The following files have changed
                Write-PodeHost $PodeLocale.filesHaveChangedMessage  -ForegroundColor Magenta

                foreach ($file in $Event.MessageData.FileSettings.Files) {
                    Write-PodeHost "> $($file)" -ForegroundColor Magenta
                }
            }

            $Event.MessageData.FileSettings.Files = @()
        }

        # trigger the restart
        $Event.MessageData.Tokens.Restart.Cancel()
        $Event.Sender.Stop()
    } -MessageData @{
        Tokens       = $PodeContext.Tokens
        FileSettings = $PodeContext.Server.FileMonitor
        Quiet        = $PodeContext.Server.Quiet
    } -SupportEvent
}

function Stop-PodeFileMonitor {
    if ($PodeContext.Server.IsServerless) {
        return
    }

    if ($PodeContext.Server.FileMonitor.Enabled) {
        Unregister-Event -SourceIdentifier (Get-PodeFileMonitorName Create) -Force
        Unregister-Event -SourceIdentifier (Get-PodeFileMonitorName Delete) -Force
        Unregister-Event -SourceIdentifier (Get-PodeFileMonitorName Update) -Force
        Unregister-Event -SourceIdentifier (Get-PodeFileMonitorTimerName) -Force
    }
}

function Get-PodeFileMonitorName {
    param(
        [Parameter(Mandatory = $true)]
        [ValidateSet('Create', 'Delete', 'Update')]
        [string]
        $Type
    )

    return "PodeFileMonitor$($Type)"
}

function Get-PodeFileMonitorTimerName {
    return 'PodeFileMonitorTimer'
}
src\Private\FileWatchers.ps1
using namespace Pode

function Test-PodeFileWatchersExist {
    [CmdletBinding()]
    [OutputType([bool])]
    param()
    return (($null -ne $PodeContext.Fim) -and (($PodeContext.Fim.Enabled) -or ($PodeContext.Fim.Items.Count -gt 0)))
}

function New-PodeFileWatcher {
    [CmdletBinding()]
    [OutputType([PodeWatcher])]
    param()
    $watcher = [PodeWatcher]::new($PodeContext.Tokens.Cancellation.Token)
    $watcher.ErrorLoggingEnabled = (Test-PodeErrorLoggingEnabled)
    $watcher.ErrorLoggingLevels = @(Get-PodeErrorLoggingLevel)
    return $watcher
}

function Start-PodeFileWatcherRunspace {
    if (!(Test-PodeFileWatchersExist)) {
        return
    }

    try {
        # create the watcher
        $watcher = New-PodeFileWatcher

        # register file watchers and events
        foreach ($item in $PodeContext.Fim.Items.Values) {
            foreach ($path in $item.Paths) {
                Write-Verbose "Creating FileWatcher for '$($path)'"
                $fileWatcher = [PodeFileWatcher]::new($item.Name, $path, $item.IncludeSubdirectories, $item.InternalBufferSize, $item.NotifyFilters)

                foreach ($evt in $item.Events) {
                    Write-Verbose "-> Registering event: $($evt)"
                    $fileWatcher.RegisterEvent($evt)
                }

                $watcher.AddFileWatcher($fileWatcher)
            }
        }

        $watcher.Start()
        $PodeContext.Watchers += $watcher
    }
    catch {
        $_ | Write-PodeErrorLog
        $_.Exception | Write-PodeErrorLog -CheckInnerException
        Close-PodeDisposable -Disposable $watcher
        throw $_.Exception
    }

    $watchScript = {
        param(
            [Parameter(Mandatory = $true)]
            $Watcher,

            [Parameter(Mandatory = $true)]
            [int]
            $ThreadId
        )

        try {
            while ($Watcher.IsConnected -and !$PodeContext.Tokens.Cancellation.IsCancellationRequested) {
                $evt = (Wait-PodeTask -Task $Watcher.GetFileEventAsync($PodeContext.Tokens.Cancellation.Token))

                try {
                    try {
                        # get file watcher
                        $fileWatcher = $PodeContext.Fim.Items[$evt.FileWatcher.Name]
                        if ($null -eq $fileWatcher) {
                            continue
                        }

                        # if there are exclusions, and one matches, return
                        $exc = (Convert-PodePathPatternsToRegex -Paths $fileWatcher.Exclude)
                        if (($null -ne $exc) -and ($evt.Name -imatch $exc)) {
                            continue
                        }

                        # if there are inclusions, and none match, return
                        $inc = (Convert-PodePathPatternsToRegex -Paths $fileWatcher.Include)
                        if (($null -ne $inc) -and ($evt.Name -inotmatch $inc)) {
                            continue
                        }

                        # set file event object
                        $FileEvent = @{
                            Type       = $evt.ChangeType
                            FullPath   = $evt.FullPath
                            Name       = $evt.Name
                            Old        = @{
                                FullPath = $evt.OldFullPath
                                Name     = $evt.OldName
                            }
                            Parameters = @{}
                            Lockable   = $PodeContext.Threading.Lockables.Global
                            Timestamp  = [datetime]::UtcNow
                            Metadata   = @{}
                        }

                        # do we have any parameters?
                        if ($fileWatcher.Placeholders.Exist -and ($FileEvent.FullPath -imatch $fileWatcher.Placeholders.Path)) {
                            $FileEvent.Parameters = $Matches
                        }

                        # invoke main script
                        $null = Invoke-PodeScriptBlock -ScriptBlock $fileWatcher.Script -Arguments $fileWatcher.Arguments -UsingVariables $fileWatcher.UsingVariables -Scoped -Splat
                    }
                    catch [System.OperationCanceledException] {
                        $_ | Write-PodeErrorLog -Level Debug
                    }
                    catch {
                        $_ | Write-PodeErrorLog
                        $_.Exception | Write-PodeErrorLog -CheckInnerException
                    }
                }
                finally {
                    $FileEvent = $null
                    Close-PodeDisposable -Disposable $evt
                }
            }
        }
        catch [System.OperationCanceledException] {
            $_ | Write-PodeErrorLog -Level Debug
        }
        catch {
            $_ | Write-PodeErrorLog
            $_.Exception | Write-PodeErrorLog -CheckInnerException
            throw $_.Exception
        }
    }

    1..$PodeContext.Threads.Files | ForEach-Object {
        Add-PodeRunspace -Type Files -Name 'Watcher' -Id $_ -ScriptBlock $watchScript -Parameters @{ 'Watcher' = $watcher ; 'ThreadId' = $_ }
    }

    # script to keep file watcher server alive until cancelled
    $waitScript = {
        param(
            [Parameter(Mandatory = $true)]
            $Watcher
        )

        try {
            while ($Watcher.IsConnected -and !$PodeContext.Tokens.Cancellation.IsCancellationRequested) {
                Start-Sleep -Seconds 1
            }
        }
        catch [System.OperationCanceledException] {
            $_ | Write-PodeErrorLog -Level Debug
        }
        catch {
            $_ | Write-PodeErrorLog
            $_.Exception | Write-PodeErrorLog -CheckInnerException
            throw $_.Exception
        }
        finally {
            Close-PodeDisposable -Disposable $Watcher
        }
    }

    Add-PodeRunspace -Type Files -Name 'KeepAlive' -ScriptBlock $waitScript -Parameters @{ 'Watcher' = $watcher } -NoProfile
}
src\Private\Gui.ps1
function Test-PodeGuiEnabled {
    return ($PodeContext.Server.Gui.Enabled -and
        !$PodeContext.Server.IsServerless -and
        !$PodeContext.Server.IsIIS -and
        !$PodeContext.Server.IsHeroku)
}

function Start-PodeGuiRunspace {
    # do nothing if gui not enabled, or running as serverless
    if (!(Test-PodeGuiEnabled)) {
        return
    }

    $script = {
        try {
            # if there are multiple endpoints, flag warning we're only using the first - unless explicitly set
            if ($null -eq $PodeContext.Server.Gui.Endpoint) {
                if ($PodeContext.Server.Endpoints.Values.Count -gt 1) {
                    # Multiple endpoints defined, only the first will be used for the GUI
                    Write-PodeHost $PodeLocale.multipleEndpointsForGuiMessage -ForegroundColor Yellow
                }
            }

            # get the endpoint on which we're currently listening, or use explicitly passed one
            $uri = (Get-PodeEndpointUrl -Endpoint $PodeContext.Server.Gui.Endpoint)

            # poll the server for a response
            $count = 0

            while (!$PodeContext.Tokens.Cancellation.IsCancellationRequested) {
                try {
                    $null = Invoke-WebRequest -Method Get -Uri $uri -UseBasicParsing -ErrorAction Stop
                    if (!$?) {
                        throw
                    }

                    break
                }
                catch {
                    $count++
                    if ($count -le 50) {
                        Start-Sleep -Milliseconds 200
                    }
                    else {
                        throw ($PodeLocale.failedToConnectToUrlExceptionMessage -f $uri) #"Failed to connect to URL: $($uri)"
                    }
                }
            }

            # import the WPF assembly
            $null = [System.Reflection.Assembly]::LoadWithPartialName('PresentationFramework')
            $null = [System.Reflection.Assembly]::LoadWithPartialName('PresentationCore')

            # Check for CefSharp
            $loadCef = [bool]([AppDomain]::CurrentDomain.GetAssemblies() | Where-Object { $_.FullName.StartsWith('CefSharp.Wpf,') })

            # setup the WPF XAML for the server
            # Check for CefSharp and used Chromium based WPF if Modules exists
            if ($loadCef) {
                $gui_browser = "
                <Window
                    xmlns=`"http://schemas.microsoft.com/winfx/2006/xaml/presentation`"
                    xmlns:wpf=`"clr-namespace:CefSharp.Wpf;assembly=CefSharp.Wpf`"
                    xmlns:x=`"http://schemas.microsoft.com/winfx/2006/xaml`"
                    Title=`"$($PodeContext.Server.Gui.Title)`"
                    Height=`"$($PodeContext.Server.Gui.Height)`"
                    Width=`"$($PodeContext.Server.Gui.Width)`"
                    ResizeMode=`"$($PodeContext.Server.Gui.ResizeMode)`"
                    WindowStartupLocation=`"CenterScreen`"
                    ShowInTaskbar = `"$($PodeContext.Server.Gui.ShowInTaskbar)`"
                    WindowStyle = `"$($PodeContext.Server.Gui.WindowStyle)`">
                        <Window.TaskbarItemInfo>
                            <TaskbarItemInfo />
                        </Window.TaskbarItemInfo>
                        <Border Grid.Row=`"1`" BorderBrush=`"Gray`" BorderThickness=`"0,1`">
                            <wpf:ChromiumWebBrowser x:Name=`"Browser`" Address=`"$uri`"/>
                        </Border>
                </Window>"
            }
            else {
                # Fall back to the IE based WPF Browser
                $gui_browser = "
                    <Window
                        xmlns=`"http://schemas.microsoft.com/winfx/2006/xaml/presentation`"
                        xmlns:x=`"http://schemas.microsoft.com/winfx/2006/xaml`"
                        Title=`"$($PodeContext.Server.Gui.Title)`"
                        Height=`"$($PodeContext.Server.Gui.Height)`"
                        Width=`"$($PodeContext.Server.Gui.Width)`"
                        ResizeMode=`"$($PodeContext.Server.Gui.ResizeMode)`"
                        WindowStartupLocation=`"CenterScreen`"
                        ShowInTaskbar = `"$($PodeContext.Server.Gui.ShowInTaskbar)`"
                        WindowStyle = `"$($PodeContext.Server.Gui.WindowStyle)`">
                            <Window.TaskbarItemInfo>
                                <TaskbarItemInfo />
                            </Window.TaskbarItemInfo>
                            <WebBrowser Name=`"WebBrowser`"></WebBrowser>
                    </Window>"
            }

            # read in the XAML
            $reader = [System.Xml.XmlNodeReader]::new([xml]$gui_browser)
            $form = [Windows.Markup.XamlReader]::Load($reader)

            # set other options
            $form.TaskbarItemInfo.Description = $form.Title

            # add the icon to the form
            if (!(Test-PodeIsEmpty $PodeContext.Server.Gui.Icon)) {
                $icon = [Uri]::new($PodeContext.Server.Gui.Icon)
                $form.Icon = [Windows.Media.Imaging.BitmapFrame]::Create($icon)
            }

            # set the state of the window onload
            if (!(Test-PodeIsEmpty $PodeContext.Server.Gui.WindowState)) {
                $form.WindowState = $PodeContext.Server.Gui.WindowState
            }

            # get the browser object from XAML and navigate to base page if Cef is not loaded
            if (!$loadCef) {
                $form.FindName('WebBrowser').Navigate($uri)
            }

            # display the form
            # Opening the GUI
            Write-PodeHost $PodeLocale.openingGuiMessage -ForegroundColor Yellow
            $null = $form.ShowDialog()
            Start-Sleep -Seconds 1
        }
        catch {
            $_ | Write-PodeErrorLog
            throw $_.Exception
        }
        finally {
            # invoke the cancellation token to close the server
            $PodeContext.Tokens.Cancellation.Cancel()
        }
    }

    Add-PodeRunspace -Type Gui -Name 'Watcher' -ScriptBlock $script
}
src\Private\Helpers.ps1
using namespace Pode

<#
.SYNOPSIS
    Dynamically executes content as a Pode file, optionally passing data to it.

.DESCRIPTION
    This function takes a string of content, which is expected to be PowerShell code, and optionally a hashtable of data. It constructs a script block that optionally includes a parameter declaration,
    and then executes this script block using the provided data. This is useful for dynamically generating content based on a template or script contained in a file or a string.

.PARAMETER Content
    The PowerShell code as a string. This content is dynamically executed as a script block. It can include placeholders or logic that utilizes the passed data.

.PARAMETER Data
    Optional hashtable of data that can be referenced within the content/script. This data is passed to the script block as parameters.

.EXAMPLE
    $scriptContent = '"Hello, world! Today is $(Get-Date)"'
    ConvertFrom-PodeFile -Content $scriptContent

    This example will execute the content of the script and output "Hello, world! Today is [current date]".

.EXAMPLE
    $template = '"Hello, $(Name)! Your balance is $$(Amount)"'
    $data = @{ Name = 'John Doe'; Amount = '100.50' }
    ConvertFrom-PodeFile -Content $template -Data $data

    This example demonstrates using the function with a data parameter to replace placeholders within the content.
#>
function ConvertFrom-PodeFile {
    param(
        [Parameter(Mandatory = $true)]
        [ValidateNotNull()]
        $Content,

        [Parameter()]
        $Data = @{}
    )

    # if we have data, then setup the data param
    if ($null -ne $Data -and $Data.Count -gt 0) {
        $Content = "param(`$data)`nreturn `"$($Content -replace '"', '``"')`""
    }
    else {
        $Content = "return `"$($Content -replace '"', '``"')`""
    }

    # invoke the content as a script to generate the dynamic content
    return (Invoke-PodeScriptBlock -ScriptBlock ([scriptblock]::Create($Content)) -Arguments $Data -Return)
}

function Get-PodeViewEngineType {
    param(
        [Parameter(Mandatory = $true)]
        [string]
        $Path
    )

    # work out the engine to use when parsing the file
    $type = $PodeContext.Server.ViewEngine.Type

    $ext = Get-PodeFileExtension -Path $Path -TrimPeriod
    if (![string]::IsNullOrWhiteSpace($ext) -and ($ext -ine $PodeContext.Server.ViewEngine.Extension)) {
        $type = $ext
    }

    return $type
}

function Get-PodeFileContentUsingViewEngine {
    param(
        [Parameter(Mandatory = $true)]
        [string]
        $Path,

        [Parameter()]
        [hashtable]
        $Data
    )

    # work out the engine to use when parsing the file
    $engine = Get-PodeViewEngineType -Path $Path

    # setup the content
    $content = [string]::Empty

    # run the relevant engine logic
    switch ($engine.ToLowerInvariant()) {
        'html' {
            $content = Get-Content -Path $Path -Raw -Encoding utf8
        }

        'md' {
            $content = Get-Content -Path $Path -Raw -Encoding utf8
        }

        'pode' {
            $content = Get-Content -Path $Path -Raw -Encoding utf8
            $content = ConvertFrom-PodeFile -Content $content -Data $Data
        }

        default {
            if ($null -ne $PodeContext.Server.ViewEngine.ScriptBlock) {
                $_args = @($Path)
                if (($null -ne $Data) -and ($Data.Count -gt 0)) {
                    $_args = @($Path, $Data)
                }

                $content = (Invoke-PodeScriptBlock -ScriptBlock $PodeContext.Server.ViewEngine.ScriptBlock -Arguments $_args -UsingVariables $PodeContext.Server.ViewEngine.UsingVariables -Return -Splat)
            }
        }
    }

    return $content
}

function Get-PodeFileContent {
    param(
        [Parameter(Mandatory = $true)]
        [string]
        $Path
    )

    return (Get-Content -Path $Path -Raw -Encoding utf8)
}

function Get-PodeType {
    param(
        [Parameter()]
        $Value
    )

    if ($null -eq $Value) {
        return $null
    }

    $type = $Value.GetType()
    return @{
        Name     = $type.Name.ToLowerInvariant()
        BaseName = $type.BaseType.Name.ToLowerInvariant()
    }
}

function Get-PodePSVersionTable {
    return $PSVersionTable
}

function Test-PodeIsAdminUser {
    # check the current platform, if it's unix then return true
    if (Test-PodeIsUnix) {
        return $true
    }

    try {
        $principal = [System.Security.Principal.WindowsPrincipal]::new([System.Security.Principal.WindowsIdentity]::GetCurrent())
        if ($null -eq $principal) {
            return $false
        }

        return $principal.IsInRole([System.Security.Principal.WindowsBuiltInRole]::Administrator)
    }
    catch [exception] {
        Write-PodeHost 'Error checking user administrator priviledges' -ForegroundColor Red
        Write-PodeHost $_.Exception.Message -ForegroundColor Red
        return $false
    }
}

function Get-PodeHostIPRegex {
    param(
        [Parameter(Mandatory = $true)]
        [ValidateSet('Both', 'Hostname', 'IP')]
        [string]
        $Type
    )

    $ip_rgx = '\[?([a-f0-9]*\:){1,}[a-f0-9]*((\d+\.){3}\d+)?\]?|((\d+\.){3}\d+)|\*|all'
    $host_rgx = '([a-z]|\*\.)(([a-z0-9]|[a-z0-9][a-z0-9\-]*[a-z0-9])\.)*([a-z0-9]|[a-z0-9][a-z0-9\-]*[a-z0-9])+'

    switch ($Type.ToLowerInvariant()) {
        'both' {
            return "(?<host>($($ip_rgx)|$($host_rgx)))"
        }

        'hostname' {
            return "(?<host>($($host_rgx)))"
        }

        'ip' {
            return "(?<host>($($ip_rgx)))"
        }
    }
}

function Get-PodePortRegex {
    return '(?<port>\d+)'
}

function Get-PodeEndpointInfo {
    param(
        [Parameter()]
        [string]
        $Address,

        [switch]
        $AnyPortOnZero
    )

    if ([string]::IsNullOrWhiteSpace($Address)) {
        return $null
    }

    $hostRgx = Get-PodeHostIPRegex -Type Both
    $portRgx = Get-PodePortRegex
    $cmbdRgx = "$($hostRgx)\:$($portRgx)"

    # validate that we have a valid ip/host:port address
    if (!(($Address -imatch "^$($cmbdRgx)$") -or ($Address -imatch "^$($hostRgx)[\:]{0,1}") -or ($Address -imatch "[\:]{0,1}$($portRgx)$"))) {
        throw ($PodeLocale.failedToParseAddressExceptionMessage -f $Address)#"Failed to parse '$($Address)' as a valid IP/Host:Port address"
    }

    # grab the ip address/hostname
    $_host = $Matches['host']
    if ([string]::IsNullOrWhiteSpace($_host)) {
        $_host = '*'
    }

    # ensure we have a valid ip address/hostname
    if (!(Test-PodeIPAddress -IP $_host)) {
        throw ($PodeLocale.invalidIpAddressExceptionMessage -f $_host) #"The IP address supplied is invalid: $($_host)"
    }

    # grab the port
    $_port = $Matches['port']
    if ([string]::IsNullOrWhiteSpace($_port)) {
        $_port = 0
    }

    # ensure the port is valid
    if ($_port -lt 0) {
        throw ($PodeLocale.invalidPortExceptionMessage -f $_port)#"The port cannot be negative: $($_port)"
    }

    # return the info
    return @{
        Host = $_host
        Port = (Resolve-PodeValue -Check ($AnyPortOnZero -and ($_port -eq 0)) -TrueValue '*' -FalseValue $_port)
    }
}

function Test-PodeIPAddress {
    param(
        [Parameter()]
        [string]
        $IP,

        [switch]
        $IPOnly
    )

    if ([string]::IsNullOrWhiteSpace($IP) -or ($IP -iin @('*', 'all'))) {
        return $true
    }

    if ($IP -imatch "^$(Get-PodeHostIPRegex -Type Hostname)$") {
        return (!$IPOnly)
    }

    try {
        $null = [System.Net.IPAddress]::Parse($IP)
        return $true
    }
    catch [exception] {
        return $false
    }
}

function Test-PodeHostname {
    param(
        [Parameter()]
        [string]
        $Hostname
    )

    return ($Hostname -imatch "^$(Get-PodeHostIPRegex -Type Hostname)$")
}

function ConvertTo-PodeIPAddress {
    param(
        [Parameter(Mandatory = $true)]
        [ValidateNotNull()]
        $Address
    )

    return [System.Net.IPAddress]::Parse(([System.Net.IPEndPoint]$Address).Address.ToString())
}

function Get-PodeIPAddressesForHostname {
    param(
        [Parameter(Mandatory = $true)]
        [string]
        $Hostname,

        [Parameter(Mandatory = $true)]
        [ValidateSet('All', 'IPv4', 'IPv6')]
        [string]
        $Type
    )

    if (!(Test-PodeHostname -Hostname $Hostname)) {
        return $Hostname
    }

    # get the ip addresses for the hostname
    try {
        $ips = @([System.Net.Dns]::GetHostAddresses($Hostname))
    }
    catch {
        return '127.0.0.1'
    }

    # return ips based on type
    switch ($Type.ToLowerInvariant()) {
        'ipv4' {
            $ips = @(foreach ($ip in $ips) {
                    if ($ip.AddressFamily -ieq 'InterNetwork') {
                        $ip
                    }
                })
        }

        'ipv6' {
            $ips = @(foreach ($ip in $ips) {
                    if ($ip.AddressFamily -ieq 'InterNetworkV6') {
                        $ip
                    }
                })
        }
    }

    return (@($ips)).IPAddressToString
}

function Test-PodeIPAddressLocal {
    param(
        [Parameter(Mandatory = $true)]
        [string]
        $IP
    )

    return (@('127.0.0.1', '::1', '[::1]', '::ffff:127.0.0.1', 'localhost') -icontains $IP)
}

function Test-PodeIPAddressAny {
    param(
        [Parameter(Mandatory = $true)]
        [string]
        $IP
    )

    return (@('0.0.0.0', '*', 'all', '::', '[::]') -icontains $IP)
}

function Test-PodeIPAddressLocalOrAny {
    param(
        [Parameter(Mandatory = $true)]
        [string]
        $IP
    )

    return ((Test-PodeIPAddressLocal -IP $IP) -or (Test-PodeIPAddressAny -IP $IP))
}

function Resolve-PodeIPDualMode {
    param(
        [Parameter()]
        [ipaddress]
        $IP
    )

    # do nothing if IPv6Any
    if ($IP -eq [ipaddress]::IPv6Any) {
        return $IP
    }

    # check loopbacks
    if (($IP -eq [ipaddress]::Loopback) -and [System.Net.Sockets.Socket]::OSSupportsIPv6) {
        return @($IP, [ipaddress]::IPv6Loopback)
    }

    if ($IP -eq [ipaddress]::IPv6Loopback) {
        return @($IP, [ipaddress]::Loopback)
    }

    # if iIPv4, convert and return both
    if (($IP.AddressFamily -eq [System.Net.Sockets.AddressFamily]::InterNetwork) -and [System.Net.Sockets.Socket]::OSSupportsIPv6) {
        return @($IP, $IP.MapToIPv6())
    }

    # if IPv6, only convert if valid IPv4
    if (($IP.AddressFamily -eq [System.Net.Sockets.AddressFamily]::InterNetworkV6) -and $IP.IsIPv4MappedToIPv6) {
        return @($IP, $IP.MapToIPv4())
    }

    # just return the IP
    return $IP
}

function Get-PodeIPAddress {
    param(
        [Parameter()]
        [string]
        $IP,

        [switch]
        $DualMode
    )

    # any address for IPv4 (or IPv6 for DualMode)
    if ([string]::IsNullOrWhiteSpace($IP) -or ($IP -iin @('*', 'all'))) {
        if ($DualMode) {
            return [System.Net.IPAddress]::IPv6Any
        }

        return [System.Net.IPAddress]::Any
    }

    # any address for IPv6 explicitly
    if ($IP -iin @('::', '[::]')) {
        return [System.Net.IPAddress]::IPv6Any
    }

    # localhost
    if ($IP -ieq 'localhost') {
        return [System.Net.IPAddress]::Loopback
    }

    # localhost IPv6 explicitly
    if ($IP -iin @('[::1]', '::1')) {
        return [System.Net.IPAddress]::IPv6Loopback
    }

    # hostname
    if ($IP -imatch "^$(Get-PodeHostIPRegex -Type Hostname)$") {
        return $IP
    }

    # raw ip
    return [System.Net.IPAddress]::Parse($IP)
}

function Test-PodeIPAddressInRange {
    param(
        [Parameter(Mandatory = $true)]
        $IP,

        [Parameter(Mandatory = $true)]
        $LowerIP,

        [Parameter(Mandatory = $true)]
        $UpperIP
    )

    if ($IP.Family -ine $LowerIP.Family) {
        return $false
    }

    $valid = $true

    foreach ($i in 0..3) {
        if (($IP.Bytes[$i] -lt $LowerIP.Bytes[$i]) -or ($IP.Bytes[$i] -gt $UpperIP.Bytes[$i])) {
            $valid = $false
            break
        }
    }

    return $valid
}

function Test-PodeIPAddressIsSubnetMask {
    param(
        [Parameter(Mandatory = $true)]
        [ValidateNotNullOrEmpty()]
        [string]
        $IP
    )

    return (($IP -split '/').Length -gt 1)
}

function Get-PodeSubnetRange {
    param(
        [Parameter(Mandatory = $true)]
        [ValidateNotNullOrEmpty()]
        [string]
        $SubnetMask
    )

    # split for ip and number of 1 bits
    $split = $SubnetMask -split '/'
    if ($split.Length -le 1) {
        return $null
    }

    $ip_parts = $split[0] -isplit '\.'
    $bits = [int]$split[1]

    # generate the netmask
    $network = @('', '', '', '')
    $count = 0

    foreach ($i in 0..3) {
        foreach ($b in 1..8) {
            $count++

            if ($count -le $bits) {
                $network[$i] += '1'
            }
            else {
                $network[$i] += '0'
            }
        }
    }

    # covert netmask to bytes
    foreach ($i in 0..3) {
        $network[$i] = [Convert]::ToByte($network[$i], 2)
    }

    # calculate the bottom range
    $bottom = @(foreach ($i in 0..3) {
            [byte]([byte]$network[$i] -band [byte]$ip_parts[$i])
        })

    # calculate the range
    $range = @(foreach ($i in 0..3) {
            256 + (-bnot [byte]$network[$i])
        })

    # calculate the top range
    $top = @(foreach ($i in 0..3) {
            [byte]([byte]$ip_parts[$i] + [byte]$range[$i])
        })

    return @{
        'Lower'   = ($bottom -join '.')
        'Upper'   = ($top -join '.')
        'Range'   = ($range -join '.')
        'Netmask' = ($network -join '.')
        'IP'      = ($ip_parts -join '.')
    }
}


function Get-PodeConsoleKey {
    if ([Console]::IsInputRedirected -or ![Console]::KeyAvailable) {
        return $null
    }

    return [Console]::ReadKey($true)
}

function Test-PodeTerminationPressed {
    param(
        [Parameter()]
        $Key = $null
    )

    if ($PodeContext.Server.DisableTermination) {
        return $false
    }

    return (Test-PodeKeyPressed -Key $Key -Character 'c')
}

function Test-PodeRestartPressed {
    param(
        [Parameter()]
        $Key = $null
    )

    return (Test-PodeKeyPressed -Key $Key -Character 'r')
}

function Test-PodeOpenBrowserPressed {
    param(
        [Parameter()]
        $Key = $null
    )

    return (Test-PodeKeyPressed -Key $Key -Character 'b')
}

function Test-PodeKeyPressed {
    param(
        [Parameter()]
        $Key = $null,

        [Parameter(Mandatory = $true)]
        [string]
        $Character
    )

    if ($null -eq $Key) {
        $Key = Get-PodeConsoleKey
    }

    return (($null -ne $Key) -and ($Key.Key -ieq $Character) -and
        (($Key.Modifiers -band [ConsoleModifiers]::Control) -or ((Test-PodeIsUnix) -and ($Key.Modifiers -band [ConsoleModifiers]::Shift))))
}

function Close-PodeServerInternal {
    param(
        [switch]
        $ShowDoneMessage
    )

    # ensure the token is cancelled
    if ($null -ne $PodeContext.Tokens.Cancellation) {
        Write-Verbose 'Cancelling main cancellation token'
        $PodeContext.Tokens.Cancellation.Cancel()
    }

    # stop all current runspaces
    Write-Verbose 'Closing runspaces'
    Close-PodeRunspace -ClosePool

    # stop the file monitor if it's running
    Write-Verbose 'Stopping file monitor'
    Stop-PodeFileMonitor

    try {
        # remove all the cancellation tokens
        Write-Verbose 'Disposing cancellation tokens'
        Close-PodeDisposable -Disposable $PodeContext.Tokens.Cancellation
        Close-PodeDisposable -Disposable $PodeContext.Tokens.Restart

        # dispose mutex/semaphores
        Write-Verbose 'Diposing mutex and semaphores'
        Clear-PodeMutexes
        Clear-PodeSemaphores
    }
    catch {
        $_ | Out-Default
    }

    # remove all of the pode temp drives
    Write-Verbose 'Removing internal PSDrives'
    Remove-PodePSDrive

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

function New-PodePSDrive {
    param(
        [Parameter(Mandatory = $true)]
        [string]
        $Path,

        [Parameter()]
        [string]
        $Name
    )

    # if the path is a share, do nothing
    if ($Path.StartsWith('\\')) {
        return $Path
    }

    # if no name is passed, used a randomly generated one
    if ([string]::IsNullOrWhiteSpace($Name)) {
        $Name = "PodeDir$(New-PodeGuid)"
    }

    # if the path supplied doesn't exist, error
    if (!(Test-Path $Path)) {
        throw ($PodeLocale.pathNotExistExceptionMessage -f $Path)#"Path does not exist: $($Path)"
    }

    # resolve the path
    $Path = Get-PodeRelativePath -Path $Path -JoinRoot -Resolve

    # create the temp drive
    if (!(Test-PodePSDrive -Name $Name -Path $Path)) {
        $drive = (New-PSDrive -Name $Name -PSProvider FileSystem -Root $Path -Scope Global -ErrorAction Stop)
    }
    else {
        $drive = Get-PodePSDrive -Name $Name
    }

    # store internally, and return the drive's name
    if (!$PodeContext.Server.Drives.ContainsKey($drive.Name)) {
        $PodeContext.Server.Drives[$drive.Name] = $Path
    }

    return "$($drive.Name):$([System.IO.Path]::DirectorySeparatorChar)"
}

function Get-PodePSDrive {
    param(
        [Parameter(Mandatory = $true)]
        [string]
        $Name
    )

    return (Get-PSDrive -Name $Name -PSProvider FileSystem -Scope Global -ErrorAction Ignore)
}

function Test-PodePSDrive {
    param(
        [Parameter(Mandatory = $true)]
        [string]
        $Name,

        [Parameter()]
        [string]
        $Path
    )

    $drive = Get-PodePSDrive -Name $Name
    if ($null -eq $drive) {
        return $false
    }

    if (![string]::IsNullOrWhiteSpace($Path)) {
        return ($drive.Root -ieq $Path)
    }

    return $true
}

<#
.SYNOPSIS
    Adds Pode PS drives to the session.

.DESCRIPTION
    This function iterates through the keys of Pode drives stored in the `$PodeContext.Server.Drives` collection and creates corresponding PS drives using `New-PodePSDrive`. The drive paths are specified by the values associated with each key.

.EXAMPLE
    Add-PodePSDrivesInternal
    # Creates Pode PS drives in the session based on the configured drive paths.

.NOTES
    This is an internal function and may change in future releases of Pode.
#>
function Add-PodePSDrivesInternal {
    foreach ($key in $PodeContext.Server.Drives.Keys) {
        $null = New-PodePSDrive -Path $PodeContext.Server.Drives[$key] -Name $key
    }
}

<#
.SYNOPSIS
    Imports other Pode modules into the session.

.DESCRIPTION
    This function iterates through the paths of other Pode modules stored in the `$PodeContext.Server.Modules.Values` collection and imports them into the session.
    It uses the `-DisableNameChecking` switch to suppress name checking during module import.

.EXAMPLE
    Import-PodeModulesInternal
    # Imports other Pode modules into the session.

.NOTES
    This is an internal function and may change in future releases of Pode.
#>
function Import-PodeModulesInternal {
    # import other modules in the session
    foreach ($path in $PodeContext.Server.Modules.Values) {
        if (Test-Path $path) {
            $null = Import-Module $path -DisableNameChecking -Scope Global -ErrorAction Stop
        }
    }
}

<#
.SYNOPSIS
Creates and registers inbuilt PowerShell drives for the Pode server's default folders.

.DESCRIPTION
This function sets up inbuilt PowerShell drives for the Pode web server's default directories: views, public content, and error pages. For each of these directories, if the physical path exists on the server, a new PowerShell drive is created and mapped to this path. These drives provide an easy and consistent way to access server resources like views, static files, and custom error pages within the Pode application.

The function leverages `$PodeContext` to access the server's configuration and to determine the paths for these default folders. If a folder's path exists, the function uses `New-PodePSDrive` to create a PowerShell drive for it and stores this drive in the server's `InbuiltDrives` dictionary, keyed by the folder type.

.PARAMETER None

.EXAMPLE
Add-PodePSInbuiltDrive

This example is typically called within the Pode server setup script or internally by the Pode framework to initialize the PowerShell drives for the server's default folders.

.NOTES
This is an internal function and may change in future releases of Pode.
#>
function Add-PodePSInbuiltDrive {

    # create drive for views, if path exists
    $path = (Join-PodeServerRoot -Folder $PodeContext.Server.DefaultFolders.Views)
    if (Test-Path $path) {
        $PodeContext.Server.InbuiltDrives[$PodeContext.Server.DefaultFolders.Views] = (New-PodePSDrive -Path $path)
    }

    # create drive for public content, if path exists
    $path = (Join-PodeServerRoot $PodeContext.Server.DefaultFolders.Public)
    if (Test-Path $path) {
        $PodeContext.Server.InbuiltDrives[$PodeContext.Server.DefaultFolders.Public] = (New-PodePSDrive -Path $path)
    }

    # create drive for errors, if path exists
    $path = (Join-PodeServerRoot $PodeContext.Server.DefaultFolders.Errors)
    if (Test-Path $path) {
        $PodeContext.Server.InbuiltDrives[$PodeContext.Server.DefaultFolders.Errors] = (New-PodePSDrive -Path $path)
    }
}

<#
.SYNOPSIS
    Removes Pode PS drives from the session.

.DESCRIPTION
    This function removes Pode PS drives from the session based on the specified drive name or pattern.
    If no specific name or pattern is provided, it removes all Pode PS drives by default.
    It uses `Get-PSDrive` to retrieve the drives and `Remove-PSDrive` to remove them.

.PARAMETER Name
    The name or pattern of the Pode PS drives to remove. Defaults to 'PodeDir*'.

.EXAMPLE
    Remove-PodePSDrive -Name 'myDir*'
    # Removes all PS drives with names matching the pattern 'myDir*'.

.EXAMPLE
    Remove-PodePSDrive
    # Removes all Pode PS drives.

.NOTES
    This is an internal function and may change in future releases of Pode.
#>
function Remove-PodePSDrive {
    [CmdletBinding()]
    param(
        $Name = 'PodeDir*'
    )
    $null = Get-PSDrive -Name $Name | Remove-PSDrive
}

<#
.SYNOPSIS
    Joins a folder and file path to the root path of the server.

.DESCRIPTION
    This function combines a folder path, file path (optional), and the root path of the server to create a complete path. If the root path is not explicitly provided, it uses the default root path from the Pode context.

.PARAMETER Folder
    The folder path to join.

.PARAMETER FilePath
    The file path (optional) to join. If not provided, only the folder path is used.

.PARAMETER Root
    The root path of the server. If not provided, the default root path from the Pode context is used.

.OUTPUTS
    Returns the combined path as a string.

.EXAMPLE
    Join-PodeServerRoot -Folder "uploads" -FilePath "document.txt"
    # Output: "/uploads/document.txt"

    This example combines the folder path "uploads" and the file path "document.txt" with the default root path from the Pode context.

#>
function Join-PodeServerRoot {
    [CmdletBinding()]
    [OutputType([string])]
    param(
        [Parameter(Mandatory = $true)]
        [ValidateNotNullOrEmpty()]
        [string]
        $Folder,

        [Parameter()]
        [string]
        $FilePath,

        [Parameter()]
        [string]
        $Root
    )

    # use the root path of the server
    if ([string]::IsNullOrWhiteSpace($Root)) {
        $Root = $PodeContext.Server.Root
    }

    # join the folder/file to the root path
    return [System.IO.Path]::Combine($Root, $Folder, $FilePath)
}

<#
.SYNOPSIS
    Removes empty items (empty strings) from an array.

.DESCRIPTION
    This function filters out empty items (empty strings) from an array. It returns a new array containing only non-empty items.

.PARAMETER Array
    The array from which to remove empty items.

.OUTPUTS
    Returns an array containing non-empty items.

.EXAMPLE
    $myArray = "apple", "", "banana", "", "cherry"
    $filteredArray = Remove-PodeEmptyItemsFromArray -Array $myArray
    Write-PodeHost "Filtered array: $filteredArray"

    This example removes empty items from the array and displays the filtered array.
#>
function Remove-PodeEmptyItemsFromArray {
    [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSPossibleIncorrectComparisonWithNull', '')]
    [CmdletBinding()]
    [OutputType([System.Object[]])]
    param(
        [Parameter()]
        $Array
    )
    if ($null -eq $Array) {
        return @()
    }

    return @( @($Array -ne ([string]::Empty)) -ne $null )

}

<#
.SYNOPSIS
    Retrieves the file extension from a given path.

.DESCRIPTION
    This function extracts the file extension (including the period) from a specified path. Optionally, it can trim the period from the extension.

.PARAMETER Path
    The path from which to extract the file extension.

.PARAMETER TrimPeriod
    Switch parameter. If specified, trims the period from the file extension.

.OUTPUTS
    Returns the file extension (with or without the period) as a string.

.EXAMPLE
    Get-PodeFileExtension -Path "C:\MyFiles\document.txt"
    # Output: ".txt"

    Get-PodeFileExtension -Path "C:\MyFiles\document.txt" -TrimPeriod
    # Output: "txt"

    This example demonstrates how to retrieve the file extension with and without the period from a given path.
#>
function Get-PodeFileExtension {
    [CmdletBinding()]
    [OutputType([string])]
    param(
        [Parameter()]
        [string]
        $Path,

        [switch]
        $TrimPeriod
    )

    # Get the file extension
    $ext = [System.IO.Path]::GetExtension($Path)

    # Trim the period if requested
    if ($TrimPeriod) {
        $ext = $ext.Trim('.')
    }

    return $ext
}


<#
.SYNOPSIS
    Retrieves the file name from a given path.

.DESCRIPTION
    This function extracts the file name (including the extension) or the file name without the extension from a specified path.

.PARAMETER Path
    The path from which to extract the file name.

.PARAMETER WithoutExtension
    Switch parameter. If specified, returns the file name without the extension.

.OUTPUTS
    Returns the file name (with or without extension) as a string.

.EXAMPLE
    Get-PodeFileName -Path "C:\MyFiles\document.txt"
    # Output: "document.txt"

    Get-PodeFileName -Path "C:\MyFiles\document.txt" -WithoutExtension
    # Output: "document"

    This example demonstrates how to retrieve the file name with and without the extension from a given path.

.NOTES
    - If the path is a directory, the function returns the directory name.
    - Use this function to extract file names for further processing or display.
#>
function Get-PodeFileName {
    [CmdletBinding()]
    [OutputType([string])]
    param(
        [Parameter()]
        [string]
        $Path,

        [switch]
        $WithoutExtension
    )

    if ($WithoutExtension) {
        return [System.IO.Path]::GetFileNameWithoutExtension($Path)
    }

    return [System.IO.Path]::GetFileName($Path)
}

<#
.SYNOPSIS
    Tests whether an exception message indicates a valid network failure.

.DESCRIPTION
    This function checks if an exception message contains specific phrases that commonly indicate network-related failures. It returns a boolean value indicating whether the exception message matches any of these network failure patterns.

.PARAMETER Exception
    The exception object whose message needs to be tested.

.OUTPUTS
    Returns $true if the exception message indicates a valid network failure, otherwise returns $false.

.EXAMPLE
    $exception = [System.Exception]::new("The network name is no longer available.")
    $isNetworkFailure = Test-PodeValidNetworkFailure -Exception $exception
    Write-PodeHost "Is network failure: $isNetworkFailure"

    This example tests whether the exception message "The network name is no longer available." indicates a network failure.
#>
function Test-PodeValidNetworkFailure {
    [CmdletBinding()]
    [OutputType([bool])]
    param(
        [Parameter()]
        $Exception
    )

    $msgs = @(
        '*network name is no longer available*',
        '*nonexistent network connection*',
        '*the response has completed*',
        '*broken pipe*'
    )

    $match = @(foreach ($msg in $msgs) {
            if ($Exception.Message -ilike $msg) {
                $msg
            }
        })[0]

    return ($null -ne $match)
}

function ConvertFrom-PodeHeaderQValue {
    param(
        [Parameter()]
        [string]
        $Value
    )

    process {
        $qs = [ordered]@{}

        # return if no value
        if ([string]::IsNullOrWhiteSpace($Value)) {
            return $qs
        }

        # split the values up
        $parts = @($Value -isplit ',').Trim()

        # go through each part and check its q-value
        foreach ($part in $parts) {
            # default of 1 if no q-value
            if ($part.IndexOf(';q=') -eq -1) {
                $qs[$part] = 1.0
                continue
            }

            # parse for q-value
            $atoms = @($part -isplit ';q=')
            $qs[$atoms[0]] = [double]$atoms[1]
        }

        return $qs
    }
}

function Get-PodeAcceptEncoding {
    param(
        [Parameter()]
        [string]
        $AcceptEncoding,

        [switch]
        $ThrowError
    )

    # return if no encoding
    if ([string]::IsNullOrWhiteSpace($AcceptEncoding)) {
        return [string]::Empty
    }

    # return empty if not compressing
    if (!$PodeContext.Server.Web.Compression.Enabled) {
        return [string]::Empty
    }

    # convert encoding form q-form
    $encodings = ConvertFrom-PodeHeaderQValue -Value $AcceptEncoding
    if ($encodings.Count -eq 0) {
        return [string]::Empty
    }

    # check the encodings for one that matches
    $normal = @('identity', '*')
    $valid = @()

    # build up supported and invalid
    foreach ($encoding in $encodings.Keys) {
        if (($encoding -iin $PodeContext.Server.Compression.Encodings) -or ($encoding -iin $normal)) {
            $valid += @{
                Name  = $encoding
                Value = $encodings[$encoding]
            }
        }
    }

    # if it's empty, just return empty
    if ($valid.Length -eq 0) {
        return [string]::Empty
    }

    # find the highest ranked match
    $found = @{}
    $failOnIdentity = $false

    foreach ($encoding in $valid) {
        if ($encoding.Value -gt $found.Value) {
            $found = $encoding
        }

        if (!$failOnIdentity -and ($encoding.Value -eq 0) -and ($encoding.Name -iin $normal)) {
            $failOnIdentity = $true
        }
    }

    # force found to identity/* if the 0 is not identity - meaning it's still allowed
    if (($found.Value -eq 0) -and !$failOnIdentity) {
        $found = @{
            Name  = 'identity'
            Value = 1.0
        }
    }

    # return invalid, error, or return empty for idenity?
    if ($found.Value -eq 0) {
        if ($ThrowError) {
            throw (New-PodeRequestException -StatusCode 406)
        }
    }

    # else, we're safe
    if ($found.Name -iin $normal) {
        return [string]::Empty
    }

    if ($found.Name -ieq 'x-gzip') {
        return 'gzip'
    }

    return $found.Name
}

<#
.SYNOPSIS
    Parses a range string and converts it into a hashtable array of start and end values.

.DESCRIPTION
    This function takes a range string (typically used in HTTP headers) and extracts the relevant start and end values. It supports the 'bytes' unit and handles multiple ranges separated by commas.

.PARAMETER Range
    The range string to parse.

.PARAMETER ThrowError
    A switch parameter. If specified, the function throws an exception (HTTP status code 416) when encountering invalid range formats.

.OUTPUTS
    An array of hashtables, each containing 'Start' and 'End' properties representing the parsed ranges.

.EXAMPLE
    Get-PodeRange -Range 'bytes=100-200,300-400'
    # Returns an array of hashtables:
    # [
    #     @{
    #         Start = 100
    #         End   = 200
    #     },
    #     @{
    #         Start = 300
    #         End   = 400
    #     }
    # ]

.NOTES
    This is an internal function and may change in future releases of Pode.
#>
function Get-PodeRange {
    [CmdletBinding()]
    [OutputType([hashtable[]])]
    param(
        [Parameter()]
        [string]
        $Range,

        [switch]
        $ThrowError
    )

    # return if no ranges
    if ([string]::IsNullOrWhiteSpace($Range)) {
        return $null
    }

    # split on '='
    $parts = @($Range -isplit '=').Trim()
    if (($parts.Length -le 1) -or ([string]::IsNullOrWhiteSpace($parts[1]))) {
        return $null
    }

    $unit = $parts[0]
    if ($unit -ine 'bytes') {
        if ($ThrowError) {
            throw (New-PodeRequestException -StatusCode 416)
        }

        return $null
    }

    # split on ','
    $parts = @($parts[1] -isplit ',').Trim()

    # parse into From-To hashtable array
    $ranges = @()

    foreach ($atom in $parts) {
        if ($atom -inotmatch '(?<start>[\d]+){0,1}\s?\-\s?(?<end>[\d]+){0,1}') {
            if ($ThrowError) {
                throw (New-PodeRequestException -StatusCode 416)
            }

            return $null
        }

        $ranges += @{
            Start = $Matches['start']
            End   = $Matches['end']
        }
    }

    return $ranges
}

function Get-PodeTransferEncoding {
    param(
        [Parameter()]
        [string]
        $TransferEncoding,

        [switch]
        $ThrowError
    )

    # return if no encoding
    if ([string]::IsNullOrWhiteSpace($TransferEncoding)) {
        return [string]::Empty
    }

    # convert encoding form q-form
    $encodings = ConvertFrom-PodeHeaderQValue -Value $TransferEncoding
    if ($encodings.Count -eq 0) {
        return [string]::Empty
    }

    # check the encodings for one that matches
    $normal = @('chunked', 'identity')
    $invalid = @()

    # if we see a supported one, return immediately. else build up invalid one
    foreach ($encoding in $encodings.Keys) {
        if ($encoding -iin $PodeContext.Server.Compression.Encodings) {
            if ($encoding -ieq 'x-gzip') {
                return 'gzip'
            }

            return $encoding
        }

        if ($encoding -iin $normal) {
            continue
        }

        $invalid += $encoding
    }

    # if we have any invalid, throw a 415 error
    if ($invalid.Length -gt 0) {
        if ($ThrowError) {
            throw (New-PodeRequestException -StatusCode 415)
        }

        return $invalid[0]
    }

    # else, we're safe
    return [string]::Empty
}

function Get-PodeEncodingFromContentType {
    param(
        [Parameter()]
        [string]
        $ContentType
    )

    if ([string]::IsNullOrWhiteSpace($ContentType)) {
        return [System.Text.Encoding]::UTF8
    }

    $parts = @($ContentType -isplit ';').Trim()

    foreach ($part in $parts) {
        if ($part.StartsWith('charset')) {
            return [System.Text.Encoding]::GetEncoding(($part -isplit '=')[1].Trim())
        }
    }

    return [System.Text.Encoding]::UTF8
}

function New-PodeRequestException {
    param(
        [Parameter(Mandatory = $true)]
        [int]
        $StatusCode
    )

    $err = [System.Net.Http.HttpRequestException]::new()
    $err.Data.Add('PodeStatusCode', $StatusCode)
    return $err
}

function ConvertTo-PodeResponseContent {
    param(
        [Parameter()]
        $InputObject,

        [Parameter()]
        [string]
        $ContentType,

        [Parameter()]
        [int]
        $Depth = 10,

        [Parameter()]
        [string]
        $Delimiter = ',',

        [switch]
        $AsHtml
    )
    # split for the main content type
    $ContentType = Split-PodeContentType -ContentType $ContentType

    # if there is no content-type then convert straight to string
    if ([string]::IsNullOrWhiteSpace($ContentType)) {
        return ([string]$InputObject)
    }

    # run action for the content type
    switch ($ContentType) {
        { $_ -match '^(.*\/)?(.*\+)?json$' } {
            if ($InputObject -isnot [string]) {
                if ($Depth -le 0) {
                    return (ConvertTo-Json -InputObject $InputObject -Compress)
                }
                else {
                    return (ConvertTo-Json -InputObject $InputObject -Depth $Depth -Compress)
                }
            }

            if ([string]::IsNullOrWhiteSpace($InputObject)) {
                return '{}'
            }
        }

        { $_ -match '^(.*\/)?(.*\+)?yaml$' } {
            if ($InputObject -isnot [string]) {
                if ($Depth -le 0) {
                    return (ConvertTo-PodeYamlInternal -InputObject $InputObject )
                }
                else {
                    return (ConvertTo-PodeYamlInternal -InputObject $InputObject -Depth $Depth  )
                }
            }

            if ([string]::IsNullOrWhiteSpace($InputObject)) {
                return '[]'
            }
        }

        { $_ -match '^(.*\/)?(.*\+)?xml$' } {
            if ($InputObject -isnot [string]) {
                $temp = @(foreach ($item in $InputObject) {
                        [pscustomobject]$item
                    })

                return ($temp | ConvertTo-Xml -Depth $Depth -As String -NoTypeInformation)
            }

            if ([string]::IsNullOrWhiteSpace($InputObject)) {
                return [string]::Empty
            }
        }

        { $_ -ilike '*/csv' } {
            if ($InputObject -isnot [string]) {
                $temp = @(foreach ($item in $InputObject) {
                        [pscustomobject]$item
                    })

                if (Test-PodeIsPSCore) {
                    $temp = ($temp | ConvertTo-Csv -Delimiter $Delimiter -IncludeTypeInformation:$false)
                }
                else {
                    $temp = ($temp | ConvertTo-Csv -Delimiter $Delimiter -NoTypeInformation)
                }

                return ($temp -join ([environment]::NewLine))
            }

            if ([string]::IsNullOrWhiteSpace($InputObject)) {
                return [string]::Empty
            }
        }

        { $_ -ilike '*/html' } {
            if ($InputObject -isnot [string]) {
                return (($InputObject | ConvertTo-Html) -join ([environment]::NewLine))
            }

            if ([string]::IsNullOrWhiteSpace($InputObject)) {
                return [string]::Empty
            }
        }

        { $_ -ilike '*/markdown' } {
            if ($AsHtml -and ($PSVersionTable.PSVersion.Major -ge 7)) {
                return ($InputObject | ConvertFrom-Markdown).Html
            }
        }
    }

    return ([string]$InputObject)
}

function ConvertFrom-PodeRequestContent {
    param(
        [Parameter()]
        $Request,

        [Parameter()]
        [string]
        $ContentType,

        [Parameter()]
        [string]
        $TransferEncoding
    )

    # get the requests content type
    $ContentType = Split-PodeContentType -ContentType $ContentType

    # result object for data/files
    $Result = @{
        Data  = @{}
        Files = @{}
    }

    # if there is no content-type then do nothing
    if ([string]::IsNullOrWhiteSpace($ContentType)) {
        return $Result
    }

    # if the content-type is not multipart/form-data, get the string data
    if ($ContentType -ine 'multipart/form-data') {
        # get the content based on server type
        if ($PodeContext.Server.IsServerless) {
            switch ($PodeContext.Server.ServerlessType.ToLowerInvariant()) {
                'awslambda' {
                    $Content = $Request.body
                }

                'azurefunctions' {
                    $Content = $Request.RawBody
                }
            }
        }
        else {
            # if the request is compressed, attempt to uncompress it
            if (![string]::IsNullOrWhiteSpace($TransferEncoding)) {
                # create a compressed stream to decompress the req bytes
                $ms = [System.IO.MemoryStream]::new()
                $ms.Write($Request.RawBody, 0, $Request.RawBody.Length)
                $null = $ms.Seek(0, 0)
                $stream = Get-PodeCompressionStream -InputStream $ms -Encoding $TransferEncoding -Mode Decompress

                # read the decompressed bytes
                $Content = Read-PodeStreamToEnd -Stream $stream -Encoding $Request.ContentEncoding
            }
            else {
                $Content = $Request.Body
            }
        }

        # if there is no content then do nothing
        if ([string]::IsNullOrWhiteSpace($Content)) {
            return $Result
        }

        # check if there is a defined custom body parser
        if ($PodeContext.Server.BodyParsers.ContainsKey($ContentType)) {
            $parser = $PodeContext.Server.BodyParsers[$ContentType]
            $Result.Data = (Invoke-PodeScriptBlock -ScriptBlock $parser.ScriptBlock -Arguments $Content -UsingVariables $parser.UsingVariables -Return)
            $Content = $null
            return $Result
        }
    }

    # run action for the content type
    switch ($ContentType) {
        { $_ -ilike '*/json' } {
            if (Test-PodeIsPSCore) {
                $Result.Data = ($Content | ConvertFrom-Json -AsHashtable)
            }
            else {
                $Result.Data = ($Content | ConvertFrom-Json)
            }
        }

        { $_ -ilike '*/xml' } {
            $Result.Data = [xml]($Content)
        }

        { $_ -ilike '*/csv' } {
            $Result.Data = ($Content | ConvertFrom-Csv)
        }

        { $_ -ilike '*/x-www-form-urlencoded' } {
            $Result.Data = (ConvertFrom-PodeNameValueToHashTable -Collection ([System.Web.HttpUtility]::ParseQueryString($Content)))
        }

        { $_ -ieq 'multipart/form-data' } {
            # parse multipart form data
            $form = $null

            if ($PodeContext.Server.IsServerless) {
                switch ($PodeContext.Server.ServerlessType.ToLowerInvariant()) {
                    'awslambda' {
                        $Content = $Request.body
                    }

                    'azurefunctions' {
                        $Content = $Request.Body
                    }
                }

                $form = [PodeForm]::Parse($Content, $WebEvent.ContentType, [System.Text.Encoding]::UTF8)
            }
            else {
                $Request.ParseFormData()
                $form = $Request.Form
            }

            # set the files/data
            foreach ($file in $form.Files) {
                $Result.Files.Add($file.FileName, $file)
            }

            foreach ($item in $form.Data) {
                if ($item.IsSingular) {
                    $Result.Data.Add($item.Key, $item.Values[0])
                }
                else {
                    $Result.Data.Add($item.Key, $item.Values)
                }
            }

            $form = $null
        }

        default {
            $Result.Data = $Content
        }
    }

    $Content = $null
    return $Result
}
<#
.SYNOPSIS
    Extracts the base MIME type from a Content-Type string that may include additional parameters.

.DESCRIPTION
    This function takes a Content-Type string as input and returns only the base MIME type by splitting the string at the semicolon (';') and trimming any excess whitespace.
    It is useful for handling HTTP headers or other contexts where Content-Type strings include parameters like charset, boundary, etc.

.PARAMETER ContentType
    The Content-Type string from which to extract the base MIME type. This string can include additional parameters separated by semicolons.

.EXAMPLE
    Split-PodeContentType -ContentType "text/html; charset=UTF-8"

    This example returns 'text/html', stripping away the 'charset=UTF-8' parameter.

.EXAMPLE
    Split-PodeContentType -ContentType "application/json; charset=utf-8"

    This example returns 'application/json', removing the charset parameter.
#>
function Split-PodeContentType {
    param(
        [Parameter()]
        [string]
        $ContentType
    )

    # Check if the input string is null, empty, or consists only of whitespace.
    if ([string]::IsNullOrWhiteSpace($ContentType)) {
        return [string]::Empty  # Return an empty string if the input is not valid.
    }

    # Split the Content-Type string by the semicolon, which separates the base MIME type from other parameters.
    # Trim any leading or trailing whitespace from the resulting MIME type to ensure clean output.
    return @($ContentType -isplit ';')[0].Trim()
}

function ConvertFrom-PodeNameValueToHashTable {
    param(
        [Parameter()]
        [System.Collections.Specialized.NameValueCollection]
        $Collection
    )

    if ((Get-PodeCount -Object $Collection) -eq 0) {
        return @{}
    }

    $ht = @{}
    foreach ($key in $Collection.Keys) {
        $htKey = $key
        if (!$key) {
            $htKey = ''
        }

        $ht[$htKey] = $Collection.Get($key)
    }

    return $ht
}

<#
.SYNOPSIS
    Gets the count of elements in the provided object or the length of a string.

.DESCRIPTION
    This function returns the count of elements in various types of objects including strings, collections, and arrays.
    If the object is a string, it returns the length of the string. If the object is null or an empty collection, it returns 0.
    This function is useful for determining the size or length of data containers in PowerShell scripts.

.PARAMETER Object
    The object from which the count or length will be determined. This can be a string, array, collection, or any other object that has a Count property.

.OUTPUTS
    [int]
    Returns an integer representing the count of elements or length of the string.

.EXAMPLE
    $array = @(1, 2, 3)
    Get-PodeCount -Object $array

    This example returns 3, as there are three elements in the array.

.EXAMPLE
    $string = "hello"
    Get-PodeCount -Object $string

    This example returns 5, as there are five characters in the string.

.EXAMPLE
    $nullObject = $null
    Get-PodeCount -Object $nullObject

    This example returns 0, as the object is null.
#>
function Get-PodeCount {
    [CmdletBinding()]
    [OutputType([int])]
    param(
        [Parameter()]
        $Object  # The object to be evaluated for its count.
    )

    # Check if the object is null.
    if ($null -eq $Object) {
        return 0  # Return 0 if the object is null.
    }

    # Check if the object is a string and return its length.
    if ($Object -is [string]) {
        return $Object.Length
    }

    # Check if the object is a NameValueCollection and is empty.
    if ($Object -is [System.Collections.Specialized.NameValueCollection] -and $Object.Count -eq 0) {
        return 0  # Return 0 if the collection is empty.
    }

    # For other types of collections, return their Count property.
    return $Object.Count
}


<#
.SYNOPSIS
    Tests if a given file system path is valid and optionally if it is not a directory.

.DESCRIPTION
    This function tests if the provided file system path is valid. It checks if the path is not null or whitespace, and if the item at the path exists. If the item exists and is not a directory (unless the $FailOnDirectory switch is not used), it returns true. If the path is not valid, it can optionally set a 404 response status code.

.PARAMETER Path
    The file system path to test for validity.

.PARAMETER NoStatus
    A switch to suppress setting the 404 response status code if the path is not valid.

.PARAMETER FailOnDirectory
    A switch to indicate that the function should return false if the path is a directory.

.PARAMETER Force
    A switch to indicate that the file with the hidden attribute has to be includede

.PARAMETER ReturnItem
    Return the item file item itself instead of true or false

.EXAMPLE
    $isValid = Test-PodePath -Path "C:\temp\file.txt"
    if ($isValid) {
        # The file exists and is not a directory
    }

.EXAMPLE
    $isValid = Test-PodePath -Path "C:\temp\folder" -FailOnDirectory
    if (!$isValid) {
        # The path is a directory or does not exist
    }

.NOTES
    This function is used within the Pode framework to validate file system paths for serving static content.

#>
function Test-PodePath {
    param(
        [Parameter()]
        $Path,

        [switch]
        $NoStatus,

        [switch]
        $FailOnDirectory,

        [switch]
        $Force,

        [switch]
        $ReturnItem
    )

    $statusCode = 404

    if (![string]::IsNullOrWhiteSpace($Path)) {
        try {
            $item = Get-Item $Path -Force:$Force -ErrorAction Stop
            if (($null -ne $item) -and (!$FailOnDirectory -or !$item.PSIsContainer)) {
                $statusCode = 200
            }
        }
        catch [System.Management.Automation.ItemNotFoundException] {
            $statusCode = 404
        }
        catch [System.UnauthorizedAccessException] {
            $statusCode = 401
        }
        catch {
            $statusCode = 400
        }

    }

    if ($statusCode -eq 200) {
        if ($ReturnItem.IsPresent) {
            return  $item
        }
        return $true
    }

    # if we failed to get the file, report back the status code and/or return true/false
    if (!$NoStatus.IsPresent) {
        Set-PodeResponseStatus -Code $statusCode
    }

    if ($ReturnItem.IsPresent) {
        return  $null
    }
    return $false
}

function Test-PodePathIsFile {
    param(
        [Parameter()]
        [string]
        $Path,

        [switch]
        $FailOnWildcard
    )

    if ([string]::IsNullOrWhiteSpace($Path)) {
        return $false
    }

    if ($FailOnWildcard -and (Test-PodePathIsWildcard $Path)) {
        return $false
    }

    return (![string]::IsNullOrWhiteSpace([System.IO.Path]::GetExtension($Path)))
}

function Test-PodePathIsWildcard {
    param(
        [Parameter()]
        [string]
        $Path
    )

    if ([string]::IsNullOrWhiteSpace($Path)) {
        return $false
    }

    return $Path.Contains('*')
}

function Test-PodePathIsDirectory {
    param(
        [Parameter(Mandatory = $true)]
        [ValidateNotNullOrEmpty()]
        [string]
        $Path,

        [switch]
        $FailOnWildcard

    )

    if ($FailOnWildcard -and (Test-PodePathIsWildcard $Path)) {
        return $false
    }

    return ([string]::IsNullOrWhiteSpace([System.IO.Path]::GetExtension($Path)))
}



function Convert-PodePathPatternToRegex {
    param(
        [Parameter()]
        [string]
        $Path,

        [switch]
        $NotSlashes,

        [switch]
        $NotStrict
    )

    if (!$NotSlashes) {
        if ($Path -match '[\\/]\*$') {
            $Path = $Path -replace '[\\/]\*$', '/{0,1}*'
        }

        $Path = $Path -ireplace '[\\/]', '[\\/]'
    }

    $Path = $Path -ireplace '\.', '\.'
    $Path = $Path -ireplace '\*', '.*?'

    if ($NotStrict) {
        return $Path
    }

    return "^$($Path)$"
}

function Convert-PodePathPatternsToRegex {
    param(
        [Parameter()]
        [string[]]
        $Paths,

        [switch]
        $NotSlashes,

        [switch]
        $NotStrict
    )

    # replace certain chars
    $Paths = @(foreach ($path in $Paths) {
            if (![string]::IsNullOrEmpty($path)) {
                Convert-PodePathPatternToRegex -Path $path -NotStrict -NotSlashes:$NotSlashes
            }
        })

    # if no paths, return null
    if (($null -eq $Paths) -or ($Paths.Length -eq 0)) {
        return $null
    }

    # join them all together
    $joined = "($($Paths -join '|'))"

    if ($NotStrict) {
        return $joined
    }

    return "^$($joined)$"
}

<#
.SYNOPSIS
    Gets the default SSL protocol(s) based on the operating system.

.DESCRIPTION
    This function determines the appropriate default SSL protocol(s) based on the operating system. On macOS, it returns TLS 1.2. On other platforms, it combines SSL 3.0 and TLS 1.2.

.OUTPUTS
    A [System.Security.Authentication.SslProtocols] enum value representing the default SSL protocol(s).

.EXAMPLE
    Get-PodeDefaultSslProtocol
    # Returns [System.Security.Authentication.SslProtocols]::Ssl3, [System.Security.Authentication.SslProtocols]::Tls12 (on non-macOS systems)
    # Returns [System.Security.Authentication.SslProtocols]::Tls12 (on macOS)

.NOTES
    This is an internal function and may change in future releases of Pode.
#>
function Get-PodeDefaultSslProtocol {
    [CmdletBinding()]
    [OutputType([System.Security.Authentication.SslProtocols])]
    param()
    if (Test-PodeIsMacOS) {
        return (ConvertTo-PodeSslProtocol -Protocol Tls12)
    }

    return (ConvertTo-PodeSslProtocol -Protocol Ssl3, Tls12)
}

<#
.SYNOPSIS
    Converts a string representation of SSL protocols to the corresponding SslProtocols enum value.

.DESCRIPTION
    This function takes an array of SSL protocol strings (such as 'Tls', 'Tls12', etc.) and combines them into a single SslProtocols enum value. It's useful for configuring SSL/TLS settings in Pode or other PowerShell scripts.

.PARAMETER Protocol
    An array of SSL protocol strings. Valid values are 'Ssl2', 'Ssl3', 'Tls', 'Tls11', 'Tls12', and 'Tls13'.

.OUTPUTS
    A [System.Security.Authentication.SslProtocols] enum value representing the combined protocols.

.EXAMPLE
    ConvertTo-PodeSslProtocol -Protocol 'Tls', 'Tls12'
    # Returns [System.Security.Authentication.SslProtocols]::Tls12

.NOTES
    This is an internal function and may change in future releases of Pode.
#>
function ConvertTo-PodeSslProtocol {
    [CmdletBinding()]
    [OutputType([System.Security.Authentication.SslProtocols])]
    param(
        [Parameter()]
        [ValidateSet('Ssl2', 'Ssl3', 'Tls', 'Tls11', 'Tls12', 'Tls13')]
        [string[]]
        $Protocol
    )

    $protos = 0
    foreach ($item in $Protocol) {
        $protos = [int]($protos -bor [System.Security.Authentication.SslProtocols]::$item)
    }

    return [System.Security.Authentication.SslProtocols]($protos)
}

<#
.SYNOPSIS
    Retrieves details about the Pode module.

.DESCRIPTION
    This function determines the relevant details of the Pode module. It first checks if the module is already imported.
    If so, it uses that module. Otherwise, it attempts to identify the module used for the 'engine' and retrieves its details.
    If there are multiple versions of the module, it selects the newest version. If no module is imported, it uses the latest installed version.

.OUTPUTS
    A hashtable containing the module details.

.EXAMPLE
    Get-PodeModuleInfo
    # Returns a hashtable with module details such as name, path, base path, data path, internal path, and whether it's in the system path.

    .NOTES
    This is an internal function and may change in future releases of Pode.
#>
function Get-PodeModuleInfo {
    [CmdletBinding()]
    [OutputType([hashtable])]
    param()
    # if there's 1 module imported already, use that
    $importedModule = @(Get-Module -Name Pode)
    if (($importedModule | Measure-Object).Count -eq 1) {
        return (Convert-PodeModuleInfo -Module @($importedModule)[0])
    }

    # if there's none or more, attempt to get the module used for 'engine'
    try {
        $usedModule = (Get-Command -Name 'Set-PodeViewEngine').Module
        if (($usedModule | Measure-Object).Count -eq 1) {
            return (Convert-PodeModuleInfo -Module $usedModule)
        }
    }
    catch {
        $_ | Write-PodeErrorLog -Level Debug
    }

    # if there were multiple to begin with, use the newest version
    if (($importedModule | Measure-Object).Count -gt 1) {
        return (Convert-PodeModuleInfo -Module @($importedModule | Sort-Object -Property Version)[-1])
    }

    # otherwise there were none, use the latest installed
    return (Convert-PodeModuleInfo -Module @(Get-Module -ListAvailable -Name Pode | Sort-Object -Property Version)[-1])
}

<#
.SYNOPSIS
    Converts Pode module details to a hashtable.

.DESCRIPTION
    This function takes a Pode module and extracts relevant details such as name, path, base path, data path, internal path, and whether it's in the system path.

.PARAMETER Module
    The Pode module to convert.

.OUTPUTS
    A hashtable containing the module details.

.EXAMPLE
    Convert-PodeModuleInfo -Module (Get-Module Pode)

.NOTES
    This is an internal function and may change in future releases of Pode.
#>
function Convert-PodeModuleInfo {
    [CmdletBinding()]
    [OutputType([hashtable])]
    param(
        [Parameter(Mandatory = $true)]
        [psmoduleinfo]
        $Module
    )

    $details = @{
        Name         = $Module.Name
        Path         = $Module.Path
        BasePath     = $Module.ModuleBase
        DataPath     = (Find-PodeModuleFile -Module $Module -CheckVersion)
        InternalPath = $null
        InPath       = (Test-PodeModuleInPath -Module $Module)
    }

    $details.InternalPath = $details.DataPath -ireplace 'Pode\.(ps[md]1)', 'Pode.Internal.$1'
    return $details
}

<#
.SYNOPSIS
    Checks if a PowerShell module is located within the directories specified in the PSModulePath environment variable.

.DESCRIPTION
    This function determines if the path of a provided PowerShell module starts with any path included in the system's PSModulePath environment variable.
    This is used to ensure that the module is being loaded from expected locations, which can be important for security and configuration verification.

.PARAMETER Module
    The module to be checked. This should be a module info object, typically obtained via Get-Module or Import-Module.

.OUTPUTS
    [bool]
    Returns $true if the module's path is under a path listed in PSModulePath, otherwise returns $false.

.EXAMPLE
    $module = Get-Module -Name Pode
    Test-PodeModuleInPath -Module $module

    This example checks if the 'Pode' module is located within the paths specified by the PSModulePath environment variable.
#>
function Test-PodeModuleInPath {
    [CmdletBinding()]
    [OutputType([bool])]
    param(
        [Parameter(Mandatory = $true)]
        [psmoduleinfo]
        $Module
    )

    # Determine the path separator based on the operating system.
    $separator = if (Test-PodeIsUnix) { ':' } else { ';' }

    # Split the PSModulePath environment variable to get individual paths.
    $paths = @($env:PSModulePath -split $separator)

    # Check each path to see if the module's path starts with it.
    foreach ($path in $paths) {
        # Return true if the module is in one of the paths.
        if ($Module.Path.StartsWith($path)) {
            return $true
        }
    }

    # Return false if no matching path is found.
    return $false
}
<#
.SYNOPSIS
    Retrieves a module and all of its recursive dependencies.

.DESCRIPTION
    This function takes a PowerShell module as input and returns an array containing
    the module and all of its required dependencies, retrieved recursively. This is
    useful for understanding the full set of dependencies a module has.

.PARAMETER Module
    The module for which to retrieve dependencies. This must be a valid PowerShell module object.

.EXAMPLE
    $module = Get-Module -Name SomeModuleName
    $dependencies = Get-PodeModuleDependencyList -Module $module
    This example retrieves all dependencies for "SomeModuleName".

.OUTPUTS
    Array[psmoduleinfo]
    Returns an array of psmoduleinfo objects, each representing a module in the dependency tree.
#>

function Get-PodeModuleDependencyList {
    param(
        [Parameter(Mandatory = $true)]
        [psmoduleinfo]
        $Module
    )

    # Check if the module has any required modules (dependencies).
    if (!$Module.RequiredModules) {
        return $Module
    }
    # Initialize an array to hold all dependencies.
    $mods = @()

    # Iterate through each required module and recursively retrieve their dependencies.
    foreach ($mod in $Module.RequiredModules) {
        # Recursive call for each dependency.
        $mods += (Get-PodeModuleDependencyList -Module $mod)
    }

    # Return the list of all dependencies plus the original module.
    return ($mods + $module)
}

function Get-PodeModuleRootPath {
    return (Split-Path -Parent -Path $PodeContext.Server.PodeModule.Path)
}

function Get-PodeModuleMiscPath {
    return [System.IO.Path]::Combine((Get-PodeModuleRootPath), 'Misc')
}

function Get-PodeUrl {
    return "$($WebEvent.Endpoint.Protocol)://$($WebEvent.Endpoint.Address)$($WebEvent.Path)"
}

function Find-PodeErrorPage {
    param(
        [Parameter()]
        [int]
        $Code,

        [Parameter()]
        [string]
        $ContentType
    )

    # if a defined content type is supplied, attempt to find an error page for that first
    if (![string]::IsNullOrWhiteSpace($ContentType)) {
        $path = Get-PodeErrorPage -Code $Code -ContentType $ContentType
        if (![string]::IsNullOrWhiteSpace($path)) {
            return @{ 'Path' = $path; 'ContentType' = $ContentType }
        }
    }

    # if a defined route error page content type is supplied, attempt to find an error page for that
    if (![string]::IsNullOrWhiteSpace($WebEvent.ErrorType)) {
        $path = Get-PodeErrorPage -Code $Code -ContentType $WebEvent.ErrorType
        if (![string]::IsNullOrWhiteSpace($path)) {
            return @{ 'Path' = $path; 'ContentType' = $WebEvent.ErrorType }
        }
    }

    # if route patterns have been defined, see if an error content type matches and attempt that
    if (!(Test-PodeIsEmpty $PodeContext.Server.Web.ErrorPages.Routes)) {
        # find type by pattern
        $matched = @(foreach ($key in $PodeContext.Server.Web.ErrorPages.Routes.Keys) {
                if ($WebEvent.Path -imatch $key) {
                    $key
                }
            })[0]

        # if we have a match, see if a page exists
        if (!(Test-PodeIsEmpty $matched)) {
            $type = $PodeContext.Server.Web.ErrorPages.Routes[$matched]
            $path = Get-PodeErrorPage -Code $Code -ContentType $type
            if (![string]::IsNullOrWhiteSpace($path)) {
                return @{ 'Path' = $path; 'ContentType' = $type }
            }
        }
    }

    # if we're using strict typing, attempt that, if we have a content type
    if ($PodeContext.Server.Web.ErrorPages.StrictContentTyping -and ![string]::IsNullOrWhiteSpace($WebEvent.ContentType)) {
        $path = Get-PodeErrorPage -Code $Code -ContentType $WebEvent.ContentType
        if (![string]::IsNullOrWhiteSpace($path)) {
            return @{ 'Path' = $path; 'ContentType' = $WebEvent.ContentType }
        }
    }

    # if we have a default defined, attempt that
    if (!(Test-PodeIsEmpty $PodeContext.Server.Web.ErrorPages.Default)) {
        $path = Get-PodeErrorPage -Code $Code -ContentType $PodeContext.Server.Web.ErrorPages.Default
        if (![string]::IsNullOrWhiteSpace($path)) {
            return @{ 'Path' = $path; 'ContentType' = $PodeContext.Server.Web.ErrorPages.Default }
        }
    }

    # if there's still no error page, use default HTML logic
    $type = Get-PodeContentType -Extension 'html'
    $path = (Get-PodeErrorPage -Code $Code -ContentType $type)

    if (![string]::IsNullOrWhiteSpace($path)) {
        return @{ 'Path' = $path; 'ContentType' = $type }
    }

    return $null
}

function Get-PodeErrorPage {
    param(
        [Parameter()]
        [int]
        $Code,

        [Parameter()]
        [string]
        $ContentType
    )

    # parse the passed content type
    $ContentType = Split-PodeContentType -ContentType $ContentType

    # object for the page path
    $path = $null

    # attempt to find a custom error page
    $path = Find-PodeCustomErrorPage -Code $Code -ContentType $ContentType

    # if there's no custom page found, attempt to find an inbuilt page
    if ([string]::IsNullOrWhiteSpace($path)) {
        $podeRoot = Get-PodeModuleMiscPath
        $path = Find-PodeFileForContentType -Path $podeRoot -Name 'default-error-page' -ContentType $ContentType -Engine 'pode'
    }

    # if there's no path found, or it's inaccessible, return null
    if (!(Test-PodePath $path -NoStatus)) {
        return $null
    }

    return $path
}

function Find-PodeCustomErrorPage {
    param(
        [Parameter()]
        [int]
        $Code,

        [Parameter()]
        [string]
        $ContentType
    )

    # get the custom errors path
    $customErrPath = $PodeContext.Server.InbuiltDrives['errors']

    # if there's no custom error path, return
    if ([string]::IsNullOrWhiteSpace($customErrPath)) {
        return $null
    }

    # retrieve a status code page
    $path = (Find-PodeFileForContentType -Path $customErrPath -Name "$($Code)" -ContentType $ContentType)
    if (![string]::IsNullOrWhiteSpace($path)) {
        return $path
    }

    # retrieve default page
    $path = (Find-PodeFileForContentType -Path $customErrPath -Name 'default' -ContentType $ContentType)
    if (![string]::IsNullOrWhiteSpace($path)) {
        return $path
    }

    # no file was found
    return $null
}

function Find-PodeFileForContentType {
    param(
        [Parameter()]
        [string]
        $Path,

        [Parameter()]
        [string]
        $Name,

        [Parameter()]
        [string]
        $ContentType,

        [Parameter()]
        [string]
        $Engine = $null
    )

    # get all files at the path that start with the name
    $files = @(Get-ChildItem -Path ([System.IO.Path]::Combine($Path, "$($Name).*")))

    # if there are no files, return
    if ($null -eq $files -or $files.Length -eq 0) {
        return $null
    }

    # filter the files by the view engine extension (but only if the current engine is dynamic - non-html)
    if ([string]::IsNullOrWhiteSpace($Engine) -and $PodeContext.Server.ViewEngine.IsDynamic) {
        $Engine = $PodeContext.Server.ViewEngine.Extension
    }

    $Engine = (Protect-PodeValue -Value $Engine -Default 'pode')
    if ($Engine -ine 'pode') {
        $Engine = "($($Engine)|pode)"
    }

    $engineFiles = @(foreach ($file in $files) {
            if ($file.Name -imatch "\.$($Engine)$") {
                $file
            }
        })

    $files = @(foreach ($file in $files) {
            if ($file.Name -inotmatch "\.$($Engine)$") {
                $file
            }
        })

    # only attempt static files if we still have files after any engine filtering
    if ($null -ne $files -and $files.Length -gt 0) {
        # get files of the format '<name>.<type>'
        $file = @(foreach ($f in $files) {
                if ($f.Name -imatch "^$($Name)\.(?<ext>.*?)$") {
                    if (($ContentType -ieq (Get-PodeContentType -Extension $Matches['ext']))) {
                        $f.FullName
                    }
                }
            })[0]

        if (![string]::IsNullOrWhiteSpace($file)) {
            return $file
        }
    }

    # only attempt these formats if we have a files for the view engine
    if ($null -ne $engineFiles -and $engineFiles.Length -gt 0) {
        # get files of the format '<name>.<type>.<engine>'
        $file = @(foreach ($f in $engineFiles) {
                if ($f.Name -imatch "^$($Name)\.(?<ext>.*?)\.$($engine)$") {
                    if ($ContentType -ieq (Get-PodeContentType -Extension $Matches['ext'])) {
                        $f.FullName
                    }
                }
            })[0]

        if (![string]::IsNullOrWhiteSpace($file)) {
            return $file
        }

        # get files of the format '<name>.<engine>'
        $file = @(foreach ($f in $engineFiles) {
                if ($f.Name -imatch "^$($Name)\.$($engine)$") {
                    $f.FullName
                }
            })[0]

        if (![string]::IsNullOrWhiteSpace($file)) {
            return $file
        }
    }

    # no file was found
    return $null
}

function Get-PodeRelativePath {
    param(
        [Parameter(Mandatory = $true)]
        [string]
        $Path,

        [Parameter()]
        [string]
        $RootPath,

        [switch]
        $JoinRoot,

        [switch]
        $Resolve,

        [switch]
        $TestPath
    )

    # if the path is relative, join to root if flagged
    if ($JoinRoot -and ($Path -match '^\.{1,2}([\\\/]|$)')) {
        if ([string]::IsNullOrWhiteSpace($RootPath)) {
            $RootPath = $PodeContext.Server.Root
        }

        $Path = [System.IO.Path]::Combine($RootPath, $Path)
    }

    # if flagged, resolve the path
    if ($Resolve) {
        $_rawPath = $Path
        $Path = [System.IO.Path]::GetFullPath($Path.Replace('\', '/'))
    }

    # if flagged, test the path and throw error if it doesn't exist
    if ($TestPath -and !(Test-PodePath $Path -NoStatus)) {
        throw ($PodeLocale.pathNotExistExceptionMessage -f (Protect-PodeValue -Value $Path -Default $_rawPath))#"The path does not exist: $(Protect-PodeValue -Value $Path -Default $_rawPath)"
    }

    return $Path
}

<#
.SYNOPSIS
    Retrieves files based on a wildcard pattern in a given path.

.DESCRIPTION
    The `Get-PodeWildcardFile` function returns files from the specified path based on a wildcard pattern.
    You can customize the wildcard and provide an optional root path for relative paths.

.PARAMETER Path
    Specifies the path to search for files. This parameter is mandatory.

.PARAMETER Wildcard
    Specifies the wildcard pattern for file matching. Default is '*.*'.

.PARAMETER RootPath
    Specifies an optional root path for relative paths. If provided, the function will join the root path with the specified path.

.OUTPUTS
    Returns an array of file paths matching the wildcard pattern.

.EXAMPLE
    # Example usage:
    $files = Get-PodeWildcardFile -Path '/path/to/files' -Wildcard '*.txt'
    # Returns an array of .txt files in the specified path.

.NOTES
    This is an internal function and may change in future releases of Pode.
#>
function Get-PodeWildcardFile {
    [CmdletBinding()]
    [OutputType([object[]])]
    param(
        [Parameter(Mandatory = $true)]
        [string]
        $Path,

        [Parameter()]
        [string]
        $Wildcard = '*.*',

        [Parameter()]
        [string]
        $RootPath
    )

    # if the OriginalPath is a directory, add wildcard
    if (Test-PodePathIsDirectory -Path $Path) {
        $Path = [System.IO.Path]::Combine($Path, $Wildcard)
    }

    # if path has a *, assume wildcard
    if (Test-PodePathIsWildcard -Path $Path) {
        $Path = Get-PodeRelativePath -Path $Path -RootPath $RootPath -JoinRoot
        return @((Get-ChildItem $Path -Recurse -Force).FullName)
    }

    return $null
}

function Test-PodeIsServerless {
    param(
        [Parameter()]
        [string]
        $FunctionName,

        [switch]
        $ThrowError
    )

    if ($PodeContext.Server.IsServerless -and $ThrowError) {
        throw ($PodeLocale.unsupportedFunctionInServerlessContextExceptionMessage -f $FunctionName) #"The $($FunctionName) function is not supported in a serverless context"
    }

    if (!$ThrowError) {
        return $PodeContext.Server.IsServerless
    }
}

function Get-PodeEndpointUrl {
    param(
        [Parameter()]
        $Endpoint
    )

    # get the endpoint on which we're currently listening - use first http/https if there are many
    if ($null -eq $Endpoint) {
        $Endpoint = @($PodeContext.Server.Endpoints.Values | Where-Object { $_.Protocol -iin @('http', 'https') -and $_.Default })[0]
        if ($null -eq $Endpoint) {
            $Endpoint = @($PodeContext.Server.Endpoints.Values | Where-Object { $_.Protocol -iin @('http', 'https') })[0]
        }
    }

    $url = $Endpoint.Url
    if ([string]::IsNullOrWhiteSpace($url)) {
        $url = "$($Endpoint.Protocol)://$($Endpoint.FriendlyName):$($Endpoint.Port)"
    }

    return $url
}

function Get-PodeDefaultPort {
    param(
        [Parameter()]
        [ValidateSet('Http', 'Https', 'Smtp', 'Smtps', 'Tcp', 'Tcps', 'Ws', 'Wss')]
        [string]
        $Protocol,

        [Parameter()]
        [ValidateSet('Implicit', 'Explicit')]
        [string]
        $TlsMode = 'Implicit',

        [switch]
        $Real
    )

    # are we after the real default ports?
    if ($Real) {
        return (@{
                Http  = @{ Implicit = 80 }
                Https = @{ Implicit = 443 }
                Smtp  = @{ Implicit = 25 }
                Smtps = @{ Implicit = 465; Explicit = 587 }
                Tcp   = @{ Implicit = 9001 }
                Tcps  = @{ Implicit = 9002; Explicit = 9003 }
                Ws    = @{ Implicit = 80 }
                Wss   = @{ Implicit = 443 }
            })[$Protocol.ToLowerInvariant()][$TlsMode.ToLowerInvariant()]
    }

    # if we running as iis, return the ASPNET port
    if ($PodeContext.Server.IsIIS) {
        return [int]$env:ASPNETCORE_PORT
    }

    # if we running as heroku, return the port
    if ($PodeContext.Server.IsHeroku) {
        return [int]$env:PORT
    }

    # otherwise, get the port for the protocol
    return (@{
            Http  = @{ Implicit = 8080 }
            Https = @{ Implicit = 8443 }
            Smtp  = @{ Implicit = 25 }
            Smtps = @{ Implicit = 465; Explicit = 587 }
            Tcp   = @{ Implicit = 9001 }
            Tcps  = @{ Implicit = 9002; Explicit = 9003 }
            Ws    = @{ Implicit = 9080 }
            Wss   = @{ Implicit = 9443 }
        })[$Protocol.ToLowerInvariant()][$TlsMode.ToLowerInvariant()]
}

function Set-PodeServerHeader {
    param(
        [Parameter()]
        [string]
        $Type,

        [switch]
        $AllowEmptyType
    )

    $name = 'Pode'
    if (![string]::IsNullOrWhiteSpace($Type) -or $AllowEmptyType) {
        $name += " - $($Type)"
    }

    Set-PodeHeader -Name 'Server' -Value $name
}

function Get-PodeHandler {
    param(
        [Parameter(Mandatory = $true)]
        [ValidateSet('Service', 'Smtp')]
        [string]
        $Type,

        [Parameter()]
        [string]
        $Name
    )

    if ([string]::IsNullOrWhiteSpace($Name)) {
        return $PodeContext.Server.Handlers[$Type]
    }

    return $PodeContext.Server.Handlers[$Type][$Name]
}

function Convert-PodeFileToScriptBlock {
    param(
        [Parameter(Mandatory = $true)]
        [Alias('FilePath')]
        [string]
        $Path
    )

    # resolve for relative path
    $Path = Get-PodeRelativePath -Path $Path -JoinRoot

    # if Path doesn't exist, error
    if (!(Test-PodePath -Path $Path -NoStatus)) {
        throw ($PodeLocale.pathNotExistExceptionMessage -f $Path) #  "The Path supplied does not exist: $($Path)"
    }

    # if the path is a wildcard or directory, error
    if (!(Test-PodePathIsFile -Path $Path -FailOnWildcard)) {
        throw ($PodeLocale.invalidPathWildcardOrDirectoryExceptionMessage -f $Path) # "The Path supplied cannot be a wildcard or a directory: $($Path)"
    }

    return ([scriptblock](Use-PodeScript -Path $Path))
}

function Convert-PodeQueryStringToHashTable {
    param(
        [Parameter()]
        [string]
        $Uri
    )

    if ([string]::IsNullOrWhiteSpace($Uri)) {
        return @{}
    }

    $qmIndex = $Uri.IndexOf('?')
    if ($qmIndex -eq -1) {
        return @{}
    }

    if ($qmIndex -gt 0) {
        $Uri = $Uri.Substring($qmIndex)
    }

    $tmpQuery = [System.Web.HttpUtility]::ParseQueryString($Uri)
    return (ConvertFrom-PodeNameValueToHashTable -Collection $tmpQuery)
}

function Get-PodeAstFromFile {
    param(
        [Parameter(Mandatory = $true)]
        [Alias('FilePath')]
        [string]
        $Path
    )

    if (!(Test-Path $Path)) {
        throw ($PodeLocale.pathNotExistExceptionMessage -f $Path) #  "The Path supplied does not exist: $($Path)"
    }

    return [System.Management.Automation.Language.Parser]::ParseFile($Path, [ref]$null, [ref]$null)
}

function Get-PodeFunctionsFromFile {
    param(
        [Parameter(Mandatory = $true)]
        [string]
        $FilePath
    )

    $ast = Get-PodeAstFromFile -FilePath $FilePath
    return @(Get-PodeFunctionsFromAst -Ast $ast)
}

function Get-PodeFunctionsFromAst {
    param(
        [Parameter(Mandatory = $true)]
        [System.Management.Automation.Language.Ast]
        $Ast
    )

    $funcs = @(($Ast.FindAll({ $args[0] -is [System.Management.Automation.Language.FunctionDefinitionAst] }, $false)))

    return @(foreach ($func in $funcs) {
            # skip null
            if ($null -eq $func) {
                continue
            }

            # skip pode funcs
            if ($func.Name -ilike '*-Pode*') {
                continue
            }

            # definition
            $def = "$($func.Body)".Trim('{}').Trim()
            if (($null -ne $func.Parameters) -and ($func.Parameters.Count -gt 0)) {
                $def = "param($($func.Parameters.Name -join ','))`n$($def)"
            }

            # the found func
            @{
                Name       = $func.Name
                Definition = $def
            }
        })
}

function Get-PodeFunctionsFromScriptBlock {
    param(
        [Parameter(Mandatory = $true)]
        [scriptblock]
        $ScriptBlock
    )

    # functions that have been found
    $foundFuncs = @()

    # get each function in the callstack
    $callstack = Get-PSCallStack
    if ($callstack.Count -gt 3) {
        $callstack = ($callstack | Select-Object -Skip 4)
        $bindingFlags = [System.Reflection.BindingFlags]'NonPublic, Instance, Static'

        foreach ($call in $callstack) {
            $_funcContext = $call.GetType().GetProperty('FunctionContext', $bindingFlags).GetValue($call, $null)
            $_scriptBlock = $_funcContext.GetType().GetField('_scriptBlock', $bindingFlags).GetValue($_funcContext)
            $foundFuncs += @(Get-PodeFunctionsFromAst -Ast $_scriptBlock.Ast)
        }
    }

    # get each function from the main script
    $foundFuncs += @(Get-PodeFunctionsFromAst -Ast $ScriptBlock.Ast)

    # return the found functions
    return $foundFuncs
}

<#
.SYNOPSIS
    Reads details from a web exception and returns relevant information.

.DESCRIPTION
    The `Read-PodeWebExceptionInfo` function processes a web exception (either `WebException` or `HttpRequestException`)
    and extracts relevant details such as status code, status description, and response body.

.PARAMETER ErrorRecord
    Specifies the error record containing the web exception. This parameter is mandatory.

.OUTPUTS
    Returns a hashtable with the following keys:
    - `Status`: A nested hashtable with `Code` (status code) and `Description` (status description).
    - `Body`: The response body from the web exception.

.EXAMPLE
    # Example usage:
    $errorRecord = Get-ErrorRecordFromWebException
    $details = Read-PodeWebExceptionInfo -ErrorRecord $errorRecord
    # Returns a hashtable with status code, description, and response body.

.NOTES
    This is an internal function and may change in future releases of Pode
#>
function Read-PodeWebExceptionInfo {
    [CmdletBinding()]
    [OutputType([hashtable])]
    param(
        [Parameter(Mandatory = $true)]
        [System.Management.Automation.ErrorRecord]
        $ErrorRecord
    )

    switch ($ErrorRecord) {
        { $_.Exception -is [System.Net.WebException] } {
            $stream = $_.Exception.Response.GetResponseStream()
            $stream.Position = 0

            $body = [System.IO.StreamReader]::new($stream).ReadToEnd()
            $code = [int]$_.Exception.Response.StatusCode
            $desc = $_.Exception.Response.StatusDescription
        }

        { $_.Exception -is [System.Net.Http.HttpRequestException] } {
            $body = $_.ErrorDetails.Message
            $code = [int]$_.Exception.Response.StatusCode
            $desc = $_.Exception.Response.ReasonPhrase
        }

        default {
            #Exception is of an invalid type, should be either WebException or HttpRequestException
            throw ($PodeLocale.invalidWebExceptionTypeExceptionMessage -f ($_.Exception.GetType().Name))
        }
    }

    return @{
        Status = @{
            Code        = $code
            Description = $desc
        }
        Body   = $body
    }
}

function Use-PodeFolder {
    param(
        [Parameter()]
        [string]
        $Path,

        [Parameter(Mandatory = $true)]
        [string]
        $DefaultPath
    )

    # use default, or custom path
    if ([string]::IsNullOrWhiteSpace($Path)) {
        $Path = Join-PodeServerRoot -Folder $DefaultPath
    }
    else {
        $Path = Get-PodeRelativePath -Path $Path -JoinRoot
    }

    # fail if path not found
    if (!(Test-PodePath -Path $Path -NoStatus)) {
        throw ($PodeLocale.pathToLoadNotFoundExceptionMessage -f $DefaultPath, $Path) #"Path to load $($DefaultPath) not found: $($Path)"
    }

    # get .ps1 files and load them
    Get-ChildItem -Path $Path -Filter *.ps1 -Force -Recurse | ForEach-Object {
        Use-PodeScript -Path $_.FullName
    }
}

function Find-PodeModuleFile {
    param(
        [Parameter(Mandatory = $true, ParameterSetName = 'Name')]
        [string]
        $Name,

        [Parameter(Mandatory = $true, ParameterSetName = 'Module')]
        [psmoduleinfo]
        $Module,

        [switch]
        $ListAvailable,

        [switch]
        $DataOnly,

        [switch]
        $CheckVersion
    )

    # get module and check psd1, then psm1
    if ($null -eq $Module) {
        $Module = (Get-Module -Name $Name -ListAvailable:$ListAvailable | Sort-Object -Property Version -Descending | Select-Object -First 1)
    }

    # if the path isn't already a psd1 do this
    $path = Join-Path $Module.ModuleBase "$($Module.Name).psd1"
    if (!(Test-Path $path)) {
        # if we only want a psd1, return null
        if ($DataOnly) {
            $path = $null
        }
        else {
            $path = $Module.Path
        }
    }

    # check the Version of the psd1
    elseif ($CheckVersion) {
        $data = Import-PowerShellDataFile -Path $path -ErrorAction Stop

        $version = $null
        if (![version]::TryParse($data.ModuleVersion, [ref]$version)) {
            if ($DataOnly) {
                $path = $null
            }
            else {
                $path = $Module.Path
            }
        }
    }

    return $path
}

<#
.SYNOPSIS
    Clears the inner keys of a hashtable.

.DESCRIPTION
    This function takes a hashtable as input and clears the values associated with each inner key. If the input hashtable is empty or null, no action is taken.

.PARAMETER InputObject
    The hashtable to process.

.EXAMPLE
    $myHashtable = @{
        'Key1' = 'Value1'
        'Key2' = 'Value2'
    }
    Clear-PodeHashtableInnerKey -InputObject $myHashtable
    # Clears the values associated with 'Key1' and 'Key2' in the hashtable.

.NOTES
    This is an internal function and may change in future releases of Pode.
#>
function Clear-PodeHashtableInnerKey {
    param(
        [Parameter()]
        [hashtable]
        $InputObject
    )

    if (Test-PodeIsEmpty $InputObject) {
        return
    }

    $InputObject.Keys.Clone() | ForEach-Object {
        $InputObject[$_].Clear()
    }
}

function Set-PodeCronInterval {
    param(
        [Parameter()]
        [hashtable]
        $Cron,

        [Parameter()]
        [string]
        $Type,

        [Parameter()]
        [int[]]
        $Value,

        [Parameter()]
        [int]
        $Interval
    )

    if ($Interval -le 0) {
        return $false
    }

    if ($Value.Length -gt 1) {
        throw ($PodeLocale.singleValueForIntervalExceptionMessage -f $Type) #"You can only supply a single $($Type) value when using intervals"
    }

    if ($Value.Length -eq 1) {
        $Cron[$Type] = "$(@($Value)[0])"
    }

    $Cron[$Type] += "/$($Interval)"
    return ($Value.Length -eq 1)
}

function Test-PodeModuleInstalled {
    param(
        [Parameter(Mandatory = $true)]
        [string]
        $Name
    )

    return ($null -ne (Get-Module -Name $Name -ListAvailable -ErrorAction Ignore -Verbose:$false))
}

function Get-PodePlaceholderRegex {
    return '\:(?<tag>[\w]+)'
}

<#
.SYNOPSIS
    Resolves placeholders in a given path using a specified regex pattern.

.DESCRIPTION
    The `Resolve-PodePlaceholder` function replaces placeholders in the provided path
    with custom placeholders based on the specified regex pattern. You can customize
    the prepend and append strings for the new placeholders. Additionally, you can
    choose to escape slashes in the path.

.PARAMETER Path
    Specifies the path to resolve. This parameter is mandatory.

.PARAMETER Pattern
    Specifies the regex pattern for identifying placeholders. If not provided, the default
    placeholder regex pattern from `Get-PodePlaceholderRegex` is used.

.PARAMETER Prepend
    Specifies the string to prepend to the new placeholders. Default is '(?<'.

.PARAMETER Append
    Specifies the string to append to the new placeholders. Default is '>[^\/]+?)'.

.PARAMETER Slashes
    If specified, escapes slashes in the path.

.OUTPUTS
    Returns the resolved path with replaced placeholders.

.EXAMPLE
    # Example usage:
    $originalPath = '/api/users/{id}'
    $resolvedPath = Resolve-PodePlaceholder -Path $originalPath
    # Returns '/api/users/(?<id>[^\/]+?)' with custom placeholders.

.NOTES
    This is an internal function and may change in future releases of Pode.
#>
function Resolve-PodePlaceholder {
    param(
        [Parameter(Mandatory = $true)]
        [string]
        $Path,

        [Parameter()]
        [string]
        $Pattern,

        [Parameter()]
        [string]
        $Prepend = '(?<',

        [Parameter()]
        [string]
        $Append = '>[^\/]+?)',

        [switch]
        $Slashes
    )

    if ([string]::IsNullOrWhiteSpace($Pattern)) {
        $Pattern = Get-PodePlaceholderRegex
    }

    if ($Path -imatch $Pattern) {
        $Path = [regex]::Escape($Path)
    }

    if ($Slashes) {
        $Path = ($Path.TrimEnd('\/') -replace '(\\\\|\/)', '[\\\/]')
        $Path = "$($Path)[\\\/]"
    }

    return (Convert-PodePlaceholder -Path $Path -Pattern $Pattern -Prepend $Prepend -Append $Append)
}

<#
.SYNOPSIS
    Converts placeholders in a given path using a specified regex pattern.

.DESCRIPTION
    The `Convert-PodePlaceholder` function replaces placeholders in the provided path
    with custom placeholders based on the specified regex pattern. You can customize
    the prepend and append strings for the new placeholders.

.PARAMETER Path
    Specifies the path to convert. This parameter is mandatory.

.PARAMETER Pattern
    Specifies the regex pattern for identifying placeholders. If not provided, the default
    placeholder regex pattern from `Get-PodePlaceholderRegex` is used.

.PARAMETER Prepend
    Specifies the string to prepend to the new placeholders. Default is '(?<'.

.PARAMETER Append
    Specifies the string to append to the new placeholders. Default is '>[^\/]+?)'.

.OUTPUTS
    Returns the path with replaced placeholders.

.EXAMPLE
    # Example usage:
    $originalPath = '/api/users/{id}'
    $convertedPath = Convert-PodePlaceholder -Path $originalPath
    # Returns '/api/users/(?<id>[^\/]+?)' with custom placeholders.

.NOTES
    This is an internal function and may change in future releases of Pode.
#>
function Convert-PodePlaceholder {
    [CmdletBinding()]
    [OutputType([string])]
    param(
        [Parameter(Mandatory = $true)]
        [string]
        $Path,

        [Parameter()]
        [string]
        $Pattern,

        [Parameter()]
        [string]
        $Prepend = '(?<',

        [Parameter()]
        [string]
        $Append = '>[^\/]+?)'
    )

    if ([string]::IsNullOrWhiteSpace($Pattern)) {
        $Pattern = Get-PodePlaceholderRegex
    }

    while ($Path -imatch $Pattern) {
        $Path = ($Path -ireplace $Matches[0], "$($Prepend)$($Matches['tag'])$($Append)")
    }

    return $Path
}

<#
.SYNOPSIS
    Tests whether a given path contains a placeholder based on a specified regex pattern.

.DESCRIPTION
    The `Test-PodePlaceholder` function checks if the provided path contains a placeholder
    by matching it against a regex pattern. Placeholders are typically used for dynamic values.

.PARAMETER Path
    Specifies the path to test. This parameter is mandatory.

.PARAMETER Placeholder
    Specifies the regex pattern for identifying placeholders. If not provided, the default
    placeholder regex pattern from `Get-PodePlaceholderRegex` is used.

.OUTPUTS
    Returns `$true` if the path contains a placeholder; otherwise, returns `$false`.

.EXAMPLE
    # Example usage:
    $isPlaceholder = Test-PodePlaceholder -Path '/api/users/{id}'
    # Returns $true because the path contains a placeholder.

.NOTES
    This is an internal function and may change in future releases of Pode.
#>
function Test-PodePlaceholder {
    param(
        [Parameter(Mandatory = $true)]
        [string]
        $Path,

        [Parameter()]
        [string]
        $Placeholder
    )

    if ([string]::IsNullOrWhiteSpace($Placeholder)) {
        $Placeholder = Get-PodePlaceholderRegex
    }

    return ($Path -imatch $Placeholder)
}


<#
.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 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) {
        # [WARNING] Pode version has not been tested on PowerShell version, as it is EOL
        Write-PodeHost ($PodeLocale.eolPowerShellWarningMessage -f $PodeVersion, $PSVersion) -ForegroundColor Yellow
    }

    $SupportedVersions = $moduleManifest.PrivateData.PwshVersions.Supported -split ','
    $isSupported = "$($psVersion.Major).$($psVersion.Minor)" -in $SupportedVersions

    if ((! $isSupported) -and (! $isEol) -and $ReportUntested) {
        # [WARNING] Pode version has not been tested on PowerShell version, as it was not available when Pode was released
        Write-PodeHost ($PodeLocale.untestedPowerShellVersionWarningMessage -f $PodeVersion, $PSVersion) -ForegroundColor Yellow
    }

    return @{
        eol       = $isEol
        supported = $isSupported
    }
}


<#
.SYNOPSIS
    creates a YAML description of the data in the object - based on https://github.com/Phil-Factor/PSYaml

.DESCRIPTION
    This produces YAML from any object you pass to it.

.PARAMETER Object
    The object that you want scripted out. This parameter accepts input via the pipeline.

.PARAMETER Depth
    The depth that you want your object scripted to

.EXAMPLE
    Get-PodeOpenApiDefinition|ConvertTo-PodeYaml
#>
function ConvertTo-PodeYaml {
    [CmdletBinding()]
    [OutputType([string])]
    param (
        [parameter(Position = 0, Mandatory = $true, ValueFromPipeline = $true)]
        [AllowNull()]
        $InputObject,

        [parameter()]
        [int]
        $Depth = 16
    )

    begin {
        $pipelineObject = @()
    }

    process {
        $pipelineObject += $_
    }

    end {
        if ($pipelineObject.Count -gt 1) {
            $InputObject = $pipelineObject
        }

        if ($PodeContext.Server.Web.OpenApi.UsePodeYamlInternal) {
            return ConvertTo-PodeYamlInternal -InputObject $InputObject -Depth $Depth -NoNewLine
        }

        if ($null -eq $PodeContext.Server.InternalCache.YamlModuleImported) {
            $PodeContext.Server.InternalCache.YamlModuleImported = ((Test-PodeModuleInstalled -Name 'PSYaml') -or (Test-PodeModuleInstalled -Name 'powershell-yaml'))
        }

        if ($PodeContext.Server.InternalCache.YamlModuleImported) {
            return ($InputObject | ConvertTo-Yaml)
        }
        else {
            return ConvertTo-PodeYamlInternal -InputObject $InputObject -Depth $Depth -NoNewLine
        }
    }
}

<#
.SYNOPSIS
    Converts PowerShell objects into a YAML-formatted string.

.DESCRIPTION
    This function takes PowerShell objects and converts them to a YAML string representation.
    It supports various data types including arrays, hashtables, strings, and more.
    The depth of conversion can be controlled, allowing for nested objects to be accurately represented.

.PARAMETER InputObject
    The PowerShell object to convert to YAML.

.PARAMETER Depth
    Specifies the maximum depth of object nesting to convert. Default is 10 levels deep.

.PARAMETER NestingLevel
    Used internally to track the current depth of recursion. Generally not specified by the user.

.PARAMETER NoNewLine
    If specified, suppresses the newline characters in the output to create a single-line string.

.OUTPUTS
    System.String. Returns a string in YAML format.

.EXAMPLE
    ConvertTo-PodeYamlInternal -InputObject $object

    Converts the object into a YAML string.

.NOTES
    This is an internal function and may change in future releases of Pode.
    It converts only basic PowerShell types, such as strings, integers, booleans, arrays, hashtables, and ordered dictionaries into a YAML format.

#>
function ConvertTo-PodeYamlInternal {
    [CmdletBinding()]
    [OutputType([string])]
    param (
        [parameter(Mandatory = $true)]
        [AllowNull()]
        $InputObject,

        [parameter()]
        [int]
        $Depth = 10,

        [parameter()]
        [int]
        $NestingLevel = 0,

        [parameter()]
        [switch]
        $NoNewLine
    )

    #report the leaves in terms of object type
    if ($Depth -ilt $NestingLevel) {
        return ''
    }
    # if it is null return null
    If ( !($InputObject) ) {
        if ($InputObject -is [Object[]]) {
            return '[]'
        }
        else {
            return ''
        }
    }

    $padding = [string]::new(' ', $NestingLevel * 2) # lets just create our left-padding for the block
    try {
        $Type = $InputObject.GetType().Name # we start by getting the object's type
        if ($InputObject -is [object[]]) {
            #what it really is
            $Type = "$($InputObject.GetType().BaseType.Name)"
        }

        # Check for specific value types string
        if ($Type -ne 'String') {
            # prevent these values being identified as an object
            if ($InputObject -is [System.Collections.Specialized.OrderedDictionary]) {
                $Type = 'hashTable'
            }
            elseif ($Type -ieq 'List`1') {
                $Type = 'array'
            }
            elseif ($InputObject -is [array]) {
                $Type = 'array'
            } # whatever it thinks it is called
            elseif ($InputObject -is [hashtable] ) {
                $Type = 'hashTable'
            } # for our purposes it is a hashtable
        }

        $output += switch ($Type.ToLower()) {
            'string' {
                $String = "$InputObject"
                if (($string -match '[\r\n]' -or $string.Length -gt 80) -and ($string -notlike 'http*')) {
                    $multiline = [System.Text.StringBuilder]::new("|`n")

                    $items = $string.Split("`n")
                    for ($i = 0; $i -lt $items.Length; $i++) {
                        $workingString = $items[$i] -replace '\r$'
                        $length = $workingString.Length
                        $index = 0
                        $wrap = 80

                        while ($index -lt $length) {
                            $breakpoint = $wrap
                            $linebreak = $false

                            if (($length - $index) -gt $wrap) {
                                $lastSpaceIndex = $workingString.LastIndexOf(' ', $index + $wrap, $wrap)
                                if ($lastSpaceIndex -ne -1) {
                                    $breakpoint = $lastSpaceIndex - $index
                                }
                                else {
                                    $linebreak = $true
                                    $breakpoint--
                                }
                            }
                            else {
                                $breakpoint = $length - $index
                            }

                            $null = $multiline.Append($padding).Append($workingString.Substring($index, $breakpoint).Trim())
                            if ($linebreak) {
                                $null = $multiline.Append('\')
                            }

                            $index += $breakpoint
                            if ($index -lt $length) {
                                $null = $multiline.Append([System.Environment]::NewLine)
                            }
                        }

                        if ($i -lt ($items.Length - 1)) {
                            $null = $multiline.Append([System.Environment]::NewLine)
                        }
                    }

                    $multiline.ToString().TrimEnd()
                    break
                }
                else {
                    if ($string -match '^[#\[\]@\{\}\!\*]') {
                        "'$($string -replace '''', '''''')'"
                    }
                    else {
                        $string
                    }
                    break
                }
                break
            }

            'hashtable' {
                if ($InputObject.Count -gt 0 ) {
                    $index = 0
                    $string = [System.Text.StringBuilder]::new()
                    foreach ($item in $InputObject.Keys) {
                        if ($NoNewLine -and $index++ -eq 0) { $NewPadding = '' } else { $NewPadding = "`n$padding" }
                        $null = $string.Append( $NewPadding).Append( $item).Append(': ')
                        if ($InputObject[$item] -is [System.ValueType]) {
                            if ($InputObject[$item] -is [bool]) {
                                $null = $string.Append($InputObject[$item].ToString().ToLower())
                            }
                            else {
                                $null = $string.Append($InputObject[$item])
                            }
                        }
                        else {
                            if ($InputObject[$item] -is [string]) { $increment = 2 } else { $increment = 1 }
                            $null = $string.Append((ConvertTo-PodeYamlInternal -InputObject $InputObject[$item] -Depth $Depth -NestingLevel ($NestingLevel + $increment)))
                        }
                    }
                    $string.ToString()
                }
                else { '{}' }
                break
            }

            'pscustomobject' {
                if ($InputObject.Count -gt 0 ) {
                    $index = 0
                    $string = [System.Text.StringBuilder]::new()
                    foreach ($item in ($InputObject | Get-Member -MemberType Properties | Select-Object -ExpandProperty Name)) {
                        if ($NoNewLine -and $index++ -eq 0) { $NewPadding = '' } else { $NewPadding = "`n$padding" }
                        $null = $string.Append( $NewPadding).Append( $item).Append(': ')
                        if ($InputObject.$item -is [System.ValueType]) {
                            if ($InputObject.$item -is [bool]) {
                                $null = $string.Append($InputObject.$item.ToString().ToLower())
                            }
                            else {
                                $null = $string.Append($InputObject.$item)
                            }
                        }
                        else {
                            if ($InputObject.$item -is [string]) { $increment = 2 } else { $increment = 1 }
                            $null = $string.Append((ConvertTo-PodeYamlInternal -InputObject $InputObject.$item -Depth $Depth -NestingLevel ($NestingLevel + $increment)))
                        }
                    }
                    $string.ToString()
                }
                else { '{}' }
                break
            }

            'array' {
                $string = [System.Text.StringBuilder]::new()
                $index = 0
                foreach ($item in $InputObject ) {
                    if ($NoNewLine -and $index++ -eq 0) { $NewPadding = '' } else { $NewPadding = "`n$padding" }
                    $null = $string.Append($NewPadding).Append('- ').Append((ConvertTo-PodeYamlInternal -InputObject $item -depth $Depth -NestingLevel ($NestingLevel + 1) -NoNewLine))
                }
                $string.ToString()
                break
            }

            default {
                "'$InputObject'"
            }
        }
        return $Output
    }
    catch {
        $_ | Write-PodeErrorLog
        $_.Exception | Write-PodeErrorLog -CheckInnerException
        throw ($PodeLocale.scriptErrorExceptionMessage -f $_, $_.InvocationInfo.ScriptName, $_.InvocationInfo.Line.Trim(), $_.InvocationInfo.ScriptLineNumber, $_.InvocationInfo.OffsetInLine, $_.InvocationInfo.MyCommand, $type, $InputObject, $InputObject.GetType().Name, $InputObject.GetType().BaseType.Name)
    }
}


<#
.SYNOPSIS
    Resolves various types of object arrays into PowerShell objects.

.DESCRIPTION
    This function takes an input property and determines its type.
    It then resolves the property into a PowerShell object or an array of objects,
    depending on whether the property is a hashtable, array, or single object.

.PARAMETER Property
    The property to be resolved. It can be a hashtable, an object array, or a single object.

.RETURNS
    Returns a PowerShell object or an array of PowerShell objects, depending on the input property type.

.EXAMPLE
    $result = Resolve-PodeObjectArray -Property $myProperty
    This example resolves the $myProperty into a PowerShell object or an array of objects.

.NOTES
    This is an internal function and may change in future releases of Pode.
#>
function Resolve-PodeObjectArray {
    [CmdletBinding()]
    [OutputType([object[]])]
    [OutputType([psobject])]
    param (
        [AllowNull()]
        [object]
        $Property
    )

    # Check if the property is a hashtable
    if ($Property -is [hashtable]) {
        # If the hashtable has only one item, convert it to a PowerShell object
        if ($Property.Count -eq 1) {
            return [pscustomobject]$Property
        }
        else {
            # If the hashtable has more than one item, recursively resolve each item
            return @(foreach ($p in $Property) {
                    Resolve-PodeObjectArray -Property $p
                })
        }
    }
    # Check if the property is an array of objects
    elseif ($Property -is [object[]]) {
        # Recursively resolve each item in the array
        return @(foreach ($p in $Property) {
                Resolve-PodeObjectArray -Property $p
            })
    }
    # Check if the property is already a PowerShell object
    elseif ($Property -is [psobject]) {
        return $Property
    }
    else {
        # For any other type, convert it to a PowerShell object
        return [pscustomobject]$Property
    }
}

<#
.SYNOPSIS
    Creates a deep clone of a PSObject by serializing and deserializing the object.

.DESCRIPTION
    The Copy-PodeObjectDeepClone function takes a PSObject as input and creates a deep clone of it.
    This is achieved by serializing the object using the PSSerializer class, and then
    deserializing it back into a new instance. This method ensures that nested objects, arrays,
    and other complex structures are copied fully, without sharing references between the original
    and the cloned object.

.PARAMETER InputObject
    The PSObject that you want to deep clone. This object will be serialized and then deserialized
    to create a deep copy.

.PARAMETER Depth
    Specifies the depth for the serialization. The depth controls how deeply nested objects
    and properties are serialized. The default value is 10.

.INPUTS
    [PSObject] - The function accepts a PSObject to deep clone.

.OUTPUTS
    [PSObject] - The function returns a new PSObject that is a deep clone of the original.

.EXAMPLE
    $originalObject = [PSCustomObject]@{
        Name = 'John Doe'
        Age = 30
        Address = [PSCustomObject]@{
            Street = '123 Main St'
            City = 'Anytown'
            Zip = '12345'
        }
    }

    $clonedObject = $originalObject | Copy-PodeObjectDeepClone -Deep 15

    # The $clonedObject is now a deep clone of $originalObject.
    # Changes to $clonedObject will not affect $originalObject and vice versa.

.NOTES
    This function uses the System.Management.Automation.PSSerializer class, which is available in
    PowerShell 5.1 and later versions. The default depth parameter is set to 10 to handle nested
    objects appropriately, but it can be customized via the -Deep parameter.
    This is an internal function and may change in future releases of Pode.
#>
function Copy-PodeObjectDeepClone {
    param (
        [Parameter(Mandatory, ValueFromPipeline)]
        [PSObject]$InputObject,

        [Parameter()]
        [int]$Depth = 10
    )

    process {
        # Serialize the object to XML format using PSSerializer
        # The depth parameter controls how deeply nested objects are serialized
        $xmlSerializer = [System.Management.Automation.PSSerializer]::Serialize($InputObject, $Depth)

        # Deserialize the XML back into a new PSObject, creating a deep clone of the original
        return [System.Management.Automation.PSSerializer]::Deserialize($xmlSerializer)
    }
}
src\Private\Logging.ps1
function Get-PodeLoggingTerminalMethod {
    return {
        param($item, $options)

        if ($PodeContext.Server.Quiet) {
            return
        }

        # check if it's an array from batching
        if ($item -is [array]) {
            $item = ($item -join [System.Environment]::NewLine)
        }

        # protect then write
        $item = ($item | Protect-PodeLogItem)
        $item.ToString() | Out-PodeHost
    }
}

function Get-PodeLoggingFileMethod {
    return {
        param($item, $options)

        # check if it's an array from batching
        if ($item -is [array]) {
            $item = ($item -join [System.Environment]::NewLine)
        }

        # mask values
        $item = ($item | Protect-PodeLogItem)

        # variables
        $date = [DateTime]::Now.ToString('yyyy-MM-dd')

        # do we need to reset the fileId?
        if ($options.Date -ine $date) {
            $options.Date = $date
            $options.FileId = 0
        }

        # get the fileId
        if ($options.FileId -eq 0) {
            $path = [System.IO.Path]::Combine($options.Path, "$($options.Name)_$($date)_*.log")
            $options.FileId = (@(Get-ChildItem -Path $path)).Length
            if ($options.FileId -eq 0) {
                $options.FileId = 1
            }
        }

        $id = "$($options.FileId)".PadLeft(3, '0')
        if ($options.MaxSize -gt 0) {
            $path = [System.IO.Path]::Combine($options.Path, "$($options.Name)_$($date)_$($id).log")
            if ((Get-Item -Path $path -Force).Length -ge $options.MaxSize) {
                $options.FileId++
                $id = "$($options.FileId)".PadLeft(3, '0')
            }
        }

        # get the file to write to
        $path = [System.IO.Path]::Combine($options.Path, "$($options.Name)_$($date)_$($id).log")

        # write the item to the file
        $item.ToString() | Out-File -FilePath $path -Encoding utf8 -Append -Force

        # if set, remove log files beyond days set (ensure this is only run once a day)
        if (($options.MaxDays -gt 0) -and ($options.NextClearDown -lt [DateTime]::Now.Date)) {
            $date = [DateTime]::Now.Date.AddDays(-$options.MaxDays)

            $null = Get-ChildItem -Path $options.Path -Filter '*.log' -Force |
                Where-Object { $_.CreationTime -lt $date } |
                Remove-Item $_ -Force

            $options.NextClearDown = [DateTime]::Now.Date.AddDays(1)
        }
    }
}

function Get-PodeLoggingEventViewerMethod {
    return {
        param($item, $options, $rawItem)

        if ($item -isnot [array]) {
            $item = @($item)
        }

        if ($rawItem -isnot [array]) {
            $rawItem = @($rawItem)
        }

        for ($i = 0; $i -lt $item.Length; $i++) {
            # convert log level - info if no level present
            $entryType = ConvertTo-PodeEventViewerLevel -Level $rawItem[$i].Level

            # create log instance
            $entryInstance = [System.Diagnostics.EventInstance]::new($options.ID, 0, $entryType)

            # create event log
            $entryLog = [System.Diagnostics.EventLog]::new()
            $entryLog.Log = $options.LogName
            $entryLog.Source = $options.Source

            try {
                $message = ($item[$i] | Protect-PodeLogItem)
                $entryLog.WriteEvent($entryInstance, $message)
            }
            catch {
                $_ | Write-PodeErrorLog -Level Debug
            }
        }
    }
}

function ConvertTo-PodeEventViewerLevel {
    param(
        [Parameter()]
        [string]
        $Level
    )

    if ([string]::IsNullOrWhiteSpace($Level)) {
        return [System.Diagnostics.EventLogEntryType]::Information
    }

    if ($Level -ieq 'error') {
        return [System.Diagnostics.EventLogEntryType]::Error
    }

    if ($Level -ieq 'warning') {
        return [System.Diagnostics.EventLogEntryType]::Warning
    }

    return [System.Diagnostics.EventLogEntryType]::Information
}

function Get-PodeLoggingInbuiltType {
    param(
        [Parameter(Mandatory = $true)]
        [ValidateSet('Errors', 'Requests')]
        [string]
        $Type
    )

    switch ($Type.ToLowerInvariant()) {
        'requests' {
            $script = {
                param($item, $options)

                # just return the item if Raw is set
                if ($options.Raw) {
                    return $item
                }

                function sg($value) {
                    if ([string]::IsNullOrWhiteSpace($value)) {
                        return '-'
                    }

                    return $value
                }

                # build the url with http method
                $url = "$(sg $item.Request.Method) $(sg $item.Request.Resource) $(sg $item.Request.Protocol)"

                # build and return the request row
                return "$(sg $item.Host) $(sg $item.RfcUserIdentity) $(sg $item.User) [$(sg $item.Date)] `"$($url)`" $(sg $item.Response.StatusCode) $(sg $item.Response.Size) `"$(sg $item.Request.Referrer)`" `"$(sg $item.Request.Agent)`""
            }
        }

        'errors' {
            $script = {
                param($item, $options)

                # do nothing if the error level isn't present
                if (@($options.Levels) -inotcontains $item.Level) {
                    return
                }

                # just return the item if Raw is set
                if ($options.Raw) {
                    return $item
                }

                # build the exception details
                $row = @(
                    "Date: $($item.Date.ToString('yyyy-MM-dd HH:mm:ss'))",
                    "Level: $($item.Level)",
                    "ThreadId: $($item.ThreadId)",
                    "Server: $($item.Server)",
                    "Category: $($item.Category)",
                    "Message: $($item.Message)",
                    "StackTrace: $($item.StackTrace)"
                )

                # join the details and return
                return "$($row -join "`n")`n"
            }
        }
    }

    return $script
}

function Get-PodeRequestLoggingName {
    return '__pode_log_requests__'
}

function Get-PodeErrorLoggingName {
    return '__pode_log_errors__'
}

<#
.SYNOPSIS
    Retrieves a Pode logger by name.

.DESCRIPTION
    This function allows you to retrieve a Pode logger by specifying its name. It returns the logger object associated with the given name.

.PARAMETER Name
    The name of the Pode logger to retrieve.

.OUTPUTS
    A Pode logger object.

.NOTES
    This is an internal function and may change in future releases of Pode.
#>
function Get-PodeLogger {
    [CmdletBinding()]
    [OutputType([hashtable])]
    param(
        [Parameter(Mandatory = $true)]
        [string]
        $Name
    )

    return $PodeContext.Server.Logging.Types[$Name]
}

function Test-PodeLoggerEnabled {
    param(
        [Parameter(Mandatory = $true)]
        [string]
        $Name
    )

    return ($PodeContext.Server.Logging.Enabled -and $PodeContext.Server.Logging.Types.ContainsKey($Name))
}

<#
.SYNOPSIS
    Gets the error logging levels for Pode.

.DESCRIPTION
    This function retrieves the error logging levels configured for Pode. It returns an array of available error levels.

.PARAMETER Name
    The name of the Pode logger to retrieve.

.OUTPUTS
    An array of error logging levels.

.NOTES
    This is an internal function and may change in future releases of Pode.
#>
function Get-PodeErrorLoggingLevel {
    return (Get-PodeLogger -Name (Get-PodeErrorLoggingName)).Arguments.Levels
}

function Test-PodeErrorLoggingEnabled {
    return (Test-PodeLoggerEnabled -Name (Get-PodeErrorLoggingName))
}

function Test-PodeRequestLoggingEnabled {
    return (Test-PodeLoggerEnabled -Name (Get-PodeRequestLoggingName))
}

function Write-PodeRequestLog {
    param(
        [Parameter(Mandatory = $true)]
        $Request,

        [Parameter(Mandatory = $true)]
        $Response,

        [Parameter()]
        [string]
        $Path
    )

    # do nothing if logging is disabled, or request logging isn't setup
    $name = Get-PodeRequestLoggingName
    if (!(Test-PodeLoggerEnabled -Name $name)) {
        return
    }

    # build a request object
    $item = @{
        Host            = $Request.RemoteEndPoint.Address.IPAddressToString
        RfcUserIdentity = '-'
        User            = '-'
        Date            = [DateTime]::Now.ToString('dd/MMM/yyyy:HH:mm:ss zzz')
        Request         = @{
            Method   = $Request.HttpMethod.ToUpperInvariant()
            Resource = $Path
            Protocol = "HTTP/$($Request.ProtocolVersion)"
            Referrer = $Request.UrlReferrer
            Agent    = $Request.UserAgent
        }
        Response        = @{
            StatusCode        = $Response.StatusCode
            StatusDescription = $Response.StatusDescription
            Size              = '-'
        }
    }

    # set size if >0
    if ($Response.ContentLength64 -gt 0) {
        $item.Response.Size = $Response.ContentLength64
    }

    # set username - dot spaces
    if (Test-PodeAuthUser -IgnoreSession) {
        $userProps = (Get-PodeLogger -Name $name).Properties.Username.Split('.')

        $user = $WebEvent.Auth.User
        foreach ($atom in $userProps) {
            $user = $user.($atom)
        }

        if (![string]::IsNullOrWhiteSpace($user)) {
            $item.User = $user -ireplace '\s+', '.'
        }
    }

    # add the item to be processed
    $null = $PodeContext.LogsToProcess.Add(@{
            Name = $name
            Item = $item
        })
}

function Add-PodeRequestLogEndware {
    param(
        [Parameter(Mandatory = $true)]
        [ValidateNotNull()]
        $WebEvent
    )

    # do nothing if logging is disabled, or request logging isn't setup
    $name = Get-PodeRequestLoggingName
    if (!(Test-PodeLoggerEnabled -Name $name)) {
        return
    }

    # add the request logging endware
    $WebEvent.OnEnd += @{
        Logic = {
            Write-PodeRequestLog -Request $WebEvent.Request -Response $WebEvent.Response -Path $WebEvent.Path
        }
    }
}

function Test-PodeLoggersExist {
    if (($null -eq $PodeContext.Server.Logging) -or ($null -eq $PodeContext.Server.Logging.Types)) {
        return $false
    }

    return (($PodeContext.Server.Logging.Types.Count -gt 0) -or ($PodeContext.Server.Logging.Enabled))
}

function Start-PodeLoggingRunspace {
    # skip if there are no loggers configured, or logging is disabled
    if (!(Test-PodeLoggersExist)) {
        return
    }

    $script = {
        try {
            while (!$PodeContext.Tokens.Cancellation.IsCancellationRequested) {
                try {
                    # if there are no logs to process, just sleep for a few seconds - but after checking the batch
                    if ($PodeContext.LogsToProcess.Count -eq 0) {
                        Test-PodeLoggerBatch
                        Start-Sleep -Seconds 5
                        continue
                    }

                    # safely pop off the first log from the array
                    $log = (Lock-PodeObject -Return -Object $PodeContext.LogsToProcess -ScriptBlock {
                            $log = $PodeContext.LogsToProcess[0]
                            $null = $PodeContext.LogsToProcess.RemoveAt(0)
                            return $log
                        })

                    # run the log item through the appropriate method
                    $logger = Get-PodeLogger -Name $log.Name
                    $now = [datetime]::Now

                    # if the log is null, check batch then sleep and skip
                    if ($null -eq $log) {
                        Start-Sleep -Milliseconds 100
                        continue
                    }

                    # convert to log item into a writable format
                    $rawItems = $log.Item
                    $_args = @($log.Item) + @($logger.Arguments)
                    $result = @(Invoke-PodeScriptBlock -ScriptBlock $logger.ScriptBlock -Arguments $_args -UsingVariables $logger.UsingVariables -Return -Splat)

                    # check batching
                    $batch = $logger.Method.Batch
                    if ($batch.Size -gt 1) {
                        # add current item to batch
                        $batch.Items += $result
                        $batch.RawItems += $log.Item
                        $batch.LastUpdate = $now

                        # if the current amount of items matches the batch, write
                        $result = $null
                        if ($batch.Items.Length -ge $batch.Size) {
                            $result = $batch.Items
                            $rawItems = $batch.RawItems
                        }

                        # if we're writing, reset the items
                        if ($null -ne $result) {
                            $batch.Items = @()
                            $batch.RawItems = @()
                        }
                    }

                    # send the writable log item off to the log writer
                    if ($null -ne $result) {
                        $_args = @(, $result) + @($logger.Method.Arguments) + @(, $rawItems)
                        $null = Invoke-PodeScriptBlock -ScriptBlock $logger.Method.ScriptBlock -Arguments $_args -UsingVariables $logger.Method.UsingVariables -Splat
                    }

                    # small sleep to lower cpu usage
                    Start-Sleep -Milliseconds 100
                }
                catch {
                    $_ | Write-PodeErrorLog
                }
            }
        }
        catch [System.OperationCanceledException] {
            $_ | Write-PodeErrorLog -Level Debug
        }
        catch {
            $_ | Write-PodeErrorLog
            throw $_.Exception
        }
    }

    Add-PodeRunspace -Type Main -Name 'Logging' -ScriptBlock $script
}

<#
.SYNOPSIS
    Tests whether Pode logger batches need to be written.

.DESCRIPTION
    This function checks each Pode logger and determines if its batch needs to be written. It evaluates the batch size, timeout, and last update timestamp to decide whether to process the batch and write the log entries.

.NOTES
    This is an internal function and may change in future releases of Pode.
#>
function Test-PodeLoggerBatch {
    $now = [datetime]::Now

    # check each logger, and see if its batch needs to be written
    foreach ($logger in $PodeContext.Server.Logging.Types.Values) {
        $batch = $logger.Method.Batch
        if (($batch.Size -gt 1) -and ($batch.Items.Length -gt 0) -and ($batch.Timeout -gt 0) `
                -and ($null -ne $batch.LastUpdate) -and ($batch.LastUpdate.AddSeconds($batch.Timeout) -le $now)
        ) {
            $result = $batch.Items
            $rawItems = $batch.RawItems

            $batch.Items = @()
            $batch.RawItems = @()

            $_args = @(, $result) + @($logger.Method.Arguments) + @(, $rawItems)
            $null = Invoke-PodeScriptBlock -ScriptBlock $logger.Method.ScriptBlock -Arguments $_args -UsingVariables $logger.Method.UsingVariables -Splat
        }
    }
}
src\Private\Mappers.ps1
function Get-PodeContentType {
    param(
        [Parameter()]
        [string]
        $Extension,

        [switch]
        $DefaultIsNull
    )

    if ([string]::IsNullOrWhiteSpace($Extension)) {
        $Extension = [string]::Empty
    }

    if (!$Extension.StartsWith('.')) {
        $Extension = ".$($Extension)"
    }

    # Sourced from https://github.com/samuelneff/MimeTypeMap
    switch ($Extension.ToLowerInvariant()) {
        '.323' { return 'text/h323' }
        '.3g2' { return 'video/3gpp2' }
        '.3gp' { return 'video/3gpp' }
        '.3gp2' { return 'video/3gpp2' }
        '.3gpp' { return 'video/3gpp' }
        '.7z' { return 'application/x-7z-compressed' }
        '.aa' { return 'audio/audible' }
        '.aac' { return 'audio/aac' }
        '.aaf' { return 'application/octet-stream' }
        '.aax' { return 'audio/vnd.audible.aax' }
        '.ac3' { return 'audio/ac3' }
        '.aca' { return 'application/octet-stream' }
        '.accda' { return 'application/msaccess.addin' }
        '.accdb' { return 'application/msaccess' }
        '.accdc' { return 'application/msaccess.cab' }
        '.accde' { return 'application/msaccess' }
        '.accdr' { return 'application/msaccess.runtime' }
        '.accdt' { return 'application/msaccess' }
        '.accdw' { return 'application/msaccess.webapplication' }
        '.accft' { return 'application/msaccess.ftemplate' }
        '.acx' { return 'application/internet-property-stream' }
        '.addin' { return 'application/xml' }
        '.ade' { return 'application/msaccess' }
        '.adobebridge' { return 'application/x-bridge-url' }
        '.adp' { return 'application/msaccess' }
        '.adt' { return 'audio/vnd.dlna.adts' }
        '.adts' { return 'audio/aac' }
        '.afm' { return 'application/octet-stream' }
        '.ai' { return 'application/postscript' }
        '.aif' { return 'audio/aiff' }
        '.aifc' { return 'audio/aiff' }
        '.aiff' { return 'audio/aiff' }
        '.air' { return 'application/vnd.adobe.air-application-installer-package+zip' }
        '.amc' { return 'application/mpeg' }
        '.anx' { return 'application/annodex' }
        '.apk' { return 'application/vnd.android.package-archive' }
        '.application' { return 'application/x-ms-application' }
        '.art' { return 'image/x-jg' }
        '.asa' { return 'application/xml' }
        '.asax' { return 'application/xml' }
        '.ascx' { return 'application/xml' }
        '.asd' { return 'application/octet-stream' }
        '.asf' { return 'video/x-ms-asf' }
        '.ashx' { return 'application/xml' }
        '.asi' { return 'application/octet-stream' }
        '.asm' { return 'text/plain' }
        '.asmx' { return 'application/xml' }
        '.aspx' { return 'application/xml' }
        '.asr' { return 'video/x-ms-asf' }
        '.asx' { return 'video/x-ms-asf' }
        '.atom' { return 'application/atom+xml' }
        '.au' { return 'audio/basic' }
        '.avi' { return 'video/x-msvideo' }
        '.axa' { return 'audio/annodex' }
        '.axs' { return 'application/olescript' }
        '.axv' { return 'video/annodex' }
        '.bas' { return 'text/plain' }
        '.bcpio' { return 'application/x-bcpio' }
        '.bin' { return 'application/octet-stream' }
        '.bmp' { return 'image/bmp' }
        '.c' { return 'text/plain' }
        '.cab' { return 'application/octet-stream' }
        '.caf' { return 'audio/x-caf' }
        '.calx' { return 'application/vnd.ms-office.calx' }
        '.cat' { return 'application/vnd.ms-pki.seccat' }
        '.cc' { return 'text/plain' }
        '.cd' { return 'text/plain' }
        '.cdda' { return 'audio/aiff' }
        '.cdf' { return 'application/x-cdf' }
        '.cer' { return 'application/x-x509-ca-cert' }
        '.cfg' { return 'text/plain' }
        '.chm' { return 'application/octet-stream' }
        '.class' { return 'application/x-java-applet' }
        '.clp' { return 'application/x-msclip' }
        '.cmd' { return 'text/plain' }
        '.cmx' { return 'image/x-cmx' }
        '.cnf' { return 'text/plain' }
        '.cod' { return 'image/cis-cod' }
        '.config' { return 'application/xml' }
        '.contact' { return 'text/x-ms-contact' }
        '.coverage' { return 'application/xml' }
        '.cpio' { return 'application/x-cpio' }
        '.cpp' { return 'text/plain' }
        '.crd' { return 'application/x-mscardfile' }
        '.crl' { return 'application/pkix-crl' }
        '.crt' { return 'application/x-x509-ca-cert' }
        '.cs' { return 'text/plain' }
        '.csdproj' { return 'text/plain' }
        '.csh' { return 'application/x-csh' }
        '.csproj' { return 'text/plain' }
        '.css' { return 'text/css' }
        '.csv' { return 'text/csv' }
        '.cur' { return 'application/octet-stream' }
        '.cxx' { return 'text/plain' }
        '.dat' { return 'application/octet-stream' }
        '.datasource' { return 'application/xml' }
        '.dbproj' { return 'text/plain' }
        '.dcr' { return 'application/x-director' }
        '.def' { return 'text/plain' }
        '.deploy' { return 'application/octet-stream' }
        '.der' { return 'application/x-x509-ca-cert' }
        '.dgml' { return 'application/xml' }
        '.dib' { return 'image/bmp' }
        '.dif' { return 'video/x-dv' }
        '.dir' { return 'application/x-director' }
        '.disco' { return 'application/xml' }
        '.divx' { return 'video/divx' }
        '.dll' { return 'application/x-msdownload' }
        '.dll.config' { return 'application/xml' }
        '.dlm' { return 'text/dlm' }
        '.doc' { return 'application/msword' }
        '.docm' { return 'application/vnd.ms-word.document.macroEnabled.12' }
        '.docx' { return 'application/vnd.openxmlformats-officedocument.wordprocessingml.document' }
        '.dot' { return 'application/msword' }
        '.dotm' { return 'application/vnd.ms-word.template.macroEnabled.12' }
        '.dotx' { return 'application/vnd.openxmlformats-officedocument.wordprocessingml.template' }
        '.dsp' { return 'application/octet-stream' }
        '.dsw' { return 'text/plain' }
        '.dtd' { return 'application/xml' }
        '.dtsconfig' { return 'application/xml' }
        '.dv' { return 'video/x-dv' }
        '.dvi' { return 'application/x-dvi' }
        '.dwf' { return 'drawing/x-dwf' }
        '.dwg' { return 'application/acad' }
        '.dwp' { return 'application/octet-stream' }
        '.dxf' { return 'application/x-dxf' }
        '.dxr' { return 'application/x-director' }
        '.eml' { return 'message/rfc822' }
        '.emz' { return 'application/octet-stream' }
        '.eot' { return 'application/vnd.ms-fontobject' }
        '.eps' { return 'application/postscript' }
        '.etl' { return 'application/etl' }
        '.etx' { return 'text/x-setext' }
        '.evy' { return 'application/envoy' }
        '.exe' { return 'application/octet-stream' }
        '.exe.config' { return 'application/xml' }
        '.fdf' { return 'application/vnd.fdf' }
        '.fif' { return 'application/fractals' }
        '.filters' { return 'application/xml' }
        '.fla' { return 'application/octet-stream' }
        '.flac' { return 'audio/flac' }
        '.flr' { return 'x-world/x-vrml' }
        '.flv' { return 'video/x-flv' }
        '.fsscript' { return 'application/fsharp-script' }
        '.fsx' { return 'application/fsharp-script' }
        '.generictest' { return 'application/xml' }
        '.gif' { return 'image/gif' }
        '.gpx' { return 'application/gpx+xml' }
        '.group' { return 'text/x-ms-group' }
        '.gsm' { return 'audio/x-gsm' }
        '.gtar' { return 'application/x-gtar' }
        '.gz' { return 'application/x-gzip' }
        '.gzip' { return 'application/x-gzip' }
        '.h' { return 'text/plain' }
        '.hdf' { return 'application/x-hdf' }
        '.hdml' { return 'text/x-hdml' }
        '.hhc' { return 'application/x-oleobject' }
        '.hhk' { return 'application/octet-stream' }
        '.hhp' { return 'application/octet-stream' }
        '.hlp' { return 'application/winhlp' }
        '.hpp' { return 'text/plain' }
        '.hqx' { return 'application/mac-binhex40' }
        '.hta' { return 'application/hta' }
        '.htc' { return 'text/x-component' }
        '.htm' { return 'text/html' }
        '.html' { return 'text/html' }
        '.htt' { return 'text/webviewhtml' }
        '.hxa' { return 'application/xml' }
        '.hxc' { return 'application/xml' }
        '.hxd' { return 'application/octet-stream' }
        '.hxe' { return 'application/xml' }
        '.hxf' { return 'application/xml' }
        '.hxh' { return 'application/octet-stream' }
        '.hxi' { return 'application/octet-stream' }
        '.hxk' { return 'application/xml' }
        '.hxq' { return 'application/octet-stream' }
        '.hxr' { return 'application/octet-stream' }
        '.hxs' { return 'application/octet-stream' }
        '.hxt' { return 'text/html' }
        '.hxv' { return 'application/xml' }
        '.hxw' { return 'application/octet-stream' }
        '.hxx' { return 'text/plain' }
        '.i' { return 'text/plain' }
        '.ico' { return 'image/x-icon' }
        '.ics' { return 'application/octet-stream' }
        '.idl' { return 'text/plain' }
        '.ief' { return 'image/ief' }
        '.iii' { return 'application/x-iphone' }
        '.inc' { return 'text/plain' }
        '.inf' { return 'application/octet-stream' }
        '.ini' { return 'text/plain' }
        '.inl' { return 'text/plain' }
        '.ins' { return 'application/x-internet-signup' }
        '.ipa' { return 'application/x-itunes-ipa' }
        '.ipg' { return 'application/x-itunes-ipg' }
        '.ipproj' { return 'text/plain' }
        '.ipsw' { return 'application/x-itunes-ipsw' }
        '.iqy' { return 'text/x-ms-iqy' }
        '.isp' { return 'application/x-internet-signup' }
        '.ite' { return 'application/x-itunes-ite' }
        '.itlp' { return 'application/x-itunes-itlp' }
        '.itms' { return 'application/x-itunes-itms' }
        '.itpc' { return 'application/x-itunes-itpc' }
        '.ivf' { return 'video/x-ivf' }
        '.jar' { return 'application/java-archive' }
        '.java' { return 'application/octet-stream' }
        '.jck' { return 'application/liquidmotion' }
        '.jcz' { return 'application/liquidmotion' }
        '.jfif' { return 'image/pjpeg' }
        '.jnlp' { return 'application/x-java-jnlp-file' }
        '.jpb' { return 'application/octet-stream' }
        '.jpe' { return 'image/jpeg' }
        '.jpeg' { return 'image/jpeg' }
        '.jpg' { return 'image/jpeg' }
        '.js' { return 'application/javascript' }
        '.json' { return 'application/json' }
        '.jsx' { return 'text/jscript' }
        '.jsxbin' { return 'text/plain' }
        '.jwt' { return 'application/jwt' }
        '.latex' { return 'application/x-latex' }
        '.library-ms' { return 'application/windows-library+xml' }
        '.lit' { return 'application/x-ms-reader' }
        '.loadtest' { return 'application/xml' }
        '.lpk' { return 'application/octet-stream' }
        '.lsf' { return 'video/x-la-asf' }
        '.lst' { return 'text/plain' }
        '.lsx' { return 'video/x-la-asf' }
        '.lzh' { return 'application/octet-stream' }
        '.m13' { return 'application/x-msmediaview' }
        '.m14' { return 'application/x-msmediaview' }
        '.m1v' { return 'video/mpeg' }
        '.m2t' { return 'video/vnd.dlna.mpeg-tts' }
        '.m2ts' { return 'video/vnd.dlna.mpeg-tts' }
        '.m2v' { return 'video/mpeg' }
        '.m3u' { return 'audio/x-mpegurl' }
        '.m3u8' { return 'audio/x-mpegurl' }
        '.m4a' { return 'audio/m4a' }
        '.m4b' { return 'audio/m4b' }
        '.m4p' { return 'audio/m4p' }
        '.m4r' { return 'audio/x-m4r' }
        '.m4v' { return 'video/x-m4v' }
        '.mac' { return 'image/x-macpaint' }
        '.mak' { return 'text/plain' }
        '.man' { return 'application/x-troff-man' }
        '.manifest' { return 'application/x-ms-manifest' }
        '.map' { return 'text/plain' }
        '.markdown' { return 'text/markdown' }
        '.master' { return 'application/xml' }
        '.mbox' { return 'application/mbox' }
        '.md' { return 'text/markdown' }
        '.mda' { return 'application/msaccess' }
        '.mdb' { return 'application/x-msaccess' }
        '.mde' { return 'application/msaccess' }
        '.mdp' { return 'application/octet-stream' }
        '.me' { return 'application/x-troff-me' }
        '.mfp' { return 'application/x-shockwave-flash' }
        '.mht' { return 'message/rfc822' }
        '.mhtml' { return 'message/rfc822' }
        '.mid' { return 'audio/mid' }
        '.midi' { return 'audio/mid' }
        '.mix' { return 'application/octet-stream' }
        '.mk' { return 'text/plain' }
        '.mk3d' { return 'video/x-matroska-3d' }
        '.mka' { return 'audio/x-matroska' }
        '.mkv' { return 'video/x-matroska' }
        '.mmf' { return 'application/x-smaf' }
        '.mno' { return 'application/xml' }
        '.mny' { return 'application/x-msmoney' }
        '.mod' { return 'video/mpeg' }
        '.mov' { return 'video/quicktime' }
        '.movie' { return 'video/x-sgi-movie' }
        '.mp2' { return 'video/mpeg' }
        '.mp2v' { return 'video/mpeg' }
        '.mp3' { return 'audio/mpeg' }
        '.mp4' { return 'video/mp4' }
        '.mp4v' { return 'video/mp4' }
        '.mpa' { return 'video/mpeg' }
        '.mpe' { return 'video/mpeg' }
        '.mpeg' { return 'video/mpeg' }
        '.mpf' { return 'application/vnd.ms-mediapackage' }
        '.mpg' { return 'video/mpeg' }
        '.mpp' { return 'application/vnd.ms-project' }
        '.mpv2' { return 'video/mpeg' }
        '.mqv' { return 'video/quicktime' }
        '.ms' { return 'application/x-troff-ms' }
        '.msg' { return 'application/vnd.ms-outlook' }
        '.msi' { return 'application/octet-stream' }
        '.mso' { return 'application/octet-stream' }
        '.mts' { return 'video/vnd.dlna.mpeg-tts' }
        '.mtx' { return 'application/xml' }
        '.mvb' { return 'application/x-msmediaview' }
        '.mvc' { return 'application/x-miva-compiled' }
        '.mxp' { return 'application/x-mmxp' }
        '.nc' { return 'application/x-netcdf' }
        '.nsc' { return 'video/x-ms-asf' }
        '.nws' { return 'message/rfc822' }
        '.ocx' { return 'application/octet-stream' }
        '.oda' { return 'application/oda' }
        '.odb' { return 'application/vnd.oasis.opendocument.database' }
        '.odc' { return 'application/vnd.oasis.opendocument.chart' }
        '.odf' { return 'application/vnd.oasis.opendocument.formula' }
        '.odg' { return 'application/vnd.oasis.opendocument.graphics' }
        '.odh' { return 'text/plain' }
        '.odi' { return 'application/vnd.oasis.opendocument.image' }
        '.odl' { return 'text/plain' }
        '.odm' { return 'application/vnd.oasis.opendocument.text-master' }
        '.odp' { return 'application/vnd.oasis.opendocument.presentation' }
        '.ods' { return 'application/vnd.oasis.opendocument.spreadsheet' }
        '.odt' { return 'application/vnd.oasis.opendocument.text' }
        '.oga' { return 'audio/ogg' }
        '.ogg' { return 'audio/ogg' }
        '.ogv' { return 'video/ogg' }
        '.ogx' { return 'application/ogg' }
        '.one' { return 'application/onenote' }
        '.onea' { return 'application/onenote' }
        '.onepkg' { return 'application/onenote' }
        '.onetmp' { return 'application/onenote' }
        '.onetoc' { return 'application/onenote' }
        '.onetoc2' { return 'application/onenote' }
        '.opus' { return 'audio/ogg' }
        '.orderedtest' { return 'application/xml' }
        '.osdx' { return 'application/opensearchdescription+xml' }
        '.otf' { return 'application/font-sfnt' }
        '.otg' { return 'application/vnd.oasis.opendocument.graphics-template' }
        '.oth' { return 'application/vnd.oasis.opendocument.text-web' }
        '.otp' { return 'application/vnd.oasis.opendocument.presentation-template' }
        '.ots' { return 'application/vnd.oasis.opendocument.spreadsheet-template' }
        '.ott' { return 'application/vnd.oasis.opendocument.text-template' }
        '.oxt' { return 'application/vnd.openofficeorg.extension' }
        '.p10' { return 'application/pkcs10' }
        '.p12' { return 'application/x-pkcs12' }
        '.p7b' { return 'application/x-pkcs7-certificates' }
        '.p7c' { return 'application/pkcs7-mime' }
        '.p7m' { return 'application/pkcs7-mime' }
        '.p7r' { return 'application/x-pkcs7-certreqresp' }
        '.p7s' { return 'application/pkcs7-signature' }
        '.pbm' { return 'image/x-portable-bitmap' }
        '.pcast' { return 'application/x-podcast' }
        '.pct' { return 'image/pict' }
        '.pcx' { return 'application/octet-stream' }
        '.pcz' { return 'application/octet-stream' }
        '.pdf' { return 'application/pdf' }
        '.pfb' { return 'application/octet-stream' }
        '.pfm' { return 'application/octet-stream' }
        '.pfx' { return 'application/x-pkcs12' }
        '.pgm' { return 'image/x-portable-graymap' }
        '.pic' { return 'image/pict' }
        '.pict' { return 'image/pict' }
        '.pkgdef' { return 'text/plain' }
        '.pkgundef' { return 'text/plain' }
        '.pko' { return 'application/vnd.ms-pki.pko' }
        '.pls' { return 'audio/scpls' }
        '.pma' { return 'application/x-perfmon' }
        '.pmc' { return 'application/x-perfmon' }
        '.pml' { return 'application/x-perfmon' }
        '.pmr' { return 'application/x-perfmon' }
        '.pmw' { return 'application/x-perfmon' }
        '.png' { return 'image/png' }
        '.pnm' { return 'image/x-portable-anymap' }
        '.pnt' { return 'image/x-macpaint' }
        '.pntg' { return 'image/x-macpaint' }
        '.pnz' { return 'image/png' }
        '.pode' { return 'application/PowerShell' }
        '.pot' { return 'application/vnd.ms-powerpoint' }
        '.potm' { return 'application/vnd.ms-powerpoint.template.macroEnabled.12' }
        '.potx' { return 'application/vnd.openxmlformats-officedocument.presentationml.template' }
        '.ppa' { return 'application/vnd.ms-powerpoint' }
        '.ppam' { return 'application/vnd.ms-powerpoint.addin.macroEnabled.12' }
        '.ppm' { return 'image/x-portable-pixmap' }
        '.pps' { return 'application/vnd.ms-powerpoint' }
        '.ppsm' { return 'application/vnd.ms-powerpoint.slideshow.macroEnabled.12' }
        '.ppsx' { return 'application/vnd.openxmlformats-officedocument.presentationml.slideshow' }
        '.ppt' { return 'application/vnd.ms-powerpoint' }
        '.pptm' { return 'application/vnd.ms-powerpoint.presentation.macroEnabled.12' }
        '.pptx' { return 'application/vnd.openxmlformats-officedocument.presentationml.presentation' }
        '.prf' { return 'application/pics-rules' }
        '.prm' { return 'application/octet-stream' }
        '.prx' { return 'application/octet-stream' }
        '.ps' { return 'application/postscript' }
        '.ps1' { return 'application/PowerShell' }
        '.psc1' { return 'application/PowerShell' }
        '.psd1' { return 'application/PowerShell' }
        '.psm1' { return 'application/PowerShell' }
        '.psd' { return 'application/octet-stream' }
        '.psess' { return 'application/xml' }
        '.psm' { return 'application/octet-stream' }
        '.psp' { return 'application/octet-stream' }
        '.pst' { return 'application/vnd.ms-outlook' }
        '.pub' { return 'application/x-mspublisher' }
        '.pwz' { return 'application/vnd.ms-powerpoint' }
        '.qht' { return 'text/x-html-insertion' }
        '.qhtm' { return 'text/x-html-insertion' }
        '.qt' { return 'video/quicktime' }
        '.qti' { return 'image/x-quicktime' }
        '.qtif' { return 'image/x-quicktime' }
        '.qtl' { return 'application/x-quicktimeplayer' }
        '.qxd' { return 'application/octet-stream' }
        '.ra' { return 'audio/x-pn-realaudio' }
        '.ram' { return 'audio/x-pn-realaudio' }
        '.rar' { return 'application/x-rar-compressed' }
        '.ras' { return 'image/x-cmu-raster' }
        '.rat' { return 'application/rat-file' }
        '.rc' { return 'text/plain' }
        '.rc2' { return 'text/plain' }
        '.rct' { return 'text/plain' }
        '.rdlc' { return 'application/xml' }
        '.reg' { return 'text/plain' }
        '.resx' { return 'application/xml' }
        '.rf' { return 'image/vnd.rn-realflash' }
        '.rgb' { return 'image/x-rgb' }
        '.rgs' { return 'text/plain' }
        '.rm' { return 'application/vnd.rn-realmedia' }
        '.rmi' { return 'audio/mid' }
        '.rmp' { return 'application/vnd.rn-rn_music_package' }
        '.roff' { return 'application/x-troff' }
        '.rpm' { return 'audio/x-pn-realaudio-plugin' }
        '.rqy' { return 'text/x-ms-rqy' }
        '.rtf' { return 'application/rtf' }
        '.rtx' { return 'text/richtext' }
        '.rvt' { return 'application/octet-stream' }
        '.ruleset' { return 'application/xml' }
        '.s' { return 'text/plain' }
        '.safariextz' { return 'application/x-safari-safariextz' }
        '.scd' { return 'application/x-msschedule' }
        '.scr' { return 'text/plain' }
        '.sct' { return 'text/scriptlet' }
        '.sd2' { return 'audio/x-sd2' }
        '.sdp' { return 'application/sdp' }
        '.sea' { return 'application/octet-stream' }
        '.searchconnector-ms' { return 'application/windows-search-connector+xml' }
        '.setpay' { return 'application/set-payment-initiation' }
        '.setreg' { return 'application/set-registration-initiation' }
        '.settings' { return 'application/xml' }
        '.sgimb' { return 'application/x-sgimb' }
        '.sgml' { return 'text/sgml' }
        '.sh' { return 'application/x-sh' }
        '.shar' { return 'application/x-shar' }
        '.shtml' { return 'text/html' }
        '.sit' { return 'application/x-stuffit' }
        '.sitemap' { return 'application/xml' }
        '.skin' { return 'application/xml' }
        '.skp' { return 'application/x-koan' }
        '.sldm' { return 'application/vnd.ms-powerpoint.slide.macroEnabled.12' }
        '.sldx' { return 'application/vnd.openxmlformats-officedocument.presentationml.slide' }
        '.slk' { return 'application/vnd.ms-excel' }
        '.sln' { return 'text/plain' }
        '.slupkg-ms' { return 'application/x-ms-license' }
        '.smd' { return 'audio/x-smd' }
        '.smi' { return 'application/octet-stream' }
        '.smx' { return 'audio/x-smd' }
        '.smz' { return 'audio/x-smd' }
        '.snd' { return 'audio/basic' }
        '.snippet' { return 'application/xml' }
        '.snp' { return 'application/octet-stream' }
        '.sol' { return 'text/plain' }
        '.sor' { return 'text/plain' }
        '.spc' { return 'application/x-pkcs7-certificates' }
        '.spl' { return 'application/futuresplash' }
        '.spx' { return 'audio/ogg' }
        '.src' { return 'application/x-wais-source' }
        '.srf' { return 'text/plain' }
        '.ssisdeploymentmanifest' { return 'application/xml' }
        '.ssm' { return 'application/streamingmedia' }
        '.sst' { return 'application/vnd.ms-pki.certstore' }
        '.stl' { return 'application/vnd.ms-pki.stl' }
        '.sv4cpio' { return 'application/x-sv4cpio' }
        '.sv4crc' { return 'application/x-sv4crc' }
        '.svc' { return 'application/xml' }
        '.svg' { return 'image/svg+xml' }
        '.swf' { return 'application/x-shockwave-flash' }
        '.step' { return 'application/step' }
        '.stp' { return 'application/step' }
        '.t' { return 'application/x-troff' }
        '.tar' { return 'application/x-tar' }
        '.tcl' { return 'application/x-tcl' }
        '.testrunconfig' { return 'application/xml' }
        '.testsettings' { return 'application/xml' }
        '.tex' { return 'application/x-tex' }
        '.texi' { return 'application/x-texinfo' }
        '.texinfo' { return 'application/x-texinfo' }
        '.tgz' { return 'application/x-compressed' }
        '.thmx' { return 'application/vnd.ms-officetheme' }
        '.thn' { return 'application/octet-stream' }
        '.tif' { return 'image/tiff' }
        '.tiff' { return 'image/tiff' }
        '.tlh' { return 'text/plain' }
        '.tli' { return 'text/plain' }
        '.toc' { return 'application/octet-stream' }
        '.tr' { return 'application/x-troff' }
        '.trm' { return 'application/x-msterminal' }
        '.trx' { return 'application/xml' }
        '.ts' { return 'video/vnd.dlna.mpeg-tts' }
        '.tsv' { return 'text/tab-separated-values' }
        '.ttf' { return 'application/font-sfnt' }
        '.tts' { return 'video/vnd.dlna.mpeg-tts' }
        '.txt' { return 'text/plain' }
        '.u32' { return 'application/octet-stream' }
        '.uls' { return 'text/iuls' }
        '.user' { return 'text/plain' }
        '.ustar' { return 'application/x-ustar' }
        '.vb' { return 'text/plain' }
        '.vbdproj' { return 'text/plain' }
        '.vbk' { return 'video/mpeg' }
        '.vbproj' { return 'text/plain' }
        '.vbs' { return 'text/vbscript' }
        '.vcf' { return 'text/x-vcard' }
        '.vcproj' { return 'application/xml' }
        '.vcs' { return 'text/plain' }
        '.vcxproj' { return 'application/xml' }
        '.vddproj' { return 'text/plain' }
        '.vdp' { return 'text/plain' }
        '.vdproj' { return 'text/plain' }
        '.vdx' { return 'application/vnd.ms-visio.viewer' }
        '.vml' { return 'application/xml' }
        '.vscontent' { return 'application/xml' }
        '.vsct' { return 'application/xml' }
        '.vsd' { return 'application/vnd.visio' }
        '.vsi' { return 'application/ms-vsi' }
        '.vsix' { return 'application/vsix' }
        '.vsixlangpack' { return 'application/xml' }
        '.vsixmanifest' { return 'application/xml' }
        '.vsmdi' { return 'application/xml' }
        '.vspscc' { return 'text/plain' }
        '.vss' { return 'application/vnd.visio' }
        '.vsscc' { return 'text/plain' }
        '.vssettings' { return 'application/xml' }
        '.vssscc' { return 'text/plain' }
        '.vst' { return 'application/vnd.visio' }
        '.vstemplate' { return 'application/xml' }
        '.vsto' { return 'application/x-ms-vsto' }
        '.vsw' { return 'application/vnd.visio' }
        '.vsx' { return 'application/vnd.visio' }
        '.vtx' { return 'application/vnd.visio' }
        '.wasm' { return 'application/wasm' }
        '.wav' { return 'audio/wav' }
        '.wave' { return 'audio/wav' }
        '.wax' { return 'audio/x-ms-wax' }
        '.wbk' { return 'application/msword' }
        '.wbmp' { return 'image/vnd.wap.wbmp' }
        '.wcm' { return 'application/vnd.ms-works' }
        '.wdb' { return 'application/vnd.ms-works' }
        '.wdp' { return 'image/vnd.ms-photo' }
        '.webarchive' { return 'application/x-safari-webarchive' }
        '.webm' { return 'video/webm' }
        '.webp' { return 'image/webp' }
        '.webtest' { return 'application/xml' }
        '.wiq' { return 'application/xml' }
        '.wiz' { return 'application/msword' }
        '.wks' { return 'application/vnd.ms-works' }
        '.wlmp' { return 'application/wlmoviemaker' }
        '.wlpginstall' { return 'application/x-wlpg-detect' }
        '.wlpginstall3' { return 'application/x-wlpg3-detect' }
        '.wm' { return 'video/x-ms-wm' }
        '.wma' { return 'audio/x-ms-wma' }
        '.wmd' { return 'application/x-ms-wmd' }
        '.wmf' { return 'application/x-msmetafile' }
        '.wml' { return 'text/vnd.wap.wml' }
        '.wmlc' { return 'application/vnd.wap.wmlc' }
        '.wmls' { return 'text/vnd.wap.wmlscript' }
        '.wmlsc' { return 'application/vnd.wap.wmlscriptc' }
        '.wmp' { return 'video/x-ms-wmp' }
        '.wmv' { return 'video/x-ms-wmv' }
        '.wmx' { return 'video/x-ms-wmx' }
        '.wmz' { return 'application/x-ms-wmz' }
        '.woff' { return 'application/font-woff' }
        '.woff2' { return 'application/font-woff2' }
        '.wpl' { return 'application/vnd.ms-wpl' }
        '.wps' { return 'application/vnd.ms-works' }
        '.wri' { return 'application/x-mswrite' }
        '.wrl' { return 'x-world/x-vrml' }
        '.wrz' { return 'x-world/x-vrml' }
        '.wsc' { return 'text/scriptlet' }
        '.wsdl' { return 'application/xml' }
        '.wvx' { return 'video/x-ms-wvx' }
        '.x' { return 'application/directx' }
        '.xaf' { return 'x-world/x-vrml' }
        '.xaml' { return 'application/xaml+xml' }
        '.xap' { return 'application/x-silverlight-app' }
        '.xbap' { return 'application/x-ms-xbap' }
        '.xbm' { return 'image/x-xbitmap' }
        '.xdr' { return 'text/plain' }
        '.xht' { return 'application/xhtml+xml' }
        '.xhtml' { return 'application/xhtml+xml' }
        '.xla' { return 'application/vnd.ms-excel' }
        '.xlam' { return 'application/vnd.ms-excel.addin.macroEnabled.12' }
        '.xlc' { return 'application/vnd.ms-excel' }
        '.xld' { return 'application/vnd.ms-excel' }
        '.xlk' { return 'application/vnd.ms-excel' }
        '.xll' { return 'application/vnd.ms-excel' }
        '.xlm' { return 'application/vnd.ms-excel' }
        '.xls' { return 'application/vnd.ms-excel' }
        '.xlsb' { return 'application/vnd.ms-excel.sheet.binary.macroEnabled.12' }
        '.xlsm' { return 'application/vnd.ms-excel.sheet.macroEnabled.12' }
        '.xlsx' { return 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' }
        '.xlt' { return 'application/vnd.ms-excel' }
        '.xltm' { return 'application/vnd.ms-excel.template.macroEnabled.12' }
        '.xltx' { return 'application/vnd.openxmlformats-officedocument.spreadsheetml.template' }
        '.xlw' { return 'application/vnd.ms-excel' }
        '.xml' { return 'application/xml' }
        '.xmp' { return 'application/octet-stream' }
        '.xmta' { return 'application/xml' }
        '.xof' { return 'x-world/x-vrml' }
        '.xoml' { return 'text/plain' }
        '.xpm' { return 'image/x-xpixmap' }
        '.xps' { return 'application/vnd.ms-xpsdocument' }
        '.xrm-ms' { return 'application/xml' }
        '.xsc' { return 'application/xml' }
        '.xsd' { return 'application/xml' }
        '.xsf' { return 'application/xml' }
        '.xsl' { return 'application/xml' }
        '.xslt' { return 'application/xml' }
        '.xsn' { return 'application/octet-stream' }
        '.xss' { return 'application/xml' }
        '.xspf' { return 'application/xspf+xml' }
        '.xtp' { return 'application/octet-stream' }
        '.xwd' { return 'image/x-xwindowdump' }
        '.yaml' { return 'application/yaml' } #RFC 9512
        '.yml' { return 'application/yaml' }
        '.z' { return 'application/x-compress' }
        '.zip' { return 'application/zip' }
        default { return (Resolve-PodeValue -Check $DefaultIsNull -TrueValue $null -FalseValue 'text/plain') }
    }
}

function Get-PodeStatusDescription {
    param(
        [Parameter()]
        [int]
        $StatusCode
    )

    switch ($StatusCode) {
        100 { return 'Continue' }
        101 { return 'Switching Protocols' }
        102 { return 'Processing' }
        103 { return 'Early Hints' }
        200 { return 'OK' }
        201 { return 'Created' }
        202 { return 'Accepted' }
        203 { return 'Non-Authoritative Information' }
        204 { return 'No Content' }
        205 { return 'Reset Content' }
        206 { return 'Partial Content' }
        207 { return 'Multi-Status' }
        208 { return 'Already Reported' }
        226 { return 'IM Used' }
        300 { return 'Multiple Choices' }
        301 { return 'Moved Permanently' }
        302 { return 'Found' }
        303 { return 'See Other' }
        304 { return 'Not Modified' }
        305 { return 'Use Proxy' }
        306 { return 'Switch Proxy' }
        307 { return 'Temporary Redirect' }
        308 { return 'Permanent Redirect' }
        400 { return 'Bad Request' }
        401 { return 'Unauthorized' }
        402 { return 'Payment Required' }
        403 { return 'Forbidden' }
        404 { return 'Not Found' }
        405 { return 'Method Not Allowed' }
        406 { return 'Not Acceptable' }
        407 { return 'Proxy Authentication Required' }
        408 { return 'Request Timeout' }
        409 { return 'Conflict' }
        410 { return 'Gone' }
        411 { return 'Length Required' }
        412 { return 'Precondition Failed' }
        413 { return 'Payload Too Large' }
        414 { return 'URI Too Long' }
        415 { return 'Unsupported Media Type' }
        416 { return 'Range Not Satisfiable' }
        417 { return 'Expectation Failed' }
        418 { return "I'm a Teapot" }
        419 { return 'Page Expired' }
        420 { return 'Enhance Your Calm' }
        421 { return 'Misdirected Request' }
        422 { return 'Unprocessable Entity' }
        423 { return 'Locked' }
        424 { return 'Failed Dependency' }
        425 { return 'Too Early' }
        426 { return 'Upgrade Required' }
        428 { return 'Precondition Required' }
        429 { return 'Too Many Requests' }
        431 { return 'Request Header Fields Too Large' }
        440 { return 'Login Time-out' }
        450 { return 'Blocked by Windows Parental Controls' }
        451 { return 'Unavailable For Legal Reasons' }
        500 { return 'Internal Server Error' }
        501 { return 'Not Implemented' }
        502 { return 'Bad Gateway' }
        503 { return 'Service Unavailable' }
        504 { return 'Gateway Timeout' }
        505 { return 'HTTP Version Not Supported' }
        506 { return 'Variant Also Negotiates' }
        507 { return 'Insufficient Storage' }
        508 { return 'Loop Detected' }
        510 { return 'Not Extended' }
        511 { return 'Network Authentication Required' }
        526 { return 'Invalid SSL Certificate' }
        default { return ([string]::Empty) }
    }
}
src\Private\Metrics.ps1
<#
.SYNOPSIS
    Updates server request metrics based on the provided web event.

.DESCRIPTION
    The `Update-PodeServerRequestMetric` function increments relevant metrics associated with server requests.
    It takes a web event (represented as a hashtable) and updates the appropriate metrics.

.PARAMETER WebEvent
    Specifies the web event to process. This parameter is optional.

.INPUTS
    None. You cannot pipe objects to Update-PodeServerRequestMetric.

.OUTPUTS
    None. The function modifies the state of metrics in the PodeContext.

.EXAMPLE
    # Example usage:
    $webEvent = @{
        Response = @{
            StatusCode = 200
        }
        Route = @{
            Metrics = @{
                Requests = $routeMetrics
            }
        }
    }

    Update-PodeServerRequestMetric -WebEvent $webEvent
    # Metrics associated with the web event are updated.

.NOTES
    This is an internal function and may change in future releases of Pode.
#>
function Update-PodeServerRequestMetric {
    param(
        [Parameter()]
        [hashtable]
        $WebEvent
    )

    if ($null -eq $WebEvent) {
        return
    }

    # Extract the status code from the web event
    $status = "$($WebEvent.Response.StatusCode)"

    # Determine which metrics to update
    $metrics = @($PodeContext.Metrics.Requests)
    if ($null -ne $WebEvent.Route) {
        $metrics += $WebEvent.Route.Metrics.Requests
    }

    # Increment the request metrics and status code counts
    foreach ($metric in $metrics) {
        Lock-PodeObject -Object $metric -ScriptBlock {
            $metric.Total++

            if (!$metric.StatusCodes.ContainsKey($status)) {
                $metric.StatusCodes[$status] = 0
            }

            $metric.StatusCodes[$status]++
        }
    }
}

<#
.SYNOPSIS
    Updates server signal metrics based on the provided signal event.

.DESCRIPTION
    The `Update-PodeServerSignalMetric` function increments relevant metrics associated with server signals.
    It takes a signal event (represented as a hashtable) and updates the appropriate metrics.

.PARAMETER SignalEvent
    Specifies the signal event to process. This parameter is optional.

.INPUTS
    None. You cannot pipe objects to Update-PodeServerSignalMetric.

.OUTPUTS
    None. The function modifies the state of metrics in the PodeContext.

.EXAMPLE
    # Example usage:
    $signalEvent = @{
        Route = @{
            Metrics = @{
                Requests = $routeMetrics
            }
        }
    }

    Update-PodeServerSignalMetric -SignalEvent $signalEvent
    # Metrics associated with the signal event are updated.

.NOTES
    This is an internal function and may change in future releases of Pode.
#>
function Update-PodeServerSignalMetric {
    param(
        [Parameter()]
        [hashtable]
        $SignalEvent
    )

    if ($null -eq $SignalEvent) {
        return
    }

     # Determine which metrics to update
    $metrics = @($PodeContext.Metrics.Signals)
    if ($null -ne $SignalEvent.Route) {
        $metrics += $SignalEvent.Route.Metrics.Requests
    }

    # increment the request metrics
    foreach ($metric in $metrics) {
        Lock-PodeObject -Object $metric -ScriptBlock {
            $metric.Total++
        }
    }
}
src\Private\Middleware.ps1
using namespace System.Security.Cryptography

function Invoke-PodeMiddleware {
    param(
        [Parameter()]
        $Middleware,

        [Parameter()]
        [string]
        $Route
    )

    # if there's no middleware, do nothing
    if (($null -eq $Middleware) -or ($Middleware.Length -eq 0)) {
        return $true
    }

    # filter the middleware down by route (retaining order)
    if (![string]::IsNullOrWhiteSpace($Route)) {
        $Middleware = @(foreach ($mware in $Middleware) {
                if ($null -eq $mware) {
                    continue
                }

                if ([string]::IsNullOrWhiteSpace($mware.Route) -or ($mware.Route -ieq '/') -or ($mware.Route -ieq $Route) -or ($Route -imatch "^$($mware.Route)$")) {
                    $mware
                }
            })
    }

    # continue or halt?
    $continue = $true

    # loop through each of the middleware, invoking the next if it returns true
    foreach ($midware in @($Middleware)) {
        if (($null -eq $midware) -or ($null -eq $midware.Logic)) {
            continue
        }

        try {
            $continue = Invoke-PodeScriptBlock -ScriptBlock $midware.Logic -Arguments $midware.Arguments -UsingVariables $midware.UsingVariables -Return -Scoped -Splat
            if ($null -eq $continue) {
                $continue = $true
            }
        }
        catch {
            Set-PodeResponseStatus -Code 500 -Exception $_
            $continue = $false
            $_ | Write-PodeErrorLog
        }

        if (!$continue) {
            break
        }
    }

    return $continue
}

function New-PodeMiddlewareInternal {
    [CmdletBinding()]
    [OutputType([hashtable])]
    param(
        [Parameter(Mandatory = $true)]
        [scriptblock]
        $ScriptBlock,

        [Parameter()]
        [string]
        $Route,

        [Parameter()]
        [object[]]
        $ArgumentList,

        [Parameter(Mandatory = $true)]
        [System.Management.Automation.SessionState]
        $PSSession
    )

    if (Test-PodeIsEmpty $ScriptBlock) {
        # No ScriptBlock supplied
        throw ($PodeLocale.noScriptBlockSuppliedExceptionMessage)
    }

    # if route is empty, set it to root
    $Route = ConvertTo-PodeRouteRegex -Path $Route

    # check for scoped vars
    $ScriptBlock, $usingVars = Convert-PodeScopedVariables -ScriptBlock $ScriptBlock -PSSession $PSSession

    # create the middleware hashtable from a scriptblock
    $HashTable = @{
        Route          = $Route
        Logic          = $ScriptBlock
        Arguments      = $ArgumentList
        UsingVariables = $usingVars
    }

    # return the middleware, so it can be cached/added at a later date
    return $HashTable
}

function Get-PodeInbuiltMiddleware {
    param(
        [Parameter(Mandatory = $true)]
        [ValidateNotNullOrEmpty()]
        [string]
        $Name,

        [Parameter(Mandatory = $true)]
        [ValidateNotNull()]
        [scriptblock]
        $ScriptBlock
    )

    # check if middleware contains an override
    $override = ($PodeContext.Server.Middleware | Where-Object { $_.Name -ieq $Name })

    # if override there, remove it from middleware
    if ($override) {
        $PodeContext.Server.Middleware = @($PodeContext.Server.Middleware | Where-Object { $_.Name -ine $Name })
        $ScriptBlock = $override.Logic
    }

    # return the script
    return @{
        Name  = $Name
        Logic = $ScriptBlock
    }
}

function Get-PodeAccessMiddleware {
    return (Get-PodeInbuiltMiddleware -Name '__pode_mw_access__' -ScriptBlock {
            # are there any rules?
            if (($PodeContext.Server.Access.Allow.Count -eq 0) -and ($PodeContext.Server.Access.Deny.Count -eq 0)) {
                return $true
            }

            # ensure the request IP address is allowed
            if (!(Test-PodeIPAccess -IP $WebEvent.Request.RemoteEndPoint.Address)) {
                Set-PodeResponseStatus -Code 403
                return $false
            }

            # request is allowed
            return $true
        })
}

<#
.SYNOPSIS
Retrieves the rate limit middleware for Pode.

.DESCRIPTION
This function returns the inbuilt rate limit middleware for Pode. It checks if the request IP address, route, and endpoint have hit their respective rate limits. If any of these checks fail, a 429 status code is set, and the request is denied.

.EXAMPLE
Get-PodeLimitMiddleware
Retrieves the rate limit middleware and adds it to the middleware pipeline.

.RETURNS
[ScriptBlock] - Returns a script block that represents the rate limit middleware.

.NOTES
This is an internal function and may change in future releases of Pode.
#>
function Get-PodeLimitMiddleware {
    [CmdletBinding()]
    [OutputType([hashtable])]
    param()
    return (Get-PodeInbuiltMiddleware -Name '__pode_mw_rate_limit__' -ScriptBlock {
            # are there any rules?
            if ($PodeContext.Server.Limits.Rules.Count -eq 0) {
                return $true
            }

            # check the request IP address has not hit a rate limit
            if (!(Test-PodeIPLimit -IP $WebEvent.Request.RemoteEndPoint.Address)) {
                Set-PodeResponseStatus -Code 429
                return $false
            }

            # check the route
            if (!(Test-PodeRouteLimit -Path $WebEvent.Path)) {
                Set-PodeResponseStatus -Code 429
                return $false
            }

            # check the endpoint
            if (!(Test-PodeEndpointLimit -EndpointName $WebEvent.Endpoint.Name)) {
                Set-PodeResponseStatus -Code 429
                return $false
            }

            # request is allowed
            return $true
        })
}

<#
.SYNOPSIS
    Retrieves middleware for serving public static content in a Pode web server.
.DESCRIPTION
    This function retrieves middleware for serving public static content in a Pode web server.
    It searches for static content based on the requested path and serves it if found.
.PARAMETER WebEvent
    The PodeWebEvent object representing the incoming web request.
.PARAMETER PodeContext
    The PodeContext object representing the current Pode server context.
.EXAMPLE
    Get-PodePublicMiddleware
    Retrieves middleware for serving public static content.
#>
function Get-PodePublicMiddleware {
    [CmdletBinding()]
    [OutputType([hashtable])]
    param()
    return (Get-PodeInbuiltMiddleware -Name '__pode_mw_static_content__' -ScriptBlock {
            # only find public static content here
            $path = Find-PodePublicRoute -Path $WebEvent.Path
            if ([string]::IsNullOrWhiteSpace($path)) {
                return $true
            }

            # check current state of caching
            $cachable = Test-PodeRouteValidForCaching -Path $WebEvent.Path

            # write the file to the response
            Write-PodeFileResponse -Path $path -MaxAge $PodeContext.Server.Web.Static.Cache.MaxAge -Cache:$cachable

            # public static content found, stop
            return $false
        })
}

<#
.SYNOPSIS
    Middleware function to validate the route for an incoming web request.

.DESCRIPTION
    This function is used as middleware to validate the route for an incoming web request. It checks if the route exists for the requested method and path. If the route does not exist, it sets the appropriate response status code (404 for not found, 405 for method not allowed) and returns false to halt further processing. If the route exists, it sets various properties on the WebEvent object, such as parameters, content type, and transfer encoding, and returns true to continue processing.

.PARAMETER None

.EXAMPLE
    $middleware = Get-PodeRouteValidateMiddleware
    Add-PodeMiddleware -Middleware $middleware

.NOTES
    This function is part of the internal Pode server logic and is typically not called directly by users.

#>
function Get-PodeRouteValidateMiddleware {
    return @{
        Name  = '__pode_mw_route_validation__'
        Logic = {
            if ($PodeContext.Server.Web.Static.ValidateLast) {
                #  check the main routes and check the static routes
                $route = Find-PodeRoute -Method $WebEvent.Method -Path $WebEvent.Path -EndpointName $WebEvent.Endpoint.Name -CheckWildMethod
                if ($null -eq $route) {
                    $route = Find-PodeStaticRoute -Path $WebEvent.Path -EndpointName $WebEvent.Endpoint.Name
                }
            }
            else {
                # check if the path is static route first, then check the main routes
                $route = Find-PodeStaticRoute -Path $WebEvent.Path -EndpointName $WebEvent.Endpoint.Name
                if ($null -eq $route) {
                    $route = Find-PodeRoute -Method $WebEvent.Method -Path $WebEvent.Path -EndpointName $WebEvent.Endpoint.Name -CheckWildMethod
                }
            }

            # if there's no route defined, it's a 404 - or a 405 if a route exists for any other method
            if ($null -eq $route) {
                # check if a route exists for another method
                $methods = @('CONNECT', 'DELETE', 'GET', 'HEAD', 'MERGE', 'OPTIONS', 'PATCH', 'POST', 'PUT', 'TRACE')
                $diff_route = @(foreach ($method in $methods) {
                        $r = Find-PodeRoute -Method $method -Path $WebEvent.Path -EndpointName $WebEvent.Endpoint.Name
                        if ($null -ne $r) {
                            $r
                            break
                        }
                    })[0]

                if ($null -ne $diff_route) {
                    Set-PodeResponseStatus -Code 405
                    return $false
                }

                # otheriwse, it's a 404
                Set-PodeResponseStatus -Code 404
                return $false
            }

            # check if static and split
            if ($null -ne $route.Content) {
                $WebEvent.StaticContent = $route.Content
                $route = $route.Route
            }

            # set the route parameters
            $WebEvent.Parameters = @{}
            if ($WebEvent.Path -imatch "$($route.Path)$") {
                $WebEvent.Parameters = $Matches
            }

            # set the route on the WebEvent
            $WebEvent.Route = $route

            # override the content type from the route if it's not empty
            if (![string]::IsNullOrWhiteSpace($route.ContentType)) {
                $WebEvent.ContentType = $route.ContentType
            }

            # override the transfer encoding from the route if it's not empty
            if (![string]::IsNullOrWhiteSpace($route.TransferEncoding)) {
                $WebEvent.TransferEncoding = $route.TransferEncoding
            }

            # set the content type for any pages for the route if it's not empty
            $WebEvent.ErrorType = $route.ErrorType

            # route exists
            return $true
        }
    }
}

function Get-PodeBodyMiddleware {
    return (Get-PodeInbuiltMiddleware -Name '__pode_mw_body_parsing__' -ScriptBlock {
            try {
                # attempt to parse that data
                $result = ConvertFrom-PodeRequestContent -Request $WebEvent.Request -ContentType $WebEvent.ContentType -TransferEncoding $WebEvent.TransferEncoding

                # set session data
                $WebEvent.Data = $result.Data
                $WebEvent.Files = $result.Files

                # payload parsed
                return $true
            }
            catch {
                Set-PodeResponseStatus -Code 400 -Exception $_
                return $false
            }
        })
}

function Get-PodeQueryMiddleware {
    return (Get-PodeInbuiltMiddleware -Name '__pode_mw_query_parsing__' -ScriptBlock {
            try {
                # set the query string from the request
                $WebEvent.Query = (ConvertFrom-PodeNameValueToHashTable -Collection $WebEvent.Request.QueryString)
                return $true
            }
            catch {
                Set-PodeResponseStatus -Code 400 -Exception $_
                return $false
            }
        })
}

function Get-PodeCookieMiddleware {
    return (Get-PodeInbuiltMiddleware -Name '__pode_mw_cookie_parsing__' -ScriptBlock {
            # if cookies already set, return
            if ($WebEvent.Cookies.Count -gt 0) {
                return $true
            }

            # if the request's header has no cookies, return
            $h_cookie = (Get-PodeHeader -Name 'Cookie')
            if ([string]::IsNullOrWhiteSpace($h_cookie)) {
                return $true
            }

            # parse the cookies from the header
            $cookies = @($h_cookie -split '; ')
            $WebEvent.Cookies = @{}

            foreach ($cookie in $cookies) {
                $atoms = $cookie.Split('=', 2)

                $value = [string]::Empty
                if ($atoms.Length -gt 1) {
                    foreach ($atom in $atoms[1..($atoms.Length - 1)]) {
                        $value += $atom
                    }
                }

                $WebEvent.Cookies[$atoms[0]] = [System.Net.Cookie]::new($atoms[0], $value)
            }

            return $true
        })
}

function Get-PodeSecurityMiddleware {
    return (Get-PodeInbuiltMiddleware -Name '__pode_mw_security__' -ScriptBlock {
            # are there any security headers setup?
            if ($PodeContext.Server.Security.Headers.Count -eq 0) {
                return $true
            }

            # add security headers
            Set-PodeHeaderBulk -Value $PodeContext.Server.Security.Headers

            # continue to next middleware/route
            return $true
        })
}

function Initialize-PodeIISMiddleware {
    # do nothing if not iis
    if (!$PodeContext.Server.IsIIS) {
        return
    }

    # fail if no iis token - because there should be!
    if ([string]::IsNullOrWhiteSpace($PodeContext.Server.IIS.Token)) {
        # IIS ASPNETCORE_TOKEN is missing
        throw ($PodeLocale.iisAspnetcoreTokenMissingExceptionMessage)
    }

    # add middleware to check every request has the token
    Add-PodeMiddleware -Name '__pode_iis_token_check__' -ScriptBlock {
        $token = Get-PodeHeader -Name 'MS-ASPNETCORE-TOKEN'
        if ($token -ne $PodeContext.Server.IIS.Token) {
            Set-PodeResponseStatus -Code 400 -Description 'MS-ASPNETCORE-TOKEN header missing'
            return $false
        }

        return $true
    }

    # add middleware to check if there's a client cert
    Add-PodeMiddleware -Name '__pode_iis_clientcert_check__' -ScriptBlock {
        if (!$WebEvent.Request.AllowClientCertificate -or ($null -ne $WebEvent.Request.ClientCertificate)) {
            return $true
        }

        $headers = @('MS-ASPNETCORE-CLIENTCERT', 'X-ARR-ClientCert')
        foreach ($header in $headers) {
            if (!(Test-PodeHeader -Name $header)) {
                continue
            }

            try {
                $value = Get-PodeHeader -Name $header
                $WebEvent.Request.ClientCertificate = [X509Certificates.X509Certificate2]::new([Convert]::FromBase64String($value))
            }
            catch {
                $WebEvent.Request.ClientCertificateErrors = [System.Net.Security.SslPolicyErrors]::RemoteCertificateNotAvailable
            }
        }

        return $true
    }

    # add route to gracefully shutdown server for iis
    Add-PodeRoute -Method Post -Path '/iisintegration' -ScriptBlock {
        $eventType = Get-PodeHeader -Name 'MS-ASPNETCORE-EVENT'

        # no x-forward
        if (Test-PodeHeader -Name 'X-Forwarded-For') {
            Set-PodeResponseStatus -Code 400
            return
        }

        # no user-agent
        if (Test-PodeHeader -Name 'User-Agent') {
            Set-PodeResponseStatus -Code 400
            return
        }

        # valid local Host
        $hostValue = Get-PodeHeader -Name 'Host'
        if ($hostValue -ine "127.0.0.1:$($PodeContext.Server.IIS.Port)") {
            Set-PodeResponseStatus -Code 400
            return
        }

        # no content-length
        if ($WebEvent.Request.ContentLength -gt 0) {
            Set-PodeResponseStatus -Code 400
            return
        }

        # valid event type
        if ($eventType -ine 'shutdown') {
            Set-PodeResponseStatus -Code 400
            return
        }

        # shutdown
        $PodeContext.Server.IIS.Shutdown = $true
        Close-PodeServer
        Set-PodeResponseStatus -Code 202
    }
}
src\Private\NameGenerator.ps1
function Get-PodeRandomName {
    $adjs = @(
        'admiring',
        'agitated',
        'blissful',
        'dazzling',
        'ecstatic',
        'eloquent',
        'friendly',
        'gracious',
        'hardcore',
        'laughing',
        'peaceful',
        'pedantic',
        'reverent',
        'romantic',
        'trusting',
        'vigilant',
        'vigorous',
        'wizardly',
        'youthful'
    )

    $names = @(
        'almeida',
        'babbage',
        'bardeen',
        'shannon',
        'davinci',
        'feynman',
        'galileo',
        'goodall',
        'hawking',
        'hermann',
        'hodgkin',
        'hypatia',
        'jackson',
        'johnson',
        'kapitsa',
        'keldysh',
        'khorana',
        'lalande',
        'lamport',
        'leavitt',
        'lumiere',
        'mcnulty',
        'meitner',
        'mestorf',
        'murdock',
        'neumann',
        'noether',
        'pasteur',
        'perlman',
        'poitras',
        'ptolemy',
        'ritchie',
        'shirley',
        'swanson',
        'swirles',
        'vaughan',
        'volhard',
        'villani',
        'wescoff',
        'wozniak'
    )

    $adjsRand = (Get-Random -Minimum 0 -Maximum $adjs.Length)
    $namesRand = (Get-Random -Minimum 0 -Maximum $names.Length)

    return "$($adjs[$adjsRand])_$($names[$namesRand])"
}
src\Private\OpenApi.ps1
<#
.SYNOPSIS
    Converts content into an OpenAPI schema object format.

.DESCRIPTION
    The ConvertTo-PodeOAObjectSchema function takes a hashtable representing content and converts it into a format suitable for OpenAPI schema objects.
    It validates the content types, processes array structures, and converts each property or reference into the appropriate OpenAPI schema format.
    The function is designed to handle complex content structures for OpenAPI documentation within the Pode framework.

.PARAMETER Content
    A hashtable representing the content to be converted into an OpenAPI schema object. The content can include various types and structures.

.PARAMETER Properties
    A switch to indicate if the content represents properties of an object schema.

.PARAMETER DefinitionTag
    A string representing the definition tag to be used in the conversion process. This tag is essential for correctly formatting the content according to OpenAPI specifications.

.EXAMPLE
    $schemaObject = ConvertTo-PodeOAObjectSchema -Content $myContent -DefinitionTag 'myTag'

    Converts a hashtable of content into an OpenAPI schema object using the definition tag 'myTag'.

.NOTES
    This is an internal function and may change in future releases of Pode.
#>
function ConvertTo-PodeOAObjectSchema {
    param(
        [Parameter( Mandatory = $true, Position = 0, ValueFromPipeline = $true)]
        [hashtable]
        $Content,

        [Parameter()]
        [switch]
        $Properties,

        [Parameter(Mandatory = $true)]
        [string ]
        $DefinitionTag

    )
    begin {
        $pipelineItemCount = 0  # Initialize counter to track items in the pipeline.
    }

    process {
        $pipelineItemCount++  # Increment the counter for each item in the pipeline.
    }

    end {
        # Throw an error if more than one item is passed in the pipeline.
        if ($pipelineItemCount -gt 1) {
            throw ($PodeLocale.fnDoesNotAcceptArrayAsPipelineInputExceptionMessage -f $($MyInvocation.MyCommand.Name))
        }

        # Ensure all content types are valid MIME types.
        foreach ($type in $Content.Keys) {
            if ($type -inotmatch '^(application|audio|image|message|model|multipart|text|video|\*)\/[\w\.\-\*]+(;[\s]*(charset|boundary)=[\w\.\-\*]+)*$|^"\*\/\*"$') {
                # Invalid content-type found for schema: $($type)
                throw ($PodeLocale.invalidContentTypeForSchemaExceptionMessage -f $type)
            }
        }

        # Manage a specific case where a generic schema conversion issue may arise.
        if ($Content.ContainsKey('*/*')) {
            $Content['"*/*"'] = $Content['*/*']  # Adjust the key format for schema compatibility.
            $Content.Remove('*/*')
        }

        # Initialize an empty hashtable for the schema object.
        $obj = [ordered]@{}

        # Get all the content keys (MIME types) to iterate through.
        $types = [string[]]$Content.Keys
        foreach ($type in $types) {
            # Initialize schema structure for each type.
            $obj[$type] = [ordered]@{}

            # Handle file upload content, arrays, and shared component schema references.
            if ($Content[$type].__upload) {
                # Check if the content is an array.
                if ($Content[$type].__array) {
                    $upload = $Content[$type].__content.__upload
                }
                else {
                    $upload = $Content[$type].__upload
                }

                # Handle specific multipart/form-data content processing.
                if ($type -ieq 'multipart/form-data' -and $upload.content) {
                    if ((Test-PodeOAVersion -Version 3.1 -DefinitionTag $DefinitionTag) -and $upload.partContentMediaType) {
                        # Iterate through properties to set content media type and remove format for binaries.
                        foreach ($key in $upload.content.Properties) {
                            if ($key.type -eq 'string' -and ($key.format -ieq 'binary' -or $key.format -ieq 'base64')) {
                                $key.ContentMediaType = $PartContentMediaType
                                $key.remove('format')
                                break
                            }
                        }
                    }
                    $newContent = $upload.content
                }
                else {
                    # Handle OpenAPI v3.0 specific content encoding.
                    if (Test-PodeOAVersion -Version 3.0 -DefinitionTag $DefinitionTag) {
                        $newContent = [ordered]@{
                            'type'   = 'string'
                            'format' = $upload.contentEncoding
                        }
                    }
                    else {
                        # Handle Base64 content encoding.
                        if ($ContentEncoding -ieq 'Base64') {
                            $newContent = [ordered]@{
                                'type'            = 'string'
                                'contentEncoding' = $upload.contentEncoding
                            }
                        }
                    }
                }

                # Update the content with the new encoding information.
                if ($Content[$type].__array) {
                    $Content[$type].__content = $newContent
                }
                else {
                    $Content[$type] = $newContent
                }
            }

            # Process arrays and object properties based on content type.
            if ($Content[$type].__array) {
                $isArray = $true
                $item = $Content[$type].__content
                $obj[$type].schema = [ordered]@{
                    'type'  = 'array'
                    'items' = $null
                }
                # Include additional metadata if present.
                if ($Content[$type].__title) {
                    $obj[$type].schema.title = $Content[$type].__title
                }
                if ($Content[$type].__uniqueItems) {
                    $obj[$type].schema.uniqueItems = $Content[$type].__uniqueItems
                }
                if ($Content[$type].__maxItems) {
                    $obj[$type].schema.__maxItems = $Content[$type].__maxItems
                }
                if ($Content[$type].minItems) {
                    $obj[$type].schema.minItems = $Content[$type].__minItems
                }
            }
            else {
                $item = $Content[$type]
                $isArray = $false
            }

            # Add schema objects or handle empty content.
            if ($item -is [string]) {
                if (![string]::IsNullOrEmpty($item)) {
                    # Handle basic type definitions or references.
                    if (@('string', 'integer', 'number', 'boolean') -icontains $item) {
                        if ($isArray) {
                            $obj[$type].schema.items = [ordered]@{
                                'type' = $item.ToLower()
                            }
                        }
                        else {
                            $obj[$type].schema = [ordered]@{
                                'type' = $item.ToLower()
                            }
                        }
                    }
                    else {
                        # Handle component references.
                        Test-PodeOAComponentInternal -Field schemas -DefinitionTag $DefinitionTag -Name $item -PostValidation
                        if ($isArray) {
                            $obj[$type].schema.items = [ordered]@{
                                '$ref' = "#/components/schemas/$($item)"
                            }
                        }
                        else {
                            $obj[$type].schema = [ordered]@{
                                '$ref' = "#/components/schemas/$($item)"
                            }
                        }
                    }
                }
                else {
                    # Create an empty content entry.
                    $obj[$type] = [ordered]@{}
                }
            }
            else {
                if ($item.Count -eq 0) {
                    $result = [ordered]@{}  # Create an empty object if the item count is zero.
                }
                else {
                    # Convert each property to a PodeOpenAPI schema property.
                    $result = ($item | ConvertTo-PodeOASchemaProperty -DefinitionTag $DefinitionTag)
                }

                # Handle the Properties parameter case.
                if ($Properties) {
                    if ($item.Name) {
                        $obj[$type].schema = [ordered]@{
                            'properties' = [ordered]@{
                                $item.Name = $result
                            }
                        }
                    }
                    else {
                        # Throw an error if Properties parameter is used without a name.
                        throw ($PodeLocale.propertiesParameterWithoutNameExceptionMessage)
                    }
                }
                else {
                    # Assign the resulting schema to the correct array or object location.
                    if ($isArray) {
                        $obj[$type].schema.items = $result
                    }
                    else {
                        $obj[$type].schema = $result
                    }
                }
            }
        }

        return $obj  # Return the final OpenAPI schema object.
    }
}

<#
.SYNOPSIS
Check if an ComponentSchemaJson reference exist.

.DESCRIPTION
Check if an ComponentSchemaJson reference with a given name exist.

.PARAMETER Name
The Name of the ComponentSchemaJson reference.

.NOTES
This is an internal function and may change in future releases of Pode.
#>


function Test-PodeOAComponentSchemaJson {
    param(
        [Parameter(Mandatory = $true)]
        [ValidateNotNullOrEmpty()]
        [string]
        $Name,

        [Parameter(Mandatory = $true)]
        [string[]]
        $DefinitionTag
    )

    foreach ($tag in $DefinitionTag) {
        if (!($PodeContext.Server.OpenAPI.Definitions[$tag].hiddenComponents.schemaJson.keys -ccontains $Name)) {
            # If $Name is not found in the current $tag, return $false
            return $false
        }
    }
    return $true
}

<#
.SYNOPSIS
    Tests if a given name exists in the external path keys of OpenAPI definitions for specified definition tags.

.DESCRIPTION
    The Test-PodeOAComponentExternalPath function iterates over a list of definition tags and checks if a given name
    is present in the external path keys of OpenAPI definitions within the Pode server context. This function is typically
    used to validate if a specific component name is already defined in the external paths of the OpenAPI documentation.

.PARAMETER Name
    The name of the external path component to be checked within the OpenAPI definitions.

.PARAMETER DefinitionTag
    An array of definition tags against which the existence of the name will be checked in the OpenAPI definitions.

.EXAMPLE
    $exists = Test-PodeOAComponentExternalPath -Name 'MyComponentName' -DefinitionTag @('tag1', 'tag2')

    Checks if 'MyComponentName' exists in the external path keys of OpenAPI definitions for 'tag1' and 'tag2'.

.NOTES
    This is an internal function and may change in future releases of Pode.
#>
function Test-PodeOAComponentExternalPath {
    param(
        [Parameter(Mandatory = $true)]
        [ValidateNotNullOrEmpty()]
        [string]
        $Name,

        [Parameter(Mandatory = $true)]
        [string[]]
        $DefinitionTag
    )

    # Iterate over each definition tag
    foreach ($tag in $DefinitionTag) {
        # Check if the name exists in the external path keys of the current definition tag
        if (!($PodeContext.Server.OpenAPI.Definitions[$tag].hiddenComponents.externalPath.keys -ccontains $Name)) {
            # If the name is not found in the current tag, return false
            return $false
        }
    }
    # If the name exists in all specified tags, return true
    return $true
}


<#
.SYNOPSIS
    Converts a property into an OpenAPI 'Of' property structure based on a given definition tag.

.DESCRIPTION
    The ConvertTo-PodeOAOfProperty function is used to convert a given property into one of the OpenAPI 'Of' properties:
    allOf, oneOf, or anyOf. These structures are used in OpenAPI documentation to define complex types. The function
    constructs the appropriate structure based on the type of the property and the definition tag provided.

.PARAMETER Property
    A hashtable representing the property to be converted. It should contain the type (allOf, oneOf, or anyOf) and
    potentially a list of schemas.

.PARAMETER DefinitionTag
    A mandatory string parameter specifying the definition tag in OpenAPI documentation, used for validating components.

.EXAMPLE
    $ofProperty = ConvertTo-PodeOAOfProperty -Property $myProperty -DefinitionTag 'myTag'

    Converts a given property into an OpenAPI 'Of' structure using the specified definition tag.

.NOTES
    This is an internal function and may change in future releases of Pode.
#>
function ConvertTo-PodeOAOfProperty {
    param (
        [hashtable]
        $Property,

        [Parameter(Mandatory = $true)]
        [string]
        $DefinitionTag
    )

    # Check if the property type is one of the supported 'Of' types
    if (@('allOf', 'oneOf', 'anyOf') -inotcontains $Property.type) {
        return @{}
    }
    # Initialize the schema with the 'Of' type
    if ($Property.name) {
        $schema = [ordered]@{
            $Property.name = [ordered]@{
                $Property.type = @()
            }
        }
        if ($Property.description) {
            $schema[$Property.name].description = $Property.description
        }
    }
    else {
        $schema = [ordered]@{
            $Property.type = @()
        }
    }

    # Process each schema defined in the property
    if ($Property.schemas) {
        foreach ($prop in $Property.schemas) {
            if ($prop -is [string]) {
                # Validate the schema component and add a reference to it
                Test-PodeOAComponentInternal -Field schemas -DefinitionTag $DefinitionTag -Name $prop -PostValidation
                if ($Property.name) {
                    $schema[$Property.name][$Property.type] += [ordered]@{ '$ref' = "#/components/schemas/$prop" }
                }
                else {
                    $schema[$Property.type] += [ordered]@{ '$ref' = "#/components/schemas/$prop" }
                }
            }
            else {
                # Convert the property to an OpenAPI schema property
                if ($Property.name) {
                    $schema[$Property.name][$Property.type] += $prop | ConvertTo-PodeOASchemaProperty -DefinitionTag $DefinitionTag
                }
                else {
                    $schema[$Property.type] += $prop | ConvertTo-PodeOASchemaProperty -DefinitionTag $DefinitionTag
                }
            }
        }
    }

    # Add discriminator if present
    if ($Property.discriminator) {
        $schema['discriminator'] = $Property.discriminator
    }

    # Return the constructed 'Of' property schema
    return $schema
}

<#
.SYNOPSIS
    Converts a hashtable representing a property into a schema property format compliant with the OpenAPI Specification (OAS).

.DESCRIPTION
    This function takes a hashtable input representing a property and converts it into a schema property format based on the OpenAPI Specification.
    It handles various property types including primitives, arrays, and complex types with allOf, oneOf, anyOf constructs.

.PARAMETER Property
    A hashtable containing property details that need to be converted to an OAS schema property.

.PARAMETER NoDescription
    A switch parameter. If set, the description of the property will not be included in the output schema.

.PARAMETER DefinitionTag
    A mandatory string parameter specifying the definition context used for schema validation and compatibility checks with OpenAPI versions.

.EXAMPLE
    $propertyDetails = [ordered]@{
        type = 'string';
        description = 'A sample property';
    }
    ConvertTo-PodeOASchemaProperty -Property $propertyDetails -DefinitionTag 'v1'

    This example will convert a simple string property into an OpenAPI schema property.
#>
function ConvertTo-PodeOASchemaProperty {
    param(
        [Parameter(Mandatory = $true, Position = 0, ValueFromPipeline = $true)]
        [hashtable]
        $Property,

        [switch]
        $NoDescription,

        [Parameter(Mandatory = $true)]
        [string]
        $DefinitionTag
    )
    begin {
        $pipelineItemCount = 0
    }

    process {

        $pipelineItemCount++
    }

    end {
        if ($pipelineItemCount -gt 1) {
            throw ($PodeLocale.fnDoesNotAcceptArrayAsPipelineInputExceptionMessage -f $($MyInvocation.MyCommand.Name))
        }

        if ( @('allof', 'oneof', 'anyof') -icontains $Property.type) {
            $schema = ConvertTo-PodeOAofProperty -DefinitionTag $DefinitionTag -Property $Property
        }
        else {
            # base schema type
            $schema = [ordered]@{ }
            if (Test-PodeOAVersion -Version 3.0 -DefinitionTag $DefinitionTag ) {
                if ($Property.type -is [string[]]) {
                    # Multi type properties requeired OpenApi Version 3.1 or above
                    throw ($PodeLocale.multiTypePropertiesRequireOpenApi31ExceptionMessage)
                }
                $schema['type'] = $Property.type.ToLower()
            }
            else {
                $schema.type = @($Property.type.ToLower())
                if ($Property.nullable) {
                    $schema.type += 'null'
                }
            }
        }

        if ($Property.externalDocs) {
            $schema['externalDocs'] = $Property.externalDocs
        }

        if (!$NoDescription -and $Property.description) {
            $schema['description'] = $Property.description
        }

        if ($Property.default) {
            $schema['default'] = $Property.default
        }

        if ($Property.deprecated) {
            $schema['deprecated'] = $Property.deprecated
        }
        if ($Property.nullable -and (Test-PodeOAVersion -Version 3.0 -DefinitionTag $DefinitionTag )) {
            $schema['nullable'] = $Property.nullable
        }

        if ($Property.writeOnly) {
            $schema['writeOnly'] = $Property.writeOnly
        }

        if ($Property.readOnly) {
            $schema['readOnly'] = $Property.readOnly
        }

        if ($Property.example) {
            if (Test-PodeOAVersion -Version 3.0 -DefinitionTag $DefinitionTag ) {
                $schema['example'] = $Property.example
            }
            else {
                if ($Property.example -is [Array]) {
                    $schema['examples'] = $Property.example
                }
                else {
                    $schema['examples'] = @( $Property.example)
                }
            }
        }
        if (Test-PodeOAVersion -Version 3.0 -DefinitionTag $DefinitionTag ) {
            if ($Property.ContainsKey('minimum')) {
                $schema['minimum'] = $Property.minimum
            }

            if ($Property.ContainsKey('maximum')) {
                $schema['maximum'] = $Property.maximum
            }

            if ($Property.exclusiveMaximum) {
                $schema['exclusiveMaximum'] = $Property.exclusiveMaximum
            }

            if ($Property.exclusiveMinimum) {
                $schema['exclusiveMinimum'] = $Property.exclusiveMinimum
            }
        }
        else {
            if ($Property.ContainsKey('maximum')) {
                if ($Property.exclusiveMaximum) {
                    $schema['exclusiveMaximum'] = $Property.maximum
                }
                else {
                    $schema['maximum'] = $Property.maximum
                }
            }
            if ($Property.ContainsKey('minimum')) {
                if ($Property.exclusiveMinimum) {
                    $schema['exclusiveMinimum'] = $Property.minimum
                }
                else {
                    $schema['minimum'] = $Property.minimum
                }
            }
        }
        if ($Property.multipleOf) {
            $schema['multipleOf'] = $Property.multipleOf
        }

        if ($Property.pattern) {
            $schema['pattern'] = $Property.pattern
        }

        if ($Property.ContainsKey('minLength')) {
            $schema['minLength'] = $Property.minLength
        }

        if ($Property.ContainsKey('maxLength')) {
            $schema['maxLength'] = $Property.maxLength
        }

        if ($Property.xml ) {
            $schema['xml'] = $Property.xml
        }

        if (Test-PodeOAVersion -Version 3.1 -DefinitionTag $DefinitionTag ) {
            if ($Property.ContentMediaType) {
                $schema['contentMediaType'] = $Property.ContentMediaType
            }
            if ($Property.ContentEncoding) {
                $schema['contentEncoding'] = $Property.ContentEncoding
            }
        }

        # are we using an array?
        if ($Property.array) {
            if ($Property.ContainsKey('maxItems') ) {
                $schema['maxItems'] = $Property.maxItems
            }

            if ($Property.ContainsKey('minItems') ) {
                $schema['minItems'] = $Property.minItems
            }

            if ($Property.uniqueItems ) {
                $schema['uniqueItems'] = $Property.uniqueItems
            }

            $schema['type'] = 'array'
            if ($Property.type -ieq 'schema') {
                Test-PodeOAComponentInternal -Field schemas -DefinitionTag $DefinitionTag -Name $Property['schema'] -PostValidation
                $schema['items'] = [ordered]@{ '$ref' = "#/components/schemas/$($Property['schema'])" }
            }
            else {
                $Property.array = $false
                if ($Property.xml) {
                    $xmlFromProperties = $Property.xml
                    $Property.Remove('xml')
                }
                $schema['items'] = ($Property | ConvertTo-PodeOASchemaProperty -DefinitionTag $DefinitionTag)
                $Property.array = $true
                if ($xmlFromProperties) {
                    $Property.xml = $xmlFromProperties
                }

                if ($Property.xmlItemName) {
                    $schema.items.xml = [ordered]@{'name' = $Property.xmlItemName }
                }
            }
            return $schema
        }
        else {
            #format is not applicable to array
            if ($Property.format) {
                $schema['format'] = $Property.format
            }

            # schema refs
            if ($Property.type -ieq 'schema') {
                Test-PodeOAComponentInternal -Field schemas -DefinitionTag $DefinitionTag -Name $Property['schema'] -PostValidation
                $schema = [ordered]@{
                    '$ref' = "#/components/schemas/$($Property['schema'])"
                }
            }
            #only if it's not an array
            if ($Property.enum ) {
                $schema['enum'] = $Property.enum
            }
        }

        if ($Property.object) {
            # are we using an object?
            $Property.object = $false

            $schema = [ordered]@{
                type       = 'object'
                properties = (ConvertTo-PodeOASchemaObjectProperty -DefinitionTag $DefinitionTag -Properties $Property)
            }
            $Property.object = $true
            if ($Property.required) {
                $schema['required'] = @($Property.name)
            }
        }

        if ($Property.type -ieq 'object') {
            $schema['properties'] = [ordered]@{}
            foreach ($prop in $Property.properties) {
                if ( @('allOf', 'oneOf', 'anyOf') -icontains $prop.type) {
                    switch ($prop.type.ToLower()) {
                        'allof' { $prop.type = 'allOf' }
                        'oneof' { $prop.type = 'oneOf' }
                        'anyof' { $prop.type = 'anyOf' }
                    }
                    if ($prop.name) {
                        $schema['properties'] += ConvertTo-PodeOAofProperty -DefinitionTag $DefinitionTag -Property $prop
                    }
                    else {
                        $schema += ConvertTo-PodeOAofProperty -DefinitionTag $DefinitionTag -Property $prop
                    }

                }
            }
            if ($Property.properties) {
                $schema['properties'] = (ConvertTo-PodeOASchemaObjectProperty -DefinitionTag $DefinitionTag -Properties $Property.properties)
                $RequiredList = @(($Property.properties | Where-Object { $_.required }) )
                if ( $RequiredList.Count -gt 0) {
                    $schema['required'] = @($RequiredList.name)
                }
            }
            else {
                #if noproperties parameter create an empty properties
                if ( $Property.properties.Count -eq 1 -and $null -eq $Property.properties[0]) {
                    $schema['properties'] = @{}
                }
            }


            if ($Property.minProperties) {
                $schema['minProperties'] = $Property.minProperties
            }

            if ($Property.maxProperties) {
                $schema['maxProperties'] = $Property.maxProperties
            }
            #Fix an issue when additionalProperties has an assigned value of $false
            if ($Property.ContainsKey('additionalProperties')) {
                if ($Property.additionalProperties) {
                    $schema['additionalProperties'] = $Property.additionalProperties | ConvertTo-PodeOASchemaProperty -DefinitionTag $DefinitionTag
                }
                else {
                    #the value is $false
                    $schema['additionalProperties'] = $false
                }
            }

            if ($Property.discriminator) {
                $schema['discriminator'] = $Property.discriminator
            }
        }

        return $schema
    }
}

<#
.SYNOPSIS
Converts a collection of properties into an OpenAPI schema object format.

.DESCRIPTION
The ConvertTo-PodeOASchemaObjectProperty function takes an array of property hashtables and converts them into
a format suitable for OpenAPI schema objects. It specifically processes properties that are not 'allOf', 'oneOf',
or 'anyOf' types, using the ConvertTo-PodeOASchemaProperty function for conversion based on a given definition tag.

.PARAMETER Properties
An array of hashtables representing properties to be converted. Each hashtable should contain the property's details.

.PARAMETER DefinitionTag
A string representing the definition tag to be used in the conversion process. This tag is crucial for correctly
formatting the properties according to OpenAPI specifications.

.EXAMPLE
$schemaObject = ConvertTo-PodeOASchemaObjectProperty -Properties $myProperties -DefinitionTag 'myTag'

Converts an array of property hashtables into an OpenAPI schema object using the definition tag 'myTag'.

.NOTES
This is an internal function and may change in future releases of Pode.
#>
function ConvertTo-PodeOASchemaObjectProperty {
    param(
        [Parameter(Mandatory = $true)]
        [hashtable[]]
        $Properties,

        [Parameter(Mandatory = $true)]
        [string]
        $DefinitionTag
    )

    # Initialize an empty hashtable for the schema
    $schema = [ordered]@{}

    # Iterate over each property and convert to OpenAPI schema property if applicable
    foreach ($prop in $Properties) {
        # Exclude properties of type 'allOf', 'oneOf', or 'anyOf'
        if (@('allOf', 'oneOf', 'anyOf') -inotcontains $prop.type) {
            # Convert the property to an OpenAPI schema property and add to the schema hashtable
            $schema[$prop.name] = ($prop | ConvertTo-PodeOASchemaProperty -DefinitionTag $DefinitionTag)
        }
    }

    # Return the constructed schema object
    return $schema
}

<#
.SYNOPSIS
Sets OpenAPI specifications for a given route.

.DESCRIPTION
The Set-PodeOpenApiRouteValue function processes and sets various OpenAPI specifications for a given route based on the provided definition tag.
It handles route attributes such as deprecated status, tags, summary, description, operation ID, parameters, request body, callbacks, authentication,
and responses to build a complete OpenAPI specification for the route.

.PARAMETER Route
A hashtable representing the route for which OpenAPI specifications are being set.

.PARAMETER DefinitionTag
A string representing the definition tag used for specifying OpenAPI documentation details for the route.

.EXAMPLE
$routeValues = Set-PodeOpenApiRouteValue -Route $route -DefinitionTag 'myTag'

Sets OpenAPI specifications for the given route using the definition tag 'myTag'.

.NOTES
This is an internal function and may change in future releases of Pode.
#>
function Set-PodeOpenApiRouteValue {
    param(
        [Parameter(Mandatory = $true)]
        [hashtable]
        $Route,

        [Parameter(Mandatory = $true)]
        [string]
        $DefinitionTag
    )
    # Initialize an ordered hashtable to store route properties
    $pm = [ordered]@{}

    # Process various OpenAPI attributes for the route
    if ($Route.OpenApi.Deprecated) {
        $pm.deprecated = $Route.OpenApi.Deprecated
    }
    if ($Route.OpenApi.Tags) {
        $pm.tags = $Route.OpenApi.Tags
    }
    if ($Route.OpenApi.Summary) {
        $pm.summary = $Route.OpenApi.Summary
    }
    if ($Route.OpenApi.Description) {
        $pm.description = $Route.OpenApi.Description
    }
    if ($Route.OpenApi.OperationId) {
        $pm.operationId = $Route.OpenApi.OperationId
    }
    if ($Route.OpenApi.Parameters) {
        $pm.parameters = $Route.OpenApi.Parameters
    }
    if ($Route.OpenApi.RequestBody.$DefinitionTag) {
        $pm.requestBody = $Route.OpenApi.RequestBody.$DefinitionTag
    }
    if ($Route.OpenApi.CallBacks.$DefinitionTag) {
        $pm.callbacks = $Route.OpenApi.CallBacks.$DefinitionTag
    }
    if ($Route.OpenApi.Servers) {
        $pm.servers = $Route.OpenApi.Servers
    }
    if ($Route.OpenApi.Authentication.Count -gt 0) {
        $pm.security = @()
        foreach ($sct in (Expand-PodeAuthMerge -Names $Route.OpenApi.Authentication.Keys)) {
            if ($PodeContext.Server.Authentications.Methods.$sct.Scheme.Scheme -ieq 'oauth2') {
                if ($Route.AccessMeta.Scope ) {
                    $sctValue = $Route.AccessMeta.Scope
                }
                else {
                    #if scope is empty means 'any role' => assign an empty array
                    $sctValue = @()
                }
                $pm.security += [ordered]@{ $sct = $sctValue }
            }
            elseif ($sct -eq '%_allowanon_%') {
                #allow anonymous access
                $pm.security += [ordered]@{}
            }
            else {
                $pm.security += [ordered]@{$sct = @() }
            }
        }
    }
    if ($Route.OpenApi.Responses.$DefinitionTag ) {
        $pm.responses = $Route.OpenApi.Responses.$DefinitionTag
    }
    else {
        # Set responses or default to '204 No Content' if not specified
        $pm.responses = [ordered]@{'204' = [ordered]@{'description' = (Get-PodeStatusDescription -StatusCode 204) } }
    }
    # Return the processed route properties
    return $pm
}


<#
.SYNOPSIS
Generates an internal OpenAPI definition based on the current Pode server context and specific parameters.

.DESCRIPTION
This function constructs an OpenAPI definition by gathering metadata, route information, and API structure based on the provided parameters.
It supports customization of the API documentation through MetaInfo and directly influences the output by including specific server, authentication, and endpoint details.

.PARAMETER EndpointName
The name of the endpoint for which the OpenAPI definition is generated.

.PARAMETER MetaInfo
A hashtable containing metadata for the OpenAPI definition such as the API title, version, and description.

.PARAMETER DefinitionTag
Mandatory. A tag that identifies the specific OpenAPI definition to be generated or manipulated.

.OUTPUTS
Ordered dictionary representing the OpenAPI definition, which can be further processed into JSON or YAML format.

.EXAMPLE
$metaInfo = [ordered]@{
Title = "My API";
Version = "v1";
Description = "This is my API description."
}
Get-PodeOpenApiDefinitionInternal -Protocol 'HTTPS' -Address 'myapi.example.com' -EndpointName 'MyAPI' -MetaInfo $metaInfo -DefinitionTag 'MyTag'

.NOTES
This is an internal function and may change in future releases of Pode.
#>

function Get-PodeOpenApiDefinitionInternal {
    param(

        [string]
        $EndpointName,

        [hashtable]
        $MetaInfo,

        [Parameter(Mandatory = $true)]
        [string]
        $DefinitionTag
    )


    $Definition = $PodeContext.Server.OpenAPI.Definitions[$DefinitionTag]

    if (!$Definition.Version) {
        # OpenApi Version property is mandatory
        throw ($PodeLocale.openApiVersionPropertyMandatoryExceptionMessage)
    }
    $localEndpoint = $null
    # set the openapi version
    $def = [ordered]@{
        openapi = $Definition.Version
    }

    if (Test-PodeOAVersion -Version 3.1 -DefinitionTag $DefinitionTag) {
        $def['jsonSchemaDialect'] = 'https://spec.openapis.org/oas/3.1/dialect/base'
    }

    if ($Definition.info) {
        $def['info'] = $Definition.info
    }

    #overwite default values
    if ($MetaInfo.Title) {
        $def.info.title = $MetaInfo.Title
    }

    if ($MetaInfo.Version) {
        $def.info.version = $MetaInfo.Version
    }

    if ($MetaInfo.Description) {
        $def.info.description = $MetaInfo.Description
    }

    if ($Definition.externalDocs) {
        $def['externalDocs'] = $Definition.externalDocs
    }

    if ($Definition.servers) {
        $def['servers'] = $Definition.servers
        if ($Definition.servers.Count -eq 1 -and $Definition.servers[0].url.StartsWith('/')) {
            $localEndpoint = $Definition.servers[0].url
        }
    }
    elseif (!$MetaInfo.RestrictRoutes -and ($PodeContext.Server.Endpoints.Count -gt 1)) {
        #$def['servers'] = $null
        $def.servers = @(foreach ($endpoint in $PodeContext.Server.Endpoints.Values) {
                @{
                    url         = $endpoint.Url
                    description = (Protect-PodeValue -Value $endpoint.Description -Default $endpoint.Name)
                }
            })
    }
    if ($Definition.tags.Count -gt 0) {
        $def['tags'] = @($Definition.tags.Values)
    }

    # paths
    $def['paths'] = [ordered]@{}
    if ($Definition.webhooks.count -gt 0) {
        if (Test-PodeOAVersion -Version 3.0 -DefinitionTag $DefinitionTag) {
            # Webhooks feature is unsupported in OpenAPI v3.0.x
            throw ($PodeLocale.webhooksFeatureNotSupportedInOpenApi30ExceptionMessage)
        }
        else {
            $keys = [string[]]$Definition.webhooks.Keys
            foreach ($key in $keys) {
                if ($Definition.webhooks[$key].NotPrepared) {
                    $Definition.webhooks[$key] = [ordered]@{
                        $Definition.webhooks[$key].Method = Set-PodeOpenApiRouteValue -Route $Definition.webhooks[$key] -DefinitionTag $DefinitionTag
                    }
                }
            }
            $def['webhooks'] = $Definition.webhooks
        }
    }
    # components
    $def['components'] = [ordered]@{}#$Definition.components
    $components = $Definition.components

    if ($components.schemas.count -gt 0) {
        $def['components'].schemas = $components.schemas
    }
    if ($components.responses.count -gt 0) {
        $def['components'].responses = $components.responses
    }
    if ($components.parameters.count -gt 0) {
        $def['components'].parameters = $components.parameters
    }
    if ($components.examples.count -gt 0) {
        $def['components'].examples = $components.examples
    }
    if ($components.requestBodies.count -gt 0) {
        $def['components'].requestBodies = $components.requestBodies
    }
    if ($components.headers.count -gt 0) {
        $def['components'].headers = $components.headers
    }
    if ($components.securitySchemes.count -gt 0) {
        $def['components'].securitySchemes = $components.securitySchemes
    }
    if ($components.links.count -gt 0) {
        $def['components'].links = $components.links
    }
    if ($components.callbacks.count -gt 0) {
        $def['components'].callbacks = $components.callbacks
    }
    if ($components.pathItems.count -gt 0) {
        if (Test-PodeOAVersion -Version 3.0 -DefinitionTag $DefinitionTag) {
            # Feature pathItems is unsupported in OpenAPI v3.0.x
            throw ($PodeLocale.pathItemsFeatureNotSupportedInOpenApi30ExceptionMessage)
        }
        else {
            $keys = [string[]]$components.pathItems.Keys
            foreach ($key in $keys) {
                if ($components.pathItems[$key].NotPrepared) {
                    $components.pathItems[$key] = [ordered]@{
                        $components.pathItems[$key].Method = Set-PodeOpenApiRouteValue -Route $components.pathItems[$key] -DefinitionTag $DefinitionTag
                    }
                }
            }
            $def['components'].pathItems = $components.pathItems
        }
    }

    # auth/security components
    if ($PodeContext.Server.Authentications.Methods.Count -gt 0) {
        $authNames = (Expand-PodeAuthMerge -Names $PodeContext.Server.Authentications.Methods.Keys)

        foreach ($authName in $authNames) {
            $authType = (Find-PodeAuth -Name $authName).Scheme
            $_authName = ($authName -replace '\s+', '')

            $_authObj = [ordered]@{}

            if ($authType.Scheme -ieq 'apikey') {
                $_authObj = [ordered]@{
                    type = $authType.Scheme
                    in   = $authType.Arguments.Location.ToLowerInvariant()
                    name = $authType.Arguments.LocationName
                }
                if ($authType.Arguments.Description) {
                    $_authObj.description = $authType.Arguments.Description
                }
            }
            elseif ($authType.Scheme -ieq 'oauth2') {
                if ($authType.Arguments.Urls.Token -and $authType.Arguments.Urls.Authorise) {
                    $oAuthFlow = 'authorizationCode'
                }
                elseif ($authType.Arguments.Urls.Token ) {
                    if ($null -ne $authType.InnerScheme) {
                        if ($authType.InnerScheme.Name -ieq 'basic' -or $authType.InnerScheme.Name -ieq 'form') {
                            $oAuthFlow = 'password'
                        }
                        else {
                            $oAuthFlow = 'implicit'
                        }
                    }
                }
                $_authObj = [ordered]@{
                    type = $authType.Scheme
                }
                if ($authType.Arguments.Description) {
                    $_authObj.description = $authType.Arguments.Description
                }
                $_authObj.flows = [ordered]@{
                    $oAuthFlow = [ordered]@{
                    }
                }
                if ($authType.Arguments.Urls.Token) {
                    $_authObj.flows.$oAuthFlow.tokenUrl = $authType.Arguments.Urls.Token
                }

                if ($authType.Arguments.Urls.Authorise) {
                    $_authObj.flows.$oAuthFlow.authorizationUrl = $authType.Arguments.Urls.Authorise
                }
                if ($authType.Arguments.Urls.Refresh) {
                    $_authObj.flows.$oAuthFlow.refreshUrl = $authType.Arguments.Urls.Refresh
                }

                $_authObj.flows.$oAuthFlow.scopes = [ordered]@{}
                if ($authType.Arguments.Scopes ) {
                    foreach ($scope in $authType.Arguments.Scopes) {
                        if ($PodeContext.Server.Authorisations.Methods.ContainsKey($scope) -and $PodeContext.Server.Authorisations.Methods[$scope].Scheme.Type -ieq 'Scope' -and $PodeContext.Server.Authorisations.Methods[$scope].Description) {
                            $_authObj.flows.$oAuthFlow.scopes[$scope] = $PodeContext.Server.Authorisations.Methods[$scope].Description
                        }
                        else {
                            $_authObj.flows.$oAuthFlow.scopes[$scope] = 'No description.'
                        }
                    }
                }
            }
            else {
                $_authObj = [ordered]@{
                    type   = $authType.Scheme.ToLowerInvariant()
                    scheme = $authType.Name.ToLowerInvariant()
                }
                if ($authType.Arguments.Description) {
                    $_authObj.description = $authType.Arguments.Description
                }
            }
            if (!$def.components.securitySchemes) {
                $def.components.securitySchemes = [ordered]@{}
            }
            $def.components.securitySchemes[$_authName] = $_authObj
        }

        if ($Definition.Security.Definition -and $Definition.Security.Definition.Length -gt 0) {
            $def['security'] = @($Definition.Security.Definition)
        }
    }

    if ($MetaInfo.RouteFilter) {
        $filter = "^$($MetaInfo.RouteFilter)"
    }
    else {
        $filter = ''
    }

    foreach ($method in $PodeContext.Server.Routes.Keys) {
        foreach ($path in ($PodeContext.Server.Routes[$method].Keys | Sort-Object)) {
            # does it match the route?
            if ($path -inotmatch $filter) {
                continue
            }
            # the current route
            $_routes = @($PodeContext.Server.Routes[$method][$path])
            if ( $MetaInfo -and $MetaInfo.RestrictRoutes) {
                $_routes = @(Get-PodeRouteByUrl -Routes $_routes -EndpointName $EndpointName)
            }

            # continue if no routes
            if (($_routes.Length -eq 0) -or ($null -eq $_routes[0])) {
                continue
            }

            # get the first route for base definition
            $_route = $_routes[0]
            # check if the route has to be published
            if (($_route.OpenApi.Swagger -and $_route.OpenApi.DefinitionTag -contains $DefinitionTag ) -or $Definition.hiddenComponents.enableMinimalDefinitions) {

                #remove the ServerUrl part
                if ( $localEndpoint) {
                    $_route.OpenApi.Path = $_route.OpenApi.Path.replace($localEndpoint, '')
                }
                # do nothing if it has no responses set
                if ($_route.OpenApi.Responses.Count -eq 0) {
                    continue
                }

                # add path to defintion
                if ($null -eq $def.paths[$_route.OpenApi.Path]) {
                    $def.paths[$_route.OpenApi.Path] = [ordered]@{}
                }
                # add path's http method to defintition

                $pm = Set-PodeOpenApiRouteValue -Route $_route -DefinitionTag $DefinitionTag
                if ($pm.responses.Count -eq 0) {
                    $pm.responses += [ordered]@{
                        'default' = [ordered]@{'description' = 'No description' }
                    }
                }
                $def.paths[$_route.OpenApi.Path][$method] = $pm

                # add any custom server endpoints for route
                foreach ($_route in $_routes) {

                    if ($_route.OpenApi.Servers.count -gt 0) {
                        if ($null -eq $def.paths[$_route.OpenApi.Path][$method].servers) {
                            $def.paths[$_route.OpenApi.Path][$method].servers = @()
                        }
                        if ($localEndpoint) {
                            $def.paths[$_route.OpenApi.Path][$method].servers += $Definition.servers[0]
                        }
                    }
                    if (![string]::IsNullOrWhiteSpace($_route.Endpoint.Address) -and ($_route.Endpoint.Address -ine '*:*')) {

                        if ($null -eq $def.paths[$_route.OpenApi.Path][$method].servers) {
                            $def.paths[$_route.OpenApi.Path][$method].servers = @()
                        }

                        $serverDef = $null
                        if (![string]::IsNullOrWhiteSpace($_route.Endpoint.Name)) {
                            $serverDef = [ordered]@{
                                url = (Get-PodeEndpointByName -Name $_route.Endpoint.Name).Url
                            }
                        }
                        else {
                            $serverDef = [ordered]@{
                                url = "$($_route.Endpoint.Protocol)://$($_route.Endpoint.Address)"
                            }
                        }

                        if ($null -ne $serverDef) {
                            $def.paths[$_route.OpenApi.Path][$method].servers += $serverDef
                        }
                    }
                }
            }
        }
    }

    #deal with the external OpenAPI paths
    if ( $Definition.hiddenComponents.externalPath) {
        foreach ($extPath in $Definition.hiddenComponents.externalPath.values) {
            foreach ($method in $extPath.keys) {
                $_route = $extPath[$method]
                if (! ( $def.paths.keys -ccontains $_route.Path)) {
                    $def.paths[$_route.OpenAPI.Path] = [ordered]@{}
                }
                $pm = Set-PodeOpenApiRouteValue -Route $_route -DefinitionTag $DefinitionTag
                # add path's http method to defintition
                $def.paths[$_route.OpenAPI.Path][$method.ToLower()] = $pm
            }
        }
    }
    return $def
}

<#
.SYNOPSIS
    Converts a cmdlet parameter to a Pode OpenAPI property.

.DESCRIPTION
    This internal function takes a cmdlet parameter and converts it into an appropriate Pode OpenAPI property based on its type.
    The function supports boolean, integer, float, and string parameter types.

.PARAMETER Parameter
    The cmdlet parameter metadata that needs to be converted. This parameter is mandatory and accepts values from the pipeline.

.EXAMPLE
    $metadata = Get-Command -Name Get-Process | Select-Object -ExpandProperty Parameters
    $metadata.Values | ConvertTo-PodeOAPropertyFromCmdletParameter

.NOTES
    This is an internal function and may change in future releases of Pode.
#>
function ConvertTo-PodeOAPropertyFromCmdletParameter {
    param(
        [Parameter(Mandatory = $true, ValueFromPipeline = $true)]
        [System.Management.Automation.ParameterMetadata]
        $Parameter
    )
    process {
        if ($Parameter.SwitchParameter -or ($Parameter.ParameterType.Name -ieq 'boolean')) {
            New-PodeOABoolProperty -Name $Parameter.Name
        }
        else {
            switch ($Parameter.ParameterType.Name) {
                { @('int32', 'int64') -icontains $_ } {
                    New-PodeOAIntProperty -Name $Parameter.Name -Format $_
                }

                { @('double', 'float') -icontains $_ } {
                    New-PodeOANumberProperty -Name $Parameter.Name -Format $_
                }
            }
        }

        New-PodeOAStringProperty -Name $Parameter.Name
    }
}


<#
.SYNOPSIS
    Creates a base OpenAPI object structure.

.DESCRIPTION
    The Get-PodeOABaseObject function generates a foundational structure for an OpenAPI object.
    This structure includes empty ordered dictionaries for info, paths, webhooks, components, and other OpenAPI elements.
    It is used as a base template for building OpenAPI documentation in the Pode framework.

.OUTPUTS
    Hashtable
    Returns a hashtable representing the base structure of an OpenAPI object.

.EXAMPLE
    $baseObject = Get-PodeOABaseObject

    This example creates a base OpenAPI object structure.

.NOTES
    This is an internal function and may change in future releases of Pode.
#>
function Get-PodeOABaseObject {
    # Returns a base template for an OpenAPI object
    return @{
        info             = [ordered]@{}
        Path             = $null
        webhooks         = [ordered]@{}
        components       = [ordered]@{
            schemas         = [ordered]@{}
            responses       = [ordered]@{}
            parameters      = [ordered]@{}
            examples        = [ordered]@{}
            requestBodies   = [ordered]@{}
            headers         = [ordered]@{}
            securitySchemes = [ordered]@{}
            links           = [ordered]@{}
            callbacks       = [ordered]@{}
            pathItems       = [ordered]@{}
        }
        Security         = @()
        tags             = [ordered]@{}
        hiddenComponents = @{
            enabled          = $false
            schemaValidation = $false
            version          = 3.0
            depth            = 20
            schemaJson       = @{}
            viewer           = @{}
            postValidation   = @{
                schemas         = [ordered]@{}
                responses       = [ordered]@{}
                parameters      = [ordered]@{}
                examples        = [ordered]@{}
                requestBodies   = [ordered]@{}
                headers         = [ordered]@{}
                securitySchemes = [ordered]@{}
                links           = [ordered]@{}
                callbacks       = [ordered]@{}
                pathItems       = [ordered]@{}
            }
            externalPath     = [ordered]@{}
            defaultResponses = [ordered]@{
                '200'     = [ordered]@{ description = 'OK' }
                'default' = [ordered]@{ description = 'Internal server error' }
            }
            operationId      = @()
        }
    }
}

<#
.SYNOPSIS
Initializes a table to manage OpenAPI definitions.

.DESCRIPTION
The Initialize-PodeOpenApiTable function creates a table to manage OpenAPI definitions within the Pode framework.
It sets up a default definition tag and initializes a dictionary to hold OpenAPI definitions for each tag.
The function is essential for managing OpenAPI documentation across different parts of the application.

.PARAMETER DefaultDefinitionTag
An optional parameter to set the default OpenAPI definition tag. If not provided, 'default' is used.

.OUTPUTS
Hashtable
Returns a hashtable for managing OpenAPI definitions.

.EXAMPLE
$openApiTable = Initialize-PodeOpenApiTable -DefaultDefinitionTag 'api-v1'

Initializes the OpenAPI table with 'api-v1' as the default definition tag.

.EXAMPLE
$openApiTable = Initialize-PodeOpenApiTable

Initializes the OpenAPI table with 'default' as the default definition tag.

.NOTES
This is an internal function and may change in future releases of Pode.
#>
function Initialize-PodeOpenApiTable {
    param(
        [string]
        $DefaultDefinitionTag = 'default'
    )
    # Initialization of the OpenAPI table with default settings
    $OpenAPI = @{
        DefinitionTagSelectionStack = [System.Collections.Generic.Stack[System.Object]]::new()
    }

    # Set the currently selected definition tag
    $OpenAPI['SelectedDefinitionTag'] = $DefaultDefinitionTag

    # Initialize the Definitions dictionary with a base OpenAPI object for the selected definition tag
    $OpenAPI['Definitions'] = @{ $OpenAPI['SelectedDefinitionTag'] = Get-PodeOABaseObject }

    # Return the initialized OpenAPI table
    return $OpenAPI
}

<#
.SYNOPSIS
Sets authentication methods for specific routes in OpenAPI documentation.

.DESCRIPTION
The Set-PodeOAAuth function assigns specified authentication methods to given routes for OpenAPI documentation.
It supports setting multiple authentication methods and optionally allows anonymous access.
The function validates the existence of the authentication methods before applying them to the routes.

.PARAMETER Route
An array of hashtables representing the routes to which the authentication methods will be applied.
Each route should contain an OpenApi key for updating OpenAPI documentation.

.PARAMETER Name
An array of names of the authentication methods to be applied to the routes.
These methods should already be defined in the Pode framework.

.PARAMETER AllowAnon
A switch parameter that, if set, allows anonymous access in addition to the specified authentication methods.

.EXAMPLE
Set-PodeOAAuth -Route $myRoute -Name @('BasicAuth', 'ApiKeyAuth') -AllowAnon

Applies 'BasicAuth' and 'ApiKeyAuth' authentication methods to the specified route and allows anonymous access.

.NOTES
This is an internal function and may change in future releases of Pode.
#>
function Set-PodeOAAuth {
    param(
        [Parameter(Mandatory = $true, ValueFromPipeline = $true)]
        [ValidateNotNullOrEmpty()]
        [hashtable[]]
        $Route,

        [string[]]
        $Name,

        [switch]
        $AllowAnon
    )
    begin {
        # Validate the existence of specified authentication methods
        foreach ($n in @($Name)) {
            if (!(Test-PodeAuthExists -Name $n)) {
                throw ($PodeLocale.authenticationMethodDoesNotExistExceptionMessage -f $n) #"Authentication method does not exist: $($n)"
            }
        }
    }

    process {
        # Iterate over each route to set authentication
        foreach ($r in @($Route)) {
            #exclude static route
            if ($r.Method -ne 'Static') {
                # Set the authentication methods for the route
                $r.OpenApi.Authentication = @(foreach ($n in @($Name)) {
                        @{
                            "$($n -replace '\s+', '')" = @() # Clean up auth name and initialize empty scopes
                        }
                    })
                # Add anonymous access if allowed
                if ($AllowAnon) {
                    $r.OpenApi.Authentication += [ordered]@{'%_allowanon_%' = '' }
                }
            }
        }
    }
}


<#
.SYNOPSIS
Sets global authentication methods for specified OpenAPI definitions in the Pode framework.

.DESCRIPTION
The Set-PodeOAGlobalAuth function is used to apply authentication methods globally to specified OpenAPI definitions.
It verifies the existence of the authentication methods and then updates the OpenAPI definitions with these methods,
associating them with specific routes.

.PARAMETER Name
The name of the authentication method to apply. This method should already be defined in the Pode framework.

.PARAMETER Route
The route to which the authentication method is to be applied.

.PARAMETER DefinitionTag
An array of definition tags specifying the OpenAPI definitions to which the authentication method should be applied.

.EXAMPLE
Set-PodeOAGlobalAuth -Name 'BasicAuth' -Route '/api/*' -DefinitionTag @('tag1', 'tag2')

Applies 'BasicAuth' authentication method to all routes under '/api/*' in the OpenAPI definitions tagged with 'tag1' and 'tag2'.

.NOTES
This is an internal function and may change in future releases of Pode.
#>
function Set-PodeOAGlobalAuth {
    param(
        [string]
        $Name,

        [string]
        $Route,

        [Parameter(Mandatory = $true)]
        [string[]]
        $DefinitionTag
    )

    # Check if the specified authentication method exists
    if (!(Test-PodeAuthExists -Name $Name)) {
        throw ($PodeLocale.authenticationMethodDoesNotExistExceptionMessage -f $Name) #"Authentication method does not exist: $($Name)"
    }

    # Iterate over each definition tag to apply the authentication method
    foreach ($tag in $DefinitionTag) {
        # Initialize security array if it's empty
        if (Test-PodeIsEmpty $PodeContext.Server.OpenAPI.Definitions[$tag].Security) {
            $PodeContext.Server.OpenAPI.Definitions[$tag].Security = @()
        }

        # Apply authentication to each expanded auth name
        foreach ($authName in (Expand-PodeAuthMerge -Names $Name)) {
            $authType = Get-PodeAuth $authName

            # Determine the scopes of the authentication
            if ($authType.Scheme.Arguments.Scopes) {
                $Scopes = @($authType.Scheme.Arguments.Scopes)
            }
            else {
                $Scopes = @()
            }

            # Update the OpenAPI definition with the authentication information
            $PodeContext.Server.OpenAPI.Definitions[$tag].Security += [ordered]@{
                Definition = [ordered]@{ "$($authName -replace '\s+', '')" = $Scopes }
                Route      = (ConvertTo-PodeRouteRegex -Path $Route)
            }
        }
    }
}

<#
.SYNOPSIS
    Resolves references in an OpenAPI schema component based on definitions within a specified definition tag context.

.DESCRIPTION
    This function navigates through a schema's properties and resolves `$ref` references to actual schemas defined within the specified definition context.
    It handles complex constructs such as 'allOf', 'oneOf', and 'anyOf', merging properties and ensuring the schema is fully resolved without unresolved references.

.PARAMETER ComponentSchema
    A hashtable representing the schema of a component where references need to be resolved.

.PARAMETER DefinitionTag
    A string identifier for the specific set of schema definitions under which references should be resolved.

.EXAMPLE
    $schema = [ordered]@{
        type = 'object';
        properties = [ordered]@{
            name = [ordered]@{
                type = 'string'
            };
            details = [ordered]@{
                '$ref' = '#/components/schemas/UserDetails'
            }
        };
    }
    Resolve-PodeOAReference -ComponentSchema $schema -DefinitionTag 'v1'

    This example demonstrates resolving a reference to 'UserDetails' within a given component schema.
#>
function Resolve-PodeOAReference {
    param(
        [Parameter(Mandatory = $true, ValueFromPipeline = $true)]
        [hashtable]
        $ComponentSchema,

        [Parameter(Mandatory = $true)]
        [string]
        $DefinitionTag
    )

    begin {
        # Initialize schema storage and a list to track keys that need resolution
        $Schemas = $PodeContext.Server.OpenAPI.Definitions[$DefinitionTag].hiddenComponents.schemaJson
        $Keys = @()
    }

    process {
        # Gather all keys from properties and directly from the schema that might have references
        if ($ComponentSchema.properties) {
            foreach ($item in $ComponentSchema.properties.Keys) {
                $Keys += $item
            }
        }
        foreach ($item in $ComponentSchema.Keys) {
            if ( @('allof', 'oneof', 'anyof') -icontains $item ) {
                $Keys += $item
            }
        }

        # Process each key to resolve references or merge schema definitions
        foreach ($key in $Keys) {
            if ( @('allof', 'oneof', 'anyof') -icontains $key ) {
                # Handle complex schema constructs like allOf, oneOf, and anyOf
                switch ($key.ToLower()) {
                    'allof' {
                        $tmpProp = @()
                        foreach ( $comp in $ComponentSchema[$key] ) {
                            if ($comp.'$ref') {
                                # Resolve $ref to a schema if it starts with the expected path
                                if (($comp.'$ref').StartsWith('#/components/schemas/')) {
                                    $refName = ($comp.'$ref') -replace '#/components/schemas/', ''
                                    if ($Schemas.ContainsKey($refName)) {
                                        $tmpProp += $Schemas[$refName].schema
                                    }
                                }
                            }
                            elseif ( $comp.properties) {
                                # Recursively resolve nested schemas
                                if ($comp.type -eq 'object') {
                                    $tmpProp += Resolve-PodeOAReference -DefinitionTag $DefinitionTag -ComponentSchema$comp
                                }
                                else {
                                    # Unsupported object
                                    throw ($PodeLocale.unsupportedObjectExceptionMessage)
                                }
                            }
                        }
                        # Update the main schema to be an object and add resolved properties
                        $ComponentSchema.type = 'object'
                        $ComponentSchema.remove('allOf')
                        if ($tmpProp.count -gt 0) {
                            foreach ($t in $tmpProp) {
                                $ComponentSchema.properties += $t.properties
                            }
                        }

                    }
                    'oneof' {
                        # Throw an error for unsupported schema constructs to notify the user
                        # Validation of schema with oneof is not supported
                        throw ($PodeLocale.validationOfOneOfSchemaNotSupportedExceptionMessage)
                    }
                    'anyof' {
                        # Throw an error for unsupported schema constructs to notify the user
                        # Validation of schema with anyof is not supported
                        throw ($PodeLocale.validationOfAnyOfSchemaNotSupportedExceptionMessage)
                    }
                }
            }
            elseif ($ComponentSchema.properties[$key].type -eq 'object') {
                # Recursively resolve object-type properties
                $ComponentSchema.properties[$key].properties = Resolve-PodeOAReference -DefinitionTag $DefinitionTag -ComponentSchema $ComponentSchema.properties[$key].properties
            }
            elseif ($ComponentSchema.properties[$key].'$ref') {
                # Resolve property references within the main properties of the schema
                if (($ComponentSchema.properties[$key].'$ref').StartsWith('#/components/schemas/')) {
                    $refName = ($ComponentSchema.properties[$key].'$ref') -replace '#/components/schemas/', ''
                    if ($Schemas.ContainsKey($refName)) {
                        $ComponentSchema.properties[$key] = $Schemas[$refName].schema
                    }
                }
            }
            elseif ($ComponentSchema.properties[$key].items -and $ComponentSchema.properties[$key].items.'$ref' ) {
                if (($ComponentSchema.properties[$key].items.'$ref').StartsWith('#/components/schemas/')) {
                    $refName = ($ComponentSchema.properties[$key].items.'$ref') -replace '#/components/schemas/', ''
                    if ($Schemas.ContainsKey($refName)) {
                        $ComponentSchema.properties[$key].items = $schemas[$refName].schema
                    }
                }
            }
        }
    }

    end {
        # Return the fully resolved component schema
        return $ComponentSchema
    }
}

<#
.SYNOPSIS
    Creates a new OpenAPI property object based on provided parameters.

.DESCRIPTION
    The New-PodeOAPropertyInternal function constructs an OpenAPI property object using parameters like type, name,
    description, and various other attributes. It is used internally for building OpenAPI documentation elements in the Pode framework.

.PARAMETER Type
    The type of the property. This parameter is optional if the type is specified in the Params hashtable.

.PARAMETER Params
    A hashtable containing various attributes of the property such as name, description, format, and constraints like
    required, readOnly, writeOnly, etc.

.OUTPUTS
    System.Collections.Specialized.OrderedDictionary
    An ordered dictionary representing the constructed OpenAPI property object.

.EXAMPLE
    $property = New-PodeOAPropertyInternal -Type 'string' -Params $myParams

    Demonstrates how to create an OpenAPI property object of type 'string' using the specified parameters.

.NOTES
    This is an internal function and may change in future releases of Pode.
#>
function New-PodeOAPropertyInternal {
    [OutputType([System.Collections.Specialized.OrderedDictionary])]
    param (
        [String]
        $Type,

        [Parameter(Mandatory = $true)]
        [hashtable]
        $Params

    )

    # Initialize an ordered dictionary for the property
    $param = [ordered]@{}

    # Set the type of the property
    if ($type) {
        $param.type = $type
    }
    else {
        if ( $Params.type) {
            $param.type = $Params.type
        }
        else {
            # Cannot create the property no type is defined
            throw ($PodeLocale.cannotCreatePropertyWithoutTypeExceptionMessage)
        }
    }

    # Set name if provided
    if ($Params.Name) {
        $param.name = $Params.Name
    }

    # Set description if provided
    if ($Params.Description) {
        $param.description = $Params.Description
    }

    # Additional property settings based on provided parameters
    if ($Params.Array.IsPresent) { $param.array = $Params.Array.IsPresent }

    if ($Params.Object.IsPresent) { $param.object = $Params.Object.IsPresent }

    if ($Params.Required.IsPresent) { $param.required = $Params.Required.IsPresent }

    if ($Params.Default) { $param.default = $Params.Default }

    if ($Params.Format) { $param.format = $Params.Format.ToLowerInvariant() }

    if ($Params.Deprecated.IsPresent) { $param.deprecated = $Params.Deprecated.IsPresent }

    if ($Params.Nullable.IsPresent) { $param.nullable = $Params.Nullable.IsPresent }

    if ($Params.WriteOnly.IsPresent) { $param.writeOnly = $Params.WriteOnly.IsPresent }

    if ($Params.ReadOnly.IsPresent) { $param.readOnly = $Params.ReadOnly.IsPresent }

    if ($Params.Example) { $param.example = $Params.Example }

    if ($Params.UniqueItems.IsPresent) { $param.uniqueItems = $Params.UniqueItems.IsPresent }

    if ($Params.ContainsKey('MaxItems')) { $param.maxItems = $Params.MaxItems }

    if ($Params.ContainsKey('MinItems')) { $param.minItems = $Params.MinItems }

    if ($Params.Enum) { $param.enum = $Params.Enum }

    if ($Params.ContainsKey('Minimum')) { $param.minimum = $Params.Minimum }

    if ($Params.ContainsKey('Maximum')) { $param.maximum = $Params.Maximum }

    if ($Params.ExclusiveMaximum.IsPresent) { $param.exclusiveMaximum = $Params.ExclusiveMaximum.IsPresent }

    if ($Params.ExclusiveMinimum.IsPresent) { $param.exclusiveMinimum = $Params.ExclusiveMinimum.IsPresent }
    if ($Params.MultiplesOf) { $param.multipleOf = $Params.MultiplesOf }

    if ($Params.Pattern) { $param.pattern = $Params.Pattern }

    if ($Params.ContainsKey('MinLength')) { $param.minLength = $Params.MinLength }

    if ($Params.ContainsKey('MaxLength')) { $param.maxLength = $Params.MaxLength }

    if ($Params.ContainsKey('MinProperties')) { $param.minProperties = $Params.MinProperties }

    if ($Params.ContainsKey('MaxProperties')) { $param.maxProperties = $Params.MaxProperties }

    if ($Params.XmlName -or $Params.XmlNamespace -or $Params.XmlPrefix -or $Params.XmlAttribute.IsPresent -or $Params.XmlWrapped.IsPresent) {

        $param.xml = [ordered]@{}

        if ($Params.XmlName) { $param.xml.name = $Params.XmlName }

        if ($Params.XmlNamespace) { $param.xml.namespace = $Params.XmlNamespace }

        if ($Params.XmlPrefix) { $param.xml.prefix = $Params.XmlPrefix }

        if ($Params.XmlAttribute.IsPresent) { $param.xml.attribute = $Params.XmlAttribute.IsPresent }

        if ($Params.XmlWrapped.IsPresent) { $param.xml.wrapped = $Params.XmlWrapped.IsPresent }
    }

    if ($Params.XmlItemName) { $param.xmlItemName = $Params.XmlItemName }

    if ($Params.ExternalDocs) { $param.externalDocs = $Params.ExternalDocs }

    if ($Params.NoAdditionalProperties.IsPresent -and $Params.AdditionalProperties) {
        # Parameters 'NoAdditionalProperties' and 'AdditionalProperties' are mutually exclusive
        throw ($PodeLocale.parametersMutuallyExclusiveExceptionMessage -f 'NoAdditionalProperties', 'AdditionalProperties')
    }
    else {
        if ($Params.NoAdditionalProperties.IsPresent) { $param.additionalProperties = $false }

        if ($Params.AdditionalProperties) { $param.additionalProperties = $Params.AdditionalProperties }
    }

    return $param
}


<#
.SYNOPSIS
    Converts header properties to a format compliant with OpenAPI specifications.

.DESCRIPTION
    The ConvertTo-PodeOAHeaderProperty function is designed to take an array of hashtables representing header properties and
    convert them into a structure suitable for OpenAPI documentation. It ensures that each header property includes a name and
    schema definition and can handle additional attributes like description.

.PARAMETER Headers
    An array of hashtables, where each hashtable represents a header property with attributes like name, type, description, etc.

.EXAMPLE
    $headerProperties = ConvertTo-PodeOAHeaderProperty -Headers $myHeaders

    This example demonstrates how to convert an array of header properties into a format suitable for OpenAPI documentation.

.NOTES
    This is an internal function and may change in future releases of Pode.
#>
function ConvertTo-PodeOAHeaderProperty {
    param (
        [Parameter(Mandatory = $true, ValueFromPipeline = $true)]
        [hashtable[]]
        $Headers
    )

    begin {
        # Initialize an array to hold piped-in values
        $pipelineValue = @()
        $elems = [ordered]@{}
    }

    process {
        # Add the current piped-in value to the array
        $pipelineValue += $_
    }

    end {
        # Set Headers to the array of values
        if ($pipelineValue.Count -gt 1) {
            $Headers = $pipelineValue
        }

        foreach ($e in $Headers) {
            # Ensure each header has a name
            if ($e.name) {
                $elems.$($e.name) = @{}
                # Add description if present
                if ($e.description) {
                    $elems.$($e.name).description = $e.description
                }
                # Define the schema, including the type and any additional properties
                $elems.$($e.name).schema = @{
                    type = $($e.type)
                }
                foreach ($k in $e.keys) {
                    if (@('name', 'description') -notcontains $k) {
                        $elems.$($e.name).schema.$k = $e.$k
                    }
                }
            }
            else {
                # Header requires a name when used in an encoding context
                throw ($PodeLocale.headerMustHaveNameInEncodingContextExceptionMessage)
            }
        }

        return $elems
    }
}


<#
.SYNOPSIS
    Creates a new OpenAPI callback component for a given definition tag.

.DESCRIPTION
    The New-PodeOAComponentCallBackInternal function constructs an OpenAPI callback component based on provided parameters.
    This function is designed for internal use within the Pode framework to define callbacks in OpenAPI documentation.
    It handles the creation of callback structures including the path, HTTP method, request bodies, and responses
    based on the given definition tag.

.PARAMETER Params
    A hashtable containing parameters for the callback component, such as Method, Path, RequestBody, and Responses.

.PARAMETER DefinitionTag
    A mandatory string parameter that specifies the definition tag in OpenAPI documentation.

.EXAMPLE
    $callback = New-PodeOAComponentCallBackInternal -Params $myParams -DefinitionTag 'myTag'

    This example demonstrates how to create an OpenAPI callback component for 'myTag' using the provided parameters.

.NOTES
    This is an internal function and may change in future releases of Pode.
#>
function New-PodeOAComponentCallBackInternal {
    param(
        [Parameter(Mandatory = $true)]
        [hashtable]
        $Params,

        [Parameter(Mandatory = $true)]
        [string]
        $DefinitionTag
    )

    # Convert HTTP method to lower case
    $_method = $Params.Method.ToLower()

    # Construct the base structure for the callback with the given path and method
    $callBack = [ordered]@{
        "'$($Params.Path)'" = [ordered]@{
            $_method = [ordered]@{}
        }
    }

    # Add request body to the callback if it is specified for the given definition tag
    if ($Params.RequestBody.ContainsKey($DefinitionTag)) {
        $callBack."'$($Params.Path)'".$_method.requestBody = $Params.RequestBody[$DefinitionTag]
    }

    # Add responses to the callback if they are specified for the given definition tag
    if ($Params.Responses.ContainsKey($DefinitionTag)) {
        $callBack."'$($Params.Path)'".$_method.responses = $Params.Responses[$DefinitionTag]
    }

    # Return the constructed callback object
    return $callBack

}

<#
.SYNOPSIS
        Creates a new OpenAPI response object based on provided parameters and a definition tag.

    .DESCRIPTION
        The New-PodeOResponseInternal function constructs an OpenAPI response object using provided parameters.
        It sets a description for the status code, references existing components if specified,
        and builds content-type and header schemas. This function is intended for internal use within the
        Pode framework for API documentation purposes.

    .PARAMETER Params
        A hashtable containing parameters for building the OpenAPI response object, including description,
        status code, content, headers, links, and reference to existing components.

    .PARAMETER DefinitionTag
        A mandatory string parameter that specifies the definition tag in OpenAPI documentation.

    .EXAMPLE
        $response = New-PodeOResponseInternal -Params $myParams -DefinitionTag 'myTag'

        This example demonstrates how to create an OpenAPI response object for 'myTag' using the provided parameters.

    .NOTES
        This is an internal function and may change in future releases of Pode.
#>
function New-PodeOResponseInternal {
    param(
        [hashtable]
        $Params,

        [Parameter(Mandatory = $true)]
        [string]
        $DefinitionTag
    )

    # Set a general description for the status code
    if ([string]::IsNullOrWhiteSpace($Params.Description)) {
        if ($Params.Default) {
            $Description = 'Default Response.'
        }
        elseif ([int]::TryParse($Params.StatusCode, [ref]$null)) {
            $Description = Get-PodeStatusDescription -StatusCode $Params.StatusCode
        }
        else {
            # A Description is required
            throw ($PodeLocale.descriptionRequiredExceptionMessage -f $params.Route.path, $Params.StatusCode )
        }
    }
    else {
        $Description = $Params.Description
    }

    # Handle response referencing an existing component
    if ($Params.Reference) {
        Test-PodeOAComponentInternal -Field responses -DefinitionTag $DefinitionTag -Name $Params.Reference -PostValidation
        $response = [ordered]@{
            '$ref' = "#/components/responses/$($Params.Reference)"
        }
    }
    else {
        # Build content-type schemas if provided
        $_content = $null
        if ($null -ne $Params.Content) {
            $_content = ConvertTo-PodeOAObjectSchema -DefinitionTag $DefinitionTag -Content $Params.Content
        }

        # Build header schemas based on the type of the Headers parameter
        $_headers = $null
        if ($null -ne $Params.Headers) {
            if ($Params.Headers -is [System.Object[]] -or $Params.Headers -is [string] -or $Params.Headers -is [string[]]) {
                if ($Params.Headers -is [System.Object[]] -and $Params.Headers.Count -gt 0 -and ($Params.Headers[0] -is [hashtable] -or $Params.Headers[0] -is [System.Collections.Specialized.OrderedDictionary])) {
                    $_headers = ConvertTo-PodeOAHeaderProperty -Headers $Params.Headers
                }
                else {
                    $_headers = [ordered]@{}
                    foreach ($h in $Params.Headers) {
                        Test-PodeOAComponentInternal -Field headers -DefinitionTag $DefinitionTag -Name $h -PostValidation
                        $_headers[$h] = [ordered]@{
                            '$ref' = "#/components/headers/$h"
                        }
                    }
                }
            }
            elseif ($Params.Headers -is [hashtable]) {
                $_headers = ConvertTo-PodeOAObjectSchema -DefinitionTag $DefinitionTag -Content $Params.Headers
            }
        }

        # Construct the response object
        $response = [ordered]@{
            description = $Description
        }

        if ($_headers) { $response.headers = $_headers }

        if ($_content) { $response.content = $_content }

        if ($Params.Links) { $response.links = $Params.Links }

    }

    return $response
}




<#
.SYNOPSIS
    Creates a new OpenAPI response link object.

.DESCRIPTION
    The New-PodeOAResponseLinkInternal function generates an OpenAPI response link object from provided parameters.
    This includes setting up descriptions, operation IDs, references, parameters, and request bodies for the link.
    This function is designed for internal use within the Pode framework to facilitate the creation of response
    link objects in OpenAPI documentation.

.PARAMETER Params
    A hashtable of parameters for the OpenAPI response link.

.EXAMPLE
    $link = New-PodeOAResponseLinkInternal -Params $myParams

    Generates a new OpenAPI response link object using the provided parameters in $myParams.

.NOTES
    This is an internal function and may change in future releases of Pode.
#>
function New-PodeOAResponseLinkInternal {
    param(
        [hashtable]
        $Params
    )

    # Initialize an ordered dictionary for the link
    $link = [ordered]@{}

    # Add properties to the link based on the provided parameters
    if ($Params.Description) { $link.description = $Params.Description }
    if ($Params.OperationId) { $link.operationId = $Params.OperationId }
    if ($Params.OperationRef) { $link.operationRef = $Params.OperationRef }
    if ($Params.Parameters) { $link.parameters = $Params.Parameters }
    if ($Params.RequestBody) { $link.requestBody = $Params.RequestBody }

    return $link
}


<#
.SYNOPSIS
Tests the internal OpenAPI definitions for compliance and validity.

.DESCRIPTION
The Test-PodeOADefinitionInternal function validates OpenAPI definitions within the Pode framework.
It checks for various issues like undefined references, mandatory fields (like title and version),
and missing components. If any issues are found, they are displayed with detailed messages, and
the function throws an error indicating non-compliance with OpenAPI document standards.

.EXAMPLE
Test-PodeOADefinitionInternal

This example demonstrates how to call the function to validate OpenAPI definitions.

.NOTES
This is an internal function and may change in future releases of Pode.
#>

function Test-PodeOADefinitionInternal {

    # Validate OpenAPI definitions and store any issues found
    $definitionIssues = Test-PodeOADefinition

    # Check if the validation result indicates issues
    if (! $definitionIssues.valid) {
        # Print a header for undefined OpenAPI references
        # Undefined OpenAPI References
        Write-PodeHost $PodeLocale.undefinedOpenApiReferencesMessage -ForegroundColor Red

        # Iterate over each issue found in the definitions
        foreach ($tag in $definitionIssues.issues.keys) {
            # Definition tag
            Write-PodeHost ($PodeLocale.definitionTagMessage -f $tag) -ForegroundColor Red

            # Check and display issues related to OpenAPI document generation error
            if ($definitionIssues.issues[$tag].definition ) {
                # OpenAPI generation document error
                Write-PodeHost $PodeLocale.openApiGenerationDocumentErrorMessage -ForegroundColor Red
                Write-PodeHost " $($definitionIssues.issues[$tag].definition)" -ForegroundColor Red
            }

            # Check for missing mandatory 'title' field
            if ($definitionIssues.issues[$tag].title ) {
                # info.title is mandatory
                Write-PodeHost $PodeLocale.infoTitleMandatoryMessage -ForegroundColor Red
            }

            # Check for missing mandatory 'version' field
            if ($definitionIssues.issues[$tag].version ) {
                # info.version is mandatory
                Write-PodeHost $PodeLocale.infoVersionMandatoryMessage -ForegroundColor Red
            }

            # Check for missing components and list them
            if ($definitionIssues.issues[$tag].components ) {
                # Missing component(s)
                Write-PodeHost $PodeLocale.missingComponentsMessage -ForegroundColor Red
                foreach ($key in $definitionIssues.issues[$tag].components.keys) {
                    $occurences = $definitionIssues.issues[$tag].components[$key]
                    # Adjust occurrence count based on schema validation setting
                    if ( $PodeContext.Server.OpenAPI.Definitions[$tag].hiddenComponents.schemaValidation) {
                        $occurences = $occurences / 2
                    }
                    Write-PodeHost "`$refs : $key ($occurences)" -ForegroundColor Red
                }
            }

            # Add a blank line for readability
            Write-PodeHost
        }

        # Throw an error indicating non-compliance with OpenAPI standards
        # OpenAPI document compliance issues
        throw ($PodeLocale.openApiDocumentNotCompliantExceptionMessage)
    }
}

<#
.SYNOPSIS
    Check the OpenAPI component exist (Internal Function)

.DESCRIPTION
    Check the OpenAPI component exist (Internal Function)

.PARAMETER Field
    The component type

.PARAMETER Name
    The component Name

.PARAMETER DefinitionTag
    An Array of strings representing the unique tag for the API specification.
    This tag helps in distinguishing between different versions or types of API specifications within the application.
    You can use this tag to reference the specific API documentation, schema, or version that your function interacts with.

.PARAMETER ThrowException
    Generate an exception if the component doesn't exist

.PARAMETER PostValidation
    Postpone the check before the server start

.EXAMPLE
    Test-PodeOAComponentInternal -Field 'responses' -Name 'myresponse' -DefinitionTag 'default'

.NOTES
    This is an internal function and may change in future releases of Pode.
#>
function Test-PodeOAComponentInternal {
    param(
        [Parameter(Mandatory = $true)]
        [ValidateSet( 'schemas' , 'responses' , 'parameters' , 'examples' , 'requestBodies' , 'headers' , 'securitySchemes' , 'links' , 'callbacks' , 'pathItems')]
        [string]
        $Field,

        [Parameter(Mandatory = $true)]
        [ValidateNotNullOrEmpty()]
        [string]
        $Name,

        [string[]]
        $DefinitionTag,

        [switch]
        $ThrowException,

        [switch]
        $PostValidation
    )

    $DefinitionTag = Test-PodeOADefinitionTag -Tag $DefinitionTag
    if ($PostValidation.IsPresent) {
        foreach ($tag in $DefinitionTag) {
            if (! ($PodeContext.Server.OpenAPI.Definitions[$tag].hiddenComponents.postValidation[$field].keys -ccontains $Name)) {
                $PodeContext.Server.OpenAPI.Definitions[$tag].hiddenComponents.postValidation[$field][$name] = 1
            }
            else {
                $PodeContext.Server.OpenAPI.Definitions[$tag].hiddenComponents.postValidation[$field][$name] += 1
            }
        }
    }
    else {
        foreach ($tag in $DefinitionTag) {
            if (!($PodeContext.Server.OpenAPI.Definitions[$tag].components[$field].keys -ccontains $Name)) {
                # If $Name is not found in the current $tag, return $false or throw an exception
                if ($ThrowException.IsPresent ) {
                    throw ($PodeLocale.noComponentInDefinitionExceptionMessage -f $field, $Name, $tag) #"No component of type $field named $Name is available in the $tag definition."
                }
                else {
                    return $false
                }
            }
        }
        if (!$ThrowException.IsPresent) {
            return $true
        }
    }
}
src\Private\PodeServer.ps1
using namespace Pode

function Start-PodeWebServer {
    param(
        [switch]
        $Browse
    )

    # setup any inbuilt middleware
    $inbuilt_middleware = @(
        (Get-PodeSecurityMiddleware),
        (Get-PodeAccessMiddleware),
        (Get-PodeLimitMiddleware),
        (Get-PodePublicMiddleware),
        (Get-PodeRouteValidateMiddleware),
        (Get-PodeBodyMiddleware),
        (Get-PodeQueryMiddleware),
        (Get-PodeCookieMiddleware)
    )

    $PodeContext.Server.Middleware = ($inbuilt_middleware + $PodeContext.Server.Middleware)

    # work out which endpoints to listen on
    $endpoints = @()
    $endpointsMap = @{}

    @(Get-PodeEndpointByProtocolType -Type Http, Ws) | ForEach-Object {
        # get the ip address
        $_ip = [string]($_.Address)
        $_ip = Get-PodeIPAddressesForHostname -Hostname $_ip -Type All | Select-Object -First 1
        $_ip = Get-PodeIPAddress -IP $_ip -DualMode:($_.DualMode)

        # dual mode?
        $addrs = $_ip
        if ($_.DualMode) {
            $addrs = Resolve-PodeIPDualMode -IP $_ip
        }

        # the endpoint
        $_endpoint = @{
            Name                   = $_.Name
            Key                    = "$($_ip):$($_.Port)"
            Address                = $addrs
            Hostname               = $_.HostName
            IsIPAddress            = $_.IsIPAddress
            Port                   = $_.Port
            Certificate            = $_.Certificate.Raw
            AllowClientCertificate = $_.Certificate.AllowClientCertificate
            Url                    = $_.Url
            Protocol               = $_.Protocol
            Type                   = $_.Type
            Pool                   = $_.Runspace.PoolName
            SslProtocols           = $_.Ssl.Protocols
            DualMode               = $_.DualMode
        }

        # add endpoint to list
        $endpoints += $_endpoint

        # add to map
        if (!$endpointsMap.ContainsKey($_endpoint.Key)) {
            $endpointsMap[$_endpoint.Key] = @{ Type = $_.Type }
        }
        else {
            if ($endpointsMap[$_endpoint.Key].Type -ine $_.Type) {
                $endpointsMap[$_endpoint.Key].Type = 'HttpAndWs'
            }
        }
    }

    # create the listener
    $listener = (. ([scriptblock]::Create("New-Pode$($PodeContext.Server.ListenerType)Listener -CancellationToken `$PodeContext.Tokens.Cancellation.Token")))
    $listener.ErrorLoggingEnabled = (Test-PodeErrorLoggingEnabled)
    $listener.ErrorLoggingLevels = @(Get-PodeErrorLoggingLevel)
    $listener.RequestTimeout = $PodeContext.Server.Request.Timeout
    $listener.RequestBodySize = $PodeContext.Server.Request.BodySize
    $listener.ShowServerDetails = [bool]$PodeContext.Server.Security.ServerDetails

    try {
        # register endpoints on the listener
        $endpoints | ForEach-Object {
            $socket = (. ([scriptblock]::Create("New-Pode$($PodeContext.Server.ListenerType)ListenerSocket -Name `$_.Name -Address `$_.Address -Port `$_.Port -SslProtocols `$_.SslProtocols -Type `$endpointsMap[`$_.Key].Type -Certificate `$_.Certificate -AllowClientCertificate `$_.AllowClientCertificate -DualMode:`$_.DualMode")))
            $socket.ReceiveTimeout = $PodeContext.Server.Sockets.ReceiveTimeout

            if (!$_.IsIPAddress) {
                $socket.Hostnames.Add($_.HostName)
            }

            $listener.Add($socket)
        }

        $listener.Start()
        $PodeContext.Listeners += $listener
        $PodeContext.Server.Signals.Enabled = $true
        $PodeContext.Server.Signals.Listener = $listener
        $PodeContext.Server.Http.Listener = $listener
    }
    catch {
        $_ | Write-PodeErrorLog
        $_.Exception | Write-PodeErrorLog -CheckInnerException
        Close-PodeDisposable -Disposable $listener
        throw $_.Exception
    }

    # only if HTTP endpoint
    if (Test-PodeEndpointByProtocolType -Type Http) {
        # script for listening out for incoming requests
        $listenScript = {
            param(
                [Parameter(Mandatory = $true)]
                $Listener,

                [Parameter(Mandatory = $true)]
                [int]
                $ThreadId
            )

            try {
                while ($Listener.IsConnected -and !$PodeContext.Tokens.Cancellation.IsCancellationRequested) {
                    # get request and response
                    $context = (Wait-PodeTask -Task $Listener.GetContextAsync($PodeContext.Tokens.Cancellation.Token))

                    try {
                        try {
                            $Request = $context.Request
                            $Response = $context.Response

                            # reset with basic event data
                            $WebEvent = @{
                                OnEnd            = @()
                                Auth             = @{}
                                Response         = $Response
                                Request          = $Request
                                Lockable         = $PodeContext.Threading.Lockables.Global
                                Path             = [System.Web.HttpUtility]::UrlDecode($Request.Url.AbsolutePath)
                                Method           = $Request.HttpMethod.ToLowerInvariant()
                                Query            = $null
                                Endpoint         = @{
                                    Protocol = $Request.Url.Scheme
                                    Address  = $Request.Host
                                    Name     = $context.EndpointName
                                }
                                ContentType      = $Request.ContentType
                                ErrorType        = $null
                                Cookies          = @{}
                                PendingCookies   = @{}
                                Parameters       = $null
                                Data             = $null
                                Files            = $null
                                Streamed         = $true
                                Route            = $null
                                StaticContent    = $null
                                Timestamp        = [datetime]::UtcNow
                                TransferEncoding = $null
                                AcceptEncoding   = $null
                                Ranges           = $null
                                Sse              = $null
                                Metadata         = @{}
                            }

                            # if iis, and we have an app path, alter it
                            if ($PodeContext.Server.IsIIS -and $PodeContext.Server.IIS.Path.IsNonRoot) {
                                $WebEvent.Path = ($WebEvent.Path -ireplace $PodeContext.Server.IIS.Path.Pattern, '')
                                if ([string]::IsNullOrEmpty($WebEvent.Path)) {
                                    $WebEvent.Path = '/'
                                }
                            }

                            # accept/transfer encoding
                            $WebEvent.TransferEncoding = (Get-PodeTransferEncoding -TransferEncoding (Get-PodeHeader -Name 'Transfer-Encoding') -ThrowError)
                            $WebEvent.AcceptEncoding = (Get-PodeAcceptEncoding -AcceptEncoding (Get-PodeHeader -Name 'Accept-Encoding') -ThrowError)
                            $WebEvent.Ranges = (Get-PodeRange -Range (Get-PodeHeader -Name 'Range') -ThrowError)

                            # add logging endware for post-request
                            Add-PodeRequestLogEndware -WebEvent $WebEvent

                            # stop now if the request has an error
                            if ($Request.IsAborted) {
                                throw $Request.Error
                            }

                            # if we have an sse clientId, verify it and then set details in WebEvent
                            if ($WebEvent.Request.HasSseClientId) {
                                if (!(Test-PodeSseClientIdValid)) {
                                    throw [System.Net.Http.HttpRequestException]::new("The X-PODE-SSE-CLIENT-ID value is not valid: $($WebEvent.Request.SseClientId)")
                                }

                                if (![string]::IsNullOrEmpty($WebEvent.Request.SseClientName) -and !(Test-PodeSseClientId -Name $WebEvent.Request.SseClientName -ClientId $WebEvent.Request.SseClientId)) {
                                    throw [System.Net.Http.HttpRequestException]::new("The SSE Connection being referenced via the X-PODE-SSE-NAME and X-PODE-SSE-CLIENT-ID headers does not exist: [$($WebEvent.Request.SseClientName)] $($WebEvent.Request.SseClientId)")
                                }

                                $WebEvent.Sse = @{
                                    Name        = $WebEvent.Request.SseClientName
                                    Group       = $WebEvent.Request.SseClientGroup
                                    ClientId    = $WebEvent.Request.SseClientId
                                    LastEventId = $null
                                    IsLocal     = $false
                                }
                            }

                            # invoke global and route middleware
                            if ((Invoke-PodeMiddleware -Middleware $PodeContext.Server.Middleware -Route $WebEvent.Path)) {
                                # has the request been aborted
                                if ($Request.IsAborted) {
                                    throw $Request.Error
                                }

                                if ((Invoke-PodeMiddleware -Middleware $WebEvent.Route.Middleware)) {
                                    # has the request been aborted
                                    if ($Request.IsAborted) {
                                        throw $Request.Error
                                    }

                                    # invoke the route
                                    if ($null -ne $WebEvent.StaticContent) {
                                        $fileBrowser = $WebEvent.Route.FileBrowser
                                        if ($WebEvent.StaticContent.IsDownload) {
                                            Write-PodeAttachmentResponseInternal -Path $WebEvent.StaticContent.Source -FileBrowser:$fileBrowser
                                        }
                                        elseif ($WebEvent.StaticContent.RedirectToDefault) {
                                            $file = [System.IO.Path]::GetFileName($WebEvent.StaticContent.Source)
                                            Move-PodeResponseUrl -Url "$($WebEvent.Path)/$($file)"
                                        }
                                        else {
                                            $cachable = $WebEvent.StaticContent.IsCachable
                                            Write-PodeFileResponseInternal -Path $WebEvent.StaticContent.Source -MaxAge $PodeContext.Server.Web.Static.Cache.MaxAge `
                                                -Cache:$cachable -FileBrowser:$fileBrowser
                                        }
                                    }
                                    elseif ($null -ne $WebEvent.Route.Logic) {
                                        $null = Invoke-PodeScriptBlock -ScriptBlock $WebEvent.Route.Logic -Arguments $WebEvent.Route.Arguments `
                                            -UsingVariables $WebEvent.Route.UsingVariables -Scoped -Splat
                                    }
                                }
                            }
                        }
                        catch [System.OperationCanceledException] {
                            $_ | Write-PodeErrorLog -Level Debug
                        }
                        catch [System.Net.Http.HttpRequestException] {
                            if ($Response.StatusCode -ge 500) {
                                $_.Exception | Write-PodeErrorLog -CheckInnerException
                            }

                            $code = [int]($_.Exception.Data['PodeStatusCode'])
                            if ($code -le 0) {
                                $code = 400
                            }

                            Set-PodeResponseStatus -Code $code -Exception $_
                        }
                        catch {
                            $_ | Write-PodeErrorLog
                            $_.Exception | Write-PodeErrorLog -CheckInnerException
                            Set-PodeResponseStatus -Code 500 -Exception $_
                        }
                        finally {
                            Update-PodeServerRequestMetric -WebEvent $WebEvent
                        }

                        # invoke endware specifc to the current web event
                        $_endware = ($WebEvent.OnEnd + @($PodeContext.Server.Endware))
                        Invoke-PodeEndware -Endware $_endware
                    }
                    finally {
                        $WebEvent = $null
                        Close-PodeDisposable -Disposable $context
                    }
                }
            }
            catch [System.OperationCanceledException] {
                $_ | Write-PodeErrorLog -Level Debug
            }
            catch {
                $_ | Write-PodeErrorLog
                $_.Exception | Write-PodeErrorLog -CheckInnerException
                throw $_.Exception
            }
        }

        # start the runspace for listening on x-number of threads
        1..$PodeContext.Threads.General | ForEach-Object {
            Add-PodeRunspace -Type Web -Name 'Listener' -Id $_ -ScriptBlock $listenScript -Parameters @{ 'Listener' = $listener; 'ThreadId' = $_ }
        }
    }

    # only if WS endpoint
    if (Test-PodeEndpointByProtocolType -Type Ws) {
        # script to write messages back to the client(s)
        $signalScript = {
            param(
                [Parameter(Mandatory = $true)]
                $Listener
            )

            try {
                while ($Listener.IsConnected -and !$PodeContext.Tokens.Cancellation.IsCancellationRequested) {
                    $message = (Wait-PodeTask -Task $Listener.GetServerSignalAsync($PodeContext.Tokens.Cancellation.Token))

                    try {
                        # get the sockets for the message
                        $sockets = @()

                        # by clientId
                        if (![string]::IsNullOrWhiteSpace($message.ClientId)) {
                            $sockets = @($Listener.Signals[$message.ClientId])
                        }
                        else {
                            $sockets = @($Listener.Signals.Values)

                            # by path
                            if (![string]::IsNullOrWhiteSpace($message.Path)) {
                                $sockets = @(foreach ($socket in $sockets) {
                                        if ($socket.Path -ieq $message.Path) {
                                            $socket
                                        }
                                    })
                            }
                        }

                        # do nothing if no socket found
                        if (($null -eq $sockets) -or ($sockets.Length -eq 0)) {
                            continue
                        }

                        # send the message to all found sockets
                        foreach ($socket in $sockets) {
                            try {
                                $null = Wait-PodeTask -Task $socket.Context.Response.SendSignal($message)
                            }
                            catch {
                                $null = $Listener.Signals.Remove($socket.ClientId)
                            }
                        }
                    }
                    catch [System.OperationCanceledException] {
                        $_ | Write-PodeErrorLog -Level Debug
                    }
                    catch {
                        $_ | Write-PodeErrorLog
                        $_.Exception | Write-PodeErrorLog -CheckInnerException
                    }
                    finally {
                        Close-PodeDisposable -Disposable $message
                    }
                }
            }
            catch [System.OperationCanceledException] {
                $_ | Write-PodeErrorLog -Level Debug
            }
            catch {
                $_ | Write-PodeErrorLog
                $_.Exception | Write-PodeErrorLog -CheckInnerException
                throw $_.Exception
            }
        }

        Add-PodeRunspace -Type Signals -Name 'Listener' -ScriptBlock $signalScript -Parameters @{ 'Listener' = $listener }
    }

    # only if WS endpoint
    if (Test-PodeEndpointByProtocolType -Type Ws) {
        # script to queue messages from clients to send back to other clients from the server
        $clientScript = {
            param(
                [Parameter(Mandatory = $true)]
                $Listener,

                [Parameter(Mandatory = $true)]
                [int]
                $ThreadId
            )

            try {
                while ($Listener.IsConnected -and !$PodeContext.Tokens.Cancellation.IsCancellationRequested) {
                    $context = (Wait-PodeTask -Task $Listener.GetClientSignalAsync($PodeContext.Tokens.Cancellation.Token))

                    try {
                        $payload = ($context.Message | ConvertFrom-Json)
                        $Request = $context.Signal.Context.Request
                        $Response = $context.Signal.Context.Response

                        $SignalEvent = @{
                            Response  = $Response
                            Request   = $Request
                            Lockable  = $PodeContext.Threading.Lockables.Global
                            Path      = [System.Web.HttpUtility]::UrlDecode($Request.Url.AbsolutePath)
                            Data      = @{
                                Path     = [System.Web.HttpUtility]::UrlDecode($payload.path)
                                Message  = $payload.message
                                ClientId = $payload.clientId
                                Direct   = [bool]$payload.direct
                            }
                            Endpoint  = @{
                                Protocol = $Request.Url.Scheme
                                Address  = $Request.Host
                                Name     = $context.Signal.Context.EndpointName
                            }
                            Route     = $null
                            ClientId  = $context.Signal.ClientId
                            Timestamp = $context.Timestamp
                            Streamed  = $true
                            Metadata  = @{}
                        }

                        # see if we have a route and invoke it, otherwise auto-send
                        $SignalEvent.Route = Find-PodeSignalRoute -Path $SignalEvent.Path -EndpointName $SignalEvent.Endpoint.Name

                        if ($null -ne $SignalEvent.Route) {
                            $null = Invoke-PodeScriptBlock -ScriptBlock $SignalEvent.Route.Logic -Arguments $SignalEvent.Route.Arguments -UsingVariables $SignalEvent.Route.UsingVariables -Scoped -Splat
                        }
                        else {
                            Send-PodeSignal -Value $SignalEvent.Data.Message -Path $SignalEvent.Data.Path -ClientId $SignalEvent.Data.ClientId
                        }
                    }
                    catch [System.OperationCanceledException] {
                        $_ | Write-PodeErrorLog -Level Debug
                    }
                    catch {
                        $_ | Write-PodeErrorLog
                        $_.Exception | Write-PodeErrorLog -CheckInnerException
                    }
                    finally {
                        Update-PodeServerSignalMetric -SignalEvent $SignalEvent
                        Close-PodeDisposable -Disposable $context
                    }
                }
            }
            catch [System.OperationCanceledException] {
                $_ | Write-PodeErrorLog -Level Debug
            }
            catch {
                $_ | Write-PodeErrorLog
                $_.Exception | Write-PodeErrorLog -CheckInnerException
                throw $_.Exception
            }
        }

        # start the runspace for listening on x-number of threads
        1..$PodeContext.Threads.General | ForEach-Object {
            Add-PodeRunspace -Type Signals -Name 'Broadcaster' -Id $_ -ScriptBlock $clientScript -Parameters @{ 'Listener' = $listener; 'ThreadId' = $_ }
        }
    }

    # script to keep web server listening until cancelled
    $waitScript = {
        param(
            [Parameter(Mandatory = $true)]
            [ValidateNotNull()]
            $Listener
        )

        try {
            while ($Listener.IsConnected -and !$PodeContext.Tokens.Cancellation.IsCancellationRequested) {
                Start-Sleep -Seconds 1
            }
        }
        catch [System.OperationCanceledException] {
            $_ | Write-PodeErrorLog -Level Debug
        }
        catch {
            $_ | Write-PodeErrorLog
            $_.Exception | Write-PodeErrorLog -CheckInnerException
            throw $_.Exception
        }
        finally {
            Close-PodeDisposable -Disposable $Listener
        }
    }


    if (Test-PodeEndpointByProtocolType -Type Http) {
        Add-PodeRunspace -Type 'Web' -Name 'KeepAlive' -ScriptBlock $waitScript -Parameters @{ 'Listener' = $listener } -NoProfile
    }
    else {
        Add-PodeRunspace -Type 'Signals' -Name 'KeepAlive' -ScriptBlock $waitScript -Parameters @{ 'Listener' = $listener } -NoProfile
    }

    # browse to the first endpoint, if flagged
    if ($Browse) {
        Start-Process $endpoints[0].Url
    }

    return @(foreach ($endpoint in $endpoints) {
            @{
                Url      = $endpoint.Url
                Pool     = $endpoint.Pool
                DualMode = $endpoint.DualMode
            }
        })
}

function New-PodeListener {
    [CmdletBinding()]
    [OutputType([Pode.PodeListener])]
    param(
        [Parameter(Mandatory = $true)]
        [System.Threading.CancellationToken]
        $CancellationToken
    )

    return [PodeListener]::new($CancellationToken)
}

function New-PodeListenerSocket {
    [CmdletBinding()]
    [OutputType([Pode.PodeSocket])]
    param(
        [Parameter(Mandatory = $true)]
        [string]
        $Name,

        [Parameter(Mandatory = $true)]
        [ipaddress[]]
        $Address,

        [Parameter(Mandatory = $true)]
        [int]
        $Port,

        [Parameter()]
        [System.Security.Authentication.SslProtocols]
        $SslProtocols,

        [Parameter(Mandatory = $true)]
        [PodeProtocolType]
        $Type,

        [Parameter()]
        [X509Certificate]
        $Certificate,

        [Parameter()]
        [bool]
        $AllowClientCertificate,

        [switch]
        $DualMode
    )

    return [PodeSocket]::new($Name, $Address, $Port, $SslProtocols, $Type, $Certificate, $AllowClientCertificate, 'Implicit', $DualMode.IsPresent)
}
src\Private\Responses.ps1
<#
.SYNOPSIS
Displays a customized error page based on the provided error code and additional error details.

.DESCRIPTION
This function is responsible for displaying a custom error page when an error occurs within a Pode web application. It takes an error code, a description, an exception object, and a content type as input. The function then attempts to find a corresponding error page based on the error code and content type. If a custom error page is found, and if exception details are to be shown (as per server settings), it builds a detailed exception message. Finally, it writes the error page to the response stream, displaying the custom error page to the user.

.PARAMETER Code
The HTTP status code of the error. This code is used to find a matching custom error page.

.PARAMETER Description
A descriptive message about the error. This is displayed on the error page if available.

.PARAMETER Exception
The exception object that caused the error. If exception tracing is enabled, details from this object are displayed on the error page.

.PARAMETER ContentType
The content type of the error page to be displayed. This is used to select an appropriate error page format (e.g., HTML, JSON).

.EXAMPLE
Show-PodeErrorPage -Code 404 -Description "Not Found" -ContentType "text/html"

This example shows how to display a custom 404 Not Found error page in HTML format.

.OUTPUTS
None. This function writes the error page directly to the response stream.

.NOTES
- The function uses `Find-PodeErrorPage` to locate a custom error page based on the HTTP status code and content type.
- It checks for server configuration to determine whether to show detailed exception information on the error page.
- The function relies on the global `$PodeContext` variable for server settings and to encode exception and URL details safely.
- `Write-PodeFileResponse` is used to send the custom error page as the response, along with any dynamic data (e.g., exception details, URL).
- This is an internal function and may change in future releases of Pode.
#>
function Show-PodeErrorPage {
    param(
        [Parameter()]
        [int]
        $Code,

        [Parameter()]
        [string]
        $Description,

        [Parameter()]
        $Exception,

        [Parameter()]
        [string]
        $ContentType
    )

    # error page info
    $errorPage = Find-PodeErrorPage -Code $Code -ContentType $ContentType

    # if no page found, return
    if (Test-PodeIsEmpty $errorPage) {
        return
    }

    # if exception trace showing enabled then build the exception details object
    $ex = $null
    if (!(Test-PodeIsEmpty $Exception) -and $PodeContext.Server.Web.ErrorPages.ShowExceptions) {
        $ex = @{
            Message    = [System.Web.HttpUtility]::HtmlEncode($Exception.Exception.Message)
            StackTrace = [System.Web.HttpUtility]::HtmlEncode($Exception.ScriptStackTrace)
            Line       = [System.Web.HttpUtility]::HtmlEncode($Exception.InvocationInfo.PositionMessage)
            Category   = [System.Web.HttpUtility]::HtmlEncode($Exception.CategoryInfo.ToString())
        }
    }

    # setup the data object for dynamic pages
    $data = @{
        Url         = [System.Web.HttpUtility]::HtmlEncode((Get-PodeUrl))
        Status      = @{
            Code        = $Code
            Description = $Description
        }
        Exception   = $ex
        ContentType = $errorPage.ContentType
    }

    # write the error page to the stream
    Write-PodeFileResponse -Path $errorPage.Path -Data $data -ContentType $errorPage.ContentType
}



<#
.SYNOPSIS
Serves files as HTTP responses in a Pode web server, handling both dynamic and static content.

.DESCRIPTION
This function serves files from the server to the client, supporting both static files and files that are dynamically processed by a view engine.
For dynamic content, it uses the server's configured view engine to process the file and returns the rendered content.
For static content, it simply returns the file's content. The function allows for specifying content type, cache control, and HTTP status code.

.PARAMETER Path
The relative path to the file to be served. This path is resolved against the server's root directory.

.PARAMETER Data
A hashtable of data that can be passed to the view engine for dynamic files.

.PARAMETER ContentType
The MIME type of the response. If not provided, it is inferred from the file extension.

.PARAMETER MaxAge
The maximum age (in seconds) for which the response can be cached by the client. Applies only to static content.

.PARAMETER StatusCode
The HTTP status code to accompany the response. Defaults to 200 (OK).

.PARAMETER Cache
A switch to indicate whether the response should include HTTP caching headers. Applies only to static content.

.EXAMPLE
Write-PodeFileResponseInternal -Path 'index.pode' -Data @{ Title = 'Home Page' } -ContentType 'text/html'

Serves the 'index.pode' file as an HTTP response, processing it with the view engine and passing in a title for dynamic content rendering.

.EXAMPLE
Write-PodeFileResponseInternal -Path 'logo.png' -ContentType 'image/png' -Cache

Serves the 'logo.png' file as a static file with the specified content type and caching enabled.

.OUTPUTS
None. The function writes directly to the HTTP response stream.

.NOTES
This is an internal function and may change in future releases of Pode.
#>

function Write-PodeFileResponseInternal {
    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $true)]
        [ValidateNotNull()]
        [string]
        $Path,

        [Parameter()]
        $Data = @{},

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

        [Parameter()]
        [int]
        $MaxAge = 3600,

        [Parameter()]
        [int]
        $StatusCode = 200,

        [switch]
        $Cache,

        [switch]
        $FileBrowser
    )

    # Attempt to retrieve information about the path
    $pathInfo = Test-PodePath -Path $Path -Force -ReturnItem -FailOnDirectory:(!$FileBrowser)

    if (!$pathinfo) {
        return
    }

    # Check if the path is a directory
    if ( $pathInfo.PSIsContainer) {
        # If directory browsing is enabled, use the directory response function
        Write-PodeDirectoryResponseInternal -Path $Path
    }
    else {
        # are we dealing with a dynamic file for the view engine? (ignore html)
        # Determine if the file is dynamic and should be processed by the view engine
        $mainExt = $pathInfo.Extension.TrimStart('.')

        # generate dynamic content
        if (![string]::IsNullOrWhiteSpace($mainExt) -and (
        ($mainExt -ieq 'pode') -or
        ($mainExt -ieq $PodeContext.Server.ViewEngine.Extension -and $PodeContext.Server.ViewEngine.IsDynamic)
            )
        ) {
            # Process dynamic content with the view engine
            $content = Get-PodeFileContentUsingViewEngine -Path $Path -Data $Data

            # Determine the correct content type for the response
            # get the sub-file extension, if empty, use original
            $subExt = [System.IO.Path]::GetExtension($pathInfo.BaseName).TrimStart('.')

            $subExt = (Protect-PodeValue -Value $subExt -Default $mainExt)

            $ContentType = (Protect-PodeValue -Value $ContentType -Default (Get-PodeContentType -Extension $subExt))

            # Write the processed content as the HTTP response
            Write-PodeTextResponse -Value $content -ContentType $ContentType -StatusCode $StatusCode
        }
        # this is a static file
        else {
            try {
                if (Test-PodeIsPSCore) {
                    $content = (Get-Content -Path $Path -Raw -AsByteStream)
                }
                else {
                    $content = (Get-Content -Path $Path -Raw -Encoding byte)
                }
                # Determine and set the content type for static files
                $ContentType = Protect-PodeValue -Value $ContentType -Default (Get-PodeContentType -Extension $mainExt)
                # Write the file content as the HTTP response
                Write-PodeTextResponse -Bytes $content -ContentType $ContentType -MaxAge $MaxAge -StatusCode $StatusCode -Cache:$Cache
                return
            }
            catch [System.UnauthorizedAccessException] {
                $statusCode = 401
            }
            catch {
                $statusCode = 400
            }
            # If the file does not exist, set the HTTP response status code appropriately
            Set-PodeResponseStatus -Code $StatusCode

        }
    }
}

<#
.SYNOPSIS
Serves a directory listing as a web page.

.DESCRIPTION
The Write-PodeDirectoryResponseInternal function generates an HTML response that lists the contents of a specified directory,
allowing for browsing of files and directories. It supports both Windows and Unix-like environments by adjusting the
display of file attributes accordingly. If the path is a directory, it generates a browsable HTML view; otherwise, it
serves the file directly.

.PARAMETER Path
The relative path to the directory that should be displayed. This path is resolved and used to generate a list of contents.


.EXAMPLE
# resolve for relative path
$RelativePath = Get-PodeRelativePath -Path './static' -JoinRoot
Write-PodeDirectoryResponseInternal -Path './static'

Generates and serves an HTML page that lists the contents of the './static' directory, allowing users to click through files and directories.

.NOTES
This is an internal function and may change in future releases of Pode.
#>
function Write-PodeDirectoryResponseInternal {
    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $true)]
        [ValidateNotNull()]
        [string]
        $Path
    )

    if ($WebEvent.Path -eq '/') {
        $leaf = '/'
        $rootPath = '/'
    }
    else {
        # get leaf of current physical path, and set root path
        $leaf = ($Path.Split(':')[1] -split '[\\/]+') -join '/'
        $rootPath = $WebEvent.Path -ireplace "$($leaf)$", ''
    }

    # Determine if the server is running in Windows mode or is running a varsion that support Linux
    # https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.management/get-childitem?view=powershell-7.4#example-10-output-for-non-windows-operating-systems
    $windowsMode = ((Test-PodeIsWindows) -or ($PSVersionTable.PSVersion -lt [version]'7.1.0') )

    # Construct the HTML content for the file browser view
    $htmlContent = [System.Text.StringBuilder]::new()

    $atoms = $WebEvent.Path -split '/'
    $atoms = @(foreach ($atom in $atoms) {
            if (![string]::IsNullOrEmpty($atom)) {
                [uri]::EscapeDataString($atom)
            }
        })
    if ([string]::IsNullOrWhiteSpace($atoms)) {
        $baseLink = ''
    }
    else {
        $baseLink = "/$($atoms -join '/')"
    }

    # Handle navigation to the parent directory (..)
    if ($leaf -ne '/') {
        $LastSlash = $baseLink.LastIndexOf('/')
        if ($LastSlash -eq -1) {
            Set-PodeResponseStatus -Code 404
            return
        }
        $ParentLink = $baseLink.Substring(0, $LastSlash)
        if ([string]::IsNullOrWhiteSpace($ParentLink)) {
            $ParentLink = '/'
        }
        $item = Get-Item '..'
        if ($windowsMode) {
            $htmlContent.Append("<tr> <td class='mode'>")
            $htmlContent.Append($item.Mode)
        }
        else {
            $htmlContent.Append("<tr> <td class='unixMode'>")
            $htmlContent.Append($item.UnixMode)
            $htmlContent.Append("</td> <td class='user'>")
            $htmlContent.Append($item.User)
            $htmlContent.Append("</td> <td class='group'>")
            $htmlContent.Append($item.Group)
        }
        $htmlContent.Append("</td> <td class='dateTime'>")
        $htmlContent.Append($item.CreationTime.ToString('yyyy-MM-dd HH:mm:ss'))
        $htmlContent.Append("</td> <td class='dateTime'>")
        $htmlContent.Append($item.LastWriteTime.ToString('yyyy-MM-dd HH:mm:ss'))
        $htmlContent.Append( "</td> <td class='size'></td> <td class='icon'><i class='bi bi-folder2-open'></td> <td class='name'><a href='")
        $htmlContent.Append($ParentLink)
        $htmlContent.AppendLine("'>..</a></td> </tr>")
    }
    # Retrieve the child items of the specified directory
    $child = Get-ChildItem -Path $Path -Force
    foreach ($item in $child) {
        $link = "$baseLink/$([uri]::EscapeDataString($item.Name))"
        if ($item.PSIsContainer) {
            $size = ''
            $icon = '📁'
        }
        else {
            $size = '{0:N2}KB' -f ($item.Length / 1KB)
            $icon = '📄'
        }

        # Format each item as an HTML row
        if ($windowsMode) {
            $htmlContent.Append("<tr> <td class='mode'>")
            $htmlContent.Append($item.Mode)
        }
        else {
            $htmlContent.Append("<tr> <td class='unixMode'>")
            $htmlContent.Append($item.UnixMode)
            $htmlContent.Append("</td> <td class='user'>")
            $htmlContent.Append($item.User)
            $htmlContent.Append("</td> <td class='group'>")
            $htmlContent.Append($item.Group)
        }
        $htmlContent.Append("</td> <td class='dateTime'>")
        $htmlContent.Append($item.CreationTime.ToString('yyyy-MM-dd HH:mm:ss'))
        $htmlContent.Append("</td> <td class='dateTime'>")
        $htmlContent.Append($item.LastWriteTime.ToString('yyyy-MM-dd HH:mm:ss'))
        $htmlContent.Append("</td> <td class='size'>")
        $htmlContent.Append( $size)
        $htmlContent.Append( "</td> <td class='icon'>")
        $htmlContent.Append( $icon)
        $htmlContent.Append( "</td> <td class='name'><a href='")
        $htmlContent.Append( $link)
        $htmlContent.Append( "'>")
        $htmlContent.Append($item.Name )
        $htmlContent.AppendLine('</a></td> </tr>' )
    }

    $Data = @{
        RootPath    = $RootPath
        Path        = $leaf.Replace('\', '/')
        WindowsMode = $windowsMode.ToString().ToLower()
        FileContent = $htmlContent.ToString() # Convert the StringBuilder content to a string
    }

    $podeRoot = Get-PodeModuleMiscPath
    # Write the response
    Write-PodeFileResponseInternal -Path ([System.IO.Path]::Combine($podeRoot, 'default-file-browsing.html.pode')) -Data $Data

}



<#
.SYNOPSIS
Sends a file as an attachment in the response, supporting both file streaming and directory browsing options.

.DESCRIPTION
The Write-PodeAttachmentResponseInternal function is designed to handle HTTP responses for file downloads or directory browsing within a Pode web server. It resolves the given file or directory path, sets the appropriate content type, and configures the response to either download the file as an attachment or list the directory contents if browsing is enabled. The function supports both PowerShell Core and Windows PowerShell environments for file content retrieval.

.PARAMETER Path
The path to the file or directory. This parameter is mandatory and accepts pipeline input. The function resolves relative paths based on the server's root directory.

.PARAMETER ContentType
The MIME type of the file being served. This is validated against a pattern to ensure it's in the format 'type/subtype'. If not specified, the function attempts to determine the content type based on the file extension.

.PARAMETER FileBrowser
A switch parameter that, when present, enables directory browsing. If the path points to a directory and this parameter is enabled, the function will list the directory's contents instead of returning a 404 error.

.EXAMPLE
Write-PodeAttachmentResponseInternal -Path './files/document.pdf' -ContentType 'application/pdf'

Serves the 'document.pdf' file with the 'application/pdf' MIME type as a downloadable attachment.

.EXAMPLE
Write-PodeAttachmentResponseInternal -Path './files' -FileBrowser

Lists the contents of the './files' directory if the FileBrowser switch is enabled; otherwise, returns a 404 error.

.NOTES
- This function integrates with Pode's internal handling of HTTP responses, leveraging other Pode-specific functions like Get-PodeContentType and Set-PodeResponseStatus. It differentiates between streamed and serverless environments to optimize file delivery.
- This is an internal function and may change in future releases of Pode.
#>
function Write-PodeAttachmentResponseInternal {
    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $true)]
        [string]
        $Path,

        [Parameter()]
        [string]
        $ContentType,

        [Parameter()]
        [switch]
        $FileBrowser

    )

    # Attempt to retrieve information about the path
    $pathInfo = Test-PodePath -Path $Path -Force -ReturnItem -FailOnDirectory:(!$FileBrowser)

    if (!$pathinfo) {
        return
    }

    # Check if the path exists
    if ($null -eq $pathInfo) {
        return
    }

    if ( $pathInfo.PSIsContainer) {
        # filebrowsing is enabled, use the directory response function
        Write-PodeDirectoryResponseInternal -Path $Path
        return
    }
    try {
        # setup the content type and disposition
        if (!$ContentType) {
            $WebEvent.Response.ContentType = (Get-PodeContentType -Extension $pathInfo.Extension)
        }
        else {
            $WebEvent.Response.ContentType = $ContentType
        }

        Set-PodeHeader -Name 'Content-Disposition' -Value "attachment; filename=$($pathInfo.Name)"

        # if serverless, get the content raw and return
        if (!$WebEvent.Streamed) {
            if (Test-PodeIsPSCore) {
                $content = (Get-Content -Path $Path -Raw -AsByteStream)
            }
            else {
                $content = (Get-Content -Path $Path -Raw -Encoding byte)
            }

            $WebEvent.Response.Body = $content
        }

        # else if normal, stream the content back
        else {
            # setup the response details and headers
            $WebEvent.Response.SendChunked = $false

            # set file as an attachment on the response
            $buffer = [byte[]]::new(64 * 1024)
            $read = 0

            # open up the file as a stream
            $fs = (Get-Item $Path).OpenRead()
            $WebEvent.Response.ContentLength64 = $fs.Length

            while (($read = $fs.Read($buffer, 0, $buffer.Length)) -gt 0) {
                $WebEvent.Response.OutputStream.Write($buffer, 0, $read)
            }
        }
    }
    finally {
        Close-PodeDisposable -Disposable $fs
    }

}
src\Private\Routes.ps1
function Test-PodeRouteFromRequest {
    param(
        [Parameter(Mandatory = $true)]
        [ValidateSet('CONNECT', 'DELETE', 'GET', 'HEAD', 'MERGE', 'OPTIONS', 'PATCH', 'POST', 'PUT', 'TRACE', 'STATIC', 'SIGNAL', '*')]
        [string]
        $Method,

        [Parameter(Mandatory = $true)]
        [ValidateNotNullOrEmpty()]
        [string]
        $Path,

        [Parameter()]
        [string]
        $EndpointName,

        [switch]
        $CheckWildMethod
    )

    $route = Find-PodeRoute -Method $Method -Path $Path -EndpointName $EndpointName -CheckWildMethod:$CheckWildMethod
    return ($null -ne $route)
}

function Find-PodeRoute {
    param(
        [Parameter(Mandatory = $true)]
        [ValidateSet('CONNECT', 'DELETE', 'GET', 'HEAD', 'MERGE', 'OPTIONS', 'PATCH', 'POST', 'PUT', 'TRACE', 'STATIC', 'SIGNAL', '*')]
        [string]
        $Method,

        [Parameter(Mandatory = $true)]
        [ValidateNotNullOrEmpty()]
        [string]
        $Path,

        [Parameter()]
        [string]
        $EndpointName,

        [switch]
        $CheckWildMethod
    )

    # first, if supplied, check the wildcard method
    if ($CheckWildMethod -and ($PodeContext.Server.Routes['*'].Count -ne 0)) {
        $found = Find-PodeRoute -Method '*' -Path $Path -EndpointName $EndpointName
        if ($null -ne $found) {
            return $found
        }
    }

    # first ensure we have the method
    $_method = $PodeContext.Server.Routes[$Method]
    if ($null -eq $_method) {
        return $null
    }

    # is this a static route?
    $isStatic = ($Method -ieq 'static')

    # if we have a perfect match for the route, return it if the protocol is right
    if (!$isStatic) {
        $found = Get-PodeRouteByUrl -Routes $_method[$Path] -EndpointName $EndpointName
        if ($null -ne $found) {
            return $found
        }
    }

    # otherwise, match the path to routes on regex (first match only)
    $paths = @($_method.Keys)
    if ($isStatic) {
        [array]::Sort($paths)
        [array]::Reverse($paths)
    }

    $valid = @(foreach ($key in $paths) {
            if ($Path -imatch "^$($key)$") {
                $key
                break
            }
        })[0]

    if ($null -eq $valid) {
        return $null
    }

    # is the route valid for any protocols/endpoints?
    $found = Get-PodeRouteByUrl -Routes $_method[$valid] -EndpointName $EndpointName
    if ($null -eq $found) {
        return $null
    }

    return $found
}

function Find-PodePublicRoute {
    param(
        [Parameter(Mandatory = $true)]
        [string]
        $Path
    )

    $source = $null
    $publicPath = $PodeContext.Server.InbuiltDrives['public']

    # reutrn null if there is no public directory
    if ([string]::IsNullOrWhiteSpace($publicPath)) {
        return $source
    }

    # use the public static directory (but only if path is a file, and a public dir is present)
    if (Test-PodePathIsFile $Path) {
        $source = [System.IO.Path]::Combine($publicPath, $Path.TrimStart('/', '\'))
        if (!(Test-PodePath -Path $source -NoStatus)) {
            $source = $null
        }
    }

    # return the route details
    return $source
}


<#
.SYNOPSIS
Finds a static route for a given path in a Pode web server application, with optional checks for public routes.

.DESCRIPTION
This function searches for a static route matching the specified path within a Pode web server application. It attempts to resolve the route to a physical file or directory and supports additional checks for public routes as a fallback option. The function returns a hashtable with route details, including whether the route is for a downloadable file, if it's cacheable, and whether it redirects to a default document.

.PARAMETER Path
The URL path for which to find a static route. This parameter is mandatory.

.PARAMETER EndpointName
Optional. Specifies the name of the endpoint to which the route may belong. If not provided, the function searches across all endpoints.

.PARAMETER CheckPublic
A switch parameter. If specified, the function also checks for the route in public routes as a fallback option.

.EXAMPLE
$staticRoute = Find-PodeStaticRoute -Path '/images/logo.png' -CheckPublic

Searches for a static route for '/images/logo.png'. If not found, checks if a public route exists for the same path.

.EXAMPLE
$staticRoute = Find-PodeStaticRoute -Path '/css/style.css' -EndpointName 'WebUI'

Searches for a static route for '/css/style.css' specifically within the 'WebUI' endpoint, without checking public routes.

.OUTPUTS
Hashtable. Returns a hashtable containing the route details, such as the source path, download flag, cacheability, and redirect status.

.NOTES
This is an internal function and may change in future releases of Pode.
#>
function Find-PodeStaticRoute {
    [CmdletBinding()]
    [OutputType([hashtable])]
    param(
        [Parameter(Mandatory = $true)]
        [string]
        $Path,

        [Parameter()]
        [string]
        $EndpointName,

        [switch]
        $CheckPublic
    )

    # attempt to get a static route for the path
    $found = Find-PodeRoute -Method 'static' -Path $Path -EndpointName $EndpointName
    $download = ([bool]$found.Download)
    $source = $null
    $isDefault = $false
    $redirectToDefault = ([bool]$found.RedirectToDefault)

    # if we have a defined static route, use that
    if ($null -ne $found) {
        # see if we have a file
        $file = [string]::Empty

        if ($found.KleeneStar) {
            $matchingPath = "$($found.Path -ireplace '.\*', '.+?')$"
        }
        else {
            $matchingPath = "$($found.Path)$"
        }
        if ($Path -imatch $matchingPath) {
            $file = (Protect-PodeValue -Value $Matches['file'] -Default ([string]::Empty))
        }

        $fileInfo = Get-Item -Path ([System.IO.Path]::Combine($found.Source, $file)) -Force -ErrorAction Ignore
        #if $file doesn't exist return $null
        if ($null -eq $fileInfo) {
            return $null
        }

        # if there's no file, we need to check defaults
        if (!$found.Download -and $fileInfo.PSIsContainer -and (Get-PodeCount @($found.Defaults)) -gt 0) {
            foreach ($def in $found.Defaults) {
                $fileInfoDefaultFile = Get-Item -Path ([System.IO.Path]::Combine($fileInfo.FullName, $def)) -Force -ErrorAction Ignore
                if ($fileInfoDefaultFile) {
                    $file = $fileInfoDefaultFile.FullName
                    $isDefault = $true
                    break
                }
            }
        }
        $source = [System.IO.Path]::Combine($found.Source, $file)

    }

    # check public, if flagged
    if ($CheckPublic -and !(Test-PodePath -Path $source -NoStatus)) {
        $source = Find-PodePublicRoute -Path $Path
        $download = $false
        $found = $null
        $isDefault = $false
        $redirectToDefault = $false
    }

    # return nothing if no source
    if ([string]::IsNullOrWhiteSpace($source)) {
        return $null
    }

    # return the route details
    if ($redirectToDefault -and $isDefault) {
        $redirectToDefault = $true
    }
    else {
        $redirectToDefault = $false
    }

    return @{
        Content = @{
            Source            = $source
            IsDownload        = $download
            IsCachable        = (Test-PodeRouteValidForCaching -Path $Path)
            RedirectToDefault = $redirectToDefault
        }
        Route   = $found
    }
}


function Find-PodeSignalRoute {
    param(
        [Parameter(Mandatory = $true)]
        [string]
        $Path,

        [Parameter()]
        [string]
        $EndpointName
    )

    # attempt to get a signal route for the path
    return (Find-PodeRoute -Method 'signal' -Path $Path -EndpointName $EndpointName)
}

function Test-PodeRouteValidForCaching {
    param(
        [Parameter(Mandatory = $true)]
        [string]
        $Path
    )

    # check current state of caching
    $config = $PodeContext.Server.Web.Static.Cache
    $caching = $config.Enabled

    # if caching, check include/exclude
    if ($caching) {
        if (($null -ne $config.Exclude) -and ($Path -imatch $config.Exclude)) {
            $caching = $false
        }

        if (($null -ne $config.Include) -and ($Path -inotmatch $config.Include)) {
            $caching = $false
        }
    }

    return $caching
}

<#
.SYNOPSIS
Finds and returns a route from an array of routes based on an endpoint name and/or path.

.DESCRIPTION
This function iterates over an array of route definitions to locate a specific route that matches the provided endpoint name and path.
It supports scenarios where only one of the parameters is provided or both. If no matching route is found, or if the routes array is empty or null,
the function returns $null.

.PARAMETER Routes
An array of hashtable objects, each representing a route with potentially defined properties like Root and Endpoint.Name.

.PARAMETER EndpointName
The name of the endpoint to search for within the route definitions. This parameter is optional.

.EXAMPLE
$routes = @(
    @{ Root = '/api'; Endpoint = @{ Name = 'GetData' } },
    @{ Root = '/home'; Endpoint = @{ Name = 'Index' } }
)
Get-PodeRouteByUrl -Routes $routes -EndpointName 'GetData'

Returns the route for the '/api' endpoint named 'GetData'.

.EXAMPLE
$routes = @(
    @{ Root = '/api'; Endpoint = @{ Name = 'GetData' } },
    @{ Root = '/home'; Endpoint = @{ Name = 'Index' } }
)
Get-PodeRouteByUrl -Routes $routes -Path '/api'

Returns the route for the '/api' path, regardless of the endpoint name.

.NOTES
The function prioritizes matching both the endpoint name and path but can return a route based on either criterion if the other is unspecified.
#>
function Get-PodeRouteByUrl {
    param(
        [Parameter()]
        [hashtable[]]
        $Routes,

        [Parameter()]
        [string]
        $EndpointName
    )

    # Return null immediately if routes are not defined or empty
    if (($null -eq $Routes) -or ($Routes.Length -eq 0)) {
        return $null
    }

    # Handle case when no specific endpoint name is provided
    if ([string]::IsNullOrWhiteSpace($EndpointName)) {
        foreach ($route in $Routes) {
            # Return the first route as a default if no path is specified
            return $route
        }
    }
    else {
        # Handle case when an endpoint name is provided
        foreach ($route in $Routes) {
            if (  $route.Endpoint.Name -ieq $EndpointName) {
                # Return the first route that matches the endpoint name as a default
                return $route
            }
        }
    }

    # Last resort check only route with no endpoint name
    foreach ($route in $Routes) {
        if ([string]::IsNullOrWhiteSpace($route.Endpoint.Name)) {
            # Return the first route that matches the endpoint name as a default
            return $route
        }
    }

    # Return null if no matching route is found
    return $null
}


function ConvertTo-PodeOpenApiRoutePath {
    param(
        [Parameter(Mandatory = $true)]
        [string]
        $Path
    )

    return (Resolve-PodePlaceholder -Path $Path -Pattern '\:(?<tag>[\w]+)' -Prepend '{' -Append '}')
}

<#
.SYNOPSIS
    Updates a Pode route path to ensure proper formatting.

.DESCRIPTION
    This function takes a Pode route path and ensures that it starts with a leading slash ('/') and follows the correct format for static routes. It also replaces '*' with '.*' for proper regex matching.

.PARAMETER Path
    The Pode route path to update.

.PARAMETER Static
    Indicates whether the route is a static route (default is false).

.PARAMETER NoLeadingSlash
    Indicates whether the route should not have a leading slash (default is false).

.OUTPUTS
    The updated Pode route path.

.NOTES
    This is an internal function and may change in future releases of Pode.
#>
function Update-PodeRouteSlash {
    [CmdletBinding()]
    [OutputType([string])]
    param(
        [Parameter(Mandatory = $true)]
        [string]
        $Path,

        [switch]
        $Static,

        [switch]
        $NoLeadingSlash
    )

    # ensure route starts with a '/'
    if (!$NoLeadingSlash -and !$Path.StartsWith('/')) {
        $Path = "/$($Path)"
    }

    if ($Static) {
        # ensure the static route ends with '/{0,1}.*'
        $Path = $Path.TrimEnd('/*')
        $Path = "$($Path)[/]{0,1}(?<file>*)"
    }

    # replace * with .*
    $Path = ($Path -ireplace '\*', '.*')
    return $Path
}

function Split-PodeRouteQuery {
    param(
        [Parameter(Mandatory = $true)]
        [string]
        $Path
    )

    return ($Path -isplit '\?')[0]
}

function ConvertTo-PodeRouteRegex {
    param(
        [Parameter()]
        [string]
        $Path
    )

    if ([string]::IsNullOrWhiteSpace($Path)) {
        return [string]::Empty
    }

    $Path = Protect-PodeValue -Value $Path -Default '/'
    $Path = Split-PodeRouteQuery -Path $Path
    $Path = Protect-PodeValue -Value $Path -Default '/'
    $Path = Update-PodeRouteSlash -Path $Path
    $Path = Resolve-PodePlaceholder -Path $Path

    return $Path
}

function Get-PodeStaticRouteDefault {
    if (!(Test-PodeIsEmpty $PodeContext.Server.Web.Static.Defaults)) {
        return @($PodeContext.Server.Web.Static.Defaults)
    }

    return @(
        'index.html',
        'index.htm',
        'default.html',
        'default.htm'
    )
}

function Test-PodeRouteInternal {
    param(
        [Parameter(Mandatory = $true)]
        [string]
        $Method,

        [Parameter(Mandatory = $true)]
        [string]
        $Path,

        [Parameter()]
        [string]
        $Protocol,

        [Parameter()]
        [string]
        $Address,

        [switch]
        $ThrowError
    )

    # check the routes
    $found = $false
    $routes = @($PodeContext.Server.Routes[$Method][$Path])

    foreach ($route in $routes) {
        if (($route.Endpoint.Protocol -ieq $Protocol) -and ($route.Endpoint.Address -ieq $Address)) {
            $found = $true
            break
        }
    }

    # skip if not found
    if (!$found) {
        return $false
    }

    # do we want to throw an error if found, or skip?
    if (!$ThrowError) {
        return $true
    }

    # throw error
    $_url = $Protocol
    if (![string]::IsNullOrEmpty($_url) -and ![string]::IsNullOrWhiteSpace($Address)) {
        $_url = "$($_url)://$($Address)"
    }
    elseif (![string]::IsNullOrWhiteSpace($Address)) {
        $_url = $Address
    }

    if ([string]::IsNullOrEmpty($_url)) {
        throw ($PodeLocale.methodPathAlreadyDefinedExceptionMessage -f $Method, $Path) #"[$($Method)] $($Path): Already defined"
    }

    throw ($PodeLocale.methodPathAlreadyDefinedForUrlExceptionMessage -f $Method, $Path, $_url) #"[$($Method)] $($Path): Already defined for $($_url)"
}

function Convert-PodeFunctionVerbToHttpMethod {
    param(
        [Parameter()]
        [string]
        $Verb
    )

    # if empty, just return default
    switch ($Verb) {
        { $_ -iin @('Find', 'Format', 'Get', 'Join', 'Search', 'Select', 'Split', 'Measure', 'Ping', 'Test', 'Trace') } { 'GET' }
        { $_ -iin @('Set') } { 'PUT' }
        { $_ -iin @('Rename', 'Edit', 'Update') } { 'PATCH' }
        { $_ -iin @('Clear', 'Close', 'Exit', 'Hide', 'Remove', 'Undo', 'Dismount', 'Unpublish', 'Disable', 'Uninstall', 'Unregister') } { 'DELETE' }
        Default { 'POST' }
    }
}


<#
.SYNOPSIS
Finds and returns the appropriate transfer encoding for a given route path in a Pode server context.

.DESCRIPTION
This function determines the correct transfer encoding for a specified route path within a Pode web server. It checks if a transfer encoding is already specified and returns it; otherwise, it defaults to the server's default transfer encoding. The function searches the server's transfer encoding route settings for a pattern that matches the given path. If a match is found, the corresponding transfer encoding is returned. This is useful for dynamically setting response encodings based on specific route patterns.

.PARAMETER Path
The route path for which the transfer encoding is being determined. This parameter is mandatory.

.PARAMETER TransferEncoding
The current transfer encoding, if already determined. This is an optional parameter. If specified and not null or whitespace, this function returns the given value without further processing.

.EXAMPLE
$encoding = Find-PodeRouteTransferEncoding -Path "/api/data" -TransferEncoding "chunked"

This example determines the transfer encoding for the route "/api/data", with an initial encoding of "chunked". If "/api/data" matches a specific pattern in the server's transfer encoding settings, the corresponding encoding is returned; otherwise, "chunked" is returned.

.OUTPUTS
String. Returns the determined transfer encoding for the given route path. This will be either the input TransferEncoding (if provided and valid), a matched encoding from the server's settings, or the server's default transfer encoding.

.NOTES
- The function uses a case-insensitive match (`-imatch`) to find the first route key pattern that matches the specified path.
- This is an internal function and may change in future releases of Pode.
#>
function Find-PodeRouteTransferEncoding {
    param(
        [Parameter(Mandatory = $true)]
        [string]
        $Path,

        [Parameter()]
        [string]
        $TransferEncoding
    )

    # if we already have one, return it
    if (![string]::IsNullOrWhiteSpace($TransferEncoding)) {
        return $TransferEncoding
    }

    # set the default
    $TransferEncoding = $PodeContext.Server.Web.TransferEncoding.Default

    # find type by pattern from settings
    $matched = $null
    foreach ($key in $PodeContext.Server.Web.TransferEncoding.Routes.Keys) {
        if ($Path -imatch $key) {
            $matched = $key
            break
        }
    }

    # if we get a match, set it
    if (!(Test-PodeIsEmpty $matched)) {
        $TransferEncoding = $PodeContext.Server.Web.TransferEncoding.Routes[$matched]
    }

    return $TransferEncoding
}

function Find-PodeRouteContentType {
    param(
        [Parameter(Mandatory = $true)]
        [string]
        $Path,

        [Parameter()]
        [string]
        $ContentType
    )

    # if we already have one, return it
    if (![string]::IsNullOrWhiteSpace($ContentType)) {
        return $ContentType
    }

    # set the default
    $ContentType = $PodeContext.Server.Web.ContentType.Default

    # find type by pattern from settings
    $matched = $null
    foreach ($key in $PodeContext.Server.Web.ContentType.Routes.Keys) {
        if ($Path -imatch $key) {
            $matched = $key
            break
        }
    }

    # if we get a match, set it
    if (!(Test-PodeIsEmpty $matched)) {
        $ContentType = $PodeContext.Server.Web.ContentType.Routes[$matched]
    }

    return $ContentType
}

function ConvertTo-PodeMiddleware {
    [OutputType([hashtable[]])]
    param(
        [Parameter()]
        [object[]]
        $Middleware,

        [Parameter(Mandatory = $true)]
        [System.Management.Automation.SessionState]
        $PSSession
    )

    # return if no middleware
    if (Test-PodeIsEmpty $Middleware) {
        return $null
    }

    $Middleware = @($Middleware)

    # ensure supplied middlewares are either a scriptblock, or a valid hashtable
    foreach ($mid in $Middleware) {
        if ($null -eq $mid) {
            continue
        }

        # check middleware is a type valid
        if (($mid -isnot [scriptblock]) -and ($mid -isnot [hashtable])) {
            throw ($PodeLocale.invalidMiddlewareTypeExceptionMessage -f $mid.GetType().Name)#"One of the Middlewares supplied is an invalid type. Expected either a ScriptBlock or Hashtable, but got: $($mid.GetType().Name)"
        }

        # if middleware is hashtable, ensure the keys are valid (logic is a scriptblock)
        if ($mid -is [hashtable]) {
            if ($null -eq $mid.Logic) {
                # A Hashtable Middleware supplied has no Logic defined
                throw ($PodeLocale.hashtableMiddlewareNoLogicExceptionMessage)
            }

            if ($mid.Logic -isnot [scriptblock]) {
                # A Hashtable Middleware supplied has an invalid Logic type. Expected ScriptBlock, but got: {0}
                throw ($PodeLocale.invalidLogicTypeInHashtableMiddlewareExceptionMessage -f $mid.Logic.GetType().Name)
            }
        }
    }

    # if we have middleware, convert scriptblocks to hashtables
    $converted = @(for ($i = 0; $i -lt $Middleware.Length; $i++) {
            if ($null -eq $Middleware[$i]) {
                continue
            }

            if ($Middleware[$i] -is [scriptblock]) {
                $_script, $_usingVars = Convert-PodeScopedVariables -ScriptBlock $Middleware[$i] -PSSession $PSSession

                $Middleware[$i] = @{
                    Logic          = $_script
                    UsingVariables = $_usingVars
                }
            }

            $Middleware[$i]
        })

    return $converted
}

function Get-PodeRouteIfExistsPreference {
    # from route groups
    $groupPref = $RouteGroup.IfExists
    if (![string]::IsNullOrWhiteSpace($groupPref) -and ($groupPref -ine 'default')) {
        return $groupPref
    }

    # from Use-PodeRoute
    if (![string]::IsNullOrWhiteSpace($script:RouteIfExists) -and ($script:RouteIfExists -ine 'default')) {
        return $script:RouteIfExists
    }

    # global preference
    $globalPref = $PodeContext.Server.Preferences.Routes.IfExists
    if (![string]::IsNullOrWhiteSpace($globalPref) -and ($globalPref -ine 'default')) {
        return $globalPref
    }

    # final global default
    return 'Error'
}
src\Private\Runspaces.ps1
<#
.SYNOPSIS
    Adds a new runspace to Pode with the specified type and script block.

.DESCRIPTION
    The `Add-PodeRunspace` function creates a new PowerShell runspace within Pode
    based on the provided type and script block. This function allows for additional
    customization through parameters, output streaming, and runspace management options.

.PARAMETER Type
    The type of runspace to create. Accepted values are:
    'Main', 'Signals', 'Schedules', 'Gui', 'Web', 'Smtp', 'Tcp', 'Tasks',
    'WebSockets', 'Files', 'Timers'.

.PARAMETER ScriptBlock
    The script block to execute within the runspace. This script block will be
    added to the runspace's pipeline.

.PARAMETER Parameters
    Optional parameters to pass to the script block.

.PARAMETER OutputStream
    A PSDataCollection object to handle output streaming for the runspace.

.PARAMETER Forget
    If specified, the pipeline's output will not be stored or remembered.

.PARAMETER NoProfile
    If specified, the runspace will not load any modules or profiles.

.PARAMETER PassThru
    If specified, returns the pipeline and handler for custom processing.

.EXAMPLE
    Add-PodeRunspace -Type 'Tasks' -ScriptBlock {
        # Your script code here
    }
#>

function Add-PodeRunspace {
    param(
        [Parameter(Mandatory = $true)]
        [ValidateSet('Main', 'Signals', 'Schedules', 'Gui', 'Web', 'Smtp', 'Tcp', 'Tasks', 'WebSockets', 'Files', 'Timers')]
        [string]
        $Type,

        [Parameter(Mandatory = $true)]
        [ValidateNotNull()]
        [scriptblock]
        $ScriptBlock,

        [Parameter()]
        $Parameters,

        [Parameter()]
        [System.Management.Automation.PSDataCollection[psobject]]
        $OutputStream = $null,

        [switch]
        $Forget,

        [switch]
        $NoProfile,

        [switch]
        $PassThru,

        [string]
        $Name,

        [string]
        $Id = '1'
    )

    try {
        # Define the script block to open the runspace and set its state.
        $openRunspaceScript = {
            param($Type, $Name, $NoProfile)
            try {
                # Set the runspace name.
                Set-PodeCurrentRunspaceName -Name $Name

                if (!$NoProfile) {
                    # Import necessary internal Pode modules for the runspace.
                    Import-PodeModulesInternal

                    # Add required PowerShell drives.
                    Add-PodePSDrivesInternal
                }

                # Mark the runspace as 'Ready' to process requests.
                $PodeContext.RunspacePools[$Type].State = 'Ready'
            }
            catch {
                # Handle errors, setting the runspace state to 'Error' if applicable.
                if ($PodeContext.RunspacePools[$Type].State -ieq 'waiting') {
                    $PodeContext.RunspacePools[$Type].State = 'Error'
                }

                # Output the error details to the default stream and rethrow.
                $_ | Out-Default
                $_.ScriptStackTrace | Out-Default
                throw
            }
        }

        # Create a PowerShell pipeline.
        $ps = [powershell]::Create()
        $ps.RunspacePool = $PodeContext.RunspacePools[$Type].Pool

        # Add the script block and parameters to the pipeline.
        $null = $ps.AddScript($openRunspaceScript)
        $null = $ps.AddParameters(
            @{
                'Type'      = $Type
                'Name'      = "Pode_$($Type)_$($Name)_$($Id)"
                'NoProfile' = $NoProfile.IsPresent
            }
        )

        # Add the main script block to the pipeline.
        $null = $ps.AddScript($ScriptBlock)

        # Add any provided parameters to the script block.
        if (!(Test-PodeIsEmpty $Parameters)) {
            $Parameters.Keys | ForEach-Object {
                $null = $ps.AddParameter($_, $Parameters[$_])
            }
        }

        # Begin invoking the pipeline, with or without output streaming.
        if ($null -eq $OutputStream) {
            $pipeline = $ps.BeginInvoke()
        }
        else {
            $pipeline = $ps.BeginInvoke($OutputStream, $OutputStream)
        }

        # Handle forgetting, returning, or storing the pipeline.
        if ($Forget) {
            $null = $pipeline
        }
        elseif ($PassThru) {
            return @{
                Pipeline = $ps
                Handler  = $pipeline
            }
        }
        else {
            $PodeContext.Runspaces += @{
                Pool     = $Type
                Pipeline = $ps
                Handler  = $pipeline
                Stopped  = $false
            }
        }
    }
    catch {
        # Log and throw any exceptions encountered during execution.
        $_ | Write-PodeErrorLog
        throw $_.Exception
    }
}

<#
.SYNOPSIS
    Closes and disposes of the Pode runspaces, listeners, receivers, watchers, and optionally runspace pools.

.DESCRIPTION
    This function checks and waits for all Listeners, Receivers, and Watchers to be disposed of
    before proceeding to close and dispose of the runspaces and optionally the runspace pools.
    It ensures a clean shutdown by managing the disposal of resources in a specified order.
    The function handles serverless and regular server environments differently, skipping
    disposal actions in serverless contexts.

.PARAMETER ClosePool
    Specifies whether to close and dispose of the runspace pools along with the runspaces.
    This is optional and should be specified if the pools need to be explicitly closed.

.EXAMPLE
    Close-PodeRunspace -ClosePool
    This example closes all runspaces and their associated pools, ensuring that all resources are properly disposed of.

.OUTPUTS
    None
    Outputs from this function are primarily internal state changes and verbose logging.
#>
function Close-PodeRunspace {
    param(
        [switch]
        $ClosePool
    )

    # Early return if server is serverless, as disposal is not required.
    if ($PodeContext.Server.IsServerless) {
        return
    }

    try {
        # Only proceed if there are runspaces to dispose of.
        if (!(Test-PodeIsEmpty $PodeContext.Runspaces)) {
            Write-Verbose 'Waiting until all Listeners are disposed'

            $count = 0
            $continue = $false
            # Attempts to dispose of resources for up to 10 seconds.
            while ($count -le 10) {
                Start-Sleep -Seconds 1
                $count++

                $continue = $false
                # Check each listener, receiver, and watcher; if any are not disposed, continue waiting.
                foreach ($listener in $PodeContext.Listeners) {
                    if (!$listener.IsDisposed) {
                        $continue = $true
                        break
                    }
                }

                foreach ($receiver in $PodeContext.Receivers) {
                    if (!$receiver.IsDisposed) {
                        $continue = $true
                        break
                    }
                }

                foreach ($watcher in $PodeContext.Watchers) {
                    if (!$watcher.IsDisposed) {
                        $continue = $true
                        break
                    }
                }
                # If undisposed resources exist, continue waiting.
                if ($continue) {
                    continue
                }

                break
            }

            Write-Verbose 'All Listeners disposed'

            # now dispose runspaces
            Write-Verbose 'Disposing Runspaces'
            $runspaceErrors = @(foreach ($item in $PodeContext.Runspaces) {
                    if ($item.Stopped) {
                        continue
                    }

                    try {
                        # only do this, if the pool is in error
                        if ($PodeContext.RunspacePools[$item.Pool].State -ieq 'error') {
                            $item.Pipeline.EndInvoke($item.Handler)
                        }
                    }
                    catch {
                        "$($item.Pool) runspace failed to load: $($_.Exception.InnerException.Message)"
                    }

                    Close-PodeDisposable -Disposable $item.Pipeline
                    $item.Stopped = $true
                })

            # dispose of schedule runspaces
            if ($PodeContext.Schedules.Processes.Count -gt 0) {
                foreach ($key in $PodeContext.Schedules.Processes.Keys.Clone()) {
                    Close-PodeScheduleInternal -Process $PodeContext.Schedules.Processes[$key]
                }
            }

            # dispose of task runspaces
            if ($PodeContext.Tasks.Processes.Count -gt 0) {
                foreach ($key in $PodeContext.Tasks.Processes.Keys.Clone()) {
                    Close-PodeTaskInternal -Process $PodeContext.Tasks.Processes[$key]
                }
            }

            $PodeContext.Runspaces = @()
            Write-Verbose 'Runspaces disposed'
        }

        # close/dispose the runspace pools
        if ($ClosePool) {
            Close-PodeRunspacePool
        }

        # Check for and throw runspace errors if any occurred during disposal.
        if (($null -ne $runspaceErrors) -and ($runspaceErrors.Length -gt 0)) {
            foreach ($err in $runspaceErrors) {
                if ($null -eq $err) {
                    continue
                }

                throw $err
            }
        }

        # garbage collect
        Invoke-PodeGC
    }
    catch {
        $_ | Write-PodeErrorLog
        throw $_.Exception
    }
}
















src\Private\Schedules.ps1
function Find-PodeSchedule {
    param(
        [Parameter(Mandatory = $true)]
        [ValidateNotNullOrEmpty()]
        [string]
        $Name
    )

    return $PodeContext.Schedules.Items[$Name]
}

function Test-PodeSchedulesExist {
    return (($null -ne $PodeContext.Schedules) -and (($PodeContext.Schedules.Enabled) -or ($PodeContext.Schedules.Items.Count -gt 0)))
}
function Start-PodeScheduleRunspace {

    if (!(Test-PodeSchedulesExist)) {
        return
    }

    Add-PodeTimer -Name '__pode_schedule_housekeeper__' -Interval 30 -ScriptBlock {
        try {
            if ($PodeContext.Schedules.Processes.Count -eq 0) {
                return
            }

            $now = [datetime]::UtcNow

            foreach ($key in $PodeContext.Schedules.Processes.Keys.Clone()) {
                try {
                    $process = $PodeContext.Schedules.Processes[$key]

                    # if it's completed or expired, dispose and remove
                    if ($process.Runspace.Handler.IsCompleted -or ($process.ExpireTime -lt $now)) {
                        Close-PodeScheduleInternal -Process $process
                    }
                }
                catch {
                    $_ | Write-PodeErrorLog
                }
            }

            $process = $null
        }
        catch {
            $_ | Write-PodeErrorLog
        }
    }

    $script = {
        try {

            # select the schedules that trigger on-start
            $_now = [DateTime]::Now

            $PodeContext.Schedules.Items.Values |
                Where-Object {
                    $_.OnStart
                } | ForEach-Object {
                    Invoke-PodeInternalSchedule -Schedule $_
                }

            # complete any schedules
            Complete-PodeInternalSchedule -Now $_now

            # first, sleep for a period of time to get to 00 seconds (start of minute)
            Start-Sleep -Seconds (60 - [DateTime]::Now.Second)

            while (!$PodeContext.Tokens.Cancellation.IsCancellationRequested) {
                try {
                    $_now = [DateTime]::Now

                    # select the schedules that need triggering
                    $PodeContext.Schedules.Items.Values |
                        Where-Object {
                            !$_.Completed -and
                            (($null -eq $_.StartTime) -or ($_.StartTime -le $_now)) -and
                            (($null -eq $_.EndTime) -or ($_.EndTime -ge $_now)) -and
                            (Test-PodeCronExpressions -Expressions $_.Crons -DateTime $_now)
                        } | ForEach-Object {
                            try {
                                Invoke-PodeInternalSchedule -Schedule $_
                            }
                            catch {
                                $_ | Write-PodeErrorLog
                            }
                        }

                    # complete any schedules
                    Complete-PodeInternalSchedule -Now $_now

                    # cron expression only goes down to the minute, so sleep for 1min
                    Start-Sleep -Seconds (60 - [DateTime]::Now.Second)
                }
                catch {
                    $_ | Write-PodeErrorLog
                }
            }
        }
        catch [System.OperationCanceledException] {
            $_ | Write-PodeErrorLog -Level Debug
        }
        catch {
            $_ | Write-PodeErrorLog
            throw $_.Exception
        }
    }

    Add-PodeRunspace -Type Main -Name 'Schedules' -ScriptBlock $script -NoProfile
}

function Close-PodeScheduleInternal {
    param(
        [Parameter()]
        [hashtable]
        $Process
    )

    if ($null -eq $Process) {
        return
    }

    Close-PodeDisposable -Disposable $Process.Runspace.Pipeline
    $null = $PodeContext.Schedules.Processes.Remove($Process.ID)
}

<#
.SYNOPSIS
    Completes schedules that have exceeded their end time.

.DESCRIPTION
    The `Complete-PodeInternalSchedule` function checks for schedules that have an end time
    and marks them as completed if their end time is earlier than the current time.

.PARAMETER Now
    Specifies the current date and time. This parameter is mandatory.

.INPUTS
    None. You cannot pipe objects to Complete-PodeInternalSchedule.

.OUTPUTS
    None. The function modifies the state of schedules in the PodeContext.

.EXAMPLE
    # Example usage:
    $now = Get-Date
    Complete-PodeInternalSchedule -Now $now
    # Schedules that have ended are marked as completed.

.NOTES
    This is an internal function and may change in future releases of Pode.
#>
function Complete-PodeInternalSchedule {
    param(
        [Parameter(Mandatory = $true)]
        [datetime]
        $Now
    )

    # set any expired schedules as being completed
    foreach ($schedule in $PodeContext.Schedules.Items.Values) {
        if (($null -ne $schedule.EndTime) -and ($schedule.EndTime -lt $Now)) {
            $schedule.Completed = $true
        }
    }
}

function Invoke-PodeInternalSchedule {
    param(
        [Parameter(Mandatory = $true)]
        $Schedule
    )

    $Schedule.OnStart = $false

    # increment total number of triggers for the schedule
    $Schedule.Count++

    # set last trigger to current next trigger
    if ($null -ne $Schedule.NextTriggerTime) {
        $Schedule.LastTriggerTime = $Schedule.NextTriggerTime
    }
    else {
        $Schedule.LastTriggerTime = [datetime]::Now
    }

    # check if we have hit the limit, and remove
    if (($Schedule.Limit -gt 0) -and ($Schedule.Count -ge $Schedule.Limit)) {
        $Schedule.Completed = $true
    }

    # reset the cron and next trigger
    if (!$Schedule.Completed) {
        $Schedule.Crons = Reset-PodeRandomCronExpressions -Expressions $Schedule.Crons
        $Schedule.NextTriggerTime = Get-PodeCronNextEarliestTrigger -Expressions $Schedule.Crons -EndTime $Schedule.EndTime
    }
    else {
        $Schedule.NextTriggerTime = $null
    }

    # trigger the schedules logic
    Invoke-PodeInternalScheduleLogic -Schedule $Schedule
}

function Invoke-PodeInternalScheduleLogic {
    param(
        [Parameter(Mandatory = $true)]
        [hashtable]
        $Schedule,

        [Parameter()]
        [hashtable]
        $ArgumentList = $null
    )

    try {
        # generate processId for schedule
        $processId = New-PodeGuid

        # setup event param
        $parameters = @{
            ProcessId    = $processId
            ArgumentList = $ArgumentList
        }

        # what is the expire time if using "create" timeout?
        $expireTime = [datetime]::MaxValue
        $createTime = [datetime]::UtcNow

        if (($Schedule.Timeout.From -ieq 'Create') -and ($Schedule.Timeout.Value -ge 0)) {
            $expireTime = $createTime.AddSeconds($Schedule.Timeout.Value)
        }
        # add the schedule process
        $PodeContext.Schedules.Processes[$processId] = @{
            ID         = $processId
            Schedule   = $Schedule.Name
            Runspace   = $null
            CreateTime = $createTime
            StartTime  = $null
            ExpireTime = $expireTime
            Timeout    = $Schedule.Timeout
            State      = 'Pending'
        }

        # start the schedule runspace
        $scriptblock = Get-PodeScheduleScriptBlock
        $runspace = Add-PodeRunspace -Type Schedules -Name $Schedule.Name -ScriptBlock $scriptblock -Parameters $parameters -PassThru
        # add runspace to process
        $PodeContext.Schedules.Processes[$processId].Runspace = $runspace
    }
    catch {
        $_ | Write-PodeErrorLog
    }
}

function Get-PodeScheduleScriptBlock {
    return {
        param($ProcessId, $ArgumentList)

        try {
            # get the schedule process, error if not found
            $process = $PodeContext.Schedules.Processes[$ProcessId]
            if ($null -eq $process) {
                # Schedule process does not exist: $ProcessId
                throw ($PodeLocale.scheduleProcessDoesNotExistExceptionMessage -f $ProcessId)
            }

            # set start time and state
            $process.StartTime = [datetime]::UtcNow
            $process.State = 'Running'

            # set expire time if timeout based on "start" time
            if (($process.Timeout.From -ieq 'Start') -and ($process.Timeout.Value -ge 0)) {
                $process.ExpireTime = $process.StartTime.AddSeconds($process.Timeout.Value)
            }

            # get the schedule, error if not found
            $schedule = Find-PodeSchedule -Name $process.Schedule
            if ($null -eq $schedule) {
                throw ($PodeLocale.scheduleDoesNotExistExceptionMessage -f $process.Schedule)
            }

            # build the script arguments
            $ScheduleEvent = @{
                Lockable  = $PodeContext.Threading.Lockables.Global
                Sender    = $schedule
                Timestamp = [DateTime]::UtcNow
                Metadata  = @{}
            }

            $_args = @{ Event = $ScheduleEvent }

            if ($null -ne $schedule.Arguments) {
                foreach ($key in $schedule.Arguments.Keys) {
                    $_args[$key] = $schedule.Arguments[$key]
                }
            }

            if ($null -ne $ArgumentList) {
                foreach ($key in $ArgumentList.Keys) {
                    $_args[$key] = $ArgumentList[$key]
                }
            }

            # add any using variables
            if ($null -ne $schedule.UsingVariables) {
                foreach ($usingVar in $schedule.UsingVariables) {
                    $_args[$usingVar.NewName] = $usingVar.Value
                }
            }

            # invoke the script from the schedule
            Invoke-PodeScriptBlock -ScriptBlock $schedule.Script -Arguments $_args -Scoped -Splat

            # set state to completed
            $process.State = 'Completed'
        }
        catch {
            # update the state
            if ($null -ne $process) {
                $process.State = 'Failed'
            }

            # log the error
            $_ | Write-PodeErrorLog
        }
        finally {
            Invoke-PodeGC
        }
    }
}
src\Private\ScopedVariables.ps1
function Add-PodeScopedVariableInternal {
    [CmdletBinding(DefaultParameterSetName = 'Replace')]
    param(
        [Parameter(Mandatory = $true)]
        [string]
        $Name,

        [Parameter(Mandatory = $true, ParameterSetName = 'Replace')]
        [string]
        $GetReplace,

        [Parameter(ParameterSetName = 'Replace')]
        [string]
        $SetReplace = $null,

        [Parameter(Mandatory = $true, ParameterSetName = 'ScriptBlock')]
        [scriptblock]
        $ScriptBlock,

        [Parameter(ParameterSetName = 'Internal')]
        [switch]
        $InternalFunction
    )

    # lowercase the name
    $Name = $Name.ToLowerInvariant()

    # check if var already defined
    if (Test-PodeScopedVariable -Name $Name) {
        throw ($PodeLocale.scopedVariableAlreadyDefinedExceptionMessage -f $Name)#"Scoped Variable already defined: $($Name)"
    }

    # add scoped var definition
    $PodeContext.Server.ScopedVariables[$Name] = @{
        Name             = $Name
        Type             = $PSCmdlet.ParameterSetName.ToLowerInvariant()
        ScriptBlock      = $ScriptBlock
        Get              = @{
            Pattern = "(?<full>\`$$($Name)\:(?<name>[a-z0-9_\?]+))"
            Replace = $GetReplace
        }
        Set              = @{
            Pattern = "(?<full>\`$$($Name)\:(?<name>[a-z0-9_\?]+)\s*=)"
            Replace = $SetReplace
        }
        InternalFunction = $InternalFunction.IsPresent
    }
}

function Add-PodeScopedVariablesInbuilt {
    Add-PodeScopedVariableInbuiltUsing
    Add-PodeScopedVariableInbuiltCache
    Add-PodeScopedVariableInbuiltSecret
    Add-PodeScopedVariableInbuiltSession
    Add-PodeScopedVariableInbuiltState
}

function Add-PodeScopedVariableInbuiltCache {
    Add-PodeScopedVariable -Name 'cache' `
        -SetReplace "Set-PodeCache -Key '{{name}}' -InputObject " `
        -GetReplace "Get-PodeCache -Key '{{name}}'"
}

function Add-PodeScopedVariableInbuiltSecret {
    Add-PodeScopedVariable -Name 'secret' `
        -SetReplace "Update-PodeSecret -Name '{{name}}' -InputObject " `
        -GetReplace "Get-PodeSecret -Name '{{name}}'"
}

function Add-PodeScopedVariableInbuiltSession {
    Add-PodeScopedVariable -Name 'session' `
        -SetReplace "`$WebEvent.Session.Data.'{{name}}' = " `
        -GetReplace "`$WebEvent.Session.Data.'{{name}}'"
}

function Add-PodeScopedVariableInbuiltState {
    Add-PodeScopedVariable -Name 'state' `
        -SetReplace "Set-PodeState -Name '{{name}}' -Value " `
        -GetReplace "`$PodeContext.Server.State.'{{name}}'.Value"
}

function Add-PodeScopedVariableInbuiltUsing {
    Add-PodeScopedVariableInternal -Name 'using' -InternalFunction
}

function Convert-PodeScopedVariableInbuiltUsing {
    param(
        [Parameter(ValueFromPipeline = $true)]
        [scriptblock]
        $ScriptBlock,

        [Parameter()]
        [System.Management.Automation.SessionState]
        $PSSession
    )

    # do nothing if no script or session
    if (($null -eq $ScriptBlock) -or ($null -eq $PSSession)) {
        return $ScriptBlock, $null
    }

    # rename any __using_ vars for inner timers, etcs
    $strScriptBlock = "$($ScriptBlock)"
    $foundInnerUsing = $false

    while ($strScriptBlock -imatch '(?<full>\$__using_(?<name>[a-z0-9_\?]+))') {
        $foundInnerUsing = $true
        $strScriptBlock = $strScriptBlock.Replace($Matches['full'], "`$using:$($Matches['name'])")
    }

    # just return if there are no $using:
    if ($strScriptBlock -inotmatch '\$using:') {
        return $ScriptBlock, $null
    }

    # if we found any inner usings, recreate the scriptblock
    if ($foundInnerUsing) {
        $ScriptBlock = [scriptblock]::Create($strScriptBlock)
    }

    # get any using variables
    $usingVars = Get-PodeScopedVariableUsingVariable -ScriptBlock $ScriptBlock
    if (($null -eq $usingVars) -or ($usingVars.Count -eq 0)) {
        return $ScriptBlock, $null
    }

    # convert any using vars to use new names
    $usingVars = Find-PodeScopedVariableUsingVariableValue -UsingVariable $usingVars -PSSession $PSSession

    # now convert the script
    $newScriptBlock = Convert-PodeScopedVariableUsingVariable -ScriptBlock $ScriptBlock -UsingVariables $usingVars

    # return converted script
    return $newScriptBlock, $usingVars
}

<#
.SYNOPSIS
    Retrieves all occurrences of using variables within a given script block.

.DESCRIPTION
    The `Get-PodeScopedVariableUsingVariable` function analyzes a script block and identifies all instances of using variables.
    It returns an array of `UsingExpressionAst` objects representing these occurrences.

.PARAMETER ScriptBlock
    Specifies the script block to analyze. This parameter is mandatory.

.OUTPUTS
    Returns an array of `UsingExpressionAst` objects representing using variables found in the script block.

.EXAMPLE
    # Example usage:
    $scriptBlock = {
        $usingVar1 = "Hello"
        $usingVar2 = "World"
        Write-Host "Using variables: $usingVar1, $usingVar2"
    }

    $usingVariables = Get-PodeScopedVariableUsingVariable -ScriptBlock $scriptBlock
    # Process the identified using variables as needed.

.NOTES
    This is an internal function and may change in future releases of Pode.
#>
function Get-PodeScopedVariableUsingVariable {
    param(
        [Parameter(Mandatory = $true)]
        [scriptblock]
        $ScriptBlock
    )

    # Analyze the script block AST to find using variables
    return $ScriptBlock.Ast.FindAll({ $args[0] -is [System.Management.Automation.Language.UsingExpressionAst] }, $true)
}

<#
.SYNOPSIS
    Finds and maps using variables within a given script block to their corresponding values.

.DESCRIPTION
    The `Find-PodeScopedVariableUsingVariableValue` function analyzes a collection of using variables
    (represented as `UsingExpressionAst` objects) within a script block. It retrieves the values of these
    variables from the specified session state (`$PSSession`) and maps them for further processing.

.PARAMETER UsingVariable
    Specifies an array of `UsingExpressionAst` objects representing using variables found in the script block.
    This parameter is mandatory.

.PARAMETER PSSession
    Specifies the session state from which to retrieve variable values. This parameter is mandatory.

.OUTPUTS
    Returns an array of custom objects, each containing the following properties:
    - `OldName`: The original expression text for the using variable.
    - `NewName`: The modified name for the using variable (prefixed with "__using_").
    - `NewNameWithDollar`: The modified name with a dollar sign prefix (e.g., `$__using_VariableName`).
    - `SubExpressions`: An array of sub-expressions associated with the using variable.
    - `Value`: The value of the using variable retrieved from the session state.

.EXAMPLE
    # Example usage:
    $usingVariables = Get-PodeScopedVariableUsingVariable -ScriptBlock $scriptBlock
    $mappedVariables = Find-PodeScopedVariableUsingVariableValue -UsingVariable $usingVariables -PSSession $sessionState
    # Process the mapped variables as needed.

.NOTES
    - The function handles both direct using variables and child script using variables (prefixed with "__using_").
    - This is an internal function and may change in future releases of Pode.
#>
function Find-PodeScopedVariableUsingVariableValue {
    param(
        [Parameter(Mandatory = $true)]
        $UsingVariable,

        [Parameter(Mandatory = $true)]
        [System.Management.Automation.SessionState]
        $PSSession
    )

    $mapped = @{}

    foreach ($usingVar in $UsingVariable) {
        # Extract variable name
        $varName = $usingVar.SubExpression.VariablePath.UserPath

        # only retrieve value if new var
        if (!$mapped.ContainsKey($varName)) {
            # get value, or get __using_ value for child scripts
            $value = $PSSession.PSVariable.Get($varName)
            if ([string]::IsNullOrEmpty($value)) {
                $value = $PSSession.PSVariable.Get("__using_$($varName)")
            }

            if ([string]::IsNullOrEmpty($value)) {
                throw ($PodeLocale.valueForUsingVariableNotFoundExceptionMessage -f $varName) #"Value for `$using:$($varName) could not be found"
            }

            # Add to mapped variables
            $mapped[$varName] = @{
                OldName           = $usingVar.SubExpression.Extent.Text
                NewName           = "__using_$($varName)"
                NewNameWithDollar = "`$__using_$($varName)"
                SubExpressions    = @()
                Value             = $value.Value
            }
        }

        # Add the variable's sub-expression for later replacement
        $mapped[$varName].SubExpressions += $usingVar.SubExpression
    }

    return @($mapped.Values)
}

<#
.SYNOPSIS
    Converts a script block by replacing using variables with their corresponding values.

.DESCRIPTION
    The `Convert-PodeScopedVariableUsingVariable` function takes a script block and a collection of using variables.
    It replaces the using variables within the script block with their associated values.

.PARAMETER ScriptBlock
    Specifies the script block to convert. This parameter is mandatory.

.PARAMETER UsingVariables
    Specifies an array of custom objects representing using variables and their values.
    Each object should have the following properties:
    - `OldName`: The original expression text for the using variable.
    - `NewNameWithDollar`: The modified name with a dollar sign prefix (e.g., `$__using_VariableName`).
    - `SubExpressions`: An array of sub-expressions associated with the using variable.
    - `Value`: The value of the using variable.

.OUTPUTS
    Returns a new script block with replaced using variables.

.EXAMPLE
    # Example usage:
    $usingVariables = @(
        @{
            OldName           = '$usingVar1'
            NewNameWithDollar = '$__using_usingVar1'
            SubExpressions    = @($usingVar1.SubExpression1, $usingVar1.SubExpression2)
            Value             = 'SomeValue1'
        },
        # Add other using variables here...
    )

    $convertedScriptBlock = Convert-PodeScopedVariableUsingVariable -ScriptBlock $originalScriptBlock -UsingVariables $usingVariables
    # Use the converted script block as needed.

.NOTES
    This is an internal function and may change in future releases of Pode.
#>
function Convert-PodeScopedVariableUsingVariable {
    [CmdletBinding()]
    [OutputType([scriptblock])]
    param(
        [Parameter(Mandatory = $true)]
        [scriptblock]
        $ScriptBlock,

        [Parameter(Mandatory = $true)]
        [hashtable[]]
        $UsingVariables
    )
    # Create a list of variable expressions for replacement
    $varsList = [System.Collections.Generic.List[System.Management.Automation.Language.VariableExpressionAst]]::new()
    $newParams = [System.Collections.ArrayList]::new()

    foreach ($usingVar in $UsingVariables) {
        foreach ($subExp in $usingVar.SubExpressions) {
            $null = $varsList.Add($subExp)
        }
    }

    # Create a comma-separated list of new parameters
    $null = $newParams.AddRange(@($UsingVariables.NewNameWithDollar))
    $newParams = ($newParams -join ', ')
    $tupleParams = [tuple]::Create($varsList, $newParams)

    # Invoke the internal method to replace variables in the script block
    $bindingFlags = [System.Reflection.BindingFlags]'Default, NonPublic, Instance'
    $_varReplacerMethod = $ScriptBlock.Ast.GetType().GetMethod('GetWithInputHandlingForInvokeCommandImpl', $bindingFlags)
    $convertedScriptBlockStr = $_varReplacerMethod.Invoke($ScriptBlock.Ast, @($tupleParams))

    if (!$ScriptBlock.Ast.ParamBlock) {
        $convertedScriptBlockStr = "param($($newParams))`n$($convertedScriptBlockStr)"
    }

    $convertedScriptBlock = [scriptblock]::Create($convertedScriptBlockStr)

    # Handle cases where the script block starts with '$input |'
    if ($convertedScriptBlock.Ast.EndBlock[0].Statements.Extent.Text.StartsWith('$input |')) {
        $convertedScriptBlockStr = ($convertedScriptBlockStr -ireplace '\$input \|')
        $convertedScriptBlock = [scriptblock]::Create($convertedScriptBlockStr)
    }

    return $convertedScriptBlock
}
src\Private\Secrets.ps1
function Initialize-PodeSecretVault {
    param(
        [Parameter(Mandatory = $true, Position = 0, ValueFromPipeline = $true)]
        [hashtable]
        $VaultConfig,

        [Parameter(Mandatory = $true)]
        [scriptblock]
        $ScriptBlock
    )
    process {
        $null = Invoke-PodeScriptBlock -ScriptBlock $ScriptBlock -Splat -Arguments @($VaultConfig.Parameters)
    }
}

function Register-PodeSecretManagementVault {
    param(
        [Parameter(Mandatory = $true, Position = 0, ValueFromPipeline = $true)]
        [hashtable]
        $VaultConfig,

        [Parameter()]
        [string]
        $VaultName,

        [Parameter(Mandatory = $true)]
        [string]
        $ModuleName
    )
    begin {
        $pipelineItemCount = 0
    }

    process {
        $pipelineItemCount++
    }

    end {
        if ($pipelineItemCount -gt 1) {
            throw ($PodeLocale.fnDoesNotAcceptArrayAsPipelineInputExceptionMessage -f $($MyInvocation.MyCommand.Name))
        }
        # use the Name for VaultName if not passed
        if ([string]::IsNullOrWhiteSpace($VaultName)) {
            $VaultName = $VaultConfig.Name
        }

        # import the modules
        $null = Import-Module -Name Microsoft.PowerShell.SecretManagement -Force -DisableNameChecking -Scope Global -ErrorAction Stop -Verbose:$false
        $null = Import-Module -Name $ModuleName -Force -DisableNameChecking -Scope Global -ErrorAction Stop -Verbose:$false

        # export the modules for pode
        Export-PodeModule -Name @('Microsoft.PowerShell.SecretManagement', $ModuleName)

        # is this the local SecretStore provider?
        $isSecretStore = ($ModuleName -ieq 'Microsoft.PowerShell.SecretStore')

        # check if we have an unlock password for local secret store
        if ($isSecretStore) {
            if ([string]::IsNullOrEmpty($VaultConfig.Unlock.Secret)) {
                # An 'UnlockSecret' is required when using Microsoft.PowerShell.SecretStore
                throw ($PodeLocale.unlockSecretRequiredExceptionMessage)
            }
        }

        # does the local secret store already exist?
        $secretStoreExists = ($isSecretStore -and (Test-PodeSecretVaultInternal -Name $VaultName))

        # do we have vault params?
        $hasVaultParams = ($null -ne $VaultConfig.Parameters)

        # attempt to register the vault
        $registerParams = @{
            Name         = $VaultName
            ModuleName   = $ModuleName
            Confirm      = $false
            AllowClobber = $true
            ErrorAction  = 'Stop'
        }

        if (!$isSecretStore -and $hasVaultParams) {
            $registerParams['VaultParameters'] = $VaultConfig.Parameters
        }

        $null = Register-SecretVault @registerParams

        # all is good, so set the config
        $VaultConfig['SecretManagement'] = @{
            VaultName  = $VaultName
            ModuleName = $ModuleName
        }

        # set local secret store config
        if ($isSecretStore) {
            if (!$hasVaultParams) {
                $VaultConfig.Parameters = @{}
            }

            $vaultParams = $VaultConfig.Parameters

            # remove the password
            $vaultParams.Remove('Password')

            # set default authentication and interaction flags
            if ([string]::IsNullOrEmpty($vaultParams.Authentication)) {
                $vaultParams['Authentication'] = 'Password'
            }

            if ([string]::IsNullOrEmpty($vaultParams.Interaction)) {
                $vaultParams['Interaction'] = 'None'
            }

            # set default password timeout and unlock interval to 1 minute
            if ($VaultConfig.Unlock.Interval -le 0) {
                $VaultConfig.Unlock.Interval = 1
            }

            # unlock the vault, and set password
            $VaultConfig | Unlock-PodeSecretManagementVault

            # set the password timeout for the vault
            if (!$secretStoreExists) {
                if ($VaultConfig.Parameters.PasswordTimeout -le 0) {
                    $vaultParams['PasswordTimeout'] = ($VaultConfig.Unlock.Interval * 60) + 10
                }
            }

            # set config
            $null = Set-SecretStoreConfiguration @vaultParams -Confirm:$false -ErrorAction Stop
        }
    }
}

function Register-PodeSecretCustomVault {
    param(
        [Parameter(Mandatory = $true, Position = 0, ValueFromPipeline = $true)]
        [hashtable]
        $VaultConfig,

        [Parameter(Mandatory = $true)]
        [scriptblock]
        $ScriptBlock,

        [Parameter()]
        [scriptblock]
        $UnlockScriptBlock,

        [Parameter()]
        [scriptblock]
        $RemoveScriptBlock,

        [Parameter()]
        [scriptblock]
        $SetScriptBlock,

        [Parameter()]
        [scriptblock]
        $UnregisterScriptBlock
    )
    process {
        # unlock secret with no script?
        if ($VaultConfig.Unlock.Enabled -and (Test-PodeIsEmpty $UnlockScriptBlock)) {
            # Unlock secret supplied for custom Secret Vault type, but not Unlock ScriptBlock supplied
            throw ($PodeLocale.unlockSecretButNoScriptBlockExceptionMessage)
        }

        # all is good, so set the config
        $VaultConfig['Custom'] = @{
            Read       = $ScriptBlock
            Unlock     = $UnlockScriptBlock
            Remove     = $RemoveScriptBlock
            Set        = $SetScriptBlock
            Unregister = $UnregisterScriptBlock
        }
    }
}

function Unlock-PodeSecretManagementVault {
    param(
        [Parameter(Mandatory = $true, Position = 0, ValueFromPipeline = $true)]
        [hashtable]
        $VaultConfig
    )

    process {
        # do we need to unlock the vault?
        if (!$VaultConfig.Unlock.Enabled) {
            return $null
        }

        # unlock the vault
        $null = Unlock-SecretVault -Name $VaultConfig.SecretManagement.VaultName -Password $VaultConfig.Unlock.Secret -ErrorAction Stop

        # interval?
        if ($VaultConfig.Unlock.Interval -gt 0) {
            return ([datetime]::UtcNow.AddMinutes($VaultConfig.Unlock.Interval))
        }

        return $null
    }
}

function Unlock-PodeSecretCustomVault {
    param(
        [Parameter(Mandatory = $true, Position = 0, ValueFromPipeline = $true)]
        [hashtable]
        $VaultConfig
    )
    process {
        # do we need to unlock the vault?
        if (!$VaultConfig.Unlock.Enabled) {
            return
        }

        # do we have an unlock scriptblock
        if ($null -eq $VaultConfig.Custom.Unlock) {
            # No Unlock ScriptBlock supplied for unlocking the vault '$($VaultConfig.Name)'
            throw ($PodeLocale.noUnlockScriptBlockForVaultExceptionMessage -f $VaultConfig.Name)
        }

        # unlock the vault, and get back an expiry
        $expiry = Invoke-PodeScriptBlock -ScriptBlock $VaultConfig.Custom.Unlock -Splat -Return -Arguments @(
            $VaultConfig.Parameters,
        (ConvertFrom-SecureString -SecureString $VaultConfig.Unlock.Secret -AsPlainText)
        )

        # return expiry if given, otherwise check interval
        if ($null -ne $expiry) {
            return $expiry
        }

        if ($VaultConfig.Unlock.Interval -gt 0) {
            return ([datetime]::UtcNow.AddMinutes($VaultConfig.Unlock.Interval))
        }

        return $null
    }
}

function Unregister-PodeSecretManagementVault {
    param(
        [Parameter(Mandatory = $true, ValueFromPipeline = $true)]
        [hashtable]
        $VaultConfig
    )
    process {
        # do we need to unregister the vault?
        if ($VaultConfig.AutoImported) {
            return
        }

        # unregister the vault
        $null = Unregister-SecretVault -Name $VaultConfig.SecretManagement.VaultName -Confirm:$false -ErrorAction Stop
    }
}

function Unregister-PodeSecretCustomVault {
    param(
        [Parameter(Mandatory = $true, ValueFromPipeline = $true)]
        [hashtable]
        $VaultConfig
    )
    process {
        # do we need to unregister the vault?
        if ($VaultConfig.AutoImported) {
            return
        }

        # do we have an unregister scriptblock? if not, just do nothing
        if ($null -eq $VaultConfig.Custom.Unregister) {
            return
        }

        # unregister the vault
        $null = Invoke-PodeScriptBlock -ScriptBlock $VaultConfig.Custom.Unregister -Splat -Arguments @(
            $VaultConfig.Parameters
        )
    }
}

function Get-PodeSecretManagementKey {
    param(
        [Parameter(Mandatory = $true)]
        [string]
        $Vault,

        [Parameter(Mandatory = $true)]
        [string]
        $Key
    )

    # get the vault
    $_vault = $PodeContext.Server.Secrets.Vaults[$Vault]

    # fetch the secret
    return (Get-Secret -Name $Key -Vault $_vault.SecretManagement.VaultName -AsPlainText -ErrorAction Stop)
}

function Get-PodeSecretCustomKey {
    param(
        [Parameter(Mandatory = $true)]
        [string]
        $Vault,

        [Parameter(Mandatory = $true)]
        [string]
        $Key,

        [Parameter()]
        [object[]]
        $ArgumentList
    )

    # get the vault
    $_vault = $PodeContext.Server.Secrets.Vaults[$Vault]

    # fetch the secret
    return Invoke-PodeScriptBlock -ScriptBlock $_vault.Custom.Read -Splat -Return -Arguments (@(
            $_vault.Parameters,
            $Key
        ) + $ArgumentList)
}

function Set-PodeSecretManagementKey {
    param(
        [Parameter(Mandatory = $true)]
        [string]
        $Vault,

        [Parameter(Mandatory = $true)]
        [string]
        $Key,

        [Parameter(Mandatory = $true)]
        [object]
        $Value,

        [Parameter()]
        [hashtable]
        $Metadata
    )

    # get the vault
    $_vault = $PodeContext.Server.Secrets.Vaults[$Vault]

    # set the secret
    $null = Set-Secret -Name $Key -Secret $Value -Vault $_vault.SecretManagement.VaultName -Metadata $Metadata -Confirm:$false -ErrorAction Stop
}

function Set-PodeSecretCustomKey {
    param(
        [Parameter(Mandatory = $true)]
        [string]
        $Vault,

        [Parameter(Mandatory = $true)]
        [string]
        $Key,

        [Parameter(Mandatory = $true)]
        [object]
        $Value,

        [Parameter()]
        [hashtable]
        $Metadata,

        [Parameter()]
        [object[]]
        $ArgumentList
    )

    # get the vault
    $_vault = $PodeContext.Server.Secrets.Vaults[$Vault]

    # do we have a set scriptblock?
    if ($null -eq $_vault.Custom.Set) {
        throw ($PodeLocale.noSetScriptBlockForVaultExceptionMessage -f $_vault.Name) #"No Set ScriptBlock supplied for updating/creating secrets in the vault '$($_vault.Name)'"
    }

    # set the secret
    $null = Invoke-PodeScriptBlock -ScriptBlock $_vault.Custom.Set -Splat -Arguments (@(
            $_vault.Parameters,
            $Key,
            $Value,
            $Metadata
        ) + $ArgumentList)
}

function Remove-PodeSecretManagementKey {
    param(
        [Parameter(Mandatory = $true)]
        [string]
        $Vault,

        [Parameter(Mandatory = $true)]
        [string]
        $Key
    )

    # get the vault
    $_vault = $PodeContext.Server.Secrets.Vaults[$Vault]

    # remove the secret
    $null = Remove-Secret -Name $Key -Vault $_vault.SecretManagement.VaultName -Confirm:$false -ErrorAction Stop
}

function Remove-PodeSecretCustomKey {
    param(
        [Parameter(Mandatory = $true)]
        [string]
        $Vault,

        [Parameter(Mandatory = $true)]
        [string]
        $Key,

        [Parameter()]
        [object[]]
        $ArgumentList
    )

    # get the vault
    $_vault = $PodeContext.Server.Secrets.Vaults[$Vault]

    # do we have a remove scriptblock?
    if ($null -eq $_vault.Custom.Remove) {
        throw ($PodeLocale.noRemoveScriptBlockForVaultExceptionMessage -f $_vault.Name) #"No Remove ScriptBlock supplied for removing secrets from the vault '$($_vault.Name)'"
    }

    # remove the secret
    $null = Invoke-PodeScriptBlock -ScriptBlock $_vault.Custom.Remove -Splat -Arguments (@(
            $_vault.Parameters,
            $Key
        ) + $ArgumentList)
}

function Start-PodeSecretCacheHousekeeper {
    if (Test-PodeTimer -Name '__pode_secrets_cache_expiry__') {
        return
    }

    Add-PodeTimer -Name '__pode_secrets_cache_expiry__' -Interval 60 -ScriptBlock {
        $now = [datetime]::UtcNow

        foreach ($key in $PodeContext.Server.Secrets.Keys.Values) {
            if (!$key.Cache.Enabled -or ($null -eq $key.Cache.Expiry) -or ($key.Cache.Expiry -gt $now)) {
                continue
            }

            $key.Cache.Expiry = $null
            $key.Cache.Value = $null
        }
    }
}

function Start-PodeSecretVaultUnlocker {
    if (Test-PodeTimer -Name '__pode_secrets_vault_unlock__') {
        return
    }

    Add-PodeTimer -Name '__pode_secrets_vault_unlock__' -Interval 60 -ScriptBlock {
        $now = [datetime]::UtcNow

        foreach ($vault in $PodeContext.Server.Secrets.Vaults.Values) {
            if (!$vault.Unlock.Enabled -or ($null -eq $vault.Unlock.Expiry) -or ($vault.Unlock.Expiry -gt $now)) {
                continue
            }

            Unlock-PodeSecretVault -Name $vault.Name
        }
    }
}

<#
.SYNOPSIS
    Unregisters multiple secret vaults within Pode.

.DESCRIPTION
    The `Unregister-PodeSecretVaultsInternal` function iterates through the list of secret vaults
    stored in the PodeContext and unregisters each one. If an error occurs during unregistration,
    it can either throw an exception or log the error.

.PARAMETER ThrowError
    If specified, the function will throw an exception when an error occurs during unregistration.
    Otherwise, it will log the error and continue processing.

.INPUTS
    None. You cannot pipe objects to Unregister-PodeSecretVaultsInternal.

.OUTPUTS
    None. The function modifies the state of secret vaults in the PodeContext.

.EXAMPLE
    # Example usage:
    Unregister-PodeSecretVaultsInternal -ThrowError
    # All registered secret vaults are unregistered, and any errors are thrown as exceptions.

.NOTES
    This is an internal function and may change in future releases of Pode.
#>
function Unregister-PodeSecretVaultsInternal {
    [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseSingularNouns', '')]
    param(
        [switch]
        $ThrowError
    )

    # Check if there are any secret vaults to unregister
    if (Test-PodeIsEmpty $PodeContext.Server.Secrets.Vaults) {
        return
    }

    # Iterate through each vault and attempt unregistration
    foreach ($vault in $PodeContext.Server.Secrets.Vaults.Values.Name) {
        if ([string]::IsNullOrEmpty($vault)) {
            continue
        }

        try {
            Unregister-PodeSecretVault -Name $vault
        }
        catch {
            if ($ThrowError) {
                throw
            }
            else {
                $_ | Write-PodeErrorLog
            }
        }
    }
}

function Protect-PodeSecretValueType {
    param(
        [Parameter(Mandatory = $true)]
        [object]
        $Value
    )

    if ($Value -is [System.ValueType]) {
        $Value = $Value.ToString()
    }

    if ([string]::IsNullOrEmpty($Value)) {
        $Value = [string]::Empty
    }

    if ($Value -is [System.Collections.Specialized.OrderedDictionary]) {
        $Value = [hashtable]$Value
    }

    if (!(
         ($Value -is [string]) -or
         ($Value -is [securestring]) -or
         ($Value -is [hashtable]) -or
         ($Value -is [byte[]]) -or
         ($Value -is [pscredential]) -or
         ($Value -is [System.Management.Automation.OrderedHashtable])
        )) {
        throw ($PodeLocale.invalidSecretValueTypeExceptionMessage -f $Value.GetType().Name) #"Value to set secret to is of an invalid type. Expected either String, SecureString, HashTable, Byte[], or PSCredential. But got: $($Value.GetType().Name)"
    }

    return $Value
}

function Test-PodeSecretVaultInternal {
    param(
        [Parameter(Mandatory = $true)]
        [string]
        $Name
    )

    return ($null -ne (Get-SecretVault -Name $Name -ErrorAction Ignore))
}
src\Private\Security.ps1
using namespace System.Security.Cryptography

function Test-PodeIPLimit {
    param(
        [Parameter(Mandatory = $true)]
        [ValidateNotNull()]
        $IP
    )

    $type = 'IP'

    # get the limit rules and active list
    $rules = $PodeContext.Server.Limits.Rules[$type]
    $active = $PodeContext.Server.Limits.Active[$type]

    # if there are no rules, it's valid
    if (($null -eq $rules) -or ($rules.Count -eq 0)) {
        return $true
    }

    # get the ip address in bytes
    $IP = @{
        String = $IP.IPAddressToString
        Family = $IP.AddressFamily
        Bytes  = $IP.GetAddressBytes()
    }

    # now
    $now = [DateTime]::UtcNow

    # is the ip active? (get a direct match, then try grouped subnets)
    $_active_ip = $active[$IP.String]
    if ($null -eq $_active_ip) {
        $_groups = @(foreach ($key in $active.Keys) {
                if ($active[$key].Rule.Grouped) {
                    $active[$key]
                }
            })

        $_active_ip = @(foreach ($_group in $_groups) {
                if (Test-PodeIPAddressInRange -IP $IP -LowerIP $_group.Rule.Lower -UpperIP $_group.Rule.Upper) {
                    $_group
                    break
                }
            })[0]
    }

    # the ip is active, or part of a grouped subnet
    if ($null -ne $_active_ip) {
        # if limit is -1, always allowed
        if ($_active_ip.Rule.Limit -eq -1) {
            return $true
        }

        # check expire time, a reset if needed
        if ($now -ge $_active_ip.Expire) {
            $_active_ip.Rate = 0
            $_active_ip.Expire = $now.AddSeconds($_active_ip.Rule.Seconds)
        }

        # are we over the limit?
        if ($_active_ip.Rate -ge $_active_ip.Rule.Limit) {
            return $false
        }

        # increment the rate
        $_active_ip.Rate++
        return $true
    }

    # the ip isn't active
    else {
        # get the ip's rule
        $_rule_ip = @(foreach ($rule in $rules.Values) {
                if (Test-PodeIPAddressInRange -IP $IP -LowerIP $rule.Lower -UpperIP $rule.Upper) {
                    $rule
                    break
                }
            })[0]

        # if ip not in rules, it's valid
        # (add to active list as always allowed - saves running where search everytime)
        if ($null -eq $_rule_ip) {
            $active[$IP.String] = @{
                Rule = @{
                    Limit = -1
                }
            }

            return $true
        }

        # add ip to active list (ip if not grouped, else the subnet if it's grouped)
        $_ip = (Resolve-PodeValue -Check $_rule_ip.Grouped -TrueValue $_rule_ip.IP -FalseValue $IP.String)

        $active[$_ip] = @{
            Rule   = $_rule_ip
            Rate   = 1
            Expire = $now.AddSeconds($_rule_ip.Seconds)
        }

        # if limit is 0, it's never allowed
        return ($_rule_ip -ne 0)
    }
}

function Test-PodeRouteLimit {
    param(
        [Parameter(Mandatory = $true)]
        [ValidateNotNull()]
        [string]
        $Path
    )

    $type = 'Route'

    # get the limit rules and active list
    $rules = $PodeContext.Server.Limits.Rules[$type]
    $active = $PodeContext.Server.Limits.Active[$type]

    # if there are no rules, it's valid
    if (($null -eq $rules) -or ($rules.Count -eq 0)) {
        return $true
    }

    # now
    $now = [DateTime]::UtcNow

    # is the route active?
    $_active_route = $active[$Path]

    # the ip is active, or part of a grouped subnet
    if ($null -ne $_active_route) {
        # if limit is -1, always allowed
        if ($_active_route.Rule.Limit -eq -1) {
            return $true
        }

        # check expire time, a reset if needed
        if ($now -ge $_active_route.Expire) {
            $_active_route.Rate = 0
            $_active_route.Expire = $now.AddSeconds($_active_route.Rule.Seconds)
        }

        # are we over the limit?
        if ($_active_route.Rate -ge $_active_route.Rule.Limit) {
            return $false
        }

        # increment the rate
        $_active_route.Rate++
        return $true
    }

    # the route isn't active
    else {
        # get the route's rule
        $_rule_route = $rules[$Path]

        # if route not in rules, it's valid (add to active list as always allowed)
        if ($null -eq $_rule_route) {
            $active[$Path] = @{
                Rule = @{
                    Limit = -1
                }
            }

            return $true
        }

        # add route to active list
        $active[$Path] = @{
            Rule   = $_rule_route
            Rate   = 1
            Expire = $now.AddSeconds($_rule_route.Seconds)
        }

        # if limit is 0, it's never allowed
        return ($_rule_route -ne 0)
    }
}

<#
.SYNOPSIS
Checks if a given endpoint has exceeded its limit according to the defined rate limiting rules in Pode.

.DESCRIPTION
This function evaluates the rate limiting rules for a specified endpoint and determines if the endpoint is allowed to proceed based on the defined limits and the current usage rate. If the endpoint is not active or not defined in the rules, it is either allowed by default or added to the active list with its respective rule.

.PARAMETER EndpointName
The name of the endpoint to check against the rate limiting rules.

.EXAMPLE
Test-PodeEndpointLimit -EndpointName "MyEndpoint"
Checks if "MyEndpoint" is allowed to proceed based on the current rate limiting rules.

.EXAMPLE
$result = Test-PodeEndpointLimit -EndpointName $null
Checks if an unnamed endpoint (e.g., $null) is allowed, which always returns $true.

.RETURNS
[boolean] - Returns $true if the endpoint is allowed, otherwise $false.

.NOTES
This is an internal function and may change in future releases of Pode.
#>
function Test-PodeEndpointLimit {
    param(
        [Parameter()]
        [string]
        $EndpointName
    )

    $type = 'Endpoint'

    if ([string]::IsNullOrWhiteSpace($EndpointName)) {
        return $true
    }

    # get the limit rules and active list
    $rules = $PodeContext.Server.Limits.Rules[$type]
    $active = $PodeContext.Server.Limits.Active[$type]

    # if there are no rules, it's valid
    if (($null -eq $rules) -or ($rules.Count -eq 0)) {
        return $true
    }

    # now
    $now = [DateTime]::UtcNow

    # is the endpoint active?
    $_active_endpoint = $active[$EndpointName]

    # the endpoint is active
    if ($null -ne $_active_endpoint) {
        # if limit is -1, always allowed
        if ($_active_endpoint.Rule.Limit -eq -1) {
            return $true
        }

        # check expire time, a reset if needed
        if ($now -ge $_active_endpoint.Expire) {
            $_active_endpoint.Rate = 0
            $_active_endpoint.Expire = $now.AddSeconds($_active_endpoint.Rule.Seconds)
        }

        # are we over the limit?
        if ($_active_endpoint.Rate -ge $_active_endpoint.Rule.Limit) {
            return $false
        }

        # increment the rate
        $_active_endpoint.Rate++
        return $true
    }

    # the endpoint isn't active
    else {
        # get the endpoint's rule
        $_rule_endpoint = $rules[$EndpointName]

        # if endpoint not in rules, it's valid (add to active list as always allowed)
        if ($null -eq $_rule_endpoint) {
            $active[$EndpointName] = @{
                Rule = @{
                    Limit = -1
                }
            }

            return $true
        }

        # add endpoint to active list
        $active[$EndpointName] = @{
            Rule   = $_rule_endpoint
            Rate   = 1
            Expire = $now.AddSeconds($_rule_endpoint.Seconds)
        }

        # if limit is 0, it's never allowed
        return ($_rule_endpoint -ne 0)
    }
}

function Test-PodeIPAccess {
    param(
        [Parameter(Mandatory = $true)]
        [ValidateNotNull()]
        $IP
    )

    $type = 'IP'

    # get permission lists for ip
    $allow = $PodeContext.Server.Access.Allow[$type]
    $deny = $PodeContext.Server.Access.Deny[$type]

    # are they empty?
    $alEmpty = (($null -eq $allow) -or ($allow.Count -eq 0))
    $dnEmpty = (($null -eq $deny) -or ($deny.Count -eq 0))

    # if both are empty, value is valid
    if ($alEmpty -and $dnEmpty) {
        return $true
    }

    # get the ip address in bytes
    $IP = @{
        Family = $IP.AddressFamily
        Bytes  = $IP.GetAddressBytes()
    }

    # if value in allow, it's allowed
    if (!$alEmpty) {
        $match = @(foreach ($value in $allow.Values) {
                if (Test-PodeIPAddressInRange -IP $IP -LowerIP $value.Lower -UpperIP $value.Upper) {
                    $value
                    break
                }
            })[0]

        if ($null -ne $match) {
            return $true
        }
    }

    # if value in deny, it's disallowed
    if (!$dnEmpty) {
        $match = @(foreach ($value in $deny.Values) {
                if (Test-PodeIPAddressInRange -IP $IP -LowerIP $value.Lower -UpperIP $value.Upper) {
                    $value
                    break
                }
            })[0]

        if ($null -ne $match) {
            return $false
        }
    }

    # if we have an allow, it's disallowed (because it's not in there)
    if (!$alEmpty) {
        return $false
    }

    # otherwise it's allowed (because it's not in the deny)
    return $true
}

function Add-PodeIPLimit {
    param(
        [Parameter(Mandatory = $true)]
        [ValidateNotNull()]
        [string]
        $IP,

        [Parameter(Mandatory = $true)]
        [int]
        $Limit,

        [Parameter(Mandatory = $true)]
        [int]
        $Seconds,

        [switch]
        $Group
    )

    # current limit type
    $type = 'IP'

    # ensure limit and seconds are non-zero and negative
    if ($Limit -le 0) {
        throw ($PodeLocale.limitValueCannotBeZeroOrLessExceptionMessage -f $IP) #"Limit value cannot be 0 or less for $($IP)"
    }

    if ($Seconds -le 0) {
        throw ($PodeLocale.secondsValueCannotBeZeroOrLessExceptionMessage -f $IP) #"Seconds value cannot be 0 or less for $($IP)"
    }

    # get current rules
    $rules = $PodeContext.Server.Limits.Rules[$type]

    # setup up perm type
    if ($null -eq $rules) {
        $PodeContext.Server.Limits.Rules[$type] = @{}
        $PodeContext.Server.Limits.Active[$type] = @{}
        $rules = $PodeContext.Server.Limits.Rules[$type]
    }

    # have we already added the ip?
    elseif ($rules.ContainsKey($IP)) {
        return
    }

    # calculate the lower/upper ip bounds
    if (Test-PodeIPAddressIsSubnetMask -IP $IP) {
        $_tmp = Get-PodeSubnetRange -SubnetMask $IP
        $_tmpLo = Get-PodeIPAddress -IP $_tmp.Lower
        $_tmpHi = Get-PodeIPAddress -IP $_tmp.Upper
    }
    elseif (Test-PodeIPAddressAny -IP $IP) {
        $_tmpLo = Get-PodeIPAddress -IP '0.0.0.0'
        $_tmpHi = Get-PodeIPAddress -IP '255.255.255.255'
    }
    else {
        $_tmpLo = Get-PodeIPAddress -IP $IP
        $_tmpHi = $_tmpLo
    }

    # add limit rule for ip
    $rules.Add($IP, @{
            Limit   = $Limit
            Seconds = $Seconds
            Grouped = [bool]$Group
            IP      = $IP
            Lower   = @{
                Family = $_tmpLo.AddressFamily
                Bytes  = $_tmpLo.GetAddressBytes()
            }
            Upper   = @{
                Family = $_tmpHi.AddressFamily
                Bytes  = $_tmpHi.GetAddressBytes()
            }
        })
}

function Add-PodeRouteLimit {
    param(
        [Parameter(Mandatory = $true)]
        [ValidateNotNull()]
        [string]
        $Path,

        [Parameter(Mandatory = $true)]
        [int]
        $Limit,

        [Parameter(Mandatory = $true)]
        [int]
        $Seconds,

        [switch]
        $Group
    )

    # current limit type
    $type = 'Route'

    # ensure limit and seconds are non-zero and negative
    if ($Limit -le 0) {
        throw ($PodeLocale.limitValueCannotBeZeroOrLessExceptionMessage -f $IP) #"Limit value cannot be 0 or less for $($IP)"
    }

    if ($Seconds -le 0) {
        throw ($PodeLocale.secondsValueCannotBeZeroOrLessExceptionMessage -f $IP) #"Seconds value cannot be 0 or less for $($IP)"
    }

    # get current rules
    $rules = $PodeContext.Server.Limits.Rules[$type]

    # setup up perm type
    if ($null -eq $rules) {
        $PodeContext.Server.Limits.Rules[$type] = @{}
        $PodeContext.Server.Limits.Active[$type] = @{}
        $rules = $PodeContext.Server.Limits.Rules[$type]
    }

    # have we already added the route?
    elseif ($rules.ContainsKey($Path)) {
        return
    }

    # add limit rule for the route
    $rules.Add($Path, @{
            Limit   = $Limit
            Seconds = $Seconds
            Grouped = [bool]$Group
            Path    = $Path
        })
}

function Add-PodeEndpointLimit {
    param(
        [Parameter(Mandatory = $true)]
        [ValidateNotNull()]
        [string]
        $EndpointName,

        [Parameter(Mandatory = $true)]
        [int]
        $Limit,

        [Parameter(Mandatory = $true)]
        [int]
        $Seconds,

        [switch]
        $Group
    )

    # current limit type
    $type = 'Endpoint'

    # does the endpoint exist?
    $endpoint = Get-PodeEndpointByName -Name $EndpointName
    if ($null -eq $endpoint) {
        throw ($PodeLocale.endpointNameNotExistExceptionMessage -f $EndpointName) #"Endpoint not found: $($EndpointName)"
    }

    # ensure limit and seconds are non-zero and negative
    if ($Limit -le 0) {
        throw ($PodeLocale.limitValueCannotBeZeroOrLessExceptionMessage -f $IP) #"Limit value cannot be 0 or less for $($IP)"
    }

    if ($Seconds -le 0) {
        throw ($PodeLocale.secondsValueCannotBeZeroOrLessExceptionMessage -f $IP) #"Seconds value cannot be 0 or less for $($IP)"
    }

    # get current rules
    $rules = $PodeContext.Server.Limits.Rules[$type]

    # setup up perm type
    if ($null -eq $rules) {
        $PodeContext.Server.Limits.Rules[$type] = @{}
        $PodeContext.Server.Limits.Active[$type] = @{}
        $rules = $PodeContext.Server.Limits.Rules[$type]
    }

    # have we already added the endpoint?
    elseif ($rules.ContainsKey($EndpointName)) {
        return
    }

    # add limit rule for the endpoint
    $rules.Add($EndpointName, @{
            Limit        = $Limit
            Seconds      = $Seconds
            Grouped      = [bool]$Group
            EndpointName = $EndpointName
        })
}

function Add-PodeIPAccess {
    param(
        [Parameter(Mandatory = $true)]
        [ValidateSet('Allow', 'Deny')]
        [string]
        $Access,

        [Parameter(Mandatory = $true)]
        [string]
        $IP
    )

    # current access type
    $type = 'IP'

    # get opposite permission
    $opp = "$(if ($Access -ieq 'allow') { 'Deny' } else { 'Allow' })"

    # get permission lists for type
    $permType = $PodeContext.Server.Access[$Access][$type]
    $oppType = $PodeContext.Server.Access[$opp][$type]

    # setup up perm type
    if ($null -eq $permType) {
        $PodeContext.Server.Access[$Access][$type] = @{}
        $permType = $PodeContext.Server.Access[$Access][$type]
    }

    # have we already added the ip?
    elseif ($permType.ContainsKey($IP)) {
        return
    }

    # remove from opp type
    if ($null -ne $oppType -and $oppType.ContainsKey($IP)) {
        $oppType.Remove($IP)
    }

    # calculate the lower/upper ip bounds
    if (Test-PodeIPAddressIsSubnetMask -IP $IP) {
        $_tmp = Get-PodeSubnetRange -SubnetMask $IP
        $_tmpLo = Get-PodeIPAddress -IP $_tmp.Lower
        $_tmpHi = Get-PodeIPAddress -IP $_tmp.Upper
    }
    elseif (Test-PodeIPAddressAny -IP $IP) {
        $_tmpLo = Get-PodeIPAddress -IP '0.0.0.0'
        $_tmpHi = Get-PodeIPAddress -IP '255.255.255.255'
    }
    else {
        $_tmpLo = Get-PodeIPAddress -IP $IP
        $_tmpHi = $_tmpLo
    }

    # add access rule for ip
    $permType.Add($IP, @{
            Lower = @{
                Family = $_tmpLo.AddressFamily
                Bytes  = $_tmpLo.GetAddressBytes()
            }
            Upper = @{
                Family = $_tmpHi.AddressFamily
                Bytes  = $_tmpHi.GetAddressBytes()
            }
        })
}

function Get-PodeCsrfToken {
    # key name to search
    $key = $PodeContext.Server.Cookies.Csrf.Name

    # check the payload
    if (!(Test-PodeIsEmpty $WebEvent.Data[$key])) {
        return $WebEvent.Data[$key]
    }

    # check the query string
    if (!(Test-PodeIsEmpty $WebEvent.Query[$key])) {
        return $WebEvent.Query[$key]
    }

    # check the headers
    $value = (Get-PodeHeader -Name $key)
    if (!(Test-PodeIsEmpty $value)) {
        return $value
    }

    return $null
}

function Test-PodeCsrfToken {
    param(
        [Parameter()]
        [string]
        $Secret,

        [Parameter()]
        [string]
        $Token
    )

    # if there's no token/secret, fail
    if ((Test-PodeIsEmpty $Secret) -or (Test-PodeIsEmpty $Token)) {
        return $false
    }

    # the token must start with "t:"
    if (!$Token.StartsWith('t:')) {
        return $false
    }

    # get the salt from the token
    $_token = $Token.Substring(2)
    $periodIndex = $_token.LastIndexOf('.')
    if ($periodIndex -eq -1) {
        return $false
    }

    $salt = $_token.Substring(0, $periodIndex)

    # ensure the token is valid
    if ((Restore-PodeCsrfToken -Secret $Secret -Salt $salt) -ne $Token) {
        return $false
    }

    return $true
}

function New-PodeCsrfSecret {
    # see if there's already a secret in session/cookie
    $secret = (Get-PodeCsrfSecret)
    if (!(Test-PodeIsEmpty $secret)) {
        return $secret
    }

    # otherwise, make a new secret and cache it
    $secret = (New-PodeGuid -Secure -Length 16)
    Set-PodeCsrfSecret -Secret $secret
    return $secret
}

function Get-PodeCsrfSecret {
    # key name to get secret
    $key = $PodeContext.Server.Cookies.Csrf.Name

    # are we getting it from a cookie, or session?
    if ($PodeContext.Server.Cookies.Csrf.UseCookies) {
        $cookie = Get-PodeCookie `
            -Name $PodeContext.Server.Cookies.Csrf.Name `
            -Secret $PodeContext.Server.Cookies.Csrf.Secret
        return $cookie.Value
    }

    # on session
    else {
        return $WebEvent.Session.Data[$key]
    }
}

function Set-PodeCsrfSecret {
    param(
        [Parameter(Mandatory = $true)]
        [string]
        $Secret
    )

    # key name to set secret under
    $key = $PodeContext.Server.Cookies.Csrf.Name

    # are we setting this on a cookie, or session?
    if ($PodeContext.Server.Cookies.Csrf.UseCookies) {
        $null = Set-PodeCookie `
            -Name $PodeContext.Server.Cookies.Csrf.Name `
            -Value $Secret `
            -Secret $PodeContext.Server.Cookies.Csrf.Secret
    }

    # on session
    else {
        $WebEvent.Session.Data[$key] = $Secret
    }
}

function Restore-PodeCsrfToken {
    param(
        [Parameter(Mandatory = $true)]
        [string]
        $Secret,

        [Parameter(Mandatory = $true)]
        [string]
        $Salt
    )

    return "t:$($Salt).$(Invoke-PodeSHA256Hash -Value "$($Salt)-$($Secret)")"
}

function Test-PodeCsrfConfigured {
    return (!(Test-PodeIsEmpty $PodeContext.Server.Cookies.Csrf))
}

function Get-PodeCertificateByFile {
    param(
        [Parameter(Mandatory = $true)]
        [string]
        $Certificate,

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

        [Parameter()]
        [string]
        $Key = $null
    )

    # cert + key
    if (![string]::IsNullOrWhiteSpace($Key)) {
        return (Get-PodeCertificateByPemFile -Certificate $Certificate -Password $Password -Key $Key)
    }

    $path = Get-PodeRelativePath -Path $Certificate -JoinRoot -Resolve

    # cert + password
    if (![string]::IsNullOrWhiteSpace($Password)) {
        return [X509Certificates.X509Certificate2]::new($path, $Password)
    }

    # plain cert
    return [X509Certificates.X509Certificate2]::new($path)
}

function Get-PodeCertificateByPemFile {
    param(
        [Parameter(Mandatory = $true)]
        [string]
        $Certificate,

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

        [Parameter()]
        [string]
        $Key = $null
    )

    $cert = $null

    $certPath = Get-PodeRelativePath -Path $Certificate -JoinRoot -Resolve
    $keyPath = Get-PodeRelativePath -Path $Key -JoinRoot -Resolve

    # pem's kinda work in .NET3/.NET5
    if ([version]$PSVersionTable.PSVersion -ge [version]'7.0.0') {
        $cert = [X509Certificates.X509Certificate2]::new($certPath)
        $keyText = [System.IO.File]::ReadAllText($keyPath)
        $rsa = [RSA]::Create()

        # .NET5
        if ([version]$PSVersionTable.PSVersion -ge [version]'7.1.0') {
            if ([string]::IsNullOrWhiteSpace($Password)) {
                $rsa.ImportFromPem($keyText)
            }
            else {
                $rsa.ImportFromEncryptedPem($keyText, $Password)
            }
        }

        # .NET3
        else {
            $keyBlocks = $keyText.Split('-', [System.StringSplitOptions]::RemoveEmptyEntries)
            $keyBytes = [System.Convert]::FromBase64String($keyBlocks[1])

            if ($keyBlocks[0] -ieq 'BEGIN PRIVATE KEY') {
                $rsa.ImportPkcs8PrivateKey($keyBytes, [ref]$null)
            }
            elseif ($keyBlocks[0] -ieq 'BEGIN RSA PRIVATE KEY') {
                $rsa.ImportRSAPrivateKey($keyBytes, [ref]$null)
            }
            elseif ($keyBlocks[0] -ieq 'BEGIN ENCRYPTED PRIVATE KEY') {
                $rsa.ImportEncryptedPkcs8PrivateKey($Password, $keyBytes, [ref]$null)
            }
        }

        $cert = [X509Certificates.RSACertificateExtensions]::CopyWithPrivateKey($cert, $rsa)
        $cert = [X509Certificates.X509Certificate2]::new($cert.Export([X509Certificates.X509ContentType]::Pkcs12))
    }

    # for everything else, there's the openssl way
    else {
        $tempFile = Join-Path (Split-Path -Parent -Path $certPath) 'temp.pfx'

        try {
            if ([string]::IsNullOrWhiteSpace($Password)) {
                $Password = [string]::Empty
            }

            $result = openssl pkcs12 -inkey $keyPath -in $certPath -export -passin pass:$Password -password pass:$Password -out $tempFile
            if (!$?) {
                throw ($PodeLocale.failedToCreateOpenSslCertExceptionMessage -f $result) #"Failed to create openssl cert: $($result)"
            }

            $cert = [X509Certificates.X509Certificate2]::new($tempFile, $Password)
        }
        finally {
            $null = Remove-Item $tempFile -Force
        }
    }

    return $cert
}

function Find-PodeCertificateInCertStore {
    param(
        [Parameter(Mandatory = $true)]
        [X509Certificates.X509FindType]
        $FindType,

        [Parameter(Mandatory = $true)]
        [string]
        $Query,

        [Parameter(Mandatory = $true)]
        [X509Certificates.StoreName]
        $StoreName,

        [Parameter(Mandatory = $true)]
        [X509Certificates.StoreLocation]
        $StoreLocation
    )

    # fail if not windows
    if (!(Test-PodeIsWindows)) {
        # Certificate Thumbprints/Name are only supported on Windows
        throw ($PodeLocale.certificateThumbprintsNameSupportedOnWindowsExceptionMessage)
    }

    # open the currentuser\my store
    $x509store = [X509Certificates.X509Store]::new($StoreName, $StoreLocation)

    try {
        # attempt to find the cert
        $x509store.Open([X509Certificates.OpenFlags]::ReadOnly)
        $x509certs = $x509store.Certificates.Find($FindType, $Query, $false)
    }
    finally {
        # close the store!
        if ($null -ne $x509store) {
            Close-PodeDisposable -Disposable $x509store -Close
        }
    }

    # fail if no cert found for query
    if (($null -eq $x509certs) -or ($x509certs.Count -eq 0)) {
        throw ($PodeLocale.noCertificateFoundExceptionMessage -f $StoreLocation, $StoreName, $Query) # "No certificate could be found in $($StoreLocation)\$($StoreName) for '$($Query)'"
    }

    return ([X509Certificates.X509Certificate2]($x509certs[0]))
}

function Get-PodeCertificateByThumbprint {
    param(
        [Parameter(Mandatory = $true)]
        [string]
        $Thumbprint,

        [Parameter(Mandatory = $true)]
        [X509Certificates.StoreName]
        $StoreName,

        [Parameter(Mandatory = $true)]
        [X509Certificates.StoreLocation]
        $StoreLocation
    )

    return Find-PodeCertificateInCertStore `
        -FindType ([X509Certificates.X509FindType]::FindByThumbprint) `
        -Query $Thumbprint `
        -StoreName $StoreName `
        -StoreLocation $StoreLocation
}

function Get-PodeCertificateByName {
    param(
        [Parameter(Mandatory = $true)]
        [string]
        $Name,

        [Parameter(Mandatory = $true)]
        [X509Certificates.StoreName]
        $StoreName,

        [Parameter(Mandatory = $true)]
        [X509Certificates.StoreLocation]
        $StoreLocation
    )

    return Find-PodeCertificateInCertStore `
        -FindType ([X509Certificates.X509FindType]::FindBySubjectName) `
        -Query $Name `
        -StoreName $StoreName `
        -StoreLocation $StoreLocation
}

function New-PodeSelfSignedCertificate {
    $sanBuilder = [X509Certificates.SubjectAlternativeNameBuilder]::new()
    $null = $sanBuilder.AddIpAddress([ipaddress]::Loopback)
    $null = $sanBuilder.AddIpAddress([ipaddress]::IPv6Loopback)
    $null = $sanBuilder.AddDnsName('localhost')

    if (![string]::IsNullOrWhiteSpace($PodeContext.Server.ComputerName)) {
        $null = $sanBuilder.AddDnsName($PodeContext.Server.ComputerName)
    }

    $rsa = [RSA]::Create(2048)
    $distinguishedName = [X500DistinguishedName]::new('CN=localhost')

    $req = [X509Certificates.CertificateRequest]::new(
        $distinguishedName,
        $rsa,
        [HashAlgorithmName]::SHA256,
        [RSASignaturePadding]::Pkcs1
    )

    $flags = (
        [X509Certificates.X509KeyUsageFlags]::DataEncipherment -bor
        [X509Certificates.X509KeyUsageFlags]::KeyEncipherment -bor
        [X509Certificates.X509KeyUsageFlags]::DigitalSignature
    )

    $null = $req.CertificateExtensions.Add(
        [X509Certificates.X509KeyUsageExtension]::new(
            $flags,
            $false
        )
    )

    $oid = [OidCollection]::new()
    $null = $oid.Add([Oid]::new('1.3.6.1.5.5.7.3.1'))

    $req.CertificateExtensions.Add(
        [X509Certificates.X509EnhancedKeyUsageExtension]::new(
            $oid,
            $false
        )
    )

    $null = $req.CertificateExtensions.Add($sanBuilder.Build())

    $cert = $req.CreateSelfSigned(
        [System.DateTimeOffset]::UtcNow.AddDays(-1),
        [System.DateTimeOffset]::UtcNow.AddYears(10)
    )

    if (Test-PodeIsWindows) {
        $cert.FriendlyName = 'localhost'
    }

    $cert = [X509Certificates.X509Certificate2]::new(
        $cert.Export([X509Certificates.X509ContentType]::Pfx, 'self-signed'),
        'self-signed'
    )

    return $cert
}

function Protect-PodeContentSecurityKeyword {
    param(
        [Parameter(Mandatory = $true)]
        [string]
        $Name,

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

        [switch]
        $Append
    )

    # cache it
    if ($Append -and !(Test-PodeIsEmpty $PodeContext.Server.Security.Cache.ContentSecurity[$Name])) {
        $Value += @($PodeContext.Server.Security.Cache.ContentSecurity[$Name])
    }

    $PodeContext.Server.Security.Cache.ContentSecurity[$Name] = $Value

    # do nothing if no value
    if (($null -eq $Value) -or ($Value.Length -eq 0)) {
        return $null
    }

    # keywords
    $Name = $Name.ToLowerInvariant()

    $keywords = @(
        'none',
        'self',
        'unsafe-inline',
        'unsafe-eval'
    )

    $schemes = @(
        'http',
        'https',
        'ws',
        'wss',
        'data',
        'file'
    )

    # build the value
    $values = @(foreach ($v in $Value) {
            if ($keywords -icontains $v) {
                "'$($v.ToLowerInvariant())'"
                continue
            }

            if ($schemes -icontains $v) {
                "$($v.ToLowerInvariant()):"
                continue
            }

            $v
        })

    return "$($Name) $($values -join ' ')"
}

function Protect-PodePermissionsPolicyKeyword {
    param(
        [Parameter(Mandatory = $true)]
        [string]
        $Name,

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

        [switch]
        $Append
    )

    # cache it
    if ($Append -and !(Test-PodeIsEmpty $PodeContext.Server.Security.Cache.PermissionsPolicy[$Name])) {
        if (($Value.Length -eq 0) -or (@($PodeContext.Server.Security.Cache.PermissionsPolicy[$Name])[0] -ine 'none')) {
            $Value += @($PodeContext.Server.Security.Cache.PermissionsPolicy[$Name])
        }
    }

    $PodeContext.Server.Security.Cache.PermissionsPolicy[$Name] = $Value

    # do nothing if no value
    if (($null -eq $Value) -or ($Value.Length -eq 0)) {
        return $null
    }

    # build value
    $Name = $Name.ToLowerInvariant()

    if ($Value -icontains 'none') {
        return "$($Name)=()"
    }

    $keywords = @(
        'self'
    )

    $values = @(foreach ($v in $Value) {
            if ($keywords -icontains $v) {
                $v
                continue
            }

            "`"$($v)`""
        })

    return "$($Name)=($($values -join ' '))"
}

<#
.SYNOPSIS
Sets the Content Security Policy (CSP) header for a Pode web server.

.DESCRIPTION
The `Set-PodeSecurityContentSecurityPolicyInternal` function constructs and sets the Content Security Policy (CSP) header based on the provided parameters. The function supports an optional switch to append the header value and explicitly disables XSS auditors in modern browsers to prevent vulnerabilities.

.PARAMETER Params
A hashtable containing the various CSP directives to be set.

.PARAMETER Append
A switch indicating whether to append the header value.

.EXAMPLE
$policyParams = @{
    Default = "'self'"
    ScriptSrc = "'self' 'unsafe-inline'"
    StyleSrc = "'self' 'unsafe-inline'"
}
Set-PodeSecurityContentSecurityPolicyInternal -Params $policyParams

.EXAMPLE
$policyParams = @{
    Default = "'self'"
    ImgSrc = "'self' data:"
    ConnectSrc = "'self' https://api.example.com"
    UpgradeInsecureRequests = $true
}
Set-PodeSecurityContentSecurityPolicyInternal -Params $policyParams -Append

.NOTES
This is an internal function and may change in future releases of Pode.
#>
function Set-PodeSecurityContentSecurityPolicyInternal {
    [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSPossibleIncorrectComparisonWithNull', '')]
    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $true)]
        [hashtable]
        $Params,

        [Parameter()]
        [switch]
        $Append
    )

    # build the header's value
    $values = @(
        Protect-PodeContentSecurityKeyword -Name 'default-src' -Value $Params.Default -Append:$Append
        Protect-PodeContentSecurityKeyword -Name 'child-src' -Value $Params.Child -Append:$Append
        Protect-PodeContentSecurityKeyword -Name 'connect-src' -Value $Params.Connect -Append:$Append
        Protect-PodeContentSecurityKeyword -Name 'font-src' -Value $Params.Font -Append:$Append
        Protect-PodeContentSecurityKeyword -Name 'frame-src' -Value $Params.Frame -Append:$Append
        Protect-PodeContentSecurityKeyword -Name 'img-src' -Value $Params.Image -Append:$Append
        Protect-PodeContentSecurityKeyword -Name 'manifest-src' -Value $Params.Manifest -Append:$Append
        Protect-PodeContentSecurityKeyword -Name 'media-src' -Value $Params.Media -Append:$Append
        Protect-PodeContentSecurityKeyword -Name 'object-src' -Value $Params.Object -Append:$Append
        Protect-PodeContentSecurityKeyword -Name 'script-src' -Value $Params.Scripts -Append:$Append
        Protect-PodeContentSecurityKeyword -Name 'style-src' -Value $Params.Style -Append:$Append
        Protect-PodeContentSecurityKeyword -Name 'base-uri' -Value $Params.BaseUri -Append:$Append
        Protect-PodeContentSecurityKeyword -Name 'form-action' -Value $Params.FormAction -Append:$Append
        Protect-PodeContentSecurityKeyword -Name 'frame-ancestors' -Value $Params.FrameAncestor -Append:$Append
    )

    if (![string]::IsNullOrWhiteSpace($Params.Sandbox) -and ($Params.Sandbox -ine 'None')) {
        $values += "sandbox $($Params.Sandbox.ToLowerInvariant())".Trim()
    }

    if ($Params.UpgradeInsecureRequests) {
        $values += 'upgrade-insecure-requests'
    }

    # Filter out $null values from the $values array using the array filter `-ne $null`. This approach
    # is equivalent to using `$values | Where-Object { $_ -ne $null }` but is more efficient. The `-ne $null`
    # operator is faster because it is a direct array operation that internally skips the overhead of
    # piping through a cmdlet and processing each item individually.
    $values = ($values -ne $null)
    $value = ($values -join '; ')

    # Add the Content Security Policy header to the response or relevant context. This cmdlet
    # sets the HTTP header with the name 'Content-Security-Policy' and the constructed value.
    Add-PodeSecurityHeader -Name 'Content-Security-Policy' -Value $value

    # this is done to explicitly disable XSS auditors in modern browsers
    # as having it enabled has now been found to cause more vulnerabilities
    if ($Params.XssBlock) {
        Add-PodeSecurityHeader -Name 'X-XSS-Protection' -Value '1; mode=block'
    }
    else {
        Add-PodeSecurityHeader -Name 'X-XSS-Protection' -Value '0'
    }
}

<#
.SYNOPSIS
Sets the Permissions Policy header for a Pode web server.

.DESCRIPTION
The `Set-PodeSecurityPermissionsPolicy` function constructs and sets the Permissions Policy header based on the provided parameters. The function supports an optional switch to append the header value.

.PARAMETER Params
A hashtable containing the various permissions policies to be set.

.PARAMETER Append
A switch indicating whether to append the header value.

.EXAMPLE
$policyParams = @{
    Accelerometer = 'none'
    Camera = 'self'
    Microphone = '*'
}
Set-PodeSecurityPermissionsPolicy -Params $policyParams

.EXAMPLE
$policyParams = @{
    Autoplay = 'self'
    Geolocation = 'none'
}
Set-PodeSecurityPermissionsPolicy -Params $policyParams -Append

.NOTES
This is an internal function and may change in future releases of Pode.
#>
function Set-PodeSecurityPermissionsPolicyInternal {
    [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSPossibleIncorrectComparisonWithNull', '')]
    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $true)]
        [hashtable]
        $Params,

        [Parameter()]
        [switch]
        $Append
    )

    # build the header's value
    $values = @(
        Protect-PodePermissionsPolicyKeyword -Name 'accelerometer' -Value $Params.Accelerometer -Append:$Append
        Protect-PodePermissionsPolicyKeyword -Name 'ambient-light-sensor' -Value $Params.AmbientLightSensor -Append:$Append
        Protect-PodePermissionsPolicyKeyword -Name 'autoplay' -Value $Params.Autoplay -Append:$Append
        Protect-PodePermissionsPolicyKeyword -Name 'battery' -Value $Params.Battery -Append:$Append
        Protect-PodePermissionsPolicyKeyword -Name 'camera' -Value $Params.Camera -Append:$Append
        Protect-PodePermissionsPolicyKeyword -Name 'display-capture' -Value $Params.DisplayCapture -Append:$Append
        Protect-PodePermissionsPolicyKeyword -Name 'document-domain' -Value $Params.DocumentDomain -Append:$Append
        Protect-PodePermissionsPolicyKeyword -Name 'encrypted-media' -Value $Params.EncryptedMedia -Append:$Append
        Protect-PodePermissionsPolicyKeyword -Name 'fullscreen' -Value $Params.Fullscreen -Append:$Append
        Protect-PodePermissionsPolicyKeyword -Name 'gamepad' -Value $Params.Gamepad -Append:$Append
        Protect-PodePermissionsPolicyKeyword -Name 'geolocation' -Value $Params.Geolocation -Append:$Append
        Protect-PodePermissionsPolicyKeyword -Name 'gyroscope' -Value $Params.Gyroscope -Append:$Append
        Protect-PodePermissionsPolicyKeyword -Name 'interest-cohort' -Value $Params.InterestCohort -Append:$Append
        Protect-PodePermissionsPolicyKeyword -Name 'layout-animations' -Value $Params.LayoutAnimations -Append:$Append
        Protect-PodePermissionsPolicyKeyword -Name 'legacy-image-formats' -Value $Params.LegacyImageFormats -Append:$Append
        Protect-PodePermissionsPolicyKeyword -Name 'magnetometer' -Value $Params.Magnetometer -Append:$Append
        Protect-PodePermissionsPolicyKeyword -Name 'microphone' -Value $Params.Microphone  -Append:$Append
        Protect-PodePermissionsPolicyKeyword -Name 'midi' -Value $Params.Midi  -Append:$Append
        Protect-PodePermissionsPolicyKeyword -Name 'oversized-images' -Value $Params.OversizedImages  -Append:$Append
        Protect-PodePermissionsPolicyKeyword -Name 'payment' -Value $Params.Payment -Append:$Append
        Protect-PodePermissionsPolicyKeyword -Name 'picture-in-picture' -Value $Params.PictureInPicture  -Append:$Append
        Protect-PodePermissionsPolicyKeyword -Name 'publickey-credentials-get' -Value $Params.PublicKeyCredentials  -Append:$Append
        Protect-PodePermissionsPolicyKeyword -Name 'speaker-selection' -Value $Params.Speakers  -Append:$Append
        Protect-PodePermissionsPolicyKeyword -Name 'sync-xhr' -Value $Params.SyncXhr -Append:$Append
        Protect-PodePermissionsPolicyKeyword -Name 'unoptimized-images' -Value $Params.UnoptimisedImages -Append:$Append
        Protect-PodePermissionsPolicyKeyword -Name 'unsized-media' -Value $Params.UnsizedMedia -Append:$Append
        Protect-PodePermissionsPolicyKeyword -Name 'usb' -Value $Params.Usb -Append:$Append
        Protect-PodePermissionsPolicyKeyword -Name 'screen-wake-lock' -Value $Params.ScreenWakeLake -Append:$Append
        Protect-PodePermissionsPolicyKeyword -Name 'web-share' -Value $Params.WebShare -Append:$Append
        Protect-PodePermissionsPolicyKeyword -Name 'xr-spatial-tracking' -Value $Params.XrSpatialTracking -Append:$Append
    )

    # Filter out $null values from the $values array using the array filter `-ne $null`. This approach
    # is equivalent to using `$values | Where-Object { $_ -ne $null }` but is more efficient. The `-ne $null`
    # operator is faster because it is a direct array operation that internally skips the overhead of
    # piping through a cmdlet and processing each item individually.
    $values = ($values -ne $null)
    $value = ($values -join ', ')

    # Add the constructed Permissions Policy header to the response or relevant context. This cmdlet
    # sets the HTTP header with the name 'Permissions-Policy' and the constructed value.
    Add-PodeSecurityHeader -Name 'Permissions-Policy' -Value $value
}
src\Private\Server.ps1
function Start-PodeInternalServer {
    param(
        [Parameter()]
        $Request,

        [switch]
        $Browse
    )

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

        # setup inbuilt scoped vars
        Add-PodeScopedVariablesInbuilt

        # create the shared runspace state
        New-PodeRunspaceState

        # if iis, setup global middleware to validate token
        Initialize-PodeIISMiddleware

        # load any secret vaults
        Import-PodeSecretVaultsIntoRegistry

        # get the server's script and invoke it - to set up routes, timers, middleware, etc
        $_script = $PodeContext.Server.Logic
        if (Test-PodePath -Path $PodeContext.Server.LogicPath -NoStatus) {
            $_script = Convert-PodeFileToScriptBlock -FilePath $PodeContext.Server.LogicPath
        }

        $_script = Convert-PodeScopedVariables -ScriptBlock $_script -Exclude Session, Using
        $null = Invoke-PodeScriptBlock -ScriptBlock $_script -NoNewClosure -Splat

        #Validate OpenAPI definitions
        Test-PodeOADefinitionInternal

        # load any modules/snapins
        Import-PodeSnapinsIntoRunspaceState
        Import-PodeModulesIntoRunspaceState

        # load any functions
        Import-PodeFunctionsIntoRunspaceState -ScriptBlock $_script

        # run start event hooks
        Invoke-PodeEvent -Type Start

        # start timer for task housekeeping
        Start-PodeTaskHousekeeper

        # start the cache housekeeper
        Start-PodeCacheHousekeeper

        # create timer/schedules for auto-restarting
        New-PodeAutoRestartServer

        # start the runspace pools for web, schedules, etc
        New-PodeRunspacePool
        Open-PodeRunspacePool

        if (!$PodeContext.Server.IsServerless) {
            # start runspace for loggers
            Start-PodeLoggingRunspace

            # start runspace for schedules
            Start-PodeScheduleRunspace

            # start runspace for timers
            Start-PodeTimerRunspace

            # start runspace for gui
            Start-PodeGuiRunspace

            # start runspace for websockets
            Start-PodeWebSocketRunspace

            # start runspace for file watchers
            Start-PodeFileWatcherRunspace
        }

        # start the appropriate server
        $endpoints = @()

        # - service
        if ($PodeContext.Server.IsService) {
            Start-PodeServiceServer
        }

        # - serverless
        elseif ($PodeContext.Server.IsServerless) {
            switch ($PodeContext.Server.ServerlessType.ToUpperInvariant()) {
                'AZUREFUNCTIONS' {
                    Start-PodeAzFuncServer -Data $Request
                }

                'AWSLAMBDA' {
                    Start-PodeAwsLambdaServer -Data $Request
                }
            }
        }

        # - normal
        else {
            # start each server type
            foreach ($_type in $PodeContext.Server.Types) {
                switch ($_type.ToUpperInvariant()) {
                    'SMTP' {
                        $endpoints += (Start-PodeSmtpServer)
                    }

                    'TCP' {
                        $endpoints += (Start-PodeTcpServer)
                    }

                    'HTTP' {
                        $endpoints += (Start-PodeWebServer -Browse:$Browse)
                    }
                }
            }

            # now go back through, and wait for each server type's runspace pool to be ready
            foreach ($pool in ($endpoints.Pool | Sort-Object -Unique)) {
                $start = [datetime]::Now
                Write-Verbose "Waiting for the $($pool) RunspacePool to be Ready"

                # wait
                while ($PodeContext.RunspacePools[$pool].State -ieq 'Waiting') {
                    Start-Sleep -Milliseconds 100
                }

                Write-Verbose "$($pool) RunspacePool $($PodeContext.RunspacePools[$pool].State) [duration: $(([datetime]::Now - $start).TotalSeconds)s]"

                # errored?
                if ($PodeContext.RunspacePools[$pool].State -ieq 'error') {
                    throw ($PodeLocale.runspacePoolFailedToLoadExceptionMessage -f $pool) #"$($pool) RunspacePool failed to load"
                }
            }
        }

        # set the start time of the server (start and after restart)
        $PodeContext.Metrics.Server.StartTime = [datetime]::UtcNow

        # run running event hooks
        Invoke-PodeEvent -Type Running

        # state what endpoints are being listened on
        if ($endpoints.Length -gt 0) {

            # Listening on the following $endpoints.Length endpoint(s) [$PodeContext.Threads.General thread(s)]
            Write-PodeHost ($PodeLocale.listeningOnEndpointsMessage -f $endpoints.Length, $PodeContext.Threads.General) -ForegroundColor Yellow
            $endpoints | ForEach-Object {
                $flags = @()
                if ($_.DualMode) {
                    $flags += 'DualMode'
                }

                if ($flags.Length -eq 0) {
                    $flags = [string]::Empty
                }
                else {
                    $flags = "[$($flags -join ',')]"
                }

                Write-PodeHost "`t- $($_.Url) $($flags)" -ForegroundColor Yellow
            }
            # state the OpenAPI endpoints for each definition
            foreach ($key in  $PodeContext.Server.OpenAPI.Definitions.keys) {
                $bookmarks = $PodeContext.Server.OpenAPI.Definitions[$key].hiddenComponents.bookmarks
                if ( $bookmarks) {
                    Write-PodeHost
                    if (!$OpenAPIHeader) {
                        # OpenAPI Info
                        Write-PodeHost $PodeLocale.openApiInfoMessage -ForegroundColor Yellow
                        $OpenAPIHeader = $true
                    }
                    Write-PodeHost " '$key':" -ForegroundColor Yellow

                    if ($bookmarks.route.count -gt 1 -or $bookmarks.route.Endpoint.Name) {
                        # Specification
                        Write-PodeHost "   - $($PodeLocale.specificationMessage):" -ForegroundColor Yellow
                        foreach ($endpoint in   $bookmarks.route.Endpoint) {
                            Write-PodeHost "     . $($endpoint.Protocol)://$($endpoint.Address)$($bookmarks.openApiUrl)" -ForegroundColor Yellow
                        }
                        # Documentation
                        Write-PodeHost "   - $($PodeLocale.documentationMessage):" -ForegroundColor Yellow
                        foreach ($endpoint in   $bookmarks.route.Endpoint) {
                            Write-PodeHost "     . $($endpoint.Protocol)://$($endpoint.Address)$($bookmarks.path)" -ForegroundColor Yellow
                        }
                    }
                    else {
                        # Specification
                        Write-PodeHost "   - $($PodeLocale.specificationMessage):" -ForegroundColor Yellow
                        $endpoints | ForEach-Object {
                            $url = [System.Uri]::new( [System.Uri]::new($_.Url), $bookmarks.openApiUrl)
                            Write-PodeHost "     . $url" -ForegroundColor Yellow
                        }
                        Write-PodeHost "   - $($PodeLocale.documentationMessage):" -ForegroundColor Yellow
                        $endpoints | ForEach-Object {
                            $url = [System.Uri]::new( [System.Uri]::new($_.Url), $bookmarks.path)
                            Write-PodeHost "     . $url" -ForegroundColor Yellow
                        }
                    }
                }
            }

        }
    }
    catch {
        throw $_.Exception
    }
}

function Restart-PodeInternalServer {
    try {
        # inform restart
        # Restarting server...
        Write-PodeHost $PodeLocale.restartingServerMessage -NoNewline -ForegroundColor Cyan

        # run restart event hooks
        Invoke-PodeEvent -Type Restart

        # cancel the session token
        $PodeContext.Tokens.Cancellation.Cancel()

        # close all current runspaces
        Close-PodeRunspace -ClosePool

        # remove all of the pode temp drives
        Remove-PodePSDrive

        # clear-up modules
        $PodeContext.Server.Modules.Clear()

        # clear up timers, schedules and loggers
        Clear-PodeHashtableInnerKey -InputObject $PodeContext.Server.Routes
        Clear-PodeHashtableInnerKey -InputObject $PodeContext.Server.Handlers
        Clear-PodeHashtableInnerKey -InputObject $PodeContext.Server.Events

        if ($null -ne $PodeContext.Server.Verbs) {
            $PodeContext.Server.Verbs.Clear()
        }

        $PodeContext.Server.Views.Clear()
        $PodeContext.Timers.Items.Clear()
        $PodeContext.Server.Logging.Types.Clear()

        # clear schedules
        $PodeContext.Schedules.Items.Clear()
        $PodeContext.Schedules.Processes.Clear()

        # clear tasks
        $PodeContext.Tasks.Items.Clear()
        $PodeContext.Tasks.Processes.Clear()

        # clear file watchers
        $PodeContext.Fim.Items.Clear()

        # auto-importers
        Reset-PodeAutoImportConfiguration

        # clear middle/endware
        $PodeContext.Server.Middleware = @()
        $PodeContext.Server.Endware = @()

        # clear body parsers
        $PodeContext.Server.BodyParsers.Clear()

        # clear security headers
        $PodeContext.Server.Security.Headers.Clear()
        Clear-PodeHashtableInnerKey -InputObject $PodeContext.Server.Security.Cache

        # clear endpoints
        $PodeContext.Server.Endpoints.Clear()
        $PodeContext.Server.EndpointsMap.Clear()

        # clear openapi
        $PodeContext.Server.OpenAPI = Initialize-PodeOpenApiTable -DefaultDefinitionTag $PodeContext.Server.Configuration.Web.OpenApi.DefaultDefinitionTag
        # clear the sockets
        $PodeContext.Server.Signals.Enabled = $false
        $PodeContext.Server.Signals.Listener = $null
        $PodeContext.Server.Http.Listener = $null
        $PodeContext.Listeners = @()
        $PodeContext.Receivers = @()
        $PodeContext.Watchers = @()

        # set view engine back to default
        $PodeContext.Server.ViewEngine = @{
            Type           = 'html'
            Extension      = 'html'
            ScriptBlock    = $null
            UsingVariables = $null
            IsDynamic      = $false
        }

        # clear up cookie sessions
        $PodeContext.Server.Sessions.Clear()

        # clear up authentication methods
        $PodeContext.Server.Authentications.Methods.Clear()
        $PodeContext.Server.Authorisations.Methods.Clear()

        # clear up shared state
        $PodeContext.Server.State.Clear()

        # clear scoped variables
        $PodeContext.Server.ScopedVariables.Clear()

        # clear cache
        $PodeContext.Server.Cache.Items.Clear()
        $PodeContext.Server.Cache.Storage.Clear()

        # clear up secret vaults/cache
        Unregister-PodeSecretVaultsInternal -ThrowError
        $PodeContext.Server.Secrets.Vaults.Clear()
        $PodeContext.Server.Secrets.Keys.Clear()

        # dispose mutex/semaphores
        Clear-PodeLockables
        Clear-PodeMutexes
        Clear-PodeSemaphores

        # clear up output
        $PodeContext.Server.Output.Variables.Clear()

        # reset type if smtp/tcp
        $PodeContext.Server.Types = @()

        # recreate the session tokens
        Close-PodeDisposable -Disposable $PodeContext.Tokens.Cancellation
        $PodeContext.Tokens.Cancellation = [System.Threading.CancellationTokenSource]::new()

        Close-PodeDisposable -Disposable $PodeContext.Tokens.Restart
        $PodeContext.Tokens.Restart = [System.Threading.CancellationTokenSource]::new()

        # reload the configuration
        $PodeContext.Server.Configuration = Open-PodeConfiguration -Context $PodeContext

        # done message
        Write-PodeHost $PodeLocale.doneMessage -ForegroundColor Green

        # restart the server
        $PodeContext.Metrics.Server.RestartCount++
        Start-PodeInternalServer
    }
    catch {
        $_ | Write-PodeErrorLog
        throw $_.Exception
    }
}

function Test-PodeServerKeepOpen {
    # if we have any timers/schedules/fim - keep open
    if ((Test-PodeTimersExist) -or (Test-PodeSchedulesExist) -or (Test-PodeFileWatchersExist)) {
        return $true
    }

    # if not a service, and not any type/serverless - close server
    if (!$PodeContext.Server.IsService -and (($PodeContext.Server.Types.Length -eq 0) -or $PodeContext.Server.IsServerless)) {
        return $false
    }

    # keep server open
    return $true
}
src\Private\Serverless.ps1
function Start-PodeAzFuncServer {
    [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidGlobalVars', '')]
    param(
        [Parameter(Mandatory = $true)]
        $Data
    )

    # setup any inbuilt middleware that works for azure functions
    $inbuilt_middleware = @(
        (Get-PodeSecurityMiddleware),
        (Get-PodePublicMiddleware),
        (Get-PodeRouteValidateMiddleware),
        (Get-PodeBodyMiddleware),
        (Get-PodeCookieMiddleware)
    )

    $PodeContext.Server.Middleware = ($inbuilt_middleware + $PodeContext.Server.Middleware)

    try {
        try {
            # get the request
            $request = $Data.Request

            # setup the response
            $response = New-PodeAzFuncResponse
            $response.StatusCode = 200
            $response.Headers = @{}

            # reset event data
            $global:WebEvent = @{
                OnEnd            = @()
                Auth             = @{}
                Response         = $response
                Request          = $request
                Lockable         = $PodeContext.Threading.Lockables.Global
                Path             = [string]::Empty
                Method           = $request.Method.ToLowerInvariant()
                Query            = $request.Query
                Endpoint         = @{
                    Protocol = ($request.Url -split '://')[0]
                    Address  = $null
                    Name     = $null
                }
                ContentType      = $null
                ErrorType        = $null
                Cookies          = @{}
                PendingCookies   = @{}
                Parameters       = $null
                Data             = $null
                Files            = $null
                Streamed         = $false
                Route            = $null
                StaticContent    = $null
                Timestamp        = [datetime]::UtcNow
                TransferEncoding = $null
                AcceptEncoding   = $null
                Ranges           = $null
                Metadata         = @{}
            }

            $WebEvent.Endpoint.Address = ((Get-PodeHeader -Name 'host') -split ':')[0]
            $WebEvent.ContentType = (Get-PodeHeader -Name 'content-type')

            # set the path, using static content query parameter if passed
            if (![string]::IsNullOrWhiteSpace($request.Query['static-file'])) {
                $WebEvent.Path = $request.Query['static-file']
            }
            else {
                $funcName = $Data.sys.MethodName
                if ([string]::IsNullOrWhiteSpace($funcName)) {
                    $funcName = $Data.FunctionName
                }

                $WebEvent.Path = "/api/$($funcName)"
            }

            $WebEvent.Path = [System.Web.HttpUtility]::UrlDecode($WebEvent.Path)

            # set pode in server response header
            Set-PodeServerHeader -Type 'Kestrel'

            # invoke global and route middleware
            if ((Invoke-PodeMiddleware -Middleware $PodeContext.Server.Middleware -Route $WebEvent.Path)) {
                if ((Invoke-PodeMiddleware -Middleware $WebEvent.Route.Middleware)) {
                    # invoke the route
                    if ($null -ne $WebEvent.StaticContent) {
                        $fileBrowser = $WebEvent.Route.FileBrowser
                        if ($WebEvent.StaticContent.IsDownload) {
                            Write-PodeAttachmentResponseInternal -Path $WebEvent.StaticContent.Source -FileBrowser:$fileBrowser
                        }
                        elseif ($WebEvent.StaticContent.RedirectToDefault) {
                            $file = [System.IO.Path]::GetFileName($WebEvent.StaticContent.Source)
                            Move-PodeResponseUrl -Url "$($WebEvent.Path)/$($file)"
                        }
                        else {
                            $cachable = $WebEvent.StaticContent.IsCachable
                            Write-PodeFileResponseInternal -Path $WebEvent.StaticContent.Source -MaxAge $PodeContext.Server.Web.Static.Cache.MaxAge -Cache:$cachable -FileBrowser:$fileBrowser
                        }
                    }
                    else {
                        $null = Invoke-PodeScriptBlock -ScriptBlock $WebEvent.Route.Logic -Arguments $WebEvent.Route.Arguments -UsingVariables $WebEvent.Route.UsingVariables -Scoped -Splat
                    }
                }
            }
        }
        catch {
            $_ | Write-PodeErrorLog
            $_.Exception | Write-PodeErrorLog -CheckInnerException
            Set-PodeResponseStatus -Code 500 -Exception $_
        }
        finally {
            Update-PodeServerRequestMetric -WebEvent $WebEvent
        }

        # invoke endware specifc to the current web event
        $_endware = ($WebEvent.OnEnd + @($PodeContext.Server.Endware))
        Invoke-PodeEndware -Endware $_endware

        # close and send the response
        Push-OutputBinding -Name Response -Value $response
    }
    catch {
        $_ | Write-PodeErrorLog
        throw $_.Exception
    }
}

function New-PodeAzFuncResponse {
    return [HttpResponseContext]::new()
}

function Start-PodeAwsLambdaServer {
    [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidGlobalVars', '')]
    param(
        [Parameter(Mandatory = $true)]
        $Data
    )

    # setup any inbuilt middleware that works for aws lambda
    $inbuilt_middleware = @(
        (Get-PodeSecurityMiddleware),
        (Get-PodePublicMiddleware),
        (Get-PodeRouteValidateMiddleware),
        (Get-PodeBodyMiddleware),
        (Get-PodeCookieMiddleware)
    )

    $PodeContext.Server.Middleware = ($inbuilt_middleware + $PodeContext.Server.Middleware)

    try {
        try {
            # get the request
            $request = $Data

            # setup the response
            $response = @{
                StatusCode = 200
                Headers    = @{}
                Body       = [string]::Empty
            }

            # reset event data
            $global:WebEvent = @{
                OnEnd            = @()
                Auth             = @{}
                Response         = $response
                Request          = $request
                Lockable         = $PodeContext.Threading.Lockables.Global
                Path             = [System.Web.HttpUtility]::UrlDecode($request.path)
                Method           = $request.httpMethod.ToLowerInvariant()
                Query            = $request.queryStringParameters
                Endpoint         = @{
                    Protocol = $null
                    Address  = $null
                    Name     = $null
                }
                ContentType      = $null
                ErrorType        = $null
                Cookies          = @{}
                PendingCookies   = @{}
                Parameters       = $null
                Data             = $null
                Files            = $null
                Streamed         = $false
                Route            = $null
                StaticContent    = $null
                Timestamp        = [datetime]::UtcNow
                TransferEncoding = $null
                AcceptEncoding   = $null
                Ranges           = $null
                Metadata         = @{}
            }

            $WebEvent.Endpoint.Protocol = (Get-PodeHeader -Name 'X-Forwarded-Proto')
            $WebEvent.Endpoint.Address = ((Get-PodeHeader -Name 'Host') -split ':')[0]
            $WebEvent.ContentType = (Get-PodeHeader -Name 'Content-Type')

            # set pode in server response header
            Set-PodeServerHeader -Type 'Lambda'

            # invoke global and route middleware
            if ((Invoke-PodeMiddleware -Middleware $PodeContext.Server.Middleware -Route $WebEvent.Path)) {
                if ((Invoke-PodeMiddleware -Middleware $WebEvent.Route.Middleware)) {
                    # invoke the route
                    if ($null -ne $WebEvent.StaticContent) {
                        $fileBrowser = $WebEvent.Route.FileBrowser
                        if ($WebEvent.StaticContent.IsDownload) {
                            Write-PodeAttachmentResponseInternal -Path $WebEvent.StaticContent.Source -FileBrowser:$fileBrowser
                        }
                        elseif ($WebEvent.StaticContent.RedirectToDefault) {
                            $file = [System.IO.Path]::GetFileName($WebEvent.StaticContent.Source)
                            Move-PodeResponseUrl -Url "$($WebEvent.Path)/$($file)"
                        }
                        else {
                            $cachable = $WebEvent.StaticContent.IsCachable
                            Write-PodeFileResponseInternal -Path $WebEvent.StaticContent.Source -MaxAge $PodeContext.Server.Web.Static.Cache.MaxAge `
                                -Cache:$cachable -FileBrowser:$fileBrowser
                        }
                    }
                    else {
                        $null = Invoke-PodeScriptBlock -ScriptBlock $WebEvent.Route.Logic -Arguments $WebEvent.Route.Arguments -UsingVariables $WebEvent.Route.UsingVariables -Scoped -Splat
                    }
                }
            }
        }
        catch {
            $_ | Write-PodeErrorLog
            $_.Exception | Write-PodeErrorLog -CheckInnerException
            Set-PodeResponseStatus -Code 500 -Exception $_
        }
        finally {
            Update-PodeServerRequestMetric -WebEvent $WebEvent
        }

        # invoke endware specifc to the current web event
        $_endware = ($WebEvent.OnEnd + @($PodeContext.Server.Endware))
        Invoke-PodeEndware -Endware $_endware

        # close and send the response
        if (![string]::IsNullOrWhiteSpace($response.ContentType)) {
            Set-PodeHeader -Name 'Content-Type' -Value $response.ContentType
        }

        return (@{
                'statusCode' = $response.StatusCode
                'headers'    = $response.Headers
                'body'       = $response.Body
            } | ConvertTo-Json -Depth 10 -Compress)
    }
    catch {
        $_ | Write-PodeErrorLog
        throw $_.Exception
    }
}
src\Private\ServiceServer.ps1
function Start-PodeServiceServer {
    # ensure we have service handlers
    if (Test-PodeIsEmpty (Get-PodeHandler -Type Service)) {
        # No Service handlers have been defined
        throw ($PodeLocale.noServiceHandlersDefinedExceptionMessage)
    }

    # state we're running
    # Server looping every $PodeContext.Server.Interval secs
    Write-PodeHost ($PodeLocale.serverLoopingMessage -f $PodeContext.Server.Interval) -ForegroundColor Yellow

    # script for the looping server
    $serverScript = {

        try {
            while (!$PodeContext.Tokens.Cancellation.IsCancellationRequested) {
                # the event object
                $script:ServiceEvent = @{
                    Lockable = $PodeContext.Threading.Lockables.Global
                    Metadata = @{}
                }

                # invoke the service handlers
                $handlers = Get-PodeHandler -Type Service
                foreach ($name in $handlers.Keys) {
                    $handler = $handlers[$name]
                    $null = Invoke-PodeScriptBlock -ScriptBlock $handler.Logic -Arguments $handler.Arguments -UsingVariables $handler.UsingVariables -Scoped -Splat
                }

                # sleep before next run
                Start-Sleep -Seconds $PodeContext.Server.Interval
            }
        }
        catch [System.OperationCanceledException] {
            $_ | Write-PodeErrorLog -Level Debug
        }
        catch {
            $_ | Write-PodeErrorLog
            throw $_.Exception
        }
    }

    # start the runspace for the server
    Add-PodeRunspace -Type Main -Name 'ServiceServer' -ScriptBlock $serverScript
}
src\Private\Sessions.ps1
function New-PodeSession {
    # sessionId
    $sessionId = Invoke-PodeScriptBlock -ScriptBlock $PodeContext.Server.Sessions.GenerateId -Return

    # tabId
    $tabId = $null
    if (!$PodeContext.Server.Sessions.Info.Scope.IsBrowser) {
        $tabId = Get-PodeSessionTabId
    }

    # return new session data
    return @{
        Name      = $PodeContext.Server.Sessions.Name
        Id        = $sessionId
        TabId     = $tabId
        FullId    = (Get-PodeSessionFullId -SessionId $sessionId -TabId $tabId)
        Extend    = $PodeContext.Server.Sessions.Info.Extend
        TimeStamp = [datetime]::UtcNow
        Data      = @{}
    }
}

function Get-PodeSessionFullId {
    param(
        [Parameter()]
        [string]
        $SessionId,

        [Parameter()]
        [string]
        $TabId
    )

    if (!$PodeContext.Server.Sessions.Info.Scope.IsBrowser -and ![string]::IsNullOrEmpty($TabId)) {
        return "$($SessionId)-$($TabId)"
    }

    return $SessionId
}

function Set-PodeSession {
    if ($null -eq $WebEvent.Session) {
        # There is no session available to set on the response
        throw ($PodeLocale.noSessionToSetOnResponseExceptionMessage)
    }

    # convert secret to strict mode
    $strict = $PodeContext.Server.Sessions.Info.Strict
    $secret = $PodeContext.Server.Sessions.Secret

    # set session on header
    if ($PodeContext.Server.Sessions.Info.UseHeaders) {
        Set-PodeHeader -Name $WebEvent.Session.Name -Value $WebEvent.Session.Id -Secret $secret -Strict:$strict
    }

    # set session as cookie
    else {
        $null = Set-PodeCookie `
            -Name $WebEvent.Session.Name `
            -Value $WebEvent.Session.Id `
            -Secret $secret `
            -Strict:$strict `
            -ExpiryDate (Get-PodeSessionExpiry) `
            -HttpOnly:$PodeContext.Server.Sessions.Info.HttpOnly `
            -Secure:$PodeContext.Server.Sessions.Info.Secure
    }
}

function Get-PodeSession {
    $secret = $PodeContext.Server.Sessions.Secret
    $sessionId = $null
    $tabId = Get-PodeSessionTabId
    $name = $PodeContext.Server.Sessions.Name

    # convert secret to strict mode
    if ($PodeContext.Server.Sessions.Info.Strict) {
        $secret = ConvertTo-PodeStrictSecret -Secret $secret
    }

    # session from header
    if ($PodeContext.Server.Sessions.Info.UseHeaders) {
        # check that the header is validly signed
        if (!(Test-PodeHeaderSigned -Name $PodeContext.Server.Sessions.Name -Secret $secret)) {
            return $null
        }

        # get the header from the request
        $sessionId = Get-PodeHeader -Name $PodeContext.Server.Sessions.Name -Secret $secret
        if ([string]::IsNullOrEmpty($sessionId)) {
            return $null
        }
    }

    # session from cookie
    else {
        # check that the cookie is validly signed
        if (!(Test-PodeCookieSigned -Name $PodeContext.Server.Sessions.Name -Secret $secret)) {
            return $null
        }

        # get the cookie from the request
        $cookie = Get-PodeCookie -Name $PodeContext.Server.Sessions.Name -Secret $secret
        if ([string]::IsNullOrEmpty($cookie)) {
            return $null
        }

        # get details from cookie
        $name = $cookie.Name
        $sessionId = $cookie.Value
    }

    # generate the session data
    return @{
        Name      = $name
        Id        = $sessionId
        TabId     = $tabId
        FullId    = (Get-PodeSessionFullId -SessionId $sessionId -TabId $tabId)
        Extend    = $PodeContext.Server.Sessions.Info.Extend
        TimeStamp = $null
        Data      = @{}
    }
}

function Revoke-PodeSession {
    # do nothing if no current session
    if ($null -eq $WebEvent.Session) {
        return
    }

    # remove from cookie if being used
    if (!$PodeContext.Server.Sessions.Info.UseHeaders) {
        Remove-PodeCookie -Name $WebEvent.Session.Name
    }

    # remove session from store
    Remove-PodeSessionInternal
}

function Set-PodeSessionDataHash {
    if ($null -eq $WebEvent.Session) {
        # No session available to calculate data hash
        throw ($PodeLocale.noSessionToCalculateDataHashExceptionMessage)
    }

    if (($null -eq $WebEvent.Session.Data) -or ($WebEvent.Session.Data.Count -eq 0)) {
        $WebEvent.Session.Data = @{}
    }

    $WebEvent.Session.DataHash = (Invoke-PodeSHA256Hash -Value (ConvertTo-Json -InputObject $WebEvent.Session.Data.Clone() -Depth 10 -Compress))
}

function Test-PodeSessionDataHash {
    if ($null -eq $WebEvent.Session) {
        return $false
    }

    if ([string]::IsNullOrWhiteSpace($WebEvent.Session.DataHash)) {
        return $false
    }

    if (($null -eq $WebEvent.Session.Data) -or ($WebEvent.Session.Data.Count -eq 0)) {
        $WebEvent.Session.Data = @{}
    }

    $hash = (Invoke-PodeSHA256Hash -Value (ConvertTo-Json -InputObject $WebEvent.Session.Data -Depth 10 -Compress))
    return ($WebEvent.Session.DataHash -eq $hash)
}

function Save-PodeSessionInternal {
    param(
        [switch]
        $Force
    )

    # do nothing if session has no ID
    if ([string]::IsNullOrEmpty($WebEvent.Session.FullId)) {
        return
    }

    # only save if check and hashes different, but not if extending expiry or updated
    if (!$WebEvent.Session.Extend -and $Force -and (Test-PodeSessionDataHash)) {
        return
    }

    # generate the expiry
    $expiry = Get-PodeSessionExpiry

    # the data to save - which will be the data, and some extra metadata like timestamp
    $data = @{
        Version  = 3
        Metadata = @{
            TimeStamp = $WebEvent.Session.TimeStamp
        }
        Data     = $WebEvent.Session.Data
    }

    # save base session data to store
    if (!$PodeContext.Server.Sessions.Info.Scope.IsBrowser -and $WebEvent.Session.TabId) {
        $authData = @{
            Version  = 3
            Metadata = @{
                TimeStamp = $WebEvent.Session.TimeStamp
                Tabbed    = $true
            }
            Data     = @{
                Auth = $WebEvent.Session.Data.Auth
            }
        }

        $null = Invoke-PodeScriptBlock -ScriptBlock $PodeContext.Server.Sessions.Store.Set -Arguments @($WebEvent.Session.Id, $authData, $expiry) -Splat
        $data.Metadata['Parent'] = $WebEvent.Session.Id
    }

    # save session data to store
    $null = Invoke-PodeScriptBlock -ScriptBlock $PodeContext.Server.Sessions.Store.Set -Arguments @($WebEvent.Session.FullId, $data, $expiry) -Splat

    # update session's data hash
    Set-PodeSessionDataHash
}

function Remove-PodeSessionInternal {
    if ($null -eq $WebEvent.Session) {
        return
    }

    # remove data from store
    $null = Invoke-PodeScriptBlock -ScriptBlock $PodeContext.Server.Sessions.Store.Delete -Arguments $WebEvent.Session.Id

    # clear session
    $WebEvent.Session.Clear()
    $WebEvent.Session = $null
}

function Get-PodeSessionInMemStore {
    $store = [psobject]::new()

    # add in-mem storage
    $store | Add-Member -MemberType NoteProperty -Name Memory -Value @{}

    # delete a sessionId and data
    $store | Add-Member -MemberType NoteProperty -Name Delete -Value {
        param($sessionId)
        $null = $PodeContext.Server.Sessions.Store.Memory.Remove($sessionId)
        if (!$PodeContext.Server.Sessions.Info.Scope.IsBrowser) {
            Invoke-PodeSchedule -Name '__pode_session_inmem_cleanup__'
        }
    }

    # get a sessionId's data
    $store | Add-Member -MemberType NoteProperty -Name Get -Value {
        param($sessionId)

        $s = $PodeContext.Server.Sessions.Store.Memory[$sessionId]

        # if expire, remove
        if (($null -ne $s) -and ($s.Expiry -lt [DateTime]::UtcNow)) {
            $null = $PodeContext.Server.Sessions.Store.Memory.Remove($sessionId)
            return $null
        }

        return $s.Data
    }

    # update/insert a sessionId and data
    $store | Add-Member -MemberType NoteProperty -Name Set -Value {
        param($sessionId, $data, $expiry)

        $PodeContext.Server.Sessions.Store.Memory[$sessionId] = @{
            Data   = $data
            Expiry = $expiry
        }
    }

    return $store
}

function Set-PodeSessionInMemClearDown {
    # don't setup if serverless - as memory is short lived anyway
    if ($PodeContext.Server.IsServerless) {
        return
    }

    # cleardown expired inmem session every 10 minutes
    Add-PodeSchedule -Name '__pode_session_inmem_cleanup__' -Cron '0/10 * * * *' -ScriptBlock {
        # do nothing if no sessions
        $store = $PodeContext.Server.Sessions.Store
        if (($null -eq $store.Memory) -or ($store.Memory.Count -eq 0)) {
            return
        }

        # remove sessions that have expired, or where the parent is gone
        $now = [DateTime]::UtcNow
        foreach ($key in $store.Memory.Keys) {
            # expired
            if ($store.Memory[$key].Expiry -lt $now) {
                $null = $store.Memory.Remove($key)
                continue
            }

            # parent check - gone/expired
            $parentKey = $store.Memory[$key].Data.Metadata.Parent
            if ($parentKey -and (!$store.Memory.ContainsKey($parentKey) -or ($store.Memory[$parentKey].Expiry -lt $now))) {
                $null = $store.Memory.Remove($key)
            }
        }
    }
}

function Test-PodeSessionsInUse {
    return (($null -ne $WebEvent.Session) -and ($WebEvent.Session.Count -gt 0))
}

function Get-PodeSessionData {
    param(
        [Parameter()]
        [string]
        $SessionId,

        [Parameter()]
        [string]
        $TabId = $null
    )

    $data = $null

    # try and get Tab session
    if (!$PodeContext.Server.Sessions.Info.Scope.IsBrowser -and ![string]::IsNullOrEmpty($TabId)) {
        $data = Invoke-PodeScriptBlock -ScriptBlock $PodeContext.Server.Sessions.Store.Get -Arguments "$($SessionId)-$($TabId)" -Return

        # now get the parent - but fail if it doesn't exist
        if ($data.Metadata.Parent) {
            $parent = Invoke-PodeScriptBlock -ScriptBlock $PodeContext.Server.Sessions.Store.Get -Arguments $data.Metadata.Parent -Return
            if (!$parent) {
                return $null
            }

            if (!$data.Data.Auth) {
                $data.Data.Auth = $parent.Data.Auth
            }
        }
    }

    # try and get normal session
    if (($null -eq $data) -and ![string]::IsNullOrEmpty($SessionId)) {
        $data = Invoke-PodeScriptBlock -ScriptBlock $PodeContext.Server.Sessions.Store.Get -Arguments $SessionId -Return
    }

    return $data
}

function Get-PodeSessionMiddleware {
    return {
        # if session already set, return
        if ($WebEvent.Session) {
            return $true
        }

        try {
            # retrieve the current session from cookie/header
            $WebEvent.Session = Get-PodeSession

            # if no session found, create a new one on the current web event
            if (!$WebEvent.Session) {
                $WebEvent.Session = New-PodeSession
                $new = $true
            }

            # get the session's data from store
            elseif ($null -ne ($data = (Get-PodeSessionData -SessionId $WebEvent.Session.Id -TabId $WebEvent.Session.TabId))) {
                if ($data.Version -lt 3) {
                    $WebEvent.Session.Data = $data
                    $WebEvent.Session.TimeStamp = [datetime]::UtcNow
                }
                else {
                    $WebEvent.Session.Data = $data.Data
                    if ($data.Metadata.Tabbed) {
                        $WebEvent.Session.TimeStamp = [datetime]::UtcNow
                    }
                    else {
                        $WebEvent.Session.TimeStamp = $data.Metadata.TimeStamp
                    }
                }
            }

            # session not in store, create a new one
            else {
                $WebEvent.Session = New-PodeSession
                $new = $true
            }

            # set data hash
            Set-PodeSessionDataHash

            # add session to response if it's new or extendible
            if ($new -or $WebEvent.Session.Extend) {
                Set-PodeSession
            }

            # assign endware for session to set cookie/header
            $WebEvent.OnEnd += @{
                Logic = {
                    if ($null -ne $WebEvent.Session) {
                        Save-PodeSession -Force
                    }
                }
            }
        }
        catch {
            $_ | Write-PodeErrorLog
            return $false
        }

        # move along
        return $true
    }
}
src\Private\Setup.ps1
function Invoke-PodePackageScript {
    [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidUsingInvokeExpression', '')]
    param(
        [Parameter()]
        [string]
        $ActionScript
    )

    if ([string]::IsNullOrWhiteSpace($ActionScript)) {
        return
    }

    Invoke-Expression -Command $ActionScript
}

<#
.SYNOPSIS
    Installs a local Pode module.

.DESCRIPTION
    This function installs a local Pode module by downloading it from the specified repository. It checks the module version and retrieves the latest version if 'latest' is specified. The module is saved to the specified path.

.PARAMETER Module
    The Pode module to install. It should include the module name, version, and repository information.

.NOTES
    This is an internal function and may change in future releases of Pode.
#>
function Install-PodeLocalModule {
    [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidUsingWriteHost', '')]
    param(
        [Parameter()]
        $Module = $null
    )

    if ($null -eq $Module) {
        return
    }

    $psModules = './ps_modules'

    # download modules to ps_modules
    $Module.psobject.properties.name | ForEach-Object {
        $_name = $_

        # get the module version
        $_version = $Module.$_name.version
        if ([string]::IsNullOrWhiteSpace($_version)) {
            $_version = $Module.$_name
        }

        # get the module repository
        $_repository = Protect-PodeValue -Value $Module.$_name.repository -Default 'PSGallery'

        try {
            # if version is latest, retrieve current
            if ($_version -ieq 'latest') {
                $_version = [string]((Find-Module $_name -Repository $_repository -ErrorAction Ignore).Version)
            }

            Write-Host "=> Downloading $($_name)@$($_version) from $($_repository)... " -NoNewline -ForegroundColor Cyan

            # if the current version exists, do nothing
            if (!(Test-Path ([System.IO.Path]::Combine($psModules, "$($_name)/$($_version)")))) {
                # remove other versions
                if (Test-Path ([System.IO.Path]::Combine($psModules, "$($_name)"))) {
                    $null = Remove-Item -Path ([System.IO.Path]::Combine($psModules, "$($_name)")) -Force -Recurse
                }

                # download the module
                $null = Save-Module -Name $_name -RequiredVersion $_version -Repository $_repository -Path $psModules -Force -ErrorAction Stop
            }

            Write-Host 'Success' -ForegroundColor Green
        }
        catch {
            Write-Host 'Failed' -ForegroundColor Red
            throw ($PodeLocale.moduleOrVersionNotFoundExceptionMessage -f $_repository, $_name, $_version) #"Module or version not found on $($_repository): $($_name)@$($_version)"
        }
    }
}
src\Private\SmtpServer.ps1
using namespace Pode

function Start-PodeSmtpServer {
    # ensure we have smtp handlers
    if (Test-PodeIsEmpty (Get-PodeHandler -Type Smtp)) {
        # No SMTP handlers have been defined
        throw ($PodeLocale.noSmtpHandlersDefinedExceptionMessage)
    }

    # work out which endpoints to listen on
    $endpoints = @()

    @(Get-PodeEndpointByProtocolType -Type Smtp) | ForEach-Object {
        # get the ip address
        $_ip = [string]($_.Address)
        $_ip = Get-PodeIPAddressesForHostname -Hostname $_ip -Type All | Select-Object -First 1
        $_ip = Get-PodeIPAddress $_ip -DualMode:($_.DualMode)

        # dual mode?
        $addrs = $_ip
        if ($_.DualMode) {
            $addrs = Resolve-PodeIPDualMode -IP $_ip
        }

        # the endpoint
        $_endpoint = @{
            Name                   = $_.Name
            Key                    = "$($_ip):$($_.Port)"
            Address                = $addrs
            Hostname               = $_.HostName
            IsIPAddress            = $_.IsIPAddress
            Port                   = $_.Port
            Certificate            = $_.Certificate.Raw
            AllowClientCertificate = $_.Certificate.AllowClientCertificate
            TlsMode                = $_.Certificate.TlsMode
            Url                    = $_.Url
            Protocol               = $_.Protocol
            Type                   = $_.Type
            Pool                   = $_.Runspace.PoolName
            Acknowledge            = $_.Tcp.Acknowledge
            SslProtocols           = $_.Ssl.Protocols
            DualMode               = $_.DualMode
        }

        # add endpoint to list
        $endpoints += $_endpoint
    }

    # create the listener
    $listener = [PodeListener]::new($PodeContext.Tokens.Cancellation.Token)
    $listener.ErrorLoggingEnabled = (Test-PodeErrorLoggingEnabled)
    $listener.ErrorLoggingLevels = @(Get-PodeErrorLoggingLevel)
    $listener.RequestTimeout = $PodeContext.Server.Request.Timeout
    $listener.RequestBodySize = $PodeContext.Server.Request.BodySize

    try {
        # register endpoints on the listener
        $endpoints | ForEach-Object {
            $socket = [PodeSocket]::new($_.Name, $_.Address, $_.Port, $_.SslProtocols, [PodeProtocolType]::Smtp, $_.Certificate, $_.AllowClientCertificate, $_.TlsMode, $_.DualMode)
            $socket.ReceiveTimeout = $PodeContext.Server.Sockets.ReceiveTimeout
            $socket.AcknowledgeMessage = $_.Acknowledge

            if (!$_.IsIPAddress) {
                $socket.Hostnames.Add($_.HostName)
            }

            $listener.Add($socket)
        }

        $listener.Start()
        $PodeContext.Listeners += $listener
    }
    catch {
        $_ | Write-PodeErrorLog
        $_.Exception | Write-PodeErrorLog -CheckInnerException
        Close-PodeDisposable -Disposable $listener
        throw $_.Exception
    }

    # script for listening out of for incoming requests
    $listenScript = {
        param(
            [Parameter(Mandatory = $true)]
            [ValidateNotNull()]
            $Listener,

            [Parameter(Mandatory = $true)]
            [int]
            $ThreadId
        )

        try {
            while ($Listener.IsConnected -and !$PodeContext.Tokens.Cancellation.IsCancellationRequested) {
                # get email
                $context = (Wait-PodeTask -Task $Listener.GetContextAsync($PodeContext.Tokens.Cancellation.Token))

                try {
                    try {
                        $Request = $context.Request
                        $Response = $context.Response

                        $script:SmtpEvent = @{
                            Response  = $Response
                            Request   = $Request
                            Lockable  = $PodeContext.Threading.Lockables.Global
                            Email     = @{
                                From            = $Request.From
                                To              = $Request.To
                                Data            = $Request.RawBody
                                Headers         = $Request.Headers
                                Subject         = $Request.Subject
                                IsUrgent        = $Request.IsUrgent
                                ContentType     = $Request.ContentType
                                ContentEncoding = $Request.ContentEncoding
                                Attachments     = $Request.Attachments
                                Body            = $Request.Body
                            }
                            Endpoint  = @{
                                Protocol = $Request.Scheme
                                Address  = $Request.Address
                                Name     = $context.EndpointName
                            }
                            Timestamp = [datetime]::UtcNow
                            Metadata  = @{}
                        }

                        # stop now if the request has an error
                        if ($Request.IsAborted) {
                            throw $Request.Error
                        }

                        # convert the ip
                        $ip = (ConvertTo-PodeIPAddress -Address $Request.RemoteEndPoint)

                        # ensure the request ip is allowed
                        if (!(Test-PodeIPAccess -IP $ip)) {
                            $Response.WriteLine('554 Your IP address was rejected', $true)
                        }

                        # has the ip hit the rate limit?
                        elseif (!(Test-PodeIPLimit -IP $ip)) {
                            $Response.WriteLine('554 Your IP address has hit the rate limit', $true)
                        }

                        # deal with smtp call
                        else {
                            $handlers = Get-PodeHandler -Type Smtp
                            foreach ($name in $handlers.Keys) {
                                $handler = $handlers[$name]
                                $null = Invoke-PodeScriptBlock -ScriptBlock $handler.Logic -Arguments $handler.Arguments -UsingVariables $handler.UsingVariables -Scoped -Splat
                            }
                        }
                    }
                    catch [System.OperationCanceledException] {
                        $_ | Write-PodeErrorLog -Level Debug
                    }
                    catch {
                        $_ | Write-PodeErrorLog
                        $_.Exception | Write-PodeErrorLog -CheckInnerException
                    }
                }
                finally {
                    $script:SmtpEvent = $null
                    Close-PodeDisposable -Disposable $context
                }
            }
        }
        catch [System.OperationCanceledException] {
            $_ | Write-PodeErrorLog -Level Debug
        }
        catch {
            $_ | Write-PodeErrorLog
            $_.Exception | Write-PodeErrorLog -CheckInnerException
            throw $_.Exception
        }
    }

    # start the runspace for listening on x-number of threads
    1..$PodeContext.Threads.General | ForEach-Object {
        Add-PodeRunspace -Type Smtp -Name 'Listener' -Id $_ -ScriptBlock $listenScript -Parameters @{ 'Listener' = $listener; 'ThreadId' = $_ }
    }

    # script to keep smtp server listening until cancelled
    $waitScript = {
        param(
            [Parameter(Mandatory = $true)]
            [ValidateNotNull()]
            $Listener
        )

        try {
            while ($Listener.IsConnected -and !$PodeContext.Tokens.Cancellation.IsCancellationRequested) {
                Start-Sleep -Seconds 1
            }
        }
        catch [System.OperationCanceledException] {
            $_ | Write-PodeErrorLog -Level Debug
        }
        catch {
            $_ | Write-PodeErrorLog
            $_.Exception | Write-PodeErrorLog -CheckInnerException
            throw $_.Exception
        }
        finally {
            Close-PodeDisposable -Disposable $Listener
        }
    }

    Add-PodeRunspace -Type Smtp -Name 'KeepAlive' -ScriptBlock $waitScript -Parameters @{ 'Listener' = $listener } -NoProfile

    # state where we're running
    return @(foreach ($endpoint in $endpoints) {
            @{
                Url      = $endpoint.Url
                Pool     = $endpoint.Pool
                DualMode = $endpoint.DualMode
            }
        })
}
src\Private\Streams.ps1
function Read-PodeStreamToEnd {
    param(
        [Parameter()]
        $Stream,

        [Parameter()]
        $Encoding = [System.Text.Encoding]::UTF8
    )

    if ($null -eq $Stream) {
        return [string]::Empty
    }

    return (Use-PodeStream -Stream ([System.IO.StreamReader]::new($Stream, $Encoding)) {
            return $args[0].ReadToEnd()
        })
}

function Read-PodeByteLineFromByteArray {
    param(
        [Parameter(Mandatory = $true)]
        [byte[]]
        $Bytes,

        [Parameter()]
        $Encoding = [System.Text.Encoding]::UTF8,

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

        [switch]
        $IncludeNewLine
    )

    $nlBytes = Get-PodeNewLineByte -Encoding $Encoding

    # attempt to find \n
    $index = [array]::IndexOf($Bytes, $nlBytes.NewLine, $StartIndex)
    $fIndex = $index

    # if not including new line, remove any trailing \r and \n
    if (!$IncludeNewLine) {
        $fIndex--

        if ($Bytes[$fIndex] -eq $nlBytes.Return) {
            $fIndex--
        }
    }

    # grab the portion of the bytes array - which is our line
    return @{
        Bytes      = $Bytes[$StartIndex..$fIndex]
        StartIndex = $StartIndex
        EndIndex   = $index
    }
}

function Get-PodeByteLinesFromByteArray {
    param(
        [Parameter(Mandatory = $true)]
        [byte[]]
        $Bytes,

        [Parameter()]
        $Encoding = [System.Text.Encoding]::UTF8,

        [switch]
        $IncludeNewLine
    )

    # lines
    $lines = @()
    $nlBytes = Get-PodeNewLineByte -Encoding $Encoding

    # attempt to find \n
    $index = 0
    while (($nextIndex = [array]::IndexOf($Bytes, $nlBytes.NewLine, $index)) -gt 0) {
        $fIndex = $nextIndex

        # if not including new line, remove any trailing \r and \n
        if (!$IncludeNewLine) {
            $fIndex--
            if ($Bytes[$fIndex] -eq $nlBytes.Return) {
                $fIndex--
            }
        }

        # add the line, and get the next one
        $lines += , $Bytes[$index..$fIndex]
        $index = $nextIndex + 1
    }

    return $lines
}
<#
.SYNOPSIS
    Converts a stream to a byte array.

.DESCRIPTION
    The `ConvertFrom-PodeValueToByteArray` function reads data from a stream and converts it to a byte array.
    It's useful for scenarios where you need to work with binary data from a stream.

.PARAMETER Stream
    Specifies the input stream to convert. This parameter is mandatory.

.OUTPUTS
    Returns a byte array containing the data read from the input stream.

.EXAMPLE
    # Example usage:
    # Read data from a file stream and convert it to a byte array
    $stream = [System.IO.File]::OpenRead("C:\path\to\file.bin")
    $byteArray = ConvertFrom-PodeValueToByteArray -Stream $stream
    $stream.Close()

.NOTES
    This is an internal function and may change in future releases of Pode.
#>
function ConvertFrom-PodeValueToByteArray {
    param(
        [Parameter(Mandatory = $true)]
        $Stream
    )

    # Initialize a buffer to read data in chunks
    $buffer = [byte[]]::new(64 * 1024)
    $ms = [System.IO.MemoryStream]::new()
    $read = 0

    # Read data from the stream and write it to the memory stream
    while (($read = $Stream.Read($buffer, 0, $buffer.Length)) -gt 0) {
        $ms.Write($buffer, 0, $read)
    }

    # Close the memory stream and return the byte array
    $ms.Close()
    return $ms.ToArray()
}
<#
.SYNOPSIS
    Converts a string value to a byte array using the specified encoding.

.DESCRIPTION
    The `ConvertFrom-PodeValueToByteArray` function takes a string value and converts it to a byte array.
    You can specify the desired encoding (default is UTF-8).

.PARAMETER Value
    Specifies the input string value to convert.

.PARAMETER Encoding
    Specifies the encoding to use when converting the string to bytes.
    Default value is UTF-8.

.OUTPUTS
    Returns a byte array containing the encoded representation of the input string.

.EXAMPLE
    # Example usage:
    $inputString = "Hello, world!"
    $byteArray = ConvertFrom-PodeValueToByteArray -Value $inputString
    # Now you can work with the byte array as needed.

.NOTES
    This is an internal function and may change in future releases of Pode.
#>
function ConvertFrom-PodeValueToByteArray {
    param(
        [Parameter()]
        [string]
        $Value,

        [Parameter()]
        $Encoding = [System.Text.Encoding]::UTF8
    )

    return $Encoding.GetBytes($Value)
}

function ConvertFrom-PodeBytesToString {
    param(
        [Parameter()]
        [byte[]]
        $Bytes,

        [Parameter()]
        $Encoding = [System.Text.Encoding]::UTF8,

        [switch]
        $RemoveNewLine
    )

    if (($null -eq $Bytes) -or ($Bytes.Length -eq 0)) {
        return $Bytes
    }

    $value = $Encoding.GetString($Bytes)
    if ($RemoveNewLine) {
        $value = $value.Trim("`r`n")
    }

    return $value
}

<#
.SYNOPSIS
    Retrieves information about newline characters in different encodings.

.DESCRIPTION
    The `Get-PodeNewLineByte` function returns a hashtable containing information about newline characters.
    It calculates the byte values for newline (`n`) and carriage return (`r`) based on the specified encoding (default is UTF-8).

.PARAMETER Encoding
    Specifies the encoding to use when calculating newline and carriage return byte values.
    Default value is UTF-8.

.OUTPUTS
    Returns a hashtable with the following keys:
    - `NewLine`: Byte value for newline character (`n`).
    - `Return`: Byte value for carriage return character (`r`).

.EXAMPLE
    Get-PodeNewLineByte -Encoding [System.Text.Encoding]::ASCII
    # Returns the byte values for newline and carriage return in ASCII encoding.

.NOTES
    This is an internal function and may change in future releases of Pode.
#>
function Get-PodeNewLineByte {
    [CmdletBinding()]
    [OutputType([hashtable])]
    param(
        [Parameter()]
        $Encoding = [System.Text.Encoding]::UTF8
    )

    return @{
        NewLine = @($Encoding.GetBytes("`n"))[0]
        Return  = @($Encoding.GetBytes("`r"))[0]
    }
}

function Test-PodeByteArrayIsBoundary {
    param(
        [Parameter()]
        [byte[]]
        $Bytes,

        [Parameter()]
        [string]
        $Boundary,

        [Parameter()]
        $Encoding = [System.Text.Encoding]::UTF8
    )

    # if no bytes, return
    if ($Bytes.Length -eq 0) {
        return $false
    }

    # if length difference >3, return (ie, 2 offset for `r`n)
    if (($Bytes.Length - $Boundary.Length) -gt 3) {
        return $false
    }

    # check if bytes starts with the boundary
    return (ConvertFrom-PodeBytesToString $Bytes $Encoding).StartsWith($Boundary)
}

function Remove-PodeNewLineBytesFromArray {
    param(
        [Parameter()]
        $Bytes,

        [Parameter()]
        $Encoding = [System.Text.Encoding]::UTF8
    )

    $nlBytes = Get-PodeNewLineByte -Encoding $Encoding
    $length = $Bytes.Length - 1

    if ($Bytes[$length] -eq $nlBytes.NewLine) {
        $length--
    }

    if ($Bytes[$length] -eq $nlBytes.Return) {
        $length--
    }

    return $Bytes[0..$length]
}

function Get-PodeCompressionStream {
    param (
        [Parameter(Mandatory = $true)]
        [System.IO.Stream]
        $InputStream,

        [Parameter(Mandatory = $true)]
        [ValidateSet('gzip', 'deflate')]
        [string]
        $Encoding,

        [Parameter(Mandatory = $true)]
        [System.IO.Compression.CompressionMode]
        $Mode
    )

    $leaveOpen = $Mode -eq [System.IO.Compression.CompressionMode]::Compress

    switch ($Encoding.ToLower()) {
        'gzip' {
            return [System.IO.Compression.GZipStream]::new($InputStream, $Mode, $leaveOpen)
        }

        'deflate' {
            return [System.IO.Compression.DeflateStream]::new($InputStream, $Mode, $leaveOpen)
        }

        default {
            # Unsupported stream compression encoding: $Encoding
            throw ($PodeLocale.unsupportedStreamCompressionEncodingExceptionMessage -f $Encoding)
        }
    }
}
src\Private\Tasks.ps1
function Test-PodeTasksExist {
    return (($null -ne $PodeContext.Tasks) -and (($PodeContext.Tasks.Enabled) -or ($PodeContext.Tasks.Items.Count -gt 0)))
}

function Start-PodeTaskHousekeeper {
    if (!(Test-PodeTasksExist)) {
        return
    }

    Add-PodeTimer -Name '__pode_task_housekeeper__' -Interval 30 -ScriptBlock {
        try {
            if ($PodeContext.Tasks.Processes.Count -eq 0) {
                return
            }

            $now = [datetime]::UtcNow

            foreach ($key in $PodeContext.Tasks.Processes.Keys.Clone()) {
                try {
                    $process = $PodeContext.Tasks.Processes[$key]

                    # has it completed or expire? then dispose and remove
                    if ((($null -ne $process.CompletedTime) -and ($process.CompletedTime.AddMinutes(1) -lt $now)) -or ($process.ExpireTime -lt $now)) {
                        Close-PodeTaskInternal -Process $process
                        continue
                    }

                    # if completed, and no completed time, set it
                    if ($process.Runspace.Handler.IsCompleted -and ($null -eq $process.CompletedTime)) {
                        $process.CompletedTime = $now
                    }
                }
                catch {
                    $_ | Write-PodeErrorLog
                }
            }

            $process = $null
        }
        catch {
            $_ | Write-PodeErrorLog
        }
    }
}

function Close-PodeTaskInternal {
    param(
        [Parameter()]
        [hashtable]
        $Process
    )

    if ($null -eq $Process) {
        return
    }

    Close-PodeDisposable -Disposable $Process.Runspace.Pipeline
    Close-PodeDisposable -Disposable $Process.Result
    $null = $PodeContext.Tasks.Processes.Remove($Process.ID)
}

function Invoke-PodeInternalTask {
    param(
        [Parameter(Mandatory = $true)]
        [hashtable]
        $Task,

        [Parameter()]
        [hashtable]
        $ArgumentList = $null,

        [Parameter()]
        [int]
        $Timeout = -1,

        [Parameter()]
        [ValidateSet('Default', 'Create', 'Start')]
        [string]
        $TimeoutFrom = 'Default'
    )

    try {
        # generate processId for task
        $processId = New-PodeGuid

        # setup event param
        $parameters = @{
            ProcessId    = $processId
            ArgumentList = $ArgumentList
        }

        # what's the timeout values to use?
        if ($TimeoutFrom -eq 'Default') {
            $TimeoutFrom = $Task.Timeout.From
        }

        if ($Timeout -eq -1) {
            $Timeout = $Task.Timeout.Value
        }

        # what is the expire time if using "create" timeout?
        $expireTime = [datetime]::MaxValue
        $createTime = [datetime]::UtcNow

        if (($TimeoutFrom -ieq 'Create') -and ($Timeout -ge 0)) {
            $expireTime = $createTime.AddSeconds($Timeout)
        }

        # add task process
        $result = [System.Management.Automation.PSDataCollection[psobject]]::new()
        $PodeContext.Tasks.Processes[$processId] = @{
            ID            = $processId
            Task          = $Task.Name
            Runspace      = $null
            Result        = $result
            CreateTime    = $createTime
            StartTime     = $null
            CompletedTime = $null
            ExpireTime    = $expireTime
            Timeout       = @{
                Value = $Timeout
                From  = $TimeoutFrom
            }
            State         = 'Pending'
        }

        # start the task runspace
        $scriptblock = Get-PodeTaskScriptBlock
        $runspace = Add-PodeRunspace -Type Tasks -Name $Task.Name -ScriptBlock $scriptblock -Parameters $parameters -OutputStream $result -PassThru

        # add runspace to process
        $PodeContext.Tasks.Processes[$processId].Runspace = $runspace

        # return the task process
        return $PodeContext.Tasks.Processes[$processId]
    }
    catch {
        $_ | Write-PodeErrorLog
    }
}

function Get-PodeTaskScriptBlock {
    return {
        param($ProcessId, $ArgumentList)

        try {
            $process = $PodeContext.Tasks.Processes[$ProcessId]
            if ($null -eq $process) {
                # Task process does not exist: $ProcessId
                throw ($PodeLocale.taskProcessDoesNotExistExceptionMessage -f $ProcessId)
            }

            # set the start time and state
            $process.StartTime = [datetime]::UtcNow
            $process.State = 'Running'

            # set the expire time of timeout based on "start" time
            if (($process.Timeout.From -ieq 'Start') -and ($process.Timeout.Value -ge 0)) {
                $process.ExpireTime = $process.StartTime.AddSeconds($process.Timeout.Value)
            }

            # get the task, error if not found
            $task = $PodeContext.Tasks.Items[$process.Task]
            if ($null -eq $task) {
                # Task does not exist
                throw ($PodeLocale.taskDoesNotExistExceptionMessage -f $process.Task)
            }

            # build the script arguments
            $TaskEvent = @{
                Lockable  = $PodeContext.Threading.Lockables.Global
                Sender    = $task
                Timestamp = [DateTime]::UtcNow
                Metadata  = @{}
            }

            $_args = @{ Event = $TaskEvent }

            if ($null -ne $task.Arguments) {
                foreach ($key in $task.Arguments.Keys) {
                    $_args[$key] = $task.Arguments[$key]
                }
            }

            if ($null -ne $ArgumentList) {
                foreach ($key in $ArgumentList.Keys) {
                    $_args[$key] = $ArgumentList[$key]
                }
            }

            # add any using variables
            if ($null -ne $task.UsingVariables) {
                foreach ($usingVar in $task.UsingVariables) {
                    $_args[$usingVar.NewName] = $usingVar.Value
                }
            }

            # invoke the script from the task
            Invoke-PodeScriptBlock -ScriptBlock $task.Script -Arguments $_args -Scoped -Splat -Return

            # set the state to completed
            $process.State = 'Completed'
        }
        catch {
            # update the state
            if ($null -ne $process) {
                $process.State = 'Failed'
            }

            # log the error
            $_ | Write-PodeErrorLog
        }
        finally {
            Invoke-PodeGC
        }
    }
}

function Wait-PodeNetTaskInternal {
    [CmdletBinding()]
    [OutputType([object])]
    param(
        [Parameter(Mandatory = $true)]
        [System.Threading.Tasks.Task]
        $Task,

        [Parameter()]
        [int]
        $Timeout = -1
    )

    # do we need a timeout?
    $timeoutTask = $null
    if ($Timeout -gt 0) {
        $timeoutTask = [System.Threading.Tasks.Task]::Delay($Timeout)
    }

    # set the check task
    if ($null -eq $timeoutTask) {
        $checkTask = $Task
    }
    else {
        $checkTask = [System.Threading.Tasks.Task]::WhenAny($Task, $timeoutTask)
    }

    # is there a cancel token to supply?
    if (($null -eq $PodeContext) -or ($null -eq $PodeContext.Tokens.Cancellation.Token)) {
        $checkTask.Wait()
    }
    else {
        $checkTask.Wait($PodeContext.Tokens.Cancellation.Token)
    }

    # if the main task isnt complete, it timed out
    if (($null -ne $timeoutTask) -and (!$Task.IsCompleted)) {
        # "Task has timed out after $($Timeout)ms")
        throw [System.TimeoutException]::new($PodeLocale.taskTimedOutExceptionMessage -f $Timeout)
    }

    # only return a value if the result has one
    if ($null -ne $Task.Result) {
        return $Task.Result
    }
}

function Wait-PodeTaskInternal {
    [CmdletBinding()]
    [OutputType([object])]
    param(
        [Parameter(Mandatory = $true)]
        [hashtable]
        $Task,

        [Parameter()]
        [int]
        $Timeout = -1
    )

    # timeout needs to be in milliseconds
    if ($Timeout -gt 0) {
        $Timeout *= 1000
    }

    # wait for the pipeline to finish processing
    $null = $Task.Runspace.Handler.AsyncWaitHandle.WaitOne($Timeout)

    # get the current result
    $result = $Task.Result.ReadAll()

    # close the task
    Close-PodeTask -Task $Task

    # only return a value if the result has one
    if (($null -ne $result) -and ($result.Count -gt 0)) {
        return $result
    }
}
src\Private\TcpServer.ps1
using namespace Pode

function Start-PodeTcpServer {
    # work out which endpoints to listen on
    $endpoints = @()

    @(Get-PodeEndpointByProtocolType -Type Tcp) | ForEach-Object {
        # get the ip address
        $_ip = [string]($_.Address)
        $_ip = Get-PodeIPAddressesForHostname -Hostname $_ip -Type All | Select-Object -First 1
        $_ip = Get-PodeIPAddress $_ip -DualMode:($_.DualMode)

        # dual mode?
        $addrs = $_ip
        if ($_.DualMode) {
            $addrs = Resolve-PodeIPDualMode -IP $_ip
        }

        # the endpoint
        $_endpoint = @{
            Name                   = $_.Name
            Key                    = "$($_ip):$($_.Port)"
            Address                = $addrs
            Hostname               = $_.HostName
            IsIPAddress            = $_.IsIPAddress
            Port                   = $_.Port
            Certificate            = $_.Certificate.Raw
            AllowClientCertificate = $_.Certificate.AllowClientCertificate
            TlsMode                = $_.Certificate.TlsMode
            Url                    = $_.Url
            Protocol               = $_.Protocol
            Type                   = $_.Type
            Pool                   = $_.Runspace.PoolName
            Acknowledge            = $_.Tcp.Acknowledge
            CRLFMessageEnd         = $_.Tcp.CRLFMessageEnd
            SslProtocols           = $_.Ssl.Protocols
            DualMode               = $_.DualMode
        }

        # add endpoint to list
        $endpoints += $_endpoint
    }

    # create the listener
    $listener = [PodeListener]::new($PodeContext.Tokens.Cancellation.Token)
    $listener.ErrorLoggingEnabled = (Test-PodeErrorLoggingEnabled)
    $listener.ErrorLoggingLevels = @(Get-PodeErrorLoggingLevel)
    $listener.RequestTimeout = $PodeContext.Server.Request.Timeout
    $listener.RequestBodySize = $PodeContext.Server.Request.BodySize

    try {
        # register endpoints on the listener
        $endpoints | ForEach-Object {
            $socket = [PodeSocket]::new($_.Name, $_.Address, $_.Port, $_.SslProtocols, [PodeProtocolType]::Tcp, $_.Certificate, $_.AllowClientCertificate, $_.TlsMode, $_.DualMode)
            $socket.ReceiveTimeout = $PodeContext.Server.Sockets.ReceiveTimeout
            $socket.AcknowledgeMessage = $_.Acknowledge
            $socket.CRLFMessageEnd = $_.CRLFMessageEnd

            if (!$_.IsIPAddress) {
                $socket.Hostnames.Add($_.HostName)
            }

            $listener.Add($socket)
        }

        $listener.Start()
        $PodeContext.Listeners += $listener
    }
    catch {
        $_ | Write-PodeErrorLog
        $_.Exception | Write-PodeErrorLog -CheckInnerException
        Close-PodeDisposable -Disposable $listener
        throw $_.Exception
    }

    # script for listening out of for incoming requests
    $listenScript = {
        param(
            [Parameter(Mandatory = $true)]
            [ValidateNotNull()]
            $Listener,

            [Parameter(Mandatory = $true)]
            [int]
            $ThreadId
        )

        try {
            while ($Listener.IsConnected -and !$PodeContext.Tokens.Cancellation.IsCancellationRequested) {
                # get email
                $context = (Wait-PodeTask -Task $Listener.GetContextAsync($PodeContext.Tokens.Cancellation.Token))

                try {
                    try {
                        $Request = $context.Request
                        $Response = $context.Response

                        $TcpEvent = @{
                            Response   = $Response
                            Request    = $Request
                            Lockable   = $PodeContext.Threading.Lockables.Global
                            Endpoint   = @{
                                Protocol = $Request.Scheme
                                Address  = $Request.Address
                                Name     = $context.EndpointName
                            }
                            Parameters = $null
                            Timestamp  = [datetime]::UtcNow
                            Metadata   = @{}
                        }

                        # stop now if the request has an error
                        if ($Request.IsAborted) {
                            throw $Request.Error
                        }

                        # convert the ip
                        $ip = (ConvertTo-PodeIPAddress -Address $Request.RemoteEndPoint)

                        # ensure the request ip is allowed
                        if (!(Test-PodeIPAccess -IP $ip)) {
                            $Response.WriteLine('Your IP address was rejected', $true)
                            Close-PodeTcpClient
                            continue
                        }

                        # has the ip hit the rate limit?
                        if (!(Test-PodeIPLimit -IP $ip)) {
                            $Response.WriteLine('Your IP address has hit the rate limit', $true)
                            Close-PodeTcpClient
                            continue
                        }

                        # deal with tcp call and find the verb, and for the endpoint
                        if ([string]::IsNullOrEmpty($TcpEvent.Request.Body)) {
                            continue
                        }

                        $verb = Find-PodeVerb -Verb $TcpEvent.Request.Body -EndpointName $TcpEvent.Endpoint.Name
                        if ($null -eq $verb) {
                            $verb = Find-PodeVerb -Verb '*' -EndpointName $TcpEvent.Endpoint.Name
                        }

                        if ($null -eq $verb) {
                            continue
                        }

                        # set the route parameters
                        if ($verb.Verb -ine '*') {
                            $TcpEvent.Parameters = @{}
                            if ($TcpEvent.Request.Body -imatch "$($verb.Verb)$") {
                                $TcpEvent.Parameters = $Matches
                            }
                        }

                        # invoke it
                        if ($null -ne $verb.Logic) {
                            $null = Invoke-PodeScriptBlock -ScriptBlock $verb.Logic -Arguments $verb.Arguments -UsingVariables $verb.UsingVariables -Scoped -Splat
                        }

                        # is the verb auto-close?
                        if ($verb.Connection.Close) {
                            Close-PodeTcpClient
                            continue
                        }

                        # is the verb auto-upgrade to ssl?
                        if ($verb.Connection.UpgradeToSsl) {
                            $Request.UpgradeToSSL()
                        }
                    }
                    catch [System.OperationCanceledException] {
                        $_ | Write-PodeErrorLog -Level Debug
                    }
                    catch {
                        $_ | Write-PodeErrorLog
                        $_.Exception | Write-PodeErrorLog -CheckInnerException
                    }
                }
                finally {
                    $TcpEvent = $null
                    Close-PodeDisposable -Disposable $context
                }
            }
        }
        catch [System.OperationCanceledException] {
            $_ | Write-PodeErrorLog -Level Debug
        }
        catch {
            $_ | Write-PodeErrorLog
            $_.Exception | Write-PodeErrorLog -CheckInnerException
            throw $_.Exception
        }
    }

    # start the runspace for listening on x-number of threads
    1..$PodeContext.Threads.General | ForEach-Object {
        Add-PodeRunspace -Type Tcp -Name 'Listener' -Id $_ -ScriptBlock $listenScript -Parameters @{ 'Listener' = $listener; 'ThreadId' = $_ }
    }

    # script to keep tcp server listening until cancelled
    $waitScript = {
        param(
            [Parameter(Mandatory = $true)]
            [ValidateNotNull()]
            $Listener
        )

        try {
            while ($Listener.IsConnected -and !$PodeContext.Tokens.Cancellation.IsCancellationRequested) {
                Start-Sleep -Seconds 1
            }
        }
        catch [System.OperationCanceledException] {
            $_ | Write-PodeErrorLog -Level Debug
        }
        catch {
            $_ | Write-PodeErrorLog
            $_.Exception | Write-PodeErrorLog -CheckInnerException
            throw $_.Exception
        }
        finally {
            Close-PodeDisposable -Disposable $Listener
        }
    }

    Add-PodeRunspace -Type Tcp -Name 'KeepAlive' -ScriptBlock $waitScript -Parameters @{ 'Listener' = $listener } -NoProfile

    # state where we're running
    return @(foreach ($endpoint in $endpoints) {
            @{
                Url      = $endpoint.Url
                Pool     = $endpoint.Pool
                DualMode = $endpoint.DualMode
            }
        })
}
src\Private\Timers.ps1
function Find-PodeTimer {
    param(
        [Parameter(Mandatory = $true)]
        [ValidateNotNullOrEmpty()]
        [string]
        $Name
    )

    return $PodeContext.Timers.Items[$Name]
}

function Test-PodeTimersExist {
    return (($null -ne $PodeContext.Timers) -and (($PodeContext.Timers.Enabled) -or ($PodeContext.Timers.Items.Count -gt 0)))
}

function Start-PodeTimerRunspace {
    if (!(Test-PodeTimersExist)) {
        return
    }

    $script = {
        try {

            while (!$PodeContext.Tokens.Cancellation.IsCancellationRequested) {
                try {
                    $_now = [DateTime]::Now

                    # only run timers that haven't completed, and have a next trigger in the past
                    foreach ($timer in $PodeContext.Timers.Items.Values) {
                        if ($timer.Completed -or (!$timer.OnStart -and ($timer.NextTriggerTime -gt $_now))) {
                            continue
                        }

                        try {
                            $timer.OnStart = $false
                            $timer.Count++

                            # set last trigger to current next trigger
                            if ($null -ne $timer.NextTriggerTime) {
                                $timer.LastTriggerTime = $timer.NextTriggerTime
                            }
                            else {
                                $timer.LastTriggerTime = $_now
                            }

                            # has the timer completed?
                            if (($timer.Limit -gt 0) -and ($timer.Count -ge $timer.Limit)) {
                                $timer.Completed = $true
                            }

                            # next trigger
                            if (!$timer.Completed) {
                                $timer.NextTriggerTime = $_now.AddSeconds($timer.Interval)
                            }
                            else {
                                $timer.NextTriggerTime = $null
                            }

                            # run the timer
                            Invoke-PodeInternalTimer -Timer $timer
                        }
                        catch {
                            $_ | Write-PodeErrorLog
                        }
                    }

                    Start-Sleep -Seconds 1
                }
                catch {
                    $_ | Write-PodeErrorLog
                }
            }
        }
        catch [System.OperationCanceledException] {
            $_ | Write-PodeErrorLog -Level Debug
        }
        catch {
            $_ | Write-PodeErrorLog
            throw $_.Exception
        }
    }

    Add-PodeRunspace -Type Timers -Name 'Scheduler' -ScriptBlock $script
}

function Invoke-PodeInternalTimer {
    [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssignments', '')]
    param(
        [Parameter(Mandatory = $true)]
        $Timer,

        [Parameter()]
        [object[]]
        $ArgumentList = $null
    )

    try {
        $TimerEvent = @{
            Lockable  = $PodeContext.Threading.Lockables.Global
            Sender    = $Timer
            Timestamp = [DateTime]::UtcNow
            Metadata  = @{}
        }

        # add main timer args
        $_args = @()
        if (($null -ne $Timer.Arguments) -and ($Timer.Arguments.Length -gt 0)) {
            $_args += $Timer.Arguments
        }

        # add adhoc timer invoke args
        if (($null -ne $ArgumentList) -and ($ArgumentList.Length -gt 0)) {
            $_args += $ArgumentList
        }

        # invoke timer
        Invoke-PodeScriptBlock -ScriptBlock $Timer.Script.GetNewClosure() -Arguments $_args -UsingVariables $Timer.UsingVariables -Scoped -Splat -NoNewClosure
    }
    catch {
        $_ | Write-PodeErrorLog
    }
    finally {
        Invoke-PodeGC
    }
}
src\Private\Verbs.ps1
function Find-PodeVerb {
    param(
        [Parameter(Mandatory = $true)]
        [ValidateNotNullOrEmpty()]
        [string]
        $Verb,

        [Parameter()]
        [string]
        $EndpointName
    )

    # if we have a perfect match for the verb, return it
    $found = Get-PodeVerbByLiteral -Verbs $PodeContext.Server.Verbs[$Verb] -EndpointName $EndpointName
    if ($null -ne $found) {
        return $found
    }

    # otherwise, match regex on the verbs (first match only)
    $valid = @(foreach ($key in $PodeContext.Server.Verbs.Keys) {
            if (($key -ine '*') -and ($Verb -imatch "^$($key)$")) {
                $key
                break
            }
        })[0]

    if ($null -eq $valid) {
        return $null
    }

    # is the verb valid for any protocols/endpoints?
    $found = Get-PodeVerbByLiteral -Verbs $PodeContext.Server.Verbs[$valid] -EndpointName $EndpointName
    if ($null -eq $found) {
        return $null
    }

    return $found
}

function Get-PodeVerbByLiteral {
    param(
        [Parameter()]
        [hashtable[]]
        $Verbs,

        [Parameter()]
        [string]
        $EndpointName
    )

    # if verbs is already null/empty just return
    if (($null -eq $Verbs) -or ($Verbs.Length -eq 0)) {
        return $null
    }

    # get the verb
    return (Get-PodeVerbsByLiteral -Verbs $Verbs -EndpointName $EndpointName)
}

function Get-PodeVerbsByLiteral {
    param(
        [Parameter()]
        [hashtable[]]
        $Verbs,

        [Parameter()]
        [string]
        $EndpointName
    )

    # see if a verb has the endpoint name
    if (![string]::IsNullOrWhiteSpace($EndpointName)) {
        foreach ($verb in $Verbs) {
            if ($verb.Endpoint.Name -ieq $EndpointName) {
                return $verb
            }
        }
    }

    # else find first default verb
    foreach ($verb in $Verbs) {
        if ([string]::IsNullOrWhiteSpace($verb.Endpoint.Name)) {
            return $verb
        }
    }

    return $null
}

function Test-PodeVerbAndError {
    param(
        [Parameter(Mandatory = $true)]
        [string]
        $Verb,

        [Parameter()]
        [string]
        $Protocol,

        [Parameter()]
        [string]
        $Address
    )

    $found = @($PodeContext.Server.Verbs[$Verb])

    if (($found | Where-Object { ($_.Endpoint.Protocol -ieq $Protocol) -and ($_.Endpoint.Address -ieq $Address) } | Measure-Object).Count -eq 0) {
        return
    }

    $_url = $Protocol
    if (![string]::IsNullOrEmpty($_url) -and ![string]::IsNullOrWhiteSpace($Address)) {
        $_url = "$($_url)://$($Address)"
    }
    elseif (![string]::IsNullOrWhiteSpace($Address)) {
        $_url = $Address
    }

    if ([string]::IsNullOrEmpty($_url)) {
        throw ($PodeLocale.verbAlreadyDefinedExceptionMessage -f $Verb) #"[Verb] $($Verb): Already defined"
    }
    else {
        throw ($PodeLocale.verbAlreadyDefinedForUrlExceptionMessage -f $Verb, $_url) # "[Verb] $($Verb): Already defined for $($_url)"
    }
}
src\Private\WebSockets.ps1
using namespace Pode

function Test-PodeWebSocketsExist {
    return (($null -ne $PodeContext.Server.WebSockets) -and (($PodeContext.Server.WebSockets.Enabled) -or ($PodeContext.Server.WebSockets.Connections.Count -gt 0)))
}

function Find-PodeWebSocket {
    param(
        [Parameter(Mandatory = $true)]
        [string]
        $Name
    )

    return $PodeContext.Server.WebSockets.Connections[$Name]
}

function New-PodeWebSocketReceiver {
    if ($null -ne $PodeContext.Server.WebSockets.Receiver) {
        return
    }

    try {
        $receiver = [PodeReceiver]::new($PodeContext.Tokens.Cancellation.Token)
        $receiver.ErrorLoggingEnabled = (Test-PodeErrorLoggingEnabled)
        $receiver.ErrorLoggingLevels = @(Get-PodeErrorLoggingLevel)
        $PodeContext.Server.WebSockets.Receiver = $receiver
        $PodeContext.Receivers += $receiver
    }
    catch {
        $_ | Write-PodeErrorLog
        $_.Exception | Write-PodeErrorLog -CheckInnerException
        Close-PodeDisposable -Disposable $receiver
        throw $_.Exception
    }
}

function Start-PodeWebSocketRunspace {
    if (!(Test-PodeWebSocketsExist)) {
        return
    }

    # script for listening out of for incoming requests (Receiver)
    $receiveScript = {
        param(
            [Parameter(Mandatory = $true)]
            [ValidateNotNull()]
            $Receiver,

            [Parameter(Mandatory = $true)]
            [int]
            $ThreadId
        )

        try {
            while ($Receiver.IsConnected -and !$PodeContext.Tokens.Cancellation.IsCancellationRequested) {
                # get request
                $request = (Wait-PodeTask -Task $Receiver.GetWebSocketRequestAsync($PodeContext.Tokens.Cancellation.Token))

                try {
                    try {
                        $WsEvent = @{
                            Request   = $request
                            Data      = $null
                            Files     = $null
                            Lockable  = $PodeContext.Threading.Lockables.Global
                            Timestamp = [datetime]::UtcNow
                            Metadata  = @{}
                        }

                        # find the websocket definition
                        $websocket = Find-PodeWebSocket -Name $request.WebSocket.Name
                        if ($null -eq $websocket.Logic) {
                            continue
                        }

                        # parse data
                        $result = ConvertFrom-PodeRequestContent -Request $request -ContentType $request.WebSocket.ContentType
                        $WsEvent.Data = $result.Data
                        $WsEvent.Files = $result.Files

                        # invoke websocket script
                        $null = Invoke-PodeScriptBlock -ScriptBlock $websocket.Logic -Arguments $websocket.Arguments -UsingVariables $websocket.UsingVariables -Scoped -Splat
                    }
                    catch [System.OperationCanceledException] {
                        $_ | Write-PodeErrorLog -Level Debug
                    }
                    catch {
                        $_ | Write-PodeErrorLog
                        $_.Exception | Write-PodeErrorLog -CheckInnerException
                    }
                }
                finally {
                    $WsEvent = $null
                    Close-PodeDisposable -Disposable $request
                }
            }
        }
        catch [System.OperationCanceledException] {
            $_ | Write-PodeErrorLog -Level Debug
        }
        catch {
            $_ | Write-PodeErrorLog
            $_.Exception | Write-PodeErrorLog -CheckInnerException
            throw $_.Exception
        }
    }

    # start the runspace for listening on x-number of threads
    1..$PodeContext.Threads.WebSockets | ForEach-Object {
        Add-PodeRunspace -Type WebSockets -Name 'Receiver' -Id $_ -ScriptBlock $receiveScript -Parameters @{ 'Receiver' = $PodeContext.Server.WebSockets.Receiver; 'ThreadId' = $_ }
    }

    # script to keep websocket server receiving until cancelled
    $waitScript = {
        param(
            [Parameter(Mandatory = $true)]
            [ValidateNotNull()]
            $Receiver
        )

        try {
            while ($Receiver.IsConnected -and !$PodeContext.Tokens.Cancellation.IsCancellationRequested) {
                Start-Sleep -Seconds 1
            }
        }
        catch [System.OperationCanceledException] {
            $_ | Write-PodeErrorLog -Level Debug
        }
        catch {
            $_ | Write-PodeErrorLog
            $_.Exception | Write-PodeErrorLog -CheckInnerException
            throw $_.Exception
        }
        finally {
            Close-PodeDisposable -Disposable $Receiver
        }
    }

    Add-PodeRunspace -Type WebSockets -Name 'KeepAlive' -ScriptBlock $waitScript -Parameters @{ 'Receiver' = $PodeContext.Server.WebSockets.Receiver } -NoProfile
}
src\Public\Access.ps1
<#
.SYNOPSIS
Create a new type of Access scheme.

.DESCRIPTION
Create a new type of Access scheme, which retrieves the destination/resource's authorisation values which a user needs for access.

.PARAMETER Type
The inbuilt Type of Access this method is for: Role, Group, Scope, User.

.PARAMETER Custom
If supplied, the access Scheme will be flagged as using Custom logic.

.PARAMETER ScriptBlock
An optional ScriptBlock for retrieving authorisation values for the authenticated user, useful if the values reside in an external data store.
This, or Path, is mandatory if using a Custom scheme.

.PARAMETER ArgumentList
An optional array of arguments to supply to the ScriptBlock.

.PARAMETER Path
An optional property Path within the $WebEvent.Auth.User object to extract authorisation values.
The default Path is based on the Access Type, either Roles; Groups; Scopes; or Username.
This, or ScriptBlock, is mandatory if using a Custom scheme.

.EXAMPLE
$role_access = New-PodeAccessScheme -Type Role

.EXAMPLE
$group_access = New-PodeAccessScheme -Type Group -Path 'Metadata.Groups'

.EXAMPLE
$scope_access = New-PodeAccessScheme -Type Scope -Scriptblock { param($user) return @(Get-ExampleAccess -Username $user.Username) }

.EXAMPLE
$custom_access = New-PodeAccessScheme -Custom -Path 'CustomProp'
#>
function New-PodeAccessScheme {
    [CmdletBinding(DefaultParameterSetName = 'Type_Path')]
    [OutputType([hashtable])]
    param(
        [Parameter(Mandatory = $true, ParameterSetName = 'Type_Scriptblock')]
        [Parameter(Mandatory = $true, ParameterSetName = 'Type_Path')]
        [ValidateSet('Role', 'Group', 'Scope', 'User')]
        [string]
        $Type,

        [Parameter(Mandatory = $true, ParameterSetName = 'Custom_Scriptblock')]
        [Parameter(Mandatory = $true, ParameterSetName = 'Custom_Path')]
        [switch]
        $Custom,

        [Parameter(Mandatory = $true, ParameterSetName = 'Custom_Scriptblock')]
        [Parameter(ParameterSetName = 'Type_Scriptblock')]
        [scriptblock]
        $ScriptBlock,

        [Parameter(ParameterSetName = 'Custom_Scriptblock')]
        [Parameter(ParameterSetName = 'Type_Scriptblock')]
        [object[]]
        $ArgumentList,

        [Parameter(Mandatory = $true, ParameterSetName = 'Custom_Path')]
        [Parameter(ParameterSetName = 'Type_Path')]
        [string]
        $Path
    )

    # for custom access a validator is mandatory
    if ($Custom) {
        if ([string]::IsNullOrWhiteSpace($Path) -and (Test-PodeIsEmpty $ScriptBlock)) {
            # A Path or ScriptBlock is required for sourcing the Custom access values
            throw ($PodeLocale.customAccessPathOrScriptBlockRequiredExceptionMessage)
        }
    }

    # parse using variables in scriptblock
    $scriptObj = $null
    if (!(Test-PodeIsEmpty $ScriptBlock)) {
        $ScriptBlock, $usingScriptVars = Convert-PodeScopedVariables -ScriptBlock $ScriptBlock -PSSession $PSCmdlet.SessionState
        $scriptObj = @{
            Script         = $ScriptBlock
            UsingVariables = $usingScriptVars
        }
    }

    # default path
    if (!$Custom -and (Test-PodeIsEmpty $ScriptBlock) -and [string]::IsNullOrWhiteSpace($Path)) {
        if ($Type -ieq 'user') {
            $Path = 'Username'
        }
        else {
            $Path = "$($Type)s"
        }
    }

    # return scheme
    return @{
        Type        = $Type
        IsCustom    = $Custom.IsPresent
        ScriptBlock = $scriptObj
        Arguments   = $ArgumentList
        Path        = $Path
    }
}

<#
.SYNOPSIS
Add an authorisation Access method.

.DESCRIPTION
Add an authorisation Access method for use with Authentication methods, which will authorise access to Routes.
Or they can be used independant of Authentication/Routes for custom scenarios.

.PARAMETER Name
A unique Name for the Access method.

.PARAMETER Description
A short description used by OpenAPI.

.PARAMETER Scheme
The access Scheme to use for retrieving credentials (From New-PodeAccessScheme).

.PARAMETER ScriptBlock
An optional Scriptblock, which can be used to invoke custom validation logic to verify authorisation.

.PARAMETER ArgumentList
An optional array of arguments to supply to the ScriptBlock.

.PARAMETER Match
An optional inbuilt Match method to use when verifying access to a Route, this only applies when no custom Validator scriptblock is supplied. (Default: One)
"One" will allow access if the User has at least one of the Route's access values.
"All" will allow access only if the User has all the values.
"None" will allow access only if the User has none of the values.

.EXAMPLE
New-PodeAccessScheme -Type Role | Add-PodeAccess -Name 'Example'

.EXAMPLE
New-PodeAccessScheme -Type Group -Path 'Metadata.Groups' | Add-PodeAccess -Name 'Example' -Match All

.EXAMPLE
New-PodeAccessScheme -Type Scope -Scriptblock { param($user) return @(Get-ExampleAccess -Username $user.Username) } | Add-PodeAccess -Name 'Example'

.EXAMPLE
New-PodeAccessScheme -Custom -Path 'CustomProp' | Add-PodeAccess -Name 'Example' -ScriptBlock { param($userAccess, $customAccess) return $userAccess.Country -ieq $customAccess.Country }
#>
function Add-PodeAccess {
    [CmdletBinding(DefaultParameterSetName = 'Match')]
    param(
        [Parameter(Mandatory = $true)]
        [string]
        $Name,

        [string]
        $Description,

        [Parameter(Mandatory = $true, ValueFromPipeline = $true)]
        [hashtable]
        $Scheme,

        [Parameter(Mandatory = $true, ParameterSetName = 'ScriptBlock')]
        [scriptblock]
        $ScriptBlock,

        [Parameter(ParameterSetName = 'ScriptBlock')]
        [object[]]
        $ArgumentList,

        [Parameter(ParameterSetName = 'Match')]
        [ValidateSet('All', 'One', 'None')]
        [string]
        $Match = 'One'
    )
    begin {
        $pipelineItemCount = 0
    }

    process {
        $pipelineItemCount++
    }

    end {
        if ($pipelineItemCount -gt 1) {
            throw ($PodeLocale.fnDoesNotAcceptArrayAsPipelineInputExceptionMessage -f $($MyInvocation.MyCommand.Name))
        }
        # check name unique
        if (Test-PodeAccessExists -Name $Name) {
            # Access method already defined: $($Name)
            throw ($PodeLocale.accessMethodAlreadyDefinedExceptionMessage -f $Name)
        }

        # parse using variables in validator scriptblock
        $scriptObj = $null
        if (!(Test-PodeIsEmpty $ScriptBlock)) {
            $ScriptBlock, $usingScriptVars = Convert-PodeScopedVariables -ScriptBlock $ScriptBlock -PSSession $PSCmdlet.SessionState
            $scriptObj = @{
                Script         = $ScriptBlock
                UsingVariables = $usingScriptVars
            }
        }

        # add access object
        $PodeContext.Server.Authorisations.Methods[$Name] = @{
            Name        = $Name
            Description = $Description
            Scheme      = $Scheme
            ScriptBlock = $scriptObj
            Arguments   = $ArgumentList
            Match       = $Match.ToLowerInvariant()
            Cache       = @{}
            Merged      = $false
            Parent      = $null
        }
    }
}

<#
.SYNOPSIS
Let's you merge multiple Access methods together, into a "single" Access method.

.DESCRIPTION
Let's you merge multiple Access methods together, into a "single" Access method.
You can specify if only One or All of the methods need to pass to allow access, and you can also
merge other merged Access methods for more advanced scenarios.

.PARAMETER Name
A unique Name for the Access method.

.PARAMETER Access
Mutliple Access method Names to be merged.

.PARAMETER Valid
How many of the Access methods are required to be valid, One or All. (Default: One)

.EXAMPLE
Merge-PodeAccess -Name MergedAccess -Access RbacAccess, GbacAccess -Valid All
#>
function Merge-PodeAccess {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $true)]
        [string]
        $Name,

        [Parameter(Mandatory = $true)]
        [string[]]
        $Access,

        [Parameter()]
        [ValidateSet('One', 'All')]
        [string]
        $Valid = 'One'
    )

    # ensure the name doesn't already exist
    if (Test-PodeAccessExists -Name $Name) {
        throw ($PodeLocale.accessMethodAlreadyDefinedExceptionMessage -f $Name) #"Access method already defined: $($Name)"
    }

    # ensure all the access methods exist
    foreach ($accName in $Access) {
        if (!(Test-PodeAccessExists -Name $accName)) {
            throw ($PodeLocale.accessMethodNotExistForMergingExceptionMessage -f $accName) #"Access method does not exist for merging: $($accName)"
        }
    }

    # set parent access
    foreach ($accName in $Access) {
        $PodeContext.Server.Authorisations.Methods[$accName].Parent = $Name
    }

    # add auth method to server
    $PodeContext.Server.Authorisations.Methods[$Name] = @{
        Name    = $Name
        Access  = @($Access)
        PassOne = ($Valid -ieq 'one')
        Cache   = @{}
        Merged  = $true
        Parent  = $null
    }
}

<#
.SYNOPSIS
Assigns Custom Access value(s) to a Route.

.DESCRIPTION
Assigns Custom Access value(s) to a Route.

.PARAMETER Route
The Route to assign the Custom Access value(s).

.PARAMETER Name
The Name of the Access method the Custom Access value(s) are for.

.PARAMETER Value
The Custom Access Value(s)

.EXAMPLE
Add-PodeRoute -Method Get -Path '/users' -ScriptBlock {} -PassThru | Add-PodeAccessCustom -Name 'Example' -Value @{ Country = 'UK' }
#>
function Add-PodeAccessCustom {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $true, ValueFromPipeline = $true)]
        [hashtable[]]
        $Route,

        [Parameter(Mandatory = $true)]
        [string]
        $Name,

        [Parameter(Mandatory = $true)]
        [object[]]
        $Value
    )

    begin {
        $routes = @()
    }

    process {
        $routes += $Route
    }

    end {
        foreach ($r in $routes) {
            if ($r.AccessMeta.Custom.ContainsKey($Name)) {
                throw ($PodeLocale.routeAlreadyContainsCustomAccessExceptionMessage -f $r.Method, $r.Path, $Name) #"Route '[$($r.Method)] $($r.Path)' already contains Custom Access with name '$($Name)'"
            }

            $r.AccessMeta.Custom[$Name] = $Value
        }
    }
}

<#
.SYNOPSIS
Get one or more Access methods.

.DESCRIPTION
Get one or more Access methods.

.PARAMETER Name
The Name of the Access method. If no name supplied, all methods will be returned.

.EXAMPLE
$methods = Get-PodeAccess

.EXAMPLE
$methods = Get-PodeAccess -Name 'Example'

.EXAMPLE
$methods = Get-PodeAccess -Name 'Example1', 'Example2'
#>
function Get-PodeAccess {
    [CmdletBinding()]
    [OutputType([object[]])]
    param(
        [Parameter()]
        [string[]]
        $Name
    )

    # return all if no Name
    if ([string]::IsNullOrEmpty($Name) -or ($Name.Length -eq 0)) {
        return $PodeContext.Server.Authorisations.Methods.Values
    }

    # return filtered
    return @(foreach ($n in $Name) {
            $PodeContext.Server.Authorisations.Methods[$n]
        })
}

<#
.SYNOPSIS
Test if an Access method exists.

.DESCRIPTION
Test if an Access method exists.

.PARAMETER Name
The Name of the Access method.

.EXAMPLE
if (Test-PodeAccessExists -Name 'Example') { }
#>
function Test-PodeAccessExists {
    [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseSingularNouns', '')]
    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $true)]
        [string]
        $Name
    )
    return $PodeContext.Server.Authorisations.Methods.ContainsKey($Name)
}

<#
.SYNOPSIS
Test access values for a Source/Destination against an Access method.

.DESCRIPTION
Test access values for a Source/Destination against an Access method.

.PARAMETER Name
The Name of the Access method to use to verify the access.

.PARAMETER Source
An array of Source access values to pass to the Access method for verification against the Destination access values. (ie: User)

.PARAMETER Destination
An array of Destination access values to pass to the Access method for verification. (ie: Route)

.PARAMETER ArgumentList
An optional array of arguments to supply to the Access Scheme's ScriptBlock for retrieving access values.

.EXAMPLE
if (Test-PodeAccess -Name 'Example' -Source 'Developer' -Destination 'Admin') { }
#>
function Test-PodeAccess {
    [CmdletBinding()]
    [OutputType([bool])]
    param(
        [Parameter(Mandatory = $true)]
        [string]
        $Name,

        [Parameter()]
        [object[]]
        $Source = $null,

        [Parameter()]
        [object[]]
        $Destination = $null,

        [Parameter()]
        [object[]]
        $ArgumentList = $null
    )

    # get the access method
    $access = $PodeContext.Server.Authorisations.Methods[$Name]

    # authorised if no destination values
    if (($null -eq $Destination) -or ($Destination.Length -eq 0)) {
        return $true
    }

    # if we have no source values, invoke the scriptblock
    if (($null -eq $Source) -or ($Source.Length -eq 0)) {
        if ($null -ne $access.Scheme.ScriptBlock) {
            $_args = $ArgumentList + @($access.Scheme.Arguments)
            $Source = Invoke-PodeScriptBlock -ScriptBlock $access.Scheme.Scriptblock.Script -Arguments $_args -UsingVariables $access.Scheme.Scriptblock.UsingVariables -Return -Splat
        }
    }

    # check for custom validator, or use default match logic
    if ($null -ne $access.ScriptBlock) {
        $_args = @(, $Source) + @(, $Destination) + @($access.Arguments)
        return [bool](Invoke-PodeScriptBlock -ScriptBlock $access.ScriptBlock.Script -Arguments $_args -UsingVariables $access.ScriptBlock.UsingVariables -Return -Splat)
    }

    # not authorised if no source values
    if (($access.Match -ne 'none') -and (($null -eq $Source) -or ($Source.Length -eq 0))) {
        return $false
    }

    # one or all match?
    else {
        switch ($access.Match) {
            'one' {
                foreach ($item in $Source) {
                    if ($item -iin $Destination) {
                        return $true
                    }
                }
            }

            'all' {
                foreach ($item in $Destination) {
                    if ($item -inotin $Source) {
                        return $false
                    }
                }

                return $true
            }

            'none' {
                foreach ($item in $Source) {
                    if ($item -iin $Destination) {
                        return $false
                    }
                }

                return $true
            }
        }
    }

    # default is not authorised
    return $false
}

<#
.SYNOPSIS
Test the currently authenticated User's access against the supplied values.

.DESCRIPTION
Test the currently authenticated User's access against the supplied values. This will be the user in a WebEvent object.

.PARAMETER Name
The Name of the Access method to use to verify the access.

.PARAMETER Value
An array of access values to pass to the Access method for verification against the User.

.EXAMPLE
if (Test-PodeAccessUser -Name 'Example' -Value 'Developer', 'QA') { }
#>
function Test-PodeAccessUser {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $true)]
        [string]
        $Name,

        [Parameter(Mandatory = $true)]
        [object[]]
        $Value
    )

    # get the access method
    $access = $PodeContext.Server.Authorisations.Methods[$Name]

    # get the user
    $user = $WebEvent.Auth.User

    # if there's no scriptblock, try the Path fallback
    if ($null -eq $access.Scheme.Scriptblock) {
        $userAccess = $user
        foreach ($atom in $access.Scheme.Path.Split('.')) {
            $userAccess = $userAccess.($atom)
        }
    }

    # otherwise, invoke scriptblock
    else {
        $_args = @($user) + @($access.Scheme.Arguments)
        $userAccess = Invoke-PodeScriptBlock -ScriptBlock $access.Scheme.Scriptblock.Script -Arguments $_args -UsingVariables $access.Scheme.Scriptblock.UsingVariables -Return -Splat
    }

    # is the user authorised?
    return (Test-PodeAccess -Name $Name -Source $userAccess -Destination $Value)
}

<#
.SYNOPSIS
Test the currently authenticated User's access against the access values supplied for the current Route.

.DESCRIPTION
Test the currently authenticated User's access against the access values supplied for the current Route.

.PARAMETER Name
The Name of the Access method to use to verify the access.

.EXAMPLE
if (Test-PodeAccessRoute -Name 'Example') { }
#>
function Test-PodeAccessRoute {
    param(
        [Parameter(Mandatory = $true)]
        [string]
        $Name
    )

    # get the access method
    $access = $PodeContext.Server.Authorisations.Methods[$Name]

    # get route access values
    if ($access.Scheme.IsCustom) {
        $routeAccess = $WebEvent.Route.AccessMeta.Custom[$access.Name]
    }
    else {
        $routeAccess = $WebEvent.Route.AccessMeta[$access.Scheme.Type]
    }

    # if no values then skip
    if (($null -eq $routeAccess) -or ($routeAccess.Length -eq 0)) {
        return $true
    }

    # tests values against user
    return (Test-PodeAccessUser -Name $Name -Value $routeAccess)
}

<#
.SYNOPSIS
Remove a specific Access method.

.DESCRIPTION
Remove a specific Access method.

.PARAMETER Name
The Name of the Access method.

.EXAMPLE
Remove-PodeAccess -Name 'RBAC'
#>
function Remove-PodeAccess {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $true, ValueFromPipeline = $true)]
        [string]
        $Name
    )
    process {
        $null = $PodeContext.Server.Authorisations.Methods.Remove($Name)
    }
}

<#
.SYNOPSIS
Clear all defined Access methods.

.DESCRIPTION
Clear all defined Access methods.

.EXAMPLE
Clear-PodeAccess
#>
function Clear-PodeAccess {
    [CmdletBinding()]
    param()

    $PodeContext.Server.Authorisations.Methods.Clear()
}

<#
.SYNOPSIS
Adds an access method as global middleware.

.DESCRIPTION
Adds an access method as global middleware.

.PARAMETER Name
The Name of the Middleware.

.PARAMETER Access
The Name of the Access method to use.

.PARAMETER Route
A Route path for which Routes this Middleware should only be invoked against.

.EXAMPLE
Add-PodeAccessMiddleware -Name 'GlobalAccess' -Access AccessName

.EXAMPLE
Add-PodeAccessMiddleware -Name 'GlobalAccess' -Access AccessName -Route '/api/*'
#>
function Add-PodeAccessMiddleware {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $true)]
        [string]
        $Name,

        [Parameter(Mandatory = $true)]
        [string]
        $Access,

        [Parameter()]
        [string]
        $Route
    )

    if (!(Test-PodeAccessExists -Name $Access)) {
        throw ($PodeLocale.accessMethodNotExistExceptionMessage -f $Access) #"Access method does not exist: $($Access)"
    }

    Get-PodeAccessMiddlewareScript |
        New-PodeMiddleware -ArgumentList @{ Name = $Access } |
        Add-PodeMiddleware -Name $Name -Route $Route
}

<#
.SYNOPSIS
Automatically loads access ps1 files

.DESCRIPTION
Automatically loads access ps1 files from either an /access folder, or a custom folder. Saves space dot-sourcing them all one-by-one.

.PARAMETER Path
Optional Path to a folder containing ps1 files, can be relative or literal.

.EXAMPLE
Use-PodeAccess

.EXAMPLE
Use-PodeAccess -Path './my-access'
#>
function Use-PodeAccess {
    [CmdletBinding()]
    param(
        [Parameter()]
        [string]
        $Path
    )

    Use-PodeFolder -Path $Path -DefaultPath 'access'
}
src\Public\Authentication.ps1
<#
.SYNOPSIS
Create a new type of Authentication scheme.

.DESCRIPTION
Create a new type of Authentication scheme, which is used to parse the Request for user credentials for validating.

.PARAMETER Basic
If supplied, will use the inbuilt Basic Authentication credentials retriever.

.PARAMETER Encoding
The Encoding to use when decoding the Basic Authorization header.

.PARAMETER HeaderTag
The Tag name used in the Authorization header, ie: Basic, Bearer, Digest.

.PARAMETER Form
If supplied, will use the inbuilt Form Authentication credentials retriever.

.PARAMETER UsernameField
The name of the Username Field in the payload to retrieve the username.

.PARAMETER PasswordField
The name of the Password Field in the payload to retrieve the password.

.PARAMETER Custom
If supplied, will allow you to create a Custom Authentication credentials retriever.

.PARAMETER ScriptBlock
The ScriptBlock is used to parse the request and retieve user credentials and other information.

.PARAMETER ArgumentList
An array of arguments to supply to the Custom Authentication type's ScriptBlock.

.PARAMETER Name
The Name of an Authentication type - such as Basic or NTLM.

.PARAMETER Description
A short description for security scheme. CommonMark syntax MAY be used for rich text representation

.PARAMETER Realm
The name of scope of the protected area.

.PARAMETER Type
The scheme type for custom Authentication types. Default is HTTP.

.PARAMETER Middleware
An array of ScriptBlocks for optional Middleware to run before the Scheme's scriptblock.

.PARAMETER PostValidator
The PostValidator is a scriptblock that is invoked after user validation.

.PARAMETER Digest
If supplied, will use the inbuilt Digest Authentication credentials retriever.

.PARAMETER Bearer
If supplied, will use the inbuilt Bearer Authentication token retriever.

.PARAMETER ClientCertificate
If supplied, will use the inbuilt Client Certificate Authentication scheme.

.PARAMETER ClientId
The Application ID generated when registering a new app for OAuth2.

.PARAMETER ClientSecret
The Application Secret generated when registering a new app for OAuth2 (this is optional when using PKCE).

.PARAMETER RedirectUrl
An optional OAuth2 Redirect URL (default: <host>/oauth2/callback)

.PARAMETER AuthoriseUrl
The OAuth2 Authorisation URL to authenticate a User. This is optional if you're using an InnerScheme like Basic/Form.

.PARAMETER TokenUrl
The OAuth2 Token URL to acquire an access token.

.PARAMETER UserUrl
An optional User profile URL to retrieve a user's details - for OAuth2

.PARAMETER UserUrlMethod
An optional HTTP method to use when calling the User profile URL - for OAuth2 (Default: Post)

.PARAMETER CodeChallengeMethod
An optional method for sending a PKCE code challenge when calling the Authorise URL - for OAuth2 (Default: S256)

.PARAMETER UsePKCE
If supplied, OAuth2 authentication will use PKCE code verifiers - for OAuth2

.PARAMETER OAuth2
If supplied, will use the inbuilt OAuth2 Authentication scheme.

.PARAMETER Scope
An optional array of Scopes for Bearer/OAuth2 Authentication. (These are case-sensitive)

.PARAMETER ApiKey
If supplied, will use the inbuilt API key Authentication scheme.

.PARAMETER Location
The Location to find an API key: Header, Query, or Cookie. (Default: Header)

.PARAMETER LocationName
The Name of the Header, Query, or Cookie to find an API key. (Default depends on Location. Header/Cookie: X-API-KEY, Query: api_key)

.PARAMETER InnerScheme
An optional authentication Scheme (from New-PodeAuthScheme) that will be called prior to this Scheme.

.PARAMETER AsCredential
If supplied, username/password credentials for Basic/Form authentication will instead be supplied as a pscredential object.

.PARAMETER AsJWT
If supplied, the token/key supplied for Bearer/API key authentication will be parsed as a JWT, and the payload supplied instead.

.PARAMETER Secret
An optional Secret, used to sign/verify JWT signatures.

.EXAMPLE
$basic_auth = New-PodeAuthScheme -Basic

.EXAMPLE
$form_auth = New-PodeAuthScheme -Form -UsernameField 'Email'

.EXAMPLE
$custom_auth = New-PodeAuthScheme -Custom -ScriptBlock { /* logic */ }
#>
function New-PodeAuthScheme {
    [CmdletBinding(DefaultParameterSetName = 'Basic')]
    [OutputType([hashtable])]
    param(
        [Parameter(ParameterSetName = 'Basic')]
        [switch]
        $Basic,

        [Parameter(ParameterSetName = 'Basic')]
        [string]
        $Encoding = 'ISO-8859-1',

        [Parameter(ParameterSetName = 'Basic')]
        [Parameter(ParameterSetName = 'Bearer')]
        [Parameter(ParameterSetName = 'Digest')]
        [string]
        $HeaderTag,

        [Parameter(ParameterSetName = 'Form')]
        [switch]
        $Form,

        [Parameter(ParameterSetName = 'Form')]
        [string]
        $UsernameField = 'username',

        [Parameter(ParameterSetName = 'Form')]
        [string]
        $PasswordField = 'password',

        [Parameter(ParameterSetName = 'Custom')]
        [switch]
        $Custom,

        [Parameter(Mandatory = $true, ParameterSetName = 'Custom')]
        [ValidateScript({
                if (Test-PodeIsEmpty $_) {
                    # A non-empty ScriptBlock is required for the Custom authentication scheme
                    throw ($PodeLocale.nonEmptyScriptBlockRequiredForCustomAuthExceptionMessage)
                }

                return $true
            })]
        [scriptblock]
        $ScriptBlock,

        [Parameter(ParameterSetName = 'Custom')]
        [hashtable]
        $ArgumentList,

        [Parameter(ParameterSetName = 'Custom')]
        [string]
        $Name,

        [string]
        $Description,

        [Parameter()]
        [string]
        $Realm,

        [Parameter(ParameterSetName = 'Custom')]
        [ValidateSet('ApiKey', 'Http', 'OAuth2', 'OpenIdConnect')]
        [string]
        $Type = 'Http',

        [Parameter()]
        [object[]]
        $Middleware,

        [Parameter(ParameterSetName = 'Custom')]
        [scriptblock]
        $PostValidator = $null,

        [Parameter(ParameterSetName = 'Digest')]
        [switch]
        $Digest,

        [Parameter(ParameterSetName = 'Bearer')]
        [switch]
        $Bearer,

        [Parameter(ParameterSetName = 'ClientCertificate')]
        [switch]
        $ClientCertificate,

        [Parameter(ParameterSetName = 'OAuth2', Mandatory = $true)]
        [string]
        $ClientId,

        [Parameter(ParameterSetName = 'OAuth2')]
        [string]
        $ClientSecret,

        [Parameter(ParameterSetName = 'OAuth2')]
        [string]
        $RedirectUrl,

        [Parameter(ParameterSetName = 'OAuth2')]
        [string]
        $AuthoriseUrl,

        [Parameter(ParameterSetName = 'OAuth2', Mandatory = $true)]
        [string]
        $TokenUrl,

        [Parameter(ParameterSetName = 'OAuth2')]
        [string]
        $UserUrl,

        [Parameter(ParameterSetName = 'OAuth2')]
        [ValidateSet('Get', 'Post')]
        [string]
        $UserUrlMethod = 'Post',

        [Parameter(ParameterSetName = 'OAuth2')]
        [ValidateSet('plain', 'S256')]
        [string]
        $CodeChallengeMethod = 'S256',

        [Parameter(ParameterSetName = 'OAuth2')]
        [switch]
        $UsePKCE,

        [Parameter(ParameterSetName = 'OAuth2')]
        [switch]
        $OAuth2,

        [Parameter(ParameterSetName = 'ApiKey')]
        [switch]
        $ApiKey,

        [Parameter(ParameterSetName = 'ApiKey')]
        [ValidateSet('Header', 'Query', 'Cookie')]
        [string]
        $Location = 'Header',

        [Parameter(ParameterSetName = 'ApiKey')]
        [string]
        $LocationName,

        [Parameter(ParameterSetName = 'Bearer')]
        [Parameter(ParameterSetName = 'OAuth2')]
        [string[]]
        $Scope,

        [Parameter(ValueFromPipeline = $true)]
        [hashtable]
        $InnerScheme,

        [Parameter(ParameterSetName = 'Basic')]
        [Parameter(ParameterSetName = 'Form')]
        [switch]
        $AsCredential,

        [Parameter(ParameterSetName = 'Bearer')]
        [Parameter(ParameterSetName = 'ApiKey')]
        [switch]
        $AsJWT,

        [Parameter(ParameterSetName = 'Bearer')]
        [Parameter(ParameterSetName = 'ApiKey')]
        [string]
        $Secret
    )
    begin {
        $pipelineItemCount = 0
    }

    process {
        $pipelineItemCount++
    }

    end {
        if ($pipelineItemCount -gt 1) {
            throw ($PodeLocale.fnDoesNotAcceptArrayAsPipelineInputExceptionMessage -f $($MyInvocation.MyCommand.Name))
        }
        # default realm
        $_realm = 'User'

        # convert any middleware into valid hashtables
        $Middleware = @(ConvertTo-PodeMiddleware -Middleware $Middleware -PSSession $PSCmdlet.SessionState)

        # configure the auth scheme
        switch ($PSCmdlet.ParameterSetName.ToLowerInvariant()) {
            'basic' {
                return @{
                    Name          = (Protect-PodeValue -Value $HeaderTag -Default 'Basic')
                    Realm         = (Protect-PodeValue -Value $Realm -Default $_realm)
                    ScriptBlock   = @{
                        Script         = (Get-PodeAuthBasicType)
                        UsingVariables = $null
                    }
                    PostValidator = $null
                    Middleware    = $Middleware
                    InnerScheme   = $InnerScheme
                    Scheme        = 'http'
                    Arguments     = @{
                        Description  = $Description
                        HeaderTag    = (Protect-PodeValue -Value $HeaderTag -Default 'Basic')
                        Encoding     = (Protect-PodeValue -Value $Encoding -Default 'ISO-8859-1')
                        AsCredential = $AsCredential
                    }
                }
            }

            'clientcertificate' {
                return @{
                    Name          = 'Mutual'
                    Realm         = (Protect-PodeValue -Value $Realm -Default $_realm)
                    ScriptBlock   = @{
                        Script         = (Get-PodeAuthClientCertificateType)
                        UsingVariables = $null
                    }
                    PostValidator = $null
                    Middleware    = $Middleware
                    InnerScheme   = $InnerScheme
                    Scheme        = 'http'
                    Arguments     = @{}
                }
            }

            'digest' {
                return @{
                    Name          = 'Digest'
                    Realm         = (Protect-PodeValue -Value $Realm -Default $_realm)
                    ScriptBlock   = @{
                        Script         = (Get-PodeAuthDigestType)
                        UsingVariables = $null
                    }
                    PostValidator = @{
                        Script         = (Get-PodeAuthDigestPostValidator)
                        UsingVariables = $null
                    }
                    Middleware    = $Middleware
                    InnerScheme   = $InnerScheme
                    Scheme        = 'http'
                    Arguments     = @{
                        HeaderTag = (Protect-PodeValue -Value $HeaderTag -Default 'Digest')
                    }
                }
            }

            'bearer' {
                $secretBytes = $null
                if (![string]::IsNullOrWhiteSpace($Secret)) {
                    $secretBytes = [System.Text.Encoding]::UTF8.GetBytes($Secret)
                }

                return @{
                    Name          = 'Bearer'
                    Realm         = (Protect-PodeValue -Value $Realm -Default $_realm)
                    ScriptBlock   = @{
                        Script         = (Get-PodeAuthBearerType)
                        UsingVariables = $null
                    }
                    PostValidator = @{
                        Script         = (Get-PodeAuthBearerPostValidator)
                        UsingVariables = $null
                    }
                    Middleware    = $Middleware
                    Scheme        = 'http'
                    InnerScheme   = $InnerScheme
                    Arguments     = @{
                        Description = $Description
                        HeaderTag   = (Protect-PodeValue -Value $HeaderTag -Default 'Bearer')
                        Scopes      = $Scope
                        AsJWT       = $AsJWT
                        Secret      = $secretBytes
                    }
                }
            }

            'form' {
                return @{
                    Name          = 'Form'
                    Realm         = (Protect-PodeValue -Value $Realm -Default $_realm)
                    ScriptBlock   = @{
                        Script         = (Get-PodeAuthFormType)
                        UsingVariables = $null
                    }
                    PostValidator = $null
                    Middleware    = $Middleware
                    InnerScheme   = $InnerScheme
                    Scheme        = 'http'
                    Arguments     = @{
                        Description  = $Description
                        Fields       = @{
                            Username = (Protect-PodeValue -Value $UsernameField -Default 'username')
                            Password = (Protect-PodeValue -Value $PasswordField -Default 'password')
                        }
                        AsCredential = $AsCredential
                    }
                }
            }

            'oauth2' {
                if (($null -ne $InnerScheme) -and ($InnerScheme.Name -inotin @('basic', 'form'))) {
                    # OAuth2 InnerScheme can only be one of either Basic or Form authentication, but got: {0}
                    throw ($PodeLocale.oauth2InnerSchemeInvalidExceptionMessage -f $InnerScheme.Name)
                }

                if (($null -eq $InnerScheme) -and [string]::IsNullOrWhiteSpace($AuthoriseUrl)) {
                    # OAuth2 requires an Authorise URL to be supplied
                    throw ($PodeLocale.oauth2RequiresAuthorizeUrlExceptionMessage)
                }

                if ($UsePKCE -and !(Test-PodeSessionsEnabled)) {
                    # Sessions are required to use OAuth2 with PKCE
                    throw ($PodeLocale.sessionsRequiredForOAuth2WithPKCEExceptionMessage)
                }

                if (!$UsePKCE -and [string]::IsNullOrEmpty($ClientSecret)) {
                    # OAuth2 requires a Client Secret when not using PKCE
                    throw ($PodeLocale.oauth2ClientSecretRequiredExceptionMessage)
                }
                return @{
                    Name          = 'OAuth2'
                    Realm         = (Protect-PodeValue -Value $Realm -Default $_realm)
                    ScriptBlock   = @{
                        Script         = (Get-PodeAuthOAuth2Type)
                        UsingVariables = $null
                    }
                    PostValidator = $null
                    Middleware    = $Middleware
                    Scheme        = 'oauth2'
                    InnerScheme   = $InnerScheme
                    Arguments     = @{
                        Description = $Description
                        Scopes      = $Scope
                        PKCE        = @{
                            Enabled       = $UsePKCE
                            CodeChallenge = @{
                                Method = $CodeChallengeMethod
                            }
                        }
                        Client      = @{
                            ID     = $ClientId
                            Secret = $ClientSecret
                        }
                        Urls        = @{
                            Redirect  = $RedirectUrl
                            Authorise = $AuthoriseUrl
                            Token     = $TokenUrl
                            User      = @{
                                Url    = $UserUrl
                                Method = (Protect-PodeValue -Value $UserUrlMethod -Default 'Post')
                            }
                        }
                    }
                }
            }

            'apikey' {
                # set default location name
                if ([string]::IsNullOrWhiteSpace($LocationName)) {
                    $LocationName = (@{
                            Header = 'X-API-KEY'
                            Query  = 'api_key'
                            Cookie = 'X-API-KEY'
                        })[$Location]
                }

                $secretBytes = $null
                if (![string]::IsNullOrWhiteSpace($Secret)) {
                    $secretBytes = [System.Text.Encoding]::UTF8.GetBytes($Secret)
                }

                return @{
                    Name          = 'ApiKey'
                    Realm         = (Protect-PodeValue -Value $Realm -Default $_realm)
                    ScriptBlock   = @{
                        Script         = (Get-PodeAuthApiKeyType)
                        UsingVariables = $null
                    }
                    PostValidator = $null
                    Middleware    = $Middleware
                    InnerScheme   = $InnerScheme
                    Scheme        = 'apiKey'
                    Arguments     = @{
                        Description  = $Description
                        Location     = $Location
                        LocationName = $LocationName
                        AsJWT        = $AsJWT
                        Secret       = $secretBytes
                    }
                }
            }

            'custom' {
                $ScriptBlock, $usingScriptVars = Convert-PodeScopedVariables -ScriptBlock $ScriptBlock -PSSession $PSCmdlet.SessionState

                if ($null -ne $PostValidator) {
                    $PostValidator, $usingPostVars = Convert-PodeScopedVariables -ScriptBlock $PostValidator -PSSession $PSCmdlet.SessionState
                }

                return @{
                    Name          = $Name
                    Realm         = (Protect-PodeValue -Value $Realm -Default $_realm)
                    InnerScheme   = $InnerScheme
                    Scheme        = $Type.ToLowerInvariant()
                    ScriptBlock   = @{
                        Script         = $ScriptBlock
                        UsingVariables = $usingScriptVars
                    }
                    PostValidator = @{
                        Script         = $PostValidator
                        UsingVariables = $usingPostVars
                    }
                    Middleware    = $Middleware
                    Arguments     = $ArgumentList
                }
            }
        }
    }
}

<#
.SYNOPSIS
Create an OAuth2 auth scheme for Azure AD.

.DESCRIPTION
A wrapper for New-PodeAuthScheme and OAuth2, which builds an OAuth2 scheme for Azure AD.

.PARAMETER Tenant
The Directory/Tenant ID from registering a new app (default: common).

.PARAMETER ClientId
The Client ID from registering a new app.

.PARAMETER ClientSecret
The Client Secret from registering a new app (this is optional when using PKCE).

.PARAMETER RedirectUrl
An optional OAuth2 Redirect URL (default: <host>/oauth2/callback)

.PARAMETER InnerScheme
An optional authentication Scheme (from New-PodeAuthScheme) that will be called prior to this Scheme.

.PARAMETER Middleware
An array of ScriptBlocks for optional Middleware to run before the Scheme's scriptblock.

.PARAMETER UsePKCE
If supplied, OAuth2 authentication will use PKCE code verifiers.

.EXAMPLE
New-PodeAuthAzureADScheme -Tenant 123-456-678 -ClientId some_id -ClientSecret 1234.abc

.EXAMPLE
New-PodeAuthAzureADScheme -Tenant 123-456-678 -ClientId some_id -UsePKCE
#>
function New-PodeAuthAzureADScheme {
    [CmdletBinding()]
    [OutputType([hashtable])]
    param(
        [Parameter()]
        [ValidateNotNullOrEmpty()]
        [string]
        $Tenant = 'common',

        [Parameter(Mandatory = $true)]
        [string]
        $ClientId,

        [Parameter()]
        [string]
        $ClientSecret,

        [Parameter()]
        [string]
        $RedirectUrl,

        [Parameter(ValueFromPipeline = $true)]
        [hashtable]
        $InnerScheme,

        [Parameter()]
        [object[]]
        $Middleware,

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

    process {

        $pipelineItemCount++
    }

    end {
        if ($pipelineItemCount -gt 1) {
            throw ($PodeLocale.fnDoesNotAcceptArrayAsPipelineInputExceptionMessage -f $($MyInvocation.MyCommand.Name))
        }
        return New-PodeAuthScheme `
            -OAuth2 `
            -ClientId $ClientId `
            -ClientSecret $ClientSecret `
            -AuthoriseUrl "https://login.microsoftonline.com/$($Tenant)/oauth2/v2.0/authorize" `
            -TokenUrl "https://login.microsoftonline.com/$($Tenant)/oauth2/v2.0/token" `
            -UserUrl 'https://graph.microsoft.com/oidc/userinfo' `
            -RedirectUrl $RedirectUrl `
            -InnerScheme $InnerScheme `
            -Middleware $Middleware `
            -UsePKCE:$UsePKCE
    }
}

<#
.SYNOPSIS
Create an OAuth2 auth scheme for Twitter.

.DESCRIPTION
A wrapper for New-PodeAuthScheme and OAuth2, which builds an OAuth2 scheme for Twitter apps.

.PARAMETER ClientId
The Client ID from registering a new app.

.PARAMETER ClientSecret
The Client Secret from registering a new app (this is optional when using PKCE).

.PARAMETER RedirectUrl
An optional OAuth2 Redirect URL (default: <host>/oauth2/callback)

.PARAMETER Middleware
An array of ScriptBlocks for optional Middleware to run before the Scheme's scriptblock.

.PARAMETER UsePKCE
If supplied, OAuth2 authentication will use PKCE code verifiers.

.EXAMPLE
New-PodeAuthTwitterScheme -ClientId some_id -ClientSecret 1234.abc

.EXAMPLE
New-PodeAuthTwitterScheme -ClientId some_id -UsePKCE
#>
function New-PodeAuthTwitterScheme {
    [CmdletBinding()]
    [OutputType([hashtable])]
    param(
        [Parameter(Mandatory = $true)]
        [string]
        $ClientId,

        [Parameter()]
        [string]
        $ClientSecret,

        [Parameter()]
        [string]
        $RedirectUrl,

        [Parameter()]
        [object[]]
        $Middleware,

        [switch]
        $UsePKCE
    )

    return New-PodeAuthScheme `
        -OAuth2 `
        -ClientId $ClientId `
        -ClientSecret $ClientSecret `
        -AuthoriseUrl 'https://twitter.com/i/oauth2/authorize' `
        -TokenUrl 'https://api.twitter.com/2/oauth2/token' `
        -UserUrl 'https://api.twitter.com/2/users/me' `
        -UserUrlMethod 'Get' `
        -RedirectUrl $RedirectUrl `
        -Middleware $Middleware `
        -Scope 'tweet.read', 'users.read' `
        -UsePKCE:$UsePKCE
}

<#
.SYNOPSIS
Adds a custom Authentication method for verifying users.

.DESCRIPTION
Adds a custom Authentication method for verifying users.

.PARAMETER Name
A unique Name for the Authentication method.

.PARAMETER Scheme
The authentication Scheme to use for retrieving credentials (From New-PodeAuthScheme).

.PARAMETER ScriptBlock
The ScriptBlock defining logic that retrieves and verifys a user.

.PARAMETER ArgumentList
An array of arguments to supply to the Custom Authentication's ScriptBlock.

.PARAMETER FailureUrl
The URL to redirect to when authentication fails.

.PARAMETER FailureMessage
An override Message to throw when authentication fails.

.PARAMETER SuccessUrl
The URL to redirect to when authentication succeeds when logging in.

.PARAMETER Sessionless
If supplied, authenticated users will not be stored in sessions, and sessions will not be used.

.PARAMETER SuccessUseOrigin
If supplied, successful authentication from a login page will redirect back to the originating page instead of the FailureUrl.

.EXAMPLE
New-PodeAuthScheme -Form | Add-PodeAuth -Name 'Main' -ScriptBlock { /* logic */ }
#>
function Add-PodeAuth {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $true)]
        [string]
        $Name,

        [Parameter(Mandatory = $true, ValueFromPipeline = $true)]
        [hashtable]
        $Scheme,

        [Parameter(Mandatory = $true)]
        [ValidateScript({
                if (Test-PodeIsEmpty $_) {
                    # A non-empty ScriptBlock is required for the authentication method
                    throw ($PodeLocale.nonEmptyScriptBlockRequiredForAuthMethodExceptionMessage)
                }

                return $true
            })]
        [scriptblock]
        $ScriptBlock,

        [Parameter()]
        [object[]]
        $ArgumentList,

        [Parameter()]
        [string]
        $FailureUrl,

        [Parameter()]
        [string]
        $FailureMessage,

        [Parameter()]
        [string]
        $SuccessUrl,

        [switch]
        $Sessionless,

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

    process {

        $pipelineItemCount++
    }

    end {
        if ($pipelineItemCount -gt 1) {
            throw ($PodeLocale.fnDoesNotAcceptArrayAsPipelineInputExceptionMessage -f $($MyInvocation.MyCommand.Name))
        }
        # ensure the name doesn't already exist
        if (Test-PodeAuthExists -Name $Name) {
            # Authentication method already defined: {0}
            throw ($PodeLocale.authMethodAlreadyDefinedExceptionMessage -f $Name)
        }

        # ensure the Scheme contains a scriptblock
        if (Test-PodeIsEmpty $Scheme.ScriptBlock) {
            # The supplied scheme for the '{0}' authentication validator requires a valid ScriptBlock
            throw ($PodeLocale.schemeRequiresValidScriptBlockExceptionMessage -f $Name)
        }

        # if we're using sessions, ensure sessions have been setup
        if (!$Sessionless -and !(Test-PodeSessionsEnabled)) {
            # Sessions are required to use session persistent authentication
            throw ($PodeLocale.sessionsRequiredForSessionPersistentAuthExceptionMessage)
        }

        # check for scoped vars
        $ScriptBlock, $usingVars = Convert-PodeScopedVariables -ScriptBlock $ScriptBlock -PSSession $PSCmdlet.SessionState

        # add auth method to server
        $PodeContext.Server.Authentications.Methods[$Name] = @{
            Name           = $Name
            Scheme         = $Scheme
            ScriptBlock    = $ScriptBlock
            UsingVariables = $usingVars
            Arguments      = $ArgumentList
            Sessionless    = $Sessionless.IsPresent
            Failure        = @{
                Url     = $FailureUrl
                Message = $FailureMessage
            }
            Success        = @{
                Url       = $SuccessUrl
                UseOrigin = $SuccessUseOrigin.IsPresent
            }
            Cache          = @{}
            Merged         = $false
            Parent         = $null
        }

        # if the scheme is oauth2, and there's no redirect, set up a default one
        if (($Scheme.Name -ieq 'oauth2') -and ($null -eq $Scheme.InnerScheme) -and [string]::IsNullOrWhiteSpace($Scheme.Arguments.Urls.Redirect)) {
            $path = '/oauth2/callback'
            $Scheme.Arguments.Urls.Redirect = $path
            Add-PodeRoute -Method Get -Path $path -Authentication $Name
        }
    }
}

<#
.SYNOPSIS
Lets you merge multiple Authentication methods together, into a "single" Authentication method.

.DESCRIPTION
Lets you merge multiple Authentication methods together, into a "single" Authentication method.
You can specify if only One or All of the methods need to pass to allow access, and you can also
merge other merged Authentication methods for more advanced scenarios.

.PARAMETER Name
A unique Name for the Authentication method.

.PARAMETER Authentication
Multiple Autentication method Names to be merged.

.PARAMETER Valid
How many of the Authentication methods are required to be valid, One or All. (Default: One)

.PARAMETER ScriptBlock
This is mandatory, and only used, when $Valid=All. A scriptblock to merge the mutliple users/headers returned by valid authentications into 1 user/header objects.
This scriptblock will receive a hashtable of all result objects returned from Authentication methods. The key for the hashtable will be the authentication names that passed.

.PARAMETER Default
The Default Authentication method to use as a fallback for Failure URLs and other settings.

.PARAMETER MergeDefault
The Default Authentication method's User details result object to use, when $Valid=All.

.PARAMETER FailureUrl
The URL to redirect to when authentication fails.
This will be used as fallback for the merged Authentication methods if not set on them.

.PARAMETER FailureMessage
An override Message to throw when authentication fails.
This will be used as fallback for the merged Authentication methods if not set on them.

.PARAMETER SuccessUrl
The URL to redirect to when authentication succeeds when logging in.
This will be used as fallback for the merged Authentication methods if not set on them.

.PARAMETER Sessionless
If supplied, authenticated users will not be stored in sessions, and sessions will not be used.
This will be used as fallback for the merged Authentication methods if not set on them.

.PARAMETER SuccessUseOrigin
If supplied, successful authentication from a login page will redirect back to the originating page instead of the FailureUrl.
This will be used as fallback for the merged Authentication methods if not set on them.

.EXAMPLE
Merge-PodeAuth -Name MergedAuth -Authentication ApiTokenAuth, BasicAuth -Valid All -ScriptBlock { ... }

.EXAMPLE
Merge-PodeAuth -Name MergedAuth -Authentication ApiTokenAuth, BasicAuth -Valid All -MergeDefault BasicAuth

.EXAMPLE
Merge-PodeAuth -Name MergedAuth -Authentication ApiTokenAuth, BasicAuth -FailureUrl 'http://localhost:8080/login'
#>
function Merge-PodeAuth {
    [CmdletBinding(DefaultParameterSetName = 'ScriptBlock')]
    param(
        [Parameter(Mandatory = $true)]
        [string]
        $Name,

        [Parameter(Mandatory = $true)]
        [Alias('Auth')]
        [string[]]
        $Authentication,

        [Parameter()]
        [ValidateSet('One', 'All')]
        [string]
        $Valid = 'One',

        [Parameter(ParameterSetName = 'ScriptBlock')]
        [scriptblock]
        $ScriptBlock,

        [Parameter()]
        [string]
        $Default,

        [Parameter(ParameterSetName = 'MergeDefault')]
        [string]
        $MergeDefault,

        [Parameter()]
        [string]
        $FailureUrl,

        [Parameter()]
        [string]
        $FailureMessage,

        [Parameter()]
        [string]
        $SuccessUrl,

        [switch]
        $Sessionless,

        [switch]
        $SuccessUseOrigin
    )

    # ensure the name doesn't already exist
    if (Test-PodeAuthExists -Name $Name) {
        # Authentication method already defined: { 0 }
        throw ($PodeLocale.authMethodAlreadyDefinedExceptionMessage -f $Name)
    }

    # ensure all the auth methods exist
    foreach ($authName in $Authentication) {
        if (!(Test-PodeAuthExists -Name $authName)) {
            throw ($PodeLocale.authMethodNotExistForMergingExceptionMessage -f $authName) #"Authentication method does not exist for merging: $($authName)"
        }
    }

    # ensure the merge default is in the auth list
    if (![string]::IsNullOrEmpty($MergeDefault) -and ($MergeDefault -inotin @($Authentication))) {
        throw ($PodeLocale.mergeDefaultAuthNotInListExceptionMessage -f $MergeDefault) # "the MergeDefault Authentication '$($MergeDefault)' is not in the Authentication list supplied"
    }

    # ensure the default is in the auth list
    if (![string]::IsNullOrEmpty($Default) -and ($Default -inotin @($Authentication))) {
        throw ($PodeLocale.defaultAuthNotInListExceptionMessage -f $Default) # "the Default Authentication '$($Default)' is not in the Authentication list supplied"
    }

    # set default
    if ([string]::IsNullOrEmpty($Default)) {
        $Default = $Authentication[0]
    }

    # get auth for default
    $tmpAuth = $PodeContext.Server.Authentications.Methods[$Default]

    # check sessionless from default
    if (!$Sessionless) {
        $Sessionless = $tmpAuth.Sessionless
    }

    # if we're using sessions, ensure sessions have been setup
    if (!$Sessionless -and !(Test-PodeSessionsEnabled)) {
        # Sessions are required to use session persistent authentication
        throw ($PodeLocale.sessionsRequiredForSessionPersistentAuthExceptionMessage)
    }

    # check failure url from default
    if ([string]::IsNullOrEmpty($FailureUrl)) {
        $FailureUrl = $tmpAuth.Failure.Url
    }

    # check failure message from default
    if ([string]::IsNullOrEmpty($FailureMessage)) {
        $FailureMessage = $tmpAuth.Failure.Message
    }

    # check success url from default
    if ([string]::IsNullOrEmpty($SuccessUrl)) {
        $SuccessUrl = $tmpAuth.Success.Url
    }

    # check success use origin from default
    if (!$SuccessUseOrigin) {
        $SuccessUseOrigin = $tmpAuth.Success.UseOrigin
    }

    # deal with using vars in scriptblock
    if (($Valid -ieq 'all') -and [string]::IsNullOrEmpty($MergeDefault)) {
        if ($null -eq $ScriptBlock) {
            # A Scriptblock for merging multiple authenticated users into 1 object is required When Valid is All
            throw ($PodeLocale.scriptBlockRequiredForMergingUsersExceptionMessage)
        }

        $ScriptBlock, $usingVars = Convert-PodeScopedVariables -ScriptBlock $ScriptBlock -PSSession $PSCmdlet.SessionState
    }
    else {
        if ($null -ne $ScriptBlock) {
            Write-Warning -Message 'The Scriptblock for merged authentications, when Valid=One, will be ignored'
        }
    }

    # set parent auth
    foreach ($authName in $Authentication) {
        $PodeContext.Server.Authentications.Methods[$authName].Parent = $Name
    }

    # add auth method to server
    $PodeContext.Server.Authentications.Methods[$Name] = @{
        Name            = $Name
        Authentications = @($Authentication)
        PassOne         = ($Valid -ieq 'one')
        ScriptBlock     = @{
            Script         = $ScriptBlock
            UsingVariables = $usingVars
        }
        Default         = $Default
        MergeDefault    = $MergeDefault
        Sessionless     = $Sessionless.IsPresent
        Failure         = @{
            Url     = $FailureUrl
            Message = $FailureMessage
        }
        Success         = @{
            Url       = $SuccessUrl
            UseOrigin = $SuccessUseOrigin.IsPresent
        }
        Cache           = @{}
        Merged          = $true
        Parent          = $null
    }
}

<#
.SYNOPSIS
Gets an Authentication method.

.DESCRIPTION
Gets an Authentication method.

.PARAMETER Name
The Name of an Authentication method.

.EXAMPLE
Get-PodeAuth -Name 'Main'
#>
function Get-PodeAuth {
    [CmdletBinding()]
    [OutputType([hashtable])]
    param(
        [Parameter(Mandatory = $true)]
        [string]
        $Name
    )

    # ensure the name exists
    if (!(Test-PodeAuthExists -Name $Name)) {
        throw ($PodeLocale.authenticationMethodDoesNotExistExceptionMessage -f $Name) # "Authentication method not defined: $($Name)"
    }

    # get auth method
    return $PodeContext.Server.Authentications.Methods[$Name]
}

<#
.SYNOPSIS
Test if an Authentication method exists.

.DESCRIPTION
Test if an Authentication method exists.

.PARAMETER Name
The Name of the Authentication method.

.EXAMPLE
if (Test-PodeAuthExists -Name BasicAuth) { ... }
#>
function Test-PodeAuthExists {
    [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseSingularNouns', '')]
    [CmdletBinding()]
    [OutputType([bool])]
    param(
        [Parameter(Mandatory = $true)]
        [string]
        $Name
    )

    return $PodeContext.Server.Authentications.Methods.ContainsKey($Name)
}

<#
.SYNOPSIS
Test and invoke an Authentication method to verify a user.

.DESCRIPTION
Test and invoke an Authentication method to verify a user. This will verify a user's credentials on the request.
When testing OAuth2 methods, the first attempt will trigger a redirect to the provider and $false will be returned.

.PARAMETER Name
The Name of the Authentication method.

.PARAMETER IgnoreSession
If supplied, authentication will be re-verified on each call even if a valid session exists on the request.

.EXAMPLE
if (Test-PodeAuth -Name 'BasicAuth') { ... }

.EXAMPLE
if (Test-PodeAuth -Name 'FormAuth' -IgnoreSession) { ... }
#>
function Test-PodeAuth {
    [CmdletBinding()]
    [OutputType([boolean])]
    param(
        [Parameter(Mandatory = $true)]
        [string]
        $Name,

        [switch]
        $IgnoreSession
    )

    # if the session already has a user/isAuth'd, then skip auth - or allow anon
    if (!$IgnoreSession -and (Test-PodeSessionsInUse) -and (Test-PodeAuthUser)) {
        return $true
    }

    try {
        $result = Invoke-PodeAuthValidation -Name $Name
    }
    catch {
        $_ | Write-PodeErrorLog
        return $false
    }

    # did the auth force a redirect?
    if ($result.Redirected) {
        return $false
    }

    # if auth failed, set appropriate response headers/redirects
    if (!$result.Success) {
        return $false
    }

    # successful auth
    return $true
}

<#
.SYNOPSIS
Adds the inbuilt Windows AD Authentication method for verifying users.

.DESCRIPTION
Adds the inbuilt Windows AD Authentication method for verifying users.

.PARAMETER Name
A unique Name for the Authentication method.

.PARAMETER Scheme
The Scheme to use for retrieving credentials (From New-PodeAuthScheme).

.PARAMETER Fqdn
A custom FQDN for the DNS of the AD you wish to authenticate against. (Alias: Server)

.PARAMETER Domain
(Unix Only) A custom NetBIOS domain name that is prepended onto usernames that are missing it (<Domain>\<Username>).

.PARAMETER SearchBase
(Unix Only) An optional searchbase to refine the LDAP query. This should be the full distinguished name.

.PARAMETER Groups
An array of Group names to only allow access.

.PARAMETER Users
An array of Usernames to only allow access.

.PARAMETER FailureUrl
The URL to redirect to when authentication fails.

.PARAMETER FailureMessage
An override Message to throw when authentication fails.

.PARAMETER SuccessUrl
The URL to redirect to when authentication succeeds when logging in.

.PARAMETER ScriptBlock
Optional ScriptBlock that is passed the found user object for further validation.

.PARAMETER Sessionless
If supplied, authenticated users will not be stored in sessions, and sessions will not be used.

.PARAMETER NoGroups
If supplied, groups will not be retrieved for the user in AD.

.PARAMETER DirectGroups
If supplied, only a user's direct groups will be retrieved rather than all groups recursively.

.PARAMETER OpenLDAP
If supplied, and on Windows, OpenLDAP will be used instead (this is the default for Linux/MacOS).

.PARAMETER ADModule
If supplied, and on Windows, the ActiveDirectory module will be used instead.

.PARAMETER SuccessUseOrigin
If supplied, successful authentication from a login page will redirect back to the originating page instead of the FailureUrl.

.PARAMETER KeepCredential
If suplied pode will save the AD credential as a PSCredential object in $WebEvent.Auth.User.Credential

.EXAMPLE
New-PodeAuthScheme -Form | Add-PodeAuthWindowsAd -Name 'WinAuth'

.EXAMPLE
New-PodeAuthScheme -Basic | Add-PodeAuthWindowsAd -Name 'WinAuth' -Groups @('Developers')

.EXAMPLE
New-PodeAuthScheme -Form | Add-PodeAuthWindowsAd -Name 'WinAuth' -NoGroups

.EXAMPLE
New-PodeAuthScheme -Form | Add-PodeAuthWindowsAd -Name 'UnixAuth' -Server 'testdomain.company.com' -Domain 'testdomain'
#>
function Add-PodeAuthWindowsAd {
    [CmdletBinding(DefaultParameterSetName = 'Groups')]
    param(
        [Parameter(Mandatory = $true)]
        [string]
        $Name,

        [Parameter(Mandatory = $true, ValueFromPipeline = $true)]
        [hashtable]
        $Scheme,

        [Parameter()]
        [Alias('Server')]
        [string]
        $Fqdn,

        [Parameter()]
        [string]
        $Domain,

        [Parameter()]
        [string]
        $SearchBase,

        [Parameter(ParameterSetName = 'Groups')]
        [string[]]
        $Groups,

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

        [Parameter()]
        [string]
        $FailureUrl,

        [Parameter()]
        [string]
        $FailureMessage,

        [Parameter()]
        [string]
        $SuccessUrl,

        [Parameter()]
        [scriptblock]
        $ScriptBlock,

        [switch]
        $Sessionless,

        [Parameter(ParameterSetName = 'NoGroups')]
        [switch]
        $NoGroups,

        [Parameter(ParameterSetName = 'Groups')]
        [switch]
        $DirectGroups,

        [switch]
        $OpenLDAP,

        [switch]
        $ADModule,

        [switch]
        $SuccessUseOrigin,

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

    process {

        $pipelineItemCount++
    }

    end {
        if ($pipelineItemCount -gt 1) {
            throw ($PodeLocale.fnDoesNotAcceptArrayAsPipelineInputExceptionMessage -f $($MyInvocation.MyCommand.Name))
        }
        # ensure the name doesn't already exist
        if (Test-PodeAuthExists -Name $Name) {
            # Authentication method already defined: {0}
            throw ($PodeLocale.authMethodAlreadyDefinedExceptionMessage -f $Name)
        }

        # ensure the Scheme contains a scriptblock
        if (Test-PodeIsEmpty $Scheme.ScriptBlock) {
            # The supplied Scheme for the '$($Name)' Windows AD authentication validator requires a valid ScriptBlock
            throw ($PodeLocale.schemeRequiresValidScriptBlockExceptionMessage -f $Name)
        }

        # if we're using sessions, ensure sessions have been setup
        if (!$Sessionless -and !(Test-PodeSessionsEnabled)) {
            # Sessions are required to use session persistent authentication
            throw ($PodeLocale.sessionsRequiredForSessionPersistentAuthExceptionMessage)
        }

        # if AD module set, ensure we're on windows and the module is available, then import/export it
        if ($ADModule) {
            Import-PodeAuthADModule
        }

        # set server name if not passed
        if ([string]::IsNullOrWhiteSpace($Fqdn)) {
            $Fqdn = Get-PodeAuthDomainName

            if ([string]::IsNullOrWhiteSpace($Fqdn)) {
                # No domain server name has been supplied for Windows AD authentication
                throw ($PodeLocale.noDomainServerNameForWindowsAdAuthExceptionMessage)
            }
        }

        # set the domain if not passed
        if ([string]::IsNullOrWhiteSpace($Domain)) {
            $Domain = ($Fqdn -split '\.')[0]
        }

        # if we have a scriptblock, deal with using vars
        if ($null -ne $ScriptBlock) {
            $ScriptBlock, $usingVars = Convert-PodeScopedVariables -ScriptBlock $ScriptBlock -PSSession $PSCmdlet.SessionState
        }

        # add Windows AD auth method to server
        $PodeContext.Server.Authentications.Methods[$Name] = @{
            Name        = $Name
            Scheme      = $Scheme
            ScriptBlock = (Get-PodeAuthWindowsADMethod)
            Arguments   = @{
                Server         = $Fqdn
                Domain         = $Domain
                SearchBase     = $SearchBase
                Users          = $Users
                Groups         = $Groups
                NoGroups       = $NoGroups
                DirectGroups   = $DirectGroups
                KeepCredential = $KeepCredential
                Provider       = (Get-PodeAuthADProvider -OpenLDAP:$OpenLDAP -ADModule:$ADModule)
                ScriptBlock    = @{
                    Script         = $ScriptBlock
                    UsingVariables = $usingVars
                }
            }
            Sessionless = $Sessionless
            Failure     = @{
                Url     = $FailureUrl
                Message = $FailureMessage
            }
            Success     = @{
                Url       = $SuccessUrl
                UseOrigin = $SuccessUseOrigin
            }
            Cache       = @{}
            Merged      = $false
            Parent      = $null
        }
    }
}

<#
.SYNOPSIS
Adds the inbuilt Session Authentication method for verifying an authenticated session is present on Requests.

.DESCRIPTION
Adds the inbuilt Session Authentication method for verifying an authenticated session is present on Requests.

.PARAMETER Name
A unique Name for the Authentication method.

.PARAMETER FailureUrl
The URL to redirect to when authentication fails.

.PARAMETER FailureMessage
An override Message to throw when authentication fails.

.PARAMETER SuccessUrl
The URL to redirect to when authentication succeeds when logging in.

.PARAMETER ScriptBlock
Optional ScriptBlock that is passed the found user object for further validation.

.PARAMETER Middleware
An array of ScriptBlocks for optional Middleware to run before the Scheme's scriptblock.

.PARAMETER SuccessUseOrigin
If supplied, successful authentication from a login page will redirect back to the originating page instead of the FailureUrl.

.EXAMPLE
Add-PodeAuthSession -Name 'SessionAuth' -FailureUrl '/login'
#>
function Add-PodeAuthSession {
    [CmdletBinding(DefaultParameterSetName = 'Groups')]
    param(
        [Parameter(Mandatory = $true)]
        [string]
        $Name,

        [Parameter()]
        [string]
        $FailureUrl,

        [Parameter()]
        [string]
        $FailureMessage,

        [Parameter()]
        [string]
        $SuccessUrl,

        [Parameter()]
        [scriptblock]
        $ScriptBlock,

        [Parameter()]
        [object[]]
        $Middleware,

        [switch]
        $SuccessUseOrigin
    )

    # if sessions haven't been setup, error
    if (!(Test-PodeSessionsEnabled)) {
        # Sessions have not been configured
        throw ($PodeLocale.sessionsNotConfiguredExceptionMessage)
    }

    # ensure the name doesn't already exist
    if (Test-PodeAuthExists -Name $Name) {
        # Authentication method already defined: { 0 }
        throw ($PodeLocale.authMethodAlreadyDefinedExceptionMessage -f $Name)
    }

    # if we have a scriptblock, deal with using vars
    if ($null -ne $ScriptBlock) {
        $ScriptBlock, $usingVars = Convert-PodeScopedVariables -ScriptBlock $ScriptBlock -PSSession $PSCmdlet.SessionState
    }

    # create the auth scheme for getting the session
    $scheme = New-PodeAuthScheme -Custom -Middleware $Middleware -ScriptBlock {
        param($options)

        # 401 if sessions not used
        if (!(Test-PodeSessionsInUse)) {
            Revoke-PodeSession
            return @{
                Message = 'Sessions are not being used'
                Code    = 401
            }
        }

        # 401 if no authenticated user
        if (!(Test-PodeAuthUser)) {
            Revoke-PodeSession
            return @{
                Message = 'Session not authenticated'
                Code    = 401
            }
        }

        # return user
        return @($WebEvent.Session.Data.Auth)
    }

    # add a custom auth method to return user back
    $method = {
        param($user, $options)
        $result = @{ User = $user }

        # call additional scriptblock if supplied
        if ($null -ne $options.ScriptBlock.Script) {
            $result = Invoke-PodeAuthInbuiltScriptBlock -User $result.User -ScriptBlock $options.ScriptBlock.Script -UsingVariables $options.ScriptBlock.UsingVariables
        }

        # return user back
        return $result
    }

    $scheme | Add-PodeAuth `
        -Name $Name `
        -ScriptBlock $method `
        -FailureUrl $FailureUrl `
        -FailureMessage $FailureMessage `
        -SuccessUrl $SuccessUrl `
        -SuccessUseOrigin:$SuccessUseOrigin `
        -ArgumentList @{
        ScriptBlock = @{
            Script         = $ScriptBlock
            UsingVariables = $usingVars
        }
    }
}

<#
.SYNOPSIS
Remove a specific Authentication method.

.DESCRIPTION
Remove a specific Authentication method.

.PARAMETER Name
The Name of the Authentication method.

.EXAMPLE
Remove-PodeAuth -Name 'Login'
#>
function Remove-PodeAuth {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $true, ValueFromPipeline = $true)]
        [string]
        $Name
    )
    process {
        $null = $PodeContext.Server.Authentications.Methods.Remove($Name)
    }
}

<#
.SYNOPSIS
Clear all defined Authentication methods.

.DESCRIPTION
Clear all defined Authentication methods.

.EXAMPLE
Clear-PodeAuth
#>
function Clear-PodeAuth {
    [CmdletBinding()]
    param()

    $PodeContext.Server.Authentications.Methods.Clear()
}

<#
.SYNOPSIS
Adds an authentication method as global middleware.

.DESCRIPTION
Adds an authentication method as global middleware.

.PARAMETER Name
The Name of the Middleware.

.PARAMETER Authentication
The Name of the Authentication method to use.

.PARAMETER Route
A Route path for which Routes this Middleware should only be invoked against.

.PARAMETER OADefinitionTag
An array of string representing the unique tag for the API specification.
This tag helps in distinguishing between different versions or types of API specifications within the application.
Use this tag to reference the specific API documentation, schema, or version that your function interacts with.

.EXAMPLE
Add-PodeAuthMiddleware -Name 'GlobalAuth' -Authentication AuthName

.EXAMPLE
Add-PodeAuthMiddleware -Name 'GlobalAuth' -Authentication AuthName -Route '/api/*'
#>
function Add-PodeAuthMiddleware {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $true)]
        [string]
        $Name,

        [Parameter(Mandatory = $true)]
        [Alias('Auth')]
        [string]
        $Authentication,

        [Parameter()]
        [string]
        $Route,

        [string[]]
        $OADefinitionTag
    )

    $DefinitionTag = Test-PodeOADefinitionTag -Tag $OADefinitionTag

    if (!(Test-PodeAuthExists -Name $Authentication)) {
        throw ($PodeLocale.authenticationMethodDoesNotExistExceptionMessage -f $Authentication) # "Authentication method does not exist: $($Authentication)"
    }

    Get-PodeAuthMiddlewareScript |
        New-PodeMiddleware -ArgumentList @{ Name = $Authentication } |
        Add-PodeMiddleware -Name $Name -Route $Route

    Set-PodeOAGlobalAuth -DefinitionTag $DefinitionTag -Name $Authentication -Route $Route
}

<#
.SYNOPSIS
Adds the inbuilt IIS Authentication method for verifying users passed to Pode from IIS.

.DESCRIPTION
Adds the inbuilt IIS Authentication method for verifying users passed to Pode from IIS.

.PARAMETER Name
A unique Name for the Authentication method.

.PARAMETER Groups
An array of Group names to only allow access.

.PARAMETER Users
An array of Usernames to only allow access.

.PARAMETER FailureUrl
The URL to redirect to when authentication fails.

.PARAMETER FailureMessage
An override Message to throw when authentication fails.

.PARAMETER SuccessUrl
The URL to redirect to when authentication succeeds when logging in.

.PARAMETER ScriptBlock
Optional ScriptBlock that is passed the found user object for further validation.

.PARAMETER Middleware
An array of ScriptBlocks for optional Middleware to run before the Scheme's scriptblock.

.PARAMETER Sessionless
If supplied, authenticated users will not be stored in sessions, and sessions will not be used.

.PARAMETER NoGroups
If supplied, groups will not be retrieved for the user in AD.

.PARAMETER DirectGroups
If supplied, only a user's direct groups will be retrieved rather than all groups recursively.

.PARAMETER ADModule
If supplied, and on Windows, the ActiveDirectory module will be used instead.

.PARAMETER NoLocalCheck
If supplied, Pode will not at attempt to retrieve local User/Group information for the authenticated user.

.PARAMETER SuccessUseOrigin
If supplied, successful authentication from a login page will redirect back to the originating page instead of the FailureUrl.

.EXAMPLE
Add-PodeAuthIIS -Name 'IISAuth'

.EXAMPLE
Add-PodeAuthIIS -Name 'IISAuth' -Groups @('Developers')

.EXAMPLE
Add-PodeAuthIIS -Name 'IISAuth' -NoGroups
#>
function Add-PodeAuthIIS {
    [CmdletBinding(DefaultParameterSetName = 'Groups')]
    param(
        [Parameter(Mandatory = $true)]
        [string]
        $Name,

        [Parameter(ParameterSetName = 'Groups')]
        [string[]]
        $Groups,

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

        [Parameter()]
        [string]
        $FailureUrl,

        [Parameter()]
        [string]
        $FailureMessage,

        [Parameter()]
        [string]
        $SuccessUrl,

        [Parameter()]
        [scriptblock]
        $ScriptBlock,

        [Parameter()]
        [object[]]
        $Middleware,

        [switch]
        $Sessionless,

        [Parameter(ParameterSetName = 'NoGroups')]
        [switch]
        $NoGroups,

        [Parameter(ParameterSetName = 'Groups')]
        [switch]
        $DirectGroups,

        [switch]
        $ADModule,

        [switch]
        $NoLocalCheck,

        [switch]
        $SuccessUseOrigin
    )

    # ensure we're on Windows!
    if (!(Test-PodeIsWindows)) {
        # IIS Authentication support is for Windows only
        throw ($PodeLocale.iisAuthSupportIsForWindowsOnlyExceptionMessage)
    }

    # ensure the name doesn't already exist
    if (Test-PodeAuthExists -Name $Name) {
        # Authentication method already defined: {0}
        throw ($PodeLocale.authMethodAlreadyDefinedExceptionMessage -f $Name)
    }

    # if AD module set, ensure we're on windows and the module is available, then import/export it
    if ($ADModule) {
        Import-PodeAuthADModule
    }

    # if we have a scriptblock, deal with using vars
    if ($null -ne $ScriptBlock) {
        $ScriptBlock, $usingVars = Convert-PodeScopedVariables -ScriptBlock $ScriptBlock -PSSession $PSCmdlet.SessionState
    }

    # create the auth scheme for getting the token header
    $scheme = New-PodeAuthScheme -Custom -Middleware $Middleware -ScriptBlock {
        param($options)

        $header = 'MS-ASPNETCORE-WINAUTHTOKEN'

        # fail if no header
        if (!(Test-PodeHeader -Name $header)) {
            return @{
                Message = "No $($header) header found"
                Code    = 401
            }
        }

        # return the header for validation
        $token = Get-PodeHeader -Name $header
        return @($token)
    }

    # add a custom auth method to validate the user
    $method = Get-PodeAuthWindowsADIISMethod

    $scheme | Add-PodeAuth `
        -Name $Name `
        -ScriptBlock $method `
        -FailureUrl $FailureUrl `
        -FailureMessage $FailureMessage `
        -SuccessUrl $SuccessUrl `
        -Sessionless:$Sessionless `
        -SuccessUseOrigin:$SuccessUseOrigin `
        -ArgumentList @{
        Users        = $Users
        Groups       = $Groups
        NoGroups     = $NoGroups
        DirectGroups = $DirectGroups
        Provider     = (Get-PodeAuthADProvider -ADModule:$ADModule)
        NoLocalCheck = $NoLocalCheck
        ScriptBlock  = @{
            Script         = $ScriptBlock
            UsingVariables = $usingVars
        }
    }
}

<#
.SYNOPSIS
Adds the inbuilt User File Authentication method for verifying users.

.DESCRIPTION
Adds the inbuilt User File Authentication method for verifying users.

.PARAMETER Name
A unique Name for the Authentication method.

.PARAMETER Scheme
The Scheme to use for retrieving credentials (From New-PodeAuthScheme).

.PARAMETER FilePath
A path to a users JSON file (Default: ./users.json)

.PARAMETER Groups
An array of Group names to only allow access.

.PARAMETER Users
An array of Usernames to only allow access.

.PARAMETER HmacSecret
An optional secret if the passwords are HMAC SHA256 hashed.

.PARAMETER FailureUrl
The URL to redirect to when authentication fails.

.PARAMETER FailureMessage
An override Message to throw when authentication fails.

.PARAMETER SuccessUrl
The URL to redirect to when authentication succeeds when logging in.

.PARAMETER ScriptBlock
Optional ScriptBlock that is passed the found user object for further validation.

.PARAMETER Sessionless
If supplied, authenticated users will not be stored in sessions, and sessions will not be used.

.PARAMETER SuccessUseOrigin
If supplied, successful authentication from a login page will redirect back to the originating page instead of the FailureUrl.

.EXAMPLE
New-PodeAuthScheme -Form | Add-PodeAuthUserFile -Name 'Login'

.EXAMPLE
New-PodeAuthScheme -Form | Add-PodeAuthUserFile -Name 'Login' -FilePath './custom/path/users.json'
#>
function Add-PodeAuthUserFile {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $true)]
        [string]
        $Name,

        [Parameter(Mandatory = $true, ValueFromPipeline = $true)]
        [hashtable]
        $Scheme,

        [Parameter()]
        [string]
        $FilePath,

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

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

        [Parameter(ParameterSetName = 'Hmac')]
        [string]
        $HmacSecret,

        [Parameter()]
        [string]
        $FailureUrl,

        [Parameter()]
        [string]
        $FailureMessage,

        [Parameter()]
        [string]
        $SuccessUrl,

        [Parameter()]
        [scriptblock]
        $ScriptBlock,

        [switch]
        $Sessionless,

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

    process {

        $pipelineItemCount++
    }

    end {
        if ($pipelineItemCount -gt 1) {
            throw ($PodeLocale.fnDoesNotAcceptArrayAsPipelineInputExceptionMessage -f $($MyInvocation.MyCommand.Name))
        }
        # ensure the name doesn't already exist
        if (Test-PodeAuthExists -Name $Name) {
            # Authentication method already defined: {0}
            throw ($PodeLocale.authMethodAlreadyDefinedExceptionMessage -f $Name)
        }

        # ensure the Scheme contains a scriptblock
        if (Test-PodeIsEmpty $Scheme.ScriptBlock) {
            # The supplied scheme for the '{0}' authentication validator requires a valid ScriptBlock.
            throw ($PodeLocale.schemeRequiresValidScriptBlockExceptionMessage -f $Name)
        }

        # if we're using sessions, ensure sessions have been setup
        if (!$Sessionless -and !(Test-PodeSessionsEnabled)) {
            # Sessions are required to use session persistent authentication
            throw ($PodeLocale.sessionsRequiredForSessionPersistentAuthExceptionMessage)
        }

        # set the file path if not passed
        if ([string]::IsNullOrWhiteSpace($FilePath)) {
            $FilePath = Join-PodeServerRoot -Folder '.' -FilePath 'users.json'
        }
        else {
            $FilePath = Get-PodeRelativePath -Path $FilePath -JoinRoot -Resolve
        }

        # ensure the user file exists
        if (!(Test-PodePath -Path $FilePath -NoStatus -FailOnDirectory)) {
            # The user file does not exist: {0}
            throw ($PodeLocale.userFileDoesNotExistExceptionMessage -f $FilePath)
        }

        # if we have a scriptblock, deal with using vars
        if ($null -ne $ScriptBlock) {
            $ScriptBlock, $usingVars = Convert-PodeScopedVariables -ScriptBlock $ScriptBlock -PSSession $PSCmdlet.SessionState
        }

        # add Windows AD auth method to server
        $PodeContext.Server.Authentications.Methods[$Name] = @{
            Name        = $Name
            Scheme      = $Scheme
            ScriptBlock = (Get-PodeAuthUserFileMethod)
            Arguments   = @{
                FilePath    = $FilePath
                Users       = $Users
                Groups      = $Groups
                HmacSecret  = $HmacSecret
                ScriptBlock = @{
                    Script         = $ScriptBlock
                    UsingVariables = $usingVars
                }
            }
            Sessionless = $Sessionless
            Failure     = @{
                Url     = $FailureUrl
                Message = $FailureMessage
            }
            Success     = @{
                Url       = $SuccessUrl
                UseOrigin = $SuccessUseOrigin
            }
            Cache       = @{}
            Merged      = $false
            Parent      = $null
        }
    }
}

<#
.SYNOPSIS
Adds the inbuilt Windows Local User Authentication method for verifying users.

.DESCRIPTION
Adds the inbuilt Windows Local User Authentication method for verifying users.

.PARAMETER Name
A unique Name for the Authentication method.

.PARAMETER Scheme
The Scheme to use for retrieving credentials (From New-PodeAuthScheme).

.PARAMETER Groups
An array of Group names to only allow access.

.PARAMETER Users
An array of Usernames to only allow access.

.PARAMETER FailureUrl
The URL to redirect to when authentication fails.

.PARAMETER FailureMessage
An override Message to throw when authentication fails.

.PARAMETER SuccessUrl
The URL to redirect to when authentication succeeds when logging in.

.PARAMETER ScriptBlock
Optional ScriptBlock that is passed the found user object for further validation.

.PARAMETER Sessionless
If supplied, authenticated users will not be stored in sessions, and sessions will not be used.

.PARAMETER NoGroups
If supplied, groups will not be retrieved for the user.

.PARAMETER SuccessUseOrigin
If supplied, successful authentication from a login page will redirect back to the originating page instead of the FailureUrl.

.EXAMPLE
New-PodeAuthScheme -Form | Add-PodeAuthWindowsLocal -Name 'WinAuth'

.EXAMPLE
New-PodeAuthScheme -Basic | Add-PodeAuthWindowsLocal -Name 'WinAuth' -Groups @('Developers')

.EXAMPLE
New-PodeAuthScheme -Form | Add-PodeAuthWindowsLocal -Name 'WinAuth' -NoGroups
#>
function Add-PodeAuthWindowsLocal {
    [CmdletBinding(DefaultParameterSetName = 'Groups')]
    param(
        [Parameter(Mandatory = $true)]
        [string]
        $Name,

        [Parameter(Mandatory = $true, ValueFromPipeline = $true)]
        [hashtable]
        $Scheme,

        [Parameter(ParameterSetName = 'Groups')]
        [string[]]
        $Groups,

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

        [Parameter()]
        [string]
        $FailureUrl,

        [Parameter()]
        [string]
        $FailureMessage,

        [Parameter()]
        [string]
        $SuccessUrl,

        [Parameter()]
        [scriptblock]
        $ScriptBlock,

        [switch]
        $Sessionless,

        [Parameter(ParameterSetName = 'NoGroups')]
        [switch]
        $NoGroups,

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

    process {

        $pipelineItemCount++
    }

    end {
        if ($pipelineItemCount -gt 1) {
            throw ($PodeLocale.fnDoesNotAcceptArrayAsPipelineInputExceptionMessage -f $($MyInvocation.MyCommand.Name))
        }
        # ensure we're on Windows!
        if (!(Test-PodeIsWindows)) {
            # Windows Local Authentication support is for Windows only
            throw ($PodeLocale.windowsLocalAuthSupportIsForWindowsOnlyExceptionMessage)
        }

        # ensure the name doesn't already exist
        if (Test-PodeAuthExists -Name $Name) {
            # Authentication method already defined: {0}
            throw ($PodeLocale.authMethodAlreadyDefinedExceptionMessage -f $Name)
        }

        # ensure the Scheme contains a scriptblock
        if (Test-PodeIsEmpty $Scheme.ScriptBlock) {
            # The supplied scheme for the '{0}' authentication validator requires a valid ScriptBlock.
            throw ($PodeLocale.schemeRequiresValidScriptBlockExceptionMessage -f $Name)
        }

        # if we're using sessions, ensure sessions have been setup
        if (!$Sessionless -and !(Test-PodeSessionsEnabled)) {
            # Sessions are required to use session persistent authentication
            throw ($PodeLocale.sessionsRequiredForSessionPersistentAuthExceptionMessage)
        }

        # if we have a scriptblock, deal with using vars
        if ($null -ne $ScriptBlock) {
            $ScriptBlock, $usingVars = Convert-PodeScopedVariables -ScriptBlock $ScriptBlock -PSSession $PSCmdlet.SessionState
        }

        # add Windows Local auth method to server
        $PodeContext.Server.Authentications.Methods[$Name] = @{
            Name        = $Name
            Scheme      = $Scheme
            ScriptBlock = (Get-PodeAuthWindowsLocalMethod)
            Arguments   = @{
                Users       = $Users
                Groups      = $Groups
                NoGroups    = $NoGroups
                ScriptBlock = @{
                    Script         = $ScriptBlock
                    UsingVariables = $usingVars
                }
            }
            Sessionless = $Sessionless
            Failure     = @{
                Url     = $FailureUrl
                Message = $FailureMessage
            }
            Success     = @{
                Url       = $SuccessUrl
                UseOrigin = $SuccessUseOrigin
            }
            Cache       = @{}
            Merged      = $false
            Parent      = $null
        }
    }
}

<#
.SYNOPSIS
Convert a Header/Payload into a JWT.

.DESCRIPTION
Convert a Header/Payload hashtable into a JWT, with the option to sign it.

.PARAMETER Header
A Hashtable containing the Header information for the JWT.

.PARAMETER Payload
A Hashtable containing the Payload information for the JWT.

.PARAMETER Secret
An Optional Secret for signing the JWT, should be a string or byte[]. This is mandatory if the Header algorithm isn't "none".

.EXAMPLE
ConvertTo-PodeJwt -Header @{ alg = 'none' } -Payload @{ sub = '123'; name = 'John' }

.EXAMPLE
ConvertTo-PodeJwt -Header @{ alg = 'hs256' } -Payload @{ sub = '123'; name = 'John' } -Secret 'abc'
#>
function ConvertTo-PodeJwt {
    [CmdletBinding()]
    [OutputType([string])]
    param(
        [Parameter(Mandatory = $true)]
        [hashtable]
        $Header,

        [Parameter(Mandatory = $true)]
        [hashtable]
        $Payload,

        [Parameter()]
        $Secret = $null
    )

    # validate header
    if ([string]::IsNullOrWhiteSpace($Header.alg)) {
        # No algorithm supplied in JWT Header
        throw ($PodeLocale.noAlgorithmInJwtHeaderExceptionMessage)
    }

    # convert the header
    $header64 = ConvertTo-PodeBase64UrlValue -Value ($Header | ConvertTo-Json -Compress)

    # convert the payload
    $payload64 = ConvertTo-PodeBase64UrlValue -Value ($Payload | ConvertTo-Json -Compress)

    # combine
    $jwt = "$($header64).$($payload64)"

    # convert secret to bytes
    if (($null -ne $Secret) -and ($Secret -isnot [byte[]])) {
        $Secret = [System.Text.Encoding]::UTF8.GetBytes([string]$Secret)
    }

    # make the signature
    $sig = New-PodeJwtSignature -Algorithm $Header.alg -Token $jwt -SecretBytes $Secret

    # add the signature and return
    $jwt += ".$($sig)"
    return $jwt
}

<#
.SYNOPSIS
Convert and return the payload of a JWT token.

.DESCRIPTION
Convert and return the payload of a JWT token, verifying the signature by default with support to ignore the signature.

.PARAMETER Token
The JWT token.

.PARAMETER Secret
The Secret, as a string or byte[], to verify the token's signature.

.PARAMETER IgnoreSignature
Skip signature verification, and return the decoded payload.

.EXAMPLE
ConvertFrom-PodeJwt -Token "eyJ0eXAiOiJKV1QiLCJhbGciOiJoczI1NiJ9.eyJleHAiOjE2MjI1NTMyMTQsIm5hbWUiOiJKb2huIERvZSIsInN1YiI6IjEyMyJ9.LP-O8OKwix91a-SZwVK35gEClLZQmsORbW0un2Z4RkY"
#>
function ConvertFrom-PodeJwt {
    [CmdletBinding(DefaultParameterSetName = 'Secret')]
    [OutputType([pscustomobject])]
    param(
        [Parameter(Mandatory = $true)]
        [string]
        $Token,

        [Parameter(ParameterSetName = 'Signed')]
        $Secret = $null,

        [Parameter(ParameterSetName = 'Ignore')]
        [switch]
        $IgnoreSignature
    )

    # get the parts
    $parts = ($Token -isplit '\.')

    # check number of parts (should be 3)
    if ($parts.Length -ne 3) {
        # Invalid JWT supplied
        throw ($PodeLocale.invalidJwtSuppliedExceptionMessage)
    }

    # convert to header
    $header = ConvertFrom-PodeJwtBase64Value -Value $parts[0]
    if ([string]::IsNullOrWhiteSpace($header.alg)) {
        # Invalid JWT header algorithm supplied
        throw ($PodeLocale.invalidJwtHeaderAlgorithmSuppliedExceptionMessage)
    }

    # convert to payload
    $payload = ConvertFrom-PodeJwtBase64Value -Value $parts[1]

    # get signature
    if ($IgnoreSignature) {
        return $payload
    }

    $signature = $parts[2]

    # check "none" signature, and return payload if no signature
    $isNoneAlg = ($header.alg -ieq 'none')

    if ([string]::IsNullOrWhiteSpace($signature) -and !$isNoneAlg) {
        # No JWT signature supplied for {0}
        throw  ($PodeLocale.noJwtSignatureForAlgorithmExceptionMessage -f $header.alg)
    }

    if (![string]::IsNullOrWhiteSpace($signature) -and $isNoneAlg) {
        # Expected no JWT signature to be supplied
        throw ($PodeLocale.expectedNoJwtSignatureSuppliedExceptionMessage)
    }

    if ($isNoneAlg -and ($null -ne $Secret) -and ($Secret.Length -gt 0)) {
        # Expected no JWT signature to be supplied
        throw ($PodeLocale.expectedNoJwtSignatureSuppliedExceptionMessage)
    }

    if ($isNoneAlg) {
        return $payload
    }

    # otherwise, we have an alg for the signature, so we need to validate it
    if (($null -ne $Secret) -and ($Secret -isnot [byte[]])) {
        $Secret = [System.Text.Encoding]::UTF8.GetBytes([string]$Secret)
    }

    $sig = "$($parts[0]).$($parts[1])"
    $sig = New-PodeJwtSignature -Algorithm $header.alg -Token $sig -SecretBytes $Secret

    if ($sig -ne $parts[2]) {
        # Invalid JWT signature supplied
        throw ($PodeLocale.invalidJwtSignatureSuppliedExceptionMessage)
    }

    # it's valid return the payload!
    return $payload
}

<#
.SYNOPSIS
Validates JSON Web Tokens (JWT) claims.

.DESCRIPTION
Validates JSON Web Tokens (JWT) claims. Checks time related claims: 'exp' and 'nbf'.

.PARAMETER Payload
Object containing JWT claims. Some of them are:
    - exp (expiration time)
    - nbf (not before)

.EXAMPLE
Test-PodeJwt @{exp = 2696258821 }

.EXAMPLE
Test-PodeJwt -Payload @{nbf = 1696258821 }
#>
function Test-PodeJwt {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $true)]
        [pscustomobject]
        $Payload
    )

    $now = [datetime]::UtcNow
    $unixStart = [datetime]::new(1970, 1, 1, 0, 0, [DateTimeKind]::Utc)

    # validate expiry
    if (![string]::IsNullOrWhiteSpace($Payload.exp)) {
        if ($now -gt $unixStart.AddSeconds($Payload.exp)) {
            # The JWT has expired
            throw ($PodeLocale.jwtExpiredExceptionMessage)
        }
    }

    # validate not-before
    if (![string]::IsNullOrWhiteSpace($Payload.nbf)) {
        if ($now -lt $unixStart.AddSeconds($Payload.nbf)) {
            # The JWT is not yet valid for use
            throw ($PodeLocale.jwtNotYetValidExceptionMessage)
        }
    }
}

<#
.SYNOPSIS
Automatically loads auth ps1 files

.DESCRIPTION
Automatically loads auth ps1 files from either a /auth folder, or a custom folder. Saves space dot-sourcing them all one-by-one.

.PARAMETER Path
Optional Path to a folder containing ps1 files, can be relative or literal.

.EXAMPLE
Use-PodeAuth

.EXAMPLE
Use-PodeAuth -Path './my-auth'
#>
function Use-PodeAuth {
    [CmdletBinding()]
    param(
        [Parameter()]
        [string]
        $Path
    )

    Use-PodeFolder -Path $Path -DefaultPath 'auth'
}

<#
.SYNOPSIS
Builds an OAuth2 scheme using an OpenID Connect Discovery URL.

.DESCRIPTION
Builds an OAuth2 scheme using an OpenID Connect Discovery URL.

.PARAMETER Url
The OpenID Connect Discovery URL, this must end with '/.well-known/openid-configuration' (if missing, it will be automatically appended).

.PARAMETER Scope
A list of optional Scopes to use during the OAuth2 request. (Default: the supported list returned)

.PARAMETER ClientId
The Client ID from registering a new app.

.PARAMETER ClientSecret
The Client Secret from registering a new app (this is optional when using PKCE).

.PARAMETER RedirectUrl
An optional OAuth2 Redirect URL (Default: <host>/oauth2/callback)

.PARAMETER InnerScheme
An optional authentication Scheme (from New-PodeAuthScheme) that will be called prior to this Scheme.

.PARAMETER Middleware
An array of ScriptBlocks for optional Middleware to run before the Scheme's scriptblock.

.PARAMETER UsePKCE
If supplied, OAuth2 authentication will use PKCE code verifiers.

.EXAMPLE
ConvertFrom-PodeOIDCDiscovery -Url 'https://accounts.google.com/.well-known/openid-configuration' -ClientId some_id -UsePKCE

.EXAMPLE
ConvertFrom-PodeOIDCDiscovery -Url 'https://accounts.google.com' -ClientId some_id -UsePKCE
#>
function ConvertFrom-PodeOIDCDiscovery {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $true)]
        [string]
        $Url,

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

        [Parameter(Mandatory = $true)]
        [string]
        $ClientId,

        [Parameter()]
        [string]
        $ClientSecret,

        [Parameter()]
        [string]
        $RedirectUrl,

        [Parameter(ValueFromPipeline = $true)]
        [hashtable]
        $InnerScheme,

        [Parameter()]
        [object[]]
        $Middleware,

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

    process {

        $pipelineItemCount++
    }

    end {
        if ($pipelineItemCount -gt 1) {
            throw ($PodeLocale.fnDoesNotAcceptArrayAsPipelineInputExceptionMessage -f $($MyInvocation.MyCommand.Name))
        }
        # get the discovery doc
        if (!$Url.EndsWith('/.well-known/openid-configuration')) {
            $Url += '/.well-known/openid-configuration'
        }

        $config = Invoke-RestMethod -Method Get -Uri $Url

        # check it supports the code response_type
        if ($config.response_types_supported -inotcontains 'code') {
            # The OAuth2 provider does not support the 'code' response_type
            throw ($PodeLocale.oauth2ProviderDoesNotSupportCodeResponseTypeExceptionMessage)
        }

        # can we have an InnerScheme?
        if (($null -ne $InnerScheme) -and ($config.grant_types_supported -inotcontains 'password')) {
            # The OAuth2 provider does not support the 'password' grant_type required by using an InnerScheme
            throw ($PodeLocale.oauth2ProviderDoesNotSupportPasswordGrantTypeExceptionMessage)
        }

        # scopes
        $scopes = $config.scopes_supported

        if (($null -ne $Scope) -and ($Scope.Length -gt 0)) {
            $scopes = @(foreach ($s in $Scope) {
                    if ($s -iin $config.scopes_supported) {
                        $s
                    }
                })
        }

        # pkce code challenge method
        $codeMethod = 'S256'
        if ($config.code_challenge_methods_supported -inotcontains $codeMethod) {
            $codeMethod = 'plain'
        }

        return New-PodeAuthScheme `
            -OAuth2 `
            -ClientId $ClientId `
            -ClientSecret $ClientSecret `
            -AuthoriseUrl $config.authorization_endpoint `
            -TokenUrl $config.token_endpoint `
            -UserUrl $config.userinfo_endpoint `
            -RedirectUrl $RedirectUrl `
            -Scope $scopes `
            -InnerScheme $InnerScheme `
            -Middleware $Middleware `
            -CodeChallengeMethod $codeMethod `
            -UsePKCE:$UsePKCE
    }
}

<#
.SYNOPSIS
Test whether the current WebEvent or Session has an authenticated user.

.DESCRIPTION
Test whether the current WebEvent or Session has an authenticated user. Returns true if there is an authenticated user.

.PARAMETER IgnoreSession
If supplied, only the Auth object in the WebEvent will be checked and the Session will be skipped.

.EXAMPLE
if (Test-PodeAuthUser) { ... }
#>
function Test-PodeAuthUser {
    [CmdletBinding()]
    [OutputType([boolean])]
    param(
        [switch]
        $IgnoreSession
    )

    # auth middleware
    if (($null -ne $WebEvent.Auth) -and $WebEvent.Auth.IsAuthenticated) {
        $auth = $WebEvent.Auth
    }

    # session?
    elseif (!$IgnoreSession -and ($null -ne $WebEvent.Session.Data.Auth) -and $WebEvent.Session.Data.Auth.IsAuthenticated) {
        $auth = $WebEvent.Session.Data.Auth
    }

    # null?
    if (($null -eq $auth) -or ($null -eq $auth.User)) {
        return $false
    }

    return ($null -ne $auth.User)
}

<#
.SYNOPSIS
Get the authenticated user from the WebEvent or Session.

.DESCRIPTION
Get the authenticated user from the WebEvent or Session. This is similar to calling $Webevent.Auth.User.

.PARAMETER IgnoreSession
If supplied, only the Auth object in the WebEvent will be used and the Session will be skipped.

.EXAMPLE
$user = Get-PodeAuthUser
#>
function Get-PodeAuthUser {
    [CmdletBinding()]
    param(
        [switch]
        $IgnoreSession
    )

    # auth middleware
    if (($null -ne $WebEvent.Auth) -and $WebEvent.Auth.IsAuthenticated) {
        $auth = $WebEvent.Auth
    }

    # session?
    elseif (!$IgnoreSession -and ($null -ne $WebEvent.Session.Data.Auth) -and $WebEvent.Session.Data.Auth.IsAuthenticated) {
        $auth = $WebEvent.Session.Data.Auth
    }

    # null?
    if (($null -eq $auth) -or ($null -eq $auth.User)) {
        return $null
    }

    return $auth.User
}
src\Public\AutoImport.ps1
<#
.SYNOPSIS
Exports modules that can be auto-imported by Pode, and into its runspaces.

.DESCRIPTION
Exports modules that can be auto-imported by Pode, and into its runspaces.

.PARAMETER Name
The Name(s) of modules to export.

.EXAMPLE
Export-PodeModule -Name Mod1, Mod2
#>
function Export-PodeModule {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $true)]
        [string[]]
        $Name
    )

    $PodeContext.Server.AutoImport.Modules.ExportList += @($Name)
    $PodeContext.Server.AutoImport.Modules.ExportList = @($PodeContext.Server.AutoImport.Modules.ExportList | Sort-Object -Unique)
}

<#
.SYNOPSIS
Exports snapins that can be auto-imported by Pode, and into its runspaces.

.DESCRIPTION
Exports snapins that can be auto-imported by Pode, and into its runspaces.

.PARAMETER Name
The Name(s) of snapins to export.

.EXAMPLE
Export-PodeSnapin -Name Mod1, Mod2
#>
function Export-PodeSnapin {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $true)]
        [string[]]
        $Name
    )

    # if non-windows or core, fail
    if ((Test-PodeIsPSCore) -or (Test-PodeIsUnix)) {
        # Snapins are only supported on Windows PowerShell
        throw ($PodeLocale.snapinsSupportedOnWindowsPowershellOnlyExceptionMessage)
    }

    $PodeContext.Server.AutoImport.Snapins.ExportList += @($Name)
    $PodeContext.Server.AutoImport.Snapins.ExportList = @($PodeContext.Server.AutoImport.Snapins.ExportList | Sort-Object -Unique)
}

<#
.SYNOPSIS
Exports functions that can be auto-imported by Pode, and into its runspaces.

.DESCRIPTION
Exports functions that can be auto-imported by Pode, and into its runspaces.

.PARAMETER Name
The Name(s) of functions to export.

.EXAMPLE
Export-PodeFunction -Name Mod1, Mod2
#>
function Export-PodeFunction {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $true)]
        [string[]]
        $Name
    )

    $PodeContext.Server.AutoImport.Functions.ExportList += @($Name)
    $PodeContext.Server.AutoImport.Functions.ExportList = @($PodeContext.Server.AutoImport.Functions.ExportList | Sort-Object -Unique)
}

<#
.SYNOPSIS
Exports Secret Vaults that can be auto-imported by Pode, and into its runspaces.

.DESCRIPTION
Exports Secret Vaults that can be auto-imported by Pode, and into its runspaces.

.PARAMETER Name
The Name(s) of a Secret Vault to export.

.PARAMETER Type
The Type of the Secret Vault to import - only option currently is SecretManagement (default: SecretManagement)

.EXAMPLE
Export-PodeSecretVault -Name Vault1, Vault2
#>
function Export-PodeSecretVault {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $true)]
        [string[]]
        $Name,

        [Parameter()]
        [ValidateSet('SecretManagement')]
        [string]
        $Type = 'SecretManagement'
    )

    $PodeContext.Server.AutoImport.SecretVaults[$Type].ExportList += @($Name)
    $PodeContext.Server.AutoImport.SecretVaults[$Type].ExportList = @($PodeContext.Server.AutoImport.SecretVaults[$Type].ExportList | Sort-Object -Unique)
}
src\Public\Caching.ps1
<#
.SYNOPSIS
Return the value of a key from the cache. You can use "$value = $cache:key" as well.

.DESCRIPTION
Return the value of a key from the cache, or returns the value plus metadata such as expiry time if required. You can use "$value = $cache:key" as well.

.PARAMETER Key
The Key to be retrieved.

.PARAMETER Storage
An optional cache Storage name. (Default: in-memory)

.PARAMETER Metadata
If supplied, and if supported by the cache storage, an metadata such as expiry times will also be returned.

.EXAMPLE
$value = Get-PodeCache -Key 'ExampleKey'

.EXAMPLE
$value = Get-PodeCache -Key 'ExampleKey' -Storage 'ExampleStorage'

.EXAMPLE
$value = Get-PodeCache -Key 'ExampleKey' -Metadata

.EXAMPLE
$value = $cache:ExampleKey
#>
function Get-PodeCache {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $true)]
        [string]
        $Key,

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

        [switch]
        $Metadata
    )

    # inmem or custom storage?
    if ([string]::IsNullOrEmpty($Storage)) {
        $Storage = $PodeContext.Server.Cache.DefaultStorage
    }

    # use inmem cache
    if ([string]::IsNullOrEmpty($Storage)) {
        return (Get-PodeCacheInternal -Key $Key -Metadata:$Metadata)
    }

    # used custom storage
    if (Test-PodeCacheStorage -Key $Storage) {
        return (Invoke-PodeScriptBlock -ScriptBlock $PodeContext.Server.Cache.Storage[$Storage].Get -Arguments @($Key, $Metadata.IsPresent) -Splat -Return)
    }

    # storage not found!
    # Cache storage with name not found when attempting to retrieve cached item
    throw ($PodeLocale.cacheStorageNotFoundForRetrieveExceptionMessage -f $Storage, $Key)
}

<#
.SYNOPSIS
Set (create/update) a key in the cache. You can use "$cache:key = 'value'" as well.

.DESCRIPTION
Set (create/update) a key in the cache, with an optional TTL value. You can use "$cache:key = 'value'" as well.

.PARAMETER Key
The Key to be set.

.PARAMETER InputObject
The value of the key to be set, can be any object type.

.PARAMETER Ttl
An optional TTL value, in seconds. The default is whatever "Get-PodeCacheDefaultTtl" retuns, which will be 3600 seconds when not set.

.PARAMETER Storage
An optional cache Storage name. (Default: in-memory)

.EXAMPLE
Set-PodeCache -Key 'ExampleKey' -InputObject 'ExampleValue'

.EXAMPLE
Set-PodeCache -Key 'ExampleKey' -InputObject 'ExampleValue' -Storage 'ExampleStorage'

.EXAMPLE
Set-PodeCache -Key 'ExampleKey' -InputObject 'ExampleValue' -Ttl 300

.EXAMPLE
Set-PodeCache -Key 'ExampleKey' -InputObject @{ Value = 'ExampleValue' }

.EXAMPLE
@{ Value = 'ExampleValue' } | Set-PodeCache -Key 'ExampleKey'

.EXAMPLE
$cache:ExampleKey = 'ExampleValue'
#>
function Set-PodeCache {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $true)]
        [string]
        $Key,

        [Parameter(Mandatory = $true, Position = 0, ValueFromPipeline = $true)]
        [object]
        $InputObject,

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

        [Parameter()]
        [string]
        $Storage = $null
    )

    begin {
        # Initialize an array to hold piped-in values
        $pipelineValue = @()
    }

    process {
        # Add the current piped-in value to the array
        $pipelineValue += $_
    }

    end {
        # If there are multiple piped-in values, set InputObject to the array of values
        if ($pipelineValue.Count -gt 1) {
            $InputObject = $pipelineValue
        }

        # use the global settable default here
        if ($Ttl -le 0) {
            $Ttl = $PodeContext.Server.Cache.DefaultTtl
        }

        # inmem or custom storage?
        if ([string]::IsNullOrEmpty($Storage)) {
            $Storage = $PodeContext.Server.Cache.DefaultStorage
        }

        # use inmem cache
        if ([string]::IsNullOrEmpty($Storage)) {
            Set-PodeCacheInternal -Key $Key -InputObject $InputObject -Ttl $Ttl
        }

        # used custom storage
        elseif (Test-PodeCacheStorage -Key $Storage) {
            $null = Invoke-PodeScriptBlock -ScriptBlock $PodeContext.Server.Cache.Storage[$Storage].Set -Arguments @($Key, $InputObject, $Ttl) -Splat
        }

        # storage not found!
        else {
            # Cache storage with name not found when attempting to set cached item
            throw ($PodeLocale.cacheStorageNotFoundForSetExceptionMessage -f $Storage, $Key)
        }
    }
}

<#
.SYNOPSIS
Test if a key exists in the cache.

.DESCRIPTION
Test if a key exists in the cache, and isn't expired.

.PARAMETER Key
The Key to test.

.PARAMETER Storage
An optional cache Storage name. (Default: in-memory)

.EXAMPLE
Test-PodeCache -Key 'ExampleKey'

.EXAMPLE
Test-PodeCache -Key 'ExampleKey' -Storage 'ExampleStorage'
#>
function Test-PodeCache {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $true)]
        [string]
        $Key,

        [Parameter()]
        [string]
        $Storage = $null
    )

    # inmem or custom storage?
    if ([string]::IsNullOrEmpty($Storage)) {
        $Storage = $PodeContext.Server.Cache.DefaultStorage
    }

    # use inmem cache
    if ([string]::IsNullOrEmpty($Storage)) {
        return (Test-PodeCacheInternal -Key $Key)
    }

    # used custom storage
    if (Test-PodeCacheStorage -Key $Storage) {
        return (Invoke-PodeScriptBlock -ScriptBlock $PodeContext.Server.Cache.Storage[$Storage].Test -Arguments @($Key) -Splat -Return)
    }

    # storage not found!
    # Cache storage with name not found when attempting to check if cached item exists
    throw ($PodeLocale.cacheStorageNotFoundForExistsExceptionMessage -f $Storage, $Key)
}

<#
.SYNOPSIS
Remove a key from the cache.

.DESCRIPTION
Remove a key from the cache.

.PARAMETER Key
The Key to be removed.

.PARAMETER Storage
An optional cache Storage name. (Default: in-memory)

.EXAMPLE
Remove-PodeCache -Key 'ExampleKey'

.EXAMPLE
Remove-PodeCache -Key 'ExampleKey' -Storage 'ExampleStorage'
#>
function Remove-PodeCache {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $true)]
        [string]
        $Key,

        [Parameter()]
        [string]
        $Storage = $null
    )

    # inmem or custom storage?
    if ([string]::IsNullOrEmpty($Storage)) {
        $Storage = $PodeContext.Server.Cache.DefaultStorage
    }

    # use inmem cache
    if ([string]::IsNullOrEmpty($Storage)) {
        Remove-PodeCacheInternal -Key $Key
    }

    # used custom storage
    elseif (Test-PodeCacheStorage -Key $Storage) {
        $null = Invoke-PodeScriptBlock -ScriptBlock $PodeContext.Server.Cache.Storage[$Storage].Remove -Arguments @($Key) -Splat
    }

    # storage not found!
    else {
        # Cache storage with name not found when attempting to remove cached item
        throw ($PodeLocale.cacheStorageNotFoundForRemoveExceptionMessage -f $Storage, $Key)
    }
}

<#
.SYNOPSIS
Clear all keys from the cache.

.DESCRIPTION
Clear all keys from the cache.

.PARAMETER Storage
An optional cache Storage name. (Default: in-memory)

.EXAMPLE
Clear-PodeCache

.EXAMPLE
Clear-PodeCache -Storage 'ExampleStorage'
#>
function Clear-PodeCache {
    [CmdletBinding()]
    param(
        [Parameter()]
        [string]
        $Storage = $null
    )

    # inmem or custom storage?
    if ([string]::IsNullOrEmpty($Storage)) {
        $Storage = $PodeContext.Server.Cache.DefaultStorage
    }

    # use inmem cache
    if ([string]::IsNullOrEmpty($Storage)) {
        Clear-PodeCacheInternal
    }

    # used custom storage
    elseif (Test-PodeCacheStorage -Key $Storage) {
        $null = Invoke-PodeScriptBlock -ScriptBlock $PodeContext.Server.Cache.Storage[$Storage].Clear
    }

    # storage not found!
    else {
        # Cache storage with name not found when attempting to clear the cache
        throw ($PodeLocale.cacheStorageNotFoundForClearExceptionMessage -f $Storage)
    }
}

<#
.SYNOPSIS
Add a cache storage.

.DESCRIPTION
Add a cache storage.

.PARAMETER Name
The Name of the cache storage.

.PARAMETER Get
A Get ScriptBlock, to retrieve a key's value from the cache, or the value plus metadata if required. Supplied parameters: Key, Metadata.

.PARAMETER Set
A Set ScriptBlock, to set/create/update a key's value in the cache. Supplied parameters: Key, Value, TTL.

.PARAMETER Remove
A Remove ScriptBlock, to remove a key from the cache. Supplied parameters: Key.

.PARAMETER Test
A Test ScriptBlock, to test if a key exists in the cache. Supplied parameters: Key.

.PARAMETER Clear
A Clear ScriptBlock, to remove all keys from the cache. Use an empty ScriptBlock if not supported.

.PARAMETER Default
If supplied, this cache storage will be set as the default storage.

.EXAMPLE
Add-PodeCacheStorage -Name 'ExampleStorage' -Get {} -Set {} -Remove {} -Test {} -Clear {}
#>
function Add-PodeCacheStorage {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $true)]
        [ValidateNotNullOrEmpty()]
        [string]
        $Name,

        [Parameter(Mandatory = $true)]
        [scriptblock]
        $Get,

        [Parameter(Mandatory = $true)]
        [scriptblock]
        $Set,

        [Parameter(Mandatory = $true)]
        [scriptblock]
        $Remove,

        [Parameter(Mandatory = $true)]
        [scriptblock]
        $Test,

        [Parameter(Mandatory = $true)]
        [scriptblock]
        $Clear,

        [switch]
        $Default
    )

    # test if storage already exists
    if (Test-PodeCacheStorage -Name $Name) {
        # Cache Storage with name already exists
        throw ($PodeLocale.cacheStorageAlreadyExistsExceptionMessage -f $Name)
    }

    # add cache storage
    $PodeContext.Server.Cache.Storage[$Name] = @{
        Name    = $Name
        Get     = $Get
        Set     = $Set
        Remove  = $Remove
        Test    = $Test
        Clear   = $Clear
        Default = $Default.IsPresent
    }

    # is default storage?
    if ($Default) {
        $PodeContext.Server.Cache.DefaultStorage = $Name
    }
}

<#
.SYNOPSIS
Remove a cache storage.

.DESCRIPTION
Remove a cache storage.

.PARAMETER Name
The Name of the cache storage.

.EXAMPLE
Remove-PodeCacheStorage -Name 'ExampleStorage'
#>
function Remove-PodeCacheStorage {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $true)]
        [string]
        $Name
    )

    $null = $PodeContext.Server.Cache.Storage.Remove($Name)
}

<#
.SYNOPSIS
Returns a cache storage.

.DESCRIPTION
Returns a cache storage.

.PARAMETER Name
The Name of the cache storage.

.EXAMPLE
$storage = Get-PodeCacheStorage -Name 'ExampleStorage'
#>
function Get-PodeCacheStorage {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $true)]
        [string]
        $Name
    )

    return $PodeContext.Server.Cache.Storage[$Name]
}

<#
.SYNOPSIS
Test if a cache storage has been added/exists.

.DESCRIPTION
Test if a cache storage has been added/exists.

.PARAMETER Name
The Name of the cache storage.

.EXAMPLE
if (Test-PodeCacheStorage -Name 'ExampleStorage') { }
#>
function Test-PodeCacheStorage {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $true)]
        [string]
        $Name
    )

    return $PodeContext.Server.Cache.Storage.ContainsKey($Name)
}

<#
.SYNOPSIS
Set a default cache storage.

.DESCRIPTION
Set a default cache storage.

.PARAMETER Name
The Name of the default storage to use for caching.

.EXAMPLE
Set-PodeCacheDefaultStorage -Name 'ExampleStorage'
#>
function Set-PodeCacheDefaultStorage {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $true)]
        [string]
        $Name
    )

    $PodeContext.Server.Cache.DefaultStorage = $Name
}

<#
.SYNOPSIS
Returns the current default cache Storage name.

.DESCRIPTION
Returns the current default cache Storage name. Empty/null if one isn't set.

.EXAMPLE
$storageName = Get-PodeCacheDefaultStorage
#>
function Get-PodeCacheDefaultStorage {
    [CmdletBinding()]
    param()

    return $PodeContext.Server.Cache.DefaultStorage
}

<#
.SYNOPSIS
Set a default cache TTL.

.DESCRIPTION
Set a default cache TTL.

.PARAMETER Value
A default TTL value, in seconds, to use when setting cache key expiries.

.EXAMPLE
Set-PodeCacheDefaultTtl -Value 3600
#>
function Set-PodeCacheDefaultTtl {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $true)]
        [int]
        $Value
    )

    if ($Value -le 0) {
        return
    }

    $PodeContext.Server.Cache.DefaultTtl = $Value
}

<#
.SYNOPSIS
Returns the current default cache TTL value.

.DESCRIPTION
Returns the current default cache TTL value. 3600 seconds is the default TTL if not set.

.EXAMPLE
$ttl = Get-PodeCacheDefaultTtl
#>
function Get-PodeCacheDefaultTtl {
    [CmdletBinding()]
    param()

    return $PodeContext.Server.Cache.DefaultTtl
}
src\Public\Cookies.ps1
<#
.SYNOPSIS
Sets a cookie on the Response.

.DESCRIPTION
Sets a cookie on the Response using the "Set-Cookie" header. You can also set cookies to expire, or being signed.

.PARAMETER Name
The name of the cookie.

.PARAMETER Value
The value of the cookie.

.PARAMETER Secret
If supplied, the secret with which to sign the cookie.

.PARAMETER Duration
The duration, in seconds, before the cookie is expired.

.PARAMETER ExpiryDate
An explicit expiry date for the cookie.

.PARAMETER HttpOnly
Only allow the cookie to be used in browsers.

.PARAMETER Discard
Inform browsers to remove the cookie.

.PARAMETER Secure
Only allow the cookie on secure (HTTPS) connections.

.PARAMETER Strict
If supplied, the Secret will be extended using the client request's UserAgent and RemoteIPAddress.

.EXAMPLE
Set-PodeCookie -Name 'Views' -Value 2

.EXAMPLE
Set-PodeCookie -Name 'Views' -Value 2 -Secret 'hunter2'

.EXAMPLE
Set-PodeCookie -Name 'Views' -Value 2 -Duration 3600
#>
function Set-PodeCookie {
    [CmdletBinding(DefaultParameterSetName = 'Duration')]
    [OutputType([hashtable])]
    param(
        [Parameter(Mandatory = $true)]
        [string]
        $Name,

        [Parameter()]
        [string]
        $Value,

        [Parameter()]
        [string]
        $Secret,

        [Parameter(ParameterSetName = 'Duration')]
        [int]
        $Duration = 0,

        [Parameter(ParameterSetName = 'ExpiryDate')]
        [datetime]
        $ExpiryDate,

        [switch]
        $HttpOnly,

        [switch]
        $Discard,

        [switch]
        $Secure,

        [switch]
        $Strict
    )

    # sign the value if we have a secret
    if (![string]::IsNullOrWhiteSpace($Secret)) {
        $Value = (Invoke-PodeValueSign -Value $Value -Secret $Secret -Strict:$Strict)
    }

    # create a new cookie
    $cookie = [System.Net.Cookie]::new($Name, $Value)
    $cookie.Secure = $Secure
    $cookie.Discard = $Discard
    $cookie.HttpOnly = $HttpOnly
    $cookie.Path = '/'

    if ($null -ne $ExpiryDate) {
        if ($ExpiryDate.Kind -eq [System.DateTimeKind]::Local) {
            $ExpiryDate = $ExpiryDate.ToUniversalTime()
        }

        $cookie.Expires = $ExpiryDate
    }
    elseif ($Duration -gt 0) {
        $cookie.Expires = [datetime]::UtcNow.AddSeconds($Duration)
    }

    # sets the cookie on the the response
    $WebEvent.PendingCookies[$cookie.Name] = $cookie
    Add-PodeHeader -Name 'Set-Cookie' -Value (ConvertTo-PodeCookieString -Cookie $cookie)
    return (ConvertTo-PodeCookie -Cookie $cookie)
}

<#
.SYNOPSIS
Retrieves a cookie from the Request.

.DESCRIPTION
Retrieves a cookie from the Request, with the option to supply a secret to unsign the cookie's value.

.PARAMETER Name
The name of the cookie to retrieve.

.PARAMETER Secret
The secret used to unsign the cookie's value.

.PARAMETER Strict
If supplied, the Secret will be extended using the client request's UserAgent and RemoteIPAddress.

.PARAMETER Raw
If supplied, the cookie returned will be the raw .NET Cookie object for manipulation.

.EXAMPLE
Get-PodeCookie -Name 'Views'

.EXAMPLE
Get-PodeCookie -Name 'Views' -Secret 'hunter2'
#>
function Get-PodeCookie {
    [CmdletBinding()]
    [OutputType([hashtable])]
    param(
        [Parameter(Mandatory = $true)]
        [string]
        $Name,

        [Parameter()]
        [string]
        $Secret,

        [switch]
        $Strict,

        [switch]
        $Raw
    )

    # get the cookie from the request
    $cookie = $WebEvent.Cookies[$Name]
    if (!$Raw) {
        $cookie = (ConvertTo-PodeCookie -Cookie $cookie)
    }

    if (($null -eq $cookie) -or [string]::IsNullOrWhiteSpace($cookie.Value)) {
        return $null
    }

    # if a secret was supplied, attempt to unsign the cookie
    if (![string]::IsNullOrWhiteSpace($Secret)) {
        $value = (Invoke-PodeValueUnsign -Value $cookie.Value -Secret $Secret -Strict:$Strict)
        if (![string]::IsNullOrWhiteSpace($value)) {
            $cookie.Value = $value
        }
    }

    return $cookie
}

<#
.SYNOPSIS
Retrieves the value of a cookie from the Request.

.DESCRIPTION
Retrieves the value of a cookie from the Request, with the option to supply a secret to unsign the cookie's value.

.PARAMETER Name
The name of the cookie to retrieve.

.PARAMETER Secret
The secret used to unsign the cookie's value.

.PARAMETER Strict
If supplied, the Secret will be extended using the client request's UserAgent and RemoteIPAddress.

.EXAMPLE
Get-PodeCookieValue -Name 'Views'

.EXAMPLE
Get-PodeCookieValue -Name 'Views' -Secret 'hunter2'
#>
function Get-PodeCookieValue {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $true)]
        [string]
        $Name,

        [Parameter()]
        [string]
        $Secret,

        [switch]
        $Strict
    )

    $cookie = Get-PodeCookie -Name $Name -Secret $Secret -Strict:$Strict
    if ($null -eq $cookie) {
        return $null
    }

    return $cookie.Value
}

<#
.SYNOPSIS
Tests if a cookie exists on the Request.

.DESCRIPTION
Tests if a cookie exists on the Request.

.PARAMETER Name
The name of the cookie to test for on the Request.

.EXAMPLE
Test-PodeCookie -Name 'Views'
#>
function Test-PodeCookie {
    [CmdletBinding()]
    [OutputType([bool])]
    param(
        [Parameter(Mandatory = $true)]
        [string]
        $Name
    )

    $cookie = $WebEvent.Cookies[$Name]
    return (($null -ne $cookie) -and ![string]::IsNullOrWhiteSpace($cookie.Value))
}

<#
.SYNOPSIS
Removes a cookie from the Response.

.DESCRIPTION
Removes a cookie from the Response, this is done by immediately expiring the cookie and flagging it for discard.

.PARAMETER Name
The name of the cookie to be removed.

.EXAMPLE
Remove-PodeCookie -Name 'Views'
#>
function Remove-PodeCookie {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $true)]
        [string]
        $Name
    )

    # get the cookie from the response - if it's not found, get it from the request
    $cookie = $WebEvent.PendingCookies[$Name]
    if ($null -eq $cookie) {
        $cookie = Get-PodeCookie -Name $Name -Raw
    }

    # remove the cookie from the response, and reset it to expire
    if ($null -ne $cookie) {
        $cookie.Discard = $true
        $cookie.Expires = [DateTime]::UtcNow.AddDays(-2)
        $cookie.Path = '/'
        $WebEvent.PendingCookies[$cookie.Name] = $cookie
        Add-PodeHeader -Name 'Set-Cookie' -Value (ConvertTo-PodeCookieString -Cookie $cookie)
    }
}

<#
.SYNOPSIS
Tests if a cookie on the Request is validly signed.

.DESCRIPTION
Tests if a cookie on the Request is validly signed, by attempting to unsign it using some secret.

.PARAMETER Name
The name of the cookie to test.

.PARAMETER Secret
A secret to use for attempting to unsign the cookie's value.

.PARAMETER Strict
If supplied, the Secret will be extended using the client request's UserAgent and RemoteIPAddress.

.EXAMPLE
Test-PodeCookieSigned -Name 'Views' -Secret 'hunter2'
#>
function Test-PodeCookieSigned {
    [CmdletBinding()]
    [OutputType([bool])]
    param(
        [Parameter(Mandatory = $true)]
        [string]
        $Name,

        [Parameter()]
        [string]
        $Secret,

        [switch]
        $Strict
    )

    $cookie = $WebEvent.Cookies[$Name]
    if (($null -eq $cookie) -or [string]::IsNullOrEmpty($cookie.Value)) {
        return $false
    }

    return Test-PodeValueSigned -Value $cookie.Value -Secret $Secret -Strict:$Strict
}

<#
.SYNOPSIS
Updates the exipry date of a cookie on the Response.

.DESCRIPTION
Updates the exipry date of a cookie on the Response. This can either be done by suppling a duration, or and explicit expiry date.

.PARAMETER Name
The name of the cookie to extend.

.PARAMETER Duration
The duration, in seconds, to extend the cookie's expiry.

.PARAMETER ExpiryDate
An explicit expiry date for the cookie.

.EXAMPLE
Update-PodeCookieExpiry -Name  'Views' -Duration 1800

.EXAMPLE
Update-PodeCookieExpiry -Name  'Views' -ExpiryDate ([datetime]::UtcNow.AddSeconds(1800))
#>
function Update-PodeCookieExpiry {
    [CmdletBinding(DefaultParameterSetName = 'Duration')]
    [OutputType([hashtable])]
    param(
        [Parameter(Mandatory = $true)]
        [string]
        $Name,

        [Parameter(ParameterSetName = 'Duration')]
        [int]
        $Duration = 0,

        [Parameter(ParameterSetName = 'ExpiryDate')]
        [datetime]
        $ExpiryDate
    )

    # get the cookie from the response - if it's not found, get it from the request
    $cookie = $WebEvent.PendingCookies[$Name]
    if ($null -eq $cookie) {
        $cookie = Get-PodeCookie -Name $Name -Raw
    }

    # extends the expiry on the cookie
    if ($null -ne $ExpiryDate) {
        if ($ExpiryDate.Kind -eq [System.DateTimeKind]::Local) {
            $ExpiryDate = $ExpiryDate.ToUniversalTime()
        }

        $cookie.Expires = $ExpiryDate
    }
    elseif ($Duration -gt 0) {
        $cookie.Expires = [datetime]::UtcNow.AddSeconds($Duration)
    }

    $cookie.Path = '/'

    # sets the cookie on the the response
    $WebEvent.PendingCookies[$cookie.Name] = $cookie
    Add-PodeHeader -Name 'Set-Cookie' -Value (ConvertTo-PodeCookieString -Cookie $cookie)
    return (ConvertTo-PodeCookie -Cookie $cookie)
}

<#
.SYNOPSIS
Stores secrets that can be used to sign cookies.

.DESCRIPTION
Stores secrets that can be used to sign cookies. A global secret can be set for easier retrieval.

.PARAMETER Name
The name of the secret to store.

.PARAMETER Value
The value of the secret to store.

.PARAMETER Global
If flagged, the secret being stored will be set as the global secret.

.EXAMPLE
Set-PodeCookieSecret -Name 'my-secret' -Value 'shhhh!'

.EXAMPLE
Set-PodeCookieSecret -Value 'hunter2' -Global
#>
function Set-PodeCookieSecret {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $true, ParameterSetName = 'General')]
        [string]
        $Name,

        [Parameter(Mandatory = $true)]
        [string]
        $Value,

        [Parameter(ParameterSetName = 'Global')]
        [switch]
        $Global
    )

    if ($Global) {
        $Name = 'global'
    }

    $PodeContext.Server.Cookies.Secrets[$Name] = $Value
}

<#
.SYNOPSIS
Retrieves a stored secret value.

.DESCRIPTION
Retrieves a stored secret value.

.PARAMETER Name
The name of the secret to retrieve.

.PARAMETER Global
If flagged, will return the current global secret value.

.EXAMPLE
Get-PodeCookieSecret -Name 'my-secret'

.EXAMPLE
Get-PodeCookieSecret -Global
#>
function Get-PodeCookieSecret {
    [CmdletBinding()]
    [OutputType([string])]
    param(
        [Parameter(Mandatory = $true, ParameterSetName = 'General')]
        [string]
        $Name,

        [Parameter(ParameterSetName = 'Global')]
        [switch]
        $Global
    )

    if ($Global) {
        return ($PodeContext.Server.Cookies.Secrets['global'])
    }

    return ($PodeContext.Server.Cookies.Secrets[$Name])
}
src\Public\Core.ps1
<#
.SYNOPSIS
Starts a Pode Server with the supplied ScriptBlock.

.DESCRIPTION
Starts a Pode Server with the supplied ScriptBlock.

.PARAMETER ScriptBlock
The main logic for the Server.

.PARAMETER FilePath
A literal, or relative, path to a file containing a ScriptBlock for 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
For 'Service' type Servers, will invoke the ScriptBlock every X seconds.

.PARAMETER Name
An optional name for the Server (intended for future ideas).

.PARAMETER Threads
The numbers of threads to use for Web, SMTP, and TCP servers.

.PARAMETER RootPath
An override for the Server's root path.

.PARAMETER Request
Intended for Serverless environments, this is Requests details that Pode can parse and use.

.PARAMETER ServerlessType
Optional, this is the serverless type, to define how Pode should run and deal with incoming Requests.

.PARAMETER StatusPageExceptions
An optional value of Show/Hide to control where Stacktraces are shown in the Status Pages.
If supplied this value will override the ShowExceptions setting in the server.psd1 file.

.PARAMETER ListenerType
An optional value to use a custom Socket Listener. The default is Pode's inbuilt listener.
There's the Pode.Kestrel module, so the value here should be "Kestrel" if using that.

.PARAMETER DisableTermination
Disables the ability to terminate the Server.

.PARAMETER Quiet
Disables any output from the Server.

.PARAMETER Browse
Open the web Server's default endpoint in your default browser.

.PARAMETER CurrentPath
Sets the Server's root path to be the current working path - for -FilePath only.

.PARAMETER EnablePool
Tells Pode to configure certain RunspacePools when they're being used adhoc, such as Timers or Schedules.

.PARAMETER EnableBreakpoints
If supplied, any breakpoints created by using Wait-PodeDebugger will be enabled - or disabled if false passed explicitly, or not supplied.

.EXAMPLE
Start-PodeServer { /* logic */ }

.EXAMPLE
Start-PodeServer -Interval 10 { /* logic */ }

.EXAMPLE
Start-PodeServer -Request $LambdaInput -ServerlessType AwsLambda { /* logic */ }
#>
function Start-PodeServer {
    [CmdletBinding(DefaultParameterSetName = 'Script')]
    param(
        [Parameter(Mandatory = $true, ValueFromPipeline = $true, Position = 0, ParameterSetName = 'Script')]
        [scriptblock]
        $ScriptBlock,

        [Parameter(Mandatory = $true, ParameterSetName = 'File')]
        [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,

        [switch]
        $DisableTermination,

        [switch]
        $Quiet,

        [switch]
        $Browse,

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

        [switch]
        $EnableBreakpoints
    )
    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
        $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
            }

            # create main context object
            $PodeContext = New-PodeContext `
                -ScriptBlock $ScriptBlock `
                -FilePath $FilePath `
                -Threads $Threads `
                -Interval $Interval `
                -ServerRoot (Protect-PodeValue -Value $RootPath -Default $MyInvocation.PSScriptRoot) `
                -ServerlessType $ServerlessType `
                -ListenerType $ListenerType `
                -EnablePool $EnablePool `
                -StatusPageExceptions $StatusPageExceptions `
                -DisableTermination:$DisableTermination `
                -Quiet:$Quiet `
                -EnableBreakpoints:$EnableBreakpoints

            # set it so ctrl-c can terminate, unless serverless/iis, or disabled
            if (!$PodeContext.Server.DisableTermination -and ($null -eq $psISE)) {
                [Console]::TreatControlCAsInput = $true
            }

            # 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 here waiting for termination/cancellation, or to restart the server
            while (!(Test-PodeTerminationPressed -Key $key) -and !($PodeContext.Tokens.Cancellation.IsCancellationRequested)) {
                Start-Sleep -Seconds 1

                # get the next key presses
                $key = Get-PodeConsoleKey

                # check for internal restart
                if (($PodeContext.Tokens.Restart.IsCancellationRequested) -or (Test-PodeRestartPressed -Key $key)) {
                    Restart-PodeInternalServer
                }

                # check for open browser
                if (Test-PodeOpenBrowserPressed -Key $key) {
                    Invoke-PodeEvent -Type Browser
                    Start-Process (Get-PodeEndpointUrl)
                }
            }

            if ($PodeContext.Server.IsIIS -and $PodeContext.Server.IIS.Shutdown) {
                # (IIS Shutdown)
                Write-PodeHost $PodeLocale.iisShutdownMessage -NoNewLine -ForegroundColor Yellow
                Write-PodeHost ' ' -NoNewLine
            }
            # Terminating...
            Write-PodeHost $PodeLocale.terminatingMessage -NoNewLine -ForegroundColor Yellow
            Invoke-PodeEvent -Type Terminate
            $PodeContext.Tokens.Cancellation.Cancel()
        }
        catch {
            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 -ShowDoneMessage:$ShowDoneMessage

            # clean the session
            $PodeContext = $null

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

<#
.SYNOPSIS
Closes the Pode server.

.DESCRIPTION
Closes the Pode server.

.EXAMPLE
Close-PodeServer
#>
function Close-PodeServer {
    [CmdletBinding()]
    param()

    $PodeContext.Tokens.Cancellation.Cancel()
}

<#
.SYNOPSIS
Restarts the Pode server.

.DESCRIPTION
Restarts the Pode server.

.EXAMPLE
Restart-PodeServer
#>
function Restart-PodeServer {
    [CmdletBinding()]
    param()

    $PodeContext.Tokens.Restart.Cancel()
}

<#
.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
Bind an endpoint to listen for incoming Requests.

.DESCRIPTION
Bind an endpoint to listen for incoming Requests. The endpoints can be HTTP, HTTPS, TCP or SMTP, with the option to bind certificates.

.PARAMETER Address
The IP/Hostname of the endpoint (Default: localhost).

.PARAMETER Port
The Port number of the endpoint.

.PARAMETER Hostname
An optional hostname for the endpoint, specifying a hostname restricts access to just the hostname.

.PARAMETER Protocol
The protocol of the supplied endpoint.

.PARAMETER Certificate
The path to a certificate that can be use to enable HTTPS

.PARAMETER CertificatePassword
The password for the certificate file referenced in Certificate

.PARAMETER CertificateKey
A key file to be paired with a PEM certificate file referenced in Certificate

.PARAMETER CertificateThumbprint
A certificate thumbprint to bind onto HTTPS endpoints (Windows).

.PARAMETER CertificateName
A certificate subject name to bind onto HTTPS endpoints (Windows).

.PARAMETER CertificateStoreName
The name of a certifcate store where a certificate can be found (Default: My) (Windows).

.PARAMETER CertificateStoreLocation
The location of a certifcate store where a certificate can be found (Default: CurrentUser) (Windows).

.PARAMETER X509Certificate
The raw X509 certificate that can be use to enable HTTPS

.PARAMETER TlsMode
The TLS mode to use on secure connections, options are Implicit or Explicit (SMTP only) (Default: Implicit).

.PARAMETER Name
An optional name for the endpoint, that can be used with other functions (Default: GUID).

.PARAMETER RedirectTo
The Name of another Endpoint to automatically generate a redirect route for all traffic.

.PARAMETER Description
A quick description of the Endpoint - normally used in OpenAPI.

.PARAMETER Acknowledge
An optional Acknowledge message to send to clients when they first connect, for TCP and SMTP endpoints only.

.PARAMETER SslProtocol
One or more optional SSL Protocols this endpoints supports. (Default: SSL3/TLS12 - Just TLS12 on MacOS).

.PARAMETER CRLFMessageEnd
If supplied, TCP endpoints will expect incoming data to end with CRLF.

.PARAMETER Force
Ignore Adminstrator checks for non-localhost endpoints.

.PARAMETER SelfSigned
Create and bind a self-signed certifcate for HTTPS endpoints.

.PARAMETER AllowClientCertificate
Allow for client certificates to be sent on requests.

.PARAMETER PassThru
If supplied, the endpoint created will be returned.

.PARAMETER LookupHostname
If supplied, a supplied Hostname will have its IP Address looked up from host file or DNS.

.PARAMETER DualMode
If supplied, this endpoint will listen on both the IPv4 and IPv6 versions of the supplied -Address.
For IPv6, this will only work if the IPv6 address can convert to a valid IPv4 address.

.PARAMETER Default
If supplied, this endpoint will be the default one used for internally generating URLs.

.EXAMPLE
Add-PodeEndpoint -Address localhost -Port 8090 -Protocol Http

.EXAMPLE
Add-PodeEndpoint -Address localhost -Protocol Smtp

.EXAMPLE
Add-PodeEndpoint -Address dev.pode.com -Port 8443 -Protocol Https -SelfSigned

.EXAMPLE
Add-PodeEndpoint -Address 127.0.0.2 -Hostname dev.pode.com -Port 8443 -Protocol Https -SelfSigned

.EXAMPLE
Add-PodeEndpoint -Address live.pode.com -Protocol Https -CertificateThumbprint '2A9467F7D3940243D6C07DE61E7FCCE292'
#>
function Add-PodeEndpoint {
    [CmdletBinding(DefaultParameterSetName = 'Default')]
    [OutputType([hashtable])]
    param(
        [Parameter()]
        [string]
        $Address = 'localhost',

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

        [Parameter()]
        [string]
        $Hostname,

        [Parameter()]
        [ValidateSet('Http', 'Https', 'Smtp', 'Smtps', 'Tcp', 'Tcps', 'Ws', 'Wss')]
        [string]
        $Protocol,

        [Parameter(Mandatory = $true, ParameterSetName = 'CertFile')]
        [string]
        $Certificate = $null,

        [Parameter(ParameterSetName = 'CertFile')]
        [string]
        $CertificatePassword = $null,

        [Parameter(ParameterSetName = 'CertFile')]
        [string]
        $CertificateKey = $null,

        [Parameter(Mandatory = $true, ParameterSetName = 'CertThumb')]
        [string]
        $CertificateThumbprint,

        [Parameter(Mandatory = $true, ParameterSetName = 'CertName')]
        [string]
        $CertificateName,

        [Parameter(ParameterSetName = 'CertName')]
        [Parameter(ParameterSetName = 'CertThumb')]
        [System.Security.Cryptography.X509Certificates.StoreName]
        $CertificateStoreName = 'My',

        [Parameter(ParameterSetName = 'CertName')]
        [Parameter(ParameterSetName = 'CertThumb')]
        [System.Security.Cryptography.X509Certificates.StoreLocation]
        $CertificateStoreLocation = 'CurrentUser',

        [Parameter(Mandatory = $true, ParameterSetName = 'CertRaw')]
        [X509Certificate]
        $X509Certificate = $null,

        [Parameter(ParameterSetName = 'CertFile')]
        [Parameter(ParameterSetName = 'CertThumb')]
        [Parameter(ParameterSetName = 'CertName')]
        [Parameter(ParameterSetName = 'CertRaw')]
        [Parameter(ParameterSetName = 'CertSelf')]
        [ValidateSet('Implicit', 'Explicit')]
        [string]
        $TlsMode = 'Implicit',

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

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

        [Parameter()]
        [string]
        $Description,

        [Parameter()]
        [string]
        $Acknowledge,

        [Parameter()]
        [ValidateSet('Ssl2', 'Ssl3', 'Tls', 'Tls11', 'Tls12', 'Tls13')]
        [string[]]
        $SslProtocol = $null,

        [switch]
        $CRLFMessageEnd,

        [switch]
        $Force,

        [Parameter(ParameterSetName = 'CertSelf')]
        [switch]
        $SelfSigned,

        [switch]
        $AllowClientCertificate,

        [switch]
        $PassThru,

        [switch]
        $LookupHostname,

        [switch]
        $DualMode,

        [switch]
        $Default
    )

    # error if serverless
    Test-PodeIsServerless -FunctionName 'Add-PodeEndpoint' -ThrowError

    # if RedirectTo is supplied, then a Name is mandatory
    if (![string]::IsNullOrWhiteSpace($RedirectTo) -and [string]::IsNullOrWhiteSpace($Name)) {
        # A Name is required for the endpoint if the RedirectTo parameter is supplied
        throw ($PodeLocale.nameRequiredForEndpointIfRedirectToSuppliedExceptionMessage)
    }

    # get the type of endpoint
    $type = Get-PodeEndpointType -Protocol $Protocol

    # are we running as IIS for HTTP/HTTPS? (if yes, force the port, address and protocol)
    $isIIS = ((Test-PodeIsIIS) -and (@('Http', 'Ws') -icontains $type))
    if ($isIIS) {
        $Port = [int]$env:ASPNETCORE_PORT
        $Address = '127.0.0.1'
        $Hostname = [string]::Empty
        $Protocol = $type
    }

    # are we running as Heroku for HTTP/HTTPS? (if yes, force the port, address and protocol)
    $isHeroku = ((Test-PodeIsHeroku) -and (@('Http') -icontains $type))
    if ($isHeroku) {
        $Port = [int]$env:PORT
        $Address = '0.0.0.0'
        $Hostname = [string]::Empty
        $Protocol = $type
    }

    # parse the endpoint for host/port info
    if (![string]::IsNullOrWhiteSpace($Hostname) -and !(Test-PodeHostname -Hostname $Hostname)) {
        # Invalid hostname supplied
        throw ($PodeLocale.invalidHostnameSuppliedExceptionMessage -f $Hostname)
    }

    if ((Test-PodeHostname -Hostname $Address) -and ($Address -inotin @('localhost', 'all'))) {
        $Hostname = $Address
        $Address = 'localhost'
    }

    if (![string]::IsNullOrWhiteSpace($Hostname) -and $LookupHostname) {
        $Address = (Get-PodeIPAddressesForHostname -Hostname $Hostname -Type All | Select-Object -First 1)
    }

    $_endpoint = Get-PodeEndpointInfo -Address "$($Address):$($Port)"

    # if no name, set to guid, then check uniqueness
    if ([string]::IsNullOrWhiteSpace($Name)) {
        $Name = New-PodeGuid -Secure
    }

    if ($PodeContext.Server.Endpoints.ContainsKey($Name)) {
        # An endpoint named has already been defined
        throw ($PodeLocale.endpointAlreadyDefinedExceptionMessage -f $Name)
    }

    # protocol must be https for client certs, or hosted behind a proxy like iis
    if (($Protocol -ine 'https') -and !(Test-PodeIsHosted) -and $AllowClientCertificate) {
        # Client certificates are only supported on HTTPS endpoints
        throw ($PodeLocale.clientCertificatesOnlySupportedOnHttpsEndpointsExceptionMessage)
    }

    # explicit tls is only supported for smtp/tcp
    if (($type -inotin @('smtp', 'tcp')) -and ($TlsMode -ieq 'explicit')) {
        # The Explicit TLS mode is only supported on SMTPS and TCPS endpoints
        throw ($PodeLocale.explicitTlsModeOnlySupportedOnSmtpsTcpsEndpointsExceptionMessage)
    }

    # ack message is only for smtp/tcp
    if (($type -inotin @('smtp', 'tcp')) -and ![string]::IsNullOrEmpty($Acknowledge)) {
        # The Acknowledge message is only supported on SMTP and TCP endpoints
        throw ($PodeLocale.acknowledgeMessageOnlySupportedOnSmtpTcpEndpointsExceptionMessage)
    }

    # crlf message end is only for tcp
    if (($type -ine 'tcp') -and $CRLFMessageEnd) {
        # The CRLF message end check is only supported on TCP endpoints
        throw ($PodeLocale.crlfMessageEndCheckOnlySupportedOnTcpEndpointsExceptionMessage)
    }

    # new endpoint object
    $obj = @{
        Name         = $Name
        Description  = $Description
        DualMode     = $DualMode
        Address      = $null
        RawAddress   = $null
        Port         = $null
        IsIPAddress  = $true
        HostName     = $Hostname
        FriendlyName = $Hostname
        Url          = $null
        Ssl          = @{
            Enabled   = (@('https', 'wss', 'smtps', 'tcps') -icontains $Protocol)
            Protocols = $PodeContext.Server.Sockets.Ssl.Protocols
        }
        Protocol     = $Protocol.ToLowerInvariant()
        Type         = $type.ToLowerInvariant()
        Runspace     = @{
            PoolName = (Get-PodeEndpointRunspacePoolName -Protocol $Protocol)
        }
        Default      = $Default.IsPresent
        Certificate  = @{
            Raw                    = $X509Certificate
            SelfSigned             = $SelfSigned
            AllowClientCertificate = $AllowClientCertificate
            TlsMode                = $TlsMode
        }
        Tcp          = @{
            Acknowledge    = $Acknowledge
            CRLFMessageEnd = $CRLFMessageEnd
        }
    }

    # set ssl protocols
    if (!(Test-PodeIsEmpty $SslProtocol)) {
        $obj.Ssl.Protocols = (ConvertTo-PodeSslProtocol -Protocol $SslProtocol)
    }

    # set the ip for the context (force to localhost for IIS)
    $obj.Address = Get-PodeIPAddress $_endpoint.Host -DualMode:$DualMode
    $obj.IsIPAddress = [string]::IsNullOrWhiteSpace($obj.HostName)

    if ($obj.IsIPAddress) {
        if (!(Test-PodeIPAddressLocalOrAny -IP $obj.Address)) {
            $obj.FriendlyName = "$($obj.Address)"
        }
        else {
            $obj.FriendlyName = 'localhost'
        }
    }

    # set the port for the context, if 0 use a default port for protocol
    $obj.Port = $_endpoint.Port
    if (([int]$obj.Port) -eq 0) {
        $obj.Port = Get-PodeDefaultPort -Protocol $Protocol -TlsMode $TlsMode
    }

    if ($obj.IsIPAddress) {
        $obj.RawAddress = "$($obj.Address):$($obj.Port)"
    }
    else {
        $obj.RawAddress = "$($obj.FriendlyName):$($obj.Port)"
    }

    # set the url of this endpoint
    $obj.Url = "$($obj.Protocol)://$($obj.FriendlyName):$($obj.Port)/"

    # if the address is non-local, then check admin privileges
    if (!$Force -and !(Test-PodeIPAddressLocal -IP $obj.Address) -and !(Test-PodeIsAdminUser)) {
        # Must be running with administrator privileges to listen on non-localhost addresses
        throw ($PodeLocale.mustBeRunningWithAdminPrivilegesExceptionMessage)
    }

    # has this endpoint been added before? (for http/https we can just not add it again)
    $exists = ($PodeContext.Server.Endpoints.Values | Where-Object {
        ($_.FriendlyName -ieq $obj.FriendlyName) -and ($_.Port -eq $obj.Port) -and ($_.Ssl.Enabled -eq $obj.Ssl.Enabled) -and ($_.Type -ieq $obj.Type)
        } | Measure-Object).Count

    # if we're dealing with a certificate, attempt to import it
    if (!(Test-PodeIsHosted) -and ($PSCmdlet.ParameterSetName -ilike 'cert*')) {
        # fail if protocol is not https
        if (@('https', 'wss', 'smtps', 'tcps') -inotcontains $Protocol) {
            # Certificate supplied for non-HTTPS/WSS endpoint
            throw ($PodeLocale.certificateSuppliedForNonHttpsWssEndpointExceptionMessage)
        }

        switch ($PSCmdlet.ParameterSetName.ToLowerInvariant()) {
            'certfile' {
                $obj.Certificate.Raw = Get-PodeCertificateByFile -Certificate $Certificate -Password $CertificatePassword -Key $CertificateKey
            }

            'certthumb' {
                $obj.Certificate.Raw = Get-PodeCertificateByThumbprint -Thumbprint $CertificateThumbprint -StoreName $CertificateStoreName -StoreLocation $CertificateStoreLocation
            }

            'certname' {
                $obj.Certificate.Raw = Get-PodeCertificateByName -Name $CertificateName -StoreName $CertificateStoreName -StoreLocation $CertificateStoreLocation
            }

            'certself' {
                $obj.Certificate.Raw = New-PodeSelfSignedCertificate
            }
        }

        # fail if the cert is expired
        if ($obj.Certificate.Raw.NotAfter -lt [datetime]::Now) {
            # The certificate has expired
            throw ($PodeLocale.certificateExpiredExceptionMessage -f $obj.Certificate.Raw.Subject, $obj.Certificate.Raw.NotAfter)
        }
    }

    if (!$exists) {
        # set server type
        $_type = $type
        if ($_type -iin @('http', 'ws')) {
            $_type = 'http'
        }

        if ($PodeContext.Server.Types -inotcontains $_type) {
            $PodeContext.Server.Types += $_type
        }

        # add the new endpoint
        $PodeContext.Server.Endpoints[$Name] = $obj
        $PodeContext.Server.EndpointsMap["$($obj.Protocol)|$($obj.RawAddress)"] = $Name
    }

    # if RedirectTo is set, attempt to build a redirecting route
    if (!(Test-PodeIsHosted) -and ![string]::IsNullOrWhiteSpace($RedirectTo)) {
        $redir_endpoint = $PodeContext.Server.Endpoints[$RedirectTo]

        # ensure the name exists
        if (Test-PodeIsEmpty $redir_endpoint) {
            # An endpoint named has not been defined for redirecting
            throw ($PodeLocale.endpointNotDefinedForRedirectingExceptionMessage -f $RedirectTo)
        }

        # build the redirect route
        Add-PodeRoute -Method * -Path * -EndpointName $obj.Name -ArgumentList $redir_endpoint -ScriptBlock {
            param($endpoint)
            Move-PodeResponseUrl -EndpointName $endpoint.Name
        }
    }

    # return the endpoint?
    if ($PassThru) {
        return $obj
    }
}

<#
.SYNOPSIS
Get an Endpoint(s).

.DESCRIPTION
Get an Endpoint(s).

.PARAMETER Address
An Address to filter the endpoints.

.PARAMETER Port
A Port to filter the endpoints.

.PARAMETER Hostname
A Hostname to filter the endpoints.

.PARAMETER Protocol
A Protocol to filter the endpoints.

.PARAMETER Name
Any endpoints Names to filter endpoints.

.EXAMPLE
Get-PodeEndpoint -Address 127.0.0.1

.EXAMPLE
Get-PodeEndpoint -Protocol Http

.EXAMPLE
Get-PodeEndpoint -Name Admin, User
#>
function Get-PodeEndpoint {
    [CmdletBinding()]
    param(
        [Parameter()]
        [string]
        $Address,

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

        [Parameter()]
        [string]
        $Hostname,

        [Parameter()]
        [ValidateSet('', 'Http', 'Https', 'Smtp', 'Smtps', 'Tcp', 'Tcps', 'Ws', 'Wss')]
        [string]
        $Protocol,

        [Parameter()]
        [string[]]
        $Name
    )

    if ((Test-PodeHostname -Hostname $Address) -and ($Address -inotin @('localhost', 'all'))) {
        $Hostname = $Address
        $Address = 'localhost'
    }

    $endpoints = $PodeContext.Server.Endpoints.Values

    # if we have an address, filter
    if (![string]::IsNullOrWhiteSpace($Address)) {
        if (($Address -eq '*') -or $PodeContext.Server.IsHeroku) {
            $Address = '0.0.0.0'
        }

        if ($PodeContext.Server.IsIIS -or ($Address -ieq 'localhost')) {
            $Address = '127.0.0.1'
        }

        $endpoints = @(foreach ($endpoint in $endpoints) {
                if ($endpoint.Address.ToString() -ine $Address) {
                    continue
                }

                $endpoint
            })
    }

    # if we have a hostname, filter
    if (![string]::IsNullOrWhiteSpace($Hostname)) {
        $endpoints = @(foreach ($endpoint in $endpoints) {
                if ($endpoint.Hostname.ToString() -ine $Hostname) {
                    continue
                }

                $endpoint
            })
    }

    # if we have a port, filter
    if ($Port -gt 0) {
        if ($PodeContext.Server.IsIIS) {
            $Port = [int]$env:ASPNETCORE_PORT
        }

        if ($PodeContext.Server.IsHeroku) {
            $Port = [int]$env:PORT
        }

        $endpoints = @(foreach ($endpoint in $endpoints) {
                if ($endpoint.Port -ne $Port) {
                    continue
                }

                $endpoint
            })
    }

    # if we have a protocol, filter
    if (![string]::IsNullOrWhiteSpace($Protocol)) {
        if ($PodeContext.Server.IsIIS -or $PodeContext.Server.IsHeroku) {
            $Protocol = 'Http'
        }

        $endpoints = @(foreach ($endpoint in $endpoints) {
                if ($endpoint.Protocol -ine $Protocol) {
                    continue
                }

                $endpoint
            })
    }

    # further filter by endpoint names
    if (($null -ne $Name) -and ($Name.Length -gt 0)) {
        $endpoints = @(foreach ($_name in $Name) {
                foreach ($endpoint in $endpoints) {
                    if ($endpoint.Name -ine $_name) {
                        continue
                    }

                    $endpoint
                }
            })
    }

    # return
    return $endpoints
}

<#
.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
}
src\Public\Events.ps1
<#
.SYNOPSIS
Registers a script to be run when a certain server event occurs within Pode

.DESCRIPTION
Registers a script to be run when a certain server event occurs within Pode, such as Start, Terminate, and Restart.

.PARAMETER Type
The Type of event to be registered.

.PARAMETER Name
A unique Name for the registered event.

.PARAMETER ScriptBlock
A ScriptBlock to invoke when the event is triggered.

.PARAMETER ArgumentList
An array of arguments to supply to the ScriptBlock.

.EXAMPLE
Register-PodeEvent -Type Start -Name 'Event1' -ScriptBlock { }
#>
function Register-PodeEvent {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $true)]
        [ValidateSet('Start', 'Terminate', 'Restart', 'Browser', 'Crash', 'Stop', 'Running')]
        [string]
        $Type,

        [Parameter(Mandatory = $true)]
        [string]
        $Name,

        [Parameter(Mandatory = $true, ValueFromPipeline = $true)]
        [scriptblock]
        $ScriptBlock,

        [Parameter()]
        [object[]]
        $ArgumentList
    )

    # error if already registered
    if (Test-PodeEvent -Type $Type -Name $Name) {
        throw ($PodeLocale.eventAlreadyRegisteredExceptionMessage -f $Type, $Name) # "$($Type) event already registered: $($Name)"
    }

    # check for scoped vars
    $ScriptBlock, $usingVars = Convert-PodeScopedVariables -ScriptBlock $ScriptBlock -PSSession $PSCmdlet.SessionState

    # add event
    $PodeContext.Server.Events[$Type][$Name] = @{
        Name           = $Name
        ScriptBlock    = $ScriptBlock
        UsingVariables = $usingVars
        Arguments      = $ArgumentList
    }
}

<#
.SYNOPSIS
Unregisters an event that has been registered with the specified Name.

.DESCRIPTION
Unregisters an event that has been registered with the specified Name.

.PARAMETER Type
The Type of the event to unregister.

.PARAMETER Name
The Name of the event to unregister.

.EXAMPLE
Unregister-PodeEvent -Type Start -Name 'Event1'
#>
function Unregister-PodeEvent {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $true)]
        [ValidateSet('Start', 'Terminate', 'Restart', 'Browser', 'Crash', 'Stop', 'Running')]
        [string]
        $Type,

        [Parameter(Mandatory = $true)]
        [string]
        $Name
    )

    # error if not registered
    if (!(Test-PodeEvent -Type $Type -Name $Name)) {
        throw ($PodeLocale.noEventRegisteredExceptionMessage -f $Type, $Name) # "No $($Type) event registered: $($Name)"
    }

    # remove event
    $null = $PodeContext.Server.Events[$Type].Remove($Name)
}

<#
.SYNOPSIS
Tests if an event has been registered with the specified Name.

.DESCRIPTION
Tests if an event has been registered with the specified Name.

.PARAMETER Type
The Type of the event to test.

.PARAMETER Name
The Name of the event to test.

.EXAMPLE
Test-PodeEvent -Type Start -Name 'Event1'
#>
function Test-PodeEvent {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $true)]
        [ValidateSet('Start', 'Terminate', 'Restart', 'Browser', 'Crash', 'Stop', 'Running')]
        [string]
        $Type,

        [Parameter(Mandatory = $true)]
        [string]
        $Name
    )

    return $PodeContext.Server.Events[$Type].Contains($Name)
}

<#
.SYNOPSIS
Retrieves an event.

.DESCRIPTION
Retrieves an event.

.PARAMETER Type
The Type of event to retrieve.

.PARAMETER Name
The Name of the event to retrieve.

.EXAMPLE
Get-PodeEvent -Type Start -Name 'Event1'
#>
function Get-PodeEvent {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $true)]
        [ValidateSet('Start', 'Terminate', 'Restart', 'Browser', 'Crash', 'Stop', 'Running')]
        [string]
        $Type,

        [Parameter(Mandatory = $true)]
        [string]
        $Name
    )

    return $PodeContext.Server.Events[$Type][$Name]
}

<#
.SYNOPSIS
Clears an event of all registered scripts.

.DESCRIPTION
Clears an event of all registered scripts.

.PARAMETER Type
The Type of event to clear.

.EXAMPLE
Clear-PodeEvent -Type Start
#>
function Clear-PodeEvent {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $true)]
        [ValidateSet('Start', 'Terminate', 'Restart', 'Browser', 'Crash', 'Stop', 'Running')]
        [string]
        $Type
    )

    $null = $PodeContext.Server.Events[$Type].Clear()
}

<#
.SYNOPSIS
Automatically loads event ps1 files

.DESCRIPTION
Automatically loads event ps1 files from either a /events folder, or a custom folder. Saves space dot-sourcing them all one-by-one.

.PARAMETER Path
Optional Path to a folder containing ps1 files, can be relative or literal.

.EXAMPLE
Use-PodeEvents

.EXAMPLE
Use-PodeEvents -Path './my-events'
#>
function Use-PodeEvents {
    [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseSingularNouns', '')]
    [CmdletBinding()]
    param(
        [Parameter()]
        [string]
        $Path
    )

    Use-PodeFolder -Path $Path -DefaultPath 'events'
}
src\Public\FileWatchers.ps1
<#
.SYNOPSIS
Adds a new File Watcher to monitor file changes in a directory.

.DESCRIPTION
Adds a new File Watcher to monitor file changes in a directory.

.PARAMETER Name
An optional Name for the File Watcher. (Default: GUID)

.PARAMETER EventName
An optional EventName to be monitored. Note: '*' refers to all event names. (Default: Changed, Created, Deleted, Renamed)

.PARAMETER Path
The Path to a directory which contains the files to be monitored.

.PARAMETER ScriptBlock
The ScriptBlock defining logic to be run when events are triggered.

.PARAMETER FilePath
A literal, or relative, path to a file containing a ScriptBlock for the File Watcher's logic.

.PARAMETER ArgumentList
A hashtable of arguments to supply to the File Watcher's ScriptBlock.

.PARAMETER NotifyFilter
The attributes on files to monitor and notify about. (Default: FileName, DirectoryName, LastWrite, CreationTime)

.PARAMETER Exclude
An optional array of file patterns to be excluded.

.PARAMETER Include
An optional array of file patterns to be included. (Default: *.*)

.PARAMETER InternalBufferSize
The InternalBufferSize of the file monitor, used when temporarily storing events. (Default: 8kb)

.PARAMETER NoSubdirectories
If supplied, the File Watcher will only monitor files in the specified directory path, and not in all sub-directories as well.

.PARAMETER PassThru
If supplied, the File Watcher object registered will be returned.

.EXAMPLE
Add-PodeFileWatcher -Path 'C:/Projects/:project/src' -Include '*.ps1' -ScriptBlock {}

.EXAMPLE
Add-PodeFileWatcher -Path 'C:/Websites/:site' -Include '*.config' -EventName Changed -ScriptBlock {}

.EXAMPLE
Add-PodeFileWatcher -Path '/temp/logs' -EventName Created -NotifyFilter CreationTime -ScriptBlock {}

.EXAMPLE
$watcher = Add-PodeFileWatcher -Path '/temp/logs' -Exclude *.txt -ScriptBlock {} -PassThru
#>
function Add-PodeFileWatcher {
    [CmdletBinding(DefaultParameterSetName = 'Script')]
    param(
        [Parameter()]
        [string]
        $Name = $null,

        [Parameter()]
        [ValidateSet('Changed', 'Created', 'Deleted', 'Renamed', 'Existed', '*')]
        [string[]]
        $EventName = @('Changed', 'Created', 'Deleted', 'Renamed'),

        [Parameter(Mandatory = $true)]
        [string]
        $Path,

        [Parameter(Mandatory = $true, ParameterSetName = 'Script')]
        [scriptblock]
        $ScriptBlock,

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

        [Parameter()]
        [object[]]
        $ArgumentList,

        [Parameter()]
        [System.IO.NotifyFilters[]]
        $NotifyFilter = @('FileName', 'DirectoryName', 'LastWrite', 'CreationTime'),

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

        [Parameter()]
        [ValidateNotNullOrEmpty()]
        [string[]]
        $Include = '*.*',

        [Parameter()]
        [ValidateRange(4kb, 64kb)]
        [int]
        $InternalBufferSize = 8kb,

        [switch]
        $NoSubdirectories,

        [switch]
        $PassThru
    )

    # set random name
    if ([string]::IsNullOrEmpty($Name)) {
        $Name = New-PodeGuid -Secure
    }

    # set all for * event
    if ('*' -iin $EventName) {
        $EventName = @('Changed', 'Created', 'Deleted', 'Renamed', 'Existed')
    }

    # resolve path if relative
    if (!(Test-PodeIsPSCore)) {
        $Path = Convert-PodePlaceholder -Path $Path -Prepend '%' -Append '%'
    }

    $Path = Get-PodeRelativePath -Path $Path -JoinRoot -Resolve

    if (!(Test-PodeIsPSCore)) {
        $Path = Convert-PodePlaceholder -Path $Path -Pattern '\%(?<tag>[\w]+)\%' -Prepend ':' -Append ([string]::Empty)
    }

    # resolve path, and test it
    $hasPlaceholders = Test-PodePlaceholder -Path $Path
    if ($hasPlaceholders) {
        $rgxPath = Update-PodeRouteSlash -Path $Path -NoLeadingSlash
        $rgxPath = Resolve-PodePlaceholder -Path $rgxPath -Slashes
        $Path = $Path -ireplace (Get-PodePlaceholderRegex), '*'
    }

    # test path to make sure it exists
    if (!(Test-PodePath $Path -NoStatus)) {
        # Path does not exist
        throw ($PodeLocale.pathNotExistExceptionMessage -f $Path)
    }

    # test if we have the file watcher already
    if (Test-PodeFileWatcher -Name $Name) {
        # A File Watcher named has already been defined
        throw ($PodeLocale.fileWatcherAlreadyDefinedExceptionMessage -f $Name)
    }

    # if we have a file path supplied, load that path as a scriptblock
    if ($PSCmdlet.ParameterSetName -ieq 'file') {
        $ScriptBlock = Convert-PodeFileToScriptBlock -FilePath $FilePath
    }

    # check for scoped vars
    $ScriptBlock, $usingVars = Convert-PodeScopedVariables -ScriptBlock $ScriptBlock -PSSession $PSCmdlet.SessionState

    # enable the file watcher threads
    $PodeContext.Fim.Enabled = $true

    # resolve the path's widacards if any
    $paths = @($Path)
    if ($Path.Contains('*')) {
        $paths = @(Get-ChildItem -Path $Path -Directory -Force | Select-Object -ExpandProperty FullName)
    }

    # add the file watcher
    $PodeContext.Fim.Items[$Name] = @{
        Name                  = $Name
        Events                = @($EventName)
        Path                  = $Path
        Placeholders          = @{
            Path  = $rgxPath
            Exist = $hasPlaceholders
        }
        Script                = $ScriptBlock
        UsingVariables        = $usingVars
        Arguments             = $ArgumentList
        NotifyFilters         = @($NotifyFilter)
        IncludeSubdirectories = !$NoSubdirectories.IsPresent
        InternalBufferSize    = $InternalBufferSize
        Exclude               = $Exclude
        Include               = $Include
        Paths                 = $paths
    }

    # return?
    if ($PassThru) {
        return $PodeContext.Fim.Items[$Name]
    }
}

<#
.SYNOPSIS
Tests whether the passed File Watcher exists.

.DESCRIPTION
Tests whether the passed File Watcher exists by its name.

.PARAMETER Name
The Name of the File Watcher.

.EXAMPLE
if (Test-PodeFileWatcher -Name WatcherName) { }
#>
function Test-PodeFileWatcher {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $true)]
        [string]
        $Name
    )

    return (($null -ne $PodeContext.Fim.Items) -and $PodeContext.Fim.Items.ContainsKey($Name))
}

<#
.SYNOPSIS
Returns any defined File Watchers.

.DESCRIPTION
Returns any defined File Watchers.

.PARAMETER Name
An optional File Watcher Name(s) to be returned.

.EXAMPLE
Get-PodeFileWatcher

.EXAMPLE
Get-PodeFileWatcher -Name Name1, Name2
#>
function Get-PodeFileWatcher {
    [CmdletBinding()]
    param(
        [Parameter()]
        [string[]]
        $Name
    )

    $watchers = $PodeContext.Fim.Items.Values

    # further filter by file watcher names
    if (($null -ne $Name) -and ($Name.Length -gt 0)) {
        $watchers = @(foreach ($_name in $Name) {
                foreach ($watcher in $watchers) {
                    if ($watcher.Name -ine $_name) {
                        continue
                    }

                    $watcher
                }
            })
    }

    # return
    return $watchers
}

<#
.SYNOPSIS
Removes a specific File Watchers.

.DESCRIPTION
Removes a specific File Watchers.

.PARAMETER Name
The Name of the File Watcher to be removed.

.EXAMPLE
Remove-PodeFileWatcher -Name 'Logs'
#>
function Remove-PodeFileWatcher {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $true)]
        [string]
        $Name
    )

    $null = $PodeContext.Fim.Items.Remove($Name)
}

<#
.SYNOPSIS
Removes all File Watchers.

.DESCRIPTION
Removes all File Watchers.

.EXAMPLE
Clear-PodeFileWatchers
#>
function Clear-PodeFileWatchers {
    [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseSingularNouns', '')]
    [CmdletBinding()]
    param()

    $PodeContext.Fim.Items.Clear()
}

<#
.SYNOPSIS
Automatically loads File Watchers ps1 files

.DESCRIPTION
Automatically loads File Watchers ps1 files from either a /filewatcher folder, or a custom folder. Saves space dot-sourcing them all one-by-one.

.PARAMETER Path
Optional Path to a folder containing ps1 files, can be relative or literal.

.EXAMPLE
Use-PodeFileWatchers

.EXAMPLE
Use-PodeFileWatchers -Path './my-watchers'
#>
function Use-PodeFileWatchers {
    [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseSingularNouns', '')]
    [CmdletBinding()]
    param(
        [Parameter()]
        [string]
        $Path
    )

    Use-PodeFolder -Path $Path -DefaultPath 'filewatchers'
}
src\Public\Flash.ps1
<#
.SYNOPSIS
Appends a message to the current flash messages stored in the session.

.DESCRIPTION
Appends a message to the current flash messages stored in the session for the supplied name.
The messages per name are stored as an array.

.PARAMETER Name
The name of the flash message to be appended.

.PARAMETER Message
The message to append.

.EXAMPLE
Add-PodeFlashMessage -Name 'error' -Message 'There was an error'
#>
function Add-PodeFlashMessage {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $true)]
        [string]
        $Name,

        [Parameter(Mandatory = $true)]
        [string]
        $Message
    )

    # if sessions haven't been setup, error
    if (!(Test-PodeSessionsEnabled)) {
        # Sessions are required to use Flash messages
        throw ($PodeLocale.sessionsRequiredForFlashMessagesExceptionMessage)
    }

    # append the message against the key
    if ($null -eq $WebEvent.Session.Data.Flash) {
        $WebEvent.Session.Data.Flash = @{}
    }

    if ($null -eq $WebEvent.Session.Data.Flash[$Name]) {
        $WebEvent.Session.Data.Flash[$Name] = @($Message)
    }
    else {
        $WebEvent.Session.Data.Flash[$Name] += @($Message)
    }
}

<#
.SYNOPSIS
Clears all flash messages.

.DESCRIPTION
Clears all of the flash messages currently stored in the session.

.EXAMPLE
Clear-PodeFlashMessages
#>
function Clear-PodeFlashMessages {
    [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseSingularNouns', '')]
    [CmdletBinding()]
    param()

    # if sessions haven't been setup, error
    if (!(Test-PodeSessionsEnabled)) {
        # Sessions are required to use Flash messages
        throw ($PodeLocale.sessionsRequiredForFlashMessagesExceptionMessage)
    }

    # clear all keys
    if ($null -ne $WebEvent.Session.Data.Flash) {
        $WebEvent.Session.Data.Flash = @{}
    }
}

<#
.SYNOPSIS
Returns all flash messages stored against a name, and the clears the messages.

.DESCRIPTION
Returns all of the flash messages, as an array, currently stored for the name within the session.
Once retrieved, the messages are removed from storage.

.PARAMETER Name
The name of the flash messages to return.

.EXAMPLE
Get-PodeFlashMessage -Name 'error'
#>
function Get-PodeFlashMessage {
    [CmdletBinding()]
    [OutputType([System.Object[]])]
    param(
        [Parameter(Mandatory = $true)]
        [string]
        $Name
    )

    # if sessions haven't been setup, error
    if (!(Test-PodeSessionsEnabled)) {
        # Sessions are required to use Flash messages
        throw ($PodeLocale.sessionsRequiredForFlashMessagesExceptionMessage)
    }

    # retrieve messages from session, then delete it
    if ($null -eq $WebEvent.Session.Data.Flash) {
        return @()
    }

    $v = @($WebEvent.Session.Data.Flash[$Name])
    $WebEvent.Session.Data.Flash.Remove($Name)

    if (Test-PodeIsEmpty $v) {
        return @()
    }

    return @($v)
}

<#
.SYNOPSIS
Returns all of the names for each of the messages currently being stored.

.DESCRIPTION
Returns all of the names for each of the messages currently being stored. This does not clear the messages.

.EXAMPLE
Get-PodeFlashMessageNames
#>
function Get-PodeFlashMessageNames {
    [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseSingularNouns', '')]
    [CmdletBinding()]
    [OutputType([System.Object[]])]
    param()

    # if sessions haven't been setup, error
    if (!(Test-PodeSessionsEnabled)) {
        # Sessions are required to use Flash messages
        throw ($PodeLocale.sessionsRequiredForFlashMessagesExceptionMessage)
    }

    # return list of all current keys
    if ($null -eq $WebEvent.Session.Data.Flash) {
        return @()
    }

    return @($WebEvent.Session.Data.Flash.Keys)
}

<#
.SYNOPSIS
Removes flash messages for the supplied name currently being stored.

.DESCRIPTION
Removes flash messages for the supplied name currently being stored.

.PARAMETER Name
The name of the flash messages to remove.

.EXAMPLE
Remove-PodeFlashMessage -Name 'error'
#>
function Remove-PodeFlashMessage {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $true)]
        [string]
        $Name
    )

    # if sessions haven't been setup, error
    if (!(Test-PodeSessionsEnabled)) {
        # Sessions are required to use Flash messages
        throw ($PodeLocale.sessionsRequiredForFlashMessagesExceptionMessage)
    }

    # remove key from flash messages
    if ($null -ne $WebEvent.Session.Data.Flash) {
        $WebEvent.Session.Data.Flash.Remove($Name)
    }
}

<#
.SYNOPSIS
Tests if there are any flash messages currently being stored for a supplied name.

.DESCRIPTION
Tests if there are any flash messages currently being stored for a supplied name.

.PARAMETER Name
The name of the flash message to check.

.EXAMPLE
Test-PodeFlashMessage -Name 'error'
#>
function Test-PodeFlashMessage {
    [CmdletBinding()]
    [OutputType([bool])]
    param(
        [Parameter(Mandatory = $true)]
        [string]
        $Name
    )

    # if sessions haven't been setup, error
    if (!(Test-PodeSessionsEnabled)) {
        # Sessions are required to use Flash messages
        throw ($PodeLocale.sessionsRequiredForFlashMessagesExceptionMessage)
    }

    # return if a key exists as a flash message
    if ($null -eq $WebEvent.Session.Data.Flash) {
        return $false
    }

    return $WebEvent.Session.Data.Flash.ContainsKey($Name)
}
src\Public\Handlers.ps1
<#
.SYNOPSIS
Adds a Handler of a specific Type.

.DESCRIPTION
Adds a Handler of a specific Type.

.PARAMETER Type
The Type of the Handler.

.PARAMETER Name
The Name of the Handler.

.PARAMETER ScriptBlock
The ScriptBlock for the Handler's main logic.

.PARAMETER FilePath
A literal, or relative, path to a file containing a ScriptBlock for the Handler's main logic.

.PARAMETER ArgumentList
An array of arguments to supply to the Handler's ScriptBlock.

.EXAMPLE
Add-PodeHandler -Type Smtp -Name 'Main' -ScriptBlock { /* logic */ }

.EXAMPLE
Add-PodeHandler -Type Service -Name 'Looper' -ScriptBlock { /* logic */ }

.EXAMPLE
Add-PodeHandler -Type Smtp -Name 'Main' -ScriptBlock { /* logic */ } -ArgumentList 'arg1', 'arg2'
#>
function Add-PodeHandler {
    [CmdletBinding(DefaultParameterSetName = 'Script')]
    param(
        [Parameter(Mandatory = $true)]
        [ValidateSet('Service', 'Smtp')]
        [string]
        $Type,

        [Parameter(Mandatory = $true)]
        [string]
        $Name,

        [Parameter(Mandatory = $true, ParameterSetName = 'Script')]
        [scriptblock]
        $ScriptBlock,

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

        [Parameter()]
        [object[]]
        $ArgumentList
    )

    # error if serverless
    Test-PodeIsServerless -FunctionName 'Add-PodeHandler' -ThrowError

    # ensure handler isn't already set
    if ($PodeContext.Server.Handlers[$Type].ContainsKey($Name)) {
        # [Type] Name: Handler already defined
        throw ($PodeLocale.handlerAlreadyDefinedExceptionMessage -f $Type, $Name)
    }

    # if we have a file path supplied, load that path as a scriptblock
    if ($PSCmdlet.ParameterSetName -ieq 'file') {
        $ScriptBlock = Convert-PodeFileToScriptBlock -FilePath $FilePath
    }

    # check for scoped vars
    $ScriptBlock, $usingVars = Convert-PodeScopedVariables -ScriptBlock $ScriptBlock -PSSession $PSCmdlet.SessionState

    # add the handler
    Write-Verbose "Adding Handler: [$($Type)] $($Name)"
    $PodeContext.Server.Handlers[$Type][$Name] += @(@{
            Logic          = $ScriptBlock
            UsingVariables = $usingVars
            Arguments      = $ArgumentList
        })
}

<#
.SYNOPSIS
Remove a specific Handler.

.DESCRIPTION
Remove a specific Handler.

.PARAMETER Type
The type of the Handler to be removed.

.PARAMETER Name
The name of the Handler to be removed.

.EXAMPLE
Remove-PodeHandler -Type Smtp -Name 'Main'
#>
function Remove-PodeHandler {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $true)]
        [ValidateSet('Service', 'Smtp')]
        [string]
        $Type,

        [Parameter(Mandatory = $true)]
        [string]
        $Name
    )

    # ensure handler does exist
    if (!$PodeContext.Server.Handlers[$Type].ContainsKey($Name)) {
        return
    }

    # remove the handler
    $null = $PodeContext.Server.Handlers[$Type].Remove($Name)
}

<#
.SYNOPSIS
Removes all added Handlers, or Handlers of a specific Type.

.DESCRIPTION
Removes all added Handlers, or Handlers of a specific Type.

.PARAMETER Type
The Type of Handlers to remove.

.EXAMPLE
Clear-PodeHandlers -Type Smtp
#>
function Clear-PodeHandlers {
    [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseSingularNouns', '')]
    [CmdletBinding()]
    param(
        [Parameter()]
        [ValidateSet('', 'Service', 'Smtp')]
        [string]
        $Type
    )

    if (![string]::IsNullOrWhiteSpace($Type)) {
        $PodeContext.Server.Handlers[$Type].Clear()
    }
    else {
        $PodeContext.Server.Handlers.Keys.Clone() | ForEach-Object {
            $PodeContext.Server.Handlers[$_].Clear()
        }
    }
}

<#
.SYNOPSIS
Automatically loads handler ps1 files

.DESCRIPTION
Automatically loads handler ps1 files from either a /handler folder, or a custom folder. Saves space dot-sourcing them all one-by-one.

.PARAMETER Path
Optional Path to a folder containing ps1 files, can be relative or literal.

.EXAMPLE
Use-PodeHandlers

.EXAMPLE
Use-PodeHandlers -Path './my-handlers'
#>
function Use-PodeHandlers {
    [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseSingularNouns', '')]
    [CmdletBinding()]
    param(
        [Parameter()]
        [string]
        $Path
    )

    Use-PodeFolder -Path $Path -DefaultPath 'handlers'
}
src\Public\Headers.ps1
<#
.SYNOPSIS
Appends a header against the Response.

.DESCRIPTION
Appends a header against the Response. If the current context is serverless, then this function acts like Set-PodeHeader.

.PARAMETER Name
The name of the header.

.PARAMETER Value
The value to set against the header.

.PARAMETER Secret
If supplied, the secret with which to sign the header's value.

.PARAMETER Strict
If supplied, the Secret will be extended using the client request's UserAgent and RemoteIPAddress.

.EXAMPLE
Add-PodeHeader -Name 'X-AuthToken' -Value 'AA-BB-CC-33'
#>
function Add-PodeHeader {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $true)]
        [string]
        $Name,

        [Parameter(Mandatory = $true)]
        [string]
        $Value,

        [Parameter()]
        [string]
        $Secret,

        [switch]
        $Strict
    )

    # sign the value if we have a secret
    if (![string]::IsNullOrWhiteSpace($Secret)) {
        $Value = (Invoke-PodeValueSign -Value $Value -Secret $Secret -Strict:$Strict)
    }

    # add the header to the response
    if ($PodeContext.Server.IsServerless) {
        $WebEvent.Response.Headers[$Name] = $Value
    }
    else {
        $WebEvent.Response.Headers.Add($Name, $Value)
    }
}

<#
.SYNOPSIS
Appends multiple headers against the Response.

.DESCRIPTION
Appends multiple headers against the Response. If the current context is serverless, then this function acts like Set-PodeHeaderBulk.

.PARAMETER Values
A hashtable of headers to be appended.

.PARAMETER Secret
If supplied, the secret with which to sign the header values.

.PARAMETER Strict
If supplied, the Secret will be extended using the client request's UserAgent and RemoteIPAddress.

.EXAMPLE
Add-PodeHeaderBulk -Values @{ Name1 = 'Value1'; Name2 = 'Value2' }
#>
function Add-PodeHeaderBulk {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $true)]
        [hashtable]
        $Values,

        [Parameter()]
        [string]
        $Secret,

        [switch]
        $Strict
    )

    foreach ($key in $Values.Keys) {
        $value = $Values[$key]

        # sign the value if we have a secret
        if (![string]::IsNullOrWhiteSpace($Secret)) {
            $value = (Invoke-PodeValueSign -Value $value -Secret $Secret -Strict:$Strict)
        }

        # add the header to the response
        if ($PodeContext.Server.IsServerless) {
            $WebEvent.Response.Headers[$key] = $value
        }
        else {
            $WebEvent.Response.Headers.Add($key, $value)
        }
    }
}

<#
.SYNOPSIS
Tests if a header is present on the Request.

.DESCRIPTION
Tests if a header is present on the Request.

.PARAMETER Name
The name of the header to test.

.EXAMPLE
Test-PodeHeader -Name 'X-AuthToken'
#>
function Test-PodeHeader {
    [CmdletBinding()]
    [OutputType([bool])]
    param(
        [Parameter(Mandatory = $true)]
        [string]
        $Name
    )

    $header = (Get-PodeHeader -Name $Name)
    return (![string]::IsNullOrWhiteSpace($header))
}

<#
.SYNOPSIS
Retrieves the value of a header from the Request.

.DESCRIPTION
Retrieves the value of a header from the Request.

.PARAMETER Name
The name of the header to retrieve.

.PARAMETER Secret
The secret used to unsign the header's value.

.PARAMETER Strict
If supplied, the Secret will be extended using the client request's UserAgent and RemoteIPAddress.

.EXAMPLE
Get-PodeHeader -Name 'X-AuthToken'
#>
function Get-PodeHeader {
    [CmdletBinding()]
    [OutputType([string])]
    param(
        [Parameter(Mandatory = $true)]
        [string]
        $Name,

        [Parameter()]
        [string]
        $Secret,

        [switch]
        $Strict
    )

    # get the value for the header from the request
    $header = $WebEvent.Request.Headers.$Name

    # if a secret was supplied, attempt to unsign the header's value
    if (![string]::IsNullOrWhiteSpace($Secret)) {
        $header = (Invoke-PodeValueUnsign -Value $header -Secret $Secret -Strict:$Strict)
    }

    return $header
}

<#
.SYNOPSIS
Sets a header on the Response, clearing all current values for the header.

.DESCRIPTION
Sets a header on the Response, clearing all current values for the header.

.PARAMETER Name
The name of the header.

.PARAMETER Value
The value to set against the header.

.PARAMETER Secret
If supplied, the secret with which to sign the header's value.

.PARAMETER Strict
If supplied, the Secret will be extended using the client request's UserAgent and RemoteIPAddress.

.EXAMPLE
Set-PodeHeader -Name 'X-AuthToken' -Value 'AA-BB-CC-33'
#>
function Set-PodeHeader {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $true)]
        [string]
        $Name,

        [Parameter(Mandatory = $true)]
        [string]
        $Value,

        [Parameter()]
        [string]
        $Secret,

        [switch]
        $Strict
    )

    # sign the value if we have a secret
    if (![string]::IsNullOrWhiteSpace($Secret)) {
        $Value = (Invoke-PodeValueSign -Value $Value -Secret $Secret -Strict:$Strict)
    }

    # set the header on the response
    if ($PodeContext.Server.IsServerless) {
        $WebEvent.Response.Headers[$Name] = $Value
    }
    else {
        $WebEvent.Response.Headers.Set($Name, $Value)
    }
}

<#
.SYNOPSIS
Sets multiple headers on the Response, clearing all current values for the header.

.DESCRIPTION
Sets multiple headers on the Response, clearing all current values for the header.

.PARAMETER Values
A hashtable of headers to be set.

.PARAMETER Secret
If supplied, the secret with which to sign the header values.

.PARAMETER Strict
If supplied, the Secret will be extended using the client request's UserAgent and RemoteIPAddress.

.EXAMPLE
Set-PodeHeaderBulk -Values @{ Name1 = 'Value1'; Name2 = 'Value2' }
#>
function Set-PodeHeaderBulk {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $true)]
        [hashtable]
        $Values,

        [Parameter()]
        [string]
        $Secret,

        [switch]
        $Strict
    )

    foreach ($key in $Values.Keys) {
        $value = $Values[$key]

        # sign the value if we have a secret
        if (![string]::IsNullOrWhiteSpace($Secret)) {
            $value = (Invoke-PodeValueSign -Value $value -Secret $Secret -Strict:$Strict)
        }

        # set the header on the response
        if ($PodeContext.Server.IsServerless) {
            $WebEvent.Response.Headers[$key] = $value
        }
        else {
            $WebEvent.Response.Headers.Set($key, $value)
        }
    }
}

<#
.SYNOPSIS
Tests if a header on the Request is validly signed.

.DESCRIPTION
Tests if a header on the Request is validly signed, by attempting to unsign it using some secret.

.PARAMETER Name
The name of the header to test.

.PARAMETER Secret
A secret to use for attempting to unsign the header's value.

.PARAMETER Strict
If supplied, the Secret will be extended using the client request's UserAgent and RemoteIPAddress.

.EXAMPLE
Test-PodeHeaderSigned -Name 'X-Header-Name' -Secret 'hunter2'
#>
function Test-PodeHeaderSigned {
    [CmdletBinding()]
    [OutputType([bool])]
    param(
        [Parameter(Mandatory = $true)]
        [string]
        $Name,

        [Parameter()]
        [string]
        $Secret,

        [switch]
        $Strict
    )

    $header = Get-PodeHeader -Name $Name
    return Test-PodeValueSigned -Value $header -Secret $Secret -Strict:$Strict
}
src\Public\Logging.ps1
<#
.SYNOPSIS
Create a new method of outputting logs.

.DESCRIPTION
Create a new method of outputting logs.

.PARAMETER Terminal
If supplied, will use the inbuilt Terminal logging output method.

.PARAMETER File
If supplied, will use the inbuilt File logging output method.

.PARAMETER Path
The File Path of where to store the logs.

.PARAMETER Name
The File Name to prepend new log files using.

.PARAMETER EventViewer
If supplied, will use the inbuilt Event Viewer logging output method.

.PARAMETER EventLogName
Optional Log Name for the Event Viewer (Default: Application)

.PARAMETER Source
Optional Source for the Event Viewer (Default: Pode)

.PARAMETER EventID
Optional EventID for the Event Viewer (Default: 0)

.PARAMETER Batch
An optional batch size to write log items in bulk (Default: 1)

.PARAMETER BatchTimeout
An optional batch timeout, in seconds, to send items off for writing if a log item isn't received (Default: 0)

.PARAMETER MaxDays
The maximum number of days to keep logs, before Pode automatically removes them.

.PARAMETER MaxSize
The maximum size of a log file, before Pode starts writing to a new log file.

.PARAMETER Custom
If supplied, will allow you to create a Custom Logging output method.

.PARAMETER ScriptBlock
The ScriptBlock that defines how to output a log item.

.PARAMETER ArgumentList
An array of arguments to supply to the Custom Logging output method's ScriptBlock.

.EXAMPLE
$term_logging = New-PodeLoggingMethod -Terminal

.EXAMPLE
$file_logging = New-PodeLoggingMethod -File -Path ./logs -Name 'requests'

.EXAMPLE
$custom_logging = New-PodeLoggingMethod -Custom -ScriptBlock { /* logic */ }
#>
function New-PodeLoggingMethod {
    [CmdletBinding(DefaultParameterSetName = 'Terminal')]
    [OutputType([hashtable])]
    param(
        [Parameter(ParameterSetName = 'Terminal')]
        [switch]
        $Terminal,

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

        [Parameter(ParameterSetName = 'File')]
        [string]
        $Path = './logs',

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

        [Parameter(ParameterSetName = 'EventViewer')]
        [switch]
        $EventViewer,

        [Parameter(ParameterSetName = 'EventViewer')]
        [string]
        $EventLogName = 'Application',

        [Parameter(ParameterSetName = 'EventViewer')]
        [string]
        $Source = 'Pode',

        [Parameter(ParameterSetName = 'EventViewer')]
        [int]
        $EventID = 0,

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

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

        [Parameter(ParameterSetName = 'File')]
        [ValidateScript({
                if ($_ -lt 0) {
                    # MaxDays must be 0 or greater, but got
                    throw ($PodeLocale.maxDaysInvalidExceptionMessage -f $MaxDays)
                }

                return $true
            })]
        [int]
        $MaxDays = 0,

        [Parameter(ParameterSetName = 'File')]
        [ValidateScript({
                if ($_ -lt 0) {
                    # MaxSize must be 0 or greater, but got
                    throw ($PodeLocale.maxSizeInvalidExceptionMessage -f $MaxSize)
                }

                return $true
            })]
        [int]
        $MaxSize = 0,

        [Parameter(ParameterSetName = 'Custom')]
        [switch]
        $Custom,

        [Parameter(Mandatory = $true, ParameterSetName = 'Custom')]
        [ValidateScript({
                if (Test-PodeIsEmpty $_) {
                    # A non-empty ScriptBlock is required for the Custom logging output method
                    throw ($PodeLocale.nonEmptyScriptBlockRequiredForCustomLoggingExceptionMessage)
                }

                return $true
            })]
        [scriptblock]
        $ScriptBlock,

        [Parameter(ParameterSetName = 'Custom')]
        [object[]]
        $ArgumentList
    )

    # batch details
    $batchInfo = @{
        Size       = $Batch
        Timeout    = $BatchTimeout
        LastUpdate = $null
        Items      = @()
        RawItems   = @()
    }

    # return info on appropriate logging type
    switch ($PSCmdlet.ParameterSetName.ToLowerInvariant()) {
        'terminal' {
            return @{
                ScriptBlock = (Get-PodeLoggingTerminalMethod)
                Batch       = $batchInfo
                Arguments   = @{}
            }
        }

        'file' {
            $Path = (Protect-PodeValue -Value $Path -Default './logs')
            $Path = (Get-PodeRelativePath -Path $Path -JoinRoot)
            $null = New-Item -Path $Path -ItemType Directory -Force

            return @{
                ScriptBlock = (Get-PodeLoggingFileMethod)
                Batch       = $batchInfo
                Arguments   = @{
                    Name          = $Name
                    Path          = $Path
                    MaxDays       = $MaxDays
                    MaxSize       = $MaxSize
                    FileId        = 0
                    Date          = $null
                    NextClearDown = [datetime]::Now.Date
                }
            }
        }

        'eventviewer' {
            # only windows
            if (!(Test-PodeIsWindows)) {
                # Event Viewer logging only supported on Windows
                throw ($PodeLocale.eventViewerLoggingSupportedOnWindowsOnlyExceptionMessage)
            }

            # create source
            if (![System.Diagnostics.EventLog]::SourceExists($Source)) {
                $null = [System.Diagnostics.EventLog]::CreateEventSource($Source, $EventLogName)
            }

            return @{
                ScriptBlock = (Get-PodeLoggingEventViewerMethod)
                Batch       = $batchInfo
                Arguments   = @{
                    LogName = $EventLogName
                    Source  = $Source
                    ID      = $EventID
                }
            }
        }

        'custom' {
            $ScriptBlock, $usingVars = Convert-PodeScopedVariables -ScriptBlock $ScriptBlock -PSSession $PSCmdlet.SessionState

            return @{
                ScriptBlock    = $ScriptBlock
                UsingVariables = $usingVars
                Batch          = $batchInfo
                Arguments      = $ArgumentList
            }
        }
    }
}


<#
.SYNOPSIS
Enables Request Logging using a supplied output method.

.DESCRIPTION
Enables Request Logging using a supplied output method.

.PARAMETER Method
The Method to use for output the log entry (From New-PodeLoggingMethod).

.PARAMETER UsernameProperty
An optional property path within the $WebEvent.Auth.User object for the user's Username. (Default: Username).

.PARAMETER Raw
If supplied, the log item returned will be the raw Request item as a hashtable and not a string (for Custom methods).

.EXAMPLE
New-PodeLoggingMethod -Terminal | Enable-PodeRequestLogging
#>
function Enable-PodeRequestLogging {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $true, ValueFromPipeline = $true)]
        [hashtable]
        $Method,

        [Parameter()]
        [string]
        $UsernameProperty,

        [switch]
        $Raw
    )

    Test-PodeIsServerless -FunctionName 'Enable-PodeRequestLogging' -ThrowError

    $name = Get-PodeRequestLoggingName

    # error if it's already enabled
    if ($PodeContext.Server.Logging.Types.Contains($name)) {
        # Request Logging has already been enabled
        throw ($PodeLocale.requestLoggingAlreadyEnabledExceptionMessage)
    }

    # ensure the Method contains a scriptblock
    if (Test-PodeIsEmpty $Method.ScriptBlock) {
        # The supplied output Method for Request Logging requires a valid ScriptBlock
        throw ($PodeLocale.loggingMethodRequiresValidScriptBlockExceptionMessage -f 'Request')
    }

    # username property
    if ([string]::IsNullOrWhiteSpace($UsernameProperty)) {
        $UsernameProperty = 'Username'
    }

    # add the request logger
    $PodeContext.Server.Logging.Types[$name] = @{
        Method      = $Method
        ScriptBlock = (Get-PodeLoggingInbuiltType -Type Requests)
        Properties  = @{
            Username = $UsernameProperty
        }
        Arguments   = @{
            Raw = $Raw
        }
    }
}

<#
.SYNOPSIS
Disables Request Logging.

.DESCRIPTION
Disables Request Logging.

.EXAMPLE
Disable-PodeRequestLogging
#>
function Disable-PodeRequestLogging {
    [CmdletBinding()]
    param()

    Remove-PodeLogger -Name (Get-PodeRequestLoggingName)
}

<#
.SYNOPSIS
Enables Error Logging using a supplied output method.

.DESCRIPTION
Enables Error Logging using a supplied output method.

.PARAMETER Method
The Method to use for output the log entry (From New-PodeLoggingMethod).

.PARAMETER Levels
The Levels of errors that should be logged (default is Error).

.PARAMETER Raw
If supplied, the log item returned will be the raw Error item as a hashtable and not a string (for Custom methods).

.EXAMPLE
New-PodeLoggingMethod -Terminal | Enable-PodeErrorLogging
#>
function Enable-PodeErrorLogging {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $true, ValueFromPipeline = $true)]
        [hashtable]
        $Method,

        [Parameter()]
        [ValidateNotNullOrEmpty()]
        [ValidateSet('Error', 'Warning', 'Informational', 'Verbose', 'Debug', '*')]
        [string[]]
        $Levels = @('Error'),

        [switch]
        $Raw
    )

    $name = Get-PodeErrorLoggingName

    # error if it's already enabled
    if ($PodeContext.Server.Logging.Types.Contains($name)) {
        # Error Logging has already been enabled
        throw ($PodeLocale.errorLoggingAlreadyEnabledExceptionMessage)
    }

    # ensure the Method contains a scriptblock
    if (Test-PodeIsEmpty $Method.ScriptBlock) {
        # The supplied output Method for Error Logging requires a valid ScriptBlock
        throw ($PodeLocale.loggingMethodRequiresValidScriptBlockExceptionMessage -f 'Error')
    }

    # all errors?
    if ($Levels -contains '*') {
        $Levels = @('Error', 'Warning', 'Informational', 'Verbose', 'Debug')
    }

    # add the error logger
    $PodeContext.Server.Logging.Types[$name] = @{
        Method      = $Method
        ScriptBlock = (Get-PodeLoggingInbuiltType -Type Errors)
        Arguments   = @{
            Raw    = $Raw
            Levels = $Levels
        }
    }
}

<#
.SYNOPSIS
Disables Error Logging.

.DESCRIPTION
Disables Error Logging.

.EXAMPLE
Disable-PodeErrorLogging
#>
function Disable-PodeErrorLogging {
    [CmdletBinding()]
    param()

    Remove-PodeLogger -Name (Get-PodeErrorLoggingName)
}

<#
.SYNOPSIS
Adds a custom Logging method for parsing custom log items.

.DESCRIPTION
Adds a custom Logging method for parsing custom log items.

.PARAMETER Name
A unique Name for the Logging method.

.PARAMETER Method
The Method to use for output the log entry (From New-PodeLoggingMethod).

.PARAMETER ScriptBlock
The ScriptBlock defining logic that transforms an item, and returns it for outputting.

.PARAMETER ArgumentList
An array of arguments to supply to the Custom Logger's ScriptBlock.

.EXAMPLE
New-PodeLoggingMethod -Terminal | Add-PodeLogger -Name 'Main' -ScriptBlock { /* logic */ }
#>
function Add-PodeLogger {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $true)]
        [string]
        $Name,

        [Parameter(Mandatory = $true, ValueFromPipeline = $true)]
        [hashtable]
        $Method,

        [Parameter(Mandatory = $true)]
        [ValidateScript({
                if (Test-PodeIsEmpty $_) {
                    # A non-empty ScriptBlock is required for the logging method
                    throw ($PodeLocale.nonEmptyScriptBlockRequiredForLoggingMethodExceptionMessage)
                }

                return $true
            })]
        [scriptblock]
        $ScriptBlock,

        [Parameter()]
        [object[]]
        $ArgumentList
    )

    # ensure the name doesn't already exist
    if ($PodeContext.Server.Logging.Types.ContainsKey($Name)) {
        # Logging method already defined
        throw ($PodeLocale.loggingMethodAlreadyDefinedExceptionMessage -f $Name)
    }

    # ensure the Method contains a scriptblock
    if (Test-PodeIsEmpty $Method.ScriptBlock) {
        # The supplied output Method for the Logging method requires a valid ScriptBlock
        throw ($PodeLocale.loggingMethodRequiresValidScriptBlockExceptionMessage -f $Name)
    }

    # check for scoped vars
    $ScriptBlock, $usingVars = Convert-PodeScopedVariables -ScriptBlock $ScriptBlock -PSSession $PSCmdlet.SessionState

    # add logging method to server
    $PodeContext.Server.Logging.Types[$Name] = @{
        Method         = $Method
        ScriptBlock    = $ScriptBlock
        UsingVariables = $usingVars
        Arguments      = $ArgumentList
    }
}

<#
.SYNOPSIS
Removes a configured Logging method.

.DESCRIPTION
Removes a configured Logging method.

.PARAMETER Name
The Name of the Logging method.

.EXAMPLE
Remove-PodeLogger -Name 'LogName'
#>
function Remove-PodeLogger {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $true, ValueFromPipeline = $true)]
        [string]
        $Name
    )

    $null = $PodeContext.Server.Logging.Types.Remove($Name)
}

<#
.SYNOPSIS
Clears all Logging methods that have been configured.

.DESCRIPTION
Clears all Logging methods that have been configured.

.EXAMPLE
Clear-PodeLoggers
#>
function Clear-PodeLoggers {
    [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseSingularNouns', '')]
    [CmdletBinding()]
    param()

    $PodeContext.Server.Logging.Types.Clear()
}

<#
.SYNOPSIS
Writes and Exception or ErrorRecord using the inbuilt error logging.

.DESCRIPTION
Writes and Exception or ErrorRecord using the inbuilt error logging.

.PARAMETER Exception
An Exception to write.

.PARAMETER ErrorRecord
An ErrorRecord to write.

.PARAMETER Level
The Level of the error being logged.

.PARAMETER CheckInnerException
If supplied, any exceptions are check for inner exceptions. If one is present, this is also logged.

.EXAMPLE
try { /* logic */ } catch { $_ | Write-PodeErrorLog }

.EXAMPLE
[System.Exception]::new('error message') | Write-PodeErrorLog
#>
function Write-PodeErrorLog {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $true, ValueFromPipeline = $true, ParameterSetName = 'Exception')]
        [System.Exception]
        $Exception,

        [Parameter(Mandatory = $true, ValueFromPipeline = $true, ParameterSetName = 'Error')]
        [System.Management.Automation.ErrorRecord]
        $ErrorRecord,

        [Parameter()]
        [ValidateNotNullOrEmpty()]
        [ValidateSet('Error', 'Warning', 'Informational', 'Verbose', 'Debug')]
        [string]
        $Level = 'Error',

        [Parameter(ParameterSetName = 'Exception')]
        [switch]
        $CheckInnerException
    )

    # do nothing if logging is disabled, or error logging isn't setup
    $name = Get-PodeErrorLoggingName
    if (!(Test-PodeLoggerEnabled -Name $name)) {
        return
    }

    # do nothing if the error level isn't present
    $levels = @(Get-PodeErrorLoggingLevel)
    if ($levels -inotcontains $Level) {
        return
    }

    # build error object for what we need
    switch ($PSCmdlet.ParameterSetName.ToLowerInvariant()) {
        'exception' {
            $item = @{
                Category   = $Exception.Source
                Message    = $Exception.Message
                StackTrace = $Exception.StackTrace
            }
        }

        'error' {
            $item = @{
                Category   = $ErrorRecord.CategoryInfo.ToString()
                Message    = $ErrorRecord.Exception.Message
                StackTrace = $ErrorRecord.ScriptStackTrace
            }
        }
    }

    # add general info
    $item['Server'] = $PodeContext.Server.ComputerName
    $item['Level'] = $Level
    $item['Date'] = [datetime]::Now
    $item['ThreadId'] = [int]$ThreadId

    # add the item to be processed
    $null = $PodeContext.LogsToProcess.Add(@{
            Name = $name
            Item = $item
        })

    # for exceptions, check the inner exception
    if ($CheckInnerException -and ($null -ne $Exception.InnerException) -and ![string]::IsNullOrWhiteSpace($Exception.InnerException.Message)) {
        $Exception.InnerException | Write-PodeErrorLog
    }
}

<#
.SYNOPSIS
Write an object to a configured custom Logging method.

.DESCRIPTION
Write an object to a configured custom Logging method.

.PARAMETER Name
The Name of the Logging method.

.PARAMETER InputObject
The Object to write.

.EXAMPLE
$object | Write-PodeLog -Name 'LogName'
#>
function Write-PodeLog {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $true)]
        [string]
        $Name,

        [Parameter(Mandatory = $true, ValueFromPipeline = $true)]
        [object]
        $InputObject
    )

    # do nothing if logging is disabled, or logger isn't setup
    if (!(Test-PodeLoggerEnabled -Name $Name)) {
        return
    }

    # add the item to be processed
    $null = $PodeContext.LogsToProcess.Add(@{
            Name = $Name
            Item = $InputObject
        })
}

<#
.SYNOPSIS
Masks values within a log item to protect sensitive information.

.DESCRIPTION
Masks values within a log item, or any string, to protect sensitive information.
Patterns, and the Mask, can be configured via the server.psd1 configuration file.

.PARAMETER Item
The string Item to mask values.

.EXAMPLE
$value = Protect-PodeLogItem -Item 'Username=Morty, Password=Hunter2'
#>
function Protect-PodeLogItem {
    [CmdletBinding()]
    [OutputType([string])]
    param(
        [Parameter(ValueFromPipeline = $true)]
        [string]
        $Item
    )

    # do nothing if there are no masks
    if (Test-PodeIsEmpty $PodeContext.Server.Logging.Masking.Patterns) {
        return $item
    }

    # attempt to apply each mask
    foreach ($mask in $PodeContext.Server.Logging.Masking.Patterns) {
        if ($Item -imatch $mask) {
            # has both keep before/after
            if ($Matches.ContainsKey('keep_before') -and $Matches.ContainsKey('keep_after')) {
                $Item = ($Item -ireplace $mask, "`${keep_before}$($PodeContext.Server.Logging.Masking.Mask)`${keep_after}")
            }

            # has just keep before
            elseif ($Matches.ContainsKey('keep_before')) {
                $Item = ($Item -ireplace $mask, "`${keep_before}$($PodeContext.Server.Logging.Masking.Mask)")
            }

            # has just keep after
            elseif ($Matches.ContainsKey('keep_after')) {
                $Item = ($Item -ireplace $mask, "$($PodeContext.Server.Logging.Masking.Mask)`${keep_after}")
            }

            # normal mask
            else {
                $Item = ($Item -ireplace $mask, $PodeContext.Server.Logging.Masking.Mask)
            }
        }
    }

    return $Item
}

<#
.SYNOPSIS
Automatically loads logging ps1 files

.DESCRIPTION
Automatically loads logging ps1 files from either a /logging folder, or a custom folder. Saves space dot-sourcing them all one-by-one.

.PARAMETER Path
Optional Path to a folder containing ps1 files, can be relative or literal.

.EXAMPLE
Use-PodeLogging

.EXAMPLE
Use-PodeLogging -Path './my-logging'
#>
function Use-PodeLogging {
    [CmdletBinding()]
    param(
        [Parameter()]
        [string]
        $Path
    )

    Use-PodeFolder -Path $Path -DefaultPath 'logging'
}
src\Public\Metrics.ps1
<#
.SYNOPSIS
Returns the uptime of the server in milliseconds.

.DESCRIPTION
Returns the uptime of the server in milliseconds. You can optionally return the total uptime regardless of server restarts.

.PARAMETER Total
If supplied, the total uptime of the server will be returned, regardless of restarts.

.EXAMPLE
$currentUptime = Get-PodeServerUptime

.EXAMPLE
$totalUptime = Get-PodeServerUptime -Total
#>
function Get-PodeServerUptime {
    [CmdletBinding()]
    [OutputType([long])]
    param(
        [switch]
        $Total
    )

    $time = $PodeContext.Metrics.Server.StartTime
    if ($Total) {
        $time = $PodeContext.Metrics.Server.InitialLoadTime
    }

    return [long]([datetime]::UtcNow - $time).TotalMilliseconds
}

<#
.SYNOPSIS
Returns the number of times the server has restarted.

.DESCRIPTION
Returns the number of times the server has restarted.

.EXAMPLE
$restarts = Get-PodeServerRestartCount
#>
function Get-PodeServerRestartCount {
    [CmdletBinding()]
    param()

    return $PodeContext.Metrics.Server.RestartCount
}

<#
.SYNOPSIS
Returns the total number of requests/per status code the Server has receieved.

.DESCRIPTION
Returns the total number of requests/per status code the Server has receieved.

.PARAMETER StatusCode
If supplied, will return the total number of requests for a specific StatusCode.

.PARAMETER Total
If supplied, will return the Total number of Requests.

.EXAMPLE
$totalReqs = Get-PodeServerRequestMetric -Total

.EXAMPLE
$statusReqs = Get-PodeServerRequestMetric

.EXAMPLE
$404Reqs = Get-PodeServerRequestMetric -StatusCode 404
#>
function Get-PodeServerRequestMetric {
    [CmdletBinding(DefaultParameterSetName = 'StatusCode')]
    [OutputType([long])]
    param(
        [Parameter(ParameterSetName = 'StatusCode')]
        [int]
        $StatusCode = 0,

        [Parameter(ParameterSetName = 'Total')]
        [switch]
        $Total
    )

    if ($Total) {
        return $PodeContext.Metrics.Requests.Total
    }

    if (($StatusCode -le 0)) {
        return $PodeContext.Metrics.Requests.StatusCodes
    }

    $strCode = "$($StatusCode)"
    if (!$PodeContext.Metrics.Requests.StatusCodes.ContainsKey($strCode)) {
        return 0L
    }

    return $PodeContext.Metrics.Requests.StatusCodes[$strCode]
}

<#
.SYNOPSIS
Returns the total number of Signal requests the Server has receieved.

.DESCRIPTION
Returns the total number of Signal requests the Server has receieved.

.EXAMPLE
$totalReqs = Get-PodeServerSignalMetric
#>
function Get-PodeServerSignalMetric {
    [CmdletBinding()]
    param()

    return $PodeContext.Metrics.Signals.Total
}

<#
.SYNOPSIS
Returns the count of active requests.

.DESCRIPTION
Returns the count of all, processing, or queued active requests.

.PARAMETER CountType
The count type to return. (Default: Total)

.EXAMPLE
Get-PodeServerActiveRequestMetric

.EXAMPLE
Get-PodeServerActiveRequestMetric -CountType Queued
#>
function Get-PodeServerActiveRequestMetric {
    [CmdletBinding()]
    param(
        [Parameter()]
        [ValidateSet('Total', 'Queued', 'Processing')]
        [string]
        $CountType = 'Total'
    )

    switch ($CountType.ToLowerInvariant()) {
        'total' {
            return $PodeContext.Server.Signals.Listener.Contexts.Count
        }

        'queued' {
            return $PodeContext.Server.Signals.Listener.Contexts.QueuedCount
        }

        'processing' {
            return $PodeContext.Server.Signals.Listener.Contexts.ProcessingCount
        }
    }
}

<#
.SYNOPSIS
Returns the count of active signals.

.DESCRIPTION
Returns the count of all, processing, or queued active signals; for either server or client signals.

.PARAMETER Type
The type of signal to return. (Default: Total)

.PARAMETER CountType
The count type to return. (Default: Total)

.EXAMPLE
Get-PodeServerActiveSignalMetric

.EXAMPLE
Get-PodeServerActiveSignalMetric -Type Client -CountType Queued
#>
function Get-PodeServerActiveSignalMetric {
    [CmdletBinding()]
    param(
        [Parameter()]
        [ValidateSet('Total', 'Server', 'Client')]
        [string]
        $Type = 'Total',

        [Parameter()]
        [ValidateSet('Total', 'Queued', 'Processing')]
        [string]
        $CountType = 'Total'
    )

    switch ($Type.ToLowerInvariant()) {
        'total' {
            switch ($CountType.ToLowerInvariant()) {
                'total' {
                    return $PodeContext.Server.Signals.Listener.ServerSignals.Count + $PodeContext.Server.Signals.Listener.ClientSignals.Count
                }

                'queued' {
                    return $PodeContext.Server.Signals.Listener.ServerSignals.QueuedCount + $PodeContext.Server.Signals.Listener.ClientSignals.QueuedCount
                }

                'processing' {
                    return $PodeContext.Server.Signals.Listener.ServerSignals.ProcessingCount + $PodeContext.Server.Signals.Listener.ClientSignals.ProcessingCount
                }
            }
        }

        'server' {
            switch ($CountType.ToLowerInvariant()) {
                'total' {
                    return $PodeContext.Server.Signals.Listener.ServerSignals.Count
                }

                'queued' {
                    return $PodeContext.Server.Signals.Listener.ServerSignals.QueuedCount
                }

                'processing' {
                    return $PodeContext.Server.Signals.Listener.ServerSignals.ProcessingCount
                }
            }
        }

        'client' {
            switch ($CountType.ToLowerInvariant()) {
                'total' {
                    return $PodeContext.Server.Signals.Listener.ClientSignals.Count
                }

                'queued' {
                    return $PodeContext.Server.Signals.Listener.ClientSignals.QueuedCount
                }

                'processing' {
                    return $PodeContext.Server.Signals.Listener.ClientSignals.ProcessingCount
                }
            }
        }
    }
}
src\Public\Middleware.ps1
<#
.SYNOPSIS
Adds an access rule to allow or deny IP addresses.

.DESCRIPTION
Adds an access rule to allow or deny IP addresses.

.PARAMETER Access
The type of access to enable.

.PARAMETER Type
What type of request are we configuring?

.PARAMETER Values
A single, or an array of values.

.EXAMPLE
Add-PodeAccessRule -Access Allow -Type IP -Values '127.0.0.1'

.EXAMPLE
Add-PodeAccessRule -Access Deny -Type IP -Values @('192.168.1.1', '10.10.1.0/24')
#>
function Add-PodeAccessRule {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $true)]
        [ValidateSet('Allow', 'Deny')]
        [string]
        $Access,

        [Parameter(Mandatory = $true)]
        [ValidateSet('IP')]
        [string]
        $Type,

        [Parameter(Mandatory = $true)]
        [string[]]
        $Values
    )

    # error if serverless
    Test-PodeIsServerless -FunctionName 'Add-PodeAccessRule' -ThrowError

    # call the appropriate access method
    switch ($Type.ToLowerInvariant()) {
        'ip' {
            foreach ($ip in $Values) {
                Add-PodeIPAccess -Access $Access -IP $ip
            }
        }
    }
}

<#
.SYNOPSIS
Adds rate limiting rules for an IP addresses, Routes, or Endpoints.

.DESCRIPTION
Adds rate limiting rules for an IP addresses, Routes, or Endpoints.

.PARAMETER Type
What type of request is being rate limited: IP, Route, or Endpoint?

.PARAMETER Values
A single, or an array of values.

.PARAMETER Limit
The maximum number of requests to allow.

.PARAMETER Seconds
The number of seconds to count requests before restarting the count.

.PARAMETER Group
If supplied, groups of IPs in a subnet will be considered as one IP.

.EXAMPLE
Add-PodeLimitRule -Type IP -Values '127.0.0.1' -Limit 10 -Seconds 1

.EXAMPLE
Add-PodeLimitRule -Type IP -Values @('192.168.1.1', '10.10.1.0/24') -Limit 50 -Seconds 1 -Group

.EXAMPLE
Add-PodeLimitRule -Type Route -Values '/downloads' -Limit 5 -Seconds 1
#>
function Add-PodeLimitRule {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $true)]
        [ValidateSet('IP', 'Route', 'Endpoint')]
        [string]
        $Type,

        [Parameter(Mandatory = $true)]
        [string[]]
        $Values,

        [Parameter(Mandatory = $true)]
        [int]
        $Limit,

        [Parameter(Mandatory = $true)]
        [int]
        $Seconds,

        [switch]
        $Group
    )

    # call the appropriate limit method
    foreach ($value in $Values) {
        switch ($Type.ToLowerInvariant()) {
            'ip' {
                Test-PodeIsServerless -FunctionName 'Add-PodeLimitRule' -ThrowError
                Add-PodeIPLimit -IP $value -Limit $Limit -Seconds $Seconds -Group:$Group
            }

            'route' {
                Add-PodeRouteLimit -Path $value -Limit $Limit -Seconds $Seconds -Group:$Group
            }

            'endpoint' {
                Add-PodeEndpointLimit -EndpointName $value -Limit $Limit -Seconds $Seconds -Group:$Group
            }
        }
    }
}

<#
.SYNOPSIS
Creates and returns a new secure token for use with CSRF.

.DESCRIPTION
Creates and returns a new secure token for use with CSRF.

.EXAMPLE
$token = New-PodeCsrfToken
#>
function New-PodeCsrfToken {
    [CmdletBinding()]
    [OutputType([string])]
    param()

    # fail if the csrf logic hasn't been initialised
    if (!(Test-PodeCsrfConfigured)) {
        # CSRF Middleware has not been initialized
        throw ($PodeLocale.csrfMiddlewareNotInitializedExceptionMessage)
    }

    # generate a new secret and salt
    $Secret = New-PodeCsrfSecret
    $Salt = (New-PodeSalt -Length 8)

    # return a new token
    return "t:$($Salt).$(Invoke-PodeSHA256Hash -Value "$($Salt)-$($Secret)")"
}

<#
.SYNOPSIS
Returns adhoc CSRF CSRF verification Middleware, for use on Routes.

.DESCRIPTION
Returns adhoc CSRF CSRF verification Middleware, for use on Routes.

.EXAMPLE
$csrf = Get-PodeCsrfMiddleware
Add-PodeRoute -Method Get -Path '/cpu' -Middleware $csrf -ScriptBlock { /* logic */ }
#>
function Get-PodeCsrfMiddleware {
    [CmdletBinding()]
    [OutputType([hashtable])]
    param()

    # fail if the csrf logic hasn't been initialised
    if (!(Test-PodeCsrfConfigured)) {
        # CSRF Middleware has not been initialized
        throw ($PodeLocale.csrfMiddlewareNotInitializedExceptionMessage)
    }

    # return scriptblock for the csrf route middleware to test tokens
    $script = {
        # if there's not a secret, generate and store it
        $secret = New-PodeCsrfSecret

        # verify the token on the request, if invalid, throw a 403
        $token = Get-PodeCsrfToken

        if (!(Test-PodeCsrfToken -Secret $secret -Token $token)) {
            Set-PodeResponseStatus -Code 403 -Description 'Invalid CSRF Token'
            return $false
        }

        # token is valid, move along
        return $true
    }

    return (New-PodeMiddleware -ScriptBlock $script)
}

<#
.SYNOPSIS
Initialises CSRF within Pode for adhoc usage.

.DESCRIPTION
Initialises CSRF within Pode for adhoc usage, with configurable HTTP methods to ignore verification.

.PARAMETER IgnoreMethods
An array of HTTP methods to ignore CSRF verification.

.PARAMETER Secret
A secret to use when signing cookies - for when using CSRF with cookies.

.PARAMETER UseCookies
If supplied, CSRF will used cookies rather than sessions.

.EXAMPLE
Initialize-PodeCsrf -IgnoreMethods @('Get', 'Trace')

.EXAMPLE
Initialize-PodeCsrf -Secret 'some-secret' -UseCookies
#>
function Initialize-PodeCsrf {
    [CmdletBinding()]
    param(
        [Parameter()]
        [ValidateSet('Connect', 'Delete', 'Get', 'Head', 'Merge', 'Options', 'Patch', 'Post', 'Put', 'Trace')]
        [string[]]
        $IgnoreMethods = @('Get', 'Head', 'Options', 'Trace'),

        [Parameter()]
        [string]
        $Secret,

        [switch]
        $UseCookies
    )

    # check that csrf logic hasn't already been intialised
    if (Test-PodeCsrfConfigured) {
        return
    }

    # if sessions haven't been setup and we're not using cookies, error
    if (!$UseCookies -and !(Test-PodeSessionsEnabled)) {
        # Sessions are required to use CSRF unless you want to use cookies
        throw ($PodeLocale.sessionsRequiredForCsrfExceptionMessage)
    }

    # if we're using cookies, ensure a global secret exists
    if ($UseCookies) {
        $Secret = (Protect-PodeValue -Value $Secret -Default (Get-PodeCookieSecret -Global))

        if (Test-PodeIsEmpty $Secret) {
            # When using cookies for CSRF, a Secret is required
            throw ($PodeLocale.csrfCookieRequiresSecretExceptionMessage)
        }
    }

    # set the options against the server context
    $PodeContext.Server.Cookies.Csrf = @{
        Name           = 'pode.csrf'
        UseCookies     = $UseCookies
        Secret         = $Secret
        IgnoredMethods = $IgnoreMethods
    }
}

<#
.SYNOPSIS
Enables Middleware for verifying CSRF tokens on Requests.

.DESCRIPTION
Enables Middleware for verifying CSRF tokens on Requests, with configurable HTTP methods to ignore verification.

.PARAMETER IgnoreMethods
An array of HTTP methods to ignore CSRF verification.

.PARAMETER Secret
A secret to use when signing cookies - for when using CSRF with cookies.

.PARAMETER UseCookies
If supplied, CSRF will used cookies rather than sessions.

.EXAMPLE
Enable-PodeCsrfMiddleware -IgnoreMethods @('Get', 'Trace')

.EXAMPLE
Enable-PodeCsrfMiddleware -Secret 'some-secret' -UseCookies
#>
function Enable-PodeCsrfMiddleware {
    [CmdletBinding()]
    param(
        [Parameter()]
        [ValidateSet('Connect', 'Delete', 'Get', 'Head', 'Merge', 'Options', 'Patch', 'Post', 'Put', 'Trace')]
        [string[]]
        $IgnoreMethods = @('Get', 'Head', 'Options', 'Trace'),

        [Parameter(ParameterSetName = 'Cookies')]
        [string]
        $Secret,

        [Parameter(ParameterSetName = 'Cookies')]
        [switch]
        $UseCookies
    )

    Initialize-PodeCsrf -IgnoreMethods $IgnoreMethods -Secret $Secret -UseCookies:$UseCookies

    # return scriptblock for the csrf middleware
    $script = {
        # if the current route method is ignored, just return
        $ignored = @($PodeContext.Server.Cookies.Csrf.IgnoredMethods)
        if (!(Test-PodeIsEmpty $ignored) -and ($ignored -icontains $WebEvent.Method)) {
            return $true
        }

        # if there's not a secret, generate and store it
        $secret = New-PodeCsrfSecret

        # verify the token on the request, if invalid, throw a 403
        $token = Get-PodeCsrfToken

        if (!(Test-PodeCsrfToken -Secret $secret -Token $token)) {
            Set-PodeResponseStatus -Code 403 -Description 'Invalid CSRF Token'
            return $false
        }

        # token is valid, move along
        return $true
    }

    (New-PodeMiddleware -ScriptBlock $script) | Add-PodeMiddleware -Name '__pode_mw_csrf__'
}

<#
.SYNOPSIS
Adds a custom body parser middleware.

.DESCRIPTION
Adds a custom body parser middleware script for a content-type, which will be used if a payload is sent with a Request.

.PARAMETER ContentType
The ContentType of the custom body parser.

.PARAMETER ScriptBlock
The ScriptBlock that will parse the body content, and return the result.

.EXAMPLE
Add-PodeBodyParser -ContentType 'application/json' -ScriptBlock { param($body) /* parsing logic */ }
#>
function Add-PodeBodyParser {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $true)]
        [ValidatePattern('^\w+\/[\w\.\+-]+$')]
        [string]
        $ContentType,

        [Parameter(Mandatory = $true, ValueFromPipeline = $true)]
        [scriptblock]
        $ScriptBlock
    )
    begin {
        $pipelineItemCount = 0
    }

    process {
        $pipelineItemCount++
    }

    end {
        if ($pipelineItemCount -gt 1) {
            throw ($PodeLocale.fnDoesNotAcceptArrayAsPipelineInputExceptionMessage -f $($MyInvocation.MyCommand.Name))
        }
        # if a parser for the type already exists, fail
        if ($PodeContext.Server.BodyParsers.ContainsKey($ContentType)) {
            # A body-parser is already defined for the content-type
            throw ($PodeLocale.bodyParserAlreadyDefinedForContentTypeExceptionMessage -f $ContentType)
        }

        # check for scoped vars
        $ScriptBlock, $usingVars = Convert-PodeScopedVariables -ScriptBlock $ScriptBlock -PSSession $PSCmdlet.SessionState

        $PodeContext.Server.BodyParsers[$ContentType] = @{
            ScriptBlock    = $ScriptBlock
            UsingVariables = $usingVars
        }
    }
}

<#
.SYNOPSIS
Removes a custom body parser.

.DESCRIPTION
Removes a custom body parser middleware script for a content-type.

.PARAMETER ContentType
The ContentType of the custom body parser.

.EXAMPLE
Remove-PodeBodyParser -ContentType 'application/json'
#>
function Remove-PodeBodyParser {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $true, ValueFromPipeline = $true)]
        [ValidatePattern('^\w+\/[\w\.\+-]+$')]
        [string]
        $ContentType
    )

    process {
        # if there's no parser for the type, return
        if (!$PodeContext.Server.BodyParsers.ContainsKey($ContentType)) {
            return
        }

        $null = $PodeContext.Server.BodyParsers.Remove($ContentType)
    }
}

<#
.SYNOPSIS
Adds a new Middleware to be invoked before every Route, or certain Routes.

.DESCRIPTION
Adds a new Middleware to be invoked before every Route, or certain Routes. ScriptBlock should return $true to continue execution, or $false to stop.

.PARAMETER Name
The Name of the Middleware.

.PARAMETER ScriptBlock
The Script defining the logic of the Middleware. Should return $true to continue execution, or $false to stop.

.PARAMETER InputObject
A Middleware HashTable from New-PodeMiddleware, or from certain other functions that return Middleware as a HashTable.

.PARAMETER Route
A Route path for which Routes this Middleware should only be invoked against.

.PARAMETER ArgumentList
An array of arguments to supply to the Middleware's ScriptBlock.

.OUTPUTS
Boolean. ScriptBlock should return $true to continue to the next middleware/route, or return $false to stop execution.

.EXAMPLE
Add-PodeMiddleware -Name 'BlockAgents' -ScriptBlock { /* logic */ }

.EXAMPLE
Add-PodeMiddleware -Name 'CheckEmailOnApi' -Route '/api/*' -ScriptBlock { /* logic */ }
#>
function Add-PodeMiddleware {
    [CmdletBinding(DefaultParameterSetName = 'Script')]
    param(
        [Parameter(Mandatory = $true)]
        [string]
        $Name,

        [Parameter(Mandatory = $true, ParameterSetName = 'Script')]
        [scriptblock]
        $ScriptBlock,

        [Parameter(Mandatory = $true, Position = 0, ValueFromPipeline = $true, ParameterSetName = 'Input')]
        [hashtable]
        $InputObject,

        [Parameter()]
        [string]
        $Route,

        [Parameter()]
        [object[]]
        $ArgumentList
    )
    begin {
        $pipelineItemCount = 0
    }

    process {
        $pipelineItemCount++
    }

    end {
        if ($pipelineItemCount -gt 1) {
            throw ($PodeLocale.fnDoesNotAcceptArrayAsPipelineInputExceptionMessage -f $($MyInvocation.MyCommand.Name))
        }
        # ensure name doesn't already exist
        if (($PodeContext.Server.Middleware | Where-Object { $_.Name -ieq $Name } | Measure-Object).Count -gt 0) {
            # [Middleware] Name: Middleware already defined
            throw ($PodeLocale.middlewareAlreadyDefinedExceptionMessage -f $Name)

        }

        # if it's a script - call New-PodeMiddleware
        if ($PSCmdlet.ParameterSetName -ieq 'script') {
            $InputObject = (New-PodeMiddlewareInternal `
                    -ScriptBlock $ScriptBlock `
                    -Route $Route `
                    -ArgumentList $ArgumentList `
                    -PSSession $PSCmdlet.SessionState)
        }
        else {
            $Route = ConvertTo-PodeRouteRegex -Path $Route
            $InputObject.Route = Protect-PodeValue -Value $Route -Default $InputObject.Route
            $InputObject.Options = Protect-PodeValue -Value $Options -Default $InputObject.Options
        }

        # ensure we have a script to run
        if (Test-PodeIsEmpty $InputObject.Logic) {
            # [Middleware]: No logic supplied in ScriptBlock
            throw ($PodeLocale.middlewareNoLogicSuppliedExceptionMessage)
        }

        # set name, and override route/args
        $InputObject.Name = $Name

        # add the logic to array of middleware that needs to be run
        $PodeContext.Server.Middleware += $InputObject
    }
}

<#
.SYNOPSIS
Creates a new Middleware HashTable object, that can be piped/used in Add-PodeMiddleware or in Routes.

.DESCRIPTION
Creates a new Middleware HashTable object, that can be piped/used in Add-PodeMiddleware or in Routes. ScriptBlock should return $true to continue execution, or $false to stop.

.PARAMETER ScriptBlock
The Script that defines the logic of the Middleware. Should return $true to continue execution, or $false to stop.

.PARAMETER Route
A Route path for which Routes this Middleware should only be invoked against.

.PARAMETER ArgumentList
An array of arguments to supply to the Middleware's ScriptBlock.

.OUTPUTS
Boolean. ScriptBlock should return $true to continue to the next middleware/route, or return $false to stop execution.

.EXAMPLE
New-PodeMiddleware -ScriptBlock { /* logic */ } -ArgumentList 'Email' | Add-PodeMiddleware -Name 'CheckEmail'
#>
function New-PodeMiddleware {
    [CmdletBinding()]
    [OutputType([hashtable])]
    param(
        [Parameter(Mandatory = $true, Position = 0, ValueFromPipeline = $true)]
        [scriptblock]
        $ScriptBlock,

        [Parameter()]
        [string]
        $Route,

        [Parameter()]
        [object[]]
        $ArgumentList
    )
    begin {
        $pipelineItemCount = 0
    }

    process {
        $pipelineItemCount++
    }

    end {
        if ($pipelineItemCount -gt 1) {
            throw ($PodeLocale.fnDoesNotAcceptArrayAsPipelineInputExceptionMessage -f $($MyInvocation.MyCommand.Name))
        }
        return New-PodeMiddlewareInternal `
            -ScriptBlock $ScriptBlock `
            -Route $Route `
            -ArgumentList $ArgumentList `
            -PSSession $PSCmdlet.SessionState
    }
}

<#
.SYNOPSIS
Removes a specific user defined Middleware.

.DESCRIPTION
Removes a specific user defined Middleware.

.PARAMETER Name
The Name of the Middleware to be removed.

.EXAMPLE
Remove-PodeMiddleware -Name 'Sessions'
#>
function Remove-PodeMiddleware {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $true)]
        [string]
        $Name
    )

    $PodeContext.Server.Middleware = @($PodeContext.Server.Middleware | Where-Object { $_.Name -ine $Name })
}

<#
.SYNOPSIS
Removes all user defined Middleware.

.DESCRIPTION
Removes all user defined Middleware.

.EXAMPLE
Clear-PodeMiddleware
#>
function Clear-PodeMiddleware {
    [CmdletBinding()]
    param()

    $PodeContext.Server.Middleware = @()
}

<#
.SYNOPSIS
Automatically loads middleware ps1 files

.DESCRIPTION
Automatically loads middleware ps1 files from either a /middleware folder, or a custom folder. Saves space dot-sourcing them all one-by-one.

.PARAMETER Path
Optional Path to a folder containing ps1 files, can be relative or literal.

.EXAMPLE
Use-PodeMiddleware

.EXAMPLE
Use-PodeMiddleware -Path './my-middleware'
#>
function Use-PodeMiddleware {
    [CmdletBinding()]
    param(
        [Parameter()]
        [string]
        $Path
    )

    Use-PodeFolder -Path $Path -DefaultPath 'middleware'
}
src\Public\OAComponents.ps1
<#
.SYNOPSIS
Adds a reusable component for responses.

.DESCRIPTION
Adds a reusable component for responses.

.LINK
https://swagger.io/docs/specification/basic-structure/

.LINK
https://swagger.io/docs/specification/data-models/

.LINK
https://swagger.io/docs/specification/serialization/

.PARAMETER Name
The reference Name of the response.

.PARAMETER Content
The content-types and schema the response returns (the schema is created using the Property functions).

.PARAMETER Headers
The header name and schema the response returns (the schema is created using the Add-PodeOAComponentHeader cmdlet).

.PARAMETER Description
The Description of the response.

.PARAMETER Reference
A Reference Name of an existing component response to use.

.PARAMETER Links
A Response link definition

.PARAMETER DefinitionTag
An Array of strings representing the unique tag for the API specification.
This tag helps in distinguishing between different versions or types of API specifications within the application.
You can use this tag to reference the specific API documentation, schema, or version that your function interacts with.

.EXAMPLE
Add-PodeOAComponentResponse -Name 'OKResponse' -Content @{ 'application/json' = (New-PodeOAIntProperty -Name 'userId' -Object) }

.EXAMPLE
Add-PodeOAComponentResponse -Name 'ErrorResponse' -Content  @{ 'application/json' = 'ErrorSchema' }
#>
function Add-PodeOAComponentResponse {
    [CmdletBinding(DefaultParameterSetName = 'Schema')]
    param(
        [Parameter(Mandatory = $true)]
        [ValidatePattern('^[a-zA-Z0-9\.\-_]+$')]
        [string]
        $Name,

        [Parameter(ParameterSetName = 'Schema')]
        [Alias('ContentSchemas')]
        [hashtable]
        $Content,

        [Parameter(ParameterSetName = 'Schema')]
        [Alias('HeaderSchemas')]
        [AllowEmptyString()]
        [ValidateNotNullOrEmpty()]
        [ValidateScript({ $_ -is [string] -or $_ -is [string[]] -or $_ -is [hashtable] })]
        $Headers,

        [Parameter(ParameterSetName = 'Schema')]
        [string]
        $Description,

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

        [Parameter(ParameterSetName = 'Schema')]
        [System.Collections.Specialized.OrderedDictionary ]
        $Links,

        [string[]]
        $DefinitionTag
    )
    $DefinitionTag = Test-PodeOADefinitionTag -Tag $DefinitionTag
    foreach ($tag in $DefinitionTag) {
        $PodeContext.Server.OpenAPI.Definitions[$tag].components.responses[$Name] = New-PodeOResponseInternal -DefinitionTag $tag -Params $PSBoundParameters
    }
}


<#
.SYNOPSIS
Adds a reusable component schema

.DESCRIPTION
Adds a reusable component  schema.

.LINK
https://swagger.io/docs/specification/basic-structure/

.LINK
https://swagger.io/docs/specification/data-models/

.LINK
https://swagger.io/docs/specification/serialization/

.LINK
https://swagger.io/docs/specification/data-models/

.PARAMETER Name
The reference Name of the schema.

.PARAMETER Component
The Component definition (the schema is created using the Property functions).

.PARAMETER Description
A description of the schema

.PARAMETER DefinitionTag
An Array of strings representing the unique tag for the API specification.
This tag helps in distinguishing between different versions or types of API specifications within the application.
You can use this tag to reference the specific API documentation, schema, or version that your function interacts with.

.EXAMPLE
Add-PodeOAComponentSchema -Name 'UserIdSchema' -Component (New-PodeOAIntProperty -Name 'userId' -Object)
#>
function Add-PodeOAComponentSchema {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $true)]
        [ValidatePattern('^[a-zA-Z0-9\.\-_]+$')]
        [string]
        $Name,

        [Parameter(Mandatory = $true, Position = 0, ValueFromPipeline = $true)]
        [Alias('Schema')]
        [hashtable]
        $Component,

        [string]
        $Description,

        [string[]]
        $DefinitionTag
    )
    begin {
        $pipelineItemCount = 0
    }

    process {
        $pipelineItemCount++
    }

    end {
        if ($pipelineItemCount -gt 1) {
            throw ($PodeLocale.fnDoesNotAcceptArrayAsPipelineInputExceptionMessage -f $($MyInvocation.MyCommand.Name))
        }
        $DefinitionTag = Test-PodeOADefinitionTag -Tag $DefinitionTag

        foreach ($tag in $DefinitionTag) {
            $PodeContext.Server.OpenAPI.Definitions[$tag].components.schemas[$Name] = ($Component | ConvertTo-PodeOASchemaProperty -DefinitionTag $tag)
            if ($PodeContext.Server.OpenAPI.Definitions[$tag].hiddenComponents.schemaValidation) {
                try {
                    $modifiedComponent = ($Component | ConvertTo-PodeOASchemaProperty -DefinitionTag $tag) | Resolve-PodeOAReference -DefinitionTag $tag
                    $PodeContext.Server.OpenAPI.Definitions[$tag].hiddenComponents.schemaJson[$Name] = @{
                        'available' = $true
                        'schema'    = $modifiedComponent
                        'json'      = $modifiedComponent | ConvertTo-Json -Depth $PodeContext.Server.OpenAPI.Definitions[$tag].hiddenComponents.depth
                    }
                }
                catch {
                    if ($_.ToString().StartsWith('Validation of schema with')) {
                        $PodeContext.Server.OpenAPI.Definitions[$tag].hiddenComponents.schemaJson[$Name] = @{
                            'available' = $false
                        }
                    }
                }
            }

            if ($Description) {
                $PodeContext.Server.OpenAPI.Definitions[$tag].components.schemas[$Name].description = $Description
            }
        }
    }
}


<#
.SYNOPSIS
Adds a reusable component for a Header schema.

.DESCRIPTION
Adds a reusable component for a Header schema.

.LINK
https://swagger.io/docs/specification/basic-structure/

.LINK
https://swagger.io/docs/specification/data-models/

.LINK
https://swagger.io/docs/specification/serialization/

.LINK
https://swagger.io/docs/specification/data-models/

.PARAMETER Name
The reference Name of the schema.

.PARAMETER Schema
The Schema definition (the schema is created using the Property functions).

.PARAMETER Description
A description of the header

.PARAMETER DefinitionTag
An Array of strings representing the unique tag for the API specification.
This tag helps in distinguishing between different versions or types of API specifications within the application.
You can use this tag to reference the specific API documentation, schema, or version that your function interacts with.

.EXAMPLE
Add-PodeOAComponentHeader -Name 'UserIdSchema' -Schema (New-PodeOAIntProperty -Name 'userId' -Object)
#>
function Add-PodeOAComponentHeader {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $true)]
        [ValidatePattern('^[a-zA-Z0-9\.\-_]+$')]
        [string]
        $Name,

        [Parameter()]
        [string]
        $Description,

        [Parameter(Mandatory = $true, Position = 0, ValueFromPipeline = $true)]
        [hashtable]
        $Schema,

        [string[]]
        $DefinitionTag
    )
    begin {
        $pipelineItemCount = 0
    }

    process {
        $pipelineItemCount++
    }

    end {
        if ($pipelineItemCount -gt 1) {
            throw ($PodeLocale.fnDoesNotAcceptArrayAsPipelineInputExceptionMessage -f $($MyInvocation.MyCommand.Name))
        }
        $DefinitionTag = Test-PodeOADefinitionTag -Tag $DefinitionTag

        foreach ($tag in $DefinitionTag) {
            $param = [ordered]@{
                'schema' = ($Schema | ConvertTo-PodeOASchemaProperty -NoDescription -DefinitionTag $tag)
            }
            if ( $Description) {
                $param['description'] = $Description
            }
            $PodeContext.Server.OpenAPI.Definitions[$tag].components.headers[$Name] = $param
        }
    }
}




<#
.SYNOPSIS
Adds a reusable component for a request body.

.DESCRIPTION
Adds a reusable component for a request body.

.LINK
https://swagger.io/docs/specification/basic-structure/

.LINK
https://swagger.io/docs/specification/data-models/

.LINK
https://swagger.io/docs/specification/describing-request-body/

.PARAMETER Name
The reference Name of the request body.

.PARAMETER Content
The content-types and schema the request body accepts (the schema is created using the Property functions).

.PARAMETER Description
A Description of the request body.

.PARAMETER Required
If supplied, the request body will be flagged as required.

.PARAMETER DefinitionTag
An Array of strings representing the unique tag for the API specification.
This tag helps in distinguishing between different versions or types of API specifications within the application.
You can use this tag to reference the specific API documentation, schema, or version that your function interacts with.

.EXAMPLE
Add-PodeOAComponentRequestBody -Name 'UserIdBody' -ContentSchemas @{ 'application/json' = (New-PodeOAIntProperty -Name 'userId' -Object) }

.EXAMPLE
Add-PodeOAComponentRequestBody -Name 'UserIdBody' -ContentSchemas @{ 'application/json' = 'UserIdSchema' }
#>
function Add-PodeOAComponentRequestBody {
    [CmdletBinding()]
    [OutputType([System.Collections.Specialized.OrderedDictionary])]
    param(
        [Parameter(Mandatory = $true)]
        [ValidatePattern('^[a-zA-Z0-9\.\-_]+$')]
        [string]
        $Name,

        [Parameter(Mandatory = $true, Position = 0, ValueFromPipeline = $true)]
        [Alias('ContentSchemas')]
        [hashtable]
        $Content,

        [Parameter()]
        [string]
        $Description  ,

        [Parameter()]
        [switch]
        $Required,

        [string[]]
        $DefinitionTag
    )
    begin {
        $pipelineItemCount = 0
    }

    process {
        $pipelineItemCount++
    }

    end {
        if ($pipelineItemCount -gt 1) {
            throw ($PodeLocale.fnDoesNotAcceptArrayAsPipelineInputExceptionMessage -f $($MyInvocation.MyCommand.Name))
        }
        $DefinitionTag = Test-PodeOADefinitionTag -Tag $DefinitionTag

        foreach ($tag in $DefinitionTag) {
            $param = [ordered]@{ content = ($Content | ConvertTo-PodeOAObjectSchema -DefinitionTag $tag) }

            if ($Required.IsPresent) {
                $param['required'] = $Required.IsPresent
            }

            if ( $Description) {
                $param['description'] = $Description
            }
            $PodeContext.Server.OpenAPI.Definitions[$tag].components.requestBodies[$Name] = $param
        }
    }

}

<#
.SYNOPSIS
Adds a reusable component for a request parameter.

.DESCRIPTION
Adds a reusable component for a request parameter.

.LINK
https://swagger.io/docs/specification/basic-structure/

.LINK
https://swagger.io/docs/specification/data-models/

.LINK
https://swagger.io/docs/specification/describing-parameters/

.PARAMETER Name
The reference Name of the parameter.

.PARAMETER Parameter
The Parameter to use for the component (from ConvertTo-PodeOAParameter)

.PARAMETER DefinitionTag
An Array of strings representing the unique tag for the API specification.
This tag helps in distinguishing between different versions or types of API specifications within the application.
You can use this tag to reference the specific API documentation, schema, or version that your function interacts with.

.EXAMPLE
New-PodeOAIntProperty -Name 'userId' | ConvertTo-PodeOAParameter -In Query | Add-PodeOAComponentParameter -Name 'UserIdParam'
#>

function Add-PodeOAComponentParameter {
    [CmdletBinding()]
    param(
        [Parameter()]
        [ValidatePattern('^[a-zA-Z0-9\.\-_]+$')]
        [string]
        $Name,

        [Parameter(Mandatory = $true, Position = 0, ValueFromPipeline = $true)]
        [hashtable]
        $Parameter,

        [string[]]
        $DefinitionTag
    )
    begin {
        $pipelineItemCount = 0
    }

    process {
        $pipelineItemCount++
    }

    end {
        if ($pipelineItemCount -gt 1) {
            throw ($PodeLocale.fnDoesNotAcceptArrayAsPipelineInputExceptionMessage -f $($MyInvocation.MyCommand.Name))
        }

        $DefinitionTag = Test-PodeOADefinitionTag -Tag $DefinitionTag

        foreach ($tag in $DefinitionTag) {
            if ([string]::IsNullOrWhiteSpace($Name)) {
                if ($Parameter.name) {
                    $Name = $Parameter.name
                }
                else {
                    # The Parameter has no name. Please provide a name to this component using the `Name` parameter
                    throw ($PodeLocale.parameterHasNoNameExceptionMessage)
                }
            }
            $PodeContext.Server.OpenAPI.Definitions[$tag].components.parameters[$Name] = $Parameter
        }
    }
}

<#
.SYNOPSIS
Adds a reusable example component.

.DESCRIPTION
Adds a reusable example component.

.PARAMETER Name
The Name of the Example.


.PARAMETER Summary
Short description for the example

.PARAMETER Description
Long description for the example.

.PARAMETER Value
Embedded literal example. The  value Parameter and ExternalValue parameter are mutually exclusive.
To represent examples of media types that cannot naturally represented in JSON or YAML, use a string value to contain the example, escaping where necessary.

.PARAMETER ExternalValue
A URL that points to the literal example. This provides the capability to reference examples that cannot easily be included in JSON or YAML documents.
The -Value parameter and -ExternalValue parameter are mutually exclusive.

.PARAMETER DefinitionTag
An Array of strings representing the unique tag for the API specification.
This tag helps in distinguishing between different versions or types of API specifications within the application.
You can use this tag to reference the specific API documentation, schema, or version that your function interacts with.                           |

.EXAMPLE
Add-PodeOAComponentExample -name 'frog-example' -Summary "An example of a frog with a cat's name" -Value @{name = 'Jaguar'; petType = 'Panthera'; color = 'Lion'; gender = 'Male'; breed = 'Mantella Baroni' }

#>
function Add-PodeOAComponentExample {
    [CmdletBinding(DefaultParameterSetName = 'Value')]
    param(

        [Parameter(Mandatory = $true)]
        [Alias('Title')]
        [ValidatePattern('^[a-zA-Z0-9\.\-_]+$')]
        [string]
        $Name,

        [string]
        $Summary,

        [Parameter()]
        [string]
        $Description,

        [Parameter(Mandatory = $true, ParameterSetName = 'Value')]
        [object]
        $Value,

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

        [string[]]
        $DefinitionTag
    )
    $DefinitionTag = Test-PodeOADefinitionTag -Tag $DefinitionTag
    foreach ($tag in $DefinitionTag) {
        $Example = [ordered]@{ }
        if ($Summary) {
            $Example.summary = $Summary
        }
        if ($Description) {
            $Example.description = $Description
        }
        if ($Value) {
            $Example.value = $Value
        }
        elseif ($ExternalValue) {
            $Example.externalValue = $ExternalValue
        }

        $PodeContext.Server.OpenAPI.Definitions[$tag].components.examples[$Name] = $Example
    }
}




<#
.SYNOPSIS
    Adds a reusable response link.

.DESCRIPTION
    The Add-PodeOAComponentResponseLink function is designed to add a new reusable response link

.PARAMETER Name
    Mandatory. A unique name for the response link.
    Must be a valid string composed of alphanumeric characters, periods (.), hyphens (-), and underscores (_).

.PARAMETER Description
    A brief description of the response link. CommonMark syntax may be used for rich text representation.
    For more information on CommonMark syntax, see [CommonMark Specification](https://spec.commonmark.org/).

.PARAMETER OperationId
    The name of an existing, resolvable OpenAPI Specification (OAS) operation, as defined with a unique `operationId`.
    This parameter is mandatory when using the 'OperationId' parameter set and is mutually exclusive of the `OperationRef` field. It is used to specify the unique identifier of the operation the link is associated with.

.PARAMETER OperationRef
    A relative or absolute URI reference to an OAS operation.
    This parameter is mandatory when using the 'OperationRef' parameter set and is mutually exclusive of the `OperationId` field.
    It MUST point to an Operation Object. Relative `operationRef` values MAY be used to locate an existing Operation Object in the OpenAPI specification.

.PARAMETER Parameters
    A map representing parameters to pass to an operation as specified with `operationId` or identified via `operationRef`.
    The key is the parameter name to be used, whereas the value can be a constant or an expression to be evaluated and passed to the linked operation.
    Parameter names can be qualified using the parameter location syntax `[{in}.]{name}` for operations that use the same parameter name in different locations (e.g., path.id).

.PARAMETER RequestBody
    A string representing the request body to use as a request body when calling the target.

.PARAMETER DefinitionTag
An Array of strings representing the unique tag for the API specification.
This tag helps in distinguishing between different versions or types of API specifications within the application.
You can use this tag to reference the specific API documentation, schema, or version that your function interacts with.

.EXAMPLE
    Add-PodeOAComponentResponseLink   -Name 'address' -OperationId 'getUserByName' -Parameters @{'username' = '$request.path.username'}
    Add-PodeOAResponse -StatusCode 200 -Content @{'application/json' = 'User'} -Links 'address'
    This example demonstrates creating and adding a link named 'address' associated with the operation 'getUserByName' to an OrderedDictionary of links. The updated dictionary is then used in the 'Add-PodeOAResponse' function to define a response with a status code of 200.

.NOTES
    The function supports adding links either by specifying an 'OperationId' or an 'OperationRef', making it versatile for different OpenAPI specification needs.
    It's important to match the parameters and response structures as per the OpenAPI specification to ensure the correct functionality of the API documentation.
#>

function Add-PodeOAComponentResponseLink {
    [CmdletBinding(DefaultParameterSetName = 'OperationId')]
    param(

        [Parameter(Mandatory = $true)]
        [ValidatePattern('^[a-zA-Z0-9\.\-_]+$')]
        [string]
        $Name,

        [Parameter()]
        [string]
        $Description,

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

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

        [Parameter()]
        [hashtable]
        $Parameters,

        [Parameter()]
        [string]
        $RequestBody,

        [string[]]
        $DefinitionTag

    )
    $DefinitionTag = Test-PodeOADefinitionTag -Tag $DefinitionTag
    foreach ($tag in $DefinitionTag) {
        $PodeContext.Server.OpenAPI.Definitions[$tag].components.links[$Name] = New-PodeOAResponseLinkInternal -Params $PSBoundParameters
    }
}




<#
.SYNOPSIS
    Adds OpenAPI reusable callback configurations.

.DESCRIPTION
    The Add-PodeOACallBack function is used for defining OpenAPI callback configurations for routes in a Pode server.
    It enables setting up API specifications including detailed parameters, request body schemas, and response structures for various HTTP methods.

.PARAMETER Path
    Specifies the callback path, usually a relative URL.
    The key that identifies the Path Item Object is a runtime expression evaluated in the context of a runtime HTTP request/response to identify the URL for the callback request.
    A simple example is `$request.body#/url`.
    The runtime expression allows complete access to the HTTP message, including any part of a body that a JSON Pointer (RFC6901) can reference.
    More information on JSON Pointer can be found at [RFC6901](https://datatracker.ietf.org/doc/html/rfc6901).

.PARAMETER Name
Alias for 'Name'. A unique identifier for the callback.
It must be a valid string of alphanumeric characters, periods (.), hyphens (-), and underscores (_).

.PARAMETER Method
    Defines the HTTP method for the callback (e.g., GET, POST, PUT). Supports standard HTTP methods and a wildcard (*) for all methods.

.PARAMETER Parameters
The Parameter definitions the request uses (from ConvertTo-PodeOAParameter).

.PARAMETER RequestBody
Defines the schema of the request body. Can be set using New-PodeOARequestBody.

.PARAMETER Responses
Defines the possible responses for the callback. Can be set using New-PodeOAResponse.

.PARAMETER DefinitionTag
An Array of strings representing the unique tag for the API specification.
This tag helps in distinguishing between different versions or types of API specifications within the application.
You can use this tag to reference the specific API documentation, schema, or version that your function interacts with.

.EXAMPLE
Add-PodeOAComponentCallBack -Title 'test' -Path '{$request.body#/id}' -Method Post `
    -RequestBody (New-PodeOARequestBody -Content @{'*/*' = (New-PodeOAStringProperty -Name 'id')}) `
    -Response (
        New-PodeOAResponse -StatusCode 200 -Description 'Successful operation'  -Content (New-PodeOAContentMediaType -ContentType 'application/json','application/xml' -Content 'Pet'  -Array)
        New-PodeOAResponse -StatusCode 400 -Description 'Invalid ID supplied' |
        New-PodeOAResponse -StatusCode 404 -Description 'Pet not found' |
        New-PodeOAResponse -Default -Description 'Something is wrong'
    )
Add-PodeOACallBack -Reference 'test'
    This example demonstrates adding a POST callback to handle a request body and define various responses based on different status codes.


.NOTES
Ensure that the provided parameters match the expected schema and formats of Pode and OpenAPI specifications.
The function is useful for dynamically configuring and documenting API callbacks in a Pode server environment.
#>

function Add-PodeOAComponentCallBack {
    param (

        [Parameter(Mandatory = $true)]
        [ValidatePattern('^[a-zA-Z0-9\.\-_]+$')]
        [string]
        $Name,

        [Parameter(Mandatory = $true)]
        [string]
        $Path,

        [Parameter(Mandatory = $true)]
        [ValidateSet('Connect', 'Delete', 'Get', 'Head', 'Merge', 'Options', 'Patch', 'Post', 'Put', 'Trace', '*')]
        [string]
        $Method,

        [hashtable[]]
        $Parameters,

        [hashtable]
        $RequestBody,

        [hashtable]
        $Responses,

        [string[]]
        $DefinitionTag
    )
    $DefinitionTag = Test-PodeOADefinitionTag -Tag $DefinitionTag
    foreach ($tag in $DefinitionTag) {
        $PodeContext.Server.OpenAPI.Definitions[$tag].components.callbacks.$Name = New-PodeOAComponentCallBackInternal -Params $PSBoundParameters -DefinitionTag $tag
    }
}


<#
.SYNOPSIS
Sets metadate for the supplied route.

.DESCRIPTION
Sets metadate for the supplied route, such as Summary and Tags.

.LINK
https://swagger.io/docs/specification/paths-and-operations/

.PARAMETER Name
    Alias for 'Name'. A unique identifier for the route.
    It must be a valid string of alphanumeric characters, periods (.), hyphens (-), and underscores (_).

.PARAMETER Path
The URI path for the Route.

.PARAMETER Method
The HTTP Method of this Route, multiple can be supplied.

.PARAMETER Servers
A list of external endpoint. created with New-PodeOAServerEndpoint

.PARAMETER PassThru
If supplied, the route passed in will be returned for further chaining.

.PARAMETER DefinitionTag
An Array of strings representing the unique tag for the API specification.
This tag helps in distinguishing between different versions or types of API specifications within the application.
You can use this tag to reference the specific API documentation, schema, or version that your function interacts with.

.EXAMPLE
Add-PodeOAExternalRoute -PassThru -Method Get -Path '/peta/:id' -Servers (
    New-PodeOAServerEndpoint -Url 'http://ext.server.com/api/v12' -Description 'ext test server' |
    New-PodeOAServerEndpoint -Url 'http://ext13.server.com/api/v12' -Description 'ext test server 13'
    ) |
        Set-PodeOARouteInfo -Summary 'Find pets by ID' -Description 'Returns pets based on ID'  -OperationId 'getPetsById' -PassThru |
        Set-PodeOARequest -PassThru -Parameters @(
        (New-PodeOAStringProperty -Name 'id' -Description 'ID of pet to use' -array | ConvertTo-PodeOAParameter -In Path -Style Simple -Required )) |
        Add-PodeOAResponse -StatusCode 200 -Description 'pet response'   -Content (@{ '*/*' = New-PodeOASchemaProperty   -ComponentSchema 'Pet' -array }) -PassThru |
        Add-PodeOAResponse -Default  -Description 'error payload' -Content (@{'text/html' = 'ErrorModel' }) -PassThru
.EXAMPLE
    Add-PodeOAComponentPathItem -PassThru -Method Get -Path '/peta/:id'  -ScriptBlock {
            Write-PodeJsonResponse -Value 'done' -StatusCode 200
        } | Add-PodeOAExternalRoute -PassThru   -Servers (
        New-PodeOAServerEndpoint -Url 'http://ext.server.com/api/v12' -Description 'ext test server' |
        New-PodeOAServerEndpoint -Url 'http://ext13.server.com/api/v12' -Description 'ext test server 13'
        ) |
        Set-PodeOARouteInfo -Summary 'Find pets by ID' -Description 'Returns pets based on ID'  -OperationId 'getPetsById' -PassThru |
        Set-PodeOARequest -PassThru -Parameters @(
        (New-PodeOAStringProperty -Name 'id' -Description 'ID of pet to use' -array | ConvertTo-PodeOAParameter -In Path -Style Simple -Required )) |
        Add-PodeOAResponse -StatusCode 200 -Description 'pet response'   -Content (@{ '*/*' = New-PodeOASchemaProperty   -ComponentSchema 'Pet' -array }) -PassThru |
        Add-PodeOAResponse -Default  -Description 'error payload' -Content (@{'text/html' = 'ErrorModel' }) -PassThru
#>
function Add-PodeOAComponentPathItem {
    param(

        [Parameter(Mandatory = $true)]
        [ValidatePattern('^[a-zA-Z0-9\.\-_]+$')]
        [string]
        $Name,

        [Parameter(Mandatory = $true )]
        [ValidateSet('Connect', 'Delete', 'Get', 'Head', 'Merge', 'Options', 'Patch', 'Post', 'Put', 'Trace', '*')]
        [string]
        $Method,

        [switch]
        $PassThru,

        [string[]]
        $DefinitionTag
    )

    $_definitionTag = Test-PodeOADefinitionTag -Tag $DefinitionTag

    $refRoute = @{
        Method      = $Method.ToLower()
        NotPrepared = $true
        OpenApi     = @{
            Responses          = $null
            Parameters         = $null
            RequestBody        = $null
            callbacks          = [ordered]@{}
            Authentication     = @()
            Servers            = @()
            DefinitionTag      = $_definitionTag
            IsDefTagConfigured = ($null -ne $DefinitionTag) #Definition Tag has been configured (Not default)
        }
    }
    foreach ($tag in $_definitionTag) {
        if (Test-PodeOAVersion -Version 3.0 -DefinitionTag $tag  ) {
            # The 'pathItems' reusable component feature is not available in OpenAPI v3.0.
            throw ($PodeLocale.reusableComponentPathItemsNotAvailableInOpenApi30ExceptionMessage)
        }
        #add the default OpenApi responses
        if ( $PodeContext.Server.OpenAPI.Definitions[$tag].hiddenComponents.defaultResponses) {
            $refRoute.OpenApi.Responses = Copy-PodeObjectDeepClone -InputObject $PodeContext.Server.OpenAPI.Definitions[$tag].hiddenComponents.defaultResponses
        }
        $PodeContext.Server.OpenAPI.Definitions[$tag].components.pathItems[$Name] = $refRoute
    }

    if ($PassThru) {
        return $refRoute
    }
}





<#
.SYNOPSIS
Check the OpenAPI version

.DESCRIPTION
Check the OpenAPI version for a specific OpenAPI Definition


.PARAMETER Version
The version number to compare

.PARAMETER DefinitionTag
An Array of strings representing the unique tag for the API specification.
This tag helps in distinguishing between different versions or types of API specifications within the application.
You can use this tag to reference the specific API documentation, schema, or version that your function interacts with.

.EXAMPLE
Test-PodeOAVersion -Version 3.1 -DefinitionTag 'default'
#>

function Test-PodeOAVersion {
    param (
        [Parameter(Mandatory = $true)]
        [ValidateSet( '3.1' , '3.0' )]
        [string]
        $Version,

        [Parameter(Mandatory = $true)]
        [string[] ]
        $DefinitionTag
    )

    return $PodeContext.Server.OpenAPI.Definitions[$DefinitionTag].hiddenComponents.version -eq $Version
}

<#
.SYNOPSIS
Check the OpenAPI component exist

.DESCRIPTION
Check the OpenAPI component exist

.PARAMETER Field
The component type

.PARAMETER Name
The component Name

.PARAMETER DefinitionTag
An Array of strings representing the unique tag for the API specification.
This tag helps in distinguishing between different versions or types of API specifications within the application.
You can use this tag to reference the specific API documentation, schema, or version that your function interacts with.

.EXAMPLE
Test-PodeOAComponent -Field 'responses' -Name 'myresponse' -DefinitionTag 'default'
#>

function Test-PodeOAComponent {
    param(
        [Parameter(Mandatory = $true)]
        [ValidateSet( 'schemas' , 'responses' , 'parameters' , 'examples' , 'requestBodies' , 'headers' , 'securitySchemes' , 'links' , 'callbacks' , 'pathItems' )]
        [string]
        $Field,

        [Parameter(Mandatory = $true)]
        [ValidateNotNullOrEmpty()]
        [string]
        $Name,

        [string[]]
        $DefinitionTag
    )

    $DefinitionTag = Test-PodeOADefinitionTag -Tag $DefinitionTag

    foreach ($tag in $DefinitionTag) {
        if (!($PodeContext.Server.OpenAPI.Definitions[$tag].components[$field].keys -ccontains $Name)) {
            return $false
        }
    }
    if (!$ThrowException.IsPresent) {
        return $true
    }
}

<#
.SYNOPSIS
Remove an OpenAPI component if exist

.DESCRIPTION
Remove an OpenAPI component if exist

.PARAMETER Field
The component type

.PARAMETER Name
The component Name

.PARAMETER DefinitionTag
An Array of strings representing the unique tag for the API specification.
This tag helps in distinguishing between different versions or types of API specifications within the application.
You can use this tag to reference the specific API documentation, schema, or version that your function interacts with.

.EXAMPLE
Remove-PodeOAComponent -Field 'responses' -Name 'myresponse' -DefinitionTag 'default'
#>
function Remove-PodeOAComponent {
    param(
        [Parameter(Mandatory = $true)]
        [ValidateSet( 'schemas' , 'responses' , 'parameters' , 'examples' , 'requestBodies' , 'headers' , 'securitySchemes' , 'links' , 'callbacks' , 'pathItems'  )]
        [string]
        $Field,

        [Parameter(Mandatory = $true)]
        [AllowEmptyString()]
        [ValidateNotNullOrEmpty()]
        [string]
        $Name,

        [string[]]
        $DefinitionTag
    )
    $DefinitionTag = Test-PodeOADefinitionTag -Tag $DefinitionTag
    foreach ($tag in $DefinitionTag) {
        if (!($PodeContext.Server.OpenAPI.Definitions[$tag].components[$field ].keys -ccontains $Name)) {
            $PodeContext.Server.OpenAPI.Definitions[$tag].components[$field ].remove($Name)
        }
    }
}

if (!(Test-Path Alias:Enable-PodeOpenApiViewer)) {
    New-Alias Enable-PodeOpenApiViewer -Value Enable-PodeOAViewer
}

if (!(Test-Path Alias:Enable-PodeOA)) {
    New-Alias Enable-PodeOA -Value Enable-PodeOpenApi
}

if (!(Test-Path Alias:Get-PodeOpenApiDefinition)) {
    New-Alias Get-PodeOpenApiDefinition -Value Get-PodeOADefinition
}
src\Public\OAProperties.ps1

<#
.SYNOPSIS
Creates a new OpenAPI New-PodeOAMultiTypeProperty property.

.DESCRIPTION
Creates a new OpenAPI multi type property, for Schemas or Parameters.
OpenAPI version 3.1 is required to use this cmdlet.

.LINK
https://swagger.io/docs/specification/basic-structure/

.LINK
https://swagger.io/docs/specification/data-models/

.PARAMETER ParamsList
Used to pipeline multiple properties

.PARAMETER Name
The Name of the property.

.PARAMETER Type
The parameter types

.PARAMETER Format
The inbuilt OpenAPI Format  . (Default: Any)

.PARAMETER CustomFormat
The name of a custom OpenAPI Format  . (Default: None)
(String type only)

.PARAMETER Default
The default value of the property. (Default: $null)

.PARAMETER Pattern
A Regex pattern that the string must match.
(String type only)

.PARAMETER Description
A Description of the property.

.PARAMETER Minimum
The minimum value of the number.
(Integer,Number types only)

.PARAMETER Maximum
The maximum value of the number.
(Integer,Number types only)

.PARAMETER ExclusiveMaximum
Specifies an exclusive upper limit for a numeric property in the OpenAPI schema.
When this parameter is used, it sets the exclusiveMaximum attribute in the OpenAPI definition to true, indicating that the numeric value must be strictly less than the specified maximum value.
This parameter is typically paired with a -Maximum parameter to define the upper bound.
(Integer,Number types only)

.PARAMETER ExclusiveMinimum
Specifies an exclusive lower limit for a numeric property in the OpenAPI schema.
When this parameter is used, it sets the exclusiveMinimun attribute in the OpenAPI definition to true, indicating that the numeric value must be strictly less than the specified minimun value.
This parameter is typically paired with a -Minimum parameter to define the lower bound.
(Integer,Number types only)

.PARAMETER MultiplesOf
The number must be in multiples of the supplied value.
(Integer,Number types only)

.PARAMETER Properties
An array of other int/string/etc properties wrap up as an object.
(Object type only)

.PARAMETER ExternalDoc
If supplied, add an additional external documentation for this operation.
The parameter is created by Add-PodeOAExternalDoc

.PARAMETER Example
An example of a parameter value

.PARAMETER Enum
An optional array of values that this property can only be set to.

.PARAMETER Required
If supplied, the string will be treated as Required where supported.

.PARAMETER Deprecated
If supplied, the string will be treated as Deprecated where supported.

.PARAMETER Object
If supplied, the string will be automatically wrapped in an object.

.PARAMETER Nullable
If supplied, the string will be treated as Nullable.

.PARAMETER ReadOnly
If supplied, the string will be included in a response but not in a request

.PARAMETER WriteOnly
If supplied, the string will be included in a request but not in a response

.PARAMETER MinLength
If supplied, the string will be restricted to minimal length of characters.

.PARAMETER  MaxLength
If supplied, the string will be restricted to maximal length of characters.

.PARAMETER NoProperties
If supplied, no properties are allowed in the object.
If no properties are assigned to the object and the NoProperties parameter is not set the object accept any property.(Object type only)

.PARAMETER MinProperties
If supplied, will restrict the minimun number of properties allowed in an object.
(Object type only)

.PARAMETER MaxProperties
If supplied, will restrict the maximum number of properties allowed in an object.
(Object type only)

.PARAMETER NoAdditionalProperties
If supplied, will configure the OpenAPI property additionalProperties to false.
This means that the defined object will not allow any properties beyond those explicitly declared in its schema.
If any additional properties are provided, they will be considered invalid.
Use this switch to enforce a strict schema definition, ensuring that objects contain only the specified set of properties and no others.

.PARAMETER AdditionalProperties
Define a set of additional properties for the OpenAPI schema. This parameter accepts a HashTable where each key-value pair represents a property name and its corresponding schema.
The schema for each property can include type, format, description, and other OpenAPI specification attributes.
When specified, these additional properties are included in the OpenAPI definition, allowing for more flexible and dynamic object structures.

.PARAMETER Array
If supplied, the object will be treated as an array of objects.

.PARAMETER UniqueItems
If supplied, specify that all items in the array must be unique

.PARAMETER MinItems
If supplied, specify minimum length of an array

.PARAMETER MaxItems
If supplied, specify maximum length of an array

.PARAMETER DiscriminatorProperty
If supplied, specifies the name of the property used to distinguish between different subtypes in a polymorphic schema in OpenAPI.
This string value represents the property in the payload that indicates which specific subtype schema should be applied.
It's essential in scenarios where an API endpoint handles data that conforms to one of several derived schemas from a common base schema.

.PARAMETER DiscriminatorMapping
If supplied, define a mapping between the values of the discriminator property and the corresponding subtype schemas.
This parameter accepts a HashTable where each key-value pair maps a discriminator value to a specific subtype schema name.
It's used in conjunction with the -DiscriminatorProperty to provide complete discrimination logic in polymorphic scenarios.

.PARAMETER XmlName
By default, XML elements get the same names that fields in the API declaration have. This property change the XML name of the property
reflecting the 'xml.name' attribute in the OpenAPI specification.

.PARAMETER XmlNamespace
Defines a specific XML namespace for the property, corresponding to the 'xml.namespace' attribute in OpenAPI.

.PARAMETER XmlPrefix
Sets a prefix for the XML element name, aligning with the 'xml.prefix' attribute in OpenAPI.

.PARAMETER XmlAttribute
Indicates whether the property should be serialized as an XML attribute, equivalent to the 'xml.attribute' attribute in OpenAPI.

.PARAMETER XmlItemName
Specifically for properties treated as arrays, it defines the XML name for each item in the array. This parameter aligns with the 'xml.name' attribute under 'items' in OpenAPI.

.PARAMETER XmlWrapped
Indicates whether array items should be wrapped in an XML element, similar to the 'xml.wrapped' attribute in OpenAPI.

.EXAMPLE
New-PodeOAMultiTypeProperty -Name 'userType' -type integer,boolean

.EXAMPLE
New-PodeOAMultiTypeProperty -Name 'password' -type string,object -Format Password -Properties (New-PodeOAStringProperty -Name 'password' -Format Password)
#>
function New-PodeOAMultiTypeProperty {
    [CmdletBinding(DefaultParameterSetName = 'Inbuilt')]
    param(
        [Parameter(ValueFromPipeline = $true, Position = 0, DontShow = $true )]
        [hashtable[]]
        $ParamsList,

        [Parameter(Mandatory)]
        [ValidateSet( 'integer', 'number', 'string', 'object', 'boolean' )]
        [string]
        $Type,

        [Parameter()]
        [ValidatePattern('^[a-zA-Z0-9\.\-_]+$')]
        [Alias('Title')]
        [string]
        $Name,

        [Parameter( ParameterSetName = 'Array')]
        [Parameter(ParameterSetName = 'Inbuilt')]
        [ValidateSet('', 'Int32', 'Int64', 'Double', 'Float', 'Binary', 'Base64', 'Byte', 'Date', 'Date-Time', 'Password', 'Email', 'Uuid', 'Uri', 'Hostname', 'Ipv4', 'Ipv6')]
        [string]
        $Format,

        [Parameter( ParameterSetName = 'Array')]
        [Parameter(ParameterSetName = 'Custom')]
        [string]
        $CustomFormat,

        [Parameter()]
        $Default,

        [Parameter()]
        [string]
        $Pattern,

        [Parameter()]
        [hashtable[]]
        $Properties,

        [Parameter()]
        [string]
        $Description,

        [Parameter()]
        [double]
        $Minimum,

        [Parameter()]
        [double]
        $Maximum,

        [Parameter()]
        [switch]
        $ExclusiveMaximum,

        [Parameter()]
        [switch]
        $ExclusiveMinimum,

        [Parameter()]
        [double]
        $MultiplesOf,

        [Parameter()]
        [string]
        $ExternalDoc,

        [Parameter()]
        [object]
        $Example,

        [Parameter()]
        [object[]]
        $Enum,

        [switch]
        $Required,

        [switch]
        $Deprecated,

        [switch]
        $Object,

        [switch]
        $Nullable,

        [switch]
        $ReadOnly,

        [switch]
        $WriteOnly,

        [Parameter()]
        [int]
        $MinLength,

        [Parameter()]
        [int]
        $MaxLength,

        [switch]
        $NoProperties,

        [int]
        $MinProperties,

        [int]
        $MaxProperties,

        [switch]
        $NoAdditionalProperties,

        [hashtable]
        $AdditionalProperties,

        [string]
        $XmlName,

        [string]
        $XmlNamespace,

        [string]
        $XmlPrefix,

        [switch]
        $XmlAttribute,

        [Parameter(  ParameterSetName = 'Array')]
        [string]
        $XmlItemName,

        [Parameter(  ParameterSetName = 'Array')]
        [switch]
        $XmlWrapped,

        [Parameter(Mandatory = $true, ParameterSetName = 'Array')]
        [switch]
        $Array,

        [Parameter(ParameterSetName = 'Array')]
        [switch]
        $UniqueItems,

        [Parameter(ParameterSetName = 'Array')]
        [int]
        $MinItems,

        [Parameter(ParameterSetName = 'Array')]
        [int]
        $MaxItems,

        [string]
        $DiscriminatorProperty,

        [hashtable]
        $DiscriminatorMapping
    )
    begin {
        $param = New-PodeOAPropertyInternal   -Params $PSBoundParameters

        if ($type -contains 'string') {
            if (![string]::IsNullOrWhiteSpace($CustomFormat)) {
                $_format = $CustomFormat
            }
            elseif ($Format) {
                $_format = $Format
            }


            if ($Format -or $CustomFormat) {
                $param.format = $_format.ToLowerInvariant()
            }
        }
        if ($type -contains 'object') {
            if ($NoProperties) {
                if ($Properties -or $MinProperties -or $MaxProperties) {
                    # The parameter 'NoProperties' is mutually exclusive with 'Properties', 'MinProperties' and 'MaxProperties'
                    throw ($PodeLocale.noPropertiesMutuallyExclusiveExceptionMessage)
                }
                $param.properties = @($null)
            }
            elseif ($Properties) {
                $param.properties = $Properties
            }
            else {
                $param.properties = @()
            }
            if ($DiscriminatorProperty) {
                $param.discriminator = [ordered]@{
                    'propertyName' = $DiscriminatorProperty
                }
                if ($DiscriminatorMapping) {
                    $param.discriminator.mapping = $DiscriminatorMapping
                }
            }
            elseif ($DiscriminatorMapping) {
                # The parameter 'DiscriminatorMapping' can only be used when 'DiscriminatorProperty' is present
                throw ($PodeLocale.discriminatorMappingRequiresDiscriminatorPropertyExceptionMessage)
            }
        }
        if ($type -contains 'boolean') {
            if ($Default) {
                if ([bool]::TryParse($Default, [ref]$null) -or $Enum -icontains $Default) {
                    $param.default = $Default
                }
                else {
                    # The default value is not a boolean and is not part of the enum
                    throw ($PodeLocale.defaultValueNotBooleanOrEnumExceptionMessage)
                }
            }
        }
        $collectedInput = [System.Collections.Generic.List[hashtable]]::new()
    }
    process {
        if ($ParamsList) {
            $collectedInput.AddRange($ParamsList)
        }
    }

    end {
        if ($collectedInput) {
            return $collectedInput + $param
        }
        else {
            return $param
        }
    }
}

<#
.SYNOPSIS
Creates a new OpenAPI integer property.

.DESCRIPTION
Creates a new OpenAPI integer property, for Schemas or Parameters.

.LINK
https://swagger.io/docs/specification/basic-structure/

.LINK
https://swagger.io/docs/specification/data-models/

.PARAMETER ParamsList
Used to pipeline multiple properties

.PARAMETER Name
The Name of the property.

.PARAMETER Format
The inbuilt OpenAPI Format of the integer. (Default: Any)

.PARAMETER Default
The default value of the property. (Default: 0)

.PARAMETER Minimum
The minimum value of the integer. (Default: Int.Min)

.PARAMETER Maximum
The maximum value of the integer. (Default: Int.Max)

.PARAMETER ExclusiveMaximum
Specifies an exclusive upper limit for a numeric property in the OpenAPI schema.
When this parameter is used, it sets the exclusiveMaximum attribute in the OpenAPI definition to true, indicating that the numeric value must be strictly less than the specified maximum value.
This parameter is typically paired with a -Maximum parameter to define the upper bound.

.PARAMETER ExclusiveMinimum
Specifies an exclusive lower limit for a numeric property in the OpenAPI schema.
When this parameter is used, it sets the exclusiveMinimun attribute in the OpenAPI definition to true, indicating that the numeric value must be strictly less than the specified minimun value.
This parameter is typically paired with a -Minimum parameter to define the lower bound.

.PARAMETER MultiplesOf
The integer must be in multiples of the supplied value.

.PARAMETER Description
A Description of the property.

.PARAMETER ExternalDoc
If supplied, add an additional external documentation for this operation.
The parameter is created by Add-PodeOAExternalDoc

.PARAMETER Example
An example of a parameter value

.PARAMETER Enum
An optional array of values that this property can only be set to.

.PARAMETER Required
If supplied, the object will be treated as Required where supported.

.PARAMETER Deprecated
If supplied, the object will be treated as Deprecated where supported.

.PARAMETER Object
If supplied, the integer will be automatically wrapped in an object.

.PARAMETER Nullable
If supplied, the integer will be treated as Nullable.

.PARAMETER ReadOnly
If supplied, the integer will be included in a response but not in a request

.PARAMETER WriteOnly
If supplied, the integer will be included in a request but not in a response

.PARAMETER NoAdditionalProperties
If supplied, will configure the OpenAPI property additionalProperties to false.
This means that the defined object will not allow any properties beyond those explicitly declared in its schema.
If any additional properties are provided, they will be considered invalid.
Use this switch to enforce a strict schema definition, ensuring that objects contain only the specified set of properties and no others.

.PARAMETER AdditionalProperties
Define a set of additional properties for the OpenAPI schema. This parameter accepts a HashTable where each key-value pair represents a property name and its corresponding schema.
The schema for each property can include type, format, description, and other OpenAPI specification attributes.
When specified, these additional properties are included in the OpenAPI definition, allowing for more flexible and dynamic object structures.

.PARAMETER Array
If supplied, the object will be treated as an array of objects.

.PARAMETER UniqueItems
If supplied, specify that all items in the array must be unique

.PARAMETER MinItems
If supplied, specify minimum length of an array

.PARAMETER MaxItems
If supplied, specify maximum length of an array

.PARAMETER XmlName
By default, XML elements get the same names that fields in the API declaration have. This property change the XML name of the property
reflecting the 'xml.name' attribute in the OpenAPI specification.

.PARAMETER XmlNamespace
Defines a specific XML namespace for the property, corresponding to the 'xml.namespace' attribute in OpenAPI.

.PARAMETER XmlPrefix
Sets a prefix for the XML element name, aligning with the 'xml.prefix' attribute in OpenAPI.

.PARAMETER XmlAttribute
Indicates whether the property should be serialized as an XML attribute, equivalent to the 'xml.attribute' attribute in OpenAPI.

.PARAMETER XmlItemName
Specifically for properties treated as arrays, it defines the XML name for each item in the array. This parameter aligns with the 'xml.name' attribute under 'items' in OpenAPI.

.PARAMETER XmlWrapped
Indicates whether array items should be wrapped in an XML element, similar to the 'xml.wrapped' attribute in OpenAPI.


.EXAMPLE
New-PodeOAIntProperty -Name 'age' -Required
Creates a required integer property named 'age'.

.EXAMPLE
New-PodeOAIntProperty -Name 'count' -Minimum 0 -Maximum 10 -Default 5 -Description 'Item count'
Creates an integer property 'count' with a minimum value of 0, maximum of 10, default value of 5, and a description.

.EXAMPLE
New-PodeOAIntProperty -Name 'quantity' -XmlName 'Quantity' -XmlNamespace 'http://example.com/quantity' -XmlPrefix 'q'
Creates an integer property 'quantity' with a custom XML element name 'Quantity', using a specified namespace and prefix.

.EXAMPLE
New-PodeOAIntProperty -Array -XmlItemName 'unit' -XmlName 'units' | Add-PodeOAComponentSchema -Name 'Units'
Generates a schema where the integer property is treated as an array, with each array item named 'unit' in XML, and the array itself represented with the XML name 'units'.


#>
function New-PodeOAIntProperty {
    [CmdletBinding(DefaultParameterSetName = 'Inbuilt')]
    [OutputType([System.Collections.Specialized.OrderedDictionary])]
    param(
        [Parameter(ValueFromPipeline = $true, Position = 0, DontShow = $true)]
        [hashtable[]]
        $ParamsList,

        [Parameter()]
        [ValidatePattern('^[a-zA-Z0-9\.\-_]+$')]
        [Alias('Title')]
        [string]
        $Name,

        [Parameter()]
        [ValidateSet('', 'Int32', 'Int64')]
        [string]
        $Format,

        [Parameter()]
        [int]
        $Default,

        [Parameter()]
        [int]
        $Minimum,

        [Parameter()]
        [int]
        $Maximum,

        [Parameter()]
        [switch]
        $ExclusiveMaximum,

        [Parameter()]
        [switch]
        $ExclusiveMinimum,

        [Parameter()]
        [int]
        $MultiplesOf,

        [Parameter()]
        [string]
        $Description,

        [Parameter()]
        [string]
        $ExternalDoc,

        [Parameter()]
        [object]
        $Example,

        [Parameter()]
        [int[]]
        $Enum,

        [switch]
        $Required,

        [switch]
        $Deprecated,

        [switch]
        $Object,

        [switch]
        $Nullable,

        [switch]
        $ReadOnly,

        [switch]
        $WriteOnly,

        [switch]
        $NoAdditionalProperties,

        [hashtable]
        $AdditionalProperties,

        [string]
        $XmlName,

        [string]
        $XmlNamespace,

        [string]
        $XmlPrefix,

        [switch]
        $XmlAttribute,

        [Parameter(  ParameterSetName = 'Array')]
        [string]
        $XmlItemName,

        [Parameter(  ParameterSetName = 'Array')]
        [switch]
        $XmlWrapped,

        [Parameter(Mandatory = $true, ParameterSetName = 'Array')]
        [switch]
        $Array,

        [Parameter(ParameterSetName = 'Array')]
        [switch]
        $UniqueItems,

        [Parameter(ParameterSetName = 'Array')]
        [int]
        $MinItems,

        [Parameter(ParameterSetName = 'Array')]
        [int]
        $MaxItems
    )
    begin {
        $param = New-PodeOAPropertyInternal -type 'integer' -Params $PSBoundParameters

        $collectedInput = [System.Collections.Generic.List[hashtable]]::new()
    }
    process {
        if ($ParamsList) {
            $collectedInput.AddRange($ParamsList)
        }
    }

    end {
        if ($collectedInput) {
            return $collectedInput + $param
        }
        else {
            return $param
        }
    }
}

<#
.SYNOPSIS
Creates a new OpenAPI number property.

.DESCRIPTION
Creates a new OpenAPI number property, for Schemas or Parameters.

.LINK
https://swagger.io/docs/specification/basic-structure/

.LINK
https://swagger.io/docs/specification/data-models/

.PARAMETER ParamsList
Used to pipeline multiple properties

.PARAMETER Name
The Name of the property.

.PARAMETER Format
The inbuilt OpenAPI Format of the number. (Default: Any)

.PARAMETER Default
The default value of the property. (Default: 0)

.PARAMETER Minimum
The minimum value of the number. (Default: Double.Min)

.PARAMETER Maximum
The maximum value of the number. (Default: Double.Max)

.PARAMETER ExclusiveMaximum
Specifies an exclusive upper limit for a numeric property in the OpenAPI schema.
When this parameter is used, it sets the exclusiveMaximum attribute in the OpenAPI definition to true, indicating that the numeric value must be strictly less than the specified maximum value.
This parameter is typically paired with a -Maximum parameter to define the upper bound.

.PARAMETER ExclusiveMinimum
Specifies an exclusive lower limit for a numeric property in the OpenAPI schema.
When this parameter is used, it sets the exclusiveMinimun attribute in the OpenAPI definition to true, indicating that the numeric value must be strictly less than the specified minimun value.
This parameter is typically paired with a -Minimum parameter to define the lower bound.

.PARAMETER MultiplesOf
The number must be in multiples of the supplied value.

.PARAMETER Description
A Description of the property.

.PARAMETER ExternalDoc
If supplied, add an additional external documentation for this operation.
The parameter is created by Add-PodeOAExternalDoc

.PARAMETER Example
An example of a parameter value

.PARAMETER Enum
An optional array of values that this property can only be set to.

.PARAMETER Required
If supplied, the object will be treated as Required where supported.

.PARAMETER Deprecated
If supplied, the object will be treated as Deprecated where supported.

.PARAMETER Object
If supplied, the number will be automatically wrapped in an object.

.PARAMETER Nullable
If supplied, the number will be treated as Nullable.

.PARAMETER ReadOnly
If supplied, the number will be included in a response but not in a request

.PARAMETER WriteOnly
If supplied, the number will be included in a request but not in a response

.PARAMETER NoAdditionalProperties
If supplied, will configure the OpenAPI property additionalProperties to false.
This means that the defined object will not allow any properties beyond those explicitly declared in its schema.
If any additional properties are provided, they will be considered invalid.
Use this switch to enforce a strict schema definition, ensuring that objects contain only the specified set of properties and no others.

.PARAMETER AdditionalProperties
Define a set of additional properties for the OpenAPI schema. This parameter accepts a HashTable where each key-value pair represents a property name and its corresponding schema.
The schema for each property can include type, format, description, and other OpenAPI specification attributes.
When specified, these additional properties are included in the OpenAPI definition, allowing for more flexible and dynamic object structures.

.PARAMETER Array
If supplied, the object will be treated as an array of objects.

.PARAMETER UniqueItems
If supplied, specify that all items in the array must be unique

.PARAMETER MinItems
If supplied, specify minimum length of an array

.PARAMETER MaxItems
If supplied, specify maximum length of an array

.PARAMETER XmlName
By default, XML elements get the same names that fields in the API declaration have. This property change the XML name of the property
reflecting the 'xml.name' attribute in the OpenAPI specification.

.PARAMETER XmlNamespace
Defines a specific XML namespace for the property, corresponding to the 'xml.namespace' attribute in OpenAPI.

.PARAMETER XmlPrefix
Sets a prefix for the XML element name, aligning with the 'xml.prefix' attribute in OpenAPI.

.PARAMETER XmlAttribute
Indicates whether the property should be serialized as an XML attribute, equivalent to the 'xml.attribute' attribute in OpenAPI.

.PARAMETER XmlItemName
Specifically for properties treated as arrays, it defines the XML name for each item in the array. This parameter aligns with the 'xml.name' attribute under 'items' in OpenAPI.

.PARAMETER XmlWrapped
Indicates whether array items should be wrapped in an XML element, similar to the 'xml.wrapped' attribute in OpenAPI.

.EXAMPLE
New-PodeOANumberProperty -Name 'gravity' -Default 9.8
#>
function New-PodeOANumberProperty {
    [CmdletBinding(DefaultParameterSetName = 'Inbuilt')]
    param(
        [Parameter(ValueFromPipeline = $true, Position = 0, DontShow = $true )]
        [hashtable[]]
        $ParamsList,

        [Parameter()]
        [ValidatePattern('^[a-zA-Z0-9\.\-_]+$')]
        [Alias('Title')]
        [string]
        $Name,

        [Parameter()]
        [ValidateSet('', 'Double', 'Float')]
        [string]
        $Format,

        [Parameter()]
        [double]
        $Default,

        [Parameter()]
        [double]
        $Minimum,

        [Parameter()]
        [double]
        $Maximum,

        [Parameter()]
        [switch]
        $ExclusiveMaximum,

        [Parameter()]
        [switch]
        $ExclusiveMinimum,

        [Parameter()]
        [double]
        $MultiplesOf,

        [Parameter()]
        [string]
        $Description,

        [Parameter()]
        [string]
        $ExternalDoc,

        [Parameter()]
        [object]
        $Example,

        [Parameter()]
        [double[]]
        $Enum,

        [switch]
        $Required,

        [switch]
        $Deprecated,

        [switch]
        $Object,

        [switch]
        $Nullable,

        [switch]
        $ReadOnly,

        [switch]
        $WriteOnly,

        [switch]
        $NoAdditionalProperties,

        [hashtable]
        $AdditionalProperties,

        [string]
        $XmlName,

        [string]
        $XmlNamespace,

        [string]
        $XmlPrefix,

        [switch]
        $XmlAttribute,

        [Parameter(  ParameterSetName = 'Array')]
        [string]
        $XmlItemName,

        [Parameter(  ParameterSetName = 'Array')]
        [switch]
        $XmlWrapped,

        [Parameter(Mandatory = $true, ParameterSetName = 'Array')]
        [switch]
        $Array,

        [Parameter(ParameterSetName = 'Array')]
        [switch]
        $UniqueItems,

        [Parameter(ParameterSetName = 'Array')]
        [int]
        $MinItems,

        [Parameter(ParameterSetName = 'Array')]
        [int]
        $MaxItems
    )
    begin {
        $param = New-PodeOAPropertyInternal -type 'number' -Params $PSBoundParameters

        $collectedInput = [System.Collections.Generic.List[hashtable]]::new()
    }
    process {
        if ($ParamsList) {
            $collectedInput.AddRange($ParamsList)
        }
    }

    end {
        if ($collectedInput) {
            return $collectedInput + $param
        }
        else {
            return $param
        }
    }
}

<#
.SYNOPSIS
Creates a new OpenAPI string property.

.DESCRIPTION
Creates a new OpenAPI string property, for Schemas or Parameters.

.LINK
https://swagger.io/docs/specification/basic-structure/

.LINK
https://swagger.io/docs/specification/data-models/

.PARAMETER ParamsList
Used to pipeline multiple properties

.PARAMETER Name
The Name of the property.

.PARAMETER Format
The inbuilt OpenAPI Format of the string. (Default: Any)

.PARAMETER CustomFormat
The name of a custom OpenAPI Format of the string. (Default: None)

.PARAMETER Default
The default value of the property. (Default: $null)

.PARAMETER Pattern
A Regex pattern that the string must match.

.PARAMETER Description
A Description of the property.

.PARAMETER ExternalDoc
If supplied, add an additional external documentation for this operation.
The parameter is created by Add-PodeOAExternalDoc

.PARAMETER Example
An example of a parameter value

.PARAMETER Enum
An optional array of values that this property can only be set to.

.PARAMETER Required
If supplied, the string will be treated as Required where supported.

.PARAMETER Deprecated
If supplied, the string will be treated as Deprecated where supported.

.PARAMETER Object
If supplied, the string will be automatically wrapped in an object.

.PARAMETER Nullable
If supplied, the string will be treated as Nullable.

.PARAMETER ReadOnly
If supplied, the string will be included in a response but not in a request

.PARAMETER WriteOnly
If supplied, the string will be included in a request but not in a response

.PARAMETER MinLength
If supplied, the string will be restricted to minimal length of characters.

.PARAMETER  MaxLength
If supplied, the string will be restricted to maximal length of characters.

.PARAMETER NoAdditionalProperties
If supplied, will configure the OpenAPI property additionalProperties to false.
This means that the defined object will not allow any properties beyond those explicitly declared in its schema.
If any additional properties are provided, they will be considered invalid.
Use this switch to enforce a strict schema definition, ensuring that objects contain only the specified set of properties and no others.

.PARAMETER AdditionalProperties
Define a set of additional properties for the OpenAPI schema. This parameter accepts a HashTable where each key-value pair represents a property name and its corresponding schema.
The schema for each property can include type, format, description, and other OpenAPI specification attributes.
When specified, these additional properties are included in the OpenAPI definition, allowing for more flexible and dynamic object structures.

.PARAMETER Array
If supplied, the object will be treated as an array of objects.

.PARAMETER UniqueItems
If supplied, specify that all items in the array must be unique

.PARAMETER MinItems
If supplied, specify minimum length of an array

.PARAMETER MaxItems
If supplied, specify maximum length of an array

.PARAMETER XmlName
By default, XML elements get the same names that fields in the API declaration have. This property change the XML name of the property
reflecting the 'xml.name' attribute in the OpenAPI specification.

.PARAMETER XmlNamespace
Defines a specific XML namespace for the property, corresponding to the 'xml.namespace' attribute in OpenAPI.

.PARAMETER XmlPrefix
Sets a prefix for the XML element name, aligning with the 'xml.prefix' attribute in OpenAPI.

.PARAMETER XmlAttribute
Indicates whether the property should be serialized as an XML attribute, equivalent to the 'xml.attribute' attribute in OpenAPI.

.PARAMETER XmlItemName
Specifically for properties treated as arrays, it defines the XML name for each item in the array. This parameter aligns with the 'xml.name' attribute under 'items' in OpenAPI.

.PARAMETER XmlWrapped
Indicates whether array items should be wrapped in an XML element, similar to the 'xml.wrapped' attribute in OpenAPI.

.EXAMPLE
New-PodeOAStringProperty -Name 'userType' -Default 'admin'

.EXAMPLE
New-PodeOAStringProperty -Name 'password' -Format Password
#>
function New-PodeOAStringProperty {
    [CmdletBinding(DefaultParameterSetName = 'Inbuilt')]
    param(
        [Parameter(ValueFromPipeline = $true, Position = 0, DontShow = $true )]
        [hashtable[]]
        $ParamsList,

        [Parameter()]
        [ValidatePattern('^[a-zA-Z0-9\.\-_]+$')]
        [Alias('Title')]
        [string]
        $Name,

        [Parameter( ParameterSetName = 'Array')]
        [Parameter(ParameterSetName = 'Inbuilt')]
        [ValidateSet('', 'Binary', 'Base64', 'Byte', 'Date', 'Date-Time', 'Password', 'Email', 'Uuid', 'Uri', 'Hostname', 'Ipv4', 'Ipv6')]
        [string]
        $Format,

        [Parameter( ParameterSetName = 'Array')]
        [Parameter(ParameterSetName = 'Custom')]
        [string]
        $CustomFormat,

        [Parameter()]
        [string]
        $Default,

        [Parameter()]
        [string]
        $Pattern,

        [Parameter()]
        [string]
        $Description,

        [Parameter()]
        [string]
        $ExternalDoc,

        [Parameter()]
        [object]
        $Example,

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

        [switch]
        $Required,

        [switch]
        $Deprecated,

        [switch]
        $Object,

        [switch]
        $Nullable,

        [switch]
        $ReadOnly,

        [switch]
        $WriteOnly,

        [Parameter()]
        [int]
        $MinLength,

        [Parameter()]
        [int]
        $MaxLength,

        [switch]
        $NoAdditionalProperties,

        [hashtable]
        $AdditionalProperties,

        [string]
        $XmlName,

        [string]
        $XmlNamespace,

        [string]
        $XmlPrefix,

        [switch]
        $XmlAttribute,

        [Parameter(  ParameterSetName = 'Array')]
        [string]
        $XmlItemName,

        [Parameter(  ParameterSetName = 'Array')]
        [switch]
        $XmlWrapped,

        [Parameter(Mandatory = $true, ParameterSetName = 'Array')]
        [switch]
        $Array,

        [Parameter(ParameterSetName = 'Array')]
        [switch]
        $UniqueItems,

        [Parameter(ParameterSetName = 'Array')]
        [int]
        $MinItems,

        [Parameter(ParameterSetName = 'Array')]
        [int]
        $MaxItems
    )
    begin {
        if (![string]::IsNullOrWhiteSpace($CustomFormat)) {
            $_format = $CustomFormat
        }
        elseif ($Format) {
            $_format = $Format
        }
        $param = New-PodeOAPropertyInternal -type 'string' -Params $PSBoundParameters

        if ($Format -or $CustomFormat) {
            $param.format = $_format.ToLowerInvariant()
        }

        $collectedInput = [System.Collections.Generic.List[hashtable]]::new()
    }
    process {
        if ($ParamsList) {
            $collectedInput.AddRange($ParamsList)
        }
    }

    end {
        if ($collectedInput) {
            return $collectedInput + $param
        }
        else {
            return $param
        }
    }
}

<#
.SYNOPSIS
Creates a new OpenAPI boolean property.

.DESCRIPTION
Creates a new OpenAPI boolean property, for Schemas or Parameters.

.LINK
https://swagger.io/docs/specification/basic-structure/

.LINK
https://swagger.io/docs/specification/data-models/

.PARAMETER ParamsList
Used to pipeline multiple properties

.PARAMETER Name
The Name of the property.

.PARAMETER Default
The default value of the property. (Default: $false)

.PARAMETER Description
A Description of the property.

.PARAMETER ExternalDoc
If supplied, add an additional external documentation for this operation.
The parameter is created by Add-PodeOAExternalDoc

.PARAMETER Example
An example of a parameter value

.PARAMETER Enum
An optional array of values that this property can only be set to.

.PARAMETER Required
If supplied, the object will be treated as Required where supported.

.PARAMETER Deprecated
If supplied, the object will be treated as Deprecated where supported.

.PARAMETER Object
If supplied, the boolean will be automatically wrapped in an object.

.PARAMETER Nullable
If supplied, the boolean will be treated as Nullable.

.PARAMETER ReadOnly
If supplied, the boolean will be included in a response but not in a request

.PARAMETER WriteOnly
If supplied, the boolean will be included in a request but not in a response

.PARAMETER NoAdditionalProperties
If supplied, will configure the OpenAPI property additionalProperties to false.
This means that the defined object will not allow any properties beyond those explicitly declared in its schema.
If any additional properties are provided, they will be considered invalid.
Use this switch to enforce a strict schema definition, ensuring that objects contain only the specified set of properties and no others.

.PARAMETER AdditionalProperties
Define a set of additional properties for the OpenAPI schema. This parameter accepts a HashTable where each key-value pair represents a property name and its corresponding schema.
The schema for each property can include type, format, description, and other OpenAPI specification attributes.
When specified, these additional properties are included in the OpenAPI definition, allowing for more flexible and dynamic object structures.

.PARAMETER Array
If supplied, the object will be treated as an array of objects.

.PARAMETER UniqueItems
If supplied, specify that all items in the array must be unique

.PARAMETER MinItems
If supplied, specify minimum length of an array

.PARAMETER MaxItems
If supplied, specify maximum length of an array

.PARAMETER XmlName
By default, XML elements get the same names that fields in the API declaration have. This property change the XML name of the property
reflecting the 'xml.name' attribute in the OpenAPI specification.

.PARAMETER XmlNamespace
Defines a specific XML namespace for the property, corresponding to the 'xml.namespace' attribute in OpenAPI.

.PARAMETER XmlPrefix
Sets a prefix for the XML element name, aligning with the 'xml.prefix' attribute in OpenAPI.

.PARAMETER XmlAttribute
Indicates whether the property should be serialized as an XML attribute, equivalent to the 'xml.attribute' attribute in OpenAPI.

.PARAMETER XmlItemName
Specifically for properties treated as arrays, it defines the XML name for each item in the array. This parameter aligns with the 'xml.name' attribute under 'items' in OpenAPI.

.PARAMETER XmlWrapped
Indicates whether array items should be wrapped in an XML element, similar to the 'xml.wrapped' attribute in OpenAPI.

.EXAMPLE
New-PodeOABoolProperty -Name 'enabled' -Required
#>
function New-PodeOABoolProperty {
    [CmdletBinding(DefaultParameterSetName = 'Inbuilt')]
    param(

        [Parameter(ValueFromPipeline = $true, Position = 0, DontShow = $true)]
        [hashtable[]]
        $ParamsList,

        [Parameter()]
        [ValidatePattern('^[a-zA-Z0-9\.\-_]+$')]
        [Alias('Title')]
        [string]
        $Name,

        [Parameter()]
        [string]
        $Default,

        [Parameter()]
        [string]
        $Description,

        [Parameter()]
        [string]
        $ExternalDoc,

        [Parameter()]
        [object]
        $Example,

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

        [switch]
        $Required,

        [switch]
        $Deprecated,

        [switch]
        $Object,

        [switch]
        $Nullable,

        [switch]
        $ReadOnly,

        [switch]
        $WriteOnly,

        [switch]
        $NoAdditionalProperties,

        [hashtable]
        $AdditionalProperties,

        [string]
        $XmlName,

        [string]
        $XmlNamespace,

        [string]
        $XmlPrefix,

        [switch]
        $XmlAttribute,

        [Parameter(  ParameterSetName = 'Array')]
        [string]
        $XmlItemName,

        [Parameter(  ParameterSetName = 'Array')]
        [switch]
        $XmlWrapped,

        [Parameter(Mandatory = $true, ParameterSetName = 'Array')]
        [switch]
        $Array,

        [Parameter(ParameterSetName = 'Array')]
        [switch]
        $UniqueItems,

        [Parameter(ParameterSetName = 'Array')]
        [int]
        $MinItems,

        [Parameter(ParameterSetName = 'Array')]
        [int]
        $MaxItems
    )
    begin {
        $param = New-PodeOAPropertyInternal -type 'boolean' -Params $PSBoundParameters

        if ($Default) {
            if ([bool]::TryParse($Default, [ref]$null) -or $Enum -icontains $Default) {
                $param.default = $Default
            }
            else {
                # The default value is not a boolean and is not part of the enum
                throw ($PodeLocale.defaultValueNotBooleanOrEnumExceptionMessage)
            }
        }

        $collectedInput = [System.Collections.Generic.List[hashtable]]::new()
    }
    process {
        if ($ParamsList) {
            $collectedInput.AddRange($ParamsList)
        }
    }

    end {
        if ($collectedInput) {
            return $collectedInput + $param
        }
        else {
            return $param
        }
    }
}

<#
.SYNOPSIS
Creates a new OpenAPI object property from other properties.

.DESCRIPTION
Creates a new OpenAPI object property from other properties, for Schemas or Parameters.

.LINK
https://swagger.io/docs/specification/basic-structure/

.LINK
https://swagger.io/docs/specification/data-models/

.PARAMETER ParamsList
Used to pipeline multiple properties

.PARAMETER Name
The Name of the property.

.PARAMETER Properties
An array of other int/string/etc properties wrap up as an object.

.PARAMETER Description
A Description of the property.

.PARAMETER ExternalDoc
If supplied, add an additional external documentation for this operation.
The parameter is created by Add-PodeOAExternalDoc

.PARAMETER Example
An example of a parameter value

.PARAMETER Deprecated
If supplied, the object will be treated as Deprecated where supported.

.PARAMETER Required
If supplied, the object will be treated as Required where supported.

.PARAMETER Array
If supplied, the object will be treated as an array of objects.

.PARAMETER Nullable
If supplied, the object will be treated as Nullable.

.PARAMETER ReadOnly
If supplied, the object will be included in a response but not in a request

.PARAMETER WriteOnly
If supplied, the object will be included in a request but not in a response

.PARAMETER NoProperties
If supplied, no properties are allowed in the object. If no properties are assigned to the object and the NoProperties parameter is not set the object accept any property

.PARAMETER MinProperties
If supplied, will restrict the minimun number of properties allowed in an object.

.PARAMETER MaxProperties
If supplied, will restrict the maximum number of properties allowed in an object.

.PARAMETER NoAdditionalProperties
If supplied, will configure the OpenAPI property additionalProperties to false.
This means that the defined object will not allow any properties beyond those explicitly declared in its schema.
If any additional properties are provided, they will be considered invalid.
Use this switch to enforce a strict schema definition, ensuring that objects contain only the specified set of properties and no others.

.PARAMETER AdditionalProperties
Define a set of additional properties for the OpenAPI schema. This parameter accepts a HashTable where each key-value pair represents a property name and its corresponding schema.
The schema for each property can include type, format, description, and other OpenAPI specification attributes.
When specified, these additional properties are included in the OpenAPI definition, allowing for more flexible and dynamic object structures.

.PARAMETER Array
If supplied, the object will be treated as an array of objects.

.PARAMETER UniqueItems
If supplied, specify that all items in the array must be unique

.PARAMETER MinItems
If supplied, specify minimum length of an array

.PARAMETER MaxItems
If supplied, specify maximum length of an array

.PARAMETER DiscriminatorProperty
If supplied, specifies the name of the property used to distinguish between different subtypes in a polymorphic schema in OpenAPI.
This string value represents the property in the payload that indicates which specific subtype schema should be applied.
It's essential in scenarios where an API endpoint handles data that conforms to one of several derived schemas from a common base schema.

.PARAMETER DiscriminatorMapping
If supplied, define a mapping between the values of the discriminator property and the corresponding subtype schemas.
This parameter accepts a HashTable where each key-value pair maps a discriminator value to a specific subtype schema name.
It's used in conjunction with the -DiscriminatorProperty to provide complete discrimination logic in polymorphic scenarios.

.PARAMETER XmlName
By default, XML elements get the same names that fields in the API declaration have. This property change the XML name of the property
reflecting the 'xml.name' attribute in the OpenAPI specification.

.PARAMETER XmlNamespace
Defines a specific XML namespace for the property, corresponding to the 'xml.namespace' attribute in OpenAPI.

.PARAMETER XmlPrefix
Sets a prefix for the XML element name, aligning with the 'xml.prefix' attribute in OpenAPI.

.PARAMETER XmlAttribute
Indicates whether the property should be serialized as an XML attribute, equivalent to the 'xml.attribute' attribute in OpenAPI.

.PARAMETER XmlItemName
Specifically for properties treated as arrays, it defines the XML name for each item in the array. This parameter aligns with the 'xml.name' attribute under 'items' in OpenAPI.

.PARAMETER XmlWrapped
Indicates whether array items should be wrapped in an XML element, similar to the 'xml.wrapped' attribute in OpenAPI.

.EXAMPLE
New-PodeOAObjectProperty -Name 'user' -Properties @('<ARRAY_OF_PROPERTIES>')

.EXAMPLE
New-PodeOABoolProperty -Name 'enabled' -Required|
    New-PodeOAObjectProperty  -Name 'extraProperties'  -AdditionalProperties [ordered]@{
        "property1" = [ordered]@{ "type" = "string"; "description" = "Description for property1" };
        "property2" = [ordered]@{ "type" = "integer"; "format" = "int32" }
}
#>
function New-PodeOAObjectProperty {
    [CmdletBinding(DefaultParameterSetName = 'Inbuilt')]
    param(

        [Parameter(ValueFromPipeline = $true, Position = 0, DontShow = $true )]
        [hashtable[]]
        $ParamsList,

        [Parameter()]
        [ValidatePattern('^[a-zA-Z0-9\.\-_]+$')]
        [Alias('Title')]
        [string]
        $Name,

        [Parameter()]
        [hashtable[]]
        $Properties,

        [Parameter()]
        [string]
        $Description,

        [Parameter()]
        [string]
        $ExternalDoc,

        [Parameter()]
        [object]
        $Example,

        [switch]
        $Deprecated,

        [switch]
        $Required,

        [switch]
        $Nullable,

        [switch]
        $ReadOnly,

        [switch]
        $WriteOnly,

        [switch]
        $NoProperties,

        [int]
        $MinProperties,

        [int]
        $MaxProperties,

        [switch]
        $NoAdditionalProperties,

        [hashtable]
        $AdditionalProperties,

        [string]
        $XmlName,

        [string]
        $XmlNamespace,

        [string]
        $XmlPrefix,

        [switch]
        $XmlAttribute,

        [Parameter(  ParameterSetName = 'Array')]
        [string]
        $XmlItemName,

        [Parameter(  ParameterSetName = 'Array')]
        [switch]
        $XmlWrapped,

        [Parameter(  Mandatory, ParameterSetName = 'Array')]
        [switch]
        $Array,

        [Parameter(ParameterSetName = 'Array')]
        [switch]
        $UniqueItems,

        [Parameter(ParameterSetName = 'Array')]
        [int]
        $MinItems,

        [Parameter(ParameterSetName = 'Array')]
        [int]
        $MaxItems,

        [string]
        $DiscriminatorProperty,

        [hashtable]
        $DiscriminatorMapping
    )
    begin {
        $param = New-PodeOAPropertyInternal -type 'object' -Params $PSBoundParameters
        if ($NoProperties) {
            if ($Properties -or $MinProperties -or $MaxProperties) {
                # The parameter `NoProperties` is mutually exclusive with `Properties`, `MinProperties` and `MaxProperties`
                throw ($PodeLocale.noPropertiesMutuallyExclusiveExceptionMessage)
            }
            $PropertiesFromPipeline = $false
        }
        elseif ($Properties) {
            $param.properties = $Properties
            $PropertiesFromPipeline = $false
        }
        else {
            $param.properties = @()
            $PropertiesFromPipeline = $true
        }
        if ($DiscriminatorProperty) {
            $param.discriminator = [ordered]@{
                'propertyName' = $DiscriminatorProperty
            }
            if ($DiscriminatorMapping) {
                $param.discriminator.mapping = $DiscriminatorMapping
            }
        }
        elseif ($DiscriminatorMapping) {
            # The parameter 'DiscriminatorMapping' can only be used when 'DiscriminatorProperty' is present
            throw ($PodeLocale.discriminatorMappingRequiresDiscriminatorPropertyExceptionMessage)
        }
        $collectedInput = [System.Collections.Generic.List[hashtable]]::new()
    }
    process {
        if ($ParamsList) {
            if ($PropertiesFromPipeline) {
                $param.properties += $ParamsList

            }
            else {
                $collectedInput.AddRange($ParamsList)
            }
        }
    }

    end {
        if ($PropertiesFromPipeline) {
            return $param
        }
        elseif ($collectedInput) {
            return $collectedInput + $param
        }
        else {
            return $param
        }
    }
}


<#
.SYNOPSIS
Creates a new OpenAPI object combining schemas and properties.

.DESCRIPTION
Creates a new OpenAPI object combining schemas and properties.

.LINK
https://swagger.io/docs/specification/basic-structure/

.LINK
https://swagger.io/docs/specification/data-models/

.PARAMETER ParamsList
Used to pipeline an object definition

.PARAMETER Type
Define the type of validation between the objects
oneOf – validates the value against exactly one of the subschemas
allOf – validates the value against all the subschemas
anyOf – validates the value against any (one or more) of the subschemas

.PARAMETER ObjectDefinitions
An array of object definitions that are used for independent validation but together compose a single object.

.PARAMETER DiscriminatorProperty
If supplied, specifies the name of the property used to distinguish between different subtypes in a polymorphic schema in OpenAPI.
This string value represents the property in the payload that indicates which specific subtype schema should be applied.
It's essential in scenarios where an API endpoint handles data that conforms to one of several derived schemas from a common base schema.

.PARAMETER DiscriminatorMapping
If supplied, defines a mapping between the values of the discriminator property and the corresponding subtype schemas.
This parameter accepts a HashTable where each key-value pair maps a discriminator value to a specific subtype schema name.
It's used in conjunction with the -DiscriminatorProperty to provide complete discrimination logic in polymorphic scenarios.

.PARAMETER NoObjectDefinitionsFromPipeline
Prevents object definitions from being used in the computation but still passes them through the pipeline.

.PARAMETER Name
Specifies the name of the OpenAPI object.

.PARAMETER Required
Indicates if the object is required.

.PARAMETER Description
Provides a description for the OpenAPI object.

.EXAMPLE
Add-PodeOAComponentSchema -Name 'Pets' -Component (Merge-PodeOAProperty -Type OneOf -ObjectDefinitions @('Cat', 'Dog') -Discriminator "petType")

.EXAMPLE
Add-PodeOAComponentSchema -Name 'Cat' -Component (
    Merge-PodeOAProperty -Type AllOf -ObjectDefinitions @(
        'Pet',
        (New-PodeOAObjectProperty -Properties @(
            (New-PodeOAStringProperty -Name 'huntingSkill' -Description 'The measured skill for hunting' -Enum @('clueless', 'lazy', 'adventurous', 'aggressive'))
        ))
    )
)
#>
function Merge-PodeOAProperty {
    [CmdletBinding(DefaultParameterSetName = 'Inbuilt')]
    [OutputType([System.Collections.Specialized.OrderedDictionary])]
    param(

        [Parameter(ValueFromPipeline = $true, Position = 0, DontShow = $true )]
        [hashtable[]]
        $ParamsList,

        [Parameter(Mandatory)]
        [ValidateSet('OneOf', 'AnyOf', 'AllOf')]
        [string]
        $Type,

        [Parameter()]
        [System.Object[]]
        $ObjectDefinitions,

        [string]
        $DiscriminatorProperty,

        [hashtable]
        $DiscriminatorMapping,

        [switch]
        $NoObjectDefinitionsFromPipeline,

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

        [Parameter( ParameterSetName = 'Name')]
        [switch]
        $Required,

        [Parameter( ParameterSetName = 'Name')]
        [string]
        $Description
    )
    begin {
        # Initialize an ordered dictionary
        $param = [ordered]@{}

        # Set the type of validation
        switch ($type.ToLower()) {
            'oneof' {
                $param.type = 'oneOf'
            }
            'anyof' {
                $param.type = 'anyOf'
            }
            'allof' {
                $param.type = 'allOf'
            }
        }

        # Add name to the parameter dictionary if provided
        if ($Name) {
            $param.name = $Name
        }

        # Add description to the parameter dictionary if provided
        if ($Description) {
            $param.description = $Description
        }

        # Set the required field if the switch is present
        if ($Required.IsPresent) {
            $param.required = $Required.IsPresent
        }

        # Initialize schemas array
        $param.schemas = @()

        # Add object definitions to the schemas array
        if ($ObjectDefinitions) {
            foreach ($schema in $ObjectDefinitions) {
                if ($schema -is [System.Object[]] -or ($schema -is [hashtable] -and
                (($schema.type -ine 'object') -and !$schema.object))) {
                    # Only properties of type Object can be associated with $param.type
                    throw ($PodeLocale.propertiesTypeObjectAssociationExceptionMessage -f $param.type)
                }
                $param.schemas += $schema
            }
        }

        # Add discriminator property and mapping if provided
        if ($DiscriminatorProperty) {
            if ($type.ToLower() -eq 'allof' ) {
                # The parameter 'Discriminator' is incompatible with `allOf`
                throw ($PodeLocale.discriminatorIncompatibleWithAllOfExceptionMessage)
            }
            $param.discriminator = [ordered]@{
                'propertyName' = $DiscriminatorProperty
            }
            if ($DiscriminatorMapping) {
                $param.discriminator.mapping = $DiscriminatorMapping
            }
        }
        elseif ($DiscriminatorMapping) {
            # The parameter 'DiscriminatorMapping' can only be used when 'DiscriminatorProperty' is present
            throw ($PodeLocale.discriminatorMappingRequiresDiscriminatorPropertyExceptionMessage)
        }

        # Initialize a list to collect input from the pipeline
        $collectedInput = [System.Collections.Generic.List[hashtable]]::new()
    }
    process {
        if ($ParamsList) {
            if ($NoObjectDefinitionsFromPipeline) {
                # Add to collected input if the switch is present
                $collectedInput.AddRange($ParamsList)
            }
            else {
                # Add to schemas if the switch is not present
                $param.schemas += $ParamsList
            }
        }
    }

    end {
        if ($NoObjectDefinitionsFromPipeline) {
            # Return collected input and param dictionary if switch is present
            return $collectedInput + $param
        }
        else {
            # Return the param dictionary
            return $param
        }
    }
}


<#
.SYNOPSIS
Creates a OpenAPI schema reference property.

.DESCRIPTION
Creates a new OpenAPI component schema reference from another OpenAPI schema.

.LINK
https://swagger.io/docs/specification/basic-structure/

.LINK
https://swagger.io/docs/specification/data-models/

.PARAMETER ParamsList
Used to pipeline multiple properties

.PARAMETER Name
The Name of the property.

.PARAMETER Reference
An component schema name.

.PARAMETER Description
A Description of the property.

.PARAMETER Example
An example of a parameter value

.PARAMETER Deprecated
If supplied, the schema will be treated as Deprecated where supported.

.PARAMETER Required
If supplied, the object will be treated as Required where supported.

.PARAMETER Array
If supplied, the schema will be treated as an array of objects.

.PARAMETER Nullable
If supplied, the schema will be treated as Nullable.

.PARAMETER ReadOnly
If supplied, the schema will be included in a response but not in a request

.PARAMETER WriteOnly
If supplied, the schema will be included in a request but not in a response

.PARAMETER MinProperties
If supplied, will restrict the minimun number of properties allowed in an schema.

.PARAMETER MaxProperties
If supplied, will restrict the maximum number of properties allowed in an schema.

.PARAMETER Array
If supplied, the schema will be treated as an array of objects.

.PARAMETER UniqueItems
If supplied, specify that all items in the array must be unique

.PARAMETER MinItems
If supplied, specify minimum length of an array

.PARAMETER MaxItems
If supplied, specify maximum length of an array

.PARAMETER XmlName
By default, XML elements get the same names that fields in the API declaration have. This property change the XML name of the property
reflecting the 'xml.name' attribute in the OpenAPI specification.

.PARAMETER XmlNamespace
Defines a specific XML namespace for the property, corresponding to the 'xml.namespace' attribute in OpenAPI.

.PARAMETER XmlPrefix
Sets a prefix for the XML element name, aligning with the 'xml.prefix' attribute in OpenAPI.

.PARAMETER XmlAttribute
Indicates whether the property should be serialized as an XML attribute, equivalent to the 'xml.attribute' attribute in OpenAPI.

.PARAMETER XmlItemName
Specifically for properties treated as arrays, it defines the XML name for each item in the array. This parameter aligns with the 'xml.name' attribute under 'items' in OpenAPI.

.PARAMETER XmlWrapped
Indicates whether array items should be wrapped in an XML element, similar to the 'xml.wrapped' attribute in OpenAPI.

.EXAMPLE
New-PodeOAComponentSchemaProperty -Name 'Config' -Component "MyConfigSchema"
#>
function New-PodeOAComponentSchemaProperty {
    [CmdletBinding(DefaultParameterSetName = 'Inbuilt')]
    param(

        [Parameter(ValueFromPipeline = $true, DontShow = $true )]
        [hashtable[]]
        $ParamsList,

        [Parameter()]
        [ValidatePattern('^[a-zA-Z0-9\.\-_]+$')]
        [string]
        $Name,

        [Parameter(Mandatory = $true)]
        [string]
        $Reference,

        [Parameter(  ParameterSetName = 'Array')]
        [string]
        $Description,

        [string]
        $XmlName,

        [string]
        $XmlNamespace,

        [string]
        $XmlPrefix,

        [switch]
        $XmlAttribute,

        [Parameter(  ParameterSetName = 'Array')]
        [string]
        $XmlItemName,

        [Parameter(  ParameterSetName = 'Array')]
        [switch]
        $XmlWrapped,

        [Parameter(ParameterSetName = 'Array')]
        [object]
        $Example,

        [Parameter(ParameterSetName = 'Array')]
        [switch]
        $Deprecated,

        [Parameter(ParameterSetName = 'Array')]
        [switch]
        $Required,

        [Parameter(ParameterSetName = 'Array')]
        [switch]
        $Nullable,

        [Parameter(ParameterSetName = 'Array')]
        [switch]
        $ReadOnly,

        [Parameter(ParameterSetName = 'Array')]
        [switch]
        $WriteOnly,

        [Parameter(ParameterSetName = 'Array')]
        [int]
        $MinProperties,

        [Parameter(ParameterSetName = 'Array')]
        [int]
        $MaxProperties,

        [Parameter(Mandatory = $true, ParameterSetName = 'Array')]
        [switch]
        $Array,

        [Parameter(ParameterSetName = 'Array')]
        [switch]
        $UniqueItems,

        [Parameter(ParameterSetName = 'Array')]
        [int]
        $MinItems,

        [Parameter(ParameterSetName = 'Array')]
        [int]
        $MaxItems
    )
    begin {
        $param = New-PodeOAPropertyInternal -type 'schema' -Params $PSBoundParameters
        if (! $param.Name) {
            $param.Name = $Reference
        }
        $param.schema = $Reference
        $collectedInput = [System.Collections.Generic.List[hashtable]]::new()
    }
    process {
        if ($ParamsList) {
            $collectedInput.AddRange($ParamsList)
        }
    }
    end {
        if ($collectedInput) {
            return $collectedInput + $param
        }
        else {
            return $param
        }
    }
}


if (!(Test-Path Alias:New-PodeOASchemaProperty)) {
    New-Alias New-PodeOASchemaProperty -Value New-PodeOAComponentSchemaProperty
}
src\Public\OpenApi.ps1
<#
.SYNOPSIS
Enables the OpenAPI default route in Pode.

.DESCRIPTION
Enables the OpenAPI default route in Pode, as well as setting up details like Title and API Version.

.PARAMETER Path
An optional custom route path to access the OpenAPI definition. (Default: /openapi)

.PARAMETER Title
The Title of the API. (Deprecated -  Use Add-PodeOAInfo)

.PARAMETER Version
The Version of the API.   (Deprecated -  Use Add-PodeOAInfo)
The OpenAPI Specification is versioned using Semantic Versioning 2.0.0 (semver) and follows the semver specification.
https://semver.org/spec/v2.0.0.html

.PARAMETER Description
A short description of the API. (Deprecated -  Use Add-PodeOAInfo)
CommonMark syntax MAY be used for rich text representation.
https://spec.commonmark.org/

.PARAMETER OpenApiVersion
Specify OpenApi Version (default: 3.0.3)

.PARAMETER RouteFilter
An optional route filter for routes that should be included in the definition. (Default: /*)

.PARAMETER Middleware
Like normal Routes, an array of Middleware that will be applied to the route.

.PARAMETER EndpointName
The EndpointName of an Endpoint(s) to bind the static Route against.

.PARAMETER Authentication
The name of an Authentication method which should be used as middleware on this Route.

.PARAMETER Role
One or more optional Roles that will be authorised to access this Route, when using Authentication with an Access method.

.PARAMETER Group
One or more optional Groups that will be authorised to access this Route, when using Authentication with an Access method.

.PARAMETER Scope
One or more optional Scopes that will be authorised to access this Route, when using Authentication with an Access method.

.PARAMETER RestrictRoutes
If supplied, only routes that are available on the Requests URI will be used to generate the OpenAPI definition.

.PARAMETER ServerEndpoint
If supplied, will be used as URL base to generate the OpenAPI definition.
The parameter is created by New-PodeOpenApiServerEndpoint

.PARAMETER Mode
Define the way the OpenAPI definition file is accessed, the value can be View or Download. (Default: View)

.PARAMETER NoCompress
If supplied, generate the OpenApi Json version in human readible form.

.PARAMETER MarkupLanguage
Define the default markup language for the OpenApi spec ('Json', 'Json-Compress', 'Yaml')

.PARAMETER EnableSchemaValidation
If supplied enable Test-PodeOAJsonSchemaCompliance  cmdlet that provide support for opeapi parameter schema validation

.PARAMETER Depth
Define the default  depth used by any JSON,YAML OpenAPI conversion (default 20)

.PARAMETER DisableMinimalDefinitions
If supplied the OpenApi decument will include only the route validated by Set-PodeOARouteInfo. Any other not OpenApi route will be excluded.

.PARAMETER NoDefaultResponses
If supplied, it will disable the default OpenAPI response with the new provided.

.PARAMETER DefaultResponses
If supplied, it will replace the default OpenAPI response with the new provided.(Default: @{'200' = @{ description = 'OK' };'default' = @{ description = 'Internal server error' }} )

.PARAMETER DefinitionTag
A string representing the unique tag for the API specification.
This tag helps distinguish between different versions or types of API specifications within the application.
You can use this tag to reference the specific API documentation, schema, or version that your function interacts with.

.EXAMPLE
Enable-PodeOpenApi -Title 'My API' -Version '1.0.0' -RouteFilter '/api/*'

.EXAMPLE
Enable-PodeOpenApi -Title 'My API' -Version '1.0.0' -RouteFilter '/api/*' -RestrictRoutes

.EXAMPLE
Enable-PodeOpenApi -Path '/docs/openapi'   -NoCompress -Mode 'Donwload' -DisableMinimalDefinitions
#>
function Enable-PodeOpenApi {
    [CmdletBinding()]
    param(
        [ValidateNotNullOrEmpty()]
        [string]
        $Path = '/openapi',

        [Parameter(ParameterSetName = 'Deprecated')]
        [string]
        $Title,

        [Parameter(ParameterSetName = 'Deprecated')]
        [ValidatePattern('^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$')]
        [string]
        $Version,

        [Parameter(ParameterSetName = 'Deprecated')]
        [string]
        $Description,

        [ValidateSet('3.1.0', '3.0.3', '3.0.2', '3.0.1', '3.0.0')]
        [string]
        $OpenApiVersion = '3.0.3',

        [ValidateNotNullOrEmpty()]
        [string]
        $RouteFilter = '/*',

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

        [Parameter()]
        [object[]]
        $Middleware,

        [Parameter()]
        [Alias('Auth')]
        [string]
        $Authentication,

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

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

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

        [switch]
        $RestrictRoutes,

        [Parameter()]
        [ValidateSet('View', 'Download')]
        [String]
        $Mode = 'view',

        [Parameter()]
        [ValidateSet('Json', 'Json-Compress', 'Yaml')]
        [String]
        $MarkupLanguage = 'Json',

        [Parameter()]
        [switch]
        $EnableSchemaValidation,

        [Parameter()]
        [ValidateRange(1, 100)]
        [int]
        $Depth = 20,

        [Parameter()]
        [switch]
        $DisableMinimalDefinitions,

        [Parameter(Mandatory, ParameterSetName = 'DefaultResponses')]
        [hashtable]
        $DefaultResponses,

        [Parameter(Mandatory, ParameterSetName = 'NoDefaultResponses')]
        [switch]
        $NoDefaultResponses,

        [Parameter()]
        [string]
        $DefinitionTag

    )
    if (Test-PodeIsEmpty -Value $DefinitionTag) {
        $DefinitionTag = $PodeContext.Server.OpenAPI.SelectedDefinitionTag
    }
    if ($Description -or $Version -or $Title) {
        if (! $Version) {
            $Version = '0.0.0'
        }
        # WARNING: Title, Version, and Description on 'Enable-PodeOpenApi' are deprecated. Please use 'Add-PodeOAInfo' instead
        Write-PodeHost $PodeLocale.deprecatedTitleVersionDescriptionWarningMessage -ForegroundColor Yellow
    }
    if ( $DefinitionTag -ine $PodeContext.Server.Web.OpenApi.DefaultDefinitionTag) {
        $PodeContext.Server.OpenAPI.Definitions[$DefinitionTag] = Get-PodeOABaseObject
    }
    $PodeContext.Server.OpenAPI.Definitions[$DefinitionTag].hiddenComponents.enableMinimalDefinitions = !$DisableMinimalDefinitions.IsPresent


    # initialise openapi info
    $PodeContext.Server.OpenAPI.Definitions[$DefinitionTag].Version = $OpenApiVersion
    $PodeContext.Server.OpenAPI.Definitions[$DefinitionTag].Path = $Path
    if ($OpenApiVersion.StartsWith('3.0')) {
        $PodeContext.Server.OpenAPI.Definitions[$DefinitionTag].hiddenComponents.version = 3.0
    }
    elseif ($OpenApiVersion.StartsWith('3.1')) {
        $PodeContext.Server.OpenAPI.Definitions[$DefinitionTag].hiddenComponents.version = 3.1
    }

    $meta = @{
        RouteFilter    = $RouteFilter
        RestrictRoutes = $RestrictRoutes
        NoCompress     = ($MarkupLanguage -ine 'Json-Compress')
        Mode           = $Mode
        MarkupLanguage = $MarkupLanguage
        DefinitionTag  = $DefinitionTag
    }
    if ( $Title) {
        $PodeContext.Server.OpenAPI.Definitions[$DefinitionTag].info.title = $Title
    }
    if ($Version) {
        $PodeContext.Server.OpenAPI.Definitions[$DefinitionTag].info.version = $Version
    }

    if ($Description ) {
        $PodeContext.Server.OpenAPI.Definitions[$DefinitionTag].info.description = $Description
    }

    if ( $EnableSchemaValidation.IsPresent) {
        #Test-Json has been introduced with version 6.1.0
        if ($PSVersionTable.PSVersion -ge [version]'6.1.0') {
            $PodeContext.Server.OpenAPI.Definitions[$DefinitionTag].hiddenComponents.schemaValidation = $EnableSchemaValidation.IsPresent
        }
        else {
            # Schema validation required PowerShell version 6.1.0 or greater
            throw ($PodeLocale.schemaValidationRequiresPowerShell610ExceptionMessage)
        }
    }

    if ( $Depth) {
        $PodeContext.Server.OpenAPI.Definitions[$DefinitionTag].hiddenComponents.depth = $Depth
    }


    $openApiCreationScriptBlock = {
        param($meta)
        $format = $WebEvent.Query['format']
        $mode = $WebEvent.Query['mode']
        $DefinitionTag = $meta.DefinitionTag

        if (!$mode) {
            $mode = $meta.Mode
        }
        elseif (@('download', 'view') -inotcontains $mode) {
            Write-PodeHtmlResponse -Value "Mode $mode not valid" -StatusCode 400
            return
        }
        if ($WebEvent.path -ilike '*.json') {
            if ($format) {
                Show-PodeErrorPage -Code 400 -ContentType 'text/html' -Description 'Format query not valid when the file extension is used'
                return
            }
            $format = 'json'
        }
        elseif ($WebEvent.path -ilike '*.yaml') {
            if ($format) {
                Show-PodeErrorPage -Code 400 -ContentType 'text/html' -Description 'Format query not valid when the file extension is used'
                return
            }
            $format = 'yaml'
        }
        elseif (!$format) {
            $format = $meta.MarkupLanguage.ToLower()
        }
        elseif (@('yaml', 'json', 'json-Compress') -inotcontains $format) {
            Show-PodeErrorPage -Code 400 -ContentType 'text/html' -Description "Format $format not valid"
            return
        }

        if ($mode -ieq 'download') {
            # Set-PodeResponseAttachment -Path
            Add-PodeHeader -Name 'Content-Disposition' -Value "attachment; filename=openapi.$format"
        }

        # generate the openapi definition
        $def = Get-PodeOpenApiDefinitionInternal `
            -EndpointName $WebEvent.Endpoint.Name `
            -DefinitionTag $DefinitionTag `
            -MetaInfo $meta

        # write the openapi definition
        if ($format -ieq 'yaml') {
            if ($mode -ieq 'view') {
                Write-PodeTextResponse -Value (ConvertTo-PodeYaml -InputObject $def -depth $PodeContext.Server.OpenAPI.Definitions[$DefinitionTag].hiddenComponents.depth) -ContentType 'application/yaml; charset=utf-8' #Changed to be RFC 9512 compliant
            }
            else {
                Write-PodeYamlResponse -Value $def -Depth $PodeContext.Server.OpenAPI.Definitions[$DefinitionTag].hiddenComponents.depth
            }
        }
        else {
            Write-PodeJsonResponse -Value $def -Depth $PodeContext.Server.OpenAPI.Definitions[$DefinitionTag].hiddenComponents.depth -NoCompress:$meta.NoCompress
        }
    }

    # add the OpenAPI route
    Add-PodeRoute -Method Get -Path $Path -ArgumentList $meta -Middleware $Middleware `
        -ScriptBlock $openApiCreationScriptBlock -EndpointName $EndpointName `
        -Authentication $Authentication -Role $Role -Scope $Scope -Group $Group

    Add-PodeRoute -Method Get -Path "$Path.json" -ArgumentList $meta -Middleware $Middleware `
        -ScriptBlock $openApiCreationScriptBlock -EndpointName $EndpointName `
        -Authentication $Authentication -Role $Role -Scope $Scope -Group $Group

    Add-PodeRoute -Method Get -Path "$Path.yaml" -ArgumentList $meta -Middleware $Middleware `
        -ScriptBlock $openApiCreationScriptBlock -EndpointName $EndpointName `
        -Authentication $Authentication -Role $Role -Scope $Scope -Group $Group

    #set new DefaultResponses
    if ($NoDefaultResponses.IsPresent) {
        $PodeContext.Server.OpenAPI.Definitions[$DefinitionTag].hiddenComponents.defaultResponses = [ordered]@{}
    }
    elseif ($DefaultResponses) {
        $PodeContext.Server.OpenAPI.Definitions[$DefinitionTag].hiddenComponents.defaultResponses = $DefaultResponses
    }
    $PodeContext.Server.OpenAPI.Definitions[$DefinitionTag].hiddenComponents.enabled = $true

    if ($EndpointName) {
        $PodeContext.Server.OpenAPI.Definitions[$DefinitionTag].hiddenComponents.EndpointName = $EndpointName
    }
}




<#
.SYNOPSIS
Creates an OpenAPI Server Object.

.DESCRIPTION
Creates an OpenAPI Server Object.

.PARAMETER Url
A URL to the target host.  This URL supports Server Variables and MAY be relative, to indicate that the host location is relative to the location where the OpenAPI document is being served.
Variable substitutions will be made when a variable is named in `{`brackets`}`.

.PARAMETER Description
An optional string describing the host designated by the URL. [CommonMark syntax](https://spec.commonmark.org/) MAY be used for rich text representation.

.PARAMETER Variables
A map between a variable name and its value.  The value is used for substitution in the server's URL template.

.PARAMETER DefinitionTag
An Array of strings representing the unique tag for the API specification.
This tag helps distinguish between different versions or types of API specifications within the application.
You can use this tag to reference the specific API documentation, schema, or version that your function interacts with.

.EXAMPLE
Add-PodeOAServerEndpoint -Url 'https://myserver.io/api' -Description 'My test server'

.EXAMPLE
Add-PodeOAServerEndpoint -Url '/api' -Description 'My local server'

.EXAMPLE
Add-PodeOAServerEndpoint -Url "https://{username}.gigantic-server.com:{port}/{basePath}" -Description "The production API server" `
    -Variable   @{
        username = @{
            default = 'demo'
            description = 'this value is assigned by the service provider, in this example gigantic-server.com'
        }
        port = @{
            enum = @('System.Object[]') # Assuming 'System.Object[]' is a placeholder for actual values
            default = 8443
        }
        basePath = @{
            default = 'v2'
        }
    }
}
#>
function Add-PodeOAServerEndpoint {
    param (
        [Parameter(Mandatory = $true)]
        [ValidatePattern('^(https?://|/).+')]
        [string]
        $Url,

        [string]
        $Description,

        [System.Collections.Specialized.OrderedDictionary]
        $Variables,

        [string[]]
        $DefinitionTag
    )

    if (Test-PodeIsEmpty -Value $DefinitionTag) {
        $DefinitionTag = @($PodeContext.Server.OpenAPI.SelectedDefinitionTag)
    }
    foreach ($tag in $DefinitionTag) {
        if (! $PodeContext.Server.OpenAPI.Definitions[$tag].servers) {
            $PodeContext.Server.OpenAPI.Definitions[$tag].servers = @()
        }
        $lUrl = [ordered]@{url = $Url }
        if ($Description) {
            $lUrl.description = $Description
        }

        if ($Variables) {
            $lUrl.variables = $Variables
        }
        $PodeContext.Server.OpenAPI.Definitions[$tag].servers += $lUrl
    }
}




<#
.SYNOPSIS
Gets the OpenAPI definition.

.DESCRIPTION
Gets the OpenAPI definition for custom use in routes, or other functions.

.PARAMETER Format
Return the definition  in a specific format 'Json', 'Json-Compress', 'Yaml', 'HashTable'

.PARAMETER Title
The Title of the API. (Default: the title supplied in Enable-PodeOpenApi)

.PARAMETER Version
The Version of the API. (Default: the version supplied in Enable-PodeOpenApi)

.PARAMETER Description
A Description of the API. (Default: the description supplied into Enable-PodeOpenApi)

.PARAMETER RouteFilter
An optional route filter for routes that should be included in the definition. (Default: /*)

.PARAMETER RestrictRoutes
If supplied, only routes that are available on the Requests URI will be used to generate the OpenAPI definition.

.PARAMETER DefinitionTag
A string representing the unique tag for the API specification.
This tag helps distinguish between different versions or types of API specifications within the application.
You can use this tag to reference the specific API documentation, schema, or version that your function interacts with.

.EXAMPLE
$defInJson = Get-PodeOADefinition -Json
#>
function Get-PodeOADefinition {
    [CmdletBinding()]
    param(
        [ValidateSet('Json', 'Json-Compress', 'Yaml', 'HashTable')]
        [string]
        $Format = 'HashTable',

        [string]
        $Title,

        [string]
        $Version,

        [string]
        $Description,

        [ValidateNotNullOrEmpty()]
        [string]
        $RouteFilter = '/*',

        [switch]
        $RestrictRoutes,

        [string]
        $DefinitionTag
    )

    $DefinitionTag = Test-PodeOADefinitionTag -Tag $DefinitionTag

    $meta = @{
        RouteFilter    = $RouteFilter
        RestrictRoutes = $RestrictRoutes
    }
    if ($RestrictRoutes) {
        $meta = @{
            RouteFilter    = $RouteFilter
            RestrictRoutes = $RestrictRoutes
        }
    }
    else {
        $meta = @{}
    }
    if ($Title) {
        $meta.Title = $Title
    }
    if ($Version) {
        $meta.Version = $Version
    }
    if ($Description) {
        $meta.Description = $Description
    }

    $oApi = Get-PodeOpenApiDefinitionInternal -MetaInfo $meta -EndpointName $WebEvent.Endpoint.Name -DefinitionTag $DefinitionTag

    switch ($Format.ToLower()) {
        'json' {
            return ConvertTo-Json -InputObject $oApi -Depth $PodeContext.Server.OpenAPI.Definitions[$DefinitionTag].hiddenComponents.depth
        }
        'json-compress' {
            return ConvertTo-Json -InputObject $oApi -Depth $PodeContext.Server.OpenAPI.Definitions[$DefinitionTag].hiddenComponents.depth -Compress
        }
        'yaml' {
            return ConvertTo-PodeYaml -InputObject $oApi -depth $PodeContext.Server.OpenAPI.Definitions[$DefinitionTag].hiddenComponents.depth
        }
        Default {
            return $oApi
        }
    }
}

<#
.SYNOPSIS
Adds a response definition to the supplied route.

.DESCRIPTION
Adds a response definition to the supplied route.

.PARAMETER Route
The route to add the response definition, usually from -PassThru on Add-PodeRoute.

.PARAMETER StatusCode
The HTTP StatusCode for the response.To define a range of response codes, this field MAY contain the uppercase wildcard character `X`.
For example, `2XX` represents all response codes between `[200-299]`. Only the following range definitions are allowed: `1XX`, `2XX`, `3XX`, `4XX`, and `5XX`.
If a response is defined using an explicit code, the explicit code definition takes precedence over the range definition for that code.

.PARAMETER Content
The content-types and schema the response returns (the schema is created using the Property functions).
Alias: ContentSchemas

.PARAMETER Headers
The header name and schema the response returns (the schema is created using Add-PodeOAComponentHeader cmd-let).
Alias: HeaderSchemas

.PARAMETER Description
A Description of the response. (Default: the HTTP StatusCode description)

.PARAMETER Reference
A Reference Name of an existing component response to use.

.PARAMETER Links
A Response link definition

.PARAMETER Default
If supplied, the response will be used as a default response - this overrides the StatusCode supplied.

.PARAMETER PassThru
If supplied, the route passed in will be returned for further chaining.

.PARAMETER DefinitionTag
An Array of strings representing the unique tag for the API specification.
This tag helps distinguish between different versions or types of API specifications within the application.
You can use this tag to reference the specific API documentation, schema, or version that your function interacts with.


.EXAMPLE
Add-PodeRoute -PassThru | Add-PodeOAResponse -StatusCode 200 -Content @{ 'application/json' = (New-PodeOAIntProperty -Name 'userId' -Object) }

.EXAMPLE
Add-PodeRoute -PassThru | Add-PodeOAResponse -StatusCode 200 -Content @{ 'application/json' = 'UserIdSchema' }

.EXAMPLE
Add-PodeRoute -PassThru | Add-PodeOAResponse -StatusCode 200 -Reference 'OKResponse'
#>
function Add-PodeOAResponse {
    [CmdletBinding(DefaultParameterSetName = 'Schema')]
    [OutputType([hashtable[]])]
    param(
        [Parameter(Mandatory = $true, ValueFromPipeline = $true, Position = 0)]
        [ValidateNotNullOrEmpty()]
        [hashtable[]]
        $Route,

        [Parameter(Mandatory = $true, ParameterSetName = 'Schema')]
        [Parameter(Mandatory = $true, ParameterSetName = 'Reference')]
        [ValidatePattern('^([1-5][0-9][0-9]|[1-5]XX)$')]
        [string]
        $StatusCode,

        [Parameter(ParameterSetName = 'Schema')]
        [Parameter(ParameterSetName = 'SchemaDefault')]
        [Alias('ContentSchemas')]
        [hashtable]
        $Content,

        [Alias('HeaderSchemas')]
        [AllowEmptyString()]
        [ValidateNotNullOrEmpty()]
        [ValidateScript({ $_ -is [string] -or $_ -is [string[]] -or $_ -is [hashtable] -or $_ -is [System.Collections.Specialized.OrderedDictionary] })]
        $Headers,

        [Parameter(Mandatory = $false, ParameterSetName = 'Schema')]
        [Parameter(Mandatory = $false, ParameterSetName = 'SchemaDefault')]
        [string]
        $Description,

        [Parameter(Mandatory = $true, ParameterSetName = 'Reference')]
        [Parameter(ParameterSetName = 'ReferenceDefault')]
        [string]
        $Reference,

        [Parameter(Mandatory = $true, ParameterSetName = 'ReferenceDefault')]
        [Parameter(Mandatory = $true, ParameterSetName = 'SchemaDefault')]
        [switch]
        $Default,

        [Parameter(ParameterSetName = 'Schema')]
        [Parameter(ParameterSetName = 'SchemaDefault')]
        [System.Collections.Specialized.OrderedDictionary ]
        $Links,

        [switch]
        $PassThru,

        [string[]]
        $DefinitionTag
    )
    begin {
        # Initialize an array to hold piped-in values
        $pipelineValue = @()
    }

    process {
        # Add the current piped-in value to the array
        $pipelineValue += $_
    }

    end {
        # Set Route to the array of values
        if ($pipelineValue.Count -gt 1) {
            $Route = $pipelineValue
        }

        $DefinitionTag = Test-PodeOADefinitionTag -Tag $DefinitionTag
        # override status code with default
        if ($Default) {
            $code = 'default'
        }
        else {
            $code = "$($StatusCode)"
        }

        # add the respones to the routes
        foreach ($r in @($Route)) {
            foreach ($tag in $DefinitionTag) {
                if (! $r.OpenApi.Responses.$tag) {
                    $r.OpenApi.Responses.$tag = [ordered]@{}
                }
                $r.OpenApi.Responses.$tag[$code] = New-PodeOResponseInternal  -DefinitionTag $tag -Params $PSBoundParameters
            }
        }

        if ($PassThru) {
            return $Route
        }
    }
}


<#
.SYNOPSIS
Remove a response definition from the supplied route.

.DESCRIPTION
Remove a response definition from the supplied route.

.PARAMETER Route
The route to remove the response definition, usually from -PassThru on Add-PodeRoute.

.PARAMETER StatusCode
The HTTP StatusCode for the response to remove.

.PARAMETER Default
If supplied, the response will be used as a default response - this overrides the StatusCode supplied.

.PARAMETER PassThru
If supplied, the route passed in will be returned for further chaining.

.EXAMPLE
Add-PodeRoute -PassThru | Remove-PodeOAResponse -StatusCode 200

.EXAMPLE
Add-PodeRoute -PassThru | Remove-PodeOAResponse -StatusCode 201 -Default
#>
function Remove-PodeOAResponse {
    [CmdletBinding()]
    [OutputType([hashtable[]])]
    param(
        [Parameter(Mandatory = $true, ValueFromPipeline = $true)]
        [ValidateNotNullOrEmpty()]
        [hashtable[]]
        $Route,

        [Parameter(Mandatory = $true)]
        [int]
        $StatusCode,

        [switch]
        $Default,

        [switch]
        $PassThru
    )
    begin {
        # Initialize an array to hold piped-in values
        $pipelineValue = @()
    }

    process {
        # Add the current piped-in value to the array
        $pipelineValue += $_
    }

    end {
        # Set Route to the array of values
        if ($pipelineValue.Count -gt 1) {
            $Route = $pipelineValue
        }

        # override status code with default
        $code = "$($StatusCode)"
        if ($Default) {
            $code = 'default'
        }
        # remove the respones from the routes
        foreach ($r in $Route) {
            if ($r.OpenApi.Responses.ContainsKey($code)) {
                $null = $r.OpenApi.Responses.Remove($code)
            }
        }

        if ($PassThru) {
            return $Route
        }
    }

}

<#
.SYNOPSIS
Sets the definition of a request for a route.

.DESCRIPTION
Sets the definition of a request for a route.

.PARAMETER Route
The route to set a request definition, usually from -PassThru on Add-PodeRoute.

.PARAMETER Parameters
The Parameter definitions the request uses (from ConvertTo-PodeOAParameter).

.PARAMETER RequestBody
The Request Body definition the request uses (from New-PodeOARequestBody).

.PARAMETER PassThru
If supplied, the route passed in will be returned for further chaining.

.EXAMPLE
Add-PodeRoute -PassThru | Set-PodeOARequest -RequestBody (New-PodeOARequestBody -Schema 'UserIdBody')
#>
function Set-PodeOARequest {
    [CmdletBinding()]
    [OutputType([hashtable[]])]
    param(
        [Parameter(Mandatory = $true, Position = 0, ValueFromPipeline = $true)]
        [ValidateNotNullOrEmpty()]
        [hashtable[]]
        $Route,

        [hashtable[]]
        $Parameters,

        [hashtable]
        $RequestBody,

        [switch]
        $PassThru
    )
    begin {
        # Initialize an array to hold piped-in values
        $pipelineValue = @()
    }

    process {
        # Add the current piped-in value to the array
        $pipelineValue += $_
    }

    end {
        # Set Route to the array of values
        if ($pipelineValue.Count -gt 1) {
            $Route = $pipelineValue
        }

        foreach ($r in $Route) {

            if (($null -ne $Parameters) -and ($Parameters.Length -gt 0)) {
                $r.OpenApi.Parameters = @($Parameters)
            }

            if ($null -ne $RequestBody) {
                # Only 'POST', 'PUT', 'PATCH' can have a request body
                if (('POST', 'PUT', 'PATCH') -inotcontains $r.Method ) {
                    # {0} operations cannot have a Request Body.
                    throw ($PodeLocale.getRequestBodyNotAllowedExceptionMessage -f $r.Method)
                }
                $r.OpenApi.RequestBody = $RequestBody
            }

        }

        if ($PassThru) {
            return $Route
        }
    }
}

<#
.SYNOPSIS
Creates a Request Body definition for routes.

.DESCRIPTION
Creates a Request Body definition for routes from the supplied content-types and schemas.

.PARAMETER Reference
A reference name from an existing component request body.
Alias: Reference

.PARAMETER Content
The content of the request body. The key is a media type or media type range and the value describes it.
For requests that match multiple keys, only the most specific key is applicable. e.g. text/plain overrides text/*
Alias: ContentSchemas

.PARAMETER Description
A brief description of the request body. This could contain examples of use. CommonMark syntax MAY be used for rich text representation.

.PARAMETER Required
Determines if the request body is required in the request. Defaults to false.

.PARAMETER Properties
Use to force the use of the properties keyword under a schema. Commonly used to specify a multipart/form-data multi file

.PARAMETER Examples
Supplied an Example of the media type.  The example object SHOULD be in the correct format as specified by the media type.
The `example` field is mutually exclusive of the `examples` field.
Furthermore, if referencing a `schema` which contains an example, the `example` value SHALL _override_ the example provided by the schema.

.PARAMETER Encoding
This parameter give you control over the serialization of parts of multipart request bodies.
This attribute is only applicable to multipart and application/x-www-form-urlencoded request bodies.
Use New-PodeOAEncodingObject to define the encode

.PARAMETER DefinitionTag
An Array of strings representing the unique tag for the API specification.
This tag helps distinguish between different versions or types of API specifications within the application.
You can use this tag to reference the specific API documentation, schema, or version that your function interacts with.

.EXAMPLE
New-PodeOARequestBody -Content @{ 'application/json' = (New-PodeOAIntProperty -Name 'userId' -Object) }

.EXAMPLE
New-PodeOARequestBody -Content @{ 'application/json' = 'UserIdSchema' }

.EXAMPLE
New-PodeOARequestBody -Reference 'UserIdBody'

.EXAMPLE
New-PodeOARequestBody -Content @{'multipart/form-data' =
                    New-PodeOAStringProperty -name 'id' -format 'uuid' |
                        New-PodeOAObjectProperty -name 'address' -NoProperties |
                        New-PodeOAObjectProperty -name 'historyMetadata' -Description 'metadata in XML format' -NoProperties |
                        New-PodeOAStringProperty -name 'profileImage' -Format Binary |
                        New-PodeOAObjectProperty
                    } -Encoding (
                        New-PodeOAEncodingObject -Name 'historyMetadata' -ContentType 'application/xml; charset=utf-8' |
                            New-PodeOAEncodingObject -Name 'profileImage' -ContentType 'image/png, image/jpeg' -Headers (
                                New-PodeOAIntProperty -name 'X-Rate-Limit-Limit' -Description 'The number of allowed requests in the current period' -Default 3 -Enum @(1,2,3)
                            )
                        )
#>
function New-PodeOARequestBody {
    [CmdletBinding(DefaultParameterSetName = 'BuiltIn' )]
    [OutputType([hashtable])]
    [OutputType([System.Collections.Specialized.OrderedDictionary])]
    param(
        [Parameter(Mandatory = $true, ParameterSetName = 'Reference')]
        [string]
        $Reference,

        [Parameter(Mandatory = $true, ParameterSetName = 'BuiltIn')]
        [Alias('ContentSchemas')]
        [hashtable]
        $Content,

        [Parameter(ParameterSetName = 'BuiltIn')]
        [string]
        $Description,

        [Parameter(ParameterSetName = 'BuiltIn')]
        [switch]
        $Required,

        [Parameter(ParameterSetName = 'BuiltIn')]
        [switch]
        $Properties,

        [System.Collections.Specialized.OrderedDictionary]
        $Examples,

        [hashtable[]]
        $Encoding,

        [string[]]
        $DefinitionTag

    )

    $DefinitionTag = Test-PodeOADefinitionTag -Tag $DefinitionTag

    $result = [ordered]@{}
    foreach ($tag in $DefinitionTag) {
        switch ($PSCmdlet.ParameterSetName.ToLowerInvariant()) {
            'builtin' {
                $param = [ordered]@{content = ConvertTo-PodeOAObjectSchema -DefinitionTag $tag -Content $Content -Properties:$Properties }

                if ($Required.IsPresent) {
                    $param['required'] = $Required.IsPresent
                }

                if ( $Description) {
                    $param['description'] = $Description
                }
                if ($Examples) {
                    if ( $Examples.'*/*') {
                        $Examples['"*/*"'] = $Examples['*/*']
                        $Examples.Remove('*/*')
                    }
                    foreach ($k in  $Examples.Keys ) {
                        if (!($param.content.Keys -contains $k)) {
                            $param.content[$k] = [ordered]@{}
                        }
                        $param.content[$k].examples = $Examples.$k
                    }
                }
            }

            'reference' {
                Test-PodeOAComponentInternal -Field requestBodies -DefinitionTag $tag -Name $Reference -PostValidation
                $param = [ordered]@{
                    '$ref' = "#/components/requestBodies/$Reference"
                }
            }
        }
        if ($Encoding) {
            if (([string]$Content.keys[0]) -match '(?i)^(multipart.*|application\/x-www-form-urlencoded)$' ) {
                $r = [ordered]@{}
                foreach ( $e in $Encoding) {
                    $key = [string]$e.Keys
                    $elems = [ordered]@{}
                    foreach ($v in $e[$key].Keys) {
                        if ($v -ieq 'headers') {
                            $elems.headers = ConvertTo-PodeOAHeaderProperty -Headers $e[$key].headers
                        }
                        else {
                            $elems.$v = $e[$key].$v
                        }
                    }
                    $r.$key = $elems
                }
                $param.Content.$($Content.keys[0]).encoding = $r
            }
            else {
                # The encoding attribute only applies to multipart and application/x-www-form-urlencoded request bodies
                throw ($PodeLocale.encodingAttributeOnlyAppliesToMultipartExceptionMessage)
            }
        }
        $result[$tag] = $param
    }

    return $result
}

<#
.SYNOPSIS
Validate a parameter with a provided schema.

.DESCRIPTION
Validate the parameter of a method against it's own schema

.PARAMETER Json
The object in Json format to validate

.PARAMETER SchemaReference
The schema name to use to validate the property.

.PARAMETER DefinitionTag
A string representing the unique tag for the API specification.
This tag helps distinguish between different versions or types of API specifications within the application.
You can use this tag to reference the specific API documentation, schema, or version that your function interacts with.

.OUTPUTS
result: true if the object is validate positively
message: any validation issue

.EXAMPLE
$UserInfo = Test-PodeOAJsonSchemaCompliance -Json $UserInfo -SchemaReference 'UserIdSchema'}

#>

function Test-PodeOAJsonSchemaCompliance {
    param (
        [Parameter(Mandatory = $true)]
        [System.Object]
        $Json,

        [Parameter(Mandatory = $true)]
        [string]
        $SchemaReference,

        [string]
        $DefinitionTag
    )
    if ($DefinitionTag) {
        if (! ($PodeContext.Server.OpenApi.Definitions.Keys -ccontains $DefinitionTag)) {
            # DefinitionTag does not exist.
            throw ($PodeLocale.definitionTagNotDefinedExceptionMessage -f $DefinitionTag)
        }
    }
    else {
        $DefinitionTag = $PodeContext.Server.Web.OpenApi.DefaultDefinitionTag
    }

    # if Powershell edition is Desktop the test cannot be done. By default everything is good
    if ($PSVersionTable.PSEdition -eq 'Desktop') {
        return $true
    }

    if ($Json -isnot [string]) {
        $json = ConvertTo-Json -InputObject $Json -Depth $PodeContext.Server.OpenAPI.Definitions[$DefinitionTag].hiddenComponents.depth
    }

    if (!$PodeContext.Server.OpenAPI.Definitions[$DefinitionTag].hiddenComponents.schemaValidation) {
        # 'Test-PodeOAComponentchema' need to be enabled using 'Enable-PodeOpenApi -EnableSchemaValidation'
        throw ($PodeLocale.testPodeOAComponentSchemaNeedToBeEnabledExceptionMessage)
    }
    if (!(Test-PodeOAComponentSchemaJson -Name $SchemaReference -DefinitionTag $DefinitionTag)) {
        # The OpenApi component schema doesn't exist
        throw ($PodeLocale.openApiComponentSchemaDoesNotExistExceptionMessage -f $SchemaReference)
    }
    if ($PodeContext.Server.OpenAPI.Definitions[$DefinitionTag].hiddenComponents.schemaJson[$SchemaReference].available) {
        [string[]] $message = @()
        $result = Test-Json -Json $Json -Schema $PodeContext.Server.OpenAPI.Definitions[$DefinitionTag].hiddenComponents.schemaJson[$SchemaReference].json -ErrorVariable jsonValidationErrors -ErrorAction SilentlyContinue
        if ($jsonValidationErrors) {
            foreach ($item in $jsonValidationErrors) {
                $message += $item
            }
        }
    }
    else {
        $result = $false
        $message = 'Validation of schema with oneof or anyof is not supported'
    }

    return @{result = $result; message = $message }
}

<#
.SYNOPSIS
Converts an OpenAPI property into a Request Parameter.

.DESCRIPTION
Converts an OpenAPI property (such as from New-PodeOAIntProperty) into a Request Parameter.

.PARAMETER In
Where in the Request can the parameter be found?

.PARAMETER Property
The Property that need converting (such as from New-PodeOAIntProperty).

.PARAMETER Reference
The name of an existing component parameter to be reused.
Alias: ComponentParameter

.PARAMETER Name
Assign a name to the parameter

.PARAMETER ContentType
The content-types to be use with  component schema

.PARAMETER Schema
The component schema to use.

.PARAMETER Description
A Description of the property.

.PARAMETER Explode
If supplied, controls how arrays are serialized in query parameters

.PARAMETER AllowReserved
If supplied, determines whether the parameter value SHOULD allow reserved characters, as defined by RFC3986 :/?#[]@!$&'()*+,;= to be included without percent-encoding.
This property only applies to parameters with an in value of query. The default value is false.

.PARAMETER Required
If supplied, the object will be treated as Required where supported.(Applicable only to ContentSchema)

.PARAMETER AllowEmptyValue
If supplied, allow the parameter to be empty

.PARAMETER Style
If supplied, defines how multiple values are delimited. Possible styles depend on the parameter location: path, query, header or cookie.

.PARAMETER Deprecated
If supplied, specifies that a parameter is deprecated and SHOULD be transitioned out of usage. Default value is false.

.PARAMETER Example
Example of the parameter's potential value. The example SHOULD match the specified schema and encoding properties if present.
The Example parameter is mutually exclusive of the Examples parameter.
Furthermore, if referencing a Schema  that contains an example, the Example value SHALL _override_ the example provided by the schema.
To represent examples of media types that cannot naturally be represented in JSON or YAML, a string value can contain the example with escaping where necessary.

.PARAMETER Examples
Examples of the parameter's potential value. Each example SHOULD contain a value in the correct format as specified in the parameter encoding.
The Examples parameter is mutually exclusive of the Example parameter.
Furthermore, if referencing a Schema that contains an example, the Examples value SHALL _override_ the example provided by the schema.

.PARAMETER DefinitionTag
An Array of strings representing the unique tag for the API specification.
This tag helps distinguish between different versions or types of API specifications within the application.
You can use this tag to reference the specific API documentation, schema, or version that your function interacts with.

.EXAMPLE
New-PodeOAIntProperty -Name 'userId' | ConvertTo-PodeOAParameter -In Query

.EXAMPLE
ConvertTo-PodeOAParameter -Reference 'UserIdParam'

.EXAMPLE
ConvertTo-PodeOAParameter  -In Header -ContentSchemas @{ 'application/json' = 'UserIdSchema' }

#>
function ConvertTo-PodeOAParameter {
    [CmdletBinding(DefaultParameterSetName = 'Reference')]
    param(
        [Parameter( Mandatory = $true, ParameterSetName = 'Schema')]
        [Parameter(Mandatory = $true, ParameterSetName = 'Properties')]
        [Parameter(Mandatory = $true, ParameterSetName = 'ContentSchema')]
        [Parameter( Mandatory = $true, ParameterSetName = 'ContentProperties')]
        [ValidateSet('Cookie', 'Header', 'Path', 'Query')]
        [string]
        $In,

        [Parameter(Mandatory = $true, Position = 0, ValueFromPipeline = $true, ParameterSetName = 'Properties')]
        [Parameter( Mandatory = $true, Position = 0, ValueFromPipeline = $true, ParameterSetName = 'ContentProperties')]
        [ValidateNotNull()]
        [hashtable]
        $Property,

        [Parameter(Mandatory = $true, ParameterSetName = 'Reference')]
        [Alias('ComponentParameter')]
        [string]
        $Reference,

        [Parameter( ParameterSetName = 'Schema')]
        [Parameter(ParameterSetName = 'Properties')]
        [Parameter(ParameterSetName = 'ContentSchema')]
        [Parameter(  ParameterSetName = 'ContentProperties')]
        [string]
        $Name,

        [Parameter(Mandatory = $true, ParameterSetName = 'Schema')]
        [Parameter(Mandatory = $true, ParameterSetName = 'ContentSchema')]
        [Alias('ComponentSchema')]
        [String]
        $Schema,

        [Parameter( Mandatory = $true, ParameterSetName = 'ContentSchema')]
        [Parameter( Mandatory = $true, ParameterSetName = 'ContentProperties')]
        [String]
        $ContentType,

        [Parameter( ParameterSetName = 'Schema')]
        [Parameter( ParameterSetName = 'ContentSchema')]
        [Parameter( ParameterSetName = 'Properties')]
        [Parameter( ParameterSetName = 'ContentProperties')]
        [String]
        $Description,

        [Parameter( ParameterSetName = 'Schema')]
        [Parameter( ParameterSetName = 'Properties')]
        [Switch]
        $Explode,

        [Parameter( ParameterSetName = 'Schema')]
        [Parameter( ParameterSetName = 'ContentSchema')]
        [Parameter( ParameterSetName = 'Properties')]
        [Parameter( ParameterSetName = 'ContentProperties')]
        [Switch]
        $Required,

        [Parameter( ParameterSetName = 'ContentSchema')]
        [Parameter( ParameterSetName = 'Schema')]
        [Parameter( ParameterSetName = 'Properties')]
        [Switch]
        $AllowEmptyValue,

        [Parameter( ParameterSetName = 'Schema')]
        [Parameter( ParameterSetName = 'Properties')]
        [Switch]
        $AllowReserved,

        [Parameter( ParameterSetName = 'Schema')]
        [Parameter( ParameterSetName = 'ContentSchema')]
        [Parameter( ParameterSetName = 'Properties')]
        [Parameter( ParameterSetName = 'ContentProperties')]
        [object]
        $Example,

        [Parameter( ParameterSetName = 'Schema')]
        [Parameter( ParameterSetName = 'ContentSchema')]
        [Parameter( ParameterSetName = 'Properties')]
        [Parameter( ParameterSetName = 'ContentProperties')]
        [System.Collections.Specialized.OrderedDictionary]
        $Examples,

        [Parameter( ParameterSetName = 'Schema')]
        [Parameter( ParameterSetName = 'Properties')]
        [ValidateSet('Simple', 'Label', 'Matrix', 'Query', 'Form', 'SpaceDelimited', 'PipeDelimited', 'DeepObject' )]
        [string]
        $Style,

        [Parameter( ParameterSetName = 'Schema')]
        [Parameter( ParameterSetName = 'ContentSchema')]
        [Parameter( ParameterSetName = 'Properties')]
        [Parameter( ParameterSetName = 'ContentProperties')]
        [Switch]
        $Deprecated,

        [string[]]
        $DefinitionTag
    )
    begin {
        $pipelineItemCount = 0
    }

    process {
        $pipelineItemCount++
    }

    end {
        if ($pipelineItemCount -gt 1) {
            throw ($PodeLocale.fnDoesNotAcceptArrayAsPipelineInputExceptionMessage -f $($MyInvocation.MyCommand.Name))
        }

        $DefinitionTag = Test-PodeOADefinitionTag -Tag $DefinitionTag

        if ($PSCmdlet.ParameterSetName -ieq 'ContentSchema' -or $PSCmdlet.ParameterSetName -ieq 'Schema') {
            if (Test-PodeIsEmpty $Schema) {
                return $null
            }
            Test-PodeOAComponentInternal -Field schemas -DefinitionTag $DefinitionTag -Name $Schema -PostValidation
            if (!$Name ) {
                $Name = $Schema
            }
            $prop = [ordered]@{
                in   = $In.ToLowerInvariant()
                name = $Name
            }
            if ($In -ieq 'Header' -and $PodeContext.Server.Security.autoHeaders) {
                Add-PodeSecurityHeader -Name 'Access-Control-Allow-Headers' -Value $Schema -Append
            }
            if ($AllowEmptyValue.IsPresent ) {
                $prop['allowEmptyValue'] = $AllowEmptyValue.IsPresent
            }
            if ($Required.IsPresent ) {
                $prop['required'] = $Required.IsPresent
            }
            if ($Description ) {
                $prop.description = $Description
            }
            if ($Deprecated.IsPresent ) {
                $prop.deprecated = $Deprecated.IsPresent
            }
            if ($ContentType ) {
                # ensure all content types are valid
                if ($ContentType -inotmatch '^[\w-]+\/[\w\.\+-]+$') {
                    # Invalid 'content-type' found for schema: $type
                    throw ($PodeLocale.invalidContentTypeForSchemaExceptionMessage -f $type)
                }
                $prop.content = [ordered]@{
                    $ContentType = [ordered]@{
                        schema = [ordered]@{
                            '$ref' = "#/components/schemas/$($Schema )"
                        }
                    }
                }
                if ($Example ) {
                    $prop.content.$ContentType.example = $Example
                }
                elseif ($Examples) {
                    $prop.content.$ContentType.examples = $Examples
                }
            }
            else {
                $prop.schema = [ordered]@{
                    '$ref' = "#/components/schemas/$($Schema )"
                }
                if ($Style) {
                    switch ($in.ToLower()) {
                        'path' {
                            if (@('Simple', 'Label', 'Matrix' ) -inotcontains $Style) {
                                # OpenApi request Style cannot be $Style for a $in parameter
                                throw ($PodeLocale.openApiRequestStyleInvalidForParameterExceptionMessage -f $Style, $in)
                            }
                            break
                        }
                        'query' {
                            if (@('Form', 'SpaceDelimited', 'PipeDelimited', 'DeepObject' ) -inotcontains $Style) {
                                # OpenApi request Style cannot be $Style for a $in parameter
                                throw ($PodeLocale.openApiRequestStyleInvalidForParameterExceptionMessage -f $Style, $in)
                            }
                            break
                        }
                        'header' {
                            if (@('Simple' ) -inotcontains $Style) {
                                # OpenApi request Style cannot be $Style for a $in parameter
                                throw ($PodeLocale.openApiRequestStyleInvalidForParameterExceptionMessage -f $Style, $in)
                            }
                            break
                        }
                        'cookie' {
                            if (@('Form' ) -inotcontains $Style) {
                                # OpenApi request Style cannot be $Style for a $in parameter
                                throw ($PodeLocale.openApiRequestStyleInvalidForParameterExceptionMessage -f $Style, $in)
                            }
                            break
                        }
                    }
                    $prop['style'] = $Style.Substring(0, 1).ToLower() + $Style.Substring(1)
                }

                if ($Explode.IsPresent ) {
                    $prop['explode'] = $Explode.IsPresent
                }

                if ($AllowEmptyValue.IsPresent ) {
                    $prop['allowEmptyValue'] = $AllowEmptyValue.IsPresent
                }

                if ($AllowReserved.IsPresent) {
                    $prop['allowReserved'] = $AllowReserved.IsPresent
                }

                if ($Example ) {
                    $prop.example = $Example
                }
                elseif ($Examples) {
                    $prop.examples = $Examples
                }
            }
        }
        elseif ($PSCmdlet.ParameterSetName -ieq 'Reference') {
            # return a reference
            Test-PodeOAComponentInternal -Field parameters  -DefinitionTag $DefinitionTag  -Name $Reference -PostValidation
            $prop = [ordered]@{
                '$ref' = "#/components/parameters/$Reference"
            }
            foreach ($tag in $DefinitionTag) {
                if ($PodeContext.Server.OpenAPI.Definitions[$tag].components.parameters.$Reference.In -eq 'Header' -and $PodeContext.Server.Security.autoHeaders) {
                    Add-PodeSecurityHeader -Name 'Access-Control-Allow-Headers' -Value $Reference -Append
                }
            }
        }
        else {

            if (!$Name ) {
                if ($Property.name) {
                    $Name = $Property.name
                }
                else {
                    # The OpenApi parameter requires a name to be specified
                    throw ($PodeLocale.openApiParameterRequiresNameExceptionMessage)
                }
            }
            if ($In -ieq 'Header' -and $PodeContext.Server.Security.autoHeaders -and $Name ) {
                Add-PodeSecurityHeader -Name 'Access-Control-Allow-Headers' -Value $Name -Append
            }

            # build the base parameter
            $prop = [ordered]@{
                in   = $In.ToLowerInvariant()
                name = $Name
            }
            $sch = [ordered]@{}
            if ($Property.array) {
                $sch.type = 'array'
                $sch.items = [ordered]@{
                    type = $Property.type
                }
                if ($Property.format) {
                    $sch.items.format = $Property.format
                }
            }
            else {
                $sch.type = $Property.type
                if ($Property.format) {
                    $sch.format = $Property.format
                }
            }
            if ($ContentType) {
                if ($ContentType -inotmatch '^[\w-]+\/[\w\.\+-]+$') {
                    # Invalid 'content-type' found for schema: $type
                    throw ($PodeLocale.invalidContentTypeForSchemaExceptionMessage -f $type)
                }
                $prop.content = [ordered]@{
                    $ContentType = [ordered] @{
                        schema = $sch
                    }
                }
            }
            else {
                $prop.schema = $sch
            }

            if ($Example -and $Examples) {
                # Parameters 'Examples' and 'Example' are mutually exclusive
                throw ($PodeLocale.parametersMutuallyExclusiveExceptionMessage -f 'Examples' , 'Example' )
            }
            if ($AllowEmptyValue.IsPresent ) {
                $prop['allowEmptyValue'] = $AllowEmptyValue.IsPresent
            }

            if ($Description ) {
                $prop.description = $Description
            }
            elseif ($Property.description) {
                $prop.description = $Property.description
            }

            if ($Required.IsPresent ) {
                $prop.required = $Required.IsPresent
            }
            elseif ($Property.required) {
                $prop.required = $Property.required
            }

            if ($Deprecated.IsPresent ) {
                $prop.deprecated = $Deprecated.IsPresent
            }
            elseif ($Property.deprecated) {
                $prop.deprecated = $Property.deprecated
            }

            if (!$ContentType) {
                if ($Style) {
                    switch ($in.ToLower()) {
                        'path' {
                            if (@('Simple', 'Label', 'Matrix' ) -inotcontains $Style) {
                                # OpenApi request Style cannot be $Style for a $in parameter
                                throw ($PodeLocale.openApiRequestStyleInvalidForParameterExceptionMessage -f $Style, $in)
                            }
                            break
                        }
                        'query' {
                            if (@('Form', 'SpaceDelimited', 'PipeDelimited', 'DeepObject' ) -inotcontains $Style) {
                                # OpenApi request Style cannot be $Style for a $in parameter
                                throw ($PodeLocale.openApiRequestStyleInvalidForParameterExceptionMessage -f $Style, $in)
                            }
                            break
                        }
                        'header' {
                            if (@('Simple' ) -inotcontains $Style) {
                                # OpenApi request Style cannot be $Style for a $in parameter
                                throw ($PodeLocale.openApiRequestStyleInvalidForParameterExceptionMessage -f $Style, $in)
                            }
                            break
                        }
                        'cookie' {
                            if (@('Form' ) -inotcontains $Style) {
                                # OpenApi request Style cannot be $Style for a $in parameter
                                throw ($PodeLocale.openApiRequestStyleInvalidForParameterExceptionMessage -f $Style, $in)
                            }
                            break
                        }
                    }
                    $prop['style'] = $Style.Substring(0, 1).ToLower() + $Style.Substring(1)
                }

                if ($Explode.IsPresent ) {
                    $prop['explode'] = $Explode.IsPresent
                }

                if ($AllowReserved.IsPresent) {
                    $prop['allowReserved'] = $AllowReserved.IsPresent
                }

                if ($Example ) {
                    $prop['example'] = $Example
                }
                elseif ($Examples) {
                    $prop['examples'] = $Examples
                }

                if ($Property.default -and !$prop.required ) {
                    $prop.schema['default'] = $Property.default
                }

                if ($Property.enum) {
                    if ($Property.array) {
                        $prop.schema.items['enum'] = $Property.enum
                    }
                    else {
                        $prop.schema['enum'] = $Property.enum
                    }
                }
            }
            else {
                if ($Example ) {
                    $prop.content.$ContentType.example = $Example
                }
                elseif ($Examples) {
                    $prop.content.$ContentType.examples = $Examples
                }
            }
        }

        if ($In -ieq 'Path' -and !$prop.required ) {
            # If the parameter location is 'Path', the switch parameter 'Required' is mandatory
            throw ($PodeLocale.pathParameterRequiresRequiredSwitchExceptionMessage)
        }

        return $prop
    }
}

<#
.SYNOPSIS
Sets metadate for the supplied route.

.DESCRIPTION
Sets metadate for the supplied route, such as Summary and Tags.

.PARAMETER Route
The route to update info, usually from -PassThru on Add-PodeRoute.

.PARAMETER Summary
A quick Summary of the route.

.PARAMETER Description
A longer Description of the route.

.PARAMETER ExternalDoc
If supplied, add an additional external documentation for this operation.
The parameter is created by Add-PodeOAExternalDoc

.PARAMETER OperationId
Sets the OperationId of the route.

.PARAMETER Tags
An array of Tags for the route, mostly for grouping.

.PARAMETER Deprecated
If supplied, the route will be flagged as deprecated.

.PARAMETER PassThru
If supplied, the route passed in will be returned for further chaining.

.PARAMETER DefinitionTag
An Array of strings representing the unique tag for the API specification.
This tag helps distinguish between different versions or types of API specifications within the application.
You can use this tag to reference the specific API documentation, schema, or version that your function interacts with.

.EXAMPLE
Add-PodeRoute -PassThru | Set-PodeOARouteInfo -Summary 'A quick summary' -Tags 'Admin'
#>
function Set-PodeOARouteInfo {
    [CmdletBinding()]
    [OutputType([hashtable[]])]
    param(
        [Parameter(Mandatory = $true, Position = 0, ValueFromPipeline = $true)]
        [ValidateNotNullOrEmpty()]
        [hashtable[]]
        $Route,

        [string]
        $Summary,

        [string]
        $Description,

        [System.Collections.Specialized.OrderedDictionary]
        $ExternalDoc,

        [string]
        $OperationId,

        [string[]]
        $Tags,

        [switch]
        $Deprecated,

        [switch]
        $PassThru,

        [string[]]
        $DefinitionTag
    )
    begin {
        # Initialize an array to hold piped-in values
        $pipelineValue = @()
    }

    process {
        # Add the current piped-in value to the array
        $pipelineValue += $_
    }

    end {
        # Set Route to the array of values
        if ($pipelineValue.Count -gt 1) {
            $Route = $pipelineValue
        }

        $DefinitionTag = Test-PodeOADefinitionTag -Tag $DefinitionTag

        foreach ($r in @($Route)) {
            if ((Compare-Object -ReferenceObject $r.OpenApi.DefinitionTag -DifferenceObject  $DefinitionTag).Count -ne 0) {
                if ($r.OpenApi.IsDefTagConfigured ) {
                    # Definition Tag for a Route cannot be changed.
                    throw ($PodeLocale.definitionTagChangeNotAllowedExceptionMessage)
                }
                else {
                    $r.OpenApi.DefinitionTag = $DefinitionTag
                    $r.OpenApi.IsDefTagConfigured = $true
                }
            }

            if ($OperationId) {
                if ($Route.Count -gt 1) {
                    # OperationID:$OperationId has to be unique and cannot be applied to an array
                    throw ($PodeLocale.operationIdMustBeUniqueForArrayExceptionMessage -f $OperationId)
                }
                foreach ($tag in $DefinitionTag) {
                    if ($PodeContext.Server.OpenAPI.Definitions[$tag].hiddenComponents.operationId -ccontains $OperationId) {
                        # OperationID:$OperationId has to be unique
                        throw ($PodeLocale.operationIdMustBeUniqueExceptionMessage -f $OperationId)
                    }
                    $PodeContext.Server.OpenAPI.Definitions[$tag].hiddenComponents.operationId += $OperationId
                }
                $r.OpenApi.OperationId = $OperationId
            }

            if ($Summary) {
                $r.OpenApi.Summary = $Summary
            }

            if ($Description) {
                $r.OpenApi.Description = $Description
            }

            if ($Tags) {
                $r.OpenApi.Tags = $Tags
            }

            if ($ExternalDocs) {
                $r.OpenApi.ExternalDocs = $ExternalDoc
            }

            $r.OpenApi.Swagger = $true

            if ($Deprecated.IsPresent) {
                $r.OpenApi.Deprecated = $Deprecated.IsPresent
            }
        }

        if ($PassThru) {
            return $Route
        }
    }
}

<#
.SYNOPSIS
Adds a route that enables a viewer to display OpenAPI docs, such as Swagger, ReDoc, RapiDoc, StopLight, Explorer, RapiPdf or Bookmarks.

.DESCRIPTION
Adds a route that enables a viewer to display OpenAPI docs, such as Swagger, ReDoc, RapiDoc, StopLight, Explorer, RapiPdf  or Bookmarks.

.LINK
https://github.com/mrin9/RapiPdf

.LINK
https://github.com/Authress-Engineering/openapi-explorer

.LINK
https://github.com/stoplightio/elements

.LINK
https://github.com/rapi-doc/RapiDoc

.LINK
https://github.com/Redocly/redoc

.LINK
https://github.com/swagger-api/swagger-ui

.PARAMETER Type
The Type of OpenAPI viewer to use.

.PARAMETER Path
The route Path where the docs can be accessed. (Default: "/$Type")

.PARAMETER OpenApiUrl
The URL where the OpenAPI definition can be retrieved. (Default is the OpenAPI path from Enable-PodeOpenApi)

.PARAMETER Middleware
Like normal Routes, an array of Middleware that will be applied.

.PARAMETER Title
The title of the web page. (Default is the OpenAPI title from Enable-PodeOpenApi)

.PARAMETER DarkMode
If supplied, the page will be rendered using a dark theme (this is not supported for all viewers).

.PARAMETER EndpointName
The EndpointName of an Endpoint(s) to bind the static Route against.This parameter is normally not required.
The Endpoint is retrieved by the OpenAPI DefinitionTag
.PARAMETER Authentication
The name of an Authentication method which should be used as middleware on this Route.

.PARAMETER Role
One or more optional Roles that will be authorised to access this Route, when using Authentication with an Access method.

.PARAMETER Group
One or more optional Groups that will be authorised to access this Route, when using Authentication with an Access method.

.PARAMETER Scope
One or more optional Scopes that will be authorised to access this Route, when using Authentication with an Access method.

.PARAMETER Bookmarks
If supplied, create a new documentation bookmarks page

.PARAMETER Editor
If supplied, enable the Swagger-Editor

.PARAMETER NoAdvertise
If supplied, it is not going to state the documentation URL at the startup of the server

.PARAMETER DefinitionTag
A string representing the unique tag for the API specification.
This tag helps distinguish between different versions or types of API specifications within the application.
You can use this tag to reference the specific API documentation, schema, or version that your function interacts with.

.EXAMPLE
Enable-PodeOAViewer -Type Swagger -DarkMode

.EXAMPLE
Enable-PodeOAViewer -Type ReDoc -Title 'Some Title' -OpenApi 'http://some-url/openapi'

.EXAMPLE
Enable-PodeOAViewer -Bookmarks

Adds a route that enables a viewer to display with links to any documentation tool associated with the OpenApi.
#>
function Enable-PodeOAViewer {
    [CmdletBinding(DefaultParameterSetName = 'Doc')]
    param(
        [Parameter(Mandatory = $true, ParameterSetName = 'Doc')]
        [ValidateSet('Swagger', 'ReDoc', 'RapiDoc', 'StopLight', 'Explorer', 'RapiPdf' )]
        [string]
        $Type,

        [string]
        $Path,

        [string]
        $OpenApiUrl,

        [object[]]
        $Middleware,

        [string]
        $Title,

        [switch]
        $DarkMode,

        [string[]]
        $EndpointName,

        [Parameter()]
        [Alias('Auth')]
        [string]
        $Authentication,

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

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

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

        [Parameter(Mandatory = $true, ParameterSetName = 'Bookmarks')]
        [switch]
        $Bookmarks,

        [Parameter( ParameterSetName = 'Bookmarks')]
        [switch]
        $NoAdvertise,

        [Parameter(Mandatory = $true, ParameterSetName = 'Editor')]
        [switch]
        $Editor,

        [string]
        $DefinitionTag
    )
    $DefinitionTag = Test-PodeOADefinitionTag -Tag $DefinitionTag

    # If no EndpointName try to reetrieve the EndpointName from the DefinitionTag if exist
    if ([string]::IsNullOrWhiteSpace($EndpointName) -and $PodeContext.Server.OpenAPI.Definitions[$DefinitionTag].hiddenComponents.EndpointName) {
        $EndpointName = $PodeContext.Server.OpenAPI.Definitions[$DefinitionTag].hiddenComponents.EndpointName
    }

    # error if there's no OpenAPI URL
    $OpenApiUrl = Protect-PodeValue -Value $OpenApiUrl -Default $PodeContext.Server.OpenAPI.Definitions[$DefinitionTag].Path
    if ([string]::IsNullOrWhiteSpace($OpenApiUrl)) {
        # No OpenAPI URL supplied for $Type
        throw ($PodeLocale.noOpenApiUrlSuppliedExceptionMessage -f $Type)

    }

    # fail if no title
    $Title = Protect-PodeValue -Value $Title -Default $PodeContext.Server.OpenAPI.Definitions[$DefinitionTag].info.Title
    if ([string]::IsNullOrWhiteSpace($Title)) {
        # No title supplied for $Type page
        throw ($PodeLocale.noTitleSuppliedForPageExceptionMessage -f $Type)
    }

    if ($Editor.IsPresent) {
        # set a default path
        $Path = Protect-PodeValue -Value $Path -Default '/editor'
        if ([string]::IsNullOrWhiteSpace($Title)) {
            # No route path supplied for $Type page
            throw ($PodeLocale.noRoutePathSuppliedForPageExceptionMessage -f $Type)
        }
        if (Test-PodeOAVersion -Version 3.1 -DefinitionTag $DefinitionTag) {
            # This version on Swagger-Editor doesn't support OpenAPI 3.1
            throw ($PodeLocale.swaggerEditorDoesNotSupportOpenApi31ExceptionMessage)
        }
        # setup meta info
        $meta = @{
            Title             = $Title
            OpenApi           = "$($OpenApiUrl)?format=yaml"
            DarkMode          = $DarkMode
            DefinitionTag     = $DefinitionTag
            SwaggerEditorDist = 'https://unpkg.com/swagger-editor-dist@4'
        }
        Add-PodeRoute -Method Get -Path $Path `
            -Middleware $Middleware -ArgumentList $meta `
            -EndpointName $EndpointName -Authentication $Authentication `
            -Role $Role -Scope $Scope -Group $Group `
            -ScriptBlock {
            param($meta)
            $Data = @{
                Title             = $meta.Title
                OpenApi           = $meta.OpenApi
                SwaggerEditorDist = $meta.SwaggerEditorDist
            }

            $podeRoot = Get-PodeModuleMiscPath
            Write-PodeFileResponseInternal -Path ([System.IO.Path]::Combine($podeRoot, 'default-swagger-editor.html.pode')) -Data $Data
        }

        $PodeContext.Server.OpenAPI.Definitions[$DefinitionTag].hiddenComponents.viewer['editor'] = $Path
    }
    elseif ($Bookmarks.IsPresent) {
        # set a default path
        $Path = Protect-PodeValue -Value $Path -Default '/bookmarks'
        if ([string]::IsNullOrWhiteSpace($Title)) {
            # No route path supplied for $Type page
            throw ($PodeLocale.noRoutePathSuppliedForPageExceptionMessage -f $Type)
        }
        # setup meta info
        $meta = @{
            Title         = $Title
            OpenApi       = $OpenApiUrl
            DarkMode      = $DarkMode
            DefinitionTag = $DefinitionTag
        }

        $route = Add-PodeRoute -Method Get -Path $Path `
            -Middleware $Middleware -ArgumentList $meta `
            -EndpointName $EndpointName -Authentication $Authentication `
            -Role $Role -Scope $Scope -Group $Group `
            -PassThru -ScriptBlock {
            param($meta)
            $Data = @{
                Title   = $meta.Title
                OpenApi = $meta.OpenApi
            }
            $DefinitionTag = $meta.DefinitionTag
            foreach ($type in $PodeContext.Server.OpenAPI.Definitions[$DefinitionTag].hiddenComponents.viewer.Keys) {
                $Data[$type] = $true
                $Data["$($type)_path"] = $PodeContext.Server.OpenAPI.Definitions[$DefinitionTag].hiddenComponents.viewer[$type]
            }

            $podeRoot = Get-PodeModuleMiscPath
            Write-PodeFileResponseInternal -Path ([System.IO.Path]::Combine($podeRoot, 'default-doc-bookmarks.html.pode')) -Data $Data
        }
        if (! $NoAdvertise.IsPresent) {
            $PodeContext.Server.OpenAPI.Definitions[$DefinitionTag].hiddenComponents.bookmarks = @{
                path       = $Path
                route      = @()
                openApiUrl = $OpenApiUrl
            }
            $PodeContext.Server.OpenAPI.Definitions[$DefinitionTag].hiddenComponents.bookmarks.route += $route
        }

    }
    else {
        if ($Type -ieq 'RapiPdf' -and (Test-PodeOAVersion -Version 3.1 -DefinitionTag $DefinitionTag)) {
            # The Document tool RapidPdf doesn't support OpenAPI 3.1
            throw ($PodeLocale.rapidPdfDoesNotSupportOpenApi31ExceptionMessage)
        }
        # set a default path
        $Path = Protect-PodeValue -Value $Path -Default "/$($Type.ToLowerInvariant())"
        if ([string]::IsNullOrWhiteSpace($Title)) {
            # No route path supplied for $Type page
            throw ($PodeLocale.noRoutePathSuppliedForPageExceptionMessage -f $Type)
        }
        # setup meta info
        $meta = @{
            Type     = $Type.ToLowerInvariant()
            Title    = $Title
            OpenApi  = $OpenApiUrl
            DarkMode = $DarkMode
        }
        $PodeContext.Server.OpenAPI.Definitions[$DefinitionTag].hiddenComponents.viewer[$($meta.Type)] = $Path
        # add the viewer route
        Add-PodeRoute -Method Get -Path $Path -Middleware $Middleware -ArgumentList $meta `
            -EndpointName $EndpointName -Authentication $Authentication `
            -Role $Role -Scope $Scope -Group $Group `
            -ScriptBlock {
            param($meta)
            $podeRoot = Get-PodeModuleMiscPath
            if ( $meta.DarkMode) { $Theme = 'dark' } else { $Theme = 'light' }
            Write-PodeFileResponseInternal -Path ([System.IO.Path]::Combine($podeRoot, "default-$($meta.Type).html.pode")) -Data @{
                Title    = $meta.Title
                OpenApi  = $meta.OpenApi
                DarkMode = $meta.DarkMode
                Theme    = $Theme
            }
        }
    }

}


<#
.SYNOPSIS
Define an external docs reference.

.DESCRIPTION
Define an external docs reference.

.PARAMETER url
The link to the external documentation

.PARAMETER Description
A Description of the external documentation.

.EXAMPLE
$swaggerDoc = New-PodeOAExternalDoc  -Description 'Find out more about Swagger' -Url 'http://swagger.io'

Add-PodeRoute -PassThru | Set-PodeOARouteInfo -Summary 'A quick summary' -Tags 'Admin' -ExternalDoc $swaggerDoc

.EXAMPLE
$swaggerDoc = New-PodeOAExternalDoc    -Description 'Find out more about Swagger' -Url 'http://swagger.io'
Add-PodeOATag -Name 'user' -Description 'Operations about user' -ExternalDoc $swaggerDoc
#>
function New-PodeOAExternalDoc {
    param(

        [Parameter(Mandatory = $true)]
        [ValidateScript({ $_ -imatch '^https?://.+' })]
        $Url,

        [string]
        $Description
    )
    $param = [ordered]@{}

    if ($Description) {
        $param.description = $Description
    }
    $param['url'] = $Url
    return $param
}



<#
.SYNOPSIS
Add an external docs reference to the OpenApi document.

.DESCRIPTION
Add an external docs reference to the OpenApi document.

.PARAMETER ExternalDoc
An externalDoc object


.PARAMETER Name
The Name of the reference.

.PARAMETER url
The link to the external documentation

.PARAMETER Description
A Description of the external documentation.

.PARAMETER DefinitionTag
An Array of strings representing the unique tag for the API specification.
This tag helps distinguish between different versions or types of API specifications within the application.
You can use this tag to reference the specific API documentation, schema, or version that your function interacts with.

.EXAMPLE
Add-PodeOAExternalDoc  -Name 'SwaggerDocs' -Description 'Find out more about Swagger' -Url 'http://swagger.io'

.EXAMPLE
$ExtDoc = New-PodeOAExternalDoc  -Name 'SwaggerDocs' -Description 'Find out more about Swagger' -Url 'http://swagger.io'
$ExtDoc|Add-PodeOAExternalDoc
#>
function Add-PodeOAExternalDoc {
    [CmdletBinding(DefaultParameterSetName = 'Pipe')]
    param(
        [Parameter(ValueFromPipeline = $true, Position = 0, DontShow = $true, ParameterSetName = 'Pipe')]
        [System.Collections.Specialized.OrderedDictionary ]
        $ExternalDoc,

        [Parameter(Mandatory = $true, ParameterSetName = 'NewRef')]
        [ValidateScript({ $_ -imatch '^https?://.+' })]
        $Url,

        [Parameter(ParameterSetName = 'NewRef')]
        [string]
        $Description,

        [string[]]
        $DefinitionTag
    )
    begin {
        $pipelineItemCount = 0
    }

    process {
        $pipelineItemCount++
    }

    end {
        if ($pipelineItemCount -gt 1) {
            throw ($PodeLocale.fnDoesNotAcceptArrayAsPipelineInputExceptionMessage -f $($MyInvocation.MyCommand.Name))
        }
        $DefinitionTag = Test-PodeOADefinitionTag -Tag $DefinitionTag

        foreach ($tag in $DefinitionTag) {
            if ($PSCmdlet.ParameterSetName -ieq 'NewRef') {
                $param = [ordered]@{url = $Url }
                if ($Description) {
                    $param.description = $Description
                }
                $PodeContext.Server.OpenAPI.Definitions[$tag].externalDocs = $param
            }
            else {
                $PodeContext.Server.OpenAPI.Definitions[$tag].externalDocs = $ExternalDoc
            }
        }
    }
}


<#
.SYNOPSIS
Creates a OpenAPI Tag reference property.

.DESCRIPTION
Creates a new OpenAPI tag reference.

.PARAMETER Name
The Name of the tag.

.PARAMETER Description
A Description of the tag.

.PARAMETER ExternalDoc
If supplied, the tag references an existing external documentation reference.
The parameter is created by Add-PodeOAExternalDoc

.PARAMETER DefinitionTag
An Array of strings representing the unique tag for the API specification.
This tag helps distinguish between different versions or types of API specifications within the application.
You can use this tag to reference the specific API documentation, schema, or version that your function interacts with.

.EXAMPLE
Add-PodeOATag -Name 'store' -Description 'Access to Petstore orders' -ExternalDoc 'SwaggerDocs'
#>
function Add-PodeOATag {
    param(
        [Parameter(Mandatory = $true)]
        [ValidatePattern('^[a-zA-Z0-9\.\-_]+$')]
        [string]
        $Name,

        [string]
        $Description,

        [System.Collections.Specialized.OrderedDictionary]
        $ExternalDoc,

        [string[]]
        $DefinitionTag
    )

    $DefinitionTag = Test-PodeOADefinitionTag -Tag $DefinitionTag

    foreach ($tag in $DefinitionTag) {
        $param = [ordered]@{
            'name' = $Name
        }

        if ($Description) {
            $param.description = $Description
        }

        if ($ExternalDoc) {
            $param.externalDocs = $ExternalDoc
        }

        $PodeContext.Server.OpenAPI.Definitions[$tag].tags[$Name] = $param
    }
}


<#
.SYNOPSIS
Creates an OpenAPI metadata.

.DESCRIPTION
Creates an OpenAPI metadata like TermOfService, license and so on.
The metadata MAY be used by the clients if needed, and MAY be presented in editing or documentation generation tools for convenience.

.PARAMETER Title
The Title of the API.

.PARAMETER Version
The Version of the API.
The OpenAPI Specification is versioned using Semantic Versioning 2.0.0 (semver) and follows the semver specification.
https://semver.org/spec/v2.0.0.html

.PARAMETER Description
A short description of the API.
CommonMark syntax MAY be used for rich text representation.
https://spec.commonmark.org/

.PARAMETER TermsOfService
A URL to the Terms of Service for the API. MUST be in the format of a URL.

.PARAMETER LicenseName
The license name used for the API.

.PARAMETER LicenseUrl
A URL to the license used for the API. MUST be in the format of a URL.

.PARAMETER ContactName
The identifying name of the contact person/organization.

.PARAMETER ContactEmail
The email address of the contact person/organization. MUST be in the format of an email address.

.PARAMETER ContactUrl
The URL pointing to the contact information. MUST be in the format of a URL.

.PARAMETER DefinitionTag
A string representing the unique tag for the API specification.
This tag helps distinguish between different versions or types of API specifications within the application.
You can use this tag to reference the specific API documentation, schema, or version that your function interacts with.

.EXAMPLE
Add-PodeOAInfo -TermsOfService 'http://swagger.io/terms/' -License 'Apache 2.0' -LicenseUrl 'http://www.apache.org/licenses/LICENSE-2.0.html' -ContactName 'API Support' -ContactEmail '[email protected]' -ContactUrl 'http://example.com/support'
#>

function Add-PodeOAInfo {
    param(
        [string]
        $Title,

        [ValidatePattern('^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$')]
        [string]
        $Version ,

        [string]
        $Description,

        [ValidateScript({ $_ -imatch '^https?://.+' })]
        [string]
        $TermsOfService,

        [string]
        $LicenseName,

        [ValidateScript({ $_ -imatch '^https?://.+' })]
        [string]
        $LicenseUrl,

        [string]
        $ContactName,

        [ValidateScript({ $_ -imatch '^\w+([-+.]\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*$' })]
        [string]
        $ContactEmail,

        [ValidateScript({ $_ -imatch '^https?://.+' })]
        [string]
        $ContactUrl,

        [string]
        $DefinitionTag
    )

    $DefinitionTag = Test-PodeOADefinitionTag -Tag $DefinitionTag

    $Info = [ordered]@{}

    if ($LicenseName) {
        $Info.license = [ordered]@{
            'name' = $LicenseName
        }
    }
    if ($LicenseUrl) {
        if ( $Info.license ) {
            $Info.license.url = $LicenseUrl
        }
        else {
            # The OpenAPI object 'license' required the property 'name'. Use -LicenseName parameter.
            throw ($PodeLocale.openApiLicenseObjectRequiresNameExceptionMessage)
        }
    }


    if ($Title) {
        $Info.title = $Title
    }
    elseif (  $PodeContext.Server.OpenAPI.Definitions[$DefinitionTag].info.title) {
        $Info.title = $PodeContext.Server.OpenAPI.Definitions[$DefinitionTag].info.title
    }

    if ($Version) {
        $Info.version = $Version
    }
    elseif ( $PodeContext.Server.OpenAPI.Definitions[$DefinitionTag].info.version) {
        $Info.version = $PodeContext.Server.OpenAPI.Definitions[$DefinitionTag].info.version
    }
    else {
        $Info.version = '1.0.0'
    }

    if ($Description ) {
        $Info.description = $Description
    }
    elseif ( $PodeContext.Server.OpenAPI.Definitions[$DefinitionTag].info.description) {
        $Info.description = $PodeContext.Server.OpenAPI.Definitions[$DefinitionTag].info.description
    }

    if ($TermsOfService) {
        $Info['termsOfService'] = $TermsOfService
    }

    if ($ContactName -or $ContactEmail -or $ContactUrl ) {
        $Info['contact'] = [ordered]@{}

        if ($ContactName) {
            $Info['contact'].name = $ContactName
        }

        if ($ContactEmail) {
            $Info['contact'].email = $ContactEmail
        }

        if ($ContactUrl) {
            $Info['contact'].url = $ContactUrl
        }
    }
    $PodeContext.Server.OpenAPI.Definitions[$DefinitionTag].info = $Info

}


<#
.SYNOPSIS
    Creates a new OpenAPI example.

.DESCRIPTION
    Creates a new OpenAPI example.

    .PARAMETER ParamsList
    Used to pipeline multiple properties

.PARAMETER ContentType
    The Media Content Type associated with the Example.

    Alias: MediaType

.PARAMETER Name
    The Name of the Example.

.PARAMETER Summary
    Short description for the example

    .PARAMETER Description
    Long description for the example.

.PARAMETER Reference
    A reference to a reusable component example

.PARAMETER Value
    Embedded literal example. The  value Parameter and ExternalValue parameter are mutually exclusive.
    To represent examples of media types that cannot naturally represented in JSON or YAML, use a string value to contain the example, escaping where necessary.

.PARAMETER ExternalValue
    A URL that points to the literal example. This provides the capability to reference examples that cannot easily be included in JSON or YAML documents.
    The -Value parameter and -ExternalValue parameter are mutually exclusive.                                |

.PARAMETER DefinitionTag
    An Array of strings representing the unique tag for the API specification.
    This tag helps distinguish between different versions or types of API specifications within the application.
    You can use this tag to reference the specific API documentation, schema, or version that your function interacts with.

.EXAMPLE
    New-PodeOAExample -ContentType 'text/plain' -Name 'user' -Summary = 'User Example in Plain text' -ExternalValue = 'http://foo.bar/examples/user-example.txt'
.EXAMPLE
    $example =
        New-PodeOAExample -ContentType 'application/json' -Name 'user' -Summary = 'User Example' -ExternalValue = 'http://foo.bar/examples/user-example.json'  |
        New-PodeOAExample -ContentType 'application/xml' -Name 'user' -Summary = 'User Example in XML' -ExternalValue = 'http://foo.bar/examples/user-example.xml'
#>
function New-PodeOAExample {
    [CmdletBinding(DefaultParameterSetName = 'Inbuilt')]
    [OutputType([System.Collections.Specialized.OrderedDictionary ])]

    param(
        [Parameter(ValueFromPipeline = $true, Position = 0, DontShow = $true, ParameterSetName = 'Inbuilt')]
        [Parameter(ValueFromPipeline = $true, Position = 0, DontShow = $true, ParameterSetName = 'Reference')]
        [System.Collections.Specialized.OrderedDictionary ]
        $ParamsList,

        [Parameter()]
        [Alias('MediaType')]
        [string]
        $ContentType,

        [Parameter(Mandatory = $true, ParameterSetName = 'Inbuilt')]
        [ValidatePattern('^[a-zA-Z0-9\.\-_]+$')]
        [string]
        $Name,

        [Parameter( ParameterSetName = 'Inbuilt')]
        [string]
        $Summary,

        [Parameter( ParameterSetName = 'Inbuilt')]
        [string]
        $Description,

        [Parameter(  ParameterSetName = 'Inbuilt')]
        [object]
        $Value,

        [Parameter(  ParameterSetName = 'Inbuilt')]
        [string]
        $ExternalValue,

        [Parameter(Mandatory = $true, ParameterSetName = 'Reference')]
        [ValidatePattern('^[a-zA-Z0-9\.\-_]+$')]
        [string]
        $Reference,

        [string[]]
        $DefinitionTag
    )
    begin {
        $pipelineValue = [ordered]@{}

        if (Test-PodeIsEmpty -Value $DefinitionTag) {
            $DefinitionTag = $PodeContext.Server.OpenAPI.SelectedDefinitionTag
        }
        if ($PSCmdlet.ParameterSetName -ieq 'Reference') {
            Test-PodeOAComponentInternal -Field examples -DefinitionTag $DefinitionTag -Name $Reference -PostValidation
            $Name = $Reference
            $Example = [ordered]@{'$ref' = "#/components/examples/$Reference" }
        }
        else {
            if ( $ExternalValue -and $Value) {
                # Parameters 'ExternalValue' and 'Value' are mutually exclusive
                throw ($PodeLocale.parametersMutuallyExclusiveExceptionMessage -f 'ExternalValue', 'Value')
            }
            $Example = [ordered]@{ }
            if ($Summary) {
                $Example.summary = $Summary
            }
            if ($Description) {
                $Example.description = $Description
            }
            if ($Value) {
                $Example.value = $Value
            }
            elseif ($ExternalValue) {
                $Example.externalValue = $ExternalValue
            }
            else {
                # Parameters 'Value' or 'ExternalValue' are mandatory
                throw ($PodeLocale.parametersValueOrExternalValueMandatoryExceptionMessage)
            }
        }
        $param = [ordered]@{}
        if ($ContentType) {
            $param.$ContentType = [ordered]@{
                $Name = $Example
            }
        }
        else {
            $param.$Name = $Example
        }

    }
    process {
        if ($_) {
            $pipelineValue += $_
        }
    }
    end {
        $examples = [ordered]@{}
        if ($pipelineValue.Count -gt 0) {
            #  foreach ($p in $pipelineValue) {
            $examples = $pipelineValue
            #  }
        }
        else {
            return $param
        }

        $key = [string]$param.Keys[0]
        if ($examples.Keys -contains $key) {
            $examples[$key] += $param[$key]
        }
        else {
            $examples += $param
        }
        return $examples
    }
}

<#
.SYNOPSIS
Adds a single encoding definition applied to a single schema property.

.DESCRIPTION
A single encoding definition applied to a single schema property.

.PARAMETER EncodingList
Used by pipe

.PARAMETER Title
The Name of the associated encoded property .

.PARAMETER ContentType
Content-Type for encoding a specific property. Default value depends on the property type: for `string` with `format` being `binary` – `application/octet-stream`;
for other primitive types – `text/plain`; for `object` - `application/json`; for `array` – the default is defined based on the inner type.
The value can be a specific media type (e.g. `application/json`), a wildcard media type (e.g. `image/*`), or a comma-separated list of the two types.

.PARAMETER Headers
A map allowing additional information to be provided as headers, for example `Content-Disposition`.
`Content-Type` is described separately and SHALL be ignored in this section.
This property SHALL be ignored if the request body media type is not a `multipart`.

.PARAMETER Style
Describes how a specific property value will be serialized depending on its type.  See [Parameter Object](#parameterObject) for details on the [`style`](#parameterStyle) property.
The behavior follows the same values as `query` parameters, including default values.
This property SHALL be ignored if the request body media type is not `application/x-www-form-urlencoded`.

.PARAMETER Explode
When enabled, property values of type `array` or `object` generate separate parameters for each value of the array, or key-value-pair of the map.  For other types of properties this property has no effect.
When [`style`](#encodingStyle) is `form`, the `Explode` is set to `true`.
This property SHALL be ignored if the request body media type is not `application/x-www-form-urlencoded`.

.PARAMETER AllowReserved
Determines whether the parameter value SHOULD allow reserved characters, as defined by [RFC3986](https://tools.ietf.org/html/rfc3986#section-2.2) `:/?#[]@!$&'()*+,;=` to be included without percent-encoding.
This property SHALL be ignored if the request body media type is not `application/x-www-form-urlencoded`.

.EXAMPLE

New-PodeOAEncodingObject -Name 'profileImage' -ContentType 'image/png, image/jpeg' -Headers (
                                New-PodeOAIntProperty -name 'X-Rate-Limit-Limit' -Description 'The number of allowed requests in the current period' -Default 3 -Enum @(1,2,3) -Maximum 3
                            )
#>
function New-PodeOAEncodingObject {
    param (
        [Parameter(ValueFromPipeline = $true, Position = 0, DontShow = $true )]
        [hashtable[]]
        $EncodingList,

        [Parameter(Mandatory = $true)]
        [Alias('Name')]
        [ValidatePattern('^[a-zA-Z0-9\.\-_]+$')]
        [string]
        $Title,

        [string]
        $ContentType,

        [hashtable[]]
        $Headers,

        [ValidateSet('Simple', 'Label', 'Matrix', 'Query', 'Form', 'SpaceDelimited', 'PipeDelimited', 'DeepObject' )]
        [string]
        $Style,

        [switch]
        $Explode,

        [switch]
        $AllowReserved
    )
    begin {

        $encoding = [ordered]@{
            $Title = [ordered]@{}
        }
        if ($ContentType) {
            $encoding.$Title.contentType = $ContentType
        }
        if ($Style) {
            $encoding.$Title.style = $Style
        }

        if ($Headers) {
            $encoding.$Title.headers = $Headers
        }

        if ($Explode.IsPresent ) {
            $encoding.$Title.explode = $Explode.IsPresent
        }
        if ($AllowReserved.IsPresent ) {
            $encoding.$Title.allowReserved = $AllowReserved.IsPresent
        }

        $collectedInput = [System.Collections.Generic.List[hashtable]]::new()
    }
    process {
        if ($EncodingList) {
            $collectedInput.AddRange($EncodingList)
        }
    }

    end {
        if ($collectedInput) {
            return $collectedInput + $encoding
        }
        else {
            return $encoding
        }
    }
}


<#
.SYNOPSIS
Adds OpenAPI callback configurations to routes in a Pode web application.

.PARAMETER Route
The route to update info, usually from -PassThru on Add-PodeRoute.

.DESCRIPTION
The Add-PodeOACallBack function is used for defining OpenAPI callback configurations for routes in a Pode server.
It enables setting up API specifications including detailed parameters, request body schemas, and response structures for various HTTP methods.

.PARAMETER Path
Specifies the callback path, usually a relative URL.
The key that identifies the Path Item Object is a runtime expression evaluated in the context of a runtime HTTP request/response to identify the URL for the callback request.
A simple example is `$request.body#/url`.
The runtime expression allows complete access to the HTTP message, including any part of a body that a JSON Pointer (RFC6901) can reference.
More information on JSON Pointer can be found at [RFC6901](https://datatracker.ietf.org/doc/html/rfc6901).

.PARAMETER Name
Alias for 'Name'. A unique identifier for the callback.
It must be a valid string of alphanumeric characters, periods (.), hyphens (-), and underscores (_).

.PARAMETER Reference
A reference to a reusable CallBack component.

.PARAMETER Method
Defines the HTTP method for the callback (e.g., GET, POST, PUT). Supports standard HTTP methods and a wildcard (*) for all methods.

.PARAMETER Parameters
The Parameter definitions the request uses (from ConvertTo-PodeOAParameter).

.PARAMETER RequestBody
Defines the schema of the request body. Can be set using New-PodeOARequestBody.

.PARAMETER Responses
Defines the possible responses for the callback. Can be set using New-PodeOAResponse.

.PARAMETER DefinitionTag
A array of string representing the unique tag for the API specification.
This tag helps distinguish between different versions or types of API specifications within the application.
You can use this tag to reference the specific API documentation, schema, or version that your function interacts with.

.PARAMETER PassThru
If supplied, the route passed in will be returned for further chaining.

.EXAMPLE
    Add-PodeOACallBack -Title 'test' -Path '{$request.body#/id}' -Method Post `
        -RequestBody (New-PodeOARequestBody -Content @{'*/*' = (New-PodeOAStringProperty -Name 'id')}) `
        -Response (
            New-PodeOAResponse -StatusCode 200 -Description 'Successful operation'  -Content (New-PodeOAContentMediaType -ContentType 'application/json','application/xml' -Content 'Pet'  -Array)
            New-PodeOAResponse -StatusCode 400 -Description 'Invalid ID supplied' |
            New-PodeOAResponse -StatusCode 404 -Description 'Pet not found' |
            New-PodeOAResponse -Default -Description 'Something is wrong'
        )
    This example demonstrates adding a POST callback to handle a request body and define various responses based on different status codes.

.NOTES
    Ensure that the provided parameters match the expected schema and formats of Pode and OpenAPI specifications.
    The function is useful for dynamically configuring and documenting API callbacks in a Pode server environment.
#>

function Add-PodeOACallBack {
    [CmdletBinding(DefaultParameterSetName = 'inbuilt')]
    [OutputType([hashtable[]])]
    param (
        [Parameter(Mandatory = $true, Position = 0, ValueFromPipeline = $true)]
        [ValidateNotNullOrEmpty()]
        [hashtable[]]
        $Route,

        [Parameter(Mandatory = $true , ParameterSetName = 'inbuilt')]
        [Parameter(Mandatory = $false, ParameterSetName = 'Reference')]
        [ValidatePattern('^[a-zA-Z0-9\.\-_]+$')]
        [string]
        $Name,

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

        [Parameter(Mandatory = $true , ParameterSetName = 'inbuilt')]
        [string]
        $Path,

        [Parameter(Mandatory = $true, ParameterSetName = 'inbuilt')]
        [ValidateSet('Connect', 'Delete', 'Get', 'Head', 'Merge', 'Options', 'Patch', 'Post', 'Put', 'Trace', '*')]
        [string]
        $Method,

        [Parameter(ParameterSetName = 'inbuilt')]
        [hashtable[]]
        $Parameters,

        [Parameter(ParameterSetName = 'inbuilt')]
        [hashtable]
        $RequestBody,

        [Parameter(ParameterSetName = 'inbuilt')]
        [hashtable]
        $Responses,

        [switch]
        $PassThru,

        [string[]]
        $DefinitionTag
    )
    begin {
        # Initialize an array to hold piped-in values
        $pipelineValue = @()
    }

    process {
        # Add the current piped-in value to the array
        $pipelineValue += $_
    }

    end {
        # Set Route to the array of values
        if ($pipelineValue.Count -gt 1) {
            $Route = $pipelineValue
        }

        $DefinitionTag = Test-PodeOADefinitionTag -Tag $DefinitionTag

        foreach ($r in @($Route)) {
            foreach ($tag in $DefinitionTag) {
                if ($Reference) {
                    Test-PodeOAComponentInternal -Field callbacks -DefinitionTag $tag -Name $Reference -PostValidation
                    if (!$Name) {
                        $Name = $Reference
                    }
                    if (! $r.OpenApi.CallBacks.ContainsKey($tag)) {
                        $r.OpenApi.CallBacks[$tag] = [ordered]@{}
                    }
                    $r.OpenApi.CallBacks[$tag].$Name = [ordered]@{
                        '$ref' = "#/components/callbacks/$Reference"
                    }
                }
                else {
                    if (! $r.OpenApi.CallBacks.ContainsKey($tag)) {
                        $r.OpenApi.CallBacks[$tag] = [ordered]@{}
                    }
                    $r.OpenApi.CallBacks[$tag].$Name = New-PodeOAComponentCallBackInternal -Params $PSBoundParameters -DefinitionTag $tag
                }
            }
        }

        if ($PassThru) {
            return $Route
        }
    }
}

<#
.SYNOPSIS
Adds a response definition to the Callback.

.DESCRIPTION
Adds a response definition to the Callback.

.PARAMETER ResponseList
Hidden parameter used to pipe multiple CallBacksResponses

.PARAMETER StatusCode
The HTTP StatusCode for the response.To define a range of response codes, this field MAY contain the uppercase wildcard character `X`.
For example, `2XX` represents all response codes between `[200-299]`. Only the following range definitions are allowed: `1XX`, `2XX`, `3XX`, `4XX`, and `5XX`.
If a response is defined using an explicit code, the explicit code definition takes precedence over the range definition for that code.

.PARAMETER Content
The content-types and schema the response returns (the schema is created using the Property functions).
Alias: ContentSchemas

.PARAMETER Headers
The header name and schema the response returns (the schema is created using Add-PodeOAComponentHeader cmd-let).
Alias: HeaderSchemas

.PARAMETER Description
A Description of the response. (Default: the HTTP StatusCode description)

.PARAMETER Reference
A Reference Name of an existing component response to use.

.PARAMETER Links
A Response link definition

.PARAMETER Default
If supplied, the response will be used as a default response - this overrides the StatusCode supplied.

.PARAMETER DefinitionTag
An Array of strings representing the unique tag for the API specification.
This tag helps distinguish between different versions or types of API specifications within the application.
You can use this tag to reference the specific API documentation, schema, or version that your function interacts with.

.EXAMPLE
New-PodeOAResponse -StatusCode 200 -Content (  New-PodeOAContentMediaType -ContentType 'application/json' -Content(New-PodeOAIntProperty -Name 'userId' -Object) )

.EXAMPLE
New-PodeOAResponse -StatusCode 200 -Content @{ 'application/json' = 'UserIdSchema' }

.EXAMPLE
New-PodeOAResponse -StatusCode 200 -Reference 'OKResponse'

.EXAMPLE
Add-PodeOACallBack -Title 'test' -Path '$request.body#/id' -Method Post  -RequestBody (
        New-PodeOARequestBody -Content (New-PodeOAContentMediaType -ContentType '*/*' -Content (New-PodeOAStringProperty -Name 'id'))
    ) `
    -Response (
        New-PodeOAResponse -StatusCode 200 -Description 'Successful operation' -Content (New-PodeOAContentMediaType -ContentType 'application/json','application/xml' -Content 'Pet'  -Array) |
            New-PodeOAResponse -StatusCode 400 -Description 'Invalid ID supplied' |
                New-PodeOAResponse -StatusCode 404 -Description 'Pet not found' |
            New-PodeOAResponse -Default   -Description 'Something is wrong'
            )
#>

function New-PodeOAResponse {
    [CmdletBinding(DefaultParameterSetName = 'Schema')]
    [OutputType([hashtable])]
    param(
        [Parameter(ValueFromPipeline = $true , Position = 0, DontShow = $true )]
        [hashtable]
        $ResponseList,

        [Parameter(Mandatory = $true, ParameterSetName = 'Schema')]
        [Parameter(Mandatory = $true, ParameterSetName = 'Reference')]
        [ValidatePattern('^([1-5][0-9][0-9]|[1-5]XX)$')]
        [string]
        $StatusCode,

        [Parameter(ParameterSetName = 'Schema')]
        [Parameter(ParameterSetName = 'SchemaDefault')]
        [Alias('ContentSchemas')]
        [hashtable]
        $Content,

        [Alias('HeaderSchemas')]
        [AllowEmptyString()]
        [ValidateNotNullOrEmpty()]
        [ValidateScript({ $_ -is [string] -or $_ -is [string[]] -or $_ -is [hashtable] })]
        $Headers,

        [Parameter(Mandatory = $true, ParameterSetName = 'Schema')]
        [Parameter(Mandatory = $true, ParameterSetName = 'SchemaDefault')]
        [string]
        $Description  ,

        [Parameter(Mandatory = $true, ParameterSetName = 'Reference')]
        [Parameter(ParameterSetName = 'ReferenceDefault')]
        [string]
        $Reference,

        [Parameter(Mandatory = $true, ParameterSetName = 'ReferenceDefault')]
        [Parameter(Mandatory = $true, ParameterSetName = 'SchemaDefault')]
        [switch]
        $Default,

        [Parameter(ParameterSetName = 'Schema')]
        [Parameter(ParameterSetName = 'SchemaDefault')]
        [System.Collections.Specialized.OrderedDictionary ]
        $Links,

        [string[]]
        $DefinitionTag
    )
    begin {

        if (Test-PodeIsEmpty -Value $DefinitionTag) {
            $DefinitionTag = $PodeContext.Server.OpenAPI.SelectedDefinitionTag
        }

        # override status code with default
        if ($Default) {
            $code = 'default'
        }
        else {
            $code = "$($StatusCode)"
        }
        $response = [ordered]@{}
    }
    process {
        foreach ($tag in $DefinitionTag) {
            if (! $response.$tag) {
                $response.$tag = [ordered] @{}
            }
            $response[$tag][$code] = New-PodeOResponseInternal -DefinitionTag $tag -Params $PSBoundParameters
        }
    }
    end {
        if ($ResponseList) {
            foreach ($tag in $DefinitionTag) {
                if (! $ResponseList.ContainsKey( $tag) ) {
                    $ResponseList[$tag] = [ordered] @{}
                }
                $response[$tag].GetEnumerator() | ForEach-Object { $ResponseList[$tag][$_.Key] = $_.Value }
            }
            return $ResponseList
        }
        else {
            return  $response
        }
    }
}

<#
.SYNOPSIS
    Creates media content type definitions for OpenAPI specifications.

.DESCRIPTION
    The New-PodeOAContentMediaType function generates media content type definitions suitable for use in OpenAPI specifications. It supports various media types and allows for the specification of content as either a single object or an array of objects.

.PARAMETER ContentType
    An array of strings specifying the media types to be defined. Media types should conform to standard MIME types (e.g., 'application/json', 'image/png'). The function validates these media types against a regular expression to ensure they are properly formatted.

    Alias: MediaType

.PARAMETER Content
    The content definition for the media type. This could be an object representing the structure of the content expected for the specified media types.

.PARAMETER Array
    A switch parameter, used in the 'Array' parameter set, to indicate that the content should be treated as an array.

.PARAMETER UniqueItems
    A switch parameter, used in the 'Array' parameter set, to specify that items in the array should be unique.

.PARAMETER MinItems
    Used in the 'Array' parameter set to specify the minimum number of items that should be present in the array.

.PARAMETER MaxItems
    Used in the 'Array' parameter set to specify the maximum number of items that should be present in the array.

.PARAMETER Title
    Used in the 'Array' parameter set to provide a title for the array content.

.PARAMETER Upload
    If provided configure the media for an upload changing the result based on the OpenApi version

.PARAMETER ContentEncoding
    Define the content encoding for upload (Default Binary)

.PARAMETER PartContentMediaType
    Define the content encoding for multipart upload

.EXAMPLE
    Add-PodeRoute -PassThru -Method get -Path '/pet/findByStatus' -Authentication 'Login-OAuth2' -Scope 'read' -ScriptBlock {
        Write-PodeJsonResponse -Value 'done' -StatusCode 200
    } | Set-PodeOARouteInfo -Summary 'Finds Pets by status' -Description 'Multiple status values can be provided with comma separated strings' -Tags 'pet' -OperationId 'findPetsByStatus' -PassThru |
        Set-PodeOARequest -PassThru -Parameters @(
            (New-PodeOAStringProperty -Name 'status' -Description 'Status values that need to be considered for filter' -Default 'available' -Enum @('available', 'pending', 'sold') | ConvertTo-PodeOAParameter -In Query)
        ) |
        Add-PodeOAResponse -StatusCode 200 -Description 'Successful operation' -Content (New-PodeOAContentMediaType -ContentType 'application/json','application/xml' -Content 'Pet' -Array -UniqueItems) -PassThru |
        Add-PodeOAResponse -StatusCode 400 -Description 'Invalid status value'
    This example demonstrates the use of New-PodeOAContentMediaType in defining a GET route '/pet/findByStatus' in an OpenAPI specification. The route includes request parameters and responses with media content types for 'application/json' and 'application/xml'.

.EXAMPLE
    $content = [ordered]@{ type = 'string' }
    $mediaType = 'application/json'
    New-PodeOAContentMediaType -ContentType $mediaType -Content $content
    This example creates a media content type definition for 'application/json' with a simple string content type.

.EXAMPLE
    $content = [ordered]@{ type = 'object'; properties = [ordered]@{ name = @{ type = 'string' } } }
    $mediaTypes = 'application/json', 'application/xml'
    New-PodeOAContentMediaType -ContentType $mediaTypes -Content $content -Array -MinItems 1 -MaxItems 5 -Title 'UserList'
    This example demonstrates defining an array of objects for both 'application/json' and 'application/xml' media types, with a specified range for the number of items and a title.

.EXAMPLE
    Add-PodeRoute -PassThru -Method get -Path '/pet/findByStatus' -Authentication 'Login-OAuth2' -Scope 'read' -ScriptBlock {
        Write-PodeJsonResponse -Value 'done' -StatusCode 200
    } | Set-PodeOARouteInfo -Summary 'Finds Pets by status' -Description 'Multiple status values can be provided with comma separated strings' -Tags 'pet' -OperationId 'findPetsByStatus' -PassThru |
        Set-PodeOARequest -PassThru -Parameters @(
            (New-PodeOAStringProperty -Name 'status' -Description 'Status values that need to be considered for filter' -Default 'available' -Enum @('available', 'pending', 'sold') | ConvertTo-PodeOAParameter -In Query)
        ) |
        Add-PodeOAResponse -StatusCode 200 -Description 'Successful operation' -Content (New-PodeOAContentMediaType -ContentType 'application/json','application/xml' -Content 'Pet' -Array -UniqueItems) -PassThru |
        Add-PodeOAResponse -StatusCode 400 -Description 'Invalid status value'
    This example demonstrates the use of New-PodeOAContentMediaType in defining a GET route '/pet/findByStatus' in an OpenAPI specification. The route includes request parameters and responses with media content types for 'application/json' and 'application/xml'.

.NOTES
    This function is useful for dynamically creating media type specifications in OpenAPI documentation, providing flexibility in defining the expected content structure for different media types.
#>

function New-PodeOAContentMediaType {
    [CmdletBinding(DefaultParameterSetName = 'inbuilt')]
    [OutputType([System.Collections.Specialized.OrderedDictionary])]
    param (
        [Parameter()]
        [Alias('MediaType')]
        [string[]]
        $ContentType = '*/*',

        [object]
        $Content,

        [Parameter(  Mandatory = $true, ParameterSetName = 'Array')]
        [switch]
        $Array,

        [Parameter(ParameterSetName = 'Array')]
        [switch]
        $UniqueItems,

        [Parameter(ParameterSetName = 'Array')]
        [int]
        $MinItems,

        [Parameter(ParameterSetName = 'Array')]
        [int]
        $MaxItems,

        [Parameter(ParameterSetName = 'Array')]
        [string]
        $Title,

        [Parameter(Mandatory = $true, ParameterSetName = 'Upload')]
        [switch]
        $Upload,

        [Parameter(  ParameterSetName = 'Upload')]
        [ValidateSet('Binary', 'Base64')]
        [string]
        $ContentEncoding = 'Binary',

        [Parameter(  ParameterSetName = 'Upload')]
        [string]
        $PartContentMediaType

    )

    $DefinitionTag = Test-PodeOADefinitionTag -Tag $DefinitionTag
    $props = [ordered]@{}
    foreach ($media in $ContentType) {
        if ($media -inotmatch '^(application|audio|image|message|model|multipart|text|video|\*)\/[\w\.\-\*]+(;[\s]*(charset|boundary)=[\w\.\-\*]+)*$') {
            # Invalid 'content-type' found for schema: $media
            throw ($PodeLocale.invalidContentTypeForSchemaExceptionMessage -f $media)
        }
        if ($Upload.IsPresent) {
            if ( $media -ieq 'multipart/form-data' -and $Content) {
                $Content = [ordered]@{'__upload' = [ordered]@{
                        'content'              = $Content
                        'partContentMediaType' = $PartContentMediaType
                    }
                }
            }
            else {
                $Content = [ordered]@{'__upload' = [ordered]@{
                        'contentEncoding' = $ContentEncoding
                    }
                }

            }
        }
        else {
            if ($null -eq $Content ) {
                $Content = [ordered]@{}
            }
        }
        if ($Array.IsPresent) {
            $props[$media] = @{
                __array   = $true
                __content = $Content
                __upload  = $Upload
            }
            if ($MinItems) {
                $props[$media].__minItems = $MinItems
            }
            if ($MaxItems) {
                $props[$media].__maxItems = $MaxItems
            }
            if ($Title) {
                $props[$media].__title = $Title
            }
            if ($UniqueItems.IsPresent) {
                $props[$media].__uniqueItems = $UniqueItems.IsPresent
            }

        }
        else {
            $props[$media] = $Content
        }
    }
    return $props
}


<#
.SYNOPSIS
    Adds a response link to an existing list of OpenAPI response links.

.DESCRIPTION
    The New-PodeOAResponseLink function is designed to add a new response link to an existing OrderedDictionary of OpenAPI response links.
    It can be used to define complex response structures with links to other operations or references, and it supports adding multiple links through pipeline input.

.PARAMETER LinkList
    An OrderedDictionary of existing response links.
    This parameter is intended for use with pipeline input, allowing the function to add multiple links to the collection.
    It is hidden from standard help displays to emphasize its use primarily in pipeline scenarios.

.PARAMETER Name
    Mandatory. A unique name for the response link.
    Must be a valid string composed of alphanumeric characters, periods (.), hyphens (-), and underscores (_).

.PARAMETER Description
    A brief description of the response link. CommonMark syntax may be used for rich text representation.
    For more information on CommonMark syntax, see [CommonMark Specification](https://spec.commonmark.org/).

.PARAMETER OperationId
    The name of an existing, resolvable OpenAPI Specification (OAS) operation, as defined with a unique `operationId`.
    This parameter is mandatory when using the 'OperationId' parameter set and is mutually exclusive of the `OperationRef` field. It is used to specify the unique identifier of the operation the link is associated with.

.PARAMETER OperationRef
    A relative or absolute URI reference to an OAS operation.
    This parameter is mandatory when using the 'OperationRef' parameter set and is mutually exclusive of the `OperationId` field.
    It MUST point to an Operation Object. Relative `operationRef` values MAY be used to locate an existing Operation Object in the OpenAPI specification.

.PARAMETER Reference
A Reference Name of an existing component link to use.

.PARAMETER Parameters
    A map representing parameters to pass to an operation as specified with `operationId` or identified via `operationRef`.
    The key is the parameter name to be used, whereas the value can be a constant or an expression to be evaluated and passed to the linked operation.
    Parameter names can be qualified using the parameter location syntax `[{in}.]{name}` for operations that use the same parameter name in different locations (e.g., path.id).

.PARAMETER RequestBody
    A string representing the request body to use as a request body when calling the target.

.PARAMETER DefinitionTag
    An Array of strings representing the unique tag for the API specification.
    This tag helps distinguish between different versions or types of API specifications within the application.
    You can use this tag to reference the specific API documentation, schema, or version that your function interacts with.

.EXAMPLE
    $links = New-PodeOAResponseLink -LinkList $links -Name 'address' -OperationId 'getUserByName' -Parameters @{'username' = '$request.path.username'}
    Add-PodeOAResponse -StatusCode 200 -Content @{'application/json' = 'User'} -Links $links
    This example demonstrates creating and adding a link named 'address' associated with the operation 'getUserByName' to an OrderedDictionary of links. The updated dictionary is then used in the 'Add-PodeOAResponse' function to define a response with a status code of 200.

.NOTES
    The function supports adding links either by specifying an 'OperationId' or an 'OperationRef', making it versatile for different OpenAPI specification needs.
    It's important to match the parameters and response structures as per the OpenAPI specification to ensure the correct functionality of the API documentation.
#>
function New-PodeOAResponseLink {
    [CmdletBinding(DefaultParameterSetName = 'OperationId')]
    [OutputType([System.Collections.Specialized.OrderedDictionary])]
    param(
        [Parameter(ValueFromPipeline = $true , Position = 0, DontShow = $true )]
        [System.Collections.Specialized.OrderedDictionary ]
        $LinkList,

        [Parameter( Mandatory = $false, ParameterSetName = 'Reference')]
        [Parameter( Mandatory = $true, ParameterSetName = 'OperationRef')]
        [Parameter( Mandatory = $true, ParameterSetName = 'OperationId')]
        [ValidatePattern('^[a-zA-Z0-9\.\-_]+$')]
        [string]
        $Name,

        [Parameter( ParameterSetName = 'OperationRef')]
        [Parameter( ParameterSetName = 'OperationId')]
        [string]
        $Description,

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

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

        [Parameter( ParameterSetName = 'OperationRef')]
        [Parameter( ParameterSetName = 'OperationId')]
        [hashtable]
        $Parameters,

        [Parameter( ParameterSetName = 'OperationRef')]
        [Parameter( ParameterSetName = 'OperationId')]
        [string]
        $RequestBody,

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

        [string[]]
        $DefinitionTag

    )
    begin {

        if (Test-PodeIsEmpty -Value $DefinitionTag) {
            $DefinitionTag = $PodeContext.Server.OpenAPI.SelectedDefinitionTag
        }
        if ($Reference) {
            Test-PodeOAComponentInternal -Field links -DefinitionTag $DefinitionTag -Name $Reference -PostValidation
            if (!$Name) {
                $Name = $Reference
            }
            $link = [ordered]@{
                $Name = [ordered]@{
                    '$ref' = "#/components/links/$Reference"
                }
            }
        }
        else {
            $link = [ordered]@{
                $Name = New-PodeOAResponseLinkInternal -Params $PSBoundParameters
            }
        }
    }
    process {
    }
    end {
        if ($LinkList) {
            $link.GetEnumerator() | ForEach-Object { $LinkList[$_.Key] = $_.Value }
            return $LinkList
        }
        else {
            return [System.Collections.Specialized.OrderedDictionary] $link
        }
    }

}





<#
.SYNOPSIS
Sets metadate for the supplied route.

.DESCRIPTION
Sets metadate for the supplied route, such as Summary and Tags.

.PARAMETER Route
The route to update info, usually from -PassThru on Add-PodeRoute.

.PARAMETER Path
The URI path for the Route.

.PARAMETER Method
The HTTP Method of this Route, multiple can be supplied.

.PARAMETER Servers
A list of external endpoint. created with New-PodeOAServerEndpoint

.PARAMETER PassThru
If supplied, the route passed in will be returned for further chaining.

.PARAMETER DefinitionTag
An Array of strings representing the unique tag for the API specification.
This tag helps distinguish between different versions or types of API specifications within the application.
You can use this tag to reference the specific API documentation, schema, or version that your function interacts with.

.EXAMPLE
Add-PodeOAExternalRoute -PassThru -Method Get -Path '/peta/:id' -Servers (
    New-PodeOAServerEndpoint -Url 'http://ext.server.com/api/v12' -Description 'ext test server' |
    New-PodeOAServerEndpoint -Url 'http://ext13.server.com/api/v12' -Description 'ext test server 13'
    ) |
        Set-PodeOARouteInfo -Summary 'Find pets by ID' -Description 'Returns pets based on ID'  -OperationId 'getPetsById' -PassThru |
        Set-PodeOARequest -PassThru -Parameters @(
        (New-PodeOAStringProperty -Name 'id' -Description 'ID of pet to use' -array | ConvertTo-PodeOAParameter -In Path -Style Simple -Required )) |
        Add-PodeOAResponse -StatusCode 200 -Description 'pet response'   -Content (@{ '*/*' = New-PodeOASchemaProperty   -ComponentSchema 'Pet' -array }) -PassThru |
        Add-PodeOAResponse -Default  -Description 'error payload' -Content (@{'text/html' = 'ErrorModel' }) -PassThru
.EXAMPLE
    Add-PodeRoute -PassThru -Method Get -Path '/peta/:id'  -ScriptBlock {
            Write-PodeJsonResponse -Value 'done' -StatusCode 200
        } | Add-PodeOAExternalRoute -PassThru   -Servers (
        New-PodeOAServerEndpoint -Url 'http://ext.server.com/api/v12' -Description 'ext test server' |
        New-PodeOAServerEndpoint -Url 'http://ext13.server.com/api/v12' -Description 'ext test server 13'
        ) |
        Set-PodeOARouteInfo -Summary 'Find pets by ID' -Description 'Returns pets based on ID'  -OperationId 'getPetsById' -PassThru |
        Set-PodeOARequest -PassThru -Parameters @(
        (New-PodeOAStringProperty -Name 'id' -Description 'ID of pet to use' -array | ConvertTo-PodeOAParameter -In Path -Style Simple -Required )) |
        Add-PodeOAResponse -StatusCode 200 -Description 'pet response'   -Content (@{ '*/*' = New-PodeOASchemaProperty   -ComponentSchema 'Pet' -array }) -PassThru |
        Add-PodeOAResponse -Default  -Description 'error payload' -Content (@{'text/html' = 'ErrorModel' }) -PassThru
#>
function Add-PodeOAExternalRoute {
    [CmdletBinding(DefaultParameterSetName = 'Pipeline')]
    [OutputType([hashtable[]], ParameterSetName = 'Pipeline')]
    [OutputType([hashtable], ParameterSetName = 'builtin')]
    param(
        [Parameter(Mandatory = $true, ValueFromPipeline = $true, Position = 0, ParameterSetName = 'Pipeline')]
        [ValidateNotNullOrEmpty()]
        [hashtable[]]
        $Route,

        [Parameter(Mandatory = $true , ParameterSetName = 'BuiltIn')]
        [string]
        $Path,

        [Parameter(Mandatory = $true)]
        [ValidateScript({ $_.Count -gt 0 })]
        [hashtable[]]
        $Servers,

        [Parameter(Mandatory = $true, ParameterSetName = 'BuiltIn')]
        [ValidateSet('Connect', 'Delete', 'Get', 'Head', 'Merge', 'Options', 'Patch', 'Post', 'Put', 'Trace', '*')]
        [string]
        $Method,

        [switch]
        $PassThru,

        [Parameter( ParameterSetName = 'BuiltIn')]
        [string[]]
        $DefinitionTag
    )
    begin {
        # Initialize an array to hold piped-in values
        $pipelineValue = @()
    }

    process {
        if ($PSCmdlet.ParameterSetName -eq 'Pipeline') {
            # Add the current piped-in value to the array
            $pipelineValue += $_
        }
    }

    end {
        $DefinitionTag = Test-PodeOADefinitionTag -Tag $DefinitionTag

        switch ($PSCmdlet.ParameterSetName.ToLowerInvariant()) {
            'builtin' {

                # ensure the route has appropriate slashes
                $Path = Update-PodeRouteSlash -Path $Path
                $OpenApiPath = ConvertTo-PodeOpenApiRoutePath -Path $Path
                $Path = Resolve-PodePlaceholder -Path $Path
                $extRoute = @{
                    Method  = $Method.ToLower()
                    Path    = $Path
                    Local   = $false
                    OpenApi = @{
                        Path           = $OpenApiPath
                        Responses      = $null
                        Parameters     = $null
                        RequestBody    = $null
                        callbacks      = [ordered]@{}
                        Authentication = @()
                        Servers        = $Servers
                        DefinitionTag  = $DefinitionTag
                    }
                }
                foreach ($tag in $DefinitionTag) {
                    #add the default OpenApi responses
                    if ( $PodeContext.Server.OpenAPI.Definitions[$tag].hiddenComponents.defaultResponses) {
                        $extRoute.OpenApi.Responses = Copy-PodeObjectDeepClone -InputObject $PodeContext.Server.OpenAPI.Definitions[$tag].hiddenComponents.defaultResponses
                    }
                    if (! (Test-PodeOAComponentExternalPath -DefinitionTag $tag -Name $Path)) {
                        $PodeContext.Server.OpenAPI.Definitions[$tag].hiddenComponents.externalPath[$Path] = [ordered]@{}
                    }

                    $PodeContext.Server.OpenAPI.Definitions[$tag].hiddenComponents.externalPath.$Path[$Method] = $extRoute
                }

                if ($PassThru) {
                    return $extRoute
                }
            }

            'pipeline' {
                # Set Route to the array of values
                if ($pipelineValue.Count -gt 1) {
                    $Route = $pipelineValue
                }

                foreach ($r in $Route) {
                    $r.OpenApi.Servers = $Servers
                }
                if ($PassThru) {
                    return $Route
                }
            }
        }
    }
}



<#
.SYNOPSIS
Creates an OpenAPI Server Object.

.DESCRIPTION
Creates an OpenAPI Server Object to use with Add-PodeOAExternalRoute

.PARAMETER ServerEndpointList
Used for piping

.PARAMETER Url
A URL to the target host.  This URL supports Server Variables and MAY be relative, to indicate that the host location is relative to the location where the OpenAPI document is being served.
Variable substitutions will be made when a variable is named in `{`brackets`}`.

.PARAMETER Description
An optional string describing the host designated by the URL. [CommonMark syntax](https://spec.commonmark.org/) MAY be used for rich text representation.


.EXAMPLE
New-PodeOAServerEndpoint -Url 'https://myserver.io/api' -Description 'My test server'

.EXAMPLE
New-PodeOAServerEndpoint -Url '/api' -Description 'My local server'
#>
function New-PodeOAServerEndpoint {
    param (
        [Parameter(ValueFromPipeline = $true , Position = 0, DontShow = $true )]
        [hashtable[]]
        $ServerEndpointList,

        [Parameter(Mandatory = $true)]
        [ValidatePattern('^(https?://|/).+')]
        [string]
        $Url,

        [string]
        $Description
    )
    begin {
        $lUrl = [ordered]@{url = $Url }
        if ($Description) {
            $lUrl.description = $Description
        }
        $collectedInput = [System.Collections.Generic.List[hashtable]]::new()
    }
    process {
        if ($ServerEndpointList) {
            $collectedInput.AddRange($ServerEndpointList)
        }
    }
    end {
        if ($ServerEndpointList) {
            return $collectedInput + $lUrl
        }
        else {
            return $lUrl
        }
    }
}

<#
.SYNOPSIS
Sets metadate for the supplied route.

.DESCRIPTION
Sets metadate for the supplied route, such as Summary and Tags.

.PARAMETER Name
    Alias for 'Name'. A unique identifier for the webhook.
    It must be a valid string of alphanumeric characters, periods (.), hyphens (-), and underscores (_).

.PARAMETER Method
The HTTP Method of this Route, multiple can be supplied.

.PARAMETER PassThru
If supplied, the route passed in will be returned for further chaining.

.PARAMETER DefinitionTag
An Array of strings representing the unique tag for the API specification.
This tag helps distinguish between different versions or types of API specifications within the application.
You can use this tag to reference the specific API documentation, schema, or version that your function interacts with.

.EXAMPLE
Add-PodeOAWebhook -PassThru -Method Get    |
        Set-PodeOARouteInfo -Summary 'Find pets by ID' -Description 'Returns pets based on ID'  -OperationId 'getPetsById' -PassThru |
        Set-PodeOARequest -PassThru -Parameters @(
        (New-PodeOAStringProperty -Name 'id' -Description 'ID of pet to use' -array | ConvertTo-PodeOAParameter -In Path -Style Simple -Required )) |
        Add-PodeOAResponse -StatusCode 200 -Description 'pet response'   -Content (@{ '*/*' = New-PodeOASchemaProperty   -ComponentSchema 'Pet' -array }) -PassThru |
        Add-PodeOAResponse -Default  -Description 'error payload' -Content (@{'text/html' = 'ErrorModel' }) -PassThru
#>
function Add-PodeOAWebhook {
    param(

        [Parameter(Mandatory = $true)]
        [ValidatePattern('^[a-zA-Z0-9\.\-_]+$')]
        [string]
        $Name,

        [Parameter(Mandatory = $true )]
        [ValidateSet('Connect', 'Delete', 'Get', 'Head', 'Merge', 'Options', 'Patch', 'Post', 'Put', 'Trace', '*')]
        [string]
        $Method,

        [switch]
        $PassThru,

        [string[]]
        $DefinitionTag
    )

    $_definitionTag = Test-PodeOADefinitionTag -Tag $DefinitionTag

    $refRoute = @{
        Method      = $Method.ToLower()
        NotPrepared = $true
        OpenApi     = @{
            Responses          = [ordered]@{}
            Parameters         = $null
            RequestBody        = $null
            callbacks          = [ordered]@{}
            Authentication     = @()
            DefinitionTag      = $_definitionTag
            IsDefTagConfigured = ($null -ne $DefinitionTag) #Definition Tag has been configured (Not default)
        }
    }
    foreach ($tag in $_definitionTag) {
        if (Test-PodeOAVersion -Version 3.0 -DefinitionTag $tag ) {
            # The Webhooks feature is not supported in OpenAPI v3.0.x
            throw ($PodeLocale.webhooksFeatureNotSupportedInOpenApi30ExceptionMessage)
        }
        $PodeContext.Server.OpenAPI.Definitions[$tag].webhooks[$Name] = $refRoute
    }

    if ($PassThru) {
        return $refRoute
    }
}

<#
.SYNOPSIS
Select a group of OpenAPI Definions for modification.

.DESCRIPTION
Select a group of OpenAPI Definions for modification.

.PARAMETER Tag
An Array of strings representing the unique tag for the API specification.
This tag helps distinguish between different versions or types of API specifications within the application.
You can use this tag to reference the specific API documentation, schema, or version that your function interacts with.
If Tag is empty or null the default Definition is selected

.PARAMETER ScriptBlock
The ScriptBlock that will modified the group.

.EXAMPLE
Select-PodeOADefinition -Tag 'v3', 'v3.1'  -Script {
        New-PodeOAIntProperty -Name 'id'-Format Int64 -Example 10 -Required |
            New-PodeOAIntProperty -Name 'petId' -Format Int64 -Example 198772 -Required |
            New-PodeOAIntProperty -Name 'quantity' -Format Int32 -Example 7 -Required |
            New-PodeOAStringProperty -Name 'shipDate' -Format Date-Time |
            New-PodeOAStringProperty -Name 'status' -Description 'Order Status' -Required -Example 'approved' -Enum @('placed', 'approved', 'delivered') |
            New-PodeOABoolProperty -Name 'complete' |
            New-PodeOAObjectProperty -XmlName 'order' |
            Add-PodeOAComponentSchema -Name 'Order'

New-PodeOAContentMediaType -ContentType 'application/json', 'application/xml' -Content 'Pet' |
    Add-PodeOAComponentRequestBody -Name 'Pet' -Description 'Pet object that needs to be added to the store'

}
#>
function Select-PodeOADefinition {
    [CmdletBinding()]
    param(
        [string[]]
        $Tag,

        [Parameter(Mandatory = $true)]
        [scriptblock]
        $Scriptblock
    )

    if (Test-PodeIsEmpty $Scriptblock) {
        # No ScriptBlock supplied
        throw ($PodeLocale.noScriptBlockSuppliedExceptionMessage)
    }
    if (Test-PodeIsEmpty -Value $Tag) {
        $Tag = $PodeContext.Server.Web.OpenApi.DefaultDefinitionTag
    }
    else {
        $Tag = Test-PodeOADefinitionTag -Tag $Tag
    }
    # check for scoped vars
    $Scriptblock, $usingVars = Convert-PodeScopedVariables -ScriptBlock $Scriptblock -PSSession $PSCmdlet.SessionState
    $PodeContext.Server.OpenApi.DefinitionTagSelectionStack.Push($PodeContext.Server.OpenAPI.SelectedDefinitionTag)

    $PodeContext.Server.OpenAPI.SelectedDefinitionTag = $Tag

    $null = Invoke-PodeScriptBlock -ScriptBlock $Scriptblock -UsingVariables $usingVars -Splat
    $PodeContext.Server.OpenAPI.SelectedDefinitionTag = $PodeContext.Server.OpenApi.DefinitionTagSelectionStack.Pop()

}

<#
.SYNOPSIS
Renames an existing OpenAPI definition tag in Pode.

.DESCRIPTION
This function renames an existing OpenAPI definition tag to a new tag name.
If the specified tag is the default definition tag, it updates the default tag as well.
It ensures that the new tag name does not already exist and that the function is not used within a Select-PodeOADefinition ScriptBlock.

.PARAMETER Tag
The current tag name of the OpenAPI definition. If not specified, the default definition tag is used.

.PARAMETER NewTag
The new tag name for the OpenAPI definition. This parameter is mandatory.

.EXAMPLE
Rename-PodeOADefinitionTag -Tag 'oldTag' -NewTag 'newTag'

Rename a specific OpenAPI definition tag

.EXAMPLE
Rename-PodeOADefinitionTag -NewTag 'newDefaultTag'

Rename the default OpenAPI definition tag

.NOTES
This function will throw an error if:
- It is used inside a Select-PodeOADefinition ScriptBlock.
- The new tag name already exists.
- The current tag name does not exist.
#>
function Rename-PodeOADefinitionTag {
    param (
        [Parameter(Mandatory = $false)]
        [string]$Tag,
        [Parameter(Mandatory = $true)]
        [string]$NewTag
    )

    # Check if the function is being used inside a Select-PodeOADefinition ScriptBlock
    if ($PodeContext.Server.OpenApi.DefinitionTagSelectionStack.Count -gt 0) {
        throw ($PodeLocale.renamePodeOADefinitionTagExceptionMessage)
    }

    # Check if the new tag name already exists in the OpenAPI definitions
    if ($PodeContext.Server.OpenAPI.Definitions.ContainsKey($NewTag)) {
        throw ($PodeLocale.openApiDefinitionAlreadyExistsExceptionMessage -f $NewTag )
    }

    # If the Tag parameter is null or whitespace, use the default definition tag
    if ([string]::IsNullOrWhiteSpace($Tag)) {
        $Tag = $PodeContext.Server.Web.OpenApi.DefaultDefinitionTag
        $PodeContext.Server.Web.OpenApi.DefaultDefinitionTag = $NewTag # Update the default definition tag
    }
    else {
        # Test if the specified tag exists in the OpenAPI definitions
        Test-PodeOADefinitionTag -Tag $Tag
    }

    # Rename the definition tag in the OpenAPI definitions
    $PodeContext.Server.OpenAPI.Definitions[$NewTag] = $PodeContext.Server.OpenAPI.Definitions[$Tag]
    $PodeContext.Server.OpenAPI.Definitions.Remove($Tag)

    # Update the selected definition tag if it matches the old tag
    if ($PodeContext.Server.OpenAPI.SelectedDefinitionTag -eq $Tag) {
        $PodeContext.Server.OpenAPI.SelectedDefinitionTag = $NewTag
    }
}



<#
.SYNOPSIS
Check if a Definition exist

.DESCRIPTION
Check if a Definition exist. If the parameter Tag is empty or Null $PodeContext.Server.OpenAPI.SelectedDefinitionTag is returned

.PARAMETER Tag
An Array of strings representing the unique tag for the API specification.
This tag helps distinguish between different versions or types of API specifications within the application.
You can use this tag to reference the specific API documentation, schema, or version that your function interacts with.

.EXAMPLE
Test-PodeOADefinitionTag -Tag 'v3', 'v3.1'
#>
function Test-PodeOADefinitionTag {
    param (
        [Parameter(Mandatory = $false)]
        [string[]]
        $Tag
    )

    if ($Tag -and $Tag.Count -gt 0) {
        foreach ($t in $Tag) {
            if (! ($PodeContext.Server.OpenApi.Definitions.Keys -ccontains $t)) {
                # DefinitionTag does not exist.
                throw ($PodeLocale.definitionTagNotDefinedExceptionMessage -f $t)
            }
        }
        return $Tag
    }
    else {
        return $PodeContext.Server.OpenAPI.SelectedDefinitionTag
    }
}



<#
.SYNOPSIS
Validate the OpenAPI definition if all Reference are satisfied

.DESCRIPTION
Validate the OpenAPI definition if all Reference are satisfied



.PARAMETER DefinitionTag
An Array of strings representing the unique tag for the API specification.
This tag helps distinguish between different versions or types of API specifications within the application.
You can use this tag to reference the specific API documentation, schema, or version that your function interacts with.

.EXAMPLE
    if ((Test-PodeOADefinition -DefinitionTag 'v3').count -eq 0){
        Write-PodeHost "The OpenAPI definition is valid"
    }
#>
function Test-PodeOADefinition {
    param (
        [string[]]
        $DefinitionTag
    )
    if (! ($DefinitionTag -and $DefinitionTag.Count -gt 0)) {
        $DefinitionTag = $PodeContext.Server.OpenAPI.Definitions.keys
    }

    $result = @{
        valid  = $true
        issues = @{
        }
    }

    foreach ($tag in $DefinitionTag) {
        if ($PodeContext.Server.OpenAPI.Definitions[$tag].hiddenComponents.enabled) {
            if ([string]::IsNullOrWhiteSpace(  $PodeContext.Server.OpenAPI.Definitions[$tag].info.title) -or [string]::IsNullOrWhiteSpace(  $PodeContext.Server.OpenAPI.Definitions[$tag].info.version)) {
                $result.valid = $false
            }
            $result.issues[$tag] = @{
                title      = [string]::IsNullOrWhiteSpace(  $PodeContext.Server.OpenAPI.Definitions[$tag].info.title)
                version    = [string]::IsNullOrWhiteSpace(  $PodeContext.Server.OpenAPI.Definitions[$tag].info.version)
                components = [ordered]@{}
                definition = ''
            }
            foreach ($field in $PodeContext.Server.OpenAPI.Definitions[$tag].hiddenComponents.postValidation.keys) {
                foreach ($name in $PodeContext.Server.OpenAPI.Definitions[$tag].hiddenComponents.postValidation[$field].keys) {
                    if (! (Test-PodeOAComponentInternal -DefinitionTag $tag -Field $field -Name $name)) {
                        $result.issues[$tag].components["#/components/$field/$name"] = $PodeContext.Server.OpenAPI.Definitions[$tag].hiddenComponents.postValidation[$field][$name]
                        $result.valid = $false
                    }
                }
            }
            try {
                Get-PodeOADefinition -DefinitionTag $tag | Out-Null
            }
            catch {
                $result.issues[$tag].definition = $_.Exception.Message
            }
        }
    }
    return  $result
}
src\Public\Responses.ps1
<#
.SYNOPSIS
Attaches a file onto the Response for downloading.

.DESCRIPTION
Attaches a file from the "/public", and static Routes, onto the Response for downloading.
If the supplied path is not in the Static Routes but is a literal/relative path, then this file is used instead.

.PARAMETER Path
The Path to a static file relative to the "/public" directory, or a static Route.
If the supplied Path doesn't match any custom static Route, then Pode will look in the "/public" directory.
Failing this, if the file path exists as a literal/relative file, then this file is used as a fall back.

.PARAMETER ContentType
Manually specify the content type of the response rather than infering it from the attachment's file extension.
The supplied value must match the valid ContentType format, e.g. application/json

.PARAMETER EndpointName
Optional EndpointName that the static route was creating under.

.PARAMETER FileBrowser
If the path is a folder, instead of returning 404, will return A browsable content of the directory.

.EXAMPLE
Set-PodeResponseAttachment -Path 'downloads/installer.exe'

.EXAMPLE
Set-PodeResponseAttachment -Path './image.png'

.EXAMPLE
Set-PodeResponseAttachment -Path 'c:/content/accounts.xlsx'

.EXAMPLE
Set-PodeResponseAttachment -Path './data.txt' -ContentType 'application/json'

.EXAMPLE
Set-PodeResponseAttachment -Path '/assets/data.txt' -EndpointName 'Example'
#>

function Set-PodeResponseAttachment {
    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $true, Position = 0, ValueFromPipeline = $true)]
        [string]
        $Path,

        [ValidatePattern('^\w+\/[\w\.\+-]+$')]
        [string]
        $ContentType,

        [Parameter()]
        [string]
        $EndpointName,

        [switch]
        $FileBrowser

    )
    begin {
        $pipelineItemCount = 0
    }

    process {
        $pipelineItemCount++
    }

    end {
        if ($pipelineItemCount -gt 1) {
            throw ($PodeLocale.fnDoesNotAcceptArrayAsPipelineInputExceptionMessage -f $($MyInvocation.MyCommand.Name))
        }

        # already sent? skip
        if ($WebEvent.Response.Sent) {
            return
        }

        # only attach files from public/static-route directories when path is relative
        $route = (Find-PodeStaticRoute -Path $Path -CheckPublic -EndpointName $EndpointName)
        if ($route) {
            $_path = $route.Content.Source

        }
        else {
            $_path = Get-PodeRelativePath -Path $Path -JoinRoot
        }
        #call internal Attachment function
        Write-PodeAttachmentResponseInternal -Path $_path -ContentType $ContentType -FileBrowser:$fileBrowser
    }
}


<#
.SYNOPSIS
Writes a String or a Byte[] to the Response.

.DESCRIPTION
Writes a String or a Byte[] to the Response, as some specified content type. This value can also be cached.

.PARAMETER Value
A String value to write.

.PARAMETER Bytes
An array of Bytes to write.

.PARAMETER ContentType
The content type of the data being written.

.PARAMETER MaxAge
The maximum age to cache the value on the browser, in seconds.

.PARAMETER StatusCode
The status code to set against the response.

.PARAMETER Cache
Should the value be cached by browsers, or not?

.EXAMPLE
Write-PodeTextResponse -Value 'Leeeeeerrrooooy Jeeeenkiiins!'

.EXAMPLE
Write-PodeTextResponse -Value '{"name": "Rick"}' -ContentType 'application/json'

.EXAMPLE
Write-PodeTextResponse -Bytes (Get-Content -Path ./some/image.png -Raw -AsByteStream) -Cache -MaxAge 1800

.EXAMPLE
Write-PodeTextResponse -Value 'Untitled Text Response' -StatusCode 418
#>
function Write-PodeTextResponse {
    [CmdletBinding(DefaultParameterSetName = 'String')]
    param (
        [Parameter(ParameterSetName = 'String', ValueFromPipeline = $true, Position = 0)]
        [string]
        $Value,

        [Parameter(ParameterSetName = 'Bytes')]
        [byte[]]
        $Bytes,

        [Parameter()]
        [string]
        $ContentType = 'text/plain',

        [Parameter()]
        [int]
        $MaxAge = 3600,

        [Parameter()]
        [int]
        $StatusCode = 200,

        [switch]
        $Cache
    )
    begin {
        # Initialize an array to hold piped-in values
        $pipelineValue = @()
    }process {
        # Add the current piped-in value to the array
        $pipelineValue += $_
    }end {
        # Set Value to the array of values
        if ($pipelineValue.Count -gt 1) {
            $Value = $pipelineValue -join "`n"
        }

        $isStringValue = ($PSCmdlet.ParameterSetName -ieq 'string')
        $isByteValue = ($PSCmdlet.ParameterSetName -ieq 'bytes')

        # set the status code of the response, but only if it's not 200 (to prevent overriding)
        if ($StatusCode -ne 200) {
            Set-PodeResponseStatus -Code $StatusCode -NoErrorPage
        }

        # if there's nothing to write, return
        if ($isStringValue -and [string]::IsNullOrWhiteSpace($Value)) {
            return
        }

        if ($isByteValue -and (($null -eq $Bytes) -or ($Bytes.Length -eq 0))) {
            return
        }

        # if the response stream isn't writable or already sent, return
        $res = $WebEvent.Response
        if (($null -eq $res) -or ($WebEvent.Streamed -and (($null -eq $res.OutputStream) -or !$res.OutputStream.CanWrite -or $res.Sent))) {
            return
        }

        # set a cache value
        if ($Cache) {
            Set-PodeHeader -Name 'Cache-Control' -Value "max-age=$($MaxAge), must-revalidate"
            Set-PodeHeader -Name 'Expires' -Value ([datetime]::UtcNow.AddSeconds($MaxAge).ToString('r', [CultureInfo]::InvariantCulture))
        }

        # specify the content-type if supplied (adding utf-8 if missing)
        if (![string]::IsNullOrWhiteSpace($ContentType)) {
            $charset = 'charset=utf-8'
            if ($ContentType -inotcontains $charset) {
                $ContentType = "$($ContentType); $($charset)"
            }

            $res.ContentType = $ContentType
        }

        # if we're serverless, set the string as the body
        if (!$WebEvent.Streamed) {
            if ($isStringValue) {
                $res.Body = $Value
            }
            else {
                $res.Body = $Bytes
            }
        }

        else {
            # convert string to bytes
            if ($isStringValue) {
                $Bytes = ConvertFrom-PodeValueToByteArray -Value $Value
            }

            # check if we only need a range of the bytes
            if (($null -ne $WebEvent.Ranges) -and ($WebEvent.Response.StatusCode -eq 200) -and ($StatusCode -eq 200)) {
                $lengths = @()
                $size = $Bytes.Length

                $Bytes = @(foreach ($range in $WebEvent.Ranges) {
                        # ensure range not invalid
                        if (([int]$range.Start -lt 0) -or ([int]$range.Start -ge $size) -or ([int]$range.End -lt 0)) {
                            Set-PodeResponseStatus -Code 416 -NoErrorPage
                            return
                        }

                        # skip start bytes only
                        if ([string]::IsNullOrWhiteSpace($range.End)) {
                            $Bytes[$range.Start..($size - 1)]
                            $lengths += "$($range.Start)-$($size - 1)/$($size)"
                        }

                        # end bytes only
                        elseif ([string]::IsNullOrWhiteSpace($range.Start)) {
                            if ([int]$range.End -gt $size) {
                                $range.End = $size
                            }

                            if ([int]$range.End -gt 0) {
                                $Bytes[$($size - $range.End)..($size - 1)]
                                $lengths += "$($size - $range.End)-$($size - 1)/$($size)"
                            }
                            else {
                                $lengths += "0-0/$($size)"
                            }
                        }

                        # normal range
                        else {
                            if ([int]$range.End -ge $size) {
                                Set-PodeResponseStatus -Code 416 -NoErrorPage
                                return
                            }

                            $Bytes[$range.Start..$range.End]
                            $lengths += "$($range.Start)-$($range.End)/$($size)"
                        }
                    })

                Set-PodeHeader -Name 'Content-Range' -Value "bytes $($lengths -join ', ')"
                if ($StatusCode -eq 200) {
                    Set-PodeResponseStatus -Code 206 -NoErrorPage
                }
            }

            # check if we need to compress the response
            if ($PodeContext.Server.Web.Compression.Enabled -and ![string]::IsNullOrWhiteSpace($WebEvent.AcceptEncoding)) {
                try {
                    $ms = [System.IO.MemoryStream]::new()
                    $stream = Get-PodeCompressionStream -InputStream $ms -Encoding $WebEvent.AcceptEncoding -Mode Compress
                    $stream.Write($Bytes, 0, $Bytes.Length)
                    $stream.Close()
                    $ms.Position = 0
                    $Bytes = $ms.ToArray()
                }
                finally {
                    if ($null -ne $stream) {
                        $stream.Close()
                    }

                    if ($null -ne $ms) {
                        $ms.Close()
                    }
                }

                # set content encoding header
                Set-PodeHeader -Name 'Content-Encoding' -Value $WebEvent.AcceptEncoding
            }

            # write the content to the response stream
            $res.ContentLength64 = $Bytes.Length

            try {
                $ms = [System.IO.MemoryStream]::new()
                $ms.Write($Bytes, 0, $Bytes.Length)
                $ms.WriteTo($res.OutputStream)
            }
            catch {
                if ((Test-PodeValidNetworkFailure $_.Exception)) {
                    return
                }

                $_ | Write-PodeErrorLog
                throw
            }
            finally {
                if ($null -ne $ms) {
                    $ms.Close()
                }
            }
        }
    }
}

<#
.SYNOPSIS
Renders the content of a static, or dynamic, file on the Response.

.DESCRIPTION
Renders the content of a static, or dynamic, file on the Response.
You can set browser's to cache the content, and also override the file's content type.

.PARAMETER Path
The path to a file.

.PARAMETER Data
A HashTable of dynamic data to supply to a dynamic file.

.PARAMETER ContentType
The content type of the file's contents - this overrides the file's extension.

.PARAMETER MaxAge
The maximum age to cache the file's content on the browser, in seconds.

.PARAMETER StatusCode
The status code to set against the response.

.PARAMETER Cache
Should the file's content be cached by browsers, or not?

.PARAMETER FileBrowser
If the path is a folder, instead of returning 404, will return A browsable content of the directory.

.EXAMPLE
Write-PodeFileResponse -Path 'C:/Files/Stuff.txt'

.EXAMPLE
Write-PodeFileResponse -Path 'C:/Files/Stuff.txt' -Cache -MaxAge 1800

.EXAMPLE
Write-PodeFileResponse -Path 'C:/Files/Stuff.txt' -ContentType 'application/json'

.EXAMPLE
Write-PodeFileResponse -Path 'C:/Views/Index.pode' -Data @{ Counter = 2 }

.EXAMPLE
Write-PodeFileResponse -Path 'C:/Files/Stuff.txt' -StatusCode 201

.EXAMPLE
Write-PodeFileResponse -Path 'C:/Files/' -FileBrowser
#>
function Write-PodeFileResponse {
    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $true, Position = 0, ValueFromPipeline = $true)]
        [ValidateNotNull()]
        [string]
        $Path,

        [Parameter()]
        $Data = @{},

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

        [Parameter()]
        [int]
        $MaxAge = 3600,

        [Parameter()]
        [int]
        $StatusCode = 200,

        [switch]
        $Cache,

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

    process {
        $pipelineItemCount++
    }

    end {
        if ($pipelineItemCount -gt 1) {
            throw ($PodeLocale.fnDoesNotAcceptArrayAsPipelineInputExceptionMessage -f $($MyInvocation.MyCommand.Name))
        }
        # resolve for relative path
        $RelativePath = Get-PodeRelativePath -Path $Path -JoinRoot

        Write-PodeFileResponseInternal -Path $RelativePath -Data $Data -ContentType $ContentType -MaxAge $MaxAge `
            -StatusCode $StatusCode -Cache:$Cache -FileBrowser:$FileBrowser
    }
}

<#
.SYNOPSIS
Serves a directory listing as a web page.

.DESCRIPTION
The Write-PodeDirectoryResponse function generates an HTML response that lists the contents of a specified directory,
allowing for browsing of files and directories. It supports both Windows and Unix-like environments by adjusting the
display of file attributes accordingly. If the path is a directory, it generates a browsable HTML view; otherwise, it
serves the file directly.

.PARAMETER Path
The path to the directory that should be displayed. This path is resolved and used to generate a list of contents.

.EXAMPLE
Write-PodeDirectoryResponse -Path './static'

Generates and serves an HTML page that lists the contents of the './static' directory, allowing users to click through files and directories.
#>
function Write-PodeDirectoryResponse {
    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $true, Position = 0, ValueFromPipeline = $true)]
        [ValidateNotNull()]
        [string]
        $Path
    )
    begin {
        $pipelineItemCount = 0
    }

    process {
        $pipelineItemCount++
    }

    end {
        if ($pipelineItemCount -gt 1) {
            throw ($PodeLocale.fnDoesNotAcceptArrayAsPipelineInputExceptionMessage -f $($MyInvocation.MyCommand.Name))
        }
        # resolve for relative path
        $RelativePath = Get-PodeRelativePath -Path $Path -JoinRoot

        if (Test-Path -Path $RelativePath -PathType Container) {
            Write-PodeDirectoryResponseInternal -Path $RelativePath
        }
        else {
            Set-PodeResponseStatus -Code 404
        }
    }
}

<#
.SYNOPSIS
Writes CSV data to the Response.

.DESCRIPTION
Writes CSV data to the Response, setting the content type accordingly.

.PARAMETER Value
A String, PSObject, or HashTable value.

.PARAMETER Path
The path to a CSV file.

.PARAMETER StatusCode
The status code to set against the response.

.EXAMPLE
Write-PodeCsvResponse -Value "Name`nRick"

.EXAMPLE
Write-PodeCsvResponse -Value @{ Name = 'Rick' }

.EXAMPLE
Write-PodeCsvResponse -Path 'E:/Files/Names.csv'
#>
function Write-PodeCsvResponse {
    [CmdletBinding(DefaultParameterSetName = 'Value')]
    param (
        [Parameter(Mandatory = $true, ParameterSetName = 'Value', ValueFromPipeline = $true, Position = 0)]
        $Value,

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

        [Parameter()]
        [int]
        $StatusCode = 200
    )

    begin {
        $pipelineValue = @()
    }

    process {
        if ($PSCmdlet.ParameterSetName -eq 'Value') {
            $pipelineValue += $_
        }
    }

    end {
        switch ($PSCmdlet.ParameterSetName.ToLowerInvariant()) {
            'file' {
                if (Test-PodePath $Path) {
                    $Value = Get-PodeFileContent -Path $Path
                }
            }

            'value' {
                if ($pipelineValue.Count -gt 1) {
                    $Value = $pipelineValue
                }

                if ($Value -isnot [string]) {
                    $Value = Resolve-PodeObjectArray -Property $Value

                    if (Test-PodeIsPSCore) {
                        $Value = ($Value | ConvertTo-Csv -Delimiter ',' -IncludeTypeInformation:$false)
                    }
                    else {
                        $Value = ($Value | ConvertTo-Csv -Delimiter ',' -NoTypeInformation)
                    }

                    $Value = ($Value -join ([environment]::NewLine))
                }
            }
        }

        if ([string]::IsNullOrWhiteSpace($Value)) {
            $Value = [string]::Empty
        }

        Write-PodeTextResponse -Value $Value -ContentType 'text/csv' -StatusCode $StatusCode
    }
}

<#
.SYNOPSIS
Writes HTML data to the Response.

.DESCRIPTION
Writes HTML data to the Response, setting the content type accordingly.

.PARAMETER Value
A String, PSObject, or HashTable value.

.PARAMETER Path
The path to a HTML file.

.PARAMETER StatusCode
The status code to set against the response.

.EXAMPLE
Write-PodeHtmlResponse -Value "Raw HTML can be placed here"

.EXAMPLE
Write-PodeHtmlResponse -Value @{ Message = 'Hello, all!' }

.EXAMPLE
Write-PodeHtmlResponse -Path 'E:/Site/About.html'
#>
function Write-PodeHtmlResponse {
    [CmdletBinding(DefaultParameterSetName = 'Value')]
    param (
        [Parameter(Mandatory = $true, ParameterSetName = 'Value', ValueFromPipeline = $true, Position = 0)]
        $Value,

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

        [Parameter()]
        [int]
        $StatusCode = 200
    )

    begin {
        $pipelineValue = @()
    }

    process {
        if ($PSCmdlet.ParameterSetName -eq 'Value') {
            $pipelineValue += $_
        }
    }

    end {
        switch ($PSCmdlet.ParameterSetName.ToLowerInvariant()) {
            'file' {
                if (Test-PodePath $Path) {
                    $Value = Get-PodeFileContent -Path $Path
                }
            }

            'value' {
                if ($pipelineValue.Count -gt 1) {
                    $Value = $pipelineValue
                }
                if ($Value -isnot [string]) {
                    $Value = ($Value | ConvertTo-Html)
                    $Value = ($Value -join ([environment]::NewLine))
                }
            }
        }

        if ([string]::IsNullOrWhiteSpace($Value)) {
            $Value = [string]::Empty
        }

        Write-PodeTextResponse -Value $Value -ContentType 'text/html' -StatusCode $StatusCode
    }
}


<#
.SYNOPSIS
Writes Markdown data to the Response.

.DESCRIPTION
Writes Markdown data to the Response, with the option to render it as HTML.

.PARAMETER Value
A String, PSObject, or HashTable value.

.PARAMETER Path
The path to a Markdown file.

.PARAMETER StatusCode
The status code to set against the response.

.PARAMETER AsHtml
If supplied, the Markdown will be converted to HTML. (This is only supported in PS7+)

.EXAMPLE
Write-PodeMarkdownResponse -Value '# Hello, world!' -AsHtml

.EXAMPLE
Write-PodeMarkdownResponse -Path 'E:/Site/About.md'
#>
function Write-PodeMarkdownResponse {
    [CmdletBinding(DefaultParameterSetName = 'Value')]
    param (
        [Parameter(Mandatory = $true, ParameterSetName = 'Value', ValueFromPipeline = $true, Position = 0)]
        $Value,

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

        [Parameter()]
        [int]
        $StatusCode = 200,

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

    process {
        $pipelineItemCount++
    }

    end {
        if ($pipelineItemCount -gt 1) {
            throw ($PodeLocale.fnDoesNotAcceptArrayAsPipelineInputExceptionMessage -f $($MyInvocation.MyCommand.Name))
        }
        switch ($PSCmdlet.ParameterSetName.ToLowerInvariant()) {
            'file' {
                if (Test-PodePath $Path) {
                    $Value = Get-PodeFileContent -Path $Path
                }
            }
        }

        if ([string]::IsNullOrWhiteSpace($Value)) {
            $Value = [string]::Empty
        }

        $mimeType = 'text/markdown'

        if ($AsHtml) {
            if ($PSVersionTable.PSVersion.Major -ge 7) {
                $mimeType = 'text/html'
                $Value = ($Value | ConvertFrom-Markdown).Html
            }
        }

        Write-PodeTextResponse -Value $Value -ContentType $mimeType -StatusCode $StatusCode
    }
}

<#
.SYNOPSIS
Writes JSON data to the Response.

.DESCRIPTION
Writes JSON data to the Response, setting the content type accordingly.

.PARAMETER Value
A String, PSObject, or HashTable value. For non-string values, they will be converted to JSON.

.PARAMETER Path
The path to a JSON file.

.PARAMETER ContentType
Because JSON content has not yet an official content type. one custom can be specified here (Default: 'application/json' )
https://www.rfc-editor.org/rfc/rfc8259

.PARAMETER Depth
The Depth to generate the JSON document - the larger this value the worse performance gets.

.PARAMETER StatusCode
The status code to set against the response.

.PARAMETER NoCompress
The JSON document is not compressed (Human readable form)

.EXAMPLE
Write-PodeJsonResponse -Value '{"name": "Rick"}'

.EXAMPLE
Write-PodeJsonResponse -Value @{ Name = 'Rick' } -StatusCode 201

.EXAMPLE
Write-PodeJsonResponse -Path 'E:/Files/Names.json'
#>
function Write-PodeJsonResponse {
    [CmdletBinding(DefaultParameterSetName = 'Value')]
    param (
        [Parameter(Mandatory = $true, ParameterSetName = 'Value', ValueFromPipeline = $true, Position = 0)]
        [AllowNull()]
        $Value,

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

        [Parameter()]
        [ValidatePattern('^\w+\/[\w\.\+-]+$')]
        [ValidateNotNullOrEmpty()]
        [string]
        $ContentType = 'application/json',

        [Parameter(ParameterSetName = 'Value')]
        [ValidateRange(0, 100)]
        [int]
        $Depth = 10,

        [Parameter()]
        [int]
        $StatusCode = 200,

        [Parameter(ParameterSetName = 'Value')]
        [switch]
        $NoCompress

    )
    begin {
        $pipelineValue = @()
    }

    process {
        if ($PSCmdlet.ParameterSetName -eq 'Value') {
            $pipelineValue += $_
        }
    }

    end {
        switch ($PSCmdlet.ParameterSetName.ToLowerInvariant()) {
            'file' {
                if (Test-PodePath $Path) {
                    $Value = Get-PodeFileContent -Path $Path
                }
                if ([string]::IsNullOrWhiteSpace($Value)) {
                    $Value = '{}'
                }
            }

            'value' {
                if ($pipelineValue.Count -gt 1) {
                    $Value = $pipelineValue
                }
                if ($Value -isnot [string]) {
                    $Value = (ConvertTo-Json -InputObject $Value -Depth $Depth -Compress:(!$NoCompress))
                }
            }
        }

        if ([string]::IsNullOrWhiteSpace($Value)) {
            $Value = '{}'
        }

        Write-PodeTextResponse -Value $Value -ContentType $ContentType -StatusCode $StatusCode
    }
}


<#
.SYNOPSIS
Writes XML data to the Response.

.DESCRIPTION
Writes XML data to the Response, setting the content type accordingly.

.PARAMETER Value
A String, PSObject, or HashTable value.

.PARAMETER Path
The path to an XML file.

.PARAMETER ContentType
Because XML content has not yet an official content type. one custom can be specified here (Default: 'application/xml' )
https://www.rfc-editor.org/rfc/rfc3023

.PARAMETER Depth
The Depth to generate the XML document - the larger this value the worse performance gets.

.PARAMETER StatusCode
The status code to set against the response.

.EXAMPLE
Write-PodeXmlResponse -Value '<root><name>Rick</name></root>'

.EXAMPLE
Write-PodeXmlResponse -Value @{ Name = 'Rick' } -StatusCode 201

.EXAMPLE
@(@{ Name = 'Rick' }, @{ Name = 'Don' }) | Write-PodeXmlResponse

.EXAMPLE
$users = @([PSCustomObject]@{
                Name = 'Rick'
            }, [PSCustomObject]@{
                Name = 'Don'
            }
        )
Write-PodeXmlResponse -Value $users

.EXAMPLE
@([PSCustomObject]@{
        Name = 'Rick'
    }, [PSCustomObject]@{
        Name = 'Don'
    }
) | Write-PodeXmlResponse

.EXAMPLE
Write-PodeXmlResponse -Path 'E:/Files/Names.xml'

#>
function Write-PodeXmlResponse {
    [CmdletBinding(DefaultParameterSetName = 'Value')]
    param (
        [Parameter(Mandatory = $true, ParameterSetName = 'Value', ValueFromPipeline = $true, Position = 0)]
        [AllowNull()]
        $Value,

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

        [Parameter(ParameterSetName = 'Value')]
        [ValidateRange(0, 100)]
        [int]
        $Depth = 10,

        [Parameter()]
        [ValidatePattern('^\w+\/[\w\.\+-]+$')]
        [ValidateNotNullOrEmpty()]
        [string]
        $ContentType = 'application/xml',

        [Parameter()]
        [int]
        $StatusCode = 200
    )
    begin {
        $pipelineValue = @()
    }

    process {
        if ($PSCmdlet.ParameterSetName -eq 'Value' -and $_) {
            $pipelineValue += $_
        }
    }

    end {

        switch ($PSCmdlet.ParameterSetName.ToLowerInvariant()) {
            'file' {
                if (Test-PodePath $Path) {
                    $Value = Get-PodeFileContent -Path $Path
                }
            }

            'value' {
                if ($pipelineValue.Count -gt 1) {
                    $Value = $pipelineValue
                }

                if ($Value -isnot [string]) {
                    $Value = Resolve-PodeObjectArray -Property $Value | ConvertTo-Xml -Depth $Depth -As String -NoTypeInformation
                }
            }
        }

        if ([string]::IsNullOrWhiteSpace($Value)) {
            $Value = [string]::Empty
        }

        Write-PodeTextResponse -Value $Value -ContentType $ContentType -StatusCode $StatusCode
    }
}

<#
.SYNOPSIS
Writes YAML data to the Response.

.DESCRIPTION
Writes YAML data to the Response, setting the content type accordingly.

.PARAMETER Value
A String, PSObject, or HashTable value. For non-string values, they will be converted to YAML.

.PARAMETER Path
The path to a YAML file.

.PARAMETER ContentType
Because YAML content has not yet an official content type. one custom can be specified here (Default: 'application/yaml' )
https://www.rfc-editor.org/rfc/rfc9512

.PARAMETER Depth
The Depth to generate the YAML document - the larger this value the worse performance gets.

.PARAMETER StatusCode
The status code to set against the response.

.EXAMPLE
Write-PodeYamlResponse -Value '{"name": "Rick"}'

.EXAMPLE
Write-PodeYamlResponse -Value @{ Name = 'Rick' } -StatusCode 201

.EXAMPLE
Write-PodeYamlResponse -Path 'E:/Files/Names.json'
#>
function Write-PodeYamlResponse {
    [CmdletBinding(DefaultParameterSetName = 'Value')]
    param (
        [Parameter(Mandatory = $true, ParameterSetName = 'Value', ValueFromPipeline = $true, Position = 0)]
        [AllowNull()]
        $Value,

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

        [Parameter()]
        [ValidatePattern('^\w+\/[\w\.\+-]+$')]
        [ValidateNotNullOrEmpty()]
        [string]
        $ContentType = 'application/yaml',


        [Parameter(ParameterSetName = 'Value')]
        [ValidateRange(0, 100)]
        [int]
        $Depth = 10,

        [Parameter()]
        [int]
        $StatusCode = 200
    )

    begin {
        $pipelineValue = @()
    }

    process {
        if ($PSCmdlet.ParameterSetName -eq 'Value') {
            $pipelineValue += $_
        }
    }

    end {

        switch ($PSCmdlet.ParameterSetName.ToLowerInvariant()) {
            'file' {
                if (Test-PodePath $Path) {
                    $Value = Get-PodeFileContent -Path $Path
                }
            }

            'value' {
                if ($pipelineValue.Count -gt 1) {
                    $Value = $pipelineValue
                }

                if ($Value -isnot [string]) {
                    $Value = ConvertTo-PodeYaml -InputObject $Value -Depth $Depth

                }
            }
        }
        if ([string]::IsNullOrWhiteSpace($Value)) {
            $Value = '[]'
        }

        Write-PodeTextResponse -Value $Value -ContentType $ContentType -StatusCode $StatusCode
    }
}



<#
.SYNOPSIS
Renders a dynamic, or static, View on the Response.

.DESCRIPTION
Renders a dynamic, or static, View on the Response; allowing for dynamic data to be supplied.

.PARAMETER Path
The path to a View, relative to the "/views" directory. (Extension is optional).

.PARAMETER Data
Any dynamic data to supply to a dynamic View.

.PARAMETER StatusCode
The status code to set against the response.

.PARAMETER Folder
If supplied, a custom views folder will be used.

.PARAMETER FlashMessages
Automatically supply all Flash messages in the current session to the View.

.EXAMPLE
Write-PodeViewResponse -Path 'index'

.EXAMPLE
Write-PodeViewResponse -Path 'accounts/profile_page' -Data @{ Username = 'Morty' }

.EXAMPLE
Write-PodeViewResponse -Path 'login' -FlashMessages
#>
function Write-PodeViewResponse {
    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $true, Position = 0, ValueFromPipeline = $true)]
        [string]
        $Path,

        [Parameter()]
        [hashtable]
        $Data = @{},

        [Parameter()]
        [int]
        $StatusCode = 200,

        [Parameter()]
        [string]
        $Folder,

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

    process {
        $pipelineItemCount++
    }

    end {
        if ($pipelineItemCount -gt 1) {
            throw ($PodeLocale.fnDoesNotAcceptArrayAsPipelineInputExceptionMessage -f $($MyInvocation.MyCommand.Name))
        }
        # default data if null
        if ($null -eq $Data) {
            $Data = @{}
        }

        # add path to data as "pagename" - unless key already exists
        if (!$Data.ContainsKey('pagename')) {
            $Data['pagename'] = $Path
        }

        # load all flash messages if needed
        if ($FlashMessages -and ($null -ne $WebEvent.Session.Data.Flash)) {
            $Data['flash'] = @{}

            foreach ($name in (Get-PodeFlashMessageNames)) {
                $Data.flash[$name] = (Get-PodeFlashMessage -Name $name)
            }
        }
        elseif ($null -eq $Data['flash']) {
            $Data['flash'] = @{}
        }

        # add view engine extension
        $ext = Get-PodeFileExtension -Path $Path
        if ([string]::IsNullOrWhiteSpace($ext)) {
            $Path += ".$($PodeContext.Server.ViewEngine.Extension)"
        }

        # only look in the view directories
        $viewFolder = $PodeContext.Server.InbuiltDrives['views']
        if (![string]::IsNullOrWhiteSpace($Folder)) {
            $viewFolder = $PodeContext.Server.Views[$Folder]
        }

        $Path = [System.IO.Path]::Combine($viewFolder, $Path)

        # test the file path, and set status accordingly
        if (!(Test-PodePath $Path)) {
            return
        }

        # run any engine logic and render it
        $engine = (Get-PodeViewEngineType -Path $Path)
        $value = (Get-PodeFileContentUsingViewEngine -Path $Path -Data $Data)

        switch ($engine.ToLowerInvariant()) {
            'md' {
                Write-PodeMarkdownResponse -Value $value -StatusCode $StatusCode -AsHtml
            }

            default {
                Write-PodeHtmlResponse -Value $value -StatusCode $StatusCode
            }
        }
    }
}


<#
.SYNOPSIS
Sets the Status Code of the Response, and controls rendering error pages.

.DESCRIPTION
Sets the Status Code of the Response, and controls rendering error pages.

.PARAMETER Code
The Status Code to set on the Response.

.PARAMETER Description
An optional Status Description.

.PARAMETER Exception
An exception to use when detailing error information on error pages.

.PARAMETER ContentType
The content type of the error page to use.

.PARAMETER NoErrorPage
Don't render an error page when the Status Code is 400+.

.EXAMPLE
Set-PodeResponseStatus -Code 404

.EXAMPLE
Set-PodeResponseStatus -Code 500 -Exception $_.Exception

.EXAMPLE
Set-PodeResponseStatus -Code 500 -Exception $_.Exception -ContentType 'application/json'
#>
function Set-PodeResponseStatus {
    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $true)]
        [int]
        $Code,

        [Parameter()]
        [string]
        $Description,

        [Parameter()]
        $Exception,

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

        [switch]
        $NoErrorPage
    )

    # already sent? skip
    if ($WebEvent.Response.Sent) {
        return
    }

    # set the code
    $WebEvent.Response.StatusCode = $Code

    # set an appropriate description (mapping if supplied is blank)
    if ([string]::IsNullOrWhiteSpace($Description)) {
        $Description = (Get-PodeStatusDescription -StatusCode $Code)
    }

    if (!$PodeContext.Server.IsServerless -and ![string]::IsNullOrWhiteSpace($Description)) {
        $WebEvent.Response.StatusDescription = $Description
    }

    # if the status code is >=400 then attempt to load error page
    if (!$NoErrorPage -and ($Code -ge 400)) {
        Show-PodeErrorPage -Code $Code -Description $Description -Exception $Exception -ContentType $ContentType
    }
}

<#
.SYNOPSIS
Redirecting a user to a new URL.

.DESCRIPTION
Redirecting a user to a new URL, or the same URL as the Request but a different Protocol - or other components.

.PARAMETER Url
Redirect the user to a new URL, or a relative path.

.PARAMETER EndpointName
The Name of an Endpoint to redirect to.

.PARAMETER Port
Change the port of the current Request before redirecting.

.PARAMETER Protocol
Change the protocol of the current Request before redirecting.

.PARAMETER Address
Change the domain address of the current Request before redirecting.

.PARAMETER Moved
Set the Status Code as "301 Moved", rather than "302 Redirect".

.EXAMPLE
Move-PodeResponseUrl -Url 'https://google.com'

.EXAMPLE
Move-PodeResponseUrl -Url '/about'

.EXAMPLE
Move-PodeResponseUrl -Protocol HTTPS

.EXAMPLE
Move-PodeResponseUrl -Port 9000 -Moved
#>
function Move-PodeResponseUrl {
    [CmdletBinding(DefaultParameterSetName = 'Url')]
    param(
        [Parameter(Mandatory = $true, ParameterSetName = 'Url')]
        [string]
        $Url,

        [Parameter(ParameterSetName = 'Endpoint')]
        [string]
        $EndpointName,

        [Parameter(ParameterSetName = 'Components')]
        [int]
        $Port = 0,

        [Parameter(ParameterSetName = 'Components')]
        [ValidateSet('', 'Http', 'Https')]
        [string]
        $Protocol,

        [Parameter(ParameterSetName = 'Components')]
        [string]
        $Address,

        [switch]
        $Moved
    )

    # build the url
    if ($PSCmdlet.ParameterSetName -ieq 'components') {
        $uri = $WebEvent.Request.Url

        # set the protocol
        $Protocol = $Protocol.ToLowerInvariant()
        if ([string]::IsNullOrWhiteSpace($Protocol)) {
            $Protocol = $uri.Scheme
        }

        # set the domain
        if ([string]::IsNullOrWhiteSpace($Address)) {
            $Address = $uri.Host
        }

        # set the port
        if ($Port -le 0) {
            $Port = $uri.Port
        }

        $PortStr = [string]::Empty
        if (@(80, 443) -notcontains $Port) {
            $PortStr = ":$($Port)"
        }

        # combine to form the url
        $Url = "$($Protocol)://$($Address)$($PortStr)$($uri.PathAndQuery)"
    }

    # build the url from an endpoint
    elseif ($PSCmdlet.ParameterSetName -ieq 'endpoint') {
        $endpoint = Get-PodeEndpointByName -Name $EndpointName -ThrowError

        # set the port
        $PortStr = [string]::Empty
        if (@(80, 443) -notcontains $endpoint.Port) {
            $PortStr = ":$($endpoint.Port)"
        }

        $Url = "$($endpoint.Protocol)://$($endpoint.FriendlyName)$($PortStr)$($WebEvent.Request.Url.PathAndQuery)"
    }

    Set-PodeHeader -Name 'Location' -Value $Url

    if ($Moved) {
        Set-PodeResponseStatus -Code 301 -Description 'Moved'
    }
    else {
        Set-PodeResponseStatus -Code 302 -Description 'Redirect'
    }
}

<#
.SYNOPSIS
Writes data to a TCP socket stream.

.DESCRIPTION
Writes data to a TCP socket stream.

.PARAMETER Message
The message to write

.EXAMPLE
Write-PodeTcpClient -Message '250 OK'
#>
function Write-PodeTcpClient {
    [CmdletBinding()]
    param(
        [Parameter(ValueFromPipeline = $true)]
        [string]
        $Message
    )
    begin {
        # Initialize an array to hold piped-in values
        $pipelineValue = @()
    }

    process {
        # Add the current piped-in value to the array
        $pipelineValue += $_
    }

    end {
        # Set Route to the array of values
        if ($pipelineValue.Count -gt 1) {
            $Message = $pipelineValue -join "`n"
        }
        $TcpEvent.Response.WriteLine($Message, $true)
    }
}

<#
.SYNOPSIS
Reads data from a TCP socket stream.

.DESCRIPTION
Reads data from a TCP socket stream.

.PARAMETER Timeout
An optional Timeout in milliseconds.

.PARAMETER CheckBytes
An optional array of bytes to check at the end of a receievd data stream, to determine if the data is complete.

.PARAMETER CRLFMessageEnd
If supplied, the CheckBytes will be set to 13 and 10 to make sure a message ends with CR and LF.

.EXAMPLE
$data = Read-PodeTcpClient

.EXAMPLE
$data = Read-PodeTcpClient -CRLFMessageEnd
#>
function Read-PodeTcpClient {
    [CmdletBinding(DefaultParameterSetName = 'default')]
    [OutputType([string])]
    param(
        [Parameter()]
        [int]
        $Timeout = 0,

        [Parameter(ParameterSetName = 'CheckBytes')]
        [byte[]]
        $CheckBytes = $null,

        [Parameter(ParameterSetName = 'CRLF')]
        [switch]
        $CRLFMessageEnd
    )

    $cBytes = $CheckBytes
    if ($CRLFMessageEnd) {
        $cBytes = [byte[]]@(13, 10)
    }

    return (Wait-PodeTask -Task $TcpEvent.Request.Read($cBytes, $PodeContext.Tokens.Cancellation.Token) -Timeout $Timeout)
}

<#
.SYNOPSIS
Close an open TCP client connection

.DESCRIPTION
Close an open TCP client connection

.EXAMPLE
Close-PodeTcpClient
#>
function Close-PodeTcpClient {
    [CmdletBinding()]
    param()

    $TcpEvent.Request.Close()
}

<#
.SYNOPSIS
Saves any uploaded files on the Request to the File System.

.DESCRIPTION
Saves any uploaded files on the Request to the File System.

.PARAMETER Key
The name of the key within the $WebEvent's Data HashTable that stores the file names.

.PARAMETER Path
The path to save files. If this is a directory then the file name of the uploaded file will be used, but if this is a file path then that name is used instead.
If the Request has multiple files in, and you specify a file path, then all files will be saved to that one file path - overwriting each other.

.PARAMETER FileName
An optional FileName to save a specific files if multiple files were supplied in the Request. By default, every file is saved.

.EXAMPLE
Save-PodeRequestFile -Key 'avatar'

.EXAMPLE
Save-PodeRequestFile -Key 'avatar' -Path 'F:/Images'

.EXAMPLE
Save-PodeRequestFile -Key 'avatar' -Path 'F:/Images' -FileName 'icon.png'
#>
function Save-PodeRequestFile {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $true)]
        [string]
        $Key,

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

        [Parameter()]
        [string[]]
        $FileName
    )

    # if path is '.', replace with server root
    $Path = Get-PodeRelativePath -Path $Path -JoinRoot

    # ensure the parameter name exists in data
    if (!(Test-PodeRequestFile -Key $Key)) {
        # A parameter called was not supplied in the request or has no data available
        throw ($PodeLocale.parameterNotSuppliedInRequestExceptionMessage -f $Key)
    }

    # get the file names
    $files = @($WebEvent.Data[$Key])
    if (($null -ne $FileName) -and ($FileName.Length -gt 0)) {
        $files = @(foreach ($file in $files) {
                if ($FileName -icontains $file) {
                    $file
                }
            })
    }

    # ensure the file data exists
    foreach ($file in $files) {
        if (!$WebEvent.Files.ContainsKey($file)) {
            # No data for file was uploaded in the request
            throw ($PodeLocale.noDataForFileUploadedExceptionMessage -f $file)
        }
    }

    # save the files
    foreach ($file in $files) {
        # if the path is a directory, add the filename
        $filePath = $Path
        if (Test-Path -Path $filePath -PathType Container) {
            $filePath = [System.IO.Path]::Combine($filePath, $file)
        }

        # save the file
        $WebEvent.Files[$file].Save($filePath)
    }
}

<#
.SYNOPSIS
Test to see if the Request contains the key for any uploaded files.

.DESCRIPTION
Test to see if the Request contains the key for any uploaded files.

.PARAMETER Key
The name of the key within the $WebEvent's Data HashTable that stores the file names.

.PARAMETER FileName
An optional FileName to test for a specific file within the list of uploaded files.

.EXAMPLE
Test-PodeRequestFile -Key 'avatar'

.EXAMPLE
Test-PodeRequestFile -Key 'avatar' -FileName 'icon.png'
#>
function Test-PodeRequestFile {
    [CmdletBinding()]
    [OutputType([bool])]
    param(
        [Parameter(Mandatory = $true)]
        [string]
        $Key,

        [Parameter()]
        [string]
        $FileName
    )

    # ensure the parameter name exists in data
    if (!$WebEvent.Data.ContainsKey($Key)) {
        return $false
    }

    # ensure it has filenames
    if ([string]::IsNullOrEmpty($WebEvent.Data[$Key])) {
        return $false
    }

    # do we have any specific files?
    if (![string]::IsNullOrEmpty($FileName)) {
        return (@($WebEvent.Data[$Key]) -icontains $FileName)
    }

    # we have files
    return $true
}

<#
.SYNOPSIS
Short description

.DESCRIPTION
Long description

.PARAMETER Type
The type name of the view engine (inbuilt types are: Pode and HTML).

.PARAMETER ScriptBlock
A ScriptBlock for specifying custom view engine rendering rules.

.PARAMETER Extension
A custom extension for the engine's files.

.EXAMPLE
Set-PodeViewEngine -Type HTML

.EXAMPLE
Set-PodeViewEngine -Type Markdown

.EXAMPLE
Set-PodeViewEngine -Type PSHTML -Extension PS1 -ScriptBlock { param($path, $data) /* logic */ }
#>
function Set-PodeViewEngine {
    [CmdletBinding()]
    param(
        [Parameter()]
        [string]
        $Type,

        [Parameter()]
        [scriptblock]
        $ScriptBlock = $null,

        [Parameter()]
        [string]
        $Extension
    )

    # truncate markdown
    if ($Type -ieq 'Markdown') {
        $Type = 'md'
    }

    # override extension with type
    if ([string]::IsNullOrWhiteSpace($Extension)) {
        $Extension = $Type
    }

    # check if the scriptblock has any using vars
    if ($null -ne $ScriptBlock) {
        $ScriptBlock, $usingVars = Convert-PodeScopedVariables -ScriptBlock $ScriptBlock -PSSession $PSCmdlet.SessionState
    }

    # setup view engine config
    $PodeContext.Server.ViewEngine.Type = $Type.ToLowerInvariant()
    $PodeContext.Server.ViewEngine.Extension = $Extension.ToLowerInvariant()
    $PodeContext.Server.ViewEngine.ScriptBlock = $ScriptBlock
    $PodeContext.Server.ViewEngine.UsingVariables = $usingVars
    $PodeContext.Server.ViewEngine.IsDynamic = (@('html', 'md') -inotcontains $Type)
}

<#
.SYNOPSIS
Includes the contents of a partial View into another dynamic View.

.DESCRIPTION
Includes the contents of a partial View into another dynamic View. The partial View can be static or dynamic.

.PARAMETER Path
The path to a partial View, relative to the "/views" directory. (Extension is optional).

.PARAMETER Data
Any dynamic data to supply to a dynamic partial View.

.PARAMETER Folder
If supplied, a custom views folder will be used.

.EXAMPLE
Use-PodePartialView -Path 'shared/footer'
#>
function Use-PodePartialView {
    [CmdletBinding()]
    [OutputType([string])]
    param (
        [Parameter(Mandatory = $true, ValueFromPipeline = $true, Position = 0)]
        [string]
        $Path,

        [Parameter()]
        $Data = @{},

        [Parameter()]
        [string]
        $Folder
    )
    begin {
        $pipelineItemCount = 0
    }

    process {
        $pipelineItemCount++
    }

    end {
        if ($pipelineItemCount -gt 1) {
            throw ($PodeLocale.fnDoesNotAcceptArrayAsPipelineInputExceptionMessage -f $($MyInvocation.MyCommand.Name))
        }
        # default data if null
        if ($null -eq $Data) {
            $Data = @{}
        }
        # add view engine extension
        $ext = Get-PodeFileExtension -Path $Path
        if ([string]::IsNullOrWhiteSpace($ext)) {
            $Path += ".$($PodeContext.Server.ViewEngine.Extension)"
        }

        # only look in the view directory
        $viewFolder = $PodeContext.Server.InbuiltDrives['views']
        if (![string]::IsNullOrWhiteSpace($Folder)) {
            $viewFolder = $PodeContext.Server.Views[$Folder]
        }

        $Path = [System.IO.Path]::Combine($viewFolder, $Path)

        # test the file path, and set status accordingly
        if (!(Test-PodePath $Path -NoStatus)) {
            # The Views path does not exist
            throw ($PodeLocale.viewsPathDoesNotExistExceptionMessage -f $Path)
        }

        # run any engine logic
        return (Get-PodeFileContentUsingViewEngine -Path $Path -Data $Data)
    }
}

<#
.SYNOPSIS
Broadcasts a message to connected WebSocket clients.

.DESCRIPTION
Broadcasts a message to all, or some, connected WebSocket clients. You can specify a path to send messages to, or a specific ClientId.

.PARAMETER Value
A String, PSObject, or HashTable value. For non-string values, they will be converted to JSON.

.PARAMETER Path
The Path of connected clients to send the message.

.PARAMETER ClientId
A specific ClientId of a connected client to send a message. Not currently used.

.PARAMETER Depth
The Depth to generate the JSON document - the larger this value the worse performance gets.

.PARAMETER Mode
The Mode to broadcast a message: Auto, Broadcast, Direct. (Default: Auto)

.PARAMETER IgnoreEvent
If supplied, if a SignalEvent is available it's data, such as path/clientId, will be ignored.

.EXAMPLE
Send-PodeSignal -Value @{ Message = 'Hello, world!' }

.EXAMPLE
Send-PodeSignal -Value @{ Data = @(123, 100, 101) } -Path '/response-charts'
#>
function Send-PodeSignal {
    [CmdletBinding()]
    param(
        [Parameter(ValueFromPipeline = $true, Position = 0 )]
        $Value,

        [Parameter()]
        [string]
        $Path,

        [Parameter()]
        [string]
        $ClientId,

        [Parameter()]
        [int]
        $Depth = 10,

        [Parameter()]
        [ValidateSet('Auto', 'Broadcast', 'Direct')]
        [string]
        $Mode = 'Auto',

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

    process {
        $pipelineItemCount++
    }

    end {
        if ($pipelineItemCount -gt 1) {
            throw ($PodeLocale.fnDoesNotAcceptArrayAsPipelineInputExceptionMessage -f $($MyInvocation.MyCommand.Name))
        }
        # error if not configured
        if (!$PodeContext.Server.Signals.Enabled) {
            # WebSockets have not been configured to send signal messages
            throw ($PodeLocale.websocketsNotConfiguredForSignalMessagesExceptionMessage)
        }

        # do nothing if no value
        if (($null -eq $Value) -or ([string]::IsNullOrEmpty($Value))) {
            return
        }

        # jsonify the value
        if ($Value -isnot [string]) {
            if ($Depth -le 0) {
                $Value = (ConvertTo-Json -InputObject $Value -Compress)
            }
            else {
                $Value = (ConvertTo-Json -InputObject $Value -Depth $Depth -Compress)
            }
        }

        # check signal event
        if (!$IgnoreEvent -and ($null -ne $SignalEvent)) {
            if ([string]::IsNullOrWhiteSpace($Path)) {
                $Path = $SignalEvent.Data.Path
            }

            if ([string]::IsNullOrWhiteSpace($ClientId)) {
                $ClientId = $SignalEvent.Data.ClientId
            }

            if (($Mode -ieq 'Auto') -and ($SignalEvent.Data.Direct -or ($SignalEvent.ClientId -ieq $SignalEvent.Data.ClientId))) {
                $Mode = 'Direct'
            }
        }

        # broadcast or direct?
        if ($Mode -iin @('Auto', 'Broadcast')) {
            $PodeContext.Server.Signals.Listener.AddServerSignal($Value, $Path, $ClientId)
        }
        else {
            $SignalEvent.Response.Write($Value)
        }
    }
}

<#
.SYNOPSIS
Add a custom path that contains additional views.

.DESCRIPTION
Add a custom path that contains additional views.

.PARAMETER Name
The Name of the views folder.

.PARAMETER Source
The literal, or relative, path to the directory that contains views.

.EXAMPLE
Add-PodeViewFolder -Name 'assets' -Source './assets'
#>
function Add-PodeViewFolder {
    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $true)]
        [string]
        $Name,

        [Parameter(Mandatory = $true)]
        [string]
        $Source
    )

    # ensure the folder doesn't already exist
    if ($PodeContext.Server.Views.ContainsKey($Name)) {
        # The Views folder name already exists
        throw ($PodeLocale.viewsFolderNameAlreadyExistsExceptionMessage -f $Name)
    }

    # ensure the path exists at server root
    $Source = Get-PodeRelativePath -Path $Source -JoinRoot
    if (!(Test-PodePath -Path $Source -NoStatus)) {
        # The Views path does not exist
        throw ($PodeLocale.viewsPathDoesNotExistExceptionMessage -f $Source)
    }

    # setup a temp drive for the path
    $Source = New-PodePSDrive -Path $Source

    # add the route(s)
    Write-Verbose "Adding View Folder: [$($Name)] $($Source)"
    $PodeContext.Server.Views[$Name] = $Source
}

<#
.SYNOPSIS
Pre-emptively send an HTTP response back to the client. This can be dangerous, so only use this function if you know what you're doing.

.DESCRIPTION
Pre-emptively send an HTTP response back to the client. This can be dangerous, so only use this function if you know what you're doing.

.EXAMPLE
Send-PodeResponse
#>
function Send-PodeResponse {
    [CmdletBinding()]
    param()

    if ($null -ne $WebEvent.Response) {
        $null = Wait-PodeTask -Task $WebEvent.Response.Send()
    }
}
src\Public\Routes.ps1
<#
.SYNOPSIS
Adds a Route for a specific HTTP Method(s).

.DESCRIPTION
Adds a Route for a specific HTTP Method(s), with path, that when called with invoke any logic and/or Middleware.

.PARAMETER Method
The HTTP Method of this Route, multiple can be supplied.

.PARAMETER Path
The URI path for the Route.

.PARAMETER Middleware
An array of ScriptBlocks for optional Middleware.

.PARAMETER ScriptBlock
A ScriptBlock for the Route's main logic.

.PARAMETER EndpointName
The EndpointName of an Endpoint(s) this Route should be bound against.

.PARAMETER ContentType
The content type the Route should use when parsing any payloads.

.PARAMETER TransferEncoding
The transfer encoding the Route should use when parsing any payloads.

.PARAMETER ErrorContentType
The content type of any error pages that may get returned.

.PARAMETER FilePath
A literal, or relative, path to a file containing a ScriptBlock for the Route's main logic.

.PARAMETER ArgumentList
An array of arguments to supply to the Route's ScriptBlock.

.PARAMETER Authentication
The name of an Authentication method which should be used as middleware on this Route.

.PARAMETER Access
The name of an Access method which should be used as middleware on this Route.

.PARAMETER AllowAnon
If supplied, the Route will allow anonymous access for non-authenticated users.

.PARAMETER Login
If supplied, the Route will be flagged to Authentication as being a Route that handles user logins.

.PARAMETER Logout
If supplied, the Route will be flagged to Authentication as being a Route that handles users logging out.

.PARAMETER PassThru
If supplied, the route created will be returned so it can be passed through a pipe.

.PARAMETER IfExists
Specifies what action to take when a Route already exists. (Default: Default)

.PARAMETER Role
One or more optional Roles that will be authorised to access this Route, when using Authentication with an Access method.

.PARAMETER Group
One or more optional Groups that will be authorised to access this Route, when using Authentication with an Access method.

.PARAMETER Scope
One or more optional Scopes that will be authorised to access this Route, when using Authentication with an Access method.

.PARAMETER User
One or more optional Users that will be authorised to access this Route, when using Authentication with an Access method.

.PARAMETER OAResponses
An alternative way to associate OpenApi responses unsing New-PodeOAResponse instead of piping multiple Add-PodeOAResponse

.PARAMETER OAReference
A reference to OpenAPI reusable pathItem component created with Add-PodeOAComponentPathItem

.PARAMETER OADefinitionTag
An Array of strings representing the unique tag for the API specification.
This tag helps in distinguishing between different versions or types of API specifications within the application.
You can use this tag to reference the specific API documentation, schema, or version that your function interacts with.

.EXAMPLE
Add-PodeRoute -Method Get -Path '/' -ScriptBlock { /* logic */ }

.EXAMPLE
Add-PodeRoute -Method Post -Path '/users/:userId/message' -Middleware (Get-PodeCsrfMiddleware) -ScriptBlock { /* logic */ }

.EXAMPLE
Add-PodeRoute -Method Post -Path '/user' -ContentType 'application/json' -ScriptBlock { /* logic */ }

.EXAMPLE
Add-PodeRoute -Method Post -Path '/user' -ContentType 'application/json' -TransferEncoding gzip -ScriptBlock { /* logic */ }

.EXAMPLE
Add-PodeRoute -Method Get -Path '/api/cpu' -ErrorContentType 'application/json' -ScriptBlock { /* logic */ }

.EXAMPLE
Add-PodeRoute -Method Get -Path '/' -ScriptBlock { /* logic */ } -ArgumentList 'arg1', 'arg2'

.EXAMPLE
Add-PodeRoute -Method Get -Path '/' -Role 'Developer', 'QA' -ScriptBlock { /* logic */ }

.EXAMPLE
$Responses = New-PodeOAResponse -StatusCode 400 -Description 'Invalid username supplied' |
            New-PodeOAResponse -StatusCode 404 -Description 'User not found' |
            New-PodeOAResponse -StatusCode 405 -Description 'Invalid Input'

Add-PodeRoute -PassThru -Method Put -Path '/user/:username' -OAResponses $Responses -ScriptBlock {
            #code is going here
        }
#>
function Add-PodeRoute {
    [CmdletBinding(DefaultParameterSetName = 'Script')]
    [OutputType([System.Object[]])]
    param(
        [Parameter(Mandatory = $true)]
        [ValidateSet('Connect', 'Delete', 'Get', 'Head', 'Merge', 'Options', 'Patch', 'Post', 'Put', 'Trace', '*')]
        [string[]]
        $Method,

        [Parameter(Mandatory = $true)]
        [string]
        $Path,

        [Parameter()]
        [object[]]
        $Middleware,

        [Parameter(ParameterSetName = 'Script')]
        [scriptblock]
        $ScriptBlock,

        [Parameter( )]
        [AllowNull()]
        [string[]]
        $EndpointName,

        [Parameter()]
        [string]
        $ContentType,

        [Parameter()]
        [ValidateSet('', 'gzip', 'deflate')]
        [string]
        $TransferEncoding,

        [Parameter()]
        [string]
        $ErrorContentType,

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

        [Parameter()]
        [object[]]
        $ArgumentList,

        [Parameter()]
        [Alias('Auth')]
        [string]
        $Authentication,

        [Parameter()]
        [string]
        $Access,

        [Parameter()]
        [ValidateSet('Default', 'Error', 'Overwrite', 'Skip')]
        [string]
        $IfExists = 'Default',

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

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

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

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

        [switch]
        $AllowAnon,

        [switch]
        $Login,

        [switch]
        $Logout,

        [hashtable]
        $OAResponses,

        [string]
        $OAReference,

        [switch]
        $PassThru,

        [string[]]
        $OADefinitionTag
    )

    # check if we have any route group info defined
    if ($null -ne $RouteGroup) {
        if (![string]::IsNullOrWhiteSpace($RouteGroup.Path)) {
            $Path = "$($RouteGroup.Path)$($Path)"
        }

        if ($null -ne $RouteGroup.Middleware) {
            $Middleware = $RouteGroup.Middleware + $Middleware
        }

        if ([string]::IsNullOrWhiteSpace($EndpointName)) {
            $EndpointName = $RouteGroup.EndpointName
        }

        if ([string]::IsNullOrWhiteSpace($ContentType)) {
            $ContentType = $RouteGroup.ContentType
        }

        if ([string]::IsNullOrWhiteSpace($TransferEncoding)) {
            $TransferEncoding = $RouteGroup.TransferEncoding
        }

        if ([string]::IsNullOrWhiteSpace($ErrorContentType)) {
            $ErrorContentType = $RouteGroup.ErrorContentType
        }

        if ([string]::IsNullOrWhiteSpace($Authentication)) {
            $Authentication = $RouteGroup.Authentication
        }

        if ([string]::IsNullOrWhiteSpace($Access)) {
            $Access = $RouteGroup.Access
        }

        if ($RouteGroup.AllowAnon) {
            $AllowAnon = $RouteGroup.AllowAnon
        }

        if ($RouteGroup.IfExists -ine 'default') {
            $IfExists = $RouteGroup.IfExists
        }

        if ($null -ne $RouteGroup.AccessMeta.Role) {
            $Role = $RouteGroup.AccessMeta.Role + $Role
        }

        if ($null -ne $RouteGroup.AccessMeta.Group) {
            $Group = $RouteGroup.AccessMeta.Group + $Group
        }

        if ($null -ne $RouteGroup.AccessMeta.Scope) {
            $Scope = $RouteGroup.AccessMeta.Scope + $Scope
        }

        if ($null -ne $RouteGroup.AccessMeta.User) {
            $User = $RouteGroup.AccessMeta.User + $User
        }

        if ($null -ne $RouteGroup.AccessMeta.Custom) {
            $CustomAccess = $RouteGroup.AccessMeta.Custom
        }

        if ($null -ne $RouteGroup.OADefinitionTag ) {
            $OADefinitionTag = $RouteGroup.OADefinitionTag
        }
    }

    # var for new routes created
    $newRoutes = @()

    # store the original path
    $origPath = $Path

    # split route on '?' for query
    $Path = Split-PodeRouteQuery -Path $Path
    if ([string]::IsNullOrWhiteSpace($Path)) {
        # No Path supplied for the Route
        throw ($PodeLocale.noPathSuppliedForRouteExceptionMessage)
    }

    # ensure the route has appropriate slashes
    $Path = Update-PodeRouteSlash -Path $Path
    $OpenApiPath = ConvertTo-PodeOpenApiRoutePath -Path $Path
    $Path = Resolve-PodePlaceholder -Path $Path

    # get endpoints from name
    $endpoints = Find-PodeEndpoint -EndpointName $EndpointName

    # get default route IfExists state
    if ($IfExists -ieq 'Default') {
        $IfExists = Get-PodeRouteIfExistsPreference
    }

    # if middleware, scriptblock and file path are all null/empty, error
    if ((Test-PodeIsEmpty $Middleware) -and (Test-PodeIsEmpty $ScriptBlock) -and (Test-PodeIsEmpty $FilePath) -and (Test-PodeIsEmpty $Authentication)) {
        # [Method] Path: No logic passed
        throw ($PodeLocale.noLogicPassedForMethodRouteExceptionMessage -f ($Method -join ','), $Path)
    }

    # if we have a file path supplied, load that path as a scriptblock
    if ($PSCmdlet.ParameterSetName -ieq 'file') {
        $ScriptBlock = Convert-PodeFileToScriptBlock -FilePath $FilePath
    }

    # check for scoped vars
    $ScriptBlock, $usingVars = Convert-PodeScopedVariables -ScriptBlock $ScriptBlock -PSSession $PSCmdlet.SessionState

    # convert any middleware into valid hashtables
    $Middleware = @(ConvertTo-PodeMiddleware -Middleware $Middleware -PSSession $PSCmdlet.SessionState)

    # if an access name was supplied, setup access as middleware first to it's after auth middleware
    if (![string]::IsNullOrWhiteSpace($Access)) {
        if ([string]::IsNullOrWhiteSpace($Authentication)) {
            # Access requires Authentication to be supplied on Routes
            throw ($PodeLocale.accessRequiresAuthenticationOnRoutesExceptionMessage)
        }

        if (!(Test-PodeAccessExists -Name $Access)) {
            # Access method does not exist
            throw ($PodeLocale.accessMethodDoesNotExistExceptionMessage -f $Access)
        }

        $options = @{
            Name = $Access
        }

        $Middleware = (@(Get-PodeAccessMiddlewareScript | New-PodeMiddleware -ArgumentList $options) + $Middleware)
    }

    # if an auth name was supplied, setup the auth as the first middleware
    if (![string]::IsNullOrWhiteSpace($Authentication)) {
        if (!(Test-PodeAuthExists -Name $Authentication)) {
            # Authentication method does not exist
            throw ($PodeLocale.authenticationMethodDoesNotExistExceptionMessage -f $Authentication)
        }

        $options = @{
            Name   = $Authentication
            Login  = $Login
            Logout = $Logout
            Anon   = $AllowAnon
        }

        $Middleware = (@(Get-PodeAuthMiddlewareScript | New-PodeMiddleware -ArgumentList $options) + $Middleware)
    }

    # custom access
    if ($null -eq $CustomAccess) {
        $CustomAccess = @{}
    }

    # workout a default content type for the route
    $ContentType = Find-PodeRouteContentType -Path $Path -ContentType $ContentType

    # workout a default transfer encoding for the route
    $TransferEncoding = Find-PodeRouteTransferEncoding -Path $Path -TransferEncoding $TransferEncoding

    # loop through each method
    foreach ($_method in $Method) {
        # ensure the route doesn't already exist for each endpoint
        $endpoints = @(foreach ($_endpoint in $endpoints) {
                $found = Test-PodeRouteInternal -Method $_method -Path $Path -Protocol $_endpoint.Protocol -Address $_endpoint.Address -ThrowError:($IfExists -ieq 'Error')

                if ($found) {
                    if ($IfExists -ieq 'Overwrite') {
                        Remove-PodeRoute -Method $_method -Path $origPath -EndpointName $_endpoint.Name
                    }

                    if ($IfExists -ieq 'Skip') {
                        continue
                    }
                }

                $_endpoint
            })

        if (($null -eq $endpoints) -or ($endpoints.Length -eq 0)) {
            continue
        }

        #add security header method if autoMethods is enabled
        if (  $PodeContext.Server.Security.autoMethods ) {
            Add-PodeSecurityHeader -Name 'Access-Control-Allow-Methods' -Value $_method.ToUpper() -Append
        }

        $DefinitionTag = Test-PodeOADefinitionTag -Tag $OADefinitionTag

        #add the default OpenApi responses
        if ( $PodeContext.Server.OpenAPI.Definitions[$DefinitionTag].hiddenComponents.defaultResponses) {
            $DefaultResponse = [ordered]@{}
            foreach ($tag in $DefinitionTag) {
                $DefaultResponse[$tag] = Copy-PodeObjectDeepClone -InputObject $PodeContext.Server.OpenAPI.Definitions[$tag].hiddenComponents.defaultResponses
            }
        }

        # add the route(s)
        Write-Verbose "Adding Route: [$($_method)] $($Path)"
        $methodRoutes = @(foreach ($_endpoint in $endpoints) {
                @{
                    Logic            = $ScriptBlock
                    UsingVariables   = $usingVars
                    Middleware       = $Middleware
                    Authentication   = $Authentication
                    Access           = $Access
                    AccessMeta       = @{
                        Role   = $Role
                        Group  = $Group
                        Scope  = $Scope
                        User   = $User
                        Custom = $CustomAccess
                    }
                    Endpoint         = @{
                        Protocol = $_endpoint.Protocol
                        Address  = $_endpoint.Address.Trim()
                        Name     = $_endpoint.Name
                    }
                    ContentType      = $ContentType
                    TransferEncoding = $TransferEncoding
                    ErrorType        = $ErrorContentType
                    Arguments        = $ArgumentList
                    Method           = $_method
                    Path             = $Path
                    OpenApi          = @{
                        Path               = $OpenApiPath
                        Responses          = $DefaultResponse
                        Parameters         = $null
                        RequestBody        = $null
                        CallBacks          = @{}
                        Authentication     = @()
                        Servers            = @()
                        DefinitionTag      = $DefinitionTag
                        IsDefTagConfigured = ($null -ne $OADefinitionTag) #Definition Tag has been configured (Not default)
                    }
                    IsStatic         = $false
                    Metrics          = @{
                        Requests = @{
                            Total       = 0
                            StatusCodes = @{}
                        }
                    }
                }
            })

        if (![string]::IsNullOrWhiteSpace($Authentication)) {
            Set-PodeOAAuth -Route $methodRoutes -Name $Authentication -AllowAnon:$AllowAnon
        }

        $PodeContext.Server.Routes[$_method][$Path] += @($methodRoutes)
        if ($PassThru) {
            $newRoutes += $methodRoutes
        }
    }
    if ($OAReference) {
        Test-PodeOAComponentInternal -Field pathItems -DefinitionTag $DefinitionTag -Name $OAReference -PostValidation
        foreach ($r in @($newRoutes)) {
            $r.OpenApi = @{
                '$ref'        = "#/components/paths/$OAReference"
                DefinitionTag = $DefinitionTag
                Path          = $OpenApiPath
            }
        }
    }
    elseif ($OAResponses) {
        foreach ($r in @($newRoutes)) {
            $r.OpenApi.Responses = $OAResponses
        }
    }

    # return the routes?
    if ($PassThru) {
        return $newRoutes
    }
}

<#
.SYNOPSIS
Add a static Route for rendering static content.

.DESCRIPTION
Add a static Route for rendering static content. You can also define default pages to display.

.PARAMETER Path
The URI path for the static Route.

.PARAMETER Source
The literal, or relative, path to the directory that contains the static content.

.PARAMETER Middleware
An array of ScriptBlocks for optional Middleware.

.PARAMETER EndpointName
The EndpointName of an Endpoint(s) to bind the static Route against.

.PARAMETER ContentType
The content type the static Route should use when parsing any payloads.

.PARAMETER TransferEncoding
The transfer encoding the static Route should use when parsing any payloads.

.PARAMETER Defaults
An array of default pages to display, such as 'index.html'.

.PARAMETER ErrorContentType
The content type of any error pages that may get returned.

.PARAMETER Authentication
The name of an Authentication method which should be used as middleware on this Route.

.PARAMETER Access
The name of an Access method which should be used as middleware on this Route.

.PARAMETER AllowAnon
If supplied, the static route will allow anonymous access for non-authenticated users.

.PARAMETER DownloadOnly
When supplied, all static content on this Route will be attached as downloads - rather than rendered.

.PARAMETER PassThru
If supplied, the static route created will be returned so it can be passed through a pipe.

.PARAMETER IfExists
Specifies what action to take when a Static Route already exists. (Default: Default)

.PARAMETER Role
One or more optional Roles that will be authorised to access this Route, when using Authentication with an Access method.

.PARAMETER Group
One or more optional Groups that will be authorised to access this Route, when using Authentication with an Access method.

.PARAMETER Scope
One or more optional Scopes that will be authorised to access this Route, when using Authentication with an Access method.

.PARAMETER User
One or more optional Users that will be authorised to access this Route, when using Authentication with an Access method.

.PARAMETER FileBrowser
If supplied, when the path is a folder, instead of returning 404, will return A browsable content of the directory.

.PARAMETER RedirectToDefault
If supplied, the user will be redirected to the default page if found instead of the page being rendered as the folder path.

.EXAMPLE
Add-PodeStaticRoute -Path '/assets' -Source './assets'

.EXAMPLE
Add-PodeStaticRoute -Path '/assets' -Source './assets' -Defaults @('index.html')

.EXAMPLE
Add-PodeStaticRoute -Path '/installers' -Source './exes' -DownloadOnly

.EXAMPLE
Add-PodeStaticRoute -Path '/assets' -Source './assets' -Defaults @('index.html') -RedirectToDefault
#>
function Add-PodeStaticRoute {
    [CmdletBinding()]
    [OutputType([System.Object[]])]
    param(
        [Parameter(Mandatory = $true)]
        [string]
        $Path,

        [Parameter(Mandatory = $true)]
        [string]
        $Source,

        [Parameter()]
        [object[]]
        $Middleware,

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

        [Parameter()]
        [string]
        $ContentType,

        [Parameter()]
        [ValidateSet('', 'gzip', 'deflate')]
        [string]
        $TransferEncoding,

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

        [Parameter()]
        [string]
        $ErrorContentType,

        [Parameter()]
        [Alias('Auth')]
        [string]
        $Authentication,

        [Parameter()]
        [string]
        $Access,

        [Parameter()]
        [ValidateSet('Default', 'Error', 'Overwrite', 'Skip')]
        [string]
        $IfExists = 'Default',

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

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

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

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

        [switch]
        $AllowAnon,

        [switch]
        $DownloadOnly,

        [switch]
        $FileBrowser,

        [switch]
        $PassThru,

        [switch]
        $RedirectToDefault
    )

    # check if we have any route group info defined
    if ($null -ne $RouteGroup) {
        if (![string]::IsNullOrWhiteSpace($RouteGroup.Path)) {
            $Path = "$($RouteGroup.Path)$($Path)"
        }

        if (![string]::IsNullOrWhiteSpace($RouteGroup.Source)) {
            $Source = [System.IO.Path]::Combine($Source, $RouteGroup.Source.TrimStart('\/'))
        }

        if ($null -ne $RouteGroup.Middleware) {
            $Middleware = $RouteGroup.Middleware + $Middleware
        }

        if ([string]::IsNullOrWhiteSpace($EndpointName)) {
            $EndpointName = $RouteGroup.EndpointName
        }

        if ([string]::IsNullOrWhiteSpace($ContentType)) {
            $ContentType = $RouteGroup.ContentType
        }

        if ([string]::IsNullOrWhiteSpace($TransferEncoding)) {
            $TransferEncoding = $RouteGroup.TransferEncoding
        }

        if ([string]::IsNullOrWhiteSpace($ErrorContentType)) {
            $ErrorContentType = $RouteGroup.ErrorContentType
        }

        if ([string]::IsNullOrWhiteSpace($Authentication)) {
            $Authentication = $RouteGroup.Authentication
        }

        if ([string]::IsNullOrWhiteSpace($Access)) {
            $Access = $RouteGroup.Access
        }

        if (Test-PodeIsEmpty $Defaults) {
            $Defaults = $RouteGroup.Defaults
        }

        if ($RouteGroup.AllowAnon) {
            $AllowAnon = $RouteGroup.AllowAnon
        }

        if ($RouteGroup.DownloadOnly) {
            $DownloadOnly = $RouteGroup.DownloadOnly
        }

        if ($RouteGroup.FileBrowser) {
            $FileBrowser = $RouteGroup.FileBrowser
        }

        if ($RouteGroup.RedirectToDefault) {
            $RedirectToDefault = $RouteGroup.RedirectToDefault
        }

        if ($RouteGroup.IfExists -ine 'default') {
            $IfExists = $RouteGroup.IfExists
        }

        if ($null -ne $RouteGroup.AccessMeta.Role) {
            $Role = $RouteGroup.AccessMeta.Role + $Role
        }

        if ($null -ne $RouteGroup.AccessMeta.Group) {
            $Group = $RouteGroup.AccessMeta.Group + $Group
        }

        if ($null -ne $RouteGroup.AccessMeta.Scope) {
            $Scope = $RouteGroup.AccessMeta.Scope + $Scope
        }

        if ($null -ne $RouteGroup.AccessMeta.User) {
            $User = $RouteGroup.AccessMeta.User + $User
        }

        if ($null -ne $RouteGroup.AccessMeta.Custom) {
            $CustomAccess = $RouteGroup.AccessMeta.Custom
        }
    }

    # store the route method
    $Method = 'Static'

    # store the original path
    $origPath = $Path

    # split route on '?' for query
    $Path = Split-PodeRouteQuery -Path $Path
    if ([string]::IsNullOrWhiteSpace($Path)) {
        # No Path supplied for the Route.
        throw ($PodeLocale.noPathSuppliedForRouteExceptionMessage)
    }

    # ensure the route has appropriate slashes
    $Path = Update-PodeRouteSlash -Path $Path -Static
    $OpenApiPath = ConvertTo-PodeOpenApiRoutePath -Path $Path
    $Path = Resolve-PodePlaceholder -Path $Path

    # get endpoints from name
    $endpoints = Find-PodeEndpoint -EndpointName $EndpointName

    # get default route IfExists state
    if ($IfExists -ieq 'Default') {
        $IfExists = Get-PodeRouteIfExistsPreference
    }

    # ensure the route doesn't already exist for each endpoint
    $endpoints = @(foreach ($_endpoint in $endpoints) {
            $found = Test-PodeRouteInternal -Method $Method -Path $Path -Protocol $_endpoint.Protocol -Address $_endpoint.Address -ThrowError:($IfExists -ieq 'Error')

            if ($found) {
                if ($IfExists -ieq 'Overwrite') {
                    Remove-PodeStaticRoute -Path $origPath -EndpointName $_endpoint.Name
                }

                if ($IfExists -ieq 'Skip') {
                    continue
                }
            }

            $_endpoint
        })

    if (($null -eq $endpoints) -or ($endpoints.Length -eq 0)) {
        return
    }

    # if static, ensure the path exists at server root
    $Source = Get-PodeRelativePath -Path $Source -JoinRoot
    if (!(Test-PodePath -Path $Source -NoStatus)) {
        # [Method)] Path: The Source path supplied for Static Route does not exist
        throw ($PodeLocale.sourcePathDoesNotExistForStaticRouteExceptionMessage -f $Path, $Source)
    }

    # setup a temp drive for the path
    $Source = New-PodePSDrive -Path $Source

    # setup default static files
    if ($null -eq $Defaults) {
        $Defaults = Get-PodeStaticRouteDefault
    }

    if (!$RedirectToDefault) {
        $RedirectToDefault = $PodeContext.Server.Web.Static.RedirectToDefault
    }

    # convert any middleware into valid hashtables
    $Middleware = @(ConvertTo-PodeMiddleware -Middleware $Middleware -PSSession $PSCmdlet.SessionState)

    # if an access name was supplied, setup access as middleware first to it's after auth middleware
    if (![string]::IsNullOrWhiteSpace($Access)) {
        if ([string]::IsNullOrWhiteSpace($Authentication)) {
            # Access requires Authentication to be supplied on Routes
            throw ($PodeLocale.accessRequiresAuthenticationOnRoutesExceptionMessage)
        }

        if (!(Test-PodeAccessExists -Name $Access)) {
            # Access method does not exist
            throw ($PodeLocale.accessMethodDoesNotExistExceptionMessage -f $Access)
        }

        $options = @{
            Name = $Access
        }

        $Middleware = (@(Get-PodeAccessMiddlewareScript | New-PodeMiddleware -ArgumentList $options) + $Middleware)
    }

    # if an auth name was supplied, setup the auth as the first middleware
    if (![string]::IsNullOrWhiteSpace($Authentication)) {
        if (!(Test-PodeAuthExists -Name $Authentication)) {
            # Authentication method does not exist
            throw ($PodeLocale.authenticationMethodDoesNotExistExceptionMessage)
        }

        $options = @{
            Name = $Authentication
            Anon = $AllowAnon
        }

        $Middleware = (@(Get-PodeAuthMiddlewareScript | New-PodeMiddleware -ArgumentList $options) + $Middleware)
    }

    # workout a default content type for the route
    $ContentType = Find-PodeRouteContentType -Path $Path -ContentType $ContentType

    # workout a default transfer encoding for the route
    $TransferEncoding = Find-PodeRouteTransferEncoding -Path $Path -TransferEncoding $TransferEncoding

    #The path use KleeneStar(Asterisk)
    $KleeneStar = $OrigPath.Contains('*')

    # add the route(s)
    Write-Verbose "Adding Route: [$($Method)] $($Path)"
    $newRoutes = @(foreach ($_endpoint in $endpoints) {
            @{
                Source            = $Source
                Path              = $Path
                KleeneStar        = $KleeneStar
                Method            = $Method
                Defaults          = $Defaults
                RedirectToDefault = $RedirectToDefault
                Middleware        = $Middleware
                Authentication    = $Authentication
                Access            = $Access
                AccessMeta        = @{
                    Role   = $Role
                    Group  = $Group
                    Scope  = $Scope
                    User   = $User
                    Custom = $CustomAccess
                }
                Endpoint          = @{
                    Protocol = $_endpoint.Protocol
                    Address  = $_endpoint.Address.Trim()
                    Name     = $_endpoint.Name
                }
                ContentType       = $ContentType
                TransferEncoding  = $TransferEncoding
                ErrorType         = $ErrorContentType
                Download          = $DownloadOnly
                IsStatic          = $true
                FileBrowser       = $FileBrowser.isPresent
                OpenApi           = @{
                    Path           = $OpenApiPath
                    Responses      = @{}
                    Parameters     = $null
                    RequestBody    = $null
                    CallBacks      = @{}
                    Authentication = @()
                    Servers        = @()
                    DefinitionTag  = $DefinitionTag
                }
                Metrics           = @{
                    Requests = @{
                        Total       = 0
                        StatusCodes = @{}
                    }
                }
            }
        })

    $PodeContext.Server.Routes[$Method][$Path] += @($newRoutes)

    # return the routes?
    if ($PassThru) {
        return $newRoutes
    }
}

<#
.SYNOPSIS
Adds a Signal Route for WebSockets.

.DESCRIPTION
Adds a Signal Route, with path, that when called with invoke any logic.

.PARAMETER Path
The URI path for the Signal Route.

.PARAMETER ScriptBlock
A ScriptBlock for the Signal Route's main logic.

.PARAMETER EndpointName
The EndpointName of an Endpoint(s) this Signal Route should be bound against.

.PARAMETER FilePath
A literal, or relative, path to a file containing a ScriptBlock for the Signal Route's main logic.

.PARAMETER ArgumentList
An array of arguments to supply to the Signal Route's ScriptBlock.

.PARAMETER IfExists
Specifies what action to take when a Signal Route already exists. (Default: Default)

.EXAMPLE
Add-PodeSignalRoute -Path '/message' -ScriptBlock { /* logic */ }

.EXAMPLE
Add-PodeSignalRoute -Path '/message' -ScriptBlock { /* logic */ } -ArgumentList 'arg1', 'arg2'
#>
function Add-PodeSignalRoute {
    [CmdletBinding(DefaultParameterSetName = 'Script')]
    [OutputType([System.Object[]])]
    param(
        [Parameter(Mandatory = $true)]
        [string]
        $Path,

        [Parameter(ParameterSetName = 'Script')]
        [scriptblock]
        $ScriptBlock,

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

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

        [Parameter()]
        [object[]]
        $ArgumentList,

        [Parameter()]
        [ValidateSet('Default', 'Error', 'Overwrite', 'Skip')]
        [string]
        $IfExists = 'Default'
    )

    # check if we have any route group info defined
    if ($null -ne $RouteGroup) {
        if (![string]::IsNullOrWhiteSpace($RouteGroup.Path)) {
            $Path = "$($RouteGroup.Path)$($Path)"
        }

        if ([string]::IsNullOrWhiteSpace($EndpointName)) {
            $EndpointName = $RouteGroup.EndpointName
        }

        if ($RouteGroup.IfExists -ine 'default') {
            $IfExists = $RouteGroup.IfExists
        }
    }

    $Method = 'Signal'

    # store the original path
    $origPath = $Path

    # ensure the route has appropriate slashes
    $Path = Update-PodeRouteSlash -Path $Path

    # get endpoints from name
    $endpoints = Find-PodeEndpoint -EndpointName $EndpointName

    # get default route IfExists state
    if ($IfExists -ieq 'Default') {
        $IfExists = Get-PodeRouteIfExistsPreference
    }

    # ensure the route doesn't already exist for each endpoint
    $endpoints = @(foreach ($_endpoint in $endpoints) {
            $found = Test-PodeRouteInternal -Method $Method -Path $Path -Protocol $_endpoint.Protocol -Address $_endpoint.Address -ThrowError:($IfExists -ieq 'Error')

            if ($found) {
                if ($IfExists -ieq 'Overwrite') {
                    Remove-PodeSignalRoute -Path $origPath -EndpointName $_endpoint.Name
                }

                if ($IfExists -ieq 'Skip') {
                    continue
                }
            }

            $_endpoint
        })

    if (($null -eq $endpoints) -or ($endpoints.Length -eq 0)) {
        return
    }

    # if scriptblock and file path are all null/empty, error
    if ((Test-PodeIsEmpty $ScriptBlock) -and (Test-PodeIsEmpty $FilePath)) {
        # [Method] Path: No logic passed
        throw ($PodeLocale.noLogicPassedForMethodRouteExceptionMessage -f $Method, $Path)
    }

    # if we have a file path supplied, load that path as a scriptblock
    if ($PSCmdlet.ParameterSetName -ieq 'file') {
        $ScriptBlock = Convert-PodeFileToScriptBlock -FilePath $FilePath
    }

    # check for scoped vars
    $ScriptBlock, $usingVars = Convert-PodeScopedVariables -ScriptBlock $ScriptBlock -PSSession $PSCmdlet.SessionState

    # add the route(s)
    Write-Verbose "Adding Route: [$($Method)] $($Path)"
    $newRoutes = @(foreach ($_endpoint in $endpoints) {
            @{
                Logic          = $ScriptBlock
                UsingVariables = $usingVars
                Endpoint       = @{
                    Protocol = $_endpoint.Protocol
                    Address  = $_endpoint.Address.Trim()
                    Name     = $_endpoint.Name
                }
                Arguments      = $ArgumentList
                Method         = $Method
                Path           = $Path
                IsStatic       = $false
                Metrics        = @{
                    Requests = @{
                        Total = 0
                    }
                }
            }
        })

    $PodeContext.Server.Routes[$Method][$Path] += @($newRoutes)
}

<#
.SYNOPSIS
Add a Route Group for multiple Routes.

.DESCRIPTION
Add a Route Group for sharing values between multiple Routes.

.PARAMETER Path
The URI path to use as a base for the Routes, that should be prepended.

.PARAMETER Routes
A ScriptBlock for adding Routes.

.PARAMETER Middleware
An array of ScriptBlocks for optional Middleware to give each Route.

.PARAMETER EndpointName
The EndpointName of an Endpoint(s) to use for the Routes.

.PARAMETER ContentType
The content type to use for the Routes, when parsing any payloads.

.PARAMETER TransferEncoding
The transfer encoding to use for the Routes, when parsing any payloads.

.PARAMETER ErrorContentType
The content type of any error pages that may get returned.

.PARAMETER Authentication
The name of an Authentication method which should be used as middleware on the Routes.

.PARAMETER Access
The name of an Access method which should be used as middleware on this Route.

.PARAMETER IfExists
Specifies what action to take when a Route already exists. (Default: Default)

.PARAMETER Role
One or more optional Roles that will be authorised to access this Route, when using Authentication with an Access method.

.PARAMETER Group
One or more optional Groups that will be authorised to access this Route, when using Authentication with an Access method.

.PARAMETER Scope
One or more optional Scopes that will be authorised to access this Route, when using Authentication with an Access method.

.PARAMETER User
One or more optional Users that will be authorised to access this Route, when using Authentication with an Access method.

.PARAMETER AllowAnon
If supplied, the Routes will allow anonymous access for non-authenticated users.

.PARAMETER OADefinitionTag
An Array of strings representing the unique tag for the API specification.
This tag helps in distinguishing between different versions or types of API specifications within the application.
You can use this tag to reference the specific API documentation, schema, or version that your function interacts with.

.EXAMPLE
Add-PodeRouteGroup -Path '/api' -Routes { Add-PodeRoute -Path '/route1' -Etc }
#>
function Add-PodeRouteGroup {
    [CmdletBinding()]
    param(
        [Parameter()]
        [string]
        $Path,

        [Parameter(Mandatory = $true)]
        [scriptblock]
        $Routes,

        [Parameter()]
        [object[]]
        $Middleware,

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

        [Parameter()]
        [string]
        $ContentType,

        [Parameter()]
        [ValidateSet('', 'gzip', 'deflate')]
        [string]
        $TransferEncoding,

        [Parameter()]
        [string]
        $ErrorContentType,

        [Parameter()]
        [Alias('Auth')]
        [string]
        $Authentication,

        [Parameter()]
        [string]
        $Access,

        [Parameter()]
        [ValidateSet('Default', 'Error', 'Overwrite', 'Skip')]
        [string]
        $IfExists = 'Default',

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

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

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

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

        [switch]
        $AllowAnon,

        [string[]]
        $OADefinitionTag
    )

    if (Test-PodeIsEmpty $Routes) {
        # The Route parameter needs a valid, not empty, scriptblock
        throw ($PodeLocale.routeParameterNeedsValidScriptblockExceptionMessage)
    }

    if ($Path -eq '/') {
        $Path = $null
    }

    # check for scoped vars
    $Routes, $usingVars = Convert-PodeScopedVariables -ScriptBlock $Routes -PSSession $PSCmdlet.SessionState

    # group details
    if ($null -ne $RouteGroup) {
        if (![string]::IsNullOrWhiteSpace($RouteGroup.Path)) {
            $Path = "$($RouteGroup.Path)$($Path)"
        }

        if ($null -ne $RouteGroup.Middleware) {
            $Middleware = $RouteGroup.Middleware + $Middleware
        }

        if ([string]::IsNullOrWhiteSpace($EndpointName)) {
            $EndpointName = $RouteGroup.EndpointName
        }

        if ([string]::IsNullOrWhiteSpace($ContentType)) {
            $ContentType = $RouteGroup.ContentType
        }

        if ([string]::IsNullOrWhiteSpace($TransferEncoding)) {
            $TransferEncoding = $RouteGroup.TransferEncoding
        }

        if ([string]::IsNullOrWhiteSpace($ErrorContentType)) {
            $ErrorContentType = $RouteGroup.ErrorContentType
        }

        if ([string]::IsNullOrWhiteSpace($Authentication)) {
            $Authentication = $RouteGroup.Authentication
        }

        if ([string]::IsNullOrWhiteSpace($Access)) {
            $Access = $RouteGroup.Access
        }

        if ($RouteGroup.AllowAnon) {
            $AllowAnon = $RouteGroup.AllowAnon
        }

        if ($RouteGroup.IfExists -ine 'default') {
            $IfExists = $RouteGroup.IfExists
        }

        if ($null -ne $RouteGroup.AccessMeta.Role) {
            $Role = $RouteGroup.AccessMeta.Role + $Role
        }

        if ($null -ne $RouteGroup.AccessMeta.Group) {
            $Group = $RouteGroup.AccessMeta.Group + $Group
        }

        if ($null -ne $RouteGroup.AccessMeta.Scope) {
            $Scope = $RouteGroup.AccessMeta.Scope + $Scope
        }

        if ($null -ne $RouteGroup.AccessMeta.User) {
            $User = $RouteGroup.AccessMeta.User + $User
        }

        if ($null -ne $RouteGroup.AccessMeta.Custom) {
            $CustomAccess = $RouteGroup.AccessMeta.Custom
        }

        if ($null -ne $RouteGroup.OADefinitionTag ) {
            $OADefinitionTag = $RouteGroup.OADefinitionTag
        }

    }

    $RouteGroup = @{
        Path             = $Path
        Middleware       = $Middleware
        EndpointName     = $EndpointName
        ContentType      = $ContentType
        TransferEncoding = $TransferEncoding
        ErrorContentType = $ErrorContentType
        Authentication   = $Authentication
        Access           = $Access
        AllowAnon        = $AllowAnon
        IfExists         = $IfExists
        OADefinitionTag  = $OADefinitionTag
        AccessMeta       = @{
            Role   = $Role
            Group  = $Group
            Scope  = $Scope
            User   = $User
            Custom = $CustomAccess
        }
    }

    # add routes
    $null = Invoke-PodeScriptBlock -ScriptBlock $Routes -UsingVariables $usingVars -Splat -NoNewClosure
}

<#
.SYNOPSIS
Add a Static Route Group for multiple Static Routes.

.DESCRIPTION
Add a Static Route Group for sharing values between multiple Static Routes.

.PARAMETER Path
The URI path to use as a base for the Static Routes.

.PARAMETER Source
A literal, or relative, base path to the directory that contains the static content, that should be prepended.

.PARAMETER Routes
A ScriptBlock for adding Static Routes.

.PARAMETER Middleware
An array of ScriptBlocks for optional Middleware to give each Static Route.

.PARAMETER EndpointName
The EndpointName of an Endpoint(s) to use for the Static Routes.

.PARAMETER ContentType
The content type to use for the Static Routes, when parsing any payloads.

.PARAMETER TransferEncoding
The transfer encoding to use for the Static Routes, when parsing any payloads.

.PARAMETER Defaults
An array of default pages to display, such as 'index.html', for each Static Route.

.PARAMETER ErrorContentType
The content type of any error pages that may get returned.

.PARAMETER Authentication
The name of an Authentication method which should be used as middleware on the Static Routes.

.PARAMETER Access
The name of an Access method which should be used as middleware on this Route.

.PARAMETER IfExists
Specifies what action to take when a Static Route already exists. (Default: Default)

.PARAMETER AllowAnon
If supplied, the Static Routes will allow anonymous access for non-authenticated users.

.PARAMETER FileBrowser
When supplied, If the path is a folder, instead of returning 404, will return A browsable content of the directory.

.PARAMETER DownloadOnly
When supplied, all static content on the Routes will be attached as downloads - rather than rendered.

.PARAMETER Role
One or more optional Roles that will be authorised to access this Route, when using Authentication with an Access method.

.PARAMETER Group
One or more optional Groups that will be authorised to access this Route, when using Authentication with an Access method.

.PARAMETER Scope
One or more optional Scopes that will be authorised to access this Route, when using Authentication with an Access method.

.PARAMETER User
One or more optional Users that will be authorised to access this Route, when using Authentication with an Access method.

.PARAMETER RedirectToDefault
If supplied, the user will be redirected to the default page if found instead of the page being rendered as the folder path.

.EXAMPLE
Add-PodeStaticRouteGroup -Path '/static' -Routes { Add-PodeStaticRoute -Path '/images' -Etc }
#>
function Add-PodeStaticRouteGroup {
    [CmdletBinding()]
    param(
        [Parameter()]
        [string]
        $Path,

        [Parameter()]
        [string]
        $Source,

        [Parameter(Mandatory = $true)]
        [scriptblock]
        $Routes,

        [Parameter()]
        [object[]]
        $Middleware,

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

        [Parameter()]
        [string]
        $ContentType,

        [Parameter()]
        [ValidateSet('', 'gzip', 'deflate')]
        [string]
        $TransferEncoding,

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

        [Parameter()]
        [string]
        $ErrorContentType,

        [Parameter()]
        [Alias('Auth')]
        [string]
        $Authentication,

        [Parameter()]
        [string]
        $Access,

        [Parameter()]
        [ValidateSet('Default', 'Error', 'Overwrite', 'Skip')]
        [string]
        $IfExists = 'Default',

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

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

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

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

        [switch]
        $AllowAnon,

        [switch]
        $FileBrowser,

        [switch]
        $DownloadOnly,

        [switch]
        $RedirectToDefault
    )

    if (Test-PodeIsEmpty $Routes) {
        # The Route parameter needs a valid, not empty, scriptblock
        throw ($PodeLocale.routeParameterNeedsValidScriptblockExceptionMessage)
    }

    if ($Path -eq '/') {
        $Path = $null
    }

    # check for scoped vars
    $Routes, $usingVars = Convert-PodeScopedVariables -ScriptBlock $Routes -PSSession $PSCmdlet.SessionState

    # group details
    if ($null -ne $RouteGroup) {
        if (![string]::IsNullOrWhiteSpace($RouteGroup.Path)) {
            $Path = "$($RouteGroup.Path)$($Path)"
        }

        if (![string]::IsNullOrWhiteSpace($RouteGroup.Source)) {
            $Source = [System.IO.Path]::Combine($Source, $RouteGroup.Source.TrimStart('\/'))
        }

        if ($null -ne $RouteGroup.Middleware) {
            $Middleware = $RouteGroup.Middleware + $Middleware
        }

        if ([string]::IsNullOrWhiteSpace($EndpointName)) {
            $EndpointName = $RouteGroup.EndpointName
        }

        if ([string]::IsNullOrWhiteSpace($ContentType)) {
            $ContentType = $RouteGroup.ContentType
        }

        if ([string]::IsNullOrWhiteSpace($TransferEncoding)) {
            $TransferEncoding = $RouteGroup.TransferEncoding
        }

        if ([string]::IsNullOrWhiteSpace($ErrorContentType)) {
            $ErrorContentType = $RouteGroup.ErrorContentType
        }

        if ([string]::IsNullOrWhiteSpace($Authentication)) {
            $Authentication = $RouteGroup.Authentication
        }

        if ([string]::IsNullOrWhiteSpace($Access)) {
            $Access = $RouteGroup.Access
        }

        if (Test-PodeIsEmpty $Defaults) {
            $Defaults = $RouteGroup.Defaults
        }

        if ($RouteGroup.AllowAnon) {
            $AllowAnon = $RouteGroup.AllowAnon
        }

        if ($RouteGroup.DownloadOnly) {
            $DownloadOnly = $RouteGroup.DownloadOnly
        }

        if ($RouteGroup.FileBrowser) {
            $FileBrowser = $RouteGroup.FileBrowser
        }

        if ($RouteGroup.RedirectToDefault) {
            $RedirectToDefault = $RouteGroup.RedirectToDefault
        }

        if ($RouteGroup.IfExists -ine 'default') {
            $IfExists = $RouteGroup.IfExists
        }

        if ($null -ne $RouteGroup.AccessMeta.Role) {
            $Role = $RouteGroup.AccessMeta.Role + $Role
        }

        if ($null -ne $RouteGroup.AccessMeta.Group) {
            $Group = $RouteGroup.AccessMeta.Group + $Group
        }

        if ($null -ne $RouteGroup.AccessMeta.Scope) {
            $Scope = $RouteGroup.AccessMeta.Scope + $Scope
        }

        if ($null -ne $RouteGroup.AccessMeta.User) {
            $User = $RouteGroup.AccessMeta.User + $User
        }

        if ($null -ne $RouteGroup.AccessMeta.Custom) {
            $CustomAccess = $RouteGroup.AccessMeta.Custom
        }
    }

    $RouteGroup = @{
        Path              = $Path
        Source            = $Source
        Middleware        = $Middleware
        EndpointName      = $EndpointName
        ContentType       = $ContentType
        TransferEncoding  = $TransferEncoding
        Defaults          = $Defaults
        RedirectToDefault = $RedirectToDefault
        ErrorContentType  = $ErrorContentType
        Authentication    = $Authentication
        Access            = $Access
        AllowAnon         = $AllowAnon
        DownloadOnly      = $DownloadOnly
        FileBrowser       = $FileBrowser
        IfExists          = $IfExists
        AccessMeta        = @{
            Role   = $Role
            Group  = $Group
            Scope  = $Scope
            User   = $User
            Custom = $CustomAccess
        }
    }

    # add routes
    $null = Invoke-PodeScriptBlock -ScriptBlock $Routes -UsingVariables $usingVars -Splat -NoNewClosure
}


<#
.SYNOPSIS
Adds a Signal Route Group for multiple WebSockets.

.DESCRIPTION
Adds a Signal Route Group for sharing values between multiple WebSockets.

.PARAMETER Path
The URI path to use as a base for the Signal Routes, that should be prepended.

.PARAMETER Routes
A ScriptBlock for adding Signal Routes.

.PARAMETER EndpointName
The EndpointName of an Endpoint(s) to use for the Signal Routes.

.PARAMETER IfExists
Specifies what action to take when a Signal Route already exists. (Default: Default)

.EXAMPLE
Add-PodeSignalRouteGroup -Path '/signals' -Routes { Add-PodeSignalRoute -Path '/signal1' -Etc }
#>
function Add-PodeSignalRouteGroup {
    [CmdletBinding()]
    param(
        [Parameter()]
        [string]
        $Path,

        [Parameter(Mandatory = $true)]
        [scriptblock]
        $Routes,

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

        [Parameter()]
        [ValidateSet('Default', 'Error', 'Overwrite', 'Skip')]
        [string]
        $IfExists = 'Default'
    )

    if (Test-PodeIsEmpty $Routes) {
        # The Route parameter needs a valid, not empty, scriptblock
        throw ($PodeLocale.routeParameterNeedsValidScriptblockExceptionMessage)
    }

    if ($Path -eq '/') {
        $Path = $null
    }

    # check for scoped vars
    $Routes, $usingVars = Convert-PodeScopedVariables -ScriptBlock $Routes -PSSession $PSCmdlet.SessionState

    # group details
    if ($null -ne $RouteGroup) {
        if (![string]::IsNullOrWhiteSpace($RouteGroup.Path)) {
            $Path = "$($RouteGroup.Path)$($Path)"
        }

        if ([string]::IsNullOrWhiteSpace($EndpointName)) {
            $EndpointName = $RouteGroup.EndpointName
        }

        if ($RouteGroup.IfExists -ine 'default') {
            $IfExists = $RouteGroup.IfExists
        }
    }

    $RouteGroup = @{
        Path         = $Path
        EndpointName = $EndpointName
        IfExists     = $IfExists
    }

    # add routes
    $null = Invoke-PodeScriptBlock -ScriptBlock $Routes -UsingVariables $usingVars -Splat -NoNewClosure
}

<#
.SYNOPSIS
Remove a specific Route.

.DESCRIPTION
Remove a specific Route.

.PARAMETER Method
The method of the Route to remove.

.PARAMETER Path
The path of the Route to remove.

.PARAMETER EndpointName
The EndpointName of an Endpoint(s) bound to the Route to be removed.

.EXAMPLE
Remove-PodeRoute -Method Get -Route '/about'

.EXAMPLE
Remove-PodeRoute -Method Post -Route '/users/:userId' -EndpointName User
#>
function Remove-PodeRoute {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $true)]
        [ValidateSet('Connect', 'Delete', 'Get', 'Head', 'Merge', 'Options', 'Patch', 'Post', 'Put', 'Trace', '*')]
        [string]
        $Method,

        [Parameter(Mandatory = $true)]
        [string]
        $Path,

        [Parameter()]
        [string]
        $EndpointName
    )

    # split route on '?' for query
    $Path = Split-PodeRouteQuery -Path $Path

    # ensure the route has appropriate slashes and replace parameters
    $Path = Update-PodeRouteSlash -Path $Path
    $Path = Resolve-PodePlaceholder -Path $Path

    # ensure route does exist
    if (!$PodeContext.Server.Routes[$Method].Contains($Path)) {
        return
    }

    # select the candidate route for deletion
    $route = @($PodeContext.Server.Routes[$Method][$Path] | Where-Object {
            $_.Endpoint.Name -ieq $EndpointName
        })

    foreach ($r in $route) {
        # remove the operationId from the openapi operationId list
        if ($r.OpenAPI) {
            foreach ( $tag in $r.OpenAPI.DefinitionTag) {
                if ($tag -and ($PodeContext.Server.OpenAPI.Definitions[$tag].hiddenComponents.operationId -ccontains $route.OpenAPI.OperationId)) {
                    $PodeContext.Server.OpenAPI.Definitions[$tag].hiddenComponents.operationId = $PodeContext.Server.OpenAPI.Definitions[$tag].hiddenComponents.operationId | Where-Object { $_ -ne $route.OpenAPI.OperationId }
                }
            }
        }
    }

    # remove the route's logic
    $PodeContext.Server.Routes[$Method][$Path] = @($PodeContext.Server.Routes[$Method][$Path] | Where-Object {
            $_.Endpoint.Name -ine $EndpointName
        })

    # if the route has no more logic, just remove it
    if ((Get-PodeCount $PodeContext.Server.Routes[$Method][$Path]) -eq 0) {
        $null = $PodeContext.Server.Routes[$Method].Remove($Path)
    }
}

<#
.SYNOPSIS
Remove a specific static Route.

.DESCRIPTION
Remove a specific static Route.

.PARAMETER Path
The path of the static Route to remove.

.PARAMETER EndpointName
The EndpointName of an Endpoint(s) bound to the static Route to be removed.

.EXAMPLE
Remove-PodeStaticRoute -Path '/assets'
#>
function Remove-PodeStaticRoute {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $true)]
        [string]
        $Path,

        [Parameter()]
        [string]
        $EndpointName
    )

    $Method = 'Static'

    # ensure the route has appropriate slashes and replace parameters
    $Path = Update-PodeRouteSlash -Path $Path -Static

    # ensure route does exist
    if (!$PodeContext.Server.Routes[$Method].Contains($Path)) {
        return
    }

    # remove the route's logic
    $PodeContext.Server.Routes[$Method][$Path] = @($PodeContext.Server.Routes[$Method][$Path] | Where-Object {
            $_.Endpoint.Name -ine $EndpointName
        })

    # if the route has no more logic, just remove it
    if ((Get-PodeCount $PodeContext.Server.Routes[$Method][$Path]) -eq 0) {
        $null = $PodeContext.Server.Routes[$Method].Remove($Path)
    }
}

<#
.SYNOPSIS
Remove a specific Signal Route.

.DESCRIPTION
Remove a specific Signal Route.

.PARAMETER Path
The path of the Signal Route to remove.

.PARAMETER EndpointName
The EndpointName of an Endpoint(s) bound to the Signal Route to be removed.

.EXAMPLE
Remove-PodeSignalRoute -Route '/message'
#>
function Remove-PodeSignalRoute {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $true)]
        [string]
        $Path,

        [Parameter()]
        [string]
        $EndpointName
    )

    $Method = 'Signal'

    # ensure the route has appropriate slashes and replace parameters
    $Path = Update-PodeRouteSlash -Path $Path

    # ensure route does exist
    if (!$PodeContext.Server.Routes[$Method].Contains($Path)) {
        return
    }

    # remove the route's logic
    $PodeContext.Server.Routes[$Method][$Path] = @($PodeContext.Server.Routes[$Method][$Path] | Where-Object {
            $_.Endpoint.Name -ine $EndpointName
        })

    # if the route has no more logic, just remove it
    if ((Get-PodeCount $PodeContext.Server.Routes[$Method][$Path]) -eq 0) {
        $null = $PodeContext.Server.Routes[$Method].Remove($Path)
    }
}

<#
.SYNOPSIS
Removes all added Routes, or Routes for a specific Method.

.DESCRIPTION
Removes all added Routes, or Routes for a specific Method.

.PARAMETER Method
The Method to from which to remove all Routes.

.EXAMPLE
Clear-PodeRoutes

.EXAMPLE
Clear-PodeRoutes -Method Get
#>
function Clear-PodeRoutes {
    [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseSingularNouns', '')]
    [CmdletBinding()]
    param(
        [Parameter()]
        [ValidateSet('', 'Connect', 'Delete', 'Get', 'Head', 'Merge', 'Options', 'Patch', 'Post', 'Put', 'Trace', '*')]
        [string]
        $Method
    )

    if (![string]::IsNullOrWhiteSpace($Method)) {
        $PodeContext.Server.Routes[$Method].Clear()
    }
    else {
        $PodeContext.Server.Routes.Keys.Clone() | ForEach-Object {
            $PodeContext.Server.Routes[$_].Clear()
        }
    }
}

<#
.SYNOPSIS
Removes all added static Routes.

.DESCRIPTION
Removes all added static Routes.

.EXAMPLE
Clear-PodeStaticRoutes
#>
function Clear-PodeStaticRoutes {
    [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseSingularNouns', '')]
    [CmdletBinding()]
    param()

    $PodeContext.Server.Routes['Static'].Clear()
}

<#
.SYNOPSIS
Removes all added Signal Routes.

.DESCRIPTION
Removes all added Signal Routes.

.EXAMPLE
Clear-PodeSignalRoutes
#>
function Clear-PodeSignalRoutes {
    [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseSingularNouns', '')]
    [CmdletBinding()]
    param()

    $PodeContext.Server.Routes['Signal'].Clear()
}

<#
.SYNOPSIS
Takes an array of Commands, or a Module, and converts them into Routes.

.DESCRIPTION
Takes an array of Commands (Functions/Aliases), or a Module, and generates appropriate Routes for the commands.

.PARAMETER Commands
An array of Commands to convert - if a Module is supplied, these Commands must be present within that Module.

.PARAMETER Module
A Module whose exported commands will be converted.

.PARAMETER Method
An override HTTP method to use when generating the Routes. If not supplied, Pode will make a best guess based on the Command's Verb.

.PARAMETER Path
An optional Path for the Route, to prepend before the Command Name and Module.

.PARAMETER Middleware
Like normal Routes, an array of Middleware that will be applied to all generated Routes.

.PARAMETER Authentication
The name of an Authentication method which should be used as middleware on this Route.

.PARAMETER Access
The name of an Access method which should be used as middleware on this Route.

.PARAMETER AllowAnon
If supplied, the Route will allow anonymous access for non-authenticated users.

.PARAMETER NoVerb
If supplied, the Command's Verb will not be included in the Route's path.

.PARAMETER NoOpenApi
If supplied, no OpenAPI definitions will be generated for the routes created.

.PARAMETER Role
One or more optional Roles that will be authorised to access this Route, when using Authentication with an Access method.

.PARAMETER Group
One or more optional Groups that will be authorised to access this Route, when using Authentication with an Access method.

.PARAMETER Scope
One or more optional Scopes that will be authorised to access this Route, when using Authentication with an Access method.

.PARAMETER User
One or more optional Users that will be authorised to access this Route, when using Authentication with an Access method.

.EXAMPLE
ConvertTo-PodeRoute -Commands @('Get-ChildItem', 'Get-Host', 'Invoke-Expression') -Middleware { ... }

.EXAMPLE
ConvertTo-PodeRoute -Commands @('Get-ChildItem', 'Get-Host', 'Invoke-Expression') -Authentication AuthName

.EXAMPLE
ConvertTo-PodeRoute -Module Pester -Path '/api'

.EXAMPLE
ConvertTo-PodeRoute -Commands @('Invoke-Pester') -Module Pester
#>
function ConvertTo-PodeRoute {
    [CmdletBinding()]
    param(
        [Parameter(ValueFromPipeline = $true, Position = 0 )]
        [string[]]
        $Commands,

        [Parameter()]
        [string]
        $Module,

        [Parameter()]
        [ValidateSet('', 'Connect', 'Delete', 'Get', 'Head', 'Merge', 'Options', 'Patch', 'Post', 'Put', 'Trace')]
        [string]
        $Method,

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

        [Parameter()]
        [object[]]
        $Middleware,

        [Parameter()]
        [Alias('Auth')]
        [string]
        $Authentication,

        [Parameter()]
        [string]
        $Access,

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

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

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

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

        [switch]
        $AllowAnon,

        [switch]
        $NoVerb,

        [switch]
        $NoOpenApi
    )
    begin {
        # Initialize an array to hold piped-in values
        $pipelineValue = @()
    }

    process {
        # Add the current piped-in value to the array
        $pipelineValue += $_
    }

    end {
        # Set InputObject to the array of values
        if ($pipelineValue.Count -gt 1) {
            $Commands = $pipelineValue
        }

        # if a module was supplied, import it - then validate the commands
        if (![string]::IsNullOrWhiteSpace($Module)) {
            Import-PodeModule -Name $Module

            Write-Verbose 'Getting exported commands from module'
            $ModuleCommands = (Get-Module -Name $Module | Sort-Object -Descending | Select-Object -First 1).ExportedCommands.Keys

            # if commands were supplied validate them - otherwise use all exported ones
            if (Test-PodeIsEmpty $Commands) {
                Write-Verbose "Using all commands in $($Module) for converting to routes"
                $Commands = $ModuleCommands
            }
            else {
                Write-Verbose "Validating supplied commands against module's exported commands"
                foreach ($cmd in $Commands) {
                    if ($ModuleCommands -inotcontains $cmd) {
                        # Module Module does not contain function cmd to convert to a Route
                        throw ($PodeLocale.moduleDoesNotContainFunctionExceptionMessage -f $Module, $cmd)
                    }
                }
            }
        }

        # if there are no commands, fail
        if (Test-PodeIsEmpty $Commands) {
            # No commands supplied to convert to Routes
            throw ($PodeLocale.noCommandsSuppliedToConvertToRoutesExceptionMessage)
        }

        # trim end trailing slashes from the path
        $Path = Protect-PodeValue -Value $Path -Default '/'
        $Path = $Path.TrimEnd('/')

        # create the routes for each of the commands
        foreach ($cmd in $Commands) {
            # get module verb/noun and comvert verb to HTTP method
            $split = ($cmd -split '\-')

            if ($split.Length -ge 2) {
                $verb = $split[0]
                $noun = $split[1..($split.Length - 1)] -join ([string]::Empty)
            }
            else {
                $verb = [string]::Empty
                $noun = $split[0]
            }

            # determine the http method, or use the one passed
            $_method = $Method
            if ([string]::IsNullOrWhiteSpace($_method)) {
                $_method = Convert-PodeFunctionVerbToHttpMethod -Verb $verb
            }

            # use the full function name, or remove the verb
            $name = $cmd
            if ($NoVerb) {
                $name = $noun
            }

            # build the route's path
            $_path = ("$($Path)/$($Module)/$($name)" -replace '[/]+', '/')

            # create the route
            $params = @{
                Method         = $_method
                Path           = $_path
                Middleware     = $Middleware
                Authentication = $Authentication
                Access         = $Access
                Role           = $Role
                Group          = $Group
                Scope          = $Scope
                User           = $User
                AllowAnon      = $AllowAnon
                ArgumentList   = $cmd
                PassThru       = $true
            }

            $route = Add-PodeRoute @params -ScriptBlock {
                param($cmd)

                # either get params from the QueryString or Payload
                if ($WebEvent.Method -ieq 'get') {
                    $parameters = $WebEvent.Query
                }
                else {
                    $parameters = $WebEvent.Data
                }

                # invoke the function
                $result = (. $cmd @parameters)

                # if we have a result, convert it to json
                if (!(Test-PodeIsEmpty $result)) {
                    Write-PodeJsonResponse -Value $result -Depth 1
                }
            }

            # set the openapi metadata of the function, unless told to skip
            if ($NoOpenApi) {
                continue
            }

            $help = Get-Help -Name $cmd
            $route = ($route | Set-PodeOARouteInfo -Summary $help.Synopsis -Tags $Module -PassThru)

            # set the routes parameters (get = query, everything else = payload)
            $params = (Get-Command -Name $cmd).Parameters
            if (($null -eq $params) -or ($params.Count -eq 0)) {
                continue
            }

            $props = @(foreach ($key in $params.Keys) {
                    $params[$key] | ConvertTo-PodeOAPropertyFromCmdletParameter
                })

            if ($_method -ieq 'get') {
                $route | Set-PodeOARequest -Parameters @(foreach ($prop in $props) { $prop | ConvertTo-PodeOAParameter -In Query })
            }

            else {
                $route | Set-PodeOARequest -RequestBody (
                    New-PodeOARequestBody -ContentSchemas @{ 'application/json' = (New-PodeOAObjectProperty -Array -Properties $props) }
                )
            }
        }
    }
}

<#
.SYNOPSIS
Helper function to generate simple GET routes.

.DESCRIPTION
Helper function to generate simple GET routes from ScritpBlocks, Files, and Views.
The output is always rendered as HTML.

.PARAMETER Name
A unique name for the page, that will be used in the Path for the route.

.PARAMETER ScriptBlock
A ScriptBlock to invoke, where any results will be converted to HTML.

.PARAMETER FilePath
A FilePath, literal or relative, to a valid HTML file.

.PARAMETER View
The name of a View to render, this can be HTML or Dynamic.

.PARAMETER Data
A hashtable of Data to supply to a Dynamic File/View, or to be splatted as arguments for the ScriptBlock.

.PARAMETER Path
An optional Path for the Route, to prepend before the Name.

.PARAMETER Middleware
Like normal Routes, an array of Middleware that will be applied to all generated Routes.

.PARAMETER Authentication
The name of an Authentication method which should be used as middleware on this Route.

.PARAMETER Access
The name of an Access method which should be used as middleware on this Route.

.PARAMETER AllowAnon
If supplied, the Page will allow anonymous access for non-authenticated users.

.PARAMETER FlashMessages
If supplied, Views will have any flash messages supplied to them for rendering.

.PARAMETER Role
One or more optional Roles that will be authorised to access this Route, when using Authentication with an Access method.

.PARAMETER Group
One or more optional Groups that will be authorised to access this Route, when using Authentication with an Access method.

.PARAMETER Scope
One or more optional Scopes that will be authorised to access this Route, when using Authentication with an Access method.

.PARAMETER User
One or more optional Users that will be authorised to access this Route, when using Authentication with an Access method.

.EXAMPLE
Add-PodePage -Name Services -ScriptBlock { Get-Service }

.EXAMPLE
Add-PodePage -Name Index -View 'index'

.EXAMPLE
Add-PodePage -Name About -FilePath '.\views\about.pode' -Data @{ Date = [DateTime]::UtcNow }
#>
function Add-PodePage {
    [CmdletBinding(DefaultParameterSetName = 'ScriptBlock')]
    param(
        [Parameter(Mandatory = $true)]
        [ValidateNotNullOrEmpty()]
        [string]
        $Name,

        [Parameter(Mandatory = $true, ParameterSetName = 'ScriptBlock')]
        [scriptblock]
        $ScriptBlock,

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

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

        [Parameter()]
        [hashtable]
        $Data,

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

        [Parameter()]
        [object[]]
        $Middleware,

        [Parameter()]
        [Alias('Auth')]
        [string]
        $Authentication,

        [Parameter()]
        [string]
        $Access,

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

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

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

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

        [switch]
        $AllowAnon,

        [Parameter(ParameterSetName = 'View')]
        [switch]
        $FlashMessages
    )

    $logic = $null
    $arg = $null

    # ensure the name is a valid alphanumeric
    if ($Name -inotmatch '^[a-z0-9\-_]+$') {
        # The Page name should be a valid AlphaNumeric value
        throw ($PodeLocale.pageNameShouldBeAlphaNumericExceptionMessage -f $Name)
    }

    # trim end trailing slashes from the path
    $Path = Protect-PodeValue -Value $Path -Default '/'
    $Path = $Path.TrimEnd('/')

    # define the appropriate logic
    switch ($PSCmdlet.ParameterSetName.ToLowerInvariant()) {
        'scriptblock' {
            if (Test-PodeIsEmpty $ScriptBlock) {
                # A non-empty ScriptBlock is required to create a Page Route
                throw ($PodeLocale.nonEmptyScriptBlockRequiredForPageRouteExceptionMessage)
            }

            $arg = @($ScriptBlock, $Data)
            $logic = {
                param($script, $data)

                # invoke the function (optional splat data)
                if (Test-PodeIsEmpty $data) {
                    $result = Invoke-PodeScriptBlock -ScriptBlock $script -Return
                }
                else {
                    $result = Invoke-PodeScriptBlock -ScriptBlock $script -Arguments $data -Return
                }

                # if we have a result, convert it to html
                if (!(Test-PodeIsEmpty $result)) {
                    Write-PodeHtmlResponse -Value $result
                }
            }
        }

        'file' {
            $FilePath = Get-PodeRelativePath -Path $FilePath -JoinRoot -TestPath
            $arg = @($FilePath, $Data)
            $logic = {
                param($file, $data)
                Write-PodeFileResponse -Path $file -ContentType 'text/html' -Data $data
            }
        }

        'view' {
            $arg = @($View, $Data, $FlashMessages)
            $logic = {
                param($view, $data, [bool]$flash)
                Write-PodeViewResponse -Path $view -Data $data -FlashMessages:$flash
            }
        }
    }

    # build the route's path
    $_path = ("$($Path)/$($Name)" -replace '[/]+', '/')

    # create the route
    $params = @{
        Method         = 'Get'
        Path           = $_path
        Middleware     = $Middleware
        Authentication = $Authentication
        Access         = $Access
        Role           = $Role
        Group          = $Group
        Scope          = $Scope
        User           = $User
        AllowAnon      = $AllowAnon
        ArgumentList   = $arg
        ScriptBlock    = $logic
    }

    Add-PodeRoute @params
}

<#
.SYNOPSIS
Get a Route(s).

.DESCRIPTION
Get a Route(s).

.PARAMETER Method
A Method to filter the routes.

.PARAMETER Path
A Path to filter the routes.

.PARAMETER EndpointName
The name of an endpoint to filter routes.

.EXAMPLE
Get-PodeRoute -Method Get -Path '/about'

.EXAMPLE
Get-PodeRoute -Method Post -Path '/users/:userId' -EndpointName User
#>
function Get-PodeRoute {
    [CmdletBinding()]
    [OutputType([System.Object[]])]
    param(
        [Parameter()]
        [ValidateSet('', 'Connect', 'Delete', 'Get', 'Head', 'Merge', 'Options', 'Patch', 'Post', 'Put', 'Trace', '*')]
        [string]
        $Method,

        [Parameter()]
        [string]
        $Path,

        [Parameter()]
        [string[]]
        $EndpointName
    )

    # start off with every route
    $routes = @()
    foreach ($route in $PodeContext.Server.Routes.Values.Values) {
        $routes += $route
    }

    # if we have a method, filter
    if (![string]::IsNullOrWhiteSpace($Method)) {
        $routes = @(foreach ($route in $routes) {
                if ($route.Method -ine $Method) {
                    continue
                }

                $route
            })
    }

    # if we have a path, filter
    if (![string]::IsNullOrWhiteSpace($Path)) {
        $Path = Split-PodeRouteQuery -Path $Path
        $Path = Update-PodeRouteSlash -Path $Path
        $Path = Resolve-PodePlaceholder -Path $Path

        $routes = @(foreach ($route in $routes) {
                if ($route.Path -ine $Path) {
                    continue
                }

                $route
            })
    }

    # further filter by endpoint names
    if (($null -ne $EndpointName) -and ($EndpointName.Length -gt 0)) {
        $routes = @(foreach ($name in $EndpointName) {
                foreach ($route in $routes) {
                    if ($route.Endpoint.Name -ine $name) {
                        continue
                    }

                    $route
                }
            })
    }

    # return
    return $routes
}

<#
.SYNOPSIS
Get a static Route(s).

.DESCRIPTION
Get a static Route(s).

.PARAMETER Path
A Path to filter the static routes.

.PARAMETER EndpointName
The name of an endpoint to filter static routes.

.EXAMPLE
Get-PodeStaticRoute -Path '/assets'

.EXAMPLE
Get-PodeStaticRoute -Path '/assets' -EndpointName User
#>
function Get-PodeStaticRoute {
    [CmdletBinding()]
    [OutputType([System.Object[]])]
    param(
        [Parameter()]
        [string]
        $Path,

        [Parameter()]
        [string[]]
        $EndpointName
    )

    # start off with every route
    $routes = @()
    foreach ($route in $PodeContext.Server.Routes['Static'].Values) {
        $routes += $route
    }

    # if we have a path, filter
    if (![string]::IsNullOrWhiteSpace($Path)) {
        $Path = Update-PodeRouteSlash -Path $Path -Static
        $routes = @(foreach ($route in $routes) {
                if ($route.Path -ine $Path) {
                    continue
                }

                $route
            })
    }

    # further filter by endpoint names
    if (($null -ne $EndpointName) -and ($EndpointName.Length -gt 0)) {
        $routes = @(foreach ($name in $EndpointName) {
                foreach ($route in $routes) {
                    if ($route.Endpoint.Name -ine $name) {
                        continue
                    }

                    $route
                }
            })
    }

    # return
    return $routes
}

<#
.SYNOPSIS
Get a Signal Route(s).

.DESCRIPTION
Get a Signal Route(s).

.PARAMETER Path
A Path to filter the signal routes.

.PARAMETER EndpointName
The name of an endpoint to filter signal routes.

.EXAMPLE
Get-PodeSignalRoute -Path '/message'
#>
function Get-PodeSignalRoute {
    [CmdletBinding()]
    [OutputType([System.Object[]])]
    param(
        [Parameter()]
        [string]
        $Path,

        [Parameter()]
        [string[]]
        $EndpointName
    )

    # start off with every route
    $routes = @()
    foreach ($route in $PodeContext.Server.Routes['Signal'].Values) {
        $routes += $route
    }

    # if we have a path, filter
    if (![string]::IsNullOrWhiteSpace($Path)) {
        $Path = Update-PodeRouteSlash -Path $Path
        $routes = @(foreach ($route in $routes) {
                if ($route.Path -ine $Path) {
                    continue
                }

                $route
            })
    }

    # further filter by endpoint names
    if (($null -ne $EndpointName) -and ($EndpointName.Length -gt 0)) {
        $routes = @(foreach ($name in $EndpointName) {
                foreach ($route in $routes) {
                    if ($route.Endpoint.Name -ine $name) {
                        continue
                    }

                    $route
                }
            })
    }

    # return
    return $routes
}

<#
.SYNOPSIS
Automatically loads route ps1 files

.DESCRIPTION
Automatically loads route ps1 files from either a /routes folder, or a custom folder. Saves space dot-sourcing them all one-by-one.

.PARAMETER Path
Optional Path to a folder containing ps1 files, can be relative or literal.

.PARAMETER IfExists
Specifies what action to take when a Route already exists. (Default: Default)

.EXAMPLE
Use-PodeRoutes

.EXAMPLE
Use-PodeRoutes -Path './my-routes' -IfExists Skip
#>
function Use-PodeRoutes {
    [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseSingularNouns', '')]
    [CmdletBinding()]
    param(
        [Parameter()]
        [string]
        $Path,

        [Parameter()]
        [ValidateSet('Default', 'Error', 'Overwrite', 'Skip')]
        [string]
        $IfExists = 'Default'
    )

    if ($IfExists -ieq 'Default') {
        $IfExists = Get-PodeRouteIfExistsPreference
    }

    Use-PodeFolder -Path $Path -DefaultPath 'routes'
}

<#
.SYNOPSIS
Set the default IfExists preference for Routes.

.DESCRIPTION
Set the default IfExists preference for Routes.

.PARAMETER Value
Specifies what action to take when a Route already exists. (Default: Default)

.EXAMPLE
Set-PodeRouteIfExistsPreference -Value Overwrite
#>
function Set-PodeRouteIfExistsPreference {
    [CmdletBinding()]
    param(
        [Parameter()]
        [ValidateSet('Default', 'Error', 'Overwrite', 'Skip')]
        [string]
        $Value = 'Default'
    )

    $PodeContext.Server.Preferences.Routes.IfExists = $Value
}

<#
.SYNOPSIS
Test if a Route already exists.

.DESCRIPTION
Test if a Route already exists for a given Method and Path.

.PARAMETER Method
The HTTP Method of the Route.

.PARAMETER Path
The URI path of the Route.

.PARAMETER EndpointName
The EndpointName of an Endpoint the Route is bound against.

.PARAMETER CheckWildcard
If supplied, Pode will check for the Route on the Method first, and then check for the Route on the '*' Method.

.EXAMPLE
Test-PodeRoute -Method Post -Path '/example'

.EXAMPLE
Test-PodeRoute -Method Post -Path '/example' -CheckWildcard

.EXAMPLE
Test-PodeRoute -Method Get -Path '/example/:exampleId' -CheckWildcard
#>
function Test-PodeRoute {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $true)]
        [ValidateSet('Connect', 'Delete', 'Get', 'Head', 'Merge', 'Options', 'Patch', 'Post', 'Put', 'Trace', '*')]
        [string]
        $Method,

        [Parameter(Mandatory = $true)]
        [string]
        $Path,

        [Parameter()]
        [string]
        $EndpointName,

        [switch]
        $CheckWildcard
    )

    # split route on '?' for query
    $Path = Split-PodeRouteQuery -Path $Path
    if ([string]::IsNullOrWhiteSpace($Path)) {
        # No Path supplied for the Route
        throw ($PodeLocale.noPathSuppliedForRouteExceptionMessage)
    }

    # ensure the route has appropriate slashes
    $Path = Update-PodeRouteSlash -Path $Path
    $Path = Resolve-PodePlaceholder -Path $Path

    # get endpoint from name
    $endpoint = @(Find-PodeEndpoint -EndpointName $EndpointName)[0]

    # check for routes
    $found = (Test-PodeRouteInternal -Method $Method -Path $Path -Protocol $endpoint.Protocol -Address $endpoint.Address)
    if (!$found -and $CheckWildcard) {
        $found = (Test-PodeRouteInternal -Method '*' -Path $Path -Protocol $endpoint.Protocol -Address $endpoint.Address)
    }

    return $found
}

<#
.SYNOPSIS
Test if a Static Route already exists.

.DESCRIPTION
Test if a Static Route already exists for a given Path.

.PARAMETER Path
The URI path of the Static Route.

.PARAMETER EndpointName
The EndpointName of an Endpoint the Static Route is bound against.

.EXAMPLE
Test-PodeStaticRoute -Path '/assets'
#>
function Test-PodeStaticRoute {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $true)]
        [string]
        $Path,

        [Parameter()]
        [string]
        $EndpointName
    )

    # store the route method
    $Method = 'Static'

    # split route on '?' for query
    $Path = Split-PodeRouteQuery -Path $Path
    if ([string]::IsNullOrWhiteSpace($Path)) {
        # No Path supplied for the Route
        throw ($PodeLocale.noPathSuppliedForRouteExceptionMessage)
    }

    # ensure the route has appropriate slashes
    $Path = Update-PodeRouteSlash -Path $Path -Static
    $Path = Resolve-PodePlaceholder -Path $Path

    # get endpoint from name
    $endpoint = @(Find-PodeEndpoint -EndpointName $EndpointName)[0]

    # check for routes
    return (Test-PodeRouteInternal -Method $Method -Path $Path -Protocol $endpoint.Protocol -Address $endpoint.Address)
}

<#
.SYNOPSIS
Test if a Signal Route already exists.

.DESCRIPTION
Test if a Signal Route already exists for a given Path.

.PARAMETER Path
The URI path of the Signal Route.

.PARAMETER EndpointName
The EndpointName of an Endpoint the Signal Route is bound against.

.EXAMPLE
Test-PodeSignalRoute -Path '/message'
#>
function Test-PodeSignalRoute {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $true)]
        [string]
        $Path,

        [Parameter()]
        [string]
        $EndpointName
    )

    $Method = 'Signal'

    # ensure the route has appropriate slashes
    $Path = Update-PodeRouteSlash -Path $Path

    # get endpoint from name
    $endpoint = @(Find-PodeEndpoint -EndpointName $EndpointName)[0]

    # check for routes
    return (Test-PodeRouteInternal -Method $Method -Path $Path -Protocol $endpoint.Protocol -Address $endpoint.Address)
}
src\Public\Runspaces.ps1
<#
.SYNOPSIS
    Sets the name of the current runspace.

.DESCRIPTION
    The Set-PodeCurrentRunspaceName function assigns a specified name to the current runspace.
    This can be useful for identifying and managing the runspace in scripts and during debugging.

.PARAMETER Name
    The name to assign to the current runspace. This parameter is mandatory.

.EXAMPLE
    Set-PodeCurrentRunspaceName -Name "MyRunspace"
    This command sets the name of the current runspace to "MyRunspace".

.NOTES
    This is an internal function and may change in future releases of Pode.
#>

function Set-PodeCurrentRunspaceName {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $true)]
        [string]
        $Name
    )

    # Get the current runspace
    $currentRunspace = [System.Management.Automation.Runspaces.Runspace]::DefaultRunspace
    # Set the name of the current runspace if the name is not already set
    if ( $currentRunspace.Name -ne $Name) {
        # Set the name of the current runspace
        $currentRunspace.Name = $Name
    }
}

<#
.SYNOPSIS
    Retrieves the name of the current PowerShell runspace.

.DESCRIPTION
    The Get-PodeCurrentRunspaceName function retrieves the name of the current PowerShell runspace.
    This can be useful for debugging or logging purposes to identify the runspace in use.

.EXAMPLE
    Get-PodeCurrentRunspaceName
    Returns the name of the current runspace.

.NOTES
    This is an internal function and may change in future releases of Pode.
#>
function Get-PodeCurrentRunspaceName {
    [CmdletBinding()]
    param()

    # Get the current runspace
    $currentRunspace = [System.Management.Automation.Runspaces.Runspace]::DefaultRunspace

    # Get the name of the current runspace
    return $currentRunspace.Name
}
src\Public\Schedules.ps1
<#
.SYNOPSIS
    Adds a new Schedule with logic to periodically invoke, defined using Cron Expressions.

.DESCRIPTION
    Adds a new Schedule with logic to periodically invoke, defined using Cron Expressions.

.PARAMETER Name
    The Name of the Schedule.

.PARAMETER Cron
    One, or an Array, of Cron Expressions to define when the Schedule should trigger.

.PARAMETER ScriptBlock
    The script defining the Schedule's logic.

.PARAMETER Limit
    The number of times the Schedule should trigger before being removed.

.PARAMETER StartTime
    A DateTime for when the Schedule should start triggering.

.PARAMETER EndTime
    A DateTime for when the Schedule should stop triggering, and be removed.

.PARAMETER ArgumentList
    A hashtable of arguments to supply to the Schedule's ScriptBlock.

.PARAMETER Timeout
    An optional timeout, in seconds, for the Schedule's logic. (Default: -1 [never timeout])

.PARAMETER TimeoutFrom
    An optional timeout from either 'Create' or 'Start'. (Default: 'Create')

.PARAMETER FilePath
    A literal, or relative, path to a file containing a ScriptBlock for the Schedule's logic.

.PARAMETER OnStart
    If supplied, the schedule will trigger when the server starts, regardless if the cron-expression matches the current time.

.EXAMPLE
    Add-PodeSchedule -Name 'RunEveryMinute' -Cron '@minutely' -ScriptBlock { /* logic */ }

.EXAMPLE
    Add-PodeSchedule -Name 'RunEveryTuesday' -Cron '0 0 * * TUE' -ScriptBlock { /* logic */ }

.EXAMPLE
    Add-PodeSchedule -Name 'StartAfter2days' -Cron '@hourly' -StartTime [DateTime]::Now.AddDays(2) -ScriptBlock { /* logic */ }

.EXAMPLE
    Add-PodeSchedule -Name 'Args' -Cron '@minutely' -ScriptBlock { /* logic */ } -ArgumentList @{ Arg1 = 'value' }
#>
function Add-PodeSchedule {
    [CmdletBinding(DefaultParameterSetName = 'Script')]
    param(
        [Parameter(Mandatory = $true)]
        [string]
        $Name,

        [Parameter(Mandatory = $true)]
        [string[]]
        $Cron,

        [Parameter(Mandatory = $true, ParameterSetName = 'Script')]
        [scriptblock]
        $ScriptBlock,

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

        [Parameter()]
        [DateTime]
        $StartTime,

        [Parameter()]
        [DateTime]
        $EndTime,

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

        [Parameter()]
        [hashtable]
        $ArgumentList,

        [Parameter()]
        [int]
        $Timeout = -1,

        [Parameter()]
        [ValidateSet('Create', 'Start')]
        [string]
        $TimeoutFrom = 'Create',

        [switch]
        $OnStart
    )

    # error if serverless
    Test-PodeIsServerless -FunctionName 'Add-PodeSchedule' -ThrowError

    # ensure the schedule doesn't already exist
    if ($PodeContext.Schedules.Items.ContainsKey($Name)) {
        # [Schedule] Name: Schedule already defined
        throw ($PodeLocale.scheduleAlreadyDefinedExceptionMessage -f $Name)
    }

    # ensure the limit is valid
    if ($Limit -lt 0) {
        # [Schedule] Name: Cannot have a negative limit
        throw ($PodeLocale.scheduleCannotHaveNegativeLimitExceptionMessage -f $Name)
    }

    # ensure the start/end dates are valid
    if (($null -ne $EndTime) -and ($EndTime -lt [DateTime]::Now)) {
        # [Schedule] Name: The EndTime value must be in the future
        throw ($PodeLocale.scheduleEndTimeMustBeInFutureExceptionMessage -f $Name)
    }

    if (($null -ne $StartTime) -and ($null -ne $EndTime) -and ($EndTime -le $StartTime)) {
        # [Schedule] Name: Cannot have a 'StartTime' after the 'EndTime'
        throw ($PodeLocale.scheduleStartTimeAfterEndTimeExceptionMessage -f $Name)
    }

    # if we have a file path supplied, load that path as a scriptblock
    if ($PSCmdlet.ParameterSetName -ieq 'file') {
        $ScriptBlock = Convert-PodeFileToScriptBlock -FilePath $FilePath
    }

    # check for scoped vars
    $ScriptBlock, $usingVars = Convert-PodeScopedVariables -ScriptBlock $ScriptBlock -PSSession $PSCmdlet.SessionState

    # add the schedule
    $parsedCrons = ConvertFrom-PodeCronExpression -Expression @($Cron)
    $nextTrigger = Get-PodeCronNextEarliestTrigger -Expressions $parsedCrons -StartTime $StartTime -EndTime $EndTime

    $PodeContext.Schedules.Enabled = $true
    $PodeContext.Schedules.Items[$Name] = @{
        Name            = $Name
        StartTime       = $StartTime
        EndTime         = $EndTime
        Crons           = $parsedCrons
        CronsRaw        = @($Cron)
        Limit           = $Limit
        Count           = 0
        NextTriggerTime = $nextTrigger
        LastTriggerTime = $null
        Script          = $ScriptBlock
        UsingVariables  = $usingVars
        Arguments       = (Protect-PodeValue -Value $ArgumentList -Default @{})
        OnStart         = $OnStart
        Completed       = ($null -eq $nextTrigger)
        Timeout         = @{
            Value = $Timeout
            From  = $TimeoutFrom
        }
    }
}

<#
.SYNOPSIS
Set the maximum number of concurrent schedules.

.DESCRIPTION
Set the maximum number of concurrent schedules.

.PARAMETER Maximum
The Maximum number of schedules to run.

.EXAMPLE
Set-PodeScheduleConcurrency -Maximum 25
#>
function Set-PodeScheduleConcurrency {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $true)]
        [int]
        $Maximum
    )

    # error if <=0
    if ($Maximum -le 0) {
        # Maximum concurrent schedules must be >=1 but got
        throw ($PodeLocale.maximumConcurrentSchedulesInvalidExceptionMessage -f $Maximum)
    }

    # ensure max > min
    $_min = 1
    if ($null -ne $PodeContext.RunspacePools.Schedules) {
        $_min = $PodeContext.RunspacePools.Schedules.Pool.GetMinRunspaces()
    }

    if ($_min -gt $Maximum) {
        # Maximum concurrent schedules cannot be less than the minimum of $_min but got $Maximum
        throw ($PodeLocale.maximumConcurrentSchedulesLessThanMinimumExceptionMessage -f $_min, $Maximum)
    }

    # set the max schedules
    $PodeContext.Threads.Schedules = $Maximum
    if ($null -ne $PodeContext.RunspacePools.Schedules) {
        $PodeContext.RunspacePools.Schedules.Pool.SetMaxRunspaces($Maximum)
    }
}

<#
.SYNOPSIS
Adhoc invoke a Schedule's logic.

.DESCRIPTION
Adhoc invoke a Schedule's logic outside of its defined cron-expression. This invocation doesn't count towards the Schedule's limit.

.PARAMETER Name
The Name of the Schedule.

.PARAMETER ArgumentList
A hashtable of arguments to supply to the Schedule's ScriptBlock.

.EXAMPLE
Invoke-PodeSchedule -Name 'schedule-name'
#>
function Invoke-PodeSchedule {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $true, ValueFromPipeline = $true)]
        [string]
        $Name,

        [Parameter()]
        [hashtable]
        $ArgumentList = $null
    )

    # ensure the schedule exists
    if (!$PodeContext.Schedules.Items.ContainsKey($Name)) {
        # Schedule 'Name' does not exist
        throw ($PodeLocale.scheduleDoesNotExistExceptionMessage -f $Name)
    }

    # run schedule logic
    Invoke-PodeInternalScheduleLogic -Schedule $PodeContext.Schedules.Items[$Name] -ArgumentList $ArgumentList
}

<#
.SYNOPSIS
Removes a specific Schedule.

.DESCRIPTION
Removes a specific Schedule.

.PARAMETER Name
The Name of the Schedule to be removed.

.EXAMPLE
Remove-PodeSchedule -Name 'RenewToken'
#>
function Remove-PodeSchedule {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $true, ValueFromPipeline = $true)]
        [string]
        $Name
    )

    $null = $PodeContext.Schedules.Items.Remove($Name)
}

<#
.SYNOPSIS
Removes all Schedules.

.DESCRIPTION
Removes all Schedules.

.EXAMPLE
Clear-PodeSchedules
#>
function Clear-PodeSchedules {
    [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseSingularNouns', '')]
    [CmdletBinding()]
    param()

    $PodeContext.Schedules.Items.Clear()
}

<#
.SYNOPSIS
Edits an existing Schedule.

.DESCRIPTION
Edits an existing Schedule's properties, such an cron expressions or scriptblock.

.PARAMETER Name
The Name of the Schedule.

.PARAMETER Cron
Any new Cron Expressions for the Schedule.

.PARAMETER ScriptBlock
The new ScriptBlock for the Schedule.

.PARAMETER ArgumentList
Any new Arguments for the Schedule.

.EXAMPLE
Edit-PodeSchedule -Name 'Hello' -Cron '@minutely'

.EXAMPLE
Edit-PodeSchedule -Name 'Hello' -Cron @('@hourly', '0 0 * * TUE')
#>
function Edit-PodeSchedule {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $true, ValueFromPipeline = $true)]
        [string]
        $Name,

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

        [Parameter()]
        [scriptblock]
        $ScriptBlock,

        [Parameter()]
        [hashtable]
        $ArgumentList
    )

    # ensure the schedule exists
    if (!$PodeContext.Schedules.Items.ContainsKey($Name)) {
        # Schedule 'Name' does not exist
        throw ($PodeLocale.scheduleDoesNotExistExceptionMessage -f $Name)
    }

    $_schedule = $PodeContext.Schedules.Items[$Name]

    # edit cron if supplied
    if (!(Test-PodeIsEmpty $Cron)) {
        $_schedule.Crons = (ConvertFrom-PodeCronExpression -Expression @($Cron))
        $_schedule.CronsRaw = $Cron
        $_schedule.NextTriggerTime = Get-PodeCronNextEarliestTrigger -Expressions $_schedule.Crons -StartTime $_schedule.StartTime -EndTime $_schedule.EndTime
    }

    # edit scriptblock if supplied
    if (!(Test-PodeIsEmpty $ScriptBlock)) {
        $ScriptBlock, $usingVars = Convert-PodeScopedVariables -ScriptBlock $ScriptBlock -PSSession $PSCmdlet.SessionState
        $_schedule.Script = $ScriptBlock
        $_schedule.UsingVariables = $usingVars
    }

    # edit arguments if supplied
    if (!(Test-PodeIsEmpty $ArgumentList)) {
        $_schedule.Arguments = $ArgumentList
    }
}

<#
.SYNOPSIS
Returns any defined schedules.

.DESCRIPTION
Returns any defined schedules, with support for filtering.

.PARAMETER Name
Any schedule Names to filter the schedules.

.PARAMETER StartTime
An optional StartTime to only return Schedules that will trigger after this date.

.PARAMETER EndTime
An optional EndTime to only return Schedules that will trigger before this date.

.EXAMPLE
Get-PodeSchedule

.EXAMPLE
Get-PodeSchedule -Name Name1, Name2

.EXAMPLE
Get-PodeSchedule -Name Name1, Name2 -StartTime [datetime]::new(2020, 3, 1) -EndTime [datetime]::new(2020, 3, 31)
#>
function Get-PodeSchedule {
    [CmdletBinding()]
    param(
        [Parameter()]
        [string[]]
        $Name,

        [Parameter()]
        $StartTime = $null,

        [Parameter()]
        $EndTime = $null
    )

    $schedules = $PodeContext.Schedules.Items.Values

    # further filter by schedule names
    if (($null -ne $Name) -and ($Name.Length -gt 0)) {
        $schedules = @(foreach ($_name in $Name) {
                foreach ($schedule in $schedules) {
                    if ($schedule.Name -ine $_name) {
                        continue
                    }

                    $schedule
                }
            })
    }

    # filter by some start time
    if ($null -ne $StartTime) {
        $schedules = @(foreach ($schedule in $schedules) {
                if (($null -ne $schedule.StartTime) -and ($StartTime -lt $schedule.StartTime)) {
                    continue
                }

                $_end = $EndTime
                if ($null -eq $_end) {
                    $_end = $schedule.EndTime
                }

                if (($null -ne $schedule.EndTime) -and
                (($StartTime -gt $schedule.EndTime) -or
                    ((Get-PodeScheduleNextTrigger -Name $schedule.Name -DateTime $StartTime) -gt $_end))) {
                    continue
                }

                $schedule
            })
    }

    # filter by some end time
    if ($null -ne $EndTime) {
        $schedules = @(foreach ($schedule in $schedules) {
                if (($null -ne $schedule.EndTime) -and ($EndTime -gt $schedule.EndTime)) {
                    continue
                }

                $_start = $StartTime
                if ($null -eq $_start) {
                    $_start = $schedule.StartTime
                }

                if (($null -ne $schedule.StartTime) -and
                (($EndTime -lt $schedule.StartTime) -or
                    ((Get-PodeScheduleNextTrigger -Name $schedule.Name -DateTime $_start) -gt $EndTime))) {
                    continue
                }

                $schedule
            })
    }

    # return
    return $schedules
}

<#
.SYNOPSIS
Tests whether the passed Schedule exists.

.DESCRIPTION
Tests whether the passed Schedule exists by its name.

.PARAMETER Name
The Name of the Schedule.

.EXAMPLE
if (Test-PodeSchedule -Name ScheduleName) { }
#>
function Test-PodeSchedule {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $true)]
        [string]
        $Name
    )

    return (($null -ne $PodeContext.Schedules.Items) -and $PodeContext.Schedules.Items.ContainsKey($Name))
}

<#
.SYNOPSIS
Get the next trigger time for a Schedule.

.DESCRIPTION
Get the next trigger time for a Schedule, either from the Schedule's StartTime or from a defined DateTime.

.PARAMETER Name
The Name of the Schedule.

.PARAMETER DateTime
An optional specific DateTime to get the next trigger time after. This DateTime must be between the Schedule's StartTime and EndTime.

.EXAMPLE
Get-PodeScheduleNextTrigger -Name Schedule1

.EXAMPLE
Get-PodeScheduleNextTrigger -Name Schedule1 -DateTime [datetime]::new(2020, 3, 10)
#>
function Get-PodeScheduleNextTrigger {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $true, ValueFromPipeline = $true)]
        [string]
        $Name,

        [Parameter()]
        $DateTime = $null
    )

    # ensure the schedule exists
    if (!$PodeContext.Schedules.Items.ContainsKey($Name)) {
        # Schedule 'Name' does not exist
        throw ($PodeLocale.scheduleDoesNotExistExceptionMessage -f $Name)
    }

    $_schedule = $PodeContext.Schedules.Items[$Name]

    # ensure date is after start/before end
    if (($null -ne $DateTime) -and ($null -ne $_schedule.StartTime) -and ($DateTime -lt $_schedule.StartTime)) {
        # Supplied date is before the start time of the schedule at $_schedule.StartTime
        throw ($PodeLocale.suppliedDateBeforeScheduleStartTimeExceptionMessage -f $_schedule.StartTime)
    }

    if (($null -ne $DateTime) -and ($null -ne $_schedule.EndTime) -and ($DateTime -gt $_schedule.EndTime)) {
        # Supplied date is after the end time of the schedule at $_schedule.EndTime
        throw ($PodeLocale.suppliedDateAfterScheduleEndTimeExceptionMessage -f $_schedule.EndTime)
    }

    # get the next trigger
    if ($null -eq $DateTime) {
        $DateTime = $_schedule.StartTime
    }

    return (Get-PodeCronNextEarliestTrigger -Expressions $_schedule.Crons -StartTime $DateTime -EndTime $_schedule.EndTime)
}

<#
.SYNOPSIS
Automatically loads schedule ps1 files

.DESCRIPTION
Automatically loads schedule ps1 files from either a /schedules folder, or a custom folder. Saves space dot-sourcing them all one-by-one.

.PARAMETER Path
Optional Path to a folder containing ps1 files, can be relative or literal.

.EXAMPLE
Use-PodeSchedules

.EXAMPLE
Use-PodeSchedules -Path './my-schedules'
#>
function Use-PodeSchedules {
    [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseSingularNouns', '')]
    [CmdletBinding()]
    param(
        [Parameter()]
        [string]
        $Path
    )

    Use-PodeFolder -Path $Path -DefaultPath 'schedules'
}

<#
.SYNOPSIS
Get all Schedule Processes.

.DESCRIPTION
Get all Schedule Processes, with support for filtering.

.PARAMETER Name
An optional Name of the Schedule to filter by, can be one or more.

.PARAMETER Id
An optional ID of the Schedule process to filter by, can be one or more.

.PARAMETER State
An optional State of the Schedule process to filter by, can be one or more.

.EXAMPLE
Get-PodeScheduleProcess

.EXAMPLE
Get-PodeScheduleProcess -Name 'ScheduleName'

.EXAMPLE
Get-PodeScheduleProcess -Id 'ScheduleId'

.EXAMPLE
Get-PodeScheduleProcess -State 'Running'
#>
function Get-PodeScheduleProcess {
    [CmdletBinding()]
    param(
        [Parameter()]
        [string[]]
        $Name,

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

        [Parameter()]
        [ValidateSet('All', 'Pending', 'Running', 'Completed', 'Failed')]
        [string[]]
        $State = 'All'
    )

    $processes = $PodeContext.Schedules.Processes.Values

    # filter processes by name
    if (($null -ne $Name) -and ($Name.Length -gt 0)) {
        $processes = @(foreach ($_name in $Name) {
                foreach ($process in $processes) {
                    if ($process.Schedule -ine $_name) {
                        continue
                    }

                    $process
                }
            })
    }

    # filter processes by id
    if (($null -ne $Id) -and ($Id.Length -gt 0)) {
        $processes = @(foreach ($_id in $Id) {
                foreach ($process in $processes) {
                    if ($process.ID -ine $_id) {
                        continue
                    }

                    $process
                }
            })
    }

    # filter processes by status
    if ($State -inotcontains 'All') {
        $processes = @(foreach ($process in $processes) {
                if ($State -inotcontains $process.State) {
                    continue
                }

                $process
            })
    }

    # return processes
    return $processes
}
src\Public\ScopedVariables.ps1
<#
.SYNOPSIS
Converts Scoped Variables within a given ScriptBlock.

.DESCRIPTION
Converts Scoped Variables within a given ScriptBlock, and returns the updated ScriptBlock back, including any
using-variable values that will need to be supplied as parameters to the ScriptBlock first.

.PARAMETER ScriptBlock
The ScriptBlock to be converted.

.PARAMETER PSSession
An optional SessionState object, used to retrieve using-variable values.
If not supplied, using-variable values will not be converted.

.PARAMETER Exclude
An optional array of one or more Scoped Variable Names to Exclude from converting. (ie: Session, Using, or a Name from Add-PodeScopedVariable)

.EXAMPLE
$ScriptBlock, $usingVars = Convert-PodeScopedVariables -ScriptBlock $ScriptBlock -PSSession $PSCmdlet.SessionState

.EXAMPLE
$ScriptBlock = Convert-PodeScopedVariables -ScriptBlock $ScriptBlock -Exclude Session, Using
#>
function Convert-PodeScopedVariables {
    [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseSingularNouns', '')]
    [CmdletBinding()]
    [OutputType([System.Object[]])]
    [OutputType([scriptblock])]
    param(
        [Parameter(ValueFromPipeline = $true)]
        [scriptblock]
        $ScriptBlock,

        [Parameter()]
        [System.Management.Automation.SessionState]
        $PSSession,

        [Parameter()]
        [string[]]
        $Exclude
    )

    # do nothing if no scriptblock
    if ($null -eq $ScriptBlock) {
        return $ScriptBlock
    }

    # using vars
    $usingVars = $null

    # loop through each defined scoped variable and convert, unless excluded
    foreach ($key in $PodeContext.Server.ScopedVariables.Keys) {
        # excluded?
        if ($Exclude -icontains $key) {
            continue
        }

        # convert scoped var
        $ScriptBlock, $otherResults = Convert-PodeScopedVariable -Name $key -ScriptBlock $ScriptBlock -PSSession $PSSession

        # using vars?
        if (($null -ne $otherResults) -and ($key -ieq 'using')) {
            $usingVars = $otherResults
        }
    }

    # return just the scriptblock, or include using vars as well
    if ($null -ne $usingVars) {
        return $ScriptBlock, $usingVars
    }

    return $ScriptBlock
}

<#
.SYNOPSIS
Converts a Scoped Variable within a given ScriptBlock.

.DESCRIPTION
Converts a Scoped Variable within a given ScriptBlock, and returns the updated ScriptBlock back, including any
other values that will need to be supplied as parameters to the ScriptBlock first.

.PARAMETER Name
The Name of the Scoped Variable to convert. (ie: Session, Using, or a Name from Add-PodeScopedVariable)

.PARAMETER ScriptBlock
The ScriptBlock to be converted.

.PARAMETER PSSession
An optional SessionState object, used to retrieve using-variable values or other values where scope is required.

.EXAMPLE
$ScriptBlock = Convert-PodeScopedVariable -Name State -ScriptBlock $ScriptBlock

.EXAMPLE
$ScriptBlock, $otherResults = Convert-PodeScopedVariable -Name Using -ScriptBlock $ScriptBlock
#>
function Convert-PodeScopedVariable {
    [CmdletBinding()]
    [OutputType([scriptblock])]
    param(
        [Parameter(Mandatory = $true)]
        [string]
        $Name,

        [Parameter(ValueFromPipeline = $true)]
        [scriptblock]
        $ScriptBlock,

        [Parameter()]
        [System.Management.Automation.SessionState]
        $PSSession
    )

    # do nothing if no scriptblock
    if ($null -eq $ScriptBlock) {
        return $ScriptBlock
    }

    # check if scoped var defined
    if (!(Test-PodeScopedVariable -Name $Name)) {
        # Scoped Variable not found
        throw ($PodeLocale.scopedVariableNotFoundExceptionMessage -f $Name)
    }

    # get the scoped var metadata
    $scopedVar = $PodeContext.Server.ScopedVariables[$Name]

    # invoke the logic for the appropriate conversion type required - internal function map, custom scriptblock, or simple replace
    switch ($scopedVar.Type) {
        'internal' {
            switch ($scopedVar.Name) {
                'using' {
                    return Convert-PodeScopedVariableInbuiltUsing -ScriptBlock $ScriptBlock -PSSession $PSSession
                }
            }
        }

        'scriptblock' {
            return Invoke-PodeScriptBlock `
                -ScriptBlock $scopedVar.ScriptBlock `
                -Arguments $ScriptBlock, $PSSession, $scopedVar.Get.Pattern, $scopedVar.Set.Pattern `
                -Splat `
                -Return `
                -NoNewClosure
        }

        'replace' {
            # convert scriptblock to string
            $strScriptBlock = "$($ScriptBlock)"

            # see if the script contains any form of the scoped variable, and if not just return
            $found = $strScriptBlock -imatch "\`$$($Name)\:"
            if (!$found) {
                return $ScriptBlock
            }

            # loop and replace "set" syntax if replace template supplied
            if (![string]::IsNullOrEmpty($scopedVar.Set.Replace)) {
                while ($strScriptBlock -imatch $scopedVar.Set.Pattern) {
                    $setReplace = $scopedVar.Set.Replace.Replace('{{name}}', $Matches['name'])
                    $strScriptBlock = $strScriptBlock.Replace($Matches['full'], $setReplace)
                }
            }

            # loop and replace "get" syntax
            while ($strScriptBlock -imatch $scopedVar.Get.Pattern) {
                $getReplace = $scopedVar.Get.Replace.Replace('{{name}}', $Matches['name'])
                $strScriptBlock = $strScriptBlock.Replace($Matches['full'], "($($getReplace))")
            }

            # convert update scriptblock back
            return [scriptblock]::Create($strScriptBlock)
        }
    }
}

<#
.SYNOPSIS
Adds a new Scoped Variable.

.DESCRIPTION
Adds a new Scoped Variable, to make calling certain functions simpler.
For example "$state:Name" instead of "Get-PodeState" and "Set-PodeState".

.PARAMETER Name
The Name of the Scoped Variable.

.PARAMETER GetReplace
A template to be used when converting "$var = $SV:<name>" to a "Get-SVValue -Name <name>" syntax.
You can use the "{{name}}" placeholder to show where the <name> would be placed in the conversion. The result will also be automatically wrapped in brackets.
For example, "$var = $state:<name>" to "Get-PodeState -Name <name>" would need a GetReplace value of "Get-PodeState -Name '{{name}}'".

.PARAMETER SetReplace
An optional template to be used when converting "$SV:<name> = <value>" to a "Set-SVValue -Name <name> -Value <value>" syntax.
You can use the "{{name}}" placeholder to show where the <name> would be placed in the conversion. The <value> will automatically be appended to the end.
For example, "$state:<name> = <value>" to "Set-PodeState -Name <name> -Value <value>" would need a SetReplace value of "Set-PodeState -Name '{{name}}' -Value ".

.PARAMETER ScriptBlock
For more advanced conversions, that aren't as simple as a simple find/replace, you can supply a ScriptBlock instead.
This ScriptBlock will be supplied ScriptBlock to convert, followed by a SessionState object, and the Get/Set regex patterns, as parameters.
The ScriptBlock should returned a converted ScriptBlock that works, plus an optional array of values that should be supplied to the ScriptBlock when invoked.

.EXAMPLE
Add-PodeScopedVariable -Name 'cache' -SetReplace "Set-PodeCache -Key '{{name}}' -InputObject " -GetReplace "Get-PodeCache -Key '{{name}}'"

.EXAMPLE
Add-PodeScopedVariable -Name 'config' -ScriptBlock {
    param($ScriptBlock, $SessionState, $GetPattern, $SetPattern)
    $strScriptBlock = "$($ScriptBlock)"
    $template = "(Get-PodeConfig).'{{name}}'"

    # allows "$port = $config:port" instead of "$port = (Get-PodeConfig).port"
    while ($strScriptBlock -imatch $GetPattern) {
        $getReplace = $template.Replace('{{name}}', $Matches['name'])
        $strScriptBlock = $strScriptBlock.Replace($Matches['full'], "($($getReplace))")
    }

    return [scriptblock]::Create($strScriptBlock)
}
#>
function Add-PodeScopedVariable {
    [CmdletBinding(DefaultParameterSetName = 'Replace')]
    param(
        [Parameter(Mandatory = $true)]
        [string]
        $Name,

        [Parameter(Mandatory = $true, ParameterSetName = 'Replace')]
        [string]
        $GetReplace,

        [Parameter(ParameterSetName = 'Replace')]
        [string]
        $SetReplace = $null,

        [Parameter(Mandatory = $true, ParameterSetName = 'ScriptBlock')]
        [scriptblock]
        $ScriptBlock
    )

    Add-PodeScopedVariableInternal @PSBoundParameters
}

<#
.SYNOPSIS
Removes a Scoped Variable.

.DESCRIPTION
Removes a Scoped Variable.

.PARAMETER Name
The Name of a Scoped Variable to remove.

.EXAMPLE
Remove-PodeScopedVariable -Name State
#>
function Remove-PodeScopedVariable {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $true)]
        [string]
        $Name
    )

    $null = $PodeContext.Server.ScopedVariables.Remove($Name)
}

<#
.SYNOPSIS
Tests if a Scoped Variable exists.

.DESCRIPTION
Tests if a Scoped Variable exists.

.PARAMETER Name
The Name of the Scoped Variable to check.

.EXAMPLE
if (Test-PodeScopedVariable -Name $Name) { ... }
#>
function Test-PodeScopedVariable {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $true)]
        [string]
        $Name
    )

    return $PodeContext.Server.ScopedVariables.Contains($Name)
}

<#
.SYNOPSIS
Removes all Scoped Variables.

.DESCRIPTION
Removes all Scoped Variables.

.EXAMPLE
Clear-PodeScopedVariables
#>
function Clear-PodeScopedVariables {
    [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseSingularNouns', '')]
    [CmdletBinding()]
    param()

    $null = $PodeContext.Server.ScopedVariables.Clear()
}

<#
.SYNOPSIS
Get a Scoped Variable(s).

.DESCRIPTION
Get a Scoped Variable(s).

.PARAMETER Name
The Name of the Scoped Variable(s) to retrieve.

.EXAMPLE
Get-PodeScopedVariable -Name State

.EXAMPLE
Get-PodeScopedVariable -Name State, Using
#>
function Get-PodeScopedVariable {
    [CmdletBinding()]
    [OutputType([System.Object[]])]
    param(
        [Parameter()]
        [string[]]
        $Name
    )

    # return all if no Name
    if ([string]::IsNullOrEmpty($Name) -or ($Name.Length -eq 0)) {
        return $PodeContext.Server.ScopedVariables.Values
    }

    # return filtered
    return @(foreach ($n in $Name) {
            $PodeContext.Server.ScopedVariables[$n]
        })
}

<#
.SYNOPSIS
Automatically loads Scoped Variable ps1 files

.DESCRIPTION
Automatically loads Scoped Variable ps1 files from either a /scoped-vars folder, or a custom folder. Saves space dot-sourcing them all one-by-one.

.PARAMETER Path
Optional Path to a folder containing ps1 files, can be relative or literal.

.EXAMPLE
Use-PodeScopedVariables

.EXAMPLE
Use-PodeScopedVariables -Path './my-vars'
#>
function Use-PodeScopedVariables {
    [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseSingularNouns', '')]
    [CmdletBinding()]
    param(
        [Parameter()]
        [string]
        $Path
    )

    Use-PodeFolder -Path $Path -DefaultPath 'scoped-vars'
}
src\Public\Secrets.ps1
<#
.SYNOPSIS
Register a Secret Vault.

.DESCRIPTION
Register a Secret Vault, which is defined by either custom logic or using the SecretManagement module.

.PARAMETER Name
The unique friendly Name of the Secret Vault within Pode.

.PARAMETER VaultParameters
A hashtable of extra parameters that should be supplied to either the SecretManagement module, or custom scriptblocks.

.PARAMETER UnlockSecret
An optional Secret to be used to unlock the Secret Vault if need.

.PARAMETER UnlockSecureSecret
An optional Secret, as a SecureString, to be used to unlock the Secret Vault if need.

.PARAMETER UnlockInterval
An optional number of minutes that Pode will periodically check/unlock the Secret Vault. (Default: 0)

.PARAMETER NoUnlock
If supplied, the Secret Vault will not be unlocked after registration. To unlock you'll need to call Unlock-PodeSecretVault.

.PARAMETER CacheTtl
An optional number of minutes that Secrets should be cached for. (Default: 0)

.PARAMETER InitScriptBlock
An optional scriptblock to run before the Secret Vault is registered, letting you initialise any connection, contexts, etc.

.PARAMETER VaultName
For SecretManagement module Secret Vaults, you can use thie parameter to specify the actual Vault name, and use the above Name parameter as a more friendly name if required.

.PARAMETER ModuleName
For SecretManagement module Secret Vaults, this is the name/path of the extension module to be used.

.PARAMETER ScriptBlock
For custom Secret Vaults, this is a scriptblock used to read the Secret from the Vault.

.PARAMETER UnlockScriptBlock
For custom Secret Vaults, this is an optional scriptblock used to unlock the Secret Vault.

.PARAMETER RemoveScriptBlock
For custom Secret Vaults, this is an optional scriptblock used to remove a Secret from the Vault.

.PARAMETER SetScriptBlock
For custom Secret Vaults, this is an optional scriptblock used to create/update a Secret in the Vault.

.PARAMETER UnregisterScriptBlock
For custom Secret Vaults, this is an optional scriptblock used unregister the Secret Vault with any custom clean-up logic.

.EXAMPLE
Register-PodeSecretVault -Name 'VaultName' -ModuleName 'Az.KeyVault' -VaultParameters @{ AZKVaultName = $name; SubscriptionId = $subId }

.EXAMPLE
Register-PodeSecretVault -Name 'VaultName' -VaultParameters @{ Address = 'http://127.0.0.1:8200' } -ScriptBlock { ... }
#>
function Register-PodeSecretVault {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $true)]
        [string]
        $Name,

        [Parameter()]
        [hashtable]
        $VaultParameters,

        [Parameter()]
        [string]
        $UnlockSecret,

        [Parameter()]
        [securestring]
        $UnlockSecureSecret,

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

        [switch]
        $NoUnlock,

        [Parameter()]
        [int]
        $CacheTtl = 0, # in minutes

        [Parameter()]
        [scriptblock]
        $InitScriptBlock,

        [Parameter(ParameterSetName = 'SecretManagement')]
        [string]
        $VaultName,

        [Parameter(Mandatory = $true, ParameterSetName = 'SecretManagement')]
        [Alias('Module')]
        [string]
        $ModuleName,

        [Parameter(Mandatory = $true, ParameterSetName = 'Custom')]
        [scriptblock]
        $ScriptBlock, # Read a secret

        [Parameter(ParameterSetName = 'Custom')]
        [Alias('Unlock')]
        [scriptblock]
        $UnlockScriptBlock,

        [Parameter(ParameterSetName = 'Custom')]
        [Alias('Remove')]
        [scriptblock]
        $RemoveScriptBlock,

        [Parameter(ParameterSetName = 'Custom')]
        [Alias('Set')]
        [scriptblock]
        $SetScriptBlock,

        [Parameter(ParameterSetName = 'Custom')]
        [Alias('Unregister')]
        [scriptblock]
        $UnregisterScriptBlock
    )

    # has the vault already been registered?
    if (Test-PodeSecretVault -Name $Name) {
        $autoImported = [string]::Empty
        if ($PodeContext.Server.Secrets.Vaults[$Name].AutoImported) {
            $autoImported = ' from auto-importing'
        }
        # A Secret Vault with the name {0} has already been registered{1}
        throw ($PodeLocale.secretVaultAlreadyRegisteredAutoImportExceptionMessage -f $Name, $autoImported)
    }

    # base vault config
    if (![string]::IsNullOrEmpty($UnlockSecret)) {
        $UnlockSecureSecret = $UnlockSecret | ConvertTo-SecureString -AsPlainText -Force
    }

    $vault = @{
        Name         = $Name
        Type         = $PSCmdlet.ParameterSetName.ToLowerInvariant()
        Parameters   = $VaultParameters
        AutoImported = $false
        LockableName = "__Pode_SecretVault_$($Name)__"
        Unlock       = @{
            Secret   = $UnlockSecureSecret
            Expiry   = $null
            Interval = $UnlockInterval
            Enabled  = (!(Test-PodeIsEmpty $UnlockSecureSecret))
        }
        Cache        = @{
            Ttl     = $CacheTtl
            Enabled = ($CacheTtl -gt 0)
        }
    }

    # initialise the secret vault
    if ($null -ne $InitScriptBlock) {
        $vault | Initialize-PodeSecretVault -ScriptBlock $InitScriptBlock
    }

    # set vault config depending on vault type
    switch ($vault.Type) {
        'custom' {
            $vault | Register-PodeSecretCustomVault `
                -ScriptBlock $ScriptBlock `
                -UnlockScriptBlock $UnlockScriptBlock `
                -RemoveScriptBlock $RemoveScriptBlock `
                -SetScriptBlock $SetScriptBlock `
                -UnregisterScriptBlock $UnregisterScriptBlock
        }

        'secretmanagement' {
            $vault | Register-PodeSecretManagementVault `
                -VaultName $VaultName `
                -ModuleName $ModuleName
        }
    }

    # create timer to clear cached secrets every minute
    Start-PodeSecretCacheHousekeeper

    # create a lockable so secrets are thread safe
    New-PodeLockable -Name $vault.LockableName

    # add vault config to context
    $PodeContext.Server.Secrets.Vaults[$Name] = $vault

    # unlock the vault?
    if (!$NoUnlock -and $vault.Unlock.Enabled) {
        Unlock-PodeSecretVault -Name $Name
    }
}

<#
.SYNOPSIS
Unregister a Secret Vault.

.DESCRIPTION
Unregister a Secret Vault. If the Vault was via the SecretManagement module it will also be unregistered there as well.

.PARAMETER Name
The Name of the Secret Vault in Pode to unregister.

.EXAMPLE
Unregister-PodeSecretVault -Name 'VaultName'
#>
function Unregister-PodeSecretVault {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $true)]
        [string]
        $Name
    )

    # has the vault been registered?
    if (!(Test-PodeSecretVault -Name $Name)) {
        return
    }

    # get vault
    $vault = $PodeContext.Server.Secrets.Vaults[$Name]

    # unlock depending on vault type, and set expiry
    switch ($vault.Type) {
        'custom' {
            $vault | Unregister-PodeSecretCustomVault
        }

        'secretmanagement' {
            $vault | Unregister-PodeSecretManagementVault
        }
    }

    # unregister from Pode
    $null = $PodeContext.Server.Secrets.Vaults.Remove($Name)
}

<#
.SYNOPSIS
Unlock the Secret Vault.

.DESCRIPTION
Unlock the Secret Vault.

.PARAMETER Name
The Name of the Secret Vault in Pode to be unlocked.

.EXAMPLE
Unlock-PodeSecretVault -Name 'VaultName'
#>
function Unlock-PodeSecretVault {
    param(
        [Parameter(Mandatory = $true)]
        [string]
        $Name
    )

    # has the vault been registered?
    if (!(Test-PodeSecretVault -Name $Name)) {
        # No Secret Vault with the name has been registered
        throw ($PodeLocale.noSecretVaultRegisteredExceptionMessage -f $Vault)
    }

    # get vault
    $vault = $PodeContext.Server.Secrets.Vaults[$Name]
    $expiry = $null

    # is unlocking even enabled?
    if (!$vault.Unlock.Enabled) {
        return
    }

    # unlock depending on vault type, and set expiry
    $expiry = Lock-PodeObject -Name $vault.LockableName -Return -ScriptBlock {
        switch ($vault.Type) {
            'custom' {
                return ($vault | Unlock-PodeSecretCustomVault)
            }

            'secretmanagement' {
                return ($vault | Unlock-PodeSecretManagementVault)
            }
        }
    }

    # if we have an expiry returned, set to UTC and configure unlock schedule
    if ($null -ne $expiry) {
        $expiry = ([datetime]$expiry).ToUniversalTime()
        if ($expiry -le [datetime]::UtcNow) {
            # Secret Vault unlock expiry date is in the past (UTC)
            throw ($PodeLocale.secretVaultUnlockExpiryDateInPastExceptionMessage -f $expiry)
        }

        $vault.Unlock.Expiry = $expiry
        Start-PodeSecretVaultUnlocker
    }
}

<#
.SYNOPSIS
Fetches and returns information of a Secret Vault.

.DESCRIPTION
Fetches and returns information of a Secret Vault.

.PARAMETER Name
The Name(s) of a Secret Vault to retrieve.

.EXAMPLE
$vault = Get-PodeSecretVault -Name 'VaultName'

.EXAMPLE
$vaults = Get-PodeSecretVault -Name 'VaultName1', 'VaultName2'
#>
function Get-PodeSecretVault {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $true)]
        [string[]]
        $Name
    )

    $vaults = $PodeContext.Server.Secrets.Vaults.Values

    # further filter by vault names
    if (($null -ne $Name) -and ($Name.Length -gt 0)) {
        $vaults = @(foreach ($_name in $Name) {
                foreach ($vault in $vaults) {
                    if ($vault.Name -ine $_name) {
                        continue
                    }

                    $vault
                }
            })
    }

    # return
    return $vaults
}

<#
.SYNOPSIS
Tests if a Secret Vault has been registered.

.DESCRIPTION
Tests if a Secret Vault has been registered.

.PARAMETER Name
The Name of the Secret Vault to test.

.EXAMPLE
if (Test-PodeSecretVault -Name 'VaultName') { ... }
#>
function Test-PodeSecretVault {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $true)]
        [string]
        $Name
    )

    return (($null -ne $PodeContext.Server.Secrets.Vaults) -and $PodeContext.Server.Secrets.Vaults.ContainsKey($Name))
}

<#
.SYNOPSIS
Mount a Secret from a Secret Vault.

.DESCRIPTION
Mount a Secret from a Secret Vault, so it can be more easily referenced and support caching.

.PARAMETER Name
A unique friendly Name for the Secret.

.PARAMETER Vault
The friendly name of the Secret Vault this Secret can be found in.

.PARAMETER Property
An optional array of Properties to be returned if the Secret contains multiple properties.

.PARAMETER ExpandProperty
An optional Property to be expanded from the Secret and return if it contains multiple properties.

.PARAMETER Key
The Key/Path of the Secret within the Secret Vault.

.PARAMETER ArgumentList
An optional array of Arguments to be supplied to a custom Secret Vault's scriptblocks.

.PARAMETER CacheTtl
An optional number of minutes to Cache the Secret's value for. You can use this parameter to override the Secret Vault's value. (Default: -1)
If the value is -1 it uses the Secret Vault's CacheTtl. A value of 0 is to disable caching for this Secret. A value >0 overrides the Secret Vault.

.EXAMPLE
Mount-PodeSecret -Name 'SecretName' -Vault 'VaultName' -Key 'path/to/secret' -ExpandProperty 'foo'

.EXAMPLE
Mount-PodeSecret -Name 'SecretName' -Vault 'VaultName' -Key 'key_of_secret' -CacheTtl 5
#>
function Mount-PodeSecret {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $true)]
        [string]
        $Name,

        [Parameter(Mandatory = $true)]
        [string]
        $Vault,

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

        [Parameter()]
        [string]
        $ExpandProperty,

        [Parameter(Mandatory = $true)]
        [string]
        $Key,

        [Parameter()]
        [object[]]
        $ArgumentList,

        # in minutes (-1 means use the vault default, 0 is off, anything higher than 0 is an override)
        [Parameter()]
        [int]
        $CacheTtl = -1
    )

    # has the secret been mounted already?
    if (Test-PodeSecret -Name $Name) {
        # A Secret with the name has already been mounted
        throw ($PodeLocale.secretAlreadyMountedExceptionMessage -f $Name)
    }

    # does the vault exist?
    if (!(Test-PodeSecretVault -Name $Vault)) {
        # No Secret Vault with the name has been registered
        throw ($PodeLocale.noSecretVaultRegisteredExceptionMessage -f $Vault)
    }

    # check properties
    if (!(Test-PodeIsEmpty $Property) -and !(Test-PodeIsEmpty $ExpandProperty)) {
        # Parameters 'Property' and 'ExpandPropery' are mutually exclusive
        throw ($PodeLocale.parametersMutuallyExclusiveExceptionMessage -f 'Property' , 'ExpandPropery')
    }

    # which cache value?
    if ($CacheTtl -lt 0) {
        $CacheTtl = [int]$PodeContext.Server.Secrets.Vaults[$Vault].Cache.Ttl
    }

    # mount secret reference
    $props = $Property
    if (![string]::IsNullOrWhiteSpace($ExpandProperty)) {
        $props = $ExpandProperty
    }

    $PodeContext.Server.Secrets.Keys[$Name] = @{
        Key        = $Key
        Properties = @{
            Fields  = $props
            Expand  = (![string]::IsNullOrWhiteSpace($ExpandProperty))
            Enabled = (!(Test-PodeIsEmpty $props))
        }
        Vault      = $Vault
        Arguments  = $ArgumentList
        Cache      = @{
            Ttl     = $CacheTtl
            Enabled = ($CacheTtl -gt 0)
        }
    }
}

<#
.SYNOPSIS
Dismount a previously mounted Secret.

.DESCRIPTION
Dismount a previously mounted Secret.

.PARAMETER Name
The friendly Name of the Secret.

.PARAMETER Remove
If supplied, the Secret will also be removed from the Secret Vault as well.

.EXAMPLE
Dismount-PodeSecret -Name 'SecretName'

.EXAMPLE
Dismount-PodeSecret -Name 'SecretName' -Remove
#>
function Dismount-PodeSecret {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $true)]
        [string]
        $Name,

        [switch]
        $Remove
    )

    # do nothing if the secret hasn't been mounted, unless Remove is specified
    if (!(Test-PodeSecret -Name $Name)) {
        if ($Remove) {
            # No Secret named has been mounted
            throw ($PodeLocale.noSecretNamedMountedExceptionMessage -f $Name)
        }

        return
    }

    # if "remove" switch passed, remove the secret from the vault as well
    if ($Remove) {
        $secret = $PodeContext.Server.Secrets.Keys[$Name]
        Remove-PodeSecret -Key $secret.Key -Vault $secret.Vault -ArgumentList $secret.Arguments
    }

    # remove reference
    $null = $PodeContext.Server.Secrets.Keys.Remove($Name)
}

<#
.SYNOPSIS
Retrieve the value of a mounted Secret.

.DESCRIPTION
Retrieve the value of a mounted Secret from a Secret Vault. You can also use "$value = $secret:<NAME>" syntax in certain places.

.PARAMETER Name
The friendly Name of a Secret.

.EXAMPLE
$value = Get-PodeSecret -Name 'SecretName'

.EXAMPLE
$value = $secret:SecretName
#>
function Get-PodeSecret {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $true)]
        [string]
        $Name
    )

    # has the secret been mounted?
    if (!(Test-PodeSecret -Name $Name)) {
        # No Secret named has been mounted
        throw ($PodeLocale.noSecretNamedMountedExceptionMessage -f $Name)
    }

    # get the secret and vault
    $secret = $PodeContext.Server.Secrets.Keys[$Name]

    # is the value cached?
    if ($secret.Cache.Enabled -and ($null -ne $secret.Cache.Expiry) -and ($secret.Cache.Expiry -gt [datetime]::UtcNow)) {
        return $secret.Cache.Value
    }

    # fetch the secret depending on vault type
    $vault = $PodeContext.Server.Secrets.Vaults[$secret.Vault]
    $value = Lock-PodeObject -Name $vault.LockableName -Return -ScriptBlock {
        switch ($vault.Type) {
            'custom' {
                return Get-PodeSecretCustomKey -Vault $secret.Vault -Key $secret.Key -ArgumentList $secret.Arguments
            }

            'secretmanagement' {
                return Get-PodeSecretManagementKey -Vault $secret.Vault -Key $secret.Key
            }
        }
    }

    # filter the value by any properties
    if ($secret.Properties.Enabled) {
        if ($secret.Properties.Expand) {
            $value = Select-Object -InputObject $value -ExpandProperty $secret.Properties.Fields
        }
        else {
            $value = Select-Object -InputObject $value -Property $secret.Properties.Fields
        }
    }

    # cache the value if needed
    if ($secret.Cache.Enabled) {
        $secret.Cache.Value = $value
        $secret.Cache.Expiry = [datetime]::UtcNow.AddMinutes($secret.Cache.Ttl)
    }

    # return value
    return $value
}

<#
.SYNOPSIS
Test if a Secret has been mounted.

.DESCRIPTION
Test if a Secret has been mounted.

.PARAMETER Name
The friendly Name of a Secret.

.EXAMPLE
if (Test-PodeSecret -Name 'SecretName') { ... }
#>
function Test-PodeSecret {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $true)]
        [string]
        $Name
    )

    return (($null -ne $PodeContext.Server.Secrets.Keys) -and $PodeContext.Server.Secrets.Keys.ContainsKey($Name))
}

<#
.SYNOPSIS
Update the value of a mounted Secret.

.DESCRIPTION
Update the value of a mounted Secret in a Secret Vault. You can also use "$secret:<NAME> = $value" syntax in certain places.

.PARAMETER Name
The friendly Name of a Secret.

.PARAMETER InputObject
The value to use when updating the Secret.
Only the following object types are supported: byte[], string, securestring, pscredential, hashtable.

.PARAMETER Metadata
An optional Metadata hashtable.

.EXAMPLE
Update-PodeSecret -Name 'SecretName' -InputObject @{ key = value }

.EXAMPLE
Update-PodeSecret -Name 'SecretName' -InputObject 'value'

.EXAMPLE
$secret:SecretName = 'value'
#>
function Update-PodeSecret {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $true)]
        [string]
        $Name,

        #> byte[], string, securestring, pscredential, hashtable
        [Parameter(Mandatory = $true, Position = 0, ValueFromPipeline = $true )]
        [object]
        $InputObject,

        [Parameter()]
        [hashtable]
        $Metadata
    )
    begin {
        # has the secret been mounted?
        if (!(Test-PodeSecret -Name $Name)) {
            # No Secret named has been mounted
            throw ($PodeLocale.noSecretNamedMountedExceptionMessage -f $Name)
        }

        $pipelineItemCount = 0  # Initialize counter to track items in the pipeline.
    }

    process {
        $pipelineItemCount++  # Increment the counter for each item in the pipeline.
    }

    end {
        # Throw an error if more than one item is passed in the pipeline.
        if ($pipelineItemCount -gt 1) {
            throw ($PodeLocale.fnDoesNotAcceptArrayAsPipelineInputExceptionMessage -f $($MyInvocation.MyCommand.Name))
        }

        # make sure the value type is correct
        $InputObject = Protect-PodeSecretValueType -Value $InputObject

        # get the secret and vault
        $secret = $PodeContext.Server.Secrets.Keys[$Name]

        # reset the cache if enabled
        if ($secret.Cache.Enabled) {
            $secret.Cache.Value = $InputObject
            $secret.Cache.Expiry = [datetime]::UtcNow.AddMinutes($secret.Cache.Ttl)
        }

        # if we're expanding a property, convert this to a hashtable
        if ($secret.Properties.Enabled -and $secret.Properties.Expand) {
            $InputObject = @{
                "$($secret.Properties.Fields)" = $InputObject
            }
        }

        # set the secret depending on vault type
        $vault = $PodeContext.Server.Secrets.Vaults[$secret.Vault]
        Lock-PodeObject -Name $vault.LockableName -ScriptBlock {
            switch ($vault.Type) {
                'custom' {
                    Set-PodeSecretCustomKey -Vault $secret.Vault -Key $secret.Key -Value $InputObject -Metadata $Metadata -ArgumentList $secret.Arguments
                }

                'secretmanagement' {
                    Set-PodeSecretManagementKey -Vault $secret.Vault -Key $secret.Key -Value $InputObject -Metadata $Metadata
                }
            }
        }
    }
}

<#
.SYNOPSIS
Remove a Secret from a Secret Vault.

.DESCRIPTION
Remove a Secret from a Secret Vault. To remove a mounted Secret, you can pass the Remove switch to Dismount-PodeSecret.

.PARAMETER Key
The Key/Path of the Secret within the Secret Vault.

.PARAMETER Vault
The friendly name of the Secret Vault this Secret can be found in.

.PARAMETER ArgumentList
An optional array of Arguments to be supplied to a custom Secret Vault's scriptblocks.

.EXAMPLE
Remove-PodeSecret -Key 'path/to/secret' -Vault 'VaultName'
#>
function Remove-PodeSecret {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $true)]
        [string]
        $Key,

        [Parameter(Mandatory = $true)]
        [string]
        $Vault,

        [Parameter()]
        [object[]]
        $ArgumentList
    )

    # has the vault been registered?
    if (!(Test-PodeSecretVault -Name $Vault)) {
        # No Secret Vault with the name has been registered
        throw ($PodeLocale.noSecretVaultRegisteredExceptionMessage -f $Vault)
    }

    # remove the secret depending on vault type
    $_vault = $PodeContext.Server.Secrets.Vaults[$Vault]
    Lock-PodeObject -Name $_vault.LockableName -ScriptBlock {
        switch ($_vault.Type) {
            'custom' {
                Remove-PodeSecretCustomKey -Vault $Vault -Key $Key -ArgumentList $ArgumentList
            }

            'secretmanagement' {
                Remove-PodeSecretManagementKey -Vault $Vault -Key $Key
            }
        }
    }
}

<#
.SYNOPSIS
Read a Secret from a Secret Vault.

.DESCRIPTION
Read a Secret from a Secret Vault.

.PARAMETER Key
The Key/Path of the Secret within the Secret Vault.

.PARAMETER Vault
The friendly name of the Secret Vault this Secret can be found in.

.PARAMETER Property
An optional array of Properties to be returned if the Secret contains multiple properties.

.PARAMETER ExpandProperty
An optional Property to be expanded from the Secret and return if it contains multiple properties.

.PARAMETER ArgumentList
An optional array of Arguments to be supplied to a custom Secret Vault's scriptblocks.

.EXAMPLE
$value = Read-PodeSecret -Key 'path/to/secret' -Vault 'VaultName'

.EXAMPLE
$value = Read-PodeSecret -Key 'key_of_secret' -Vault 'VaultName' -Property prop1, prop2
#>
function Read-PodeSecret {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $true)]
        [string]
        $Key,

        [Parameter(Mandatory = $true)]
        [string]
        $Vault,

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

        [Parameter()]
        [string]
        $ExpandProperty,

        [Parameter()]
        [object[]]
        $ArgumentList
    )

    # has the vault been registered?
    if (!(Test-PodeSecretVault -Name $Vault)) {
        # No Secret Vault with the name has been registered
        throw ($PodeLocale.noSecretVaultRegisteredExceptionMessage -f $Vault)
    }

    # fetch the secret depending on vault type
    $_vault = $PodeContext.Server.Secrets.Vaults[$Vault]
    $value = Lock-PodeObject -Name $_vault.LockableName -Return -ScriptBlock {
        switch ($_vault.Type) {
            'custom' {
                return Get-PodeSecretCustomKey -Vault $Vault -Key $Key -ArgumentList $ArgumentList
            }

            'secretmanagement' {
                return Get-PodeSecretManagementKey -Vault $Vault -Key $Key
            }
        }
    }

    # filter the value by any properties
    if (![string]::IsNullOrWhiteSpace($ExpandProperty)) {
        $value = Select-Object -InputObject $value -ExpandProperty $ExpandProperty
    }
    elseif (![string]::IsNullOrEmpty($Property)) {
        $value = Select-Object -InputObject $value -Property $Property
    }

    # return value
    return $value
}

<#
.SYNOPSIS
Create/update a Secret in a Secret Vault.

.DESCRIPTION
Create/update a Secret in a Secret Vault.

.PARAMETER Key
The Key/Path of the Secret within the Secret Vault.

.PARAMETER Vault
The friendly name of the Secret Vault this Secret should be created in.

.PARAMETER InputObject
The value to use when updating the Secret.
Only the following object types are supported: byte[], string, securestring, pscredential, hashtable.

.PARAMETER Metadata
An optional Metadata hashtable.

.PARAMETER ArgumentList
An optional array of Arguments to be supplied to a custom Secret Vault's scriptblocks.

.EXAMPLE
Set-PodeSecret -Key 'path/to/secret' -Vault 'VaultName' -InputObject 'value'

.EXAMPLE
Set-PodeSecret -Key 'key_of_secret' -Vault 'VaultName' -InputObject @{ key = value }
#>
function Set-PodeSecret {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $true)]
        [string]
        $Key,

        [Parameter(Mandatory = $true)]
        [string]
        $Vault,

        #> byte[], string, securestring, pscredential, hashtable
        [Parameter(Mandatory = $true, Position = 0, ValueFromPipeline = $true)]
        [object]
        $InputObject,

        [Parameter()]
        [hashtable]
        $Metadata,

        [Parameter()]
        [object[]]
        $ArgumentList
    )
    begin {
        # has the vault been registered?
        if (!(Test-PodeSecretVault -Name $Vault)) {
            # No Secret Vault with the name has been registered
            throw ($PodeLocale.noSecretVaultRegisteredExceptionMessage -f $Vault)
        }

        $pipelineItemCount = 0  # Initialize counter to track items in the pipeline.
    }

    process {
        $pipelineItemCount++  # Increment the counter for each item in the pipeline.
    }

    end {
        # Throw an error if more than one item is passed in the pipeline.
        if ($pipelineItemCount -gt 1) {
            throw ($PodeLocale.fnDoesNotAcceptArrayAsPipelineInputExceptionMessage -f $($MyInvocation.MyCommand.Name))
        }

        # make sure the value type is correct
        $InputObject = Protect-PodeSecretValueType -Value $InputObject

        # set the secret depending on vault type
        $_vault = $PodeContext.Server.Secrets.Vaults[$Vault]
        Lock-PodeObject -Name $_vault.LockableName -ScriptBlock {
            switch ($_vault.Type) {
                'custom' {
                    Set-PodeSecretCustomKey -Vault $Vault -Key $Key -Value $InputObject -Metadata $Metadata -ArgumentList $ArgumentList
                }

                'secretmanagement' {
                    Set-PodeSecretManagementKey -Vault $Vault -Key $Key -Value $InputObject -Metadata $Metadata
                }
            }
        }
    }
}
src\Public\Security.ps1
<#
.SYNOPSIS
Sets inbuilt definitions for security headers.

.DESCRIPTION
Sets inbuilt definitions for security headers, in either Simple or Strict types.

.PARAMETER Type
The Type of security to use.

.PARAMETER UseHsts
If supplied, the Strict-Transport-Security header will be set.

.PARAMETER XssBlock
If supplied, the X-XSS-Protection header will be set to blocking mode. (Default: Off)

.EXAMPLE
Set-PodeSecurity -Type Simple

.EXAMPLE
Set-PodeSecurity -Type Strict -UseHsts
#>
function Set-PodeSecurity {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $true)]
        [ValidateSet('Simple', 'Strict')]
        [string]
        $Type,

        [switch]
        $UseHsts,

        [switch]
        $XssBlock
    )

    # general headers
    Set-PodeSecurityContentTypeOptions

    Set-PodeSecurityPermissionsPolicy `
        -SyncXhr 'none' `
        -Fullscreen 'self' `
        -Camera 'none' `
        -Geolocation 'self' `
        -PictureInPicture 'self' `
        -Accelerometer 'none' `
        -Microphone 'none' `
        -Usb 'none' `
        -Autoplay 'self' `
        -Payment 'none' `
        -Magnetometer 'self' `
        -Gyroscope 'self' `
        -DisplayCapture 'self'

    Set-PodeSecurityCrossOrigin -Embed Require-Corp -Open Same-Origin -Resource Same-Origin
    Set-PodeSecurityAccessControl -Origin '*' -Methods '*' -Headers '*' -Duration 7200
    Set-PodeSecurityContentSecurityPolicy -Default 'self' -XssBlock:$XssBlock

    # only add hsts if specifiec
    if ($UseHsts) {
        Set-PodeSecurityStrictTransportSecurity -Duration 31536000 -IncludeSubDomains
    }

    # type specific headers
    switch ($Type.ToLowerInvariant()) {
        'simple' {
            Set-PodeSecurityFrameOptions -Type SameOrigin
            Set-PodeSecurityReferrerPolicy -Type Strict-Origin
        }

        'strict' {
            Set-PodeSecurityFrameOptions -Type Deny
            Set-PodeSecurityReferrerPolicy -Type No-Referrer
        }
    }

    # hide server info
    Hide-PodeSecurityServer
}

<#
.SYNOPSIS
Removes definitions for all security headers.

.DESCRIPTION
Removes definitions for all security headers.

.EXAMPLE
Remove-PodeSecurity
#>
function Remove-PodeSecurity {
    [CmdletBinding()]
    param()

    $PodeContext.Server.Security.Headers.Clear()
    Show-PodeSecurityServer
}

<#
.SYNOPSIS
Add definition for specified security header.

.DESCRIPTION
Add definition for specified security header.

.PARAMETER Name
The Name of the security header.

.PARAMETER Value
The Value of the security header.

.PARAMETER Append
Append the value to the header instead of replacing it

.EXAMPLE
Add-PodeSecurityHeader -Name 'X-Header-Name' -Value 'SomeValue'
#>
function Add-PodeSecurityHeader {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $true)]
        [string]
        $Name,

        [Parameter()]
        [string]
        $Value,

        [Parameter()]
        [switch]
        $Append
    )

    if ([string]::IsNullOrWhiteSpace($Value)) {
        return
    }

    if ($Append -and $PodeContext.Server.Security.Headers.ContainsKey($Name)) {
        $Headers = @(($PodeContext.Server.Security.Headers[$Name].split(',')).trim())
        if ($Headers -inotcontains $Value) {
            $Headers += $Value
            $PodeContext.Server.Security.Headers[$Name] = (($Headers.trim() | Select-Object -Unique) -join ', ')
        }
        else {
            return
        }
    }
    else {
        $PodeContext.Server.Security.Headers[$Name] = $Value
    }
}

<#
.SYNOPSIS
Removes definition for specified security header.

.DESCRIPTION
Removes definition for specified security header.

.PARAMETER Name
The Name of the security header.

.EXAMPLE
Remove-PodeSecurityHeader -Name 'X-Header-Name'
#>
function Remove-PodeSecurityHeader {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $true)]
        [string]
        $Name
    )

    $PodeContext.Server.Security.Headers.Remove($Name)
}

<#
.SYNOPSIS
Hide the Server HTTP Header from Responses

.DESCRIPTION
Hide the Server HTTP Header from Responses

.EXAMPLE
Hide-PodeSecurityServer
#>
function Hide-PodeSecurityServer {
    [CmdletBinding()]
    param()

    $PodeContext.Server.Security.ServerDetails = $false
}

<#
.SYNOPSIS
Show the Server HTTP Header on Responses

.DESCRIPTION
Show the Server HTTP Header on Responses

.EXAMPLE
Show-PodeSecurityServer
#>
function Show-PodeSecurityServer {
    [CmdletBinding()]
    param()

    $PodeContext.Server.Security.ServerDetails = $true
}

<#
.SYNOPSIS
Set a value for the X-Frame-Options header.

.DESCRIPTION
Set a value for the X-Frame-Options header.

.PARAMETER Type
The Type to use.

.EXAMPLE
Set-PodeSecurityFrameOptions -Type SameOrigin
#>
function Set-PodeSecurityFrameOptions {
    [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseSingularNouns', '')]
    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $true)]
        [ValidateSet('Deny', 'SameOrigin')]
        [string]
        $Type
    )

    Add-PodeSecurityHeader -Name 'X-Frame-Options' -Value $Type.ToUpperInvariant()
}

<#
.SYNOPSIS
Removes definition for the X-Frame-Options header.

.DESCRIPTION
Removes definition for the X-Frame-Options header.

.EXAMPLE
Remove-PodeSecurityFrameOptions
#>
function Remove-PodeSecurityFrameOptions {
    [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseSingularNouns', '')]
    [CmdletBinding()]
    param()

    Remove-PodeSecurityHeader -Name 'X-Frame-Options'
}

<#
.SYNOPSIS
Set the value to use for the Content-Security-Policy and X-XSS-Protection headers.

.DESCRIPTION
Set the value to use for the Content-Security-Policy and X-XSS-Protection headers.

.PARAMETER Default
The values to use for the Default portion of the header.

.PARAMETER Child
The values to use for the Child portion of the header.

.PARAMETER Connect
The values to use for the Connect portion of the header.

.PARAMETER Font
The values to use for the Font portion of the header.

.PARAMETER Frame
The values to use for the Frame portion of the header.

.PARAMETER Image
The values to use for the Image portion of the header.

.PARAMETER Manifest
The values to use for the Manifest portion of the header.

.PARAMETER Media
The values to use for the Media portion of the header.

.PARAMETER Object
The values to use for the Object portion of the header.

.PARAMETER Scripts
The values to use for the Scripts portion of the header.

.PARAMETER Style
The values to use for the Style portion of the header.

.PARAMETER BaseUri
The values to use for the BaseUri portion of the header.

.PARAMETER FormAction
The values to use for the FormAction portion of the header.

.PARAMETER FrameAncestor
The values to use for the FrameAncestor portion of the header.

.PARAMETER Sandbox
The value to use for the Sandbox portion of the header.

.PARAMETER UpgradeInsecureRequests
If supplied, the header will have the upgrade-insecure-requests value added.

.PARAMETER XssBlock
If supplied, the X-XSS-Protection header will be set to blocking mode. (Default: Off)

.EXAMPLE
Set-PodeSecurityContentSecurityPolicy -Default 'self'
#>
function Set-PodeSecurityContentSecurityPolicy {
    [CmdletBinding()]
    param(
        [Parameter()]
        [string[]]
        $Default,

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

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

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

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

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

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

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

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

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

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

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

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

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

        [Parameter()]
        [ValidateSet('', 'Allow-Downloads', 'Allow-Downloads-Without-User-Activation', 'Allow-Forms', 'Allow-Modals', 'Allow-Orientation-Lock',
            'Allow-Pointer-Lock', 'Allow-Popups', 'Allow-Popups-To-Escape-Sandbox', 'Allow-Presentation', 'Allow-Same-Origin', 'Allow-Scripts',
            'Allow-Storage-Access-By-User-Activation', 'Allow-Top-Navigation', 'Allow-Top-Navigation-By-User-Activation', 'None')]
        [string]
        $Sandbox = 'None',

        [switch]
        $UpgradeInsecureRequests,

        [switch]
        $XssBlock
    )

    Set-PodeSecurityContentSecurityPolicyInternal -Params $PSBoundParameters
}

<#
.SYNOPSIS
Adds additional values to already defined values for the Content-Security-Policy header.

.DESCRIPTION
Adds additional values to already defined values for the Content-Security-Policy header, instead of overriding them.

.PARAMETER Default
The values to add for the Default portion of the header.

.PARAMETER Child
The values to add for the Child portion of the header.

.PARAMETER Connect
The values to add for the Connect portion of the header.

.PARAMETER Font
The values to add for the Font portion of the header.

.PARAMETER Frame
The values to add for the Frame portion of the header.

.PARAMETER Image
The values to add for the Image portion of the header.

.PARAMETER Manifest
The values to add for the Manifest portion of the header.

.PARAMETER Media
The values to add for the Media portion of the header.

.PARAMETER Object
The values to add for the Object portion of the header.

.PARAMETER Scripts
The values to add for the Scripts portion of the header.

.PARAMETER Style
The values to add for the Style portion of the header.

.PARAMETER BaseUri
The values to add for the BaseUri portion of the header.

.PARAMETER FormAction
The values to add for the FormAction portion of the header.

.PARAMETER FrameAncestor
The values to add for the FrameAncestor portion of the header.

.PARAMETER Sandbox
The value to use for the Sandbox portion of the header.

.PARAMETER UpgradeInsecureRequests
If supplied, the header will have the upgrade-insecure-requests value added.

.EXAMPLE
Add-PodeSecurityContentSecurityPolicy -Default '*.twitter.com' -Image 'data'
#>
function Add-PodeSecurityContentSecurityPolicy {
    [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSPossibleIncorrectComparisonWithNull', '')]
    [CmdletBinding()]
    param(
        [Parameter()]
        [string[]]
        $Default,

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

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

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

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

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

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

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

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

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

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

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

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

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

        [Parameter()]
        [ValidateSet('', 'Allow-Downloads', 'Allow-Downloads-Without-User-Activation', 'Allow-Forms', 'Allow-Modals', 'Allow-Orientation-Lock',
            'Allow-Pointer-Lock', 'Allow-Popups', 'Allow-Popups-To-Escape-Sandbox', 'Allow-Presentation', 'Allow-Same-Origin', 'Allow-Scripts',
            'Allow-Storage-Access-By-User-Activation', 'Allow-Top-Navigation', 'Allow-Top-Navigation-By-User-Activation', 'None')]
        [string]
        $Sandbox = 'None',

        [switch]
        $UpgradeInsecureRequests
    )

    Set-PodeSecurityContentSecurityPolicyInternal -Params $PSBoundParameters -Append
}

<#
.SYNOPSIS
Removes definition for the Content-Security-Policy and X-XSS-Protection headers.

.DESCRIPTION
Removes definition for the Content-Security-Policy and X-XSS-Protection headers.

.EXAMPLE
Remove-PodeSecurityContentSecurityPolicy
#>
function Remove-PodeSecurityContentSecurityPolicy {
    [CmdletBinding()]
    param()

    Remove-PodeSecurityHeader -Name 'Content-Security-Policy'
    Remove-PodeSecurityHeader -Name 'X-XSS-Protection'
}

<#
.SYNOPSIS
Set the value to use for the Permissions-Policy header.

.DESCRIPTION
Set the value to use for the Permissions-Policy header.

.PARAMETER Accelerometer
The values to use for the Accelerometer portion of the header.

.PARAMETER AmbientLightSensor
The values to use for the AmbientLightSensor portion of the header.

.PARAMETER Autoplay
The values to use for the Autoplay portion of the header.

.PARAMETER Battery
The values to use for the Battery portion of the header.

.PARAMETER Camera
The values to use for the Camera portion of the header.

.PARAMETER DisplayCapture
The values to use for the DisplayCapture portion of the header.

.PARAMETER DocumentDomain
The values to use for the DocumentDomain portion of the header.

.PARAMETER EncryptedMedia
The values to use for the EncryptedMedia portion of the header.

.PARAMETER Fullscreen
The values to use for the Fullscreen portion of the header.

.PARAMETER Gamepad
The values to use for the Gamepad portion of the header.

.PARAMETER Geolocation
The values to use for the Geolocation portion of the header.

.PARAMETER Gyroscope
The values to use for the Gyroscope portion of the header.

.PARAMETER InterestCohort
The values to use for the InterestCohort portal of the header.

.PARAMETER LayoutAnimations
The values to use for the LayoutAnimations portion of the header.

.PARAMETER LegacyImageFormats
The values to use for the LegacyImageFormats portion of the header.

.PARAMETER Magnetometer
The values to use for the Magnetometer portion of the header.

.PARAMETER Microphone
The values to use for the Microphone portion of the header.

.PARAMETER Midi
The values to use for the Midi portion of the header.

.PARAMETER OversizedImages
The values to use for the OversizedImages portion of the header.

.PARAMETER Payment
The values to use for the Payment portion of the header.

.PARAMETER PictureInPicture
The values to use for the PictureInPicture portion of the header.

.PARAMETER PublicKeyCredentials
The values to use for the PublicKeyCredentials portion of the header.

.PARAMETER Speakers
The values to use for the Speakers portion of the header.

.PARAMETER SyncXhr
The values to use for the SyncXhr portion of the header.

.PARAMETER UnoptimisedImages
The values to use for the UnoptimisedImages portion of the header.

.PARAMETER UnsizedMedia
The values to use for the UnsizedMedia portion of the header.

.PARAMETER Usb
The values to use for the Usb portion of the header.

.PARAMETER ScreenWakeLake
The values to use for the ScreenWakeLake portion of the header.

.PARAMETER WebShare
The values to use for the WebShare portion of the header.

.PARAMETER XrSpatialTracking
The values to use for the XrSpatialTracking portion of the header.

#>
function Set-PodeSecurityPermissionsPolicy {
    [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSPossibleIncorrectComparisonWithNull', '')]
    [CmdletBinding()]
    param(
        [Parameter()]
        [string[]]
        $Accelerometer,

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

        [Parameter()]
        [string[]]
        $XrSpatialTracking
    )

    Set-PodeSecurityPermissionsPolicyInternal -Params $PSBoundParameters
}

<#
.SYNOPSIS
Adds additional values to already defined values for the Permissions-Policy header.

.DESCRIPTION
Adds additional values to already defined values for the Permissions-Policy header, instead of overriding them.

.PARAMETER Accelerometer
The values to add for the Accelerometer portion of the header.

.PARAMETER AmbientLightSensor
The values to add for the AmbientLightSensor portion of the header.

.PARAMETER Autoplay
The values to add for the Autoplay portion of the header.

.PARAMETER Battery
The values to add for the Battery portion of the header.

.PARAMETER Camera
The values to add for the Camera portion of the header.

.PARAMETER DisplayCapture
The values to add for the DisplayCapture portion of the header.

.PARAMETER DocumentDomain
The values to add for the DocumentDomain portion of the header.

.PARAMETER EncryptedMedia
The values to add for the EncryptedMedia portion of the header.

.PARAMETER Fullscreen
The values to add for the Fullscreen portion of the header.

.PARAMETER Gamepad
The values to add for the Gamepad portion of the header.

.PARAMETER Geolocation
The values to add for the Geolocation portion of the header.

.PARAMETER Gyroscope
The values to add for the Gyroscope portion of the header.

.PARAMETER InterestCohort
The values to use for the InterestCohort portal of the header.

.PARAMETER LayoutAnimations
The values to add for the LayoutAnimations portion of the header.

.PARAMETER LegacyImageFormats
The values to add for the LegacyImageFormats portion of the header.

.PARAMETER Magnetometer
The values to add for the Magnetometer portion of the header.

.PARAMETER Microphone
The values to add for the Microphone portion of the header.

.PARAMETER Midi
The values to add for the Midi portion of the header.

.PARAMETER OversizedImages
The values to add for the OversizedImages portion of the header.

.PARAMETER Payment
The values to add for the Payment portion of the header.

.PARAMETER PictureInPicture
The values to add for the PictureInPicture portion of the header.

.PARAMETER PublicKeyCredentials
The values to add for the PublicKeyCredentials portion of the header.

.PARAMETER Speakers
The values to add for the Speakers portion of the header.

.PARAMETER SyncXhr
The values to add for the SyncXhr portion of the header.

.PARAMETER UnoptimisedImages
The values to add for the UnoptimisedImages portion of the header.

.PARAMETER UnsizedMedia
The values to add for the UnsizedMedia portion of the header.

.PARAMETER Usb
The values to add for the Usb portion of the header.

.PARAMETER ScreenWakeLake
The values to add for the ScreenWakeLake portion of the header.

.PARAMETER WebShare
The values to add for the WebShare portion of the header.

.PARAMETER XrSpatialTracking
The values to add for the XrSpatialTracking portion of the header.

.EXAMPLE
Add-PodeSecurityPermissionsPolicy -AmbientLightSensor 'none'
#>
function Add-PodeSecurityPermissionsPolicy {
    [CmdletBinding()]
    param(
        [Parameter()]
        [string[]]
        $Accelerometer,

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

        [Parameter()]
        [string[]]
        $XrSpatialTracking
    )

    Set-PodeSecurityPermissionsPolicyInternal -Params $PSBoundParameters -Append
}

<#
.SYNOPSIS
Removes definition for the Permissions-Policy header.

.DESCRIPTION
Removes definitions for the Permissions-Policy header.

.EXAMPLE
Remove-PodeSecurityPermissionsPolicy
#>
function Remove-PodeSecurityPermissionsPolicy {
    [CmdletBinding()]
    param()

    Remove-PodeSecurityHeader -Name 'Permissions-Policy'
}

<#
.SYNOPSIS
Set a value for the Referrer-Policy header.

.DESCRIPTION
Set a value for the Referrer-Policy header.

.PARAMETER Type
The Type to use.

.EXAMPLE
Set-PodeSecurityReferrerPolicy -Type No-Referrer
#>
function Set-PodeSecurityReferrerPolicy {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $true)]
        [ValidateSet('No-Referrer', 'No-Referrer-When-Downgrade', 'Same-Origin', 'Origin', 'Strict-Origin',
            'Origin-When-Cross-Origin', 'Strict-Origin-When-Cross-Origin', 'Unsafe-Url')]
        [string]
        $Type
    )

    Add-PodeSecurityHeader -Name 'Referrer-Policy' -Value $Type.ToLowerInvariant()
}

<#
.SYNOPSIS
Removes definition for the Referrer-Policy header.

.DESCRIPTION
Removes definitions for the Referrer-Policy header.

.EXAMPLE
Remove-PodeSecurityReferrerPolicy
#>
function Remove-PodeSecurityReferrerPolicy {
    [CmdletBinding()]
    param()

    Remove-PodeSecurityHeader -Name 'Referrer-Policy'
}

<#
.SYNOPSIS
Set a value for the X-Content-Type-Options header.

.DESCRIPTION
Set a value for the X-Content-Type-Options header to "nosniff".

.EXAMPLE
Set-PodeSecurityContentTypeOptions
#>
function Set-PodeSecurityContentTypeOptions {
    [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseSingularNouns', '')]
    [CmdletBinding()]
    param()

    Add-PodeSecurityHeader -Name 'X-Content-Type-Options' -Value 'nosniff'
}

<#
.SYNOPSIS
Removes definition for the X-Content-Type-Options header.

.DESCRIPTION
Removes definitions for the X-Content-Type-Options header.

.EXAMPLE
Remove-PodeSecurityContentTypeOptions
#>
function Remove-PodeSecurityContentTypeOptions {
    [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseSingularNouns', '')]
    [CmdletBinding()]
    param()

    Remove-PodeSecurityHeader -Name 'X-Content-Type-Options'
}

<#
.SYNOPSIS
Set a value for the Strict-Transport-Security header.

.DESCRIPTION
Set a value for the Strict-Transport-Security header.

.PARAMETER Duration
The Duration the browser to respect the header in seconds. (Default: 1 year)

.PARAMETER IncludeSubDomains
If supplied, the header will have includeSubDomains.

.EXAMPLE
Set-PodeSecurityStrictTransportSecurity -Duration 86400 -IncludeSubDomains
#>
function Set-PodeSecurityStrictTransportSecurity {
    [CmdletBinding()]
    param(
        [Parameter()]
        [int]
        $Duration = 31536000,

        [switch]
        $IncludeSubDomains
    )

    if ($Duration -le 0) {
        # Invalid Strict-Transport-Security duration supplied
        throw ($PodeLocale.invalidStrictTransportSecurityDurationExceptionMessage -f $Duration)
    }

    $value = "max-age=$($Duration)"

    if ($IncludeSubDomains) {
        $value += '; includeSubDomains'
    }

    Add-PodeSecurityHeader -Name 'Strict-Transport-Security' -Value $value
}

<#
.SYNOPSIS
Removes definition for the Strict-Transport-Security header.

.DESCRIPTION
Removes definitions for the Strict-Transport-Security header.

.EXAMPLE
Remove-PodeSecurityStrictTransportSecurity
#>
function Remove-PodeSecurityStrictTransportSecurity {
    [CmdletBinding()]
    param()

    Remove-PodeSecurityHeader -Name 'Strict-Transport-Security'
}

<#
.SYNOPSIS
Removes definitions for the Cross-Origin headers.

.DESCRIPTION
Removes definitions for the Cross-Origin headers: Cross-Origin-Embedder-Policy, Cross-Origin-Opener-Policy, Cross-Origin-Resource-Policy

.PARAMETER Embed
Specifies a value for Cross-Origin-Embedder-Policy.

.PARAMETER Open
Specifies a value for Cross-Origin-Opener-Policy.

.PARAMETER Resource
Specifies a value for Cross-Origin-Resource-Policy.

.EXAMPLE
Set-PodeSecurityCrossOrigin -Embed Require-Corp -Open Same-Origin -Resource Same-Origin
#>
function Set-PodeSecurityCrossOrigin {
    [CmdletBinding()]
    param(
        [Parameter()]
        [ValidateSet('', 'Unsafe-None', 'Require-Corp')]
        [string]
        $Embed = '',

        [Parameter()]
        [ValidateSet('', 'Unsafe-None', 'Same-Origin-Allow-Popups', 'Same-Origin')]
        [string]
        $Open = '',

        [Parameter()]
        [ValidateSet('', 'Same-Site', 'Same-Origin', 'Cross-Origin')]
        [string]
        $Resource = ''
    )

    Add-PodeSecurityHeader -Name 'Cross-Origin-Embedder-Policy' -Value $Embed.ToLowerInvariant()
    Add-PodeSecurityHeader -Name 'Cross-Origin-Opener-Policy' -Value $Open.ToLowerInvariant()
    Add-PodeSecurityHeader -Name 'Cross-Origin-Resource-Policy' -Value $Resource.ToLowerInvariant()
}

<#
.SYNOPSIS
Removes definitions for the Cross-Origin headers.

.DESCRIPTION
Removes definitions for the Cross-Origin headers: Cross-Origin-Embedder-Policy, Cross-Origin-Opener-Policy, Cross-Origin-Resource-Policy

.EXAMPLE
Remove-PodeSecurityCrossOrigin
#>
function Remove-PodeSecurityCrossOrigin {
    [CmdletBinding()]
    param()

    Remove-PodeSecurityHeader -Name 'Cross-Origin-Embedder-Policy'
    Remove-PodeSecurityHeader -Name 'Cross-Origin-Opener-Policy'
    Remove-PodeSecurityHeader -Name 'Cross-Origin-Resource-Policy'
}

<#
.SYNOPSIS
Set definitions for Access-Control headers.

.DESCRIPTION
Removes definitions for the Access-Control headers: Access-Control-Allow-Origin, Access-Control-Allow-Methods, Access-Control-Allow-Headers, Access-Control-Max-Age, Access-Control-Allow-Credentials

.PARAMETER Origin
Specifies a value for Access-Control-Allow-Origin.

.PARAMETER Methods
Specifies a value for Access-Control-Allow-Methods.

.PARAMETER Headers
Specifies a value for Access-Control-Allow-Headers.

.PARAMETER Duration
Specifies a value for Access-Control-Max-Age in seconds. (Default: 7200)
Use a value of one for debugging any CORS related issues

.PARAMETER Credentials
Specifies a value for Access-Control-Allow-Credentials

.PARAMETER WithOptions
If supplied, a global Options Route will be created.

.PARAMETER AuthorizationHeader
Add 'Authorization' to the headers list

.PARAMETER AutoHeaders
Automatically populate the list of allowed Headers based on the OpenApi definition.
This parameter can works in conjuntion with CrossDomainXhrRequests,AuthorizationHeader and Headers (Headers cannot be '*').
By default add  'content-type' to the headers

.PARAMETER AutoMethods
Automatically populate the list of allowed Methods based on the defined Routes.
This parameter can works in conjuntion with the parameter Methods, if Methods is not including '*'

.PARAMETER CrossDomainXhrRequests
Add 'x-requested-with' to the list of allowed headers
More info available here:
https://fetch.spec.whatwg.org/
https://learn.microsoft.com/en-us/aspnet/core/security/cors?view=aspnetcore-7.0#credentials-in-cross-origin-requests
https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS

.EXAMPLE
Set-PodeSecurityAccessControl -Origin '*' -Methods '*' -Headers '*' -Duration 7200
#>
function Set-PodeSecurityAccessControl {
    [CmdletBinding()]
    param(
        [Parameter()]
        [string]
        $Origin,

        [Parameter()]
        [ValidateSet('', 'Connect', 'Delete', 'Get', 'Head', 'Merge', 'Options', 'Patch', 'Post', 'Put', 'Trace', '*')]
        [string[]]
        $Methods = '',

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

        [Parameter()]
        [int]
        $Duration = 7200,

        [switch]
        $Credentials,

        [switch]
        $WithOptions,

        [switch]
        $AuthorizationHeader,

        [switch]
        $AutoHeaders,

        [switch]
        $AutoMethods,

        [switch]
        $CrossDomainXhrRequests
    )

    # origin
    Add-PodeSecurityHeader -Name 'Access-Control-Allow-Origin' -Value $Origin

    # methods
    if (![string]::IsNullOrWhiteSpace($Methods)) {
        if ($Methods -icontains '*') {
            Add-PodeSecurityHeader -Name 'Access-Control-Allow-Methods' -Value '*'
        }
        else {
            Add-PodeSecurityHeader -Name 'Access-Control-Allow-Methods' -Value ($Methods -join ', ')
        }
    }

    # headers
    if (![string]::IsNullOrWhiteSpace($Headers) -or $AuthorizationHeader -or $CrossDomainXhrRequests) {
        if ($Headers -icontains '*') {
            if ($Credentials) {
                # When Credentials is passed, The * wildcard for Headers will be taken as a literal string and not a wildcard
                throw ($PodeLocale.credentialsPassedWildcardForHeadersLiteralExceptionMessage)
            }

            $Headers = @('*')
        }

        if ($AuthorizationHeader) {
            if ([string]::IsNullOrWhiteSpace($Headers)) {
                $Headers = @()
            }

            $Headers += 'Authorization'
        }

        if ($CrossDomainXhrRequests) {
            if ([string]::IsNullOrWhiteSpace($Headers)) {
                $Headers = @()
            }
            $Headers += 'x-requested-with'
        }
        Add-PodeSecurityHeader -Name 'Access-Control-Allow-Headers' -Value (($Headers | Select-Object -Unique) -join ', ')
    }

    if ($AutoHeaders) {
        if ($Headers -icontains '*') {
            # The * wildcard for Headers is incompatible with the AutoHeaders switch
            throw ($PodeLocale.wildcardHeadersIncompatibleWithAutoHeadersExceptionMessage)
        }

        Add-PodeSecurityHeader -Name 'Access-Control-Allow-Headers' -Value 'content-type' -Append
        $PodeContext.Server.Security.autoHeaders = $true
    }

    if ($AutoMethods) {
        if ($Methods -icontains '*') {
            # The * wildcard for Methods is incompatible with the AutoMethods switch
            throw ($PodeLocale.wildcardMethodsIncompatibleWithAutoMethodsExceptionMessage)
        }
        if ($WithOptions) {
            Add-PodeSecurityHeader -Name 'Access-Control-Allow-Methods' -Value 'Options' -Append
        }
        $PodeContext.Server.Security.autoMethods = $true
    }

    # duration
    if ($Duration -le 0) {
        # Invalid Access-Control-Max-Age duration supplied
        throw ($PodeLocale.invalidAccessControlMaxAgeDurationExceptionMessage -f $Duration)
    }

    Add-PodeSecurityHeader -Name 'Access-Control-Max-Age' -Value $Duration

    # creds
    if ($Credentials) {
        Add-PodeSecurityHeader -Name 'Access-Control-Allow-Credentials' -Value 'true'
    }

    # opts route
    if ($WithOptions) {
        Add-PodeRoute -Method Options -Path * -ScriptBlock {
            Set-PodeResponseStatus -Code 200
        }
    }
}

<#
.SYNOPSIS
Removes definitions for the Access-Control headers.

.DESCRIPTION
Removes definitions for the Access-Control headers: Access-Control-Allow-Origin, Access-Control-Allow-Methods, Access-Control-Allow-Headers, Access-Control-Max-Age, Access-Control-Allow-Credentials

.EXAMPLE
Remove-PodeSecurityAccessControl
#>
function Remove-PodeSecurityAccessControl {
    [CmdletBinding()]
    param()

    Remove-PodeSecurityHeader -Name 'Access-Control-Allow-Origin'
    Remove-PodeSecurityHeader -Name 'Access-Control-Allow-Methods'
    Remove-PodeSecurityHeader -Name 'Access-Control-Allow-Headers'
    Remove-PodeSecurityHeader -Name 'Access-Control-Max-Age'
    Remove-PodeSecurityHeader -Name 'Access-Control-Allow-Credentials'
}
src\Public\Sessions.ps1
<#
.SYNOPSIS
Enables Middleware for creating, retrieving and using Sessions within Pode.

.DESCRIPTION
Enables Middleware for creating, retrieving and using Sessions within Pode; with support for defining Session duration, and custom Storage.
If you're storing sessions outside of Pode, you must supply a Secret value so sessions aren't corrupted.

.PARAMETER Secret
An optional Secret to use when signing Sessions (Default: random GUID).

.PARAMETER Name
The name of the cookie/header used for the Session.

.PARAMETER Duration
The duration a Session should last for, before being expired.

.PARAMETER Generator
A custom ScriptBlock to generate a random unique SessionId. The value returned must be a String.

.PARAMETER Storage
A custom PSObject that defines methods for Delete, Get, and Set. This allow you to store Sessions in custom Storage such as Redis. A Secret is required.

.PARAMETER Scope
The Scope that the Session applies to, possible values are Browser and Tab (Default: Browser).
The Browser scope is the default logic, where authentication and general data for the sessions are shared across all tabs.
The Tab scope keep the authentication data shared across all tabs, but general data is separated across different tabs.
For the Tab scope, the "Tab ID" required will be sourced from the "X-PODE-SESSION-TAB-ID" header.

.PARAMETER Extend
If supplied, the Sessions will have their durations extended on each successful Request.

.PARAMETER HttpOnly
If supplied, the Session cookie will only be accessible to browsers.

.PARAMETER Secure
If supplied, the Session cookie will only be accessible over HTTPS Requests.

.PARAMETER Strict
If supplied, the Secret will be extended using the client request's UserAgent and RemoteIPAddress.

.PARAMETER UseHeaders
If supplied, Sessions will be sent back in a header on the Response with the Name supplied.

.EXAMPLE
Enable-PodeSessionMiddleware -Duration 120

.EXAMPLE
Enable-PodeSessionMiddleware -Duration 120 -Extend -Generator { return [System.IO.Path]::GetRandomFileName() }

.EXAMPLE
Enable-PodeSessionMiddleware -Secret 'schwifty' -Duration 120 -UseHeaders -Strict
#>
function Enable-PodeSessionMiddleware {
    [CmdletBinding(DefaultParameterSetName = 'Cookies')]
    param(
        [Parameter()]
        [string]
        $Secret,

        [Parameter()]
        [ValidateNotNullOrEmpty()]
        [string]
        $Name = 'pode.sid',

        [Parameter()]
        [ValidateScript({
                if ($_ -lt 0) {
                    # Duration must be 0 or greater, but got
                    throw ($PodeLocale.durationMustBeZeroOrGreaterExceptionMessage -f $_)
                }

                return $true
            })]
        [int]
        $Duration = 0,

        [Parameter()]
        [scriptblock]
        $Generator,

        [Parameter()]
        [psobject]
        $Storage = $null,

        [Parameter()]
        [ValidateSet('Browser', 'Tab')]
        [string]
        $Scope = 'Browser',

        [switch]
        $Extend,

        [Parameter(ParameterSetName = 'Cookies')]
        [switch]
        $HttpOnly,

        [Parameter(ParameterSetName = 'Cookies')]
        [switch]
        $Secure,

        [switch]
        $Strict,

        [Parameter(ParameterSetName = 'Headers')]
        [switch]
        $UseHeaders
    )

    # check that session logic hasn't already been initialised
    if (Test-PodeSessionsEnabled) {
        # Session Middleware has already been initialized
        throw ($PodeLocale.sessionMiddlewareAlreadyInitializedExceptionMessage)
    }

    # ensure the override store has the required methods
    if (!(Test-PodeIsEmpty $Storage)) {
        $members = @($Storage | Get-Member | Select-Object -ExpandProperty Name)
        @('delete', 'get', 'set') | ForEach-Object {
            if ($members -inotcontains $_) {
                # The custom session storage does not implement the required '{0}()' method
                throw ($PodeLocale.customSessionStorageMethodNotImplementedExceptionMessage -f $_)
            }
        }
    }

    # verify the secret, set to guid if not supplied, or error if none and we have a storage
    if ([string]::IsNullOrEmpty($Secret)) {
        if (!(Test-PodeIsEmpty $Storage)) {
            # A Secret is required when using custom session storage
            throw ($PodeLocale.secretRequiredForCustomSessionStorageExceptionMessage)
        }

        $Secret = Get-PodeServerDefaultSecret
    }

    # if no custom storage, use the inmem one
    if (Test-PodeIsEmpty $Storage) {
        $Storage = (Get-PodeSessionInMemStore)
        Set-PodeSessionInMemClearDown
    }

    # set options against server context
    $PodeContext.Server.Sessions = @{
        Name       = $Name
        Secret     = $Secret
        GenerateId = (Protect-PodeValue -Value $Generator -Default { return (New-PodeGuid) })
        Store      = $Storage
        Info       = @{
            Duration   = $Duration
            Extend     = $Extend.IsPresent
            Secure     = $Secure.IsPresent
            Strict     = $Strict.IsPresent
            HttpOnly   = $HttpOnly.IsPresent
            UseHeaders = $UseHeaders.IsPresent
            Scope      = @{
                Type      = $Scope.ToLowerInvariant()
                IsBrowser = ($Scope -ieq 'Browser')
            }
        }
    }

    # return scriptblock for the session middleware
    Get-PodeSessionMiddleware |
        New-PodeMiddleware |
        Add-PodeMiddleware -Name '__pode_mw_sessions__'
}

<#
.SYNOPSIS
Remove the current Session, logging it out.

.DESCRIPTION
Remove the current Session, logging it out. This will remove the session from Storage, and Cookies.

.EXAMPLE
Remove-PodeSession
#>
function Remove-PodeSession {
    [CmdletBinding()]
    param()

    # if sessions haven't been setup, error
    if (!(Test-PodeSessionsEnabled)) {
        # The sessions have not been configured
        throw ($PodeLocale.sessionsNotConfiguredExceptionMessage)
    }

    # do nothing if session is null
    if ($null -eq $WebEvent.Session) {
        return
    }

    # remove the session, and from auth and cookies
    Remove-PodeAuthSession
}

<#
.SYNOPSIS
Saves the current Session's data.

.DESCRIPTION
Saves the current Session's data.

.PARAMETER Force
If supplied, the data will be saved even if nothing has changed.

.EXAMPLE
Save-PodeSession -Force
#>
function Save-PodeSession {
    [CmdletBinding()]
    param(
        [switch]
        $Force
    )

    # if sessions haven't been setup, error
    if (!(Test-PodeSessionsEnabled)) {
        # The sessions have not been configured
        throw ($PodeLocale.sessionsNotConfiguredExceptionMessage)
    }

    # error if session is null
    if ($null -eq $WebEvent.Session) {
        # There is no session available to save
        throw ($PodeLocale.noSessionAvailableToSaveExceptionMessage)
    }

    # if auth is in use, then assign to session store
    if (!(Test-PodeIsEmpty $WebEvent.Auth) -and $WebEvent.Auth.Store) {
        $WebEvent.Session.Data.Auth = $WebEvent.Auth
    }

    # save the session
    Save-PodeSessionInternal -Force:$Force
}

<#
.SYNOPSIS
Returns the currently authenticated SessionId.

.DESCRIPTION
Returns the currently authenticated SessionId. If there's no session, or it's not authenticated, then null is returned instead.
You can also have the SessionId returned as signed as well.

.PARAMETER Signed
If supplied, the returned SessionId will also be signed.

.PARAMETER Force
If supplied, the sessionId will be returned regardless of authentication.

.EXAMPLE
$sessionId = Get-PodeSessionId
#>
function Get-PodeSessionId {
    [CmdletBinding()]
    param(
        [switch]
        $Signed,

        [switch]
        $Force
    )

    $sessionId = $null

    # do nothing if not authenticated, or force passed
    if (!$Force -and ((Test-PodeIsEmpty $WebEvent.Session.Data.Auth.User) -or !$WebEvent.Session.Data.Auth.IsAuthenticated)) {
        return $sessionId
    }

    # get the sessionId
    $sessionId = $WebEvent.Session.FullId

    # do they want the session signed?
    if ($Signed) {
        $strict = $PodeContext.Server.Sessions.Info.Strict
        $secret = $PodeContext.Server.Sessions.Secret

        # sign the value if we have a secret
        $sessionId = (Invoke-PodeValueSign -Value $sessionId -Secret $secret -Strict:$strict)
    }

    # return the ID
    return $sessionId
}

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

    if ($PodeContext.Server.Sessions.Info.Scope.IsBrowser) {
        return $null
    }

    return Get-PodeHeader -Name 'X-PODE-SESSION-TAB-ID'
}

<#
.SYNOPSIS
Resets the current Session's expiry date.

.DESCRIPTION
Resets the current Session's expiry date, to be from the current time plus the defined Session duration.

.EXAMPLE
Reset-PodeSessionExpiry
#>
function Reset-PodeSessionExpiry {
    [CmdletBinding()]
    param()

    # if sessions haven't been setup, error
    if (!(Test-PodeSessionsEnabled)) {
        # The sessions have not been configured
        throw ($PodeLocale.sessionsNotConfiguredExceptionMessage)
    }

    # error if session is null
    if ($null -eq $WebEvent.Session) {
        # There is no session available to save
        throw ($PodeLocale.noSessionAvailableToSaveExceptionMessage)
    }

    # temporarily set this session to auto-extend
    $WebEvent.Session.Extend = $true

    # reset on response
    Set-PodeSession
}

<#
.SYNOPSIS
Returns the defined Session duration.

.DESCRIPTION
Returns the defined Session duration that all Session are created using.

.EXAMPLE
$duration = Get-PodeSessionDuration
#>
function Get-PodeSessionDuration {
    [CmdletBinding()]
    [OutputType([int])]
    param()

    return [int]$PodeContext.Server.Sessions.Info.Duration
}

<#
.SYNOPSIS
Returns the datetime on which the current Session's will expire.

.DESCRIPTION
Returns the datetime on which the current Session's will expire.

.EXAMPLE
$expiry = Get-PodeSessionExpiry
#>
function Get-PodeSessionExpiry {
    [CmdletBinding()]
    [OutputType([datetime])]
    param()

    # error if session is null
    if ($null -eq $WebEvent.Session) {
        # There is no session available to save
        throw ($PodeLocale.noSessionAvailableToSaveExceptionMessage)
    }

    # default min date
    if ($null -eq $WebEvent.Session.TimeStamp) {
        return [datetime]::MinValue
    }

    # use datetime.now or existing timestamp?
    $expiry = [DateTime]::UtcNow

    if (!$WebEvent.Session.Extend -and ($null -ne $WebEvent.Session.TimeStamp)) {
        $expiry = $WebEvent.Session.TimeStamp
    }

    # add session duration on
    $expiry = $expiry.AddSeconds($PodeContext.Server.Sessions.Info.Duration)

    # return expiry
    return $expiry
}

function Test-PodeSessionsEnabled {
    return (($null -ne $PodeContext.Server.Sessions) -and ($PodeContext.Server.Sessions.Count -gt 0))
}

function Get-PodeSessionInfo {
    return $PodeContext.Server.Sessions.Info
}

function Test-PodeSessionScopeIsBrowser {
    return [bool]$PodeContext.Server.Sessions.Info.Scope.IsBrowser
}
src\Public\SSE.ps1
<#
.SYNOPSIS
Converts the current HTTP request to a Route to be an SSE connection.

.DESCRIPTION
Converts the current HTTP request to a Route to be an SSE connection, by sending the required headers back to the client.
The connection can only be configured if the request's Accept header is "text/event-stream", unless Forced.

.PARAMETER Name
The Name of the SSE connection, which ClientIds will be stored under.

.PARAMETER Group
An optional Group for this SSE connection, to enable broadcasting events to all connections for an SSE connection name in a Group.

.PARAMETER Scope
The Scope of the SSE connection, either Default, Local or Global (Default: Default).
- If the Scope is Default, then it will be Global unless the default has been updated via Set-PodeSseDefaultScope.
- If the Scope is Local, then the SSE connection will only be opened for the duration of the request to a Route that configured it.
- If the Scope is Global, then the SSE connection will be cached internally so events can be sent to the connection from Tasks, Timers, and other Routes, etc.

.PARAMETER RetryDuration
An optional RetryDuration, in milliseconds, for the period of time a browser should wait before reattempting a connection if lost (Default: 0).

.PARAMETER ClientId
An optional ClientId to use for the SSE connection, this value will be signed if signing is enabled (Default: GUID).

.PARAMETER AllowAllOrigins
If supplied, then Access-Control-Allow-Origin will be set to * on the response.

.PARAMETER Force
If supplied, the Accept header of the request will be ignored; attempting to configure an SSE connection even if the header isn't "text/event-stream".

.EXAMPLE
ConvertTo-PodeSseConnection -Name 'Actions'

.EXAMPLE
ConvertTo-PodeSseConnection -Name 'Actions' -Scope Local

.EXAMPLE
ConvertTo-PodeSseConnection -Name 'Actions' -Group 'admins'

.EXAMPLE
ConvertTo-PodeSseConnection -Name 'Actions' -AllowAllOrigins

.EXAMPLE
ConvertTo-PodeSseConnection -Name 'Actions' -ClientId 'my-client-id'
#>
function ConvertTo-PodeSseConnection {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $true)]
        [string]
        $Name,

        [Parameter()]
        [string]
        $Group,

        [Parameter()]
        [ValidateSet('Default', 'Local', 'Global')]
        [string]
        $Scope = 'Default',

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

        [Parameter()]
        [string]
        $ClientId,

        [switch]
        $AllowAllOrigins,

        [switch]
        $Force
    )

    # check Accept header - unless forcing
    if (!$Force -and ((Get-PodeHeader -Name 'Accept') -ine 'text/event-stream')) {
        # SSE can only be configured on requests with an Accept header value of text/event-stream
        throw ($PodeLocale.sseOnlyConfiguredOnEventStreamAcceptHeaderExceptionMessage)
    }

    # check for default scope, and set
    if ($Scope -ieq 'default') {
        $Scope = $PodeContext.Server.Sse.DefaultScope
    }

    # generate clientId
    $ClientId = New-PodeSseClientId -ClientId $ClientId

    # set and send SSE headers
    $ClientId = Wait-PodeTask -Task $WebEvent.Response.SetSseConnection($Scope, $ClientId, $Name, $Group, $RetryDuration, $AllowAllOrigins.IsPresent)

    # create SSE property on WebEvent
    $WebEvent.Sse = @{
        Name        = $Name
        Group       = $Group
        ClientId    = $ClientId
        LastEventId = Get-PodeHeader -Name 'Last-Event-ID'
        IsLocal     = ($Scope -ieq 'local')
    }
}

<#
.SYNOPSIS
Sets the default scope for new SSE connections.

.DESCRIPTION
Sets the default scope for new SSE connections.

.PARAMETER Scope
The default Scope for new SSE connections, either Local or Global.
- If the Scope is Local, then new SSE connections will only be opened for the duration of the request to a Route that configured it.
- If the Scope is Global, then new SSE connections will be cached internally so events can be sent to the connection from Tasks, Timers, and other Routes, etc.

.EXAMPLE
Set-PodeSseDefaultScope -Scope Local

.EXAMPLE
Set-PodeSseDefaultScope -Scope Global
#>
function Set-PodeSseDefaultScope {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $true)]
        [ValidateSet('Local', 'Global')]
        [string]
        $Scope
    )

    $PodeContext.Server.Sse.DefaultScope = $Scope
}

<#
.SYNOPSIS
Retrieves the default SSE connection scope for new SSE connections.

.DESCRIPTION
Retrieves the default SSE connection scope for new SSE connections.

.EXAMPLE
$scope = Get-PodeSseDefaultScope
#>
function Get-PodeSseDefaultScope {
    [CmdletBinding()]
    param()

    return $PodeContext.Server.Sse.DefaultScope
}

<#
.SYNOPSIS
Send an Event to one or more SSE connections.

.DESCRIPTION
Send an Event to one or more SSE connections. This can either be:
- Every client for an SSE connection Name
- Specific ClientIds for an SSE connection Name
- The current SSE connection being referenced within $WebEvent.Sse

.PARAMETER Name
An SSE connection Name.

.PARAMETER Group
An optional array of 1 or more SSE connection Groups to send Events to, for the specified SSE connection Name.

.PARAMETER ClientId
An optional array of 1 or more SSE connection ClientIds to send Events to, for the specified SSE connection Name.

.PARAMETER Id
An optional ID for the Event being sent.

.PARAMETER EventType
An optional EventType for the Event being sent.

.PARAMETER Data
The Data for the Event being sent, either as a String or a Hashtable/PSObject. If the latter, it will be converted into JSON.

.PARAMETER Depth
The Depth to generate the JSON document - the larger this value the worse performance gets.

.PARAMETER FromEvent
If supplied, the SSE connection Name and ClientId will atttempt to be retrived from $WebEvent.Sse.
These details will be set if ConvertTo-PodeSseConnection has just been called. Or if X-PODE-SSE-CLIENT-ID and X-PODE-SSE-NAME are set on an HTTP request.

.EXAMPLE
Send-PodeSseEvent -FromEvent -Data 'This is an event'

.EXAMPLE
Send-PodeSseEvent -FromEvent -Data @{ Message = 'A message' }

.EXAMPLE
Send-PodeSseEvent -Name 'Actions' -Data @{ Message = 'A message' }

.EXAMPLE
Send-PodeSseEvent -Name 'Actions' -Group 'admins' -Data @{ Message = 'A message' }

.EXAMPLE
Send-PodeSseEvent -Name 'Actions' -Data @{ Message = 'A message' } -ID 123 -EventType 'action'
#>
function Send-PodeSseEvent {
    [CmdletBinding(DefaultParameterSetName = 'WebEvent')]
    param(
        [Parameter(Mandatory = $true, ValueFromPipeline = $true, Position = 0)]
        $Data,

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

        [Parameter(ParameterSetName = 'Name')]
        [string[]]
        $Group = $null,

        [Parameter(ParameterSetName = 'Name')]
        [string[]]
        $ClientId = $null,

        [Parameter()]
        [string]
        $Id,

        [Parameter()]
        [string]
        $EventType,

        [Parameter()]
        [int]
        $Depth = 10,

        [Parameter(ParameterSetName = 'WebEvent')]
        [switch]
        $FromEvent
    )


    begin {
        $pipelineValue = @()
        # do nothing if no value
        if (($null -eq $Data) -or ([string]::IsNullOrEmpty($Data))) {
            return
        }
    }

    process {
        $pipelineValue += $_
    }

    end {
        if ($pipelineValue.Count -gt 1) {
            $Data = $pipelineValue
        }
        # jsonify the value
        if ($Data -isnot [string]) {
            if ($Depth -le 0) {
                $Data = (ConvertTo-Json -InputObject $Data -Compress)
            }
            else {
                $Data = (ConvertTo-Json -InputObject $Data -Depth $Depth -Compress)
            }
        }

        # send directly back to current connection
        if ($FromEvent -and $WebEvent.Sse.IsLocal) {
            $null = Wait-PodeTask -Task $WebEvent.Response.SendSseEvent($EventType, $Data, $Id)
            return
        }

        # from event and global?
        if ($FromEvent) {
            $Name = $WebEvent.Sse.Name
            $Group = $WebEvent.Sse.Group
            $ClientId = $WebEvent.Sse.ClientId
        }

        # error if no name
        if ([string]::IsNullOrEmpty($Name)) {
            # An SSE connection Name is required, either from -Name or $WebEvent.Sse.Name
            throw ($PodeLocale.sseConnectionNameRequiredExceptionMessage)
        }

        # check if broadcast level
        if (!(Test-PodeSseBroadcastLevel -Name $Name -Group $Group -ClientId $ClientId)) {
            # SSE failed to broadcast due to defined SSE broadcast level
            throw ($PodeLocale.sseFailedToBroadcastExceptionMessage -f $Name, (Get-PodeSseBroadcastLevel -Name $Name))
        }

        # send event
        $PodeContext.Server.Http.Listener.SendSseEvent($Name, $Group, $ClientId, $EventType, $Data, $Id)
    }
}
<#
.SYNOPSIS
Close one or more SSE connections.

.DESCRIPTION
Close one or more SSE connections. Either all connections for an SSE connection Name, or specific ClientIds for a Name.

.PARAMETER Name
The Name of the SSE connection which has the ClientIds for the connections to close. If supplied on its own, all connections will be closed.

.PARAMETER Group
An optional array of 1 or more SSE connection Groups, that are for the SSE connection Name. If supplied without any ClientIds, then all connections for the Group(s) will be closed.

.PARAMETER ClientId
An optional array of 1 or more SSE connection ClientIds, that are for the SSE connection Name.
If not supplied, every SSE connection for the supplied Name will be closed.

.EXAMPLE
Close-PodeSseConnection -Name 'Actions'

.EXAMPLE
Close-PodeSseConnection -Name 'Actions' -Group 'admins'

.EXAMPLE
Close-PodeSseConnection -Name 'Actions' -ClientId @('my-client-id', 'my-other'id')
#>
function Close-PodeSseConnection {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $true)]
        [string]
        $Name,

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

        [Parameter()]
        [string[]]
        $ClientId = $null
    )

    $PodeContext.Server.Http.Listener.CloseSseConnection($Name, $Group, $ClientId)
}

<#
.SYNOPSIS
Test if an SSE connection ClientId is validly signed.

.DESCRIPTION
Test if an SSE connection ClientId is validly signed.

.PARAMETER ClientId
An optional SSE connection ClientId, if not supplied it will be retrieved from $WebEvent.

.EXAMPLE
if (Test-PodeSseClientIdValid) { ... }

.EXAMPLE
if (Test-PodeSseClientIdValid -ClientId 's:my-already-signed-client-id.uvG49LcojTMuJ0l4yzBzr6jCqEV8gGC/0YgsYU1QEuQ=') { ... }
#>
function Test-PodeSseClientIdSigned {
    [CmdletBinding()]
    param(
        [Parameter()]
        [string]
        $ClientId
    )

    # get clientId from WebEvent if not passed
    if ([string]::IsNullOrEmpty($ClientId)) {
        $ClientId = $WebEvent.Request.SseClientId
    }

    # test if clientId is validly signed
    return Test-PodeValueSigned -Value $ClientId -Secret $PodeContext.Server.Sse.Secret -Strict:($PodeContext.Server.Sse.Strict)
}

<#
.SYNOPSIS
Test if an SSE connection ClientId is valid.

.DESCRIPTION
Test if an SSE connection ClientId, passed or from $WebEvent, is valid. A ClientId is valid if it's not signed and we're not signing ClientIds,
or if we are signing ClientIds and the ClientId is validly signed.

.PARAMETER ClientId
An optional SSE connection ClientId, if not supplied it will be retrieved from $WebEvent.

.EXAMPLE
if (Test-PodeSseClientIdValid) { ... }

.EXAMPLE
if (Test-PodeSseClientIdValid -ClientId 'my-client-id') { ... }
#>
function Test-PodeSseClientIdValid {
    [CmdletBinding()]
    [OutputType([bool])]
    param(
        [Parameter()]
        [string]
        $ClientId
    )

    # get clientId from WebEvent if not passed
    if ([string]::IsNullOrEmpty($ClientId)) {
        $ClientId = $WebEvent.Request.SseClientId
    }

    # if no clientId, then it's not valid
    if ([string]::IsNullOrEmpty($ClientId)) {
        return $false
    }

    # if we're not signing, then valid if not signed, but invalid if signed
    if (!$PodeContext.Server.Sse.Signed) {
        return !$ClientId.StartsWith('s:')
    }

    # test if clientId is validly signed
    return Test-PodeSseClientIdSigned -ClientId $ClientId
}

<#
.SYNOPSIS
Test if the name of an SSE connection exists or not.

.DESCRIPTION
Test if the name of an SSE connection exists or not.

.PARAMETER Name
The Name of an SSE connection to test.

.EXAMPLE
if (Test-PodeSseName -Name 'Example') { ... }
#>
function Test-PodeSseName {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $true)]
        [string]
        $Name
    )

    return $PodeContext.Server.Http.Listener.TestSseConnectionExists($Name)
}

<#
.SYNOPSIS
Test if an SSE connection ClientId exists or not.

.DESCRIPTION
Test if an SSE connection ClientId exists or not.

.PARAMETER Name
The Name of an SSE connection.

.PARAMETER ClientId
The SSE connection ClientId to test.

.EXAMPLE
if (Test-PodeSseClientId -Name 'Example' -ClientId 'my-client-id') { ... }
#>
function Test-PodeSseClientId {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $true)]
        [string]
        $Name,

        [Parameter(Mandatory = $true)]
        [string]
        $ClientId
    )

    return $PodeContext.Server.Http.Listener.TestSseConnectionExists($Name, $ClientId)
}

<#
.SYNOPSIS
Generate a new SSE connection ClientId.

.DESCRIPTION
Generate a new SSE connection ClientId, which will be signed if signing enabled.

.PARAMETER ClientId
An optional SSE connection ClientId to use, if a custom ClientId is needed and required to be signed.

.EXAMPLE
$clientId = New-PodeSseClientId

.EXAMPLE
$clientId = New-PodeSseClientId -ClientId 'my-client-id'

.EXAMPLE
$clientId = New-PodeSseClientId -ClientId 's:my-already-signed-client-id.uvG49LcojTMuJ0l4yzBzr6jCqEV8gGC/0YgsYU1QEuQ='
#>
function New-PodeSseClientId {
    [CmdletBinding()]
    param(
        [Parameter()]
        [string]
        $ClientId
    )

    # if no clientId passed, generate a random guid
    if ([string]::IsNullOrEmpty($ClientId)) {
        $ClientId = New-PodeGuid -Secure
    }

    # if we're signing the clientId, and it's not already signed, then sign it
    if ($PodeContext.Server.Sse.Signed -and !$ClientId.StartsWith('s:')) {
        $ClientId = Invoke-PodeValueSign -Value $ClientId -Secret $PodeContext.Server.Sse.Secret -Strict:($PodeContext.Server.Sse.Strict)
    }

    # return the clientId
    return $ClientId
}

<#
.SYNOPSIS
Enable the signing of SSE connection ClientIds.

.DESCRIPTION
Enable the signing of SSE connection ClientIds.

.PARAMETER Secret
A Secret to sign ClientIds, Get-PodeServerDefaultSecret can be used.

.PARAMETER Strict
If supplied, the Secret will be extended using the client request's UserAgent and RemoteIPAddress.

.EXAMPLE
Enable-PodeSseSigning

.EXAMPLE
Enable-PodeSseSigning -Strict

.EXAMPLE
Enable-PodeSseSigning -Secret 'Sup3rS3cr37!' -Strict

.EXAMPLE
Enable-PodeSseSigning -Secret 'Sup3rS3cr37!'
#>
function Enable-PodeSseSigning {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $true)]
        [string]
        $Secret,

        [switch]
        $Strict
    )

    # flag that we're signing SSE connections
    $PodeContext.Server.Sse.Signed = $true
    $PodeContext.Server.Sse.Secret = $Secret
    $PodeContext.Server.Sse.Strict = $Strict.IsPresent
}

<#
.SYNOPSIS
Disable the signing of SSE connection ClientIds.

.DESCRIPTION
Disable the signing of SSE connection ClientIds.

.EXAMPLE
Disable-PodeSseSigning
#>
function Disable-PodeSseSigning {
    [CmdletBinding()]
    param()

    # flag that we're not signing SSE connections
    $PodeContext.Server.Sse.Signed = $false
    $PodeContext.Server.Sse.Secret = $null
    $PodeContext.Server.Sse.Strict = $false
}

<#
.SYNOPSIS
Set an allowed broadcast level for SSE connections.

.DESCRIPTION
Set an allowed broadcast level for SSE connections, either for all SSE connection names or specific ones.

.PARAMETER Name
An optional Name for an SSE connection (default: *).

.PARAMETER Type
The broadcast level Type for the SSE connection.
Name = Allow broadcasting at all levels, including broadcasting to all Groups and/or ClientIds for an SSE connection Name.
Group = Allow broadcasting to only Groups or specific ClientIds. If neither Groups nor ClientIds are supplied, sending an event will fail.
ClientId = Allow broadcasting to only ClientIds. If no ClientIds are supplied, sending an event will fail.

.EXAMPLE
Set-PodeSseBroadcastLevel -Type Name

.EXAMPLE
Set-PodeSseBroadcastLevel -Type Group

.EXAMPLE
Set-PodeSseBroadcastLevel -Name 'Actions' -Type ClientId
#>
function Set-PodeSseBroadcastLevel {
    [CmdletBinding()]
    param(
        [Parameter()]
        [ValidateNotNullOrEmpty()]
        [string]
        $Name = '*',

        [Parameter()]
        [ValidateSet('Name', 'Group', 'ClientId')]
        [string]
        $Type
    )

    $PodeContext.Server.Sse.BroadcastLevel[$Name] = $Type.ToLowerInvariant()
}

<#
.SYNOPSIS
Retrieve the broadcast level for an SSE connection Name.

.DESCRIPTION
Retrieve the broadcast level for an SSE connection Name. If one hasn't been set explicitly then the base level will be checked.
If no broadcasting level have been set at all, then the "Name" level will be returned.

.PARAMETER Name
The Name of an SSE connection.

.EXAMPLE
$level = Get-PodeSseBroadcastLevel -Name 'Actions'
#>
function Get-PodeSseBroadcastLevel {
    [CmdletBinding()]
    [OutputType([string])]
    param(
        [Parameter(Mandatory = $true)]
        [string]
        $Name
    )

    # if no levels, return null
    if ($PodeContext.Server.Sse.BroadcastLevel.Count -eq 0) {
        return 'name'
    }

    # get level or default level
    $level = $PodeContext.Server.Sse.BroadcastLevel[$Name]
    if ([string]::IsNullOrEmpty($level)) {
        $level = $PodeContext.Server.Sse.BroadcastLevel['*']
    }

    if ([string]::IsNullOrEmpty($level)) {
        $level = 'name'
    }

    # return level
    return $level
}

<#
.SYNOPSIS
Test if an SSE connection can be broadcasted to, given the Name, Group, and ClientIds.

.DESCRIPTION
Test if an SSE connection can be broadcasted to, given the Name, Group, and ClientIds.

.PARAMETER Name
The Name of the SSE connection.

.PARAMETER Group
An array of 1 or more Groups.

.PARAMETER ClientId
An array of 1 or more ClientIds.

.EXAMPLE
if (Test-PodeSseBroadcastLevel -Name 'Actions') { ... }

.EXAMPLE
if (Test-PodeSseBroadcastLevel -Name 'Actions' -Group 'admins') { ... }

.EXAMPLE
if (Test-PodeSseBroadcastLevel -Name 'Actions' -ClientId 'my-client-id') { ... }
#>
function Test-PodeSseBroadcastLevel {
    [CmdletBinding()]
    [OutputType([bool])]
    param(
        [Parameter(Mandatory = $true)]
        [string]
        $Name,

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

        [Parameter()]
        [string[]]
        $ClientId
    )

    # get level, and if no level or level=name, return true
    $level = Get-PodeSseBroadcastLevel -Name $Name
    if ([string]::IsNullOrEmpty($level) -or ($level -ieq 'name')) {
        return $true
    }

    # if level=group, return false if no groups or clientIds
    # if level=clientId, return false if no clientIds
    switch ($level) {
        'group' {
            if ((($null -eq $Group) -or ($Group.Length -eq 0)) -and (($null -eq $ClientId) -or ($ClientId.Length -eq 0))) {
                return $false
            }
        }

        'clientid' {
            if (($null -eq $ClientId) -or ($ClientId.Length -eq 0)) {
                return $false
            }
        }
    }

    # valid, return true
    return $true
}
src\Public\State.ps1
<#
.SYNOPSIS
Sets an object within the shared state.

.DESCRIPTION
Sets an object within the shared state.

.PARAMETER Name
The name of the state object.

.PARAMETER Value
The value to set in the state.

.PARAMETER Scope
An optional Scope for the state object, used when saving the state.

.EXAMPLE
Set-PodeState -Name 'Data' -Value @{ 'Name' = 'Rick Sanchez' }

.EXAMPLE
Set-PodeState -Name 'Users' -Value @('user1', 'user2') -Scope General, Users
#>
function Set-PodeState {
    [CmdletBinding()]
    [OutputType([object])]
    param(
        [Parameter(Mandatory = $true)]
        [string]
        $Name,

        [Parameter(ValueFromPipeline = $true, Position = 0)]
        [object]
        $Value,

        [Parameter()]
        [string[]]
        $Scope
    )

    begin {
        if ($null -eq $PodeContext.Server.State) {
            # Pode has not been initialized
            throw ($PodeLocale.podeNotInitializedExceptionMessage)
        }

        if ($null -eq $Scope) {
            $Scope = @()
        }

        # Initialize an array to hold piped-in values
        $pipelineValue = @()
    }

    process {
        # Add the current piped-in value to the array
        $pipelineValue += $_
    }

    end {
        # Set Value to the array of values
        if ($pipelineValue.Count -gt 1) {
            $Value = $pipelineValue
        }

        $PodeContext.Server.State[$Name] = @{
            Value = $Value
            Scope = $Scope
        }

        return $Value
    }
}

<#
.SYNOPSIS
Retrieves some state object from the shared state.

.DESCRIPTION
Retrieves some state object from the shared state.

.PARAMETER Name
The name of the state object.

.PARAMETER WithScope
If supplied, the state's value and scope will be returned as a hashtable.

.EXAMPLE
Get-PodeState -Name 'Data'
#>
function Get-PodeState {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $true)]
        [string]
        $Name,

        [switch]
        $WithScope
    )

    if ($null -eq $PodeContext.Server.State) {
        # Pode has not been initialized
        throw ($PodeLocale.podeNotInitializedExceptionMessage)
    }

    if ($WithScope) {
        return $PodeContext.Server.State[$Name]
    }
    else {
        return $PodeContext.Server.State[$Name].Value
    }
}

<#
.SYNOPSIS
Returns the current names of state variables.

.DESCRIPTION
Returns the current names of state variables that have been set. You can filter the result using Scope or a Pattern.

.PARAMETER Pattern
An optional regex Pattern to filter the state names.

.PARAMETER Scope
An optional Scope to filter the state names.

.EXAMPLE
$names = Get-PodeStateNames -Scope '<scope>'

.EXAMPLE
$names = Get-PodeStateNames -Pattern '^\w+[0-9]{0,2}$'
#>
function Get-PodeStateNames {
    [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseSingularNouns', '')]
    [CmdletBinding()]
    param(
        [Parameter()]
        [string]
        $Pattern,

        [Parameter()]
        [string[]]
        $Scope
    )

    if ($null -eq $PodeContext.Server.State) {
        # Pode has not been initialized
        throw ($PodeLocale.podeNotInitializedExceptionMessage)
    }

    if ($null -eq $Scope) {
        $Scope = @()
    }

    $tempState = $PodeContext.Server.State.Clone()
    $keys = $tempState.Keys

    if ($Scope.Length -gt 0) {
        $keys = @(foreach ($key in $keys) {
                if ($tempState[$key].Scope -iin $Scope) {
                    $key
                }
            })
    }

    if (![string]::IsNullOrWhiteSpace($Pattern)) {
        $keys = @(foreach ($key in $keys) {
                if ($key -imatch $Pattern) {
                    $key
                }
            })
    }

    return $keys
}

<#
.SYNOPSIS
Removes some state object from the shared state.

.DESCRIPTION
Removes some state object from the shared state. After removal, the original object being stored is returned.

.PARAMETER Name
The name of the state object.

.EXAMPLE
Remove-PodeState -Name 'Data'
#>
function Remove-PodeState {
    [CmdletBinding()]
    [OutputType([object])]
    param(
        [Parameter(Mandatory = $true)]
        [string]
        $Name
    )

    if ($null -eq $PodeContext.Server.State) {
        # Pode has not been initialized
        throw ($PodeLocale.podeNotInitializedExceptionMessage)
    }

    $value = $PodeContext.Server.State[$Name].Value
    $null = $PodeContext.Server.State.Remove($Name)
    return $value
}

<#
.SYNOPSIS
Saves the current shared state to a supplied JSON file.

.DESCRIPTION
Saves the current shared state to a supplied JSON file. When using this function, it's recommended to wrap it in a Lock-PodeObject block.

.PARAMETER Path
The path to a JSON file which the current state will be saved to.

.PARAMETER Scope
An optional array of scopes for state objects that should be saved. (This has a lower precedence than Exclude/Include)

.PARAMETER Exclude
An optional array of state object names to exclude from being saved. (This has a higher precedence than Include)

.PARAMETER Include
An optional array of state object names to only include when being saved.

.PARAMETER Depth
Saved JSON maximum depth. Will be passed to ConvertTo-JSON's -Depth parameter. Default is 10.

.PARAMETER Compress
If supplied, the saved JSON will be compressed.

.EXAMPLE
Save-PodeState -Path './state.json'

.EXAMPLE
Save-PodeState -Path './state.json' -Exclude Name1, Name2

.EXAMPLE
Save-PodeState -Path './state.json' -Scope Users
#>
function Save-PodeState {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $true)]
        [string]
        $Path,

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

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

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

        [Parameter()]
        [int16]
        $Depth = 10,

        [switch]
        $Compress
    )

    # error if attempting to use outside of the pode server
    if ($null -eq $PodeContext.Server.State) {
        # Pode has not been initialized
        throw ($PodeLocale.podeNotInitializedExceptionMessage)
    }

    # get the full path to save the state
    $Path = Get-PodeRelativePath -Path $Path -JoinRoot

    # contruct the state to save (excludes, etc)
    $state = $PodeContext.Server.State.Clone()

    # scopes
    if (($null -ne $Scope) -and ($Scope.Length -gt 0)) {
        foreach ($_key in $state.Clone().Keys) {
            # remove if no scope
            if (($null -eq $state[$_key].Scope) -or ($state[$_key].Scope.Length -eq 0)) {
                $null = $state.Remove($_key)
                continue
            }

            # check scopes (only remove if none match)
            $found = $false

            foreach ($_scope in $state[$_key].Scope) {
                if ($Scope -icontains $_scope) {
                    $found = $true
                    break
                }
            }

            if ($found) {
                continue
            }

            # none matched, remove
            $null = $state.Remove($_key)
        }
    }

    # include keys
    if (($null -ne $Include) -and ($Include.Length -gt 0)) {
        foreach ($_key in $state.Clone().Keys) {
            if ($Include -inotcontains $_key) {
                $null = $state.Remove($_key)
            }
        }
    }

    # exclude keys
    if (($null -ne $Exclude) -and ($Exclude.Length -gt 0)) {
        foreach ($_key in $state.Clone().Keys) {
            if ($Exclude -icontains $_key) {
                $null = $state.Remove($_key)
            }
        }
    }

    # save the state
    $null = ConvertTo-Json -InputObject $state -Depth $Depth -Compress:$Compress | Out-File -FilePath $Path -Force
}

<#
.SYNOPSIS
Restores the shared state from some JSON file.

.DESCRIPTION
Restores the shared state from some JSON file.

.PARAMETER Path
The path to a JSON file that contains the state information.

.PARAMETER Merge
If supplied, the state loaded from the JSON file will be merged with the current state, instead of overwriting it.

.PARAMETER Depth
Saved JSON maximum depth. Will be passed to ConvertFrom-JSON's -Depth parameter (Powershell >=6). Default is 10.

.EXAMPLE
Restore-PodeState -Path './state.json'
#>
function Restore-PodeState {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $true)]
        [string]
        $Path,

        [switch]
        $Merge,

        [int16]
        $Depth = 10
    )

    # error if attempting to use outside of the pode server
    if ($null -eq $PodeContext.Server.State) {
        # Pode has not been initialized
        throw ($PodeLocale.podeNotInitializedExceptionMessage)
    }

    # get the full path to the state
    $Path = Get-PodeRelativePath -Path $Path -JoinRoot
    if (!(Test-Path $Path)) {
        return
    }

    # restore the state from file
    $state = @{}

    if (Test-PodeIsPSCore) {
        $state = (Get-Content $Path -Force | ConvertFrom-Json -AsHashtable -Depth $Depth)
    }
    else {
        $props = (Get-Content $Path -Force | ConvertFrom-Json).psobject.properties
        foreach ($prop in $props) {
            $state[$prop.Name] = $prop.Value
        }
    }

    # check for no scopes, and add for backwards compat
    $convert = $false
    foreach ($_key in $state.Clone().Keys) {
        if ($null -eq $state[$_key].Scope) {
            $convert = $true
            break
        }
    }

    if ($convert) {
        foreach ($_key in $state.Clone().Keys) {
            $state[$_key] = @{
                Value = $state[$_key]
                Scope = @()
            }
        }
    }

    # set the scope to the main context
    if ($Merge) {
        foreach ($_key in $state.Clone().Keys) {
            $PodeContext.Server.State[$_key] = $state[$_key]
        }
    }
    else {
        $PodeContext.Server.State = $state.Clone()
    }
}

<#
.SYNOPSIS
Tests if the shared state contains some state object.

.DESCRIPTION
Tests if the shared state contains some state object.

.PARAMETER Name
The name of the state object.

.EXAMPLE
Test-PodeState -Name 'Data'
#>
function Test-PodeState {
    [CmdletBinding()]
    [OutputType([bool])]
    param(
        [Parameter(Mandatory = $true)]
        [string]
        $Name
    )

    if ($null -eq $PodeContext.Server.State) {
        # Pode has not been initialized
        throw ($PodeLocale.podeNotInitializedExceptionMessage)
    }

    return $PodeContext.Server.State.ContainsKey($Name)
}
src\Public\Tasks.ps1
<#
.SYNOPSIS
    Adds a new Task.

.DESCRIPTION
    Adds a new Task, which can be asynchronously or synchronously invoked.

.PARAMETER Name
    The Name of the Task.

.PARAMETER ScriptBlock
    The script for the Task.

.PARAMETER FilePath
    A literal, or relative, path to a file containing a ScriptBlock for the Task's logic.

.PARAMETER ArgumentList
    A hashtable of arguments to supply to the Task's ScriptBlock.

.PARAMETER Timeout
    A Timeout, in seconds, to abort running the Task process. (Default: -1 [never timeout])

.PARAMETER TimeoutFrom
    Where to start the Timeout from, either 'Create', 'Start'. (Default: 'Create')

.EXAMPLE
    Add-PodeTask -Name 'Example1' -ScriptBlock { Invoke-SomeLogic }

.EXAMPLE
    Add-PodeTask -Name 'Example1' -ScriptBlock { return Get-SomeObject }
#>
function Add-PodeTask {
    [CmdletBinding(DefaultParameterSetName = 'Script')]
    param(
        [Parameter(Mandatory = $true)]
        [string]
        $Name,

        [Parameter(Mandatory = $true, ParameterSetName = 'Script')]
        [scriptblock]
        $ScriptBlock,

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

        [Parameter()]
        [hashtable]
        $ArgumentList,

        [Parameter()]
        [int]
        $Timeout = -1,

        [Parameter()]
        [ValidateSet('Create', 'Start')]
        [string]
        $TimeoutFrom = 'Create'
    )
    # ensure the task doesn't already exist
    if ($PodeContext.Tasks.Items.ContainsKey($Name)) {
        # [Task] Task already defined
        throw ($PodeLocale.taskAlreadyDefinedExceptionMessage -f $Name)
    }

    # if we have a file path supplied, load that path as a scriptblock
    if ($PSCmdlet.ParameterSetName -ieq 'file') {
        $ScriptBlock = Convert-PodeFileToScriptBlock -FilePath $FilePath
    }

    # check for scoped vars
    $ScriptBlock, $usingVars = Convert-PodeScopedVariables -ScriptBlock $ScriptBlock -PSSession $PSCmdlet.SessionState

    # add the task
    $PodeContext.Tasks.Enabled = $true
    $PodeContext.Tasks.Items[$Name] = @{
        Name           = $Name
        Script         = $ScriptBlock
        UsingVariables = $usingVars
        Arguments      = (Protect-PodeValue -Value $ArgumentList -Default @{})
        Timeout        = @{
            Value = $Timeout
            From  = $TimeoutFrom
        }
    }
}

<#
.SYNOPSIS
Set the maximum number of concurrent Tasks.

.DESCRIPTION
Set the maximum number of concurrent Tasks.

.PARAMETER Maximum
The Maximum number of Tasks to run.

.EXAMPLE
Set-PodeTaskConcurrency -Maximum 10
#>
function Set-PodeTaskConcurrency {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $true)]
        [int]
        $Maximum
    )

    # error if <=0
    if ($Maximum -le 0) {
        # Maximum concurrent tasks must be >=1 but got
        throw ($PodeLocale.maximumConcurrentTasksInvalidExceptionMessage -f $Maximum)

    }

    # ensure max > min
    $_min = 1
    if ($null -ne $PodeContext.RunspacePools.Tasks) {
        $_min = $PodeContext.RunspacePools.Tasks.Pool.GetMinRunspaces()
    }

    if ($_min -gt $Maximum) {
        # Maximum concurrent tasks cannot be less than the minimum of $_min but got $Maximum
        throw ($PodeLocale.maximumConcurrentTasksLessThanMinimumExceptionMessage -f $_min, $Maximum)
    }

    # set the max tasks
    $PodeContext.Threads.Tasks = $Maximum
    if ($null -ne $PodeContext.RunspacePools.Tasks) {
        $PodeContext.RunspacePools.Tasks.Pool.SetMaxRunspaces($Maximum)
    }
}

<#
.SYNOPSIS
Invoke a Task.

.DESCRIPTION
Invoke a Task either asynchronously or synchronously, with support for returning values.
The function returns the Task process onbject which was triggered.

.PARAMETER Name
The Name of the Task.

.PARAMETER ArgumentList
A hashtable of arguments to supply to the Task's ScriptBlock.

.PARAMETER Timeout
A Timeout, in seconds, to abort running the Task process. (Default: -1 [never timeout])

.PARAMETER TimeoutFrom
Where to start the Timeout from, either 'Default', 'Create', or 'Start'. (Default: 'Default' - will use the value from Add-PodeTask)

.PARAMETER Wait
If supplied, Pode will wait until the Task process has finished executing, and then return any values.

.OUTPUTS
The triggered Task process.

.EXAMPLE
Invoke-PodeTask -Name 'Example1' -Wait -Timeout 5

.EXAMPLE
$task = Invoke-PodeTask -Name 'Example1'

.EXAMPLE
Invoke-PodeTask -Name 'Example1' | Wait-PodeTask -Timeout 3
#>
function Invoke-PodeTask {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $true, Position = 0, ValueFromPipeline = $true)]
        [string]
        $Name,

        [Parameter()]
        [hashtable]
        $ArgumentList = $null,

        [Parameter()]
        [int]
        $Timeout = -1,

        [Parameter()]
        [ValidateSet('Default', 'Create', 'Start')]
        [string]
        $TimeoutFrom = 'Default',

        [switch]
        $Wait
    )
    process {
        # ensure the task exists
        if (!$PodeContext.Tasks.Items.ContainsKey($Name)) {
            # Task does not exist
            throw ($PodeLocale.taskDoesNotExistExceptionMessage -f $Name)
        }

        # run task logic
        $task = Invoke-PodeInternalTask -Task $PodeContext.Tasks.Items[$Name] -ArgumentList $ArgumentList -Timeout $Timeout -TimeoutFrom $TimeoutFrom

        # wait, and return result?
        if ($Wait) {
            return (Wait-PodeTask -Task $task -Timeout $Timeout)
        }

        # return task
        return $task
    }
}

<#
.SYNOPSIS
Removes a specific Task.

.DESCRIPTION
Removes a specific Task.

.PARAMETER Name
The Name of Task to be removed.

.EXAMPLE
Remove-PodeTask -Name 'Example1'
#>
function Remove-PodeTask {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $true, Position = 0, ValueFromPipeline = $true)]
        [string]
        $Name
    )
    process {
        $null = $PodeContext.Tasks.Items.Remove($Name)
    }
}

<#
.SYNOPSIS
Removes all Tasks.

.DESCRIPTION
Removes all Tasks.

.EXAMPLE
Clear-PodeTasks
#>
function Clear-PodeTasks {
    [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseSingularNouns', '')]
    [CmdletBinding()]
    param()

    $PodeContext.Tasks.Items.Clear()
}

<#
.SYNOPSIS
Edits an existing Task.

.DESCRIPTION
Edits an existing Task's properties, such as scriptblock.

.PARAMETER Name
The Name of the Task.

.PARAMETER ScriptBlock
The new ScriptBlock for the Task.

.PARAMETER ArgumentList
Any new Arguments for the Task.

.EXAMPLE
Edit-PodeTask -Name 'Example1' -ScriptBlock { Invoke-SomeNewLogic }
#>
function Edit-PodeTask {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $true, Position = 0, ValueFromPipeline = $true)]
        [string]
        $Name,

        [Parameter()]
        [scriptblock]
        $ScriptBlock,

        [Parameter()]
        [hashtable]
        $ArgumentList
    )
    process {
        # ensure the task exists
        if (!$PodeContext.Tasks.Items.ContainsKey($Name)) {
            # Task does not exist
            throw ($PodeLocale.taskDoesNotExistExceptionMessage -f $Name)
        }

        $_task = $PodeContext.Tasks.Items[$Name]

        # edit scriptblock if supplied
        if (!(Test-PodeIsEmpty $ScriptBlock)) {
            $ScriptBlock, $usingVars = Convert-PodeScopedVariables -ScriptBlock $ScriptBlock -PSSession $PSCmdlet.SessionState
            $_task.Script = $ScriptBlock
            $_task.UsingVariables = $usingVars
        }

        # edit arguments if supplied
        if (!(Test-PodeIsEmpty $ArgumentList)) {
            $_task.Arguments = $ArgumentList
        }
    }
}

<#
.SYNOPSIS
Returns any defined Tasks.

.DESCRIPTION
Returns any defined Tasks, with support for filtering.

.PARAMETER Name
Any Task Names to filter the Tasks.

.EXAMPLE
Get-PodeTask

.EXAMPLE
Get-PodeTask -Name Example1, Example2
#>
function Get-PodeTask {
    [CmdletBinding()]
    param(
        [Parameter()]
        [string[]]
        $Name
    )

    $tasks = $PodeContext.Tasks.Items.Values

    # further filter by task names
    if (($null -ne $Name) -and ($Name.Length -gt 0)) {
        $tasks = @(foreach ($_name in $Name) {
                foreach ($task in $tasks) {
                    if ($task.Name -ine $_name) {
                        continue
                    }

                    $task
                }
            })
    }

    # return
    return $tasks
}

<#
.SYNOPSIS
Automatically loads task ps1 files

.DESCRIPTION
Automatically loads task ps1 files from either a /tasks folder, or a custom folder. Saves space dot-sourcing them all one-by-one.

.PARAMETER Path
Optional Path to a folder containing ps1 files, can be relative or literal.

.EXAMPLE
Use-PodeTasks

.EXAMPLE
Use-PodeTasks -Path './my-tasks'
#>
function Use-PodeTasks {
    [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseSingularNouns', '')]
    [CmdletBinding()]
    param(
        [Parameter()]
        [string]
        $Path
    )

    Use-PodeFolder -Path $Path -DefaultPath 'tasks'
}

<#
.SYNOPSIS
Close and dispose of a Task.

.DESCRIPTION
Close and dispose of a Task, even if still running.

.PARAMETER Task
The Task to be closed.

.EXAMPLE
Invoke-PodeTask -Name 'Example1' | Close-PodeTask
#>
function Close-PodeTask {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $true, Position = 0, ValueFromPipeline = $true)]
        [hashtable]
        $Task
    )
    process {
        Close-PodeTaskInternal -Process $Task
    }
}

<#
.SYNOPSIS
Test if a running Task process has completed.

.DESCRIPTION
Test if a running Task process has completed.

.PARAMETER Task
The Task process to be check. The process returned by either Invoke-PodeTask or Get-PodeTaskProcess.

.EXAMPLE
Invoke-PodeTask -Name 'Example1' | Test-PodeTaskCompleted
#>
function Test-PodeTaskCompleted {
    [CmdletBinding()]
    [OutputType([bool])]
    param(
        [Parameter(Mandatory = $true, Position = 0, ValueFromPipeline = $true)]
        [hashtable]
        $Task
    )
    process {
        return [bool]$Task.Runspace.Handler.IsCompleted
    }
}

<#
.SYNOPSIS
Waits for a Task process to finish, and returns a result if there is one.

.DESCRIPTION
Waits for a Task process to finish, and returns a result if there is one.

.PARAMETER Task
The Task process to wait on. The process returned by either Invoke-PodeTask or Get-PodeTaskProcess.

.PARAMETER Timeout
An optional Timeout in milliseconds.

.EXAMPLE
$context = Wait-PodeTask -Task $listener.GetContextAsync()

.EXAMPLE
$result = Invoke-PodeTask -Name 'Example1' | Wait-PodeTask
#>
function Wait-PodeTask {
    [CmdletBinding()]
    [OutputType([object])]
    param(
        [Parameter(Mandatory = $true, Position = 0, ValueFromPipeline = $true)]
        $Task,

        [Parameter()]
        [int]
        $Timeout = -1
    )
    begin {
        $pipelineItemCount = 0
    }

    process {
        $pipelineItemCount++
    }

    end {
        if ($pipelineItemCount -gt 1) {
            throw ($PodeLocale.fnDoesNotAcceptArrayAsPipelineInputExceptionMessage -f $($MyInvocation.MyCommand.Name))
        }
        if ($Task -is [System.Threading.Tasks.Task]) {
            return (Wait-PodeNetTaskInternal -Task $Task -Timeout $Timeout)
        }

        if ($Task -is [hashtable]) {
            return (Wait-PodeTaskInternal -Task $Task -Timeout $Timeout)
        }

        # Task type is invalid, expected either [System.Threading.Tasks.Task] or [hashtable]
        throw ($PodeLocale.invalidTaskTypeExceptionMessage)
    }
}

<#
.SYNOPSIS
Get all Task Processes.

.DESCRIPTION
Get all Task Processes, with support for filtering. These are the processes created when using Invoke-PodeTask.

.PARAMETER Name
An optional Name of the Task to filter by, can be one or more.

.PARAMETER Id
An optional ID of the Task process to filter by, can be one or more.

.PARAMETER State
An optional State of the Task process to filter by, can be one or more.

.EXAMPLE
Get-PodeTaskProcess

.EXAMPLE
Get-PodeTaskProcess -Name 'TaskName'

.EXAMPLE
Get-PodeTaskProcess -Id 'TaskId'

.EXAMPLE
Get-PodeTaskProcess -State 'Running'
#>
function Get-PodeTaskProcess {
    [CmdletBinding()]
    param(
        [Parameter()]
        [string[]]
        $Name,

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

        [Parameter()]
        [ValidateSet('All', 'Pending', 'Running', 'Completed', 'Failed')]
        [string[]]
        $State = 'All'
    )

    $processes = $PodeContext.Tasks.Processes.Values

    # filter processes by name
    if (($null -ne $Name) -and ($Name.Length -gt 0)) {
        $processes = @(foreach ($_name in $Name) {
                foreach ($process in $processes) {
                    if ($process.Task -ine $_name) {
                        continue
                    }

                    $process
                }
            })
    }

    # filter processes by id
    if (($null -ne $Id) -and ($Id.Length -gt 0)) {
        $processes = @(foreach ($_id in $Id) {
                foreach ($process in $processes) {
                    if ($process.ID -ine $_id) {
                        continue
                    }

                    $process
                }
            })
    }

    # filter processes by status
    if ($State -inotcontains 'All') {
        $processes = @(foreach ($process in $processes) {
                if ($State -inotcontains $process.State) {
                    continue
                }

                $process
            })
    }

    # return processes
    return $processes
}


<#
.SYNOPSIS
Get all Task Processes.

.DESCRIPTION
Get all Task Processes, with support for filtering. These are the processes created when using Invoke-PodeTask.

.PARAMETER Name
An optional Name of the Task to filter by, can be one or more.

.PARAMETER Id
An optional ID of the Task process to filter by, can be one or more.

.PARAMETER State
An optional State of the Task process to filter by, can be one or more.

.EXAMPLE
Get-PodeTaskProcess

.EXAMPLE
Get-PodeTaskProcess -Name 'TaskName'

.EXAMPLE
Get-PodeTaskProcess -Id 'TaskId'

.EXAMPLE
Get-PodeTaskProcess -State 'Running'
#>
function Get-PodeTaskProcess {
    [CmdletBinding()]
    param(
        [Parameter()]
        [string[]]
        $Name,

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

        [Parameter()]
        [ValidateSet('All', 'Pending', 'Running', 'Completed', 'Failed')]
        [string[]]
        $State = 'All'
    )

    $processes = $PodeContext.Tasks.Processes.Values

    # filter processes by name
    if (($null -ne $Name) -and ($Name.Length -gt 0)) {
        $processes = @(foreach ($_name in $Name) {
                foreach ($process in $processes) {
                    if ($process.Task -ine $_name) {
                        continue
                    }

                    $process
                }
            })
    }

    # filter processes by id
    if (($null -ne $Id) -and ($Id.Length -gt 0)) {
        $processes = @(foreach ($_id in $Id) {
                foreach ($process in $processes) {
                    if ($process.ID -ine $_id) {
                        continue
                    }

                    $process
                }
            })
    }

    # filter processes by status
    if ($State -inotcontains 'All') {
        $processes = @(foreach ($process in $processes) {
                if ($State -inotcontains $process.State) {
                    continue
                }

                $process
            })
    }

    # return processes
    return $processes
}
src\Public\Threading.ps1
<#
.SYNOPSIS
Places a temporary lock on an object, or Lockable, while a ScriptBlock is invoked.

.DESCRIPTION
Places a temporary lock on an object, or Lockable, while a ScriptBlock is invoked.

.PARAMETER Object
The Object, or Lockable, to lock. If no Object is supplied then the global lockable is used by default.

.PARAMETER Name
The Name of a Lockable object in Pode to lock, if no Name is supplied then the global lockable is used by default.

.PARAMETER ScriptBlock
The ScriptBlock to invoke.

.PARAMETER Timeout
If supplied, a number of milliseconds to timeout after if a lock cannot be acquired. (Default: Infinite)

.PARAMETER Return
If supplied, any values from the ScriptBlock will be returned.

.PARAMETER CheckGlobal
If supplied, will check the global Lockable object and wait until it's freed-up before locking the passed object.

.EXAMPLE
Lock-PodeObject -ScriptBlock { /* logic */ }

.EXAMPLE
Lock-PodeObject -Object $SomeArray -ScriptBlock { /* logic */ }

.EXAMPLE
Lock-PodeObject -Name 'LockName' -Timeout 5000 -ScriptBlock { /* logic */ }

.EXAMPLE
$result = (Lock-PodeObject -Return -Object $SomeArray -ScriptBlock { /* logic */ })
#>
function Lock-PodeObject {
    [CmdletBinding(DefaultParameterSetName = 'Object')]
    [OutputType([object])]
    param(
        [Parameter(ValueFromPipeline = $true, Position = 0, ParameterSetName = 'Object')]
        [object]
        $Object,

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

        [Parameter(Mandatory = $true)]
        [scriptblock]
        $ScriptBlock,

        [Parameter()]
        [int]
        $Timeout = [System.Threading.Timeout]::Infinite,

        [switch]
        $Return,

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

    process {
        $pipelineItemCount++
    }

    end {
        if ($pipelineItemCount -gt 1) {
            throw ($PodeLocale.fnDoesNotAcceptArrayAsPipelineInputExceptionMessage -f $($MyInvocation.MyCommand.Name))
        }
        try {
            if ([string]::IsNullOrEmpty($Name)) {
                Enter-PodeLockable -Object $Object -Timeout $Timeout -CheckGlobal:$CheckGlobal
            }
            else {
                Enter-PodeLockable -Name $Name -Timeout $Timeout -CheckGlobal:$CheckGlobal
            }

            if ($null -ne $ScriptBlock) {
                Invoke-PodeScriptBlock -ScriptBlock $ScriptBlock -NoNewClosure -Return:$Return
            }
        }
        catch {
            $_ | Write-PodeErrorLog
            throw $_.Exception
        }
        finally {
            if ([string]::IsNullOrEmpty($Name)) {
                Exit-PodeLockable -Object $Object
            }
            else {
                Exit-PodeLockable -Name $Name
            }
        }
    }
}

<#
.SYNOPSIS
Creates a new custom Lockable object.

.DESCRIPTION
Creates a new custom Lockable object for use with Lock-PodeObject, and Enter/Exit-PodeLockable.

.PARAMETER Name
The Name of the Lockable object.

.EXAMPLE
New-PodeLockable -Name 'Lock1'
#>
function New-PodeLockable {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $true)]
        [string]
        $Name
    )

    if (Test-PodeLockable -Name $Name) {
        return
    }

    $PodeContext.Threading.Lockables.Custom[$Name] = [hashtable]::Synchronized(@{})
}

<#
.SYNOPSIS
Removes a custom Lockable object.

.DESCRIPTION
Removes a custom Lockable object.

.PARAMETER Name
The Name of the Lockable object to remove.

.EXAMPLE
Remove-PodeLockable -Name 'Lock1'
#>
function Remove-PodeLockable {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $true)]
        [string]
        $Name
    )

    if (Test-PodeLockable -Name $Name) {
        $PodeContext.Threading.Lockables.Custom.Remove($Name)
    }
}

<#
.SYNOPSIS
Get a custom Lockable object.

.DESCRIPTION
Get a custom Lockable object for use with Lock-PodeObject, and Enter/Exit-PodeLockable.

.PARAMETER Name
The Name of the Lockable object.

.EXAMPLE
Get-PodeLockable -Name 'Lock1' | Lock-PodeObject -ScriptBlock {}
#>
function Get-PodeLockable {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $true)]
        [string]
        $Name
    )

    return $PodeContext.Threading.Lockables.Custom[$Name]
}

<#
.SYNOPSIS
Test if a custom Lockable object exists.

.DESCRIPTION
Test if a custom Lockable object exists.

.PARAMETER Name
The Name of the Lockable object.

.EXAMPLE
Test-PodeLockable -Name 'Lock1'
#>
function Test-PodeLockable {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $true)]
        [string]
        $Name
    )

    return $PodeContext.Threading.Lockables.Custom.ContainsKey($Name)
}

<#
.SYNOPSIS
Place a lock on an object or Lockable.

.DESCRIPTION
Place a lock on an object or Lockable. This should eventually be followed by a call to Exit-PodeLockable.

.PARAMETER Object
The Object, or Lockable, to lock. If no Object is supplied then the global lockable is used by default.

.PARAMETER Name
The Name of a Lockable object in Pode to lock, if no Name is supplied then the global lockable is used by default.

.PARAMETER Timeout
If supplied, a number of milliseconds to timeout after if a lock cannot be acquired. (Default: Infinite)

.PARAMETER CheckGlobal
If supplied, will check the global Lockable object and wait until it's freed-up before locking the passed object.

.EXAMPLE
Enter-PodeLockable -Object $SomeArray

.EXAMPLE
Enter-PodeLockable -Name 'LockName' -Timeout 5000
#>
function Enter-PodeLockable {
    [CmdletBinding(DefaultParameterSetName = 'Object')]
    param(
        [Parameter(ValueFromPipeline = $true, Position = 0, ParameterSetName = 'Object')]
        [object]
        $Object,

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

        [Parameter()]
        [int]
        $Timeout = [System.Threading.Timeout]::Infinite,

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

    process {
        $pipelineItemCount++
    }

    end {
        if ($pipelineItemCount -gt 1) {
            throw ($PodeLocale.fnDoesNotAcceptArrayAsPipelineInputExceptionMessage -f $($MyInvocation.MyCommand.Name))
        }
        # get object by name if set
        if (![string]::IsNullOrEmpty($Name)) {
            $Object = Get-PodeLockable -Name $Name
        }

        # if object is null, default to global
        if ($null -eq $Object) {
            $Object = $PodeContext.Threading.Lockables.Global
        }

        # check if value type and throw
        if ($Object -is [valuetype]) {
            # Cannot lock a [ValueType]
            throw ($PodeLocale.cannotLockValueTypeExceptionMessage)
        }

        # check if null and throw
        if ($null -eq $Object) {
            # Cannot lock an object that is null
            throw ($PodeLocale.cannotLockNullObjectExceptionMessage)
        }

        # check if the global lockable is locked
        if ($CheckGlobal) {
            Lock-PodeObject -Object $PodeContext.Threading.Lockables.Global -ScriptBlock {} -Timeout $Timeout
        }

        # attempt to acquire lock
        $locked = $false
        [System.Threading.Monitor]::TryEnter($Object.SyncRoot, $Timeout, [ref]$locked)
        if (!$locked) {
            # Failed to acquire a lock on the object
            throw ($PodeLocale.failedToAcquireLockExceptionMessage)
        }
    }
}

<#
.SYNOPSIS
Remove a lock from an object or Lockable.

.DESCRIPTION
Remove a lock from an object or Lockable, that was originally locked via Enter-PodeLockable.

.PARAMETER Object
The Object, or Lockable, to unlock. If no Object is supplied then the global lockable is used by default.

.PARAMETER Name
The Name of a Lockable object in Pode to unlock, if no Name is supplied then the global lockable is used by default.

.EXAMPLE
Exit-PodeLockable -Object $SomeArray

.EXAMPLE
Exit-PodeLockable -Name 'LockName'
#>
function Exit-PodeLockable {
    [CmdletBinding(DefaultParameterSetName = 'Object')]
    param(
        [Parameter(ValueFromPipeline = $true, Position = 0, ParameterSetName = 'Object')]
        [object]
        $Object,

        [Parameter(Mandatory = $true, ParameterSetName = 'Name')]
        [string]
        $Name
    )
    begin {
        $pipelineItemCount = 0
    }

    process {
        $pipelineItemCount++
    }

    end {
        if ($pipelineItemCount -gt 1) {
            throw ($PodeLocale.fnDoesNotAcceptArrayAsPipelineInputExceptionMessage -f $($MyInvocation.MyCommand.Name))
        }
        # get object by name if set
        if (![string]::IsNullOrEmpty($Name)) {
            $Object = Get-PodeLockable -Name $Name
        }

        # if object is null, default to global
        if ($null -eq $Object) {
            $Object = $PodeContext.Threading.Lockables.Global
        }

        # check if value type and throw
        if ($Object -is [valuetype]) {
            # Cannot unlock a [ValueType]
            throw ($PodeLocale.cannotUnlockValueTypeExceptionMessage)
        }

        # check if null and throw
        if ($null -eq $Object) {
            # Cannot unlock an object that is null
            throw ($PodeLocale.cannotUnlockNullObjectExceptionMessage)
        }

        if ([System.Threading.Monitor]::IsEntered($Object.SyncRoot)) {
            [System.Threading.Monitor]::Pulse($Object.SyncRoot)
            [System.Threading.Monitor]::Exit($Object.SyncRoot)
        }
    }
}

<#
.SYNOPSIS
Remove all Lockables.

.DESCRIPTION
Remove all Lockables.

.EXAMPLE
Clear-PodeLockables
#>
function Clear-PodeLockables {
    [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseSingularNouns', '')]
    [CmdletBinding()]
    param()

    if (Test-PodeIsEmpty $PodeContext.Threading.Lockables.Custom) {
        return
    }

    foreach ($name in $PodeContext.Threading.Lockables.Custom.Keys.Clone()) {
        Remove-PodeLockable -Name $name
    }
}

<#
.SYNOPSIS
Create a new Mutex.

.DESCRIPTION
Create a new Mutex.

.PARAMETER Name
The Name of the Mutex.

.PARAMETER Scope
The Scope of the Mutex, can be either Self, Local, or Global. (Default: Self)
Self: The current process, or child processes.
Local: All processes for the current login session on Windows, or the the same as Self on Unix.
Global: All processes on the system, across every session.

.EXAMPLE
New-PodeMutex -Name 'SelfMutex'

.EXAMPLE
New-PodeMutex -Name 'LocalMutex' -Scope Local

.EXAMPLE
New-PodeMutex -Name 'GlobalMutex' -Scope Global
#>
function New-PodeMutex {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $true)]
        [string]
        $Name,

        [Parameter()]
        [ValidateSet('Self', 'Local', 'Global')]
        [string]
        $Scope = 'Self'
    )

    if (Test-PodeMutex -Name $Name) {
        # A mutex with the following name already exists
        throw ($PodeLocale.mutexAlreadyExistsExceptionMessage -f $Name)
    }

    $mutex = $null

    switch ($Scope.ToLowerInvariant()) {
        'self' {
            $mutex = [System.Threading.Mutex]::new($false)
        }

        'local' {
            $mutex = [System.Threading.Mutex]::new($false, "Local\$($Name)")
        }

        'global' {
            $mutex = [System.Threading.Mutex]::new($false, "Global\$($Name)")
        }
    }

    $PodeContext.Threading.Mutexes[$Name] = $mutex
}

<#
.SYNOPSIS
Test if a Mutex exists.

.DESCRIPTION
Test if a Mutex exists.

.PARAMETER Name
The Name of the Mutex.

.EXAMPLE
Test-PodeMutex -Name 'LocalMutex'
#>
function Test-PodeMutex {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $true)]
        [string]
        $Name
    )

    return $PodeContext.Threading.Mutexes.ContainsKey($Name)
}

<#
.SYNOPSIS
Get a Mutex.

.DESCRIPTION
Get a Mutex.

.PARAMETER Name
The Name of the Mutex.

.EXAMPLE
$mutex = Get-PodeMutex -Name 'SelfMutex'
#>
function Get-PodeMutex {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $true)]
        [string]
        $Name
    )

    return $PodeContext.Threading.Mutexes[$Name]
}

<#
.SYNOPSIS
Remove a Mutex.

.DESCRIPTION
Remove a Mutex.

.PARAMETER Name
The Name of the Mutex.

.EXAMPLE
Remove-PodeMutex -Name 'GlobalMutex'
#>
function Remove-PodeMutex {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $true)]
        [string]
        $Name
    )

    if (Test-PodeMutex -Name $Name) {
        $PodeContext.Threading.Mutexes[$Name].Dispose()
        $PodeContext.Threading.Mutexes.Remove($Name)
    }
}

<#
.SYNOPSIS
Places a temporary hold on a Mutex, invokes a ScriptBlock, then releases the Mutex.

.DESCRIPTION
Places a temporary hold on a Mutex, invokes a ScriptBlock, then releases the Mutex.

.PARAMETER Name
The Name of the Mutex.

.PARAMETER ScriptBlock
The ScriptBlock to invoke.

.PARAMETER Timeout
If supplied, a number of milliseconds to timeout after if a hold cannot be acquired on the Mutex. (Default: Infinite)

.PARAMETER Return
If supplied, any values from the ScriptBlock will be returned.

.EXAMPLE
Use-PodeMutex -Name 'SelfMutex' -Timeout 5000 -ScriptBlock {}

.EXAMPLE
$result = Use-PodeMutex -Name 'LocalMutex' -Return -ScriptBlock {}
#>
function Use-PodeMutex {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $true)]
        [string]
        $Name,

        [Parameter(Mandatory = $true)]
        [scriptblock]
        $ScriptBlock,

        [Parameter()]
        [int]
        $Timeout = [System.Threading.Timeout]::Infinite,

        [switch]
        $Return
    )

    try {
        $acquired = $false
        Enter-PodeMutex -Name $Name -Timeout $Timeout
        $acquired = $true
        Invoke-PodeScriptBlock -ScriptBlock $ScriptBlock -NoNewClosure -Return:$Return
    }
    catch {
        $_ | Write-PodeErrorLog
        throw $_.Exception
    }
    finally {
        if ($acquired) {
            Exit-PodeMutex -Name $Name
        }
    }
}

<#
.SYNOPSIS
Acquires a hold on a Mutex.

.DESCRIPTION
Acquires a hold on a Mutex. This should eventually by followed by a call to Exit-PodeMutex.

.PARAMETER Name
The Name of the Mutex.

.PARAMETER Timeout
If supplied, a number of milliseconds to timeout after if a hold cannot be acquired on the Mutex. (Default: Infinite)

.EXAMPLE
Enter-PodeMutex -Name 'SelfMutex' -Timeout 5000
#>
function Enter-PodeMutex {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $true)]
        [string]
        $Name,

        [Parameter()]
        [int]
        $Timeout = [System.Threading.Timeout]::Infinite
    )

    $mutex = Get-PodeMutex -Name $Name
    if ($null -eq $mutex) {
        # No mutex found called 'Name'
        throw ($PodeLocale.noMutexFoundExceptionMessage -f $Name)
    }

    if (!$mutex.WaitOne($Timeout)) {
        # Failed to acquire mutex ownership. Mutex name: Name
        throw ($PodeLocale.failedToAcquireMutexOwnershipExceptionMessage -f $Name)
    }
}

<#
.SYNOPSIS
Release the hold on a Mutex.

.DESCRIPTION
Release the hold on a Mutex, that was originally acquired by Enter-PodeMutex.

.PARAMETER Name
The Name of the Mutex.

.EXAMPLE
Exit-PodeMutex -Name 'SelfMutex'
#>
function Exit-PodeMutex {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $true)]
        [string]
        $Name
    )

    $mutex = Get-PodeMutex -Name $Name
    if ($null -eq $mutex) {
        # No mutex found called 'Name'
        throw ($PodeLocale.noMutexFoundExceptionMessage -f $Name)
    }

    $mutex.ReleaseMutex()
}

<#
.SYNOPSIS
Removes all Mutexes.

.DESCRIPTION
Removes all Mutexes.

.EXAMPLE
Clear-PodeMutexes
#>
function Clear-PodeMutexes {
    [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseSingularNouns', '')]
    [CmdletBinding()]
    param()

    if (Test-PodeIsEmpty $PodeContext.Threading.Mutexes) {
        return
    }

    foreach ($name in $PodeContext.Threading.Mutexes.Keys.Clone()) {
        Remove-PodeMutex -Name $name
    }
}

<#
.SYNOPSIS
Create a new Semaphore.

.DESCRIPTION
Create a new Semaphore.

.PARAMETER Name
The Name of the Semaphore.

.PARAMETER Count
The number of threads to allow a hold on the Semaphore. (Default: 1)

.PARAMETER Scope
The Scope of the Semaphore, can be either Self, Local, or Global. (Default: Self)
Self: The current process, or child processes.
Local: All processes for the current login session on Windows, or the the same as Self on Unix.
Global: All processes on the system, across every session.

.EXAMPLE
New-PodeSemaphore -Name 'SelfSemaphore'

.EXAMPLE
New-PodeSemaphore -Name 'LocalSemaphore' -Scope Local

.EXAMPLE
New-PodeSemaphore -Name 'GlobalSemaphore' -Count 3 -Scope Global
#>
function New-PodeSemaphore {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $true)]
        [string]
        $Name,

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

        [Parameter()]
        [ValidateSet('Self', 'Local', 'Global')]
        [string]
        $Scope = 'Self'
    )

    if (Test-PodeSemaphore -Name $Name) {
        # A semaphore with the following name already exists
        throw ($PodeLocale.semaphoreAlreadyExistsExceptionMessage -f $Name)
    }

    if ($Count -le 0) {
        $Count = 1
    }

    $semaphore = $null

    switch ($Scope.ToLowerInvariant()) {
        'self' {
            $semaphore = [System.Threading.Semaphore]::new($Count, $Count)
        }

        'local' {
            $semaphore = [System.Threading.Semaphore]::new($Count, $Count, "Local\$($Name)")
        }

        'global' {
            $semaphore = [System.Threading.Semaphore]::new($Count, $Count, "Global\$($Name)")
        }
    }

    $PodeContext.Threading.Semaphores[$Name] = $semaphore
}

<#
.SYNOPSIS
Test if a Semaphore exists.

.DESCRIPTION
Test if a Semaphore exists.

.PARAMETER Name
The Name of the Semaphore.

.EXAMPLE
Test-PodeSemaphore -Name 'LocalSemaphore'
#>
function Test-PodeSemaphore {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $true)]
        [string]
        $Name
    )

    return $PodeContext.Threading.Semaphores.ContainsKey($Name)
}

<#
.SYNOPSIS
Get a Semaphore.

.DESCRIPTION
Get a Semaphore.

.PARAMETER Name
The Name of the Semaphore.

.EXAMPLE
$semaphore = Get-PodeSemaphore -Name 'SelfSemaphore'
#>
function Get-PodeSemaphore {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $true)]
        [string]
        $Name
    )

    return $PodeContext.Threading.Semaphores[$Name]
}

<#
.SYNOPSIS
Remove a Semaphore.

.DESCRIPTION
Remove a Semaphore.

.PARAMETER Name
The Name of the Semaphore.

.EXAMPLE
Remove-PodeSemaphore -Name 'GlobalSemaphore'
#>
function Remove-PodeSemaphore {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $true)]
        [string]
        $Name
    )

    if (Test-PodeSemaphore -Name $Name) {
        $PodeContext.Threading.Semaphores[$Name].Dispose()
        $PodeContext.Threading.Semaphores.Remove($Name)
    }
}

<#
.SYNOPSIS
Places a temporary hold on a Semaphore, invokes a ScriptBlock, then releases the Semaphore.

.DESCRIPTION
Places a temporary hold on a Semaphore, invokes a ScriptBlock, then releases the Semaphore.

.PARAMETER Name
The Name of the Semaphore.

.PARAMETER ScriptBlock
The ScriptBlock to invoke.

.PARAMETER Timeout
If supplied, a number of milliseconds to timeout after if a hold cannot be acquired on the Semaphore. (Default: Infinite)

.PARAMETER Return
If supplied, any values from the ScriptBlock will be returned.

.EXAMPLE
Use-PodeSemaphore -Name 'SelfSemaphore' -Timeout 5000 -ScriptBlock {}

.EXAMPLE
$result = Use-PodeSemaphore -Name 'LocalSemaphore' -Return -ScriptBlock {}
#>
function Use-PodeSemaphore {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $true)]
        [string]
        $Name,

        [Parameter(Mandatory = $true)]
        [scriptblock]
        $ScriptBlock,

        [Parameter()]
        [int]
        $Timeout = [System.Threading.Timeout]::Infinite,

        [switch]
        $Return
    )

    try {
        $acquired = $false
        Enter-PodeSemaphore -Name $Name -Timeout $Timeout
        $acquired = $true
        Invoke-PodeScriptBlock -ScriptBlock $ScriptBlock -NoNewClosure -Return:$Return
    }
    catch {
        $_ | Write-PodeErrorLog
        throw $_.Exception
    }
    finally {
        if ($acquired) {
            Exit-PodeSemaphore -Name $Name
        }
    }
}

<#
.SYNOPSIS
Acquires a hold on a Semaphore.

.DESCRIPTION
Acquires a hold on a Semaphore. This should eventually by followed by a call to Exit-PodeSemaphore.

.PARAMETER Name
The Name of the Semaphore.

.PARAMETER Timeout
If supplied, a number of milliseconds to timeout after if a hold cannot be acquired on the Semaphore. (Default: Infinite)

.EXAMPLE
Enter-PodeSemaphore -Name 'SelfSemaphore' -Timeout 5000
#>
function Enter-PodeSemaphore {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $true)]
        [string]
        $Name,

        [Parameter()]
        [int]
        $Timeout = [System.Threading.Timeout]::Infinite
    )

    $semaphore = Get-PodeSemaphore -Name $Name
    if ($null -eq $semaphore) {
        # No semaphore found called 'Name'
        throw ($PodeLocale.noSemaphoreFoundExceptionMessage -f $Name)
    }

    if (!$semaphore.WaitOne($Timeout)) {
        # Failed to acquire semaphore ownership. Semaphore name: Name
        throw ($PodeLocale.failedToAcquireSemaphoreOwnershipExceptionMessage -f $Name)
    }
}

<#
.SYNOPSIS
Release the hold on a Semaphore.

.DESCRIPTION
Release the hold on a Semaphore, that was originally acquired by Enter-PodeSemaphore.

.PARAMETER Name
The Name of the Semaphore.

.PARAMETER ReleaseCount
The number of releases to release in one go. (Default: 1)

.EXAMPLE
Exit-PodeSemaphore -Name 'SelfSemaphore'
#>
function Exit-PodeSemaphore {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $true)]
        [string]
        $Name,

        [Parameter()]
        [int]
        $ReleaseCount = 1
    )

    $semaphore = Get-PodeSemaphore -Name $Name
    if ($null -eq $semaphore) {
        # No semaphore found called 'Name'
        throw ($PodeLocale.noSemaphoreFoundExceptionMessage -f $Name)
    }

    if ($ReleaseCount -lt 1) {
        $ReleaseCount = 1
    }

    $semaphore.Release($ReleaseCount)
}

<#
.SYNOPSIS
Removes all Semaphores.

.DESCRIPTION
Removes all Semaphores.

.EXAMPLE
Clear-PodeSemaphores
#>
function Clear-PodeSemaphores {
    [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseSingularNouns', '')]
    [CmdletBinding()]
    param()

    if (Test-PodeIsEmpty $PodeContext.Threading.Semaphores) {
        return
    }

    foreach ($name in $PodeContext.Threading.Semaphores.Keys.Clone()) {
        Remove-PodeSemaphore -Name $name
    }
}
src\Public\Timers.ps1
<#
.SYNOPSIS
    Adds a new Timer with logic to periodically invoke.

.DESCRIPTION
    Adds a new Timer with logic to periodically invoke, with options to only run a specific number of times.

.PARAMETER Name
    The Name of the Timer.

.PARAMETER Interval
    The number of seconds to periodically invoke the Timer's ScriptBlock.

.PARAMETER ScriptBlock
    The script for the Timer.

.PARAMETER Limit
    The number of times the Timer should be invoked before being removed. (If 0, it will run indefinitely)

.PARAMETER Skip
    The number of "invokes" to skip before the Timer actually runs.

.PARAMETER ArgumentList
    An array of arguments to supply to the Timer's ScriptBlock.

.PARAMETER FilePath
    A literal, or relative, path to a file containing a ScriptBlock for the Timer's logic.

.PARAMETER OnStart
    If supplied, the timer will trigger when the server starts.

.EXAMPLE
    Add-PodeTimer -Name 'Hello' -Interval 10 -ScriptBlock { 'Hello, world!' | Out-Default }

.EXAMPLE
    Add-PodeTimer -Name 'RunOnce' -Interval 1 -Limit 1 -ScriptBlock { /* logic */ }

.EXAMPLE
    Add-PodeTimer -Name 'RunAfter60secs' -Interval 10 -Skip 6 -ScriptBlock { /* logic */ }

.EXAMPLE
    Add-PodeTimer -Name 'Args' -Interval 2 -ScriptBlock { /* logic */ } -ArgumentList 'arg1', 'arg2'
#>
function Add-PodeTimer {
    [CmdletBinding(DefaultParameterSetName = 'Script')]
    param(
        [Parameter(Mandatory = $true)]
        [string]
        $Name,

        [Parameter(Mandatory = $true)]
        [int]
        $Interval,

        [Parameter(Mandatory = $true, ParameterSetName = 'Script')]
        [scriptblock]
        $ScriptBlock,

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

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

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

        [Parameter()]
        [object[]]
        $ArgumentList,

        [switch]
        $OnStart
    )

    # error if serverless
    Test-PodeIsServerless -FunctionName 'Add-PodeTimer' -ThrowError

    # ensure the timer doesn't already exist
    if ($PodeContext.Timers.Items.ContainsKey($Name)) {
        # [Timer] Name: Timer already defined
        throw ($PodeLocale.timerAlreadyDefinedExceptionMessage -f $Name)
    }

    # is the interval valid?
    if ($Interval -le 0) {
        # [Timer] Name: parameter must be greater than 0
        throw ($PodeLocale.timerParameterMustBeGreaterThanZeroExceptionMessage -f $Name, 'Interval')
    }

    # is the limit valid?
    if ($Limit -lt 0) {
        # [Timer] Name: parameter must be greater than 0
        throw ($PodeLocale.timerParameterMustBeGreaterThanZeroExceptionMessage -f $Name, 'Limit')
    }

    # is the skip valid?
    if ($Skip -lt 0) {
        # [Timer] Name: parameter must be greater than 0
        throw ($PodeLocale.timerParameterMustBeGreaterThanZeroExceptionMessage -f $Name, 'Skip')
    }

    # if we have a file path supplied, load that path as a scriptblock
    if ($PSCmdlet.ParameterSetName -ieq 'file') {
        $ScriptBlock = Convert-PodeFileToScriptBlock -FilePath $FilePath
    }

    # check for scoped vars
    $ScriptBlock, $usingVars = Convert-PodeScopedVariables -ScriptBlock $ScriptBlock -PSSession $PSCmdlet.SessionState

    # calculate the next tick time (based on Skip)
    $NextTriggerTime = [DateTime]::Now.AddSeconds($Interval)
    if ($Skip -gt 1) {
        $NextTriggerTime = $NextTriggerTime.AddSeconds($Interval * $Skip)
    }

    # add the timer
    $PodeContext.Timers.Enabled = $true
    $PodeContext.Timers.Items[$Name] = @{
        Name            = $Name
        Interval        = $Interval
        Limit           = $Limit
        Count           = 0
        Skip            = $Skip
        NextTriggerTime = $NextTriggerTime
        LastTriggerTime = $null
        Script          = $ScriptBlock
        UsingVariables  = $usingVars
        Arguments       = $ArgumentList
        OnStart         = $OnStart
        Completed       = $false
    }
}


<#
.SYNOPSIS
Adhoc invoke a Timer's logic.

.DESCRIPTION
Adhoc invoke a Timer's logic outside of its defined interval. This invocation doesn't count towards the Timer's limit.

.PARAMETER Name
The Name of the Timer.

.PARAMETER ArgumentList
An array of arguments to supply to the Timer's ScriptBlock.

.EXAMPLE
Invoke-PodeTimer -Name 'timer-name'
#>
function Invoke-PodeTimer {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $true, Position = 0, ValueFromPipeline = $true)]
        [string]
        $Name,

        [Parameter()]
        [object[]]
        $ArgumentList = $null
    )
    process {
        # ensure the timer exists
        if (!$PodeContext.Timers.Items.ContainsKey($Name)) {
            # Timer 'Name' does not exist
            throw ($PodeLocale.timerDoesNotExistExceptionMessage -f $Name)
        }

        # run timer logic
        Invoke-PodeInternalTimer -Timer $PodeContext.Timers.Items[$Name] -ArgumentList $ArgumentList
    }
}

<#
.SYNOPSIS
Removes a specific Timer.

.DESCRIPTION
Removes a specific Timer.

.PARAMETER Name
The Name of Timer to be removed.

.EXAMPLE
Remove-PodeTimer -Name 'SaveState'
#>
function Remove-PodeTimer {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $true, Position = 0, ValueFromPipeline = $true)]
        [string]
        $Name
    )
    process {
        $null = $PodeContext.Timers.Items.Remove($Name)
    }
}

<#
.SYNOPSIS
Removes all Timers.

.DESCRIPTION
Removes all Timers.

.EXAMPLE
Clear-PodeTimers
#>
function Clear-PodeTimers {
    [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseSingularNouns', '')]
    [CmdletBinding()]
    param()

    $PodeContext.Timers.Items.Clear()
}

<#
.SYNOPSIS
Edits an existing Timer.

.DESCRIPTION
Edits an existing Timer's properties, such as interval or scriptblock.

.PARAMETER Name
The Name of the Timer.

.PARAMETER Interval
The new Interval for the Timer in seconds.

.PARAMETER ScriptBlock
The new ScriptBlock for the Timer.

.PARAMETER ArgumentList
Any new Arguments for the Timer.

.EXAMPLE
Edit-PodeTimer -Name 'Hello' -Interval 10
#>
function Edit-PodeTimer {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $true, Position = 0, ValueFromPipeline = $true)]
        [string]
        $Name,

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

        [Parameter()]
        [scriptblock]
        $ScriptBlock,

        [Parameter()]
        [object[]]
        $ArgumentList
    )
    process {
        # ensure the timer exists
        if (!$PodeContext.Timers.Items.ContainsKey($Name)) {
            # Timer 'Name' does not exist
            throw ($PodeLocale.timerDoesNotExistExceptionMessage -f $Name)
        }

        $_timer = $PodeContext.Timers.Items[$Name]

        # edit interval if supplied
        if ($Interval -gt 0) {
            $_timer.Interval = $Interval
        }

        # edit scriptblock if supplied
        if (!(Test-PodeIsEmpty $ScriptBlock)) {
            $ScriptBlock, $usingVars = Convert-PodeScopedVariables -ScriptBlock $ScriptBlock -PSSession $PSCmdlet.SessionState
            $_timer.Script = $ScriptBlock
            $_timer.UsingVariables = $usingVars
        }

        # edit arguments if supplied
        if (!(Test-PodeIsEmpty $ArgumentList)) {
            $_timer.Arguments = $ArgumentList
        }
    }
}

<#
.SYNOPSIS
Returns any defined timers.

.DESCRIPTION
Returns any defined timers, with support for filtering.

.PARAMETER Name
Any timer Names to filter the timers.

.EXAMPLE
Get-PodeTimer

.EXAMPLE
Get-PodeTimer -Name Name1, Name2
#>
function Get-PodeTimer {
    [CmdletBinding()]
    param(
        [Parameter()]
        [string[]]
        $Name
    )

    $timers = $PodeContext.Timers.Items.Values

    # further filter by timer names
    if (($null -ne $Name) -and ($Name.Length -gt 0)) {
        $timers = @(foreach ($_name in $Name) {
                foreach ($timer in $timers) {
                    if ($timer.Name -ine $_name) {
                        continue
                    }

                    $timer
                }
            })
    }

    # return
    return $timers
}

<#
.SYNOPSIS
Tests whether the passed Timer exists.

.DESCRIPTION
Tests whether the passed Timer exists by its name.

.PARAMETER Name
The Name of the Timer.

.EXAMPLE
if (Test-PodeTimer -Name TimerName) { }
#>
function Test-PodeTimer {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $true)]
        [string]
        $Name
    )

    return (($null -ne $PodeContext.Timers.Items) -and $PodeContext.Timers.Items.ContainsKey($Name))
}

<#
.SYNOPSIS
Automatically loads timer ps1 files

.DESCRIPTION
Automatically loads timer ps1 files from either a /timers folder, or a custom folder. Saves space dot-sourcing them all one-by-one.

.PARAMETER Path
Optional Path to a folder containing ps1 files, can be relative or literal.

.EXAMPLE
Use-PodeTimers

.EXAMPLE
Use-PodeTimers -Path './my-timers'
#>
function Use-PodeTimers {
    [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseSingularNouns', '')]
    [CmdletBinding()]
    param(
        [Parameter()]
        [string]
        $Path
    )

    Use-PodeFolder -Path $Path -DefaultPath 'timers'
}
src\Public\Utilities.ps1
<#
.SYNOPSIS
Dispose and close streams, tokens, and other Disposables.

.DESCRIPTION
Dispose and close streams, tokens, and other Disposables.

.PARAMETER Disposable
The Disposable object to dispose and close.

.PARAMETER Close
Should the Disposable also be closed, as well as disposed?

.PARAMETER CheckNetwork
If an error is thrown, check the reason - if it's network related ignore the error.

.EXAMPLE
Close-PodeDisposable -Disposable $stream -Close
#>
function Close-PodeDisposable {
    [CmdletBinding()]
    param(
        [Parameter()]
        [System.IDisposable]
        $Disposable,

        [switch]
        $Close,

        [switch]
        $CheckNetwork
    )

    if ($null -eq $Disposable) {
        return
    }

    try {
        if ($Close) {
            $Disposable.Close()
        }
    }
    catch [exception] {
        if ($CheckNetwork -and (Test-PodeValidNetworkFailure $_.Exception)) {
            return
        }

        $_ | Write-PodeErrorLog
        throw $_.Exception
    }
    finally {
        $Disposable.Dispose()
    }
}

<#
.SYNOPSIS
Returns the literal path of the server.

.DESCRIPTION
Returns the literal path of the server.

.EXAMPLE
$path = Get-PodeServerPath
#>
function Get-PodeServerPath {
    [CmdletBinding()]
    [OutputType([string])]
    param()

    return $PodeContext.Server.Root
}

<#
.SYNOPSIS
Starts a Stopwatch on some ScriptBlock, and outputs the duration at the end.

.DESCRIPTION
Starts a Stopwatch on some ScriptBlock, and outputs the duration at the end.

.PARAMETER Name
The name of the Stopwatch.

.PARAMETER ScriptBlock
The ScriptBlock to time.

.EXAMPLE
Start-PodeStopwatch -Name 'ReadFile' -ScriptBlock { $content = Get-Content './file.txt' }
#>
function Start-PodeStopwatch {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $true)]
        [string]
        $Name,

        [Parameter(Mandatory = $true, Position = 0, ValueFromPipeline = $true)]
        [scriptblock]
        $ScriptBlock
    )
    begin {
        $pipelineItemCount = 0
    }

    process {
        $pipelineItemCount++
    }

    end {
        if ($pipelineItemCount -gt 1) {
            throw ($PodeLocale.fnDoesNotAcceptArrayAsPipelineInputExceptionMessage -f $($MyInvocation.MyCommand.Name))
        }
        try {
            $watch = [System.Diagnostics.Stopwatch]::StartNew()
            . $ScriptBlock
        }
        catch {
            $_ | Write-PodeErrorLog
            throw $_.Exception
        }
        finally {
            $watch.Stop()
            "[Stopwatch]: $($watch.Elapsed) [$($Name)]" | Out-PodeHost
        }
    }
}

<#
.SYNOPSIS
Like the "using" keyword in .NET. Allows you to use a Stream and then disposes of it.

.DESCRIPTION
Like the "using" keyword in .NET. Allows you to use a Stream and then disposes of it.

.PARAMETER Stream
The Stream to use and then dispose.

.PARAMETER ScriptBlock
The ScriptBlock to invoke. It will be supplied the Stream.

.EXAMPLE
$content = (Use-PodeStream -Stream $stream -ScriptBlock { return $args[0].ReadToEnd() })
#>
function Use-PodeStream {
    [CmdletBinding()]
    [OutputType([object])]
    param(
        [Parameter(Mandatory = $true)]
        [System.IDisposable]
        $Stream,

        [Parameter(Mandatory = $true)]
        [scriptblock]
        $ScriptBlock
    )

    try {
        return (Invoke-PodeScriptBlock -ScriptBlock $ScriptBlock -Arguments $Stream -Return -NoNewClosure)
    }
    catch {
        $_ | Write-PodeErrorLog
        throw $_.Exception
    }
    finally {
        $Stream.Dispose()
    }
}

<#
.SYNOPSIS
Loads a script, by dot-sourcing, at the supplied path.

.DESCRIPTION
Loads a script, by dot-sourcing, at the supplied path. If the path is relative, the server's path is prepended.

.PARAMETER Path
The path, literal or relative to the server, to some script.

.EXAMPLE
Use-PodeScript -Path './scripts/tools.ps1'
#>
function Use-PodeScript {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $true)]
        [string]
        $Path
    )

    # if path is '.', replace with server root
    $_path = Get-PodeRelativePath -Path $Path -JoinRoot -Resolve

    # we have a path, if it's a directory/wildcard then loop over all files
    if (![string]::IsNullOrWhiteSpace($_path)) {
        $_paths = Get-PodeWildcardFile -Path $Path -Wildcard '*.ps1'
        if (!(Test-PodeIsEmpty $_paths)) {
            foreach ($_path in $_paths) {
                Use-PodeScript -Path $_path
            }

            return
        }
    }

    # check if the path exists
    if (!(Test-PodePath $_path -NoStatus)) {
        # The script path does not exist
        throw ($PodeLocale.scriptPathDoesNotExistExceptionMessage -f (Protect-PodeValue -Value $_path -Default $Path))
    }

    # dot-source the script
    . $_path

    # load any functions from the file into pode's runspaces
    Import-PodeFunctionsIntoRunspaceState -FilePath $_path
}

<#
.SYNOPSIS
Returns the loaded configuration of the server.

.DESCRIPTION
Returns the loaded configuration of the server.

.EXAMPLE
$s = Get-PodeConfig
#>
function Get-PodeConfig {
    [CmdletBinding()]
    [OutputType([hashtable])]
    param()

    return $PodeContext.Server.Configuration
}

<#
.SYNOPSIS
Adds a ScriptBlock as Endware to run at the end of each web Request.

.DESCRIPTION
Adds a ScriptBlock as Endware to run at the end of each web Request.

.PARAMETER ScriptBlock
The ScriptBlock to add. It will be supplied the current web event.

.PARAMETER ArgumentList
An array of arguments to supply to the Endware's ScriptBlock.

.EXAMPLE
Add-PodeEndware -ScriptBlock { /* logic */ }
#>
function Add-PodeEndware {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $true, Position = 0, ValueFromPipeline = $true)]
        [scriptblock]
        $ScriptBlock,

        [Parameter()]
        [object[]]
        $ArgumentList
    )
    begin {
        $pipelineItemCount = 0
    }

    process {
        $pipelineItemCount++
    }

    end {
        if ($pipelineItemCount -gt 1) {
            throw ($PodeLocale.fnDoesNotAcceptArrayAsPipelineInputExceptionMessage -f $($MyInvocation.MyCommand.Name))
        }
        # check for scoped vars
        $ScriptBlock, $usingVars = Convert-PodeScopedVariables -ScriptBlock $ScriptBlock -PSSession $PSCmdlet.SessionState

        # add the scriptblock to array of endware that needs to be run
        $PodeContext.Server.Endware += @{
            Logic          = $ScriptBlock
            UsingVariables = $usingVars
            Arguments      = $ArgumentList
        }
    }
}

<#
.SYNOPSIS
Automatically loads endware ps1 files

.DESCRIPTION
Automatically loads endware ps1 files from either a /endware folder, or a custom folder. Saves space dot-sourcing them all one-by-one.

.PARAMETER Path
Optional Path to a folder containing ps1 files, can be relative or literal.

.EXAMPLE
Use-PodeEndware

.EXAMPLE
Use-PodeEndware -Path './endware'
#>
function Use-PodeEndware {
    [CmdletBinding()]
    param(
        [Parameter()]
        [string]
        $Path
    )

    Use-PodeFolder -Path $Path -DefaultPath 'endware'
}

<#
.SYNOPSIS
Imports a Module into the current, and all runspaces that Pode uses.

.DESCRIPTION
Imports a Module into the current, and all runspaces that Pode uses. Modules can also be imported from the ps_modules directory.

.PARAMETER Name
The name of a globally installed Module, or one within the ps_modules directory, to import.

.PARAMETER Path
The path, literal or relative, to a Module to import.

.EXAMPLE
Import-PodeModule -Name IISManager

.EXAMPLE
Import-PodeModule -Path './modules/utilities.psm1'
#>
function Import-PodeModule {
    [CmdletBinding(DefaultParameterSetName = 'Name')]
    param(
        [Parameter(Mandatory = $true, ParameterSetName = 'Name')]
        [string]
        $Name,

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

    # script root path
    $rootPath = $null
    if ($null -eq $PodeContext) {
        $rootPath = (Protect-PodeValue -Value $MyInvocation.PSScriptRoot -Default $pwd.Path)
    }

    # get the path of a module, or import modules on mass
    switch ($PSCmdlet.ParameterSetName.ToLowerInvariant()) {
        'name' {
            $modulePath = Join-PodeServerRoot -Folder ([System.IO.Path]::Combine('ps_modules', $Name)) -Root $rootPath
            if (Test-PodePath -Path $modulePath -NoStatus) {
                $Path = (Get-ChildItem ([System.IO.Path]::Combine($modulePath, '*', "$($Name).ps*1")) -Recurse -Force | Select-Object -First 1).FullName
            }
            else {
                $Path = Find-PodeModuleFile -Name $Name -ListAvailable
            }
        }

        'path' {
            $Path = Get-PodeRelativePath -Path $Path -RootPath $rootPath -JoinRoot -Resolve
            $paths = Get-PodeWildcardFile -Path $Path -RootPath $rootPath -Wildcard '*.ps*1'
            if (!(Test-PodeIsEmpty $paths)) {
                foreach ($_path in $paths) {
                    Import-PodeModule -Path $_path
                }

                return
            }
        }
    }

    # if it's still empty, error
    if ([string]::IsNullOrWhiteSpace($Path)) {
        # Failed to import module
        throw ($PodeLocale.failedToImportModuleExceptionMessage -f (Protect-PodeValue -Value $Path -Default $Name))
    }

    # check if the path exists
    if (!(Test-PodePath $Path -NoStatus)) {
        # The module path does not exist
        throw ($PodeLocale.modulePathDoesNotExistExceptionMessage -f (Protect-PodeValue -Value $Path -Default $Name))
    }

    $null = Import-Module $Path -Force -DisableNameChecking -Scope Global -ErrorAction Stop
}

<#
.SYNOPSIS
Imports a Snapin into the current, and all runspaces that Pode uses.

.DESCRIPTION
Imports a Snapin into the current, and all runspaces that Pode uses.

.PARAMETER Name
The name of a Snapin to import.

.EXAMPLE
Import-PodeSnapin -Name 'WDeploySnapin3.0'
#>
function Import-PodeSnapin {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $true)]
        [string]
        $Name
    )

    # if non-windows or core, fail
    if ((Test-PodeIsPSCore) -or (Test-PodeIsUnix)) {
        # Snapins are only supported on Windows PowerShell
        throw ($PodeLocale.snapinsSupportedOnWindowsPowershellOnlyExceptionMessage)
    }

    # import the snap-in
    $null = Add-PSSnapin -Name $Name
}

<#
.SYNOPSIS
Protects a value, by returning a default value is the main one is null/empty.

.DESCRIPTION
Protects a value, by returning a default value is the main one is null/empty.

.PARAMETER Value
The main value to use.

.PARAMETER Default
A default value to return should the main value be null/empty.

.EXAMPLE
$Name = Protect-PodeValue -Value $Name -Default 'Rick'
#>
function Protect-PodeValue {
    [CmdletBinding()]
    [OutputType([object])]
    param(
        [Parameter()]
        $Value,

        [Parameter()]
        $Default
    )

    return (Resolve-PodeValue -Check (Test-PodeIsEmpty $Value) -TrueValue $Default -FalseValue $Value)
}

<#
.SYNOPSIS
Resolves a query, and returns a value based on the response.

.DESCRIPTION
Resolves a query, and returns a value based on the response.

.PARAMETER Check
The query, or variable, to evalulate.

.PARAMETER TrueValue
The value to use if evaluated to True.

.PARAMETER FalseValue
The value to use if evaluated to False.

.EXAMPLE
$Port = Resolve-PodeValue -Check $AllowSsl -TrueValue 443 -FalseValue -80
#>
function Resolve-PodeValue {
    [CmdletBinding()]
    [OutputType([object])]
    param(
        [Parameter(Mandatory = $true)]
        [bool]
        $Check,

        [Parameter()]
        $TrueValue,

        [Parameter()]
        $FalseValue
    )

    if ($Check) {
        return $TrueValue
    }

    return $FalseValue
}

<#
.SYNOPSIS
Invokes a ScriptBlock.

.DESCRIPTION
Invokes a ScriptBlock, supplying optional arguments, splatting, and returning any optional values.

.PARAMETER ScriptBlock
The ScriptBlock to invoke.

.PARAMETER Arguments
Any arguments that should be supplied to the ScriptBlock.

.PARAMETER UsingVariables
Optional array of "using-variable" values, which will be automatically prepended to any supplied Arguments when supplied to the ScriptBlock.

.PARAMETER Scoped
Run the ScriptBlock in a scoped context.

.PARAMETER Return
Return any values that the ScriptBlock may return.

.PARAMETER Splat
Spat the argument onto the ScriptBlock.

.PARAMETER NoNewClosure
Don't create a new closure before invoking the ScriptBlock.

.EXAMPLE
Invoke-PodeScriptBlock -ScriptBlock { Write-PodeHost 'Hello!' }

.EXAMPLE
Invoke-PodeScriptBlock -Arguments 'Morty' -ScriptBlock { /* logic */ }
#>
function Invoke-PodeScriptBlock {
    [CmdletBinding()]
    [OutputType([object])]
    param(
        [Parameter(Mandatory = $true)]
        [scriptblock]
        $ScriptBlock,

        [Parameter()]
        $Arguments = $null,

        [Parameter()]
        [object[]]
        $UsingVariables = $null,

        [switch]
        $Scoped,

        [switch]
        $Return,

        [switch]
        $Splat,

        [switch]
        $NoNewClosure
    )

    # force no new closure if running serverless
    if ($PodeContext.Server.IsServerless) {
        $NoNewClosure = $true
    }

    # if new closure needed, create it
    if (!$NoNewClosure) {
        $ScriptBlock = ($ScriptBlock).GetNewClosure()
    }

    # merge arguments together, if we have using vars supplied
    if (($null -ne $UsingVariables) -and ($UsingVariables.Length -gt 0)) {
        $Arguments = @(Merge-PodeScriptblockArguments -ArgumentList $Arguments -UsingVariables $UsingVariables)
    }

    # invoke the scriptblock
    if ($Scoped) {
        if ($Splat) {
            $result = (& $ScriptBlock @Arguments)
        }
        else {
            $result = (& $ScriptBlock $Arguments)
        }
    }
    else {
        if ($Splat) {
            $result = (. $ScriptBlock @Arguments)
        }
        else {
            $result = (. $ScriptBlock $Arguments)
        }
    }

    # if needed, return the result
    if ($Return) {
        return $result
    }
}

<#
.SYNOPSIS
Merges Arguments and Using Variables together.

.DESCRIPTION
Merges Arguments and Using Variables together to be supplied to a ScriptBlock.
The Using Variables will be prepended so then are supplied first to a ScriptBlock.

.PARAMETER ArgumentList
And optional array of Arguments.

.PARAMETER UsingVariables
And optional array of "using-variable" values to be prepended.

.EXAMPLE
$Arguments = @(Merge-PodeScriptblockArguments -ArgumentList $Arguments -UsingVariables $UsingVariables)

.EXAMPLE
$Arguments = @(Merge-PodeScriptblockArguments -UsingVariables $UsingVariables)
#>
function Merge-PodeScriptblockArguments {
    [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseSingularNouns', '')]
    [CmdletBinding()]
    [OutputType([object[]])]
    param(
        [Parameter()]
        [object[]]
        $ArgumentList = $null,

        [Parameter()]
        [object[]]
        $UsingVariables = $null
    )

    if ($null -eq $ArgumentList) {
        $ArgumentList = @()
    }

    if (($null -eq $UsingVariables) -or ($UsingVariables.Length -le 0)) {
        return $ArgumentList
    }

    $_vars = @()
    foreach ($_var in $UsingVariables) {
        $_vars += , $_var.Value
    }

    return ($_vars + $ArgumentList)
}

<#
.SYNOPSIS
Tests if a value is empty - the value can be of any type.

.DESCRIPTION
Tests if a value is empty - the value can be of any type.

.PARAMETER Value
The value to test.

.EXAMPLE
if (Test-PodeIsEmpty @{}) { /* logic */ }
#>
function Test-PodeIsEmpty {
    [CmdletBinding()]
    [OutputType([bool])]
    param(
        [Parameter()]
        $Value
    )

    if ($null -eq $Value) {
        return $true
    }

    if ($Value -is [string]) {
        return [string]::IsNullOrWhiteSpace($Value)
    }

    if ($Value -is [array]) {
        return ($Value.Length -eq 0)
    }

    if (($Value -is [hashtable]) -or ($Value -is [System.Collections.Specialized.OrderedDictionary])) {
        return ($Value.Count -eq 0)
    }

    if ($Value -is [scriptblock]) {
        return ([string]::IsNullOrWhiteSpace($Value.ToString()))
    }

    if ($Value -is [valuetype]) {
        return $false
    }

    return ([string]::IsNullOrWhiteSpace($Value) -or ((Get-PodeCount $Value) -eq 0))
}

<#
.SYNOPSIS
Tests if the the current session is running in PowerShell Core.

.DESCRIPTION
Tests if the the current session is running in PowerShell Core.

.EXAMPLE
if (Test-PodeIsPSCore) { /* logic */ }
#>
function Test-PodeIsPSCore {
    [CmdletBinding()]
    [OutputType([bool])]
    param()

    return (Get-PodePSVersionTable).PSEdition -ieq 'core'
}

<#
.SYNOPSIS
Tests if the current OS is Unix.

.DESCRIPTION
Tests if the current OS is Unix.

.EXAMPLE
if (Test-PodeIsUnix) { /* logic */ }
#>
function Test-PodeIsUnix {
    [CmdletBinding()]
    [OutputType([bool])]
    param()

    return (Get-PodePSVersionTable).Platform -ieq 'unix'
}

<#
.SYNOPSIS
Tests if the current OS is Windows.

.DESCRIPTION
Tests if the current OS is Windows.

.EXAMPLE
if (Test-PodeIsWindows) { /* logic */ }
#>
function Test-PodeIsWindows {
    [CmdletBinding()]
    [OutputType([bool])]
    param()

    $v = Get-PodePSVersionTable
    return ($v.Platform -ilike '*win*' -or ($null -eq $v.Platform -and $v.PSEdition -ieq 'desktop'))
}

<#
.SYNOPSIS
Tests if the current OS is MacOS.

.DESCRIPTION
Tests if the current OS is MacOS.

.EXAMPLE
if (Test-PodeIsMacOS) { /* logic */ }
#>
function Test-PodeIsMacOS {
    [CmdletBinding()]
    [OutputType([bool])]
    param()

    return ([bool]$IsMacOS)
}

<#
.SYNOPSIS
Tests if the scope you're in is currently within a Pode runspace.

.DESCRIPTION
Tests if the scope you're in is currently within a Pode runspace.

.EXAMPLE
If (Test-PodeInRunspace) { ... }
#>
function Test-PodeInRunspace {
    [CmdletBinding()]
    param()

    return ([bool]$PODE_SCOPE_RUNSPACE)
}

<#
.SYNOPSIS
Outputs an object to the main Host.

.DESCRIPTION
Due to Pode's use of runspaces, this will output a given object back to the main Host.
It's advised to use this function, so that any output respects the -Quiet flag of the server.

.PARAMETER InputObject
The object to output.

.EXAMPLE
'Hello, world!' | Out-PodeHost

.EXAMPLE
@{ Name = 'Rick' } | Out-PodeHost
#>
function Out-PodeHost {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $true, Position = 0, ValueFromPipeline = $true)]
        [object]
        $InputObject
    )
    begin {
        # Initialize an array to hold piped-in values
        $pipelineValue = @()
    }

    process {
        # Add the current piped-in value to the array
        $pipelineValue += $_
    }

    end {
        if ($PodeContext.Server.Quiet) {
            return
        }
        # Set InputObject to the array of values
        if ($pipelineValue.Count -gt 1) {
            $InputObject = $pipelineValue
            $InputObject | Out-Default
        }
        else {
            Out-Default -InputObject $InputObject
        }
    }

}

<#
.SYNOPSIS
Writes an object to the Host.

.DESCRIPTION
Writes an object to the Host.
It's advised to use this function, so that any output respects the -Quiet flag of the server.

.PARAMETER Object
The object to write.

.PARAMETER ForegroundColor
An optional foreground colour.

.PARAMETER NoNewLine
Whether or not to write a new line.

.PARAMETER Explode
Show the object content

.PARAMETER ShowType
Show the Object Type

.PARAMETER Label
Show a label for the object

.EXAMPLE
'Some output' | Write-PodeHost -ForegroundColor Cyan
#>
function Write-PodeHost {
    [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidUsingWriteHost', '')]
    [CmdletBinding(DefaultParameterSetName = 'inbuilt')]
    param(
        [Parameter(Position = 0, ValueFromPipeline = $true)]
        [object]
        $Object,

        [Parameter()]
        [System.ConsoleColor]
        $ForegroundColor,

        [switch]
        $NoNewLine,

        [Parameter( Mandatory = $true, ParameterSetName = 'object')]
        [switch]
        $Explode,

        [Parameter( Mandatory = $false, ParameterSetName = 'object')]
        [switch]
        $ShowType,

        [Parameter( Mandatory = $false, ParameterSetName = 'object')]
        [string]
        $Label
    )
    begin {
        # Initialize an array to hold piped-in values
        $pipelineValue = @()
    }

    process {
        # Add the current piped-in value to the array
        $pipelineValue += $_
    }

    end {
        if ($PodeContext.Server.Quiet) {
            return
        }
        # Set Object to the array of values
        if ($pipelineValue.Count -gt 1) {
            $Object = $pipelineValue
        }

        if ($Explode.IsPresent ) {
            if ($null -eq $Object) {
                if ($ShowType) {
                    $Object = "`tNull Value"
                }
            }
            else {
                $type = $Object.gettype().FullName
                $Object = $Object | Out-String
                if ($ShowType) {
                    $Object = "`tTypeName: $type`n$Object"
                }
            }
            if ($Label) {
                $Object = "`tName: $Label $Object"
            }

        }

        if ($ForegroundColor) {
            if ($pipelineValue.Count -gt 1) {
                $Object | Write-Host -ForegroundColor $ForegroundColor -NoNewline:$NoNewLine
            }
            else {
                Write-Host -Object $Object -ForegroundColor $ForegroundColor -NoNewline:$NoNewLine
            }
        }
        else {
            if ($pipelineValue.Count -gt 1) {
                $Object | Write-Host -NoNewline:$NoNewLine
            }
            else {
                Write-Host -Object $Object -NoNewline:$NoNewLine
            }
        }
    }
}

<#
.SYNOPSIS
Returns whether or not the server is running via IIS.

.DESCRIPTION
Returns whether or not the server is running via IIS.

.EXAMPLE
if (Test-PodeIsIIS) { }
#>
function Test-PodeIsIIS {
    [CmdletBinding()]
    param()

    return $PodeContext.Server.IsIIS
}

<#
.SYNOPSIS
Returns the IIS application path.

.DESCRIPTION
Returns the IIS application path, or null if not using IIS.

.EXAMPLE
$path = Get-PodeIISApplicationPath
#>
function Get-PodeIISApplicationPath {
    [CmdletBinding()]
    param()

    if (!$PodeContext.Server.IsIIS) {
        return $null
    }

    return $PodeContext.Server.IIS.Path.Raw
}

<#
.SYNOPSIS
Returns whether or not the server is running via Heroku.

.DESCRIPTION
Returns whether or not the server is running via Heroku.

.EXAMPLE
if (Test-PodeIsHeroku) { }
#>
function Test-PodeIsHeroku {
    [CmdletBinding()]
    param()

    return $PodeContext.Server.IsHeroku
}

<#
.SYNOPSIS
Returns whether or not the server is being hosted behind another application.

.DESCRIPTION
Returns whether or not the server is being hosted behind another application, such as Heroku or IIS.

.EXAMPLE
if (Test-PodeIsHosted) { }
#>
function Test-PodeIsHosted {
    [CmdletBinding()]
    param()

    return ((Test-PodeIsIIS) -or (Test-PodeIsHeroku))
}

<#
.SYNOPSIS
Defines variables to be created when the Pode server stops.

.DESCRIPTION
Allows you to define a variable, with a value, that should be created on the in the main scope after the Pode server is stopped.

.PARAMETER Name
The Name of the variable to be set

.PARAMETER Value
The Value of the variable to be set

.EXAMPLE
Out-PodeVariable -Name ExampleVar -Value @{ Name = 'Bob' }
#>
function Out-PodeVariable {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $true)]
        [string]
        $Name,

        [Parameter(Position = 0, ValueFromPipeline = $true)]
        [object]
        $Value
    )
    begin {
        # Initialize an array to hold piped-in values
        $pipelineValue = @()
    }

    process {
        # Add the current piped-in value to the array
        $pipelineValue += $_
    }

    end {
        # Set Value to the array of values
        if ($pipelineValue.Count -gt 1) {
            $Value = $pipelineValue
        }

        $PodeContext.Server.Output.Variables[$Name] = $Value
    }
}

<#
.SYNOPSIS
A helper function to generate cron expressions.

.DESCRIPTION
A helper function to generate cron expressions, which can be used for Schedules and other functions that use cron expressions.
This helper function only covers simple cron use-cases, with some advanced use-cases. If you need further advanced cron
expressions it would be best to write the expression by hand.

.PARAMETER Minute
This is an array of Minutes that the expression should use between 0-59.

.PARAMETER Hour
This is an array of Hours that the expression should use between 0-23.

.PARAMETER Date
This is an array of Dates in the monnth that the expression should use between 1-31.

.PARAMETER Month
This is an array of Months that the expression should use between January-December.

.PARAMETER Day
This is an array of Days in the week that the expression should use between Monday-Sunday.

.PARAMETER Every
This can be used to more easily specify "Every Hour" than writing out all the hours.

.PARAMETER Interval
This can only be used when using the Every parameter, and will setup an interval on the "every" used.
If you want "every 2 hours" then Every should be set to Hour and Interval to 2.

.EXAMPLE
New-PodeCron -Every Day                                             # every 00:00

.EXAMPLE
New-PodeCron -Every Day -Day Tuesday, Friday -Hour 1                # every tuesday and friday at 01:00

.EXAMPLE
New-PodeCron -Every Month -Date 15                                  # every 15th of the month at 00:00

.EXAMPLE
New-PodeCron -Every Date -Interval 2 -Date 2                        # every month, every other day from 2nd, at 00:00

.EXAMPLE
New-PodeCron -Every Year -Month June                                # every 1st june, at 00:00

.EXAMPLE
New-PodeCron -Every Hour -Hour 1 -Interval 1                        # every hour, starting at 01:00

.EXAMPLE
New-PodeCron -Every Minute -Hour 1, 2, 3, 4, 5 -Interval 15         # every 15mins, starting at 01:00 until 05:00

.EXAMPLE
New-PodeCron -Every Hour -Day Monday                                # every hour of every monday

.EXAMPLE
New-PodeCron -Every Quarter                                         # every 1st jan, apr, jul, oct, at 00:00
#>
function New-PodeCron {
    [CmdletBinding()]
    [OutputType([String])]
    param(
        [Parameter()]
        [ValidateRange(0, 59)]
        [int[]]
        $Minute = $null,

        [Parameter()]
        [ValidateRange(0, 23)]
        [int[]]
        $Hour = $null,

        [Parameter()]
        [ValidateRange(1, 31)]
        [int[]]
        $Date = $null,

        [Parameter()]
        [ValidateSet('January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December')]
        [string[]]
        $Month = $null,

        [Parameter()]
        [ValidateSet('Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', 'Sunday')]
        [string[]]
        $Day = $null,

        [Parameter()]
        [ValidateSet('Minute', 'Hour', 'Day', 'Date', 'Month', 'Quarter', 'Year', 'None')]
        [string]
        $Every = 'None',

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

    # cant have None and Interval
    if (($Every -ieq 'none') -and ($Interval -gt 0)) {
        # Cannot supply an interval when the parameter `Every` is set to None
        throw ($PodeLocale.cannotSupplyIntervalWhenEveryIsNoneExceptionMessage)
    }

    # base cron
    $cron = @{
        Minute = '*'
        Hour   = '*'
        Date   = '*'
        Month  = '*'
        Day    = '*'
    }

    # convert month/day to numbers
    if ($Month.Length -gt 0) {
        $MonthInts = @(foreach ($item in $Month) {
            (@{
                    January   = 1
                    February  = 2
                    March     = 3
                    April     = 4
                    May       = 5
                    June      = 6
                    July      = 7
                    August    = 8
                    September = 9
                    October   = 10
                    November  = 11
                    December  = 12
                })[$item]
            })
    }

    if ($Day.Length -gt 0) {
        $DayInts = @(foreach ($item in $Day) {
            (@{
                    Sunday    = 0
                    Monday    = 1
                    Tuesday   = 2
                    Wednesday = 3
                    Thursday  = 4
                    Friday    = 5
                    Saturday  = 6
                })[$item]
            })
    }

    # set "every" defaults
    switch ($Every.ToUpperInvariant()) {
        'MINUTE' {
            if (Set-PodeCronInterval -Cron $cron -Type 'Minute' -Value $Minute -Interval $Interval) {
                $Minute = @()
            }
        }

        'HOUR' {
            $cron.Minute = '0'

            if (Set-PodeCronInterval -Cron $cron -Type 'Hour' -Value $Hour -Interval $Interval) {
                $Hour = @()
            }
        }

        'DAY' {
            $cron.Minute = '0'
            $cron.Hour = '0'

            if (Set-PodeCronInterval -Cron $cron -Type 'Day' -Value $DayInts -Interval $Interval) {
                $DayInts = @()
            }
        }

        'DATE' {
            $cron.Minute = '0'
            $cron.Hour = '0'

            if (Set-PodeCronInterval -Cron $cron -Type 'Date' -Value $Date -Interval $Interval) {
                $Date = @()
            }
        }

        'MONTH' {
            $cron.Minute = '0'
            $cron.Hour = '0'

            if ($DayInts.Length -eq 0) {
                $cron.Date = '1'
            }

            if (Set-PodeCronInterval -Cron $cron -Type 'Month' -Value $MonthInts -Interval $Interval) {
                $MonthInts = @()
            }
        }

        'QUARTER' {
            $cron.Minute = '0'
            $cron.Hour = '0'
            $cron.Date = '1'
            $cron.Month = '1,4,7,10'

            if ($Interval -gt 0) {
                # Cannot supply interval value for every quarter
                throw ($PodeLocale.cannotSupplyIntervalForQuarterExceptionMessage)
            }
        }

        'YEAR' {
            $cron.Minute = '0'
            $cron.Hour = '0'
            $cron.Date = '1'
            $cron.Month = '1'

            if ($Interval -gt 0) {
                # Cannot supply interval value for every year
                throw ($PodeLocale.cannotSupplyIntervalForYearExceptionMessage)
            }
        }
    }

    # set any custom overrides
    if ($Minute.Length -gt 0) {
        $cron.Minute = $Minute -join ','
    }

    if ($Hour.Length -gt 0) {
        $cron.Hour = $Hour -join ','
    }

    if ($DayInts.Length -gt 0) {
        $cron.Day = $DayInts -join ','
    }

    if ($Date.Length -gt 0) {
        $cron.Date = $Date -join ','
    }

    if ($MonthInts.Length -gt 0) {
        $cron.Month = $MonthInts -join ','
    }

    # 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]'
    }
}

<#
.SYNOPSIS
Converts an XML node to a PowerShell hashtable.

.DESCRIPTION
The ConvertFrom-PodeXml function converts an XML node, including all its child nodes and attributes, into an ordered hashtable. This is useful for manipulating XML data in a more PowerShell-centric way.

.PARAMETER node
The XML node to convert. This parameter takes an XML node and processes it, along with its child nodes and attributes.

.PARAMETER Prefix
A string prefix used to indicate an attribute. Default is an empty string.

.PARAMETER ShowDocElement
Indicates whether to show the document element. Default is false.

.PARAMETER KeepAttributes
If set, the function keeps the attributes of the XML nodes in the resulting hashtable.

.EXAMPLE
$node = [xml](Get-Content 'path\to\file.xml').DocumentElement
ConvertFrom-PodeXml -node $node

Converts the XML document's root node to a hashtable.

.INPUTS
System.Xml.XmlNode
You can pipe a XmlNode to ConvertFrom-PodeXml.

.OUTPUTS
System.Collections.Hashtable
Outputs an ordered hashtable representing the XML node structure.

.NOTES
This cmdlet is useful for transforming XML data into a structure that's easier to manipulate in PowerShell scripts.
#>
function ConvertFrom-PodeXml {
    [CmdletBinding()]
    [OutputType([System.Collections.Specialized.OrderedDictionary])]
    param
    (
        [Parameter(Mandatory = $true, Position = 0, ValueFromPipeline = $true)]
        [System.Xml.XmlNode]$node,

        [Parameter()]
        [string]
        $Prefix = '',

        [Parameter()]
        [switch]
        $ShowDocElement,

        [Parameter()]
        [switch]
        $KeepAttributes
    )
    process {
        #if option set, we skip the Document element
        if ($node.DocumentElement -and !($ShowDocElement.IsPresent))
        { $node = $node.DocumentElement }
        $oHash = [ordered] @{ } # start with an ordered hashtable.
        #The order of elements is always significant regardless of what they are
        if ($null -ne $node.Attributes  ) {
            #if there are elements
            # record all the attributes first in the ordered hash
            $node.Attributes | ForEach-Object {
                $oHash.$("$Prefix$($_.FirstChild.parentNode.LocalName)") = $_.FirstChild.value
            }
        }
        # check to see if there is a pseudo-array. (more than one
        # child-node with the same name that must be handled as an array)
        $node.ChildNodes | #we just group the names and create an empty
            #array for each
            Group-Object -Property LocalName | Where-Object { $_.count -gt 1 } | Select-Object Name |
            ForEach-Object {
                $oHash.($_.Name) = @() <# create an empty array for each one#>
            }
        foreach ($child in $node.ChildNodes) {
            #now we look at each node in turn.
            $childName = $child.LocalName
            if ($child -is [system.xml.xmltext]) {
                # if it is simple XML text
                $oHash.$childname += $child.InnerText
            }
            # if it has a #text child we may need to cope with attributes
            elseif ($child.FirstChild.Name -eq '#text' -and $child.ChildNodes.Count -eq 1) {
                if ($null -ne $child.Attributes -and $KeepAttributes ) {
                    #hah, an attribute
                    <#we need to record the text with the #text label and preserve all
					the attributes #>
                    $aHash = [ordered]@{ }
                    $child.Attributes | ForEach-Object {
                        $aHash.$($_.FirstChild.parentNode.LocalName) = $_.FirstChild.value
                    }
                    #now we add the text with an explicit name
                    $aHash.'#text' += $child.'#text'
                    $oHash.$childname += $aHash
                }
                else {
                    #phew, just a simple text attribute.
                    $oHash.$childname += $child.FirstChild.InnerText
                }
            }
            elseif ($null -ne $child.'#cdata-section' ) {
                # if it is a data section, a block of text that isnt parsed by the parser,
                # but is otherwise recognized as markup
                $oHash.$childname = $child.'#cdata-section'
            }
            elseif ($child.ChildNodes.Count -gt 1 -and
                        ($child | Get-Member -MemberType Property).Count -eq 1) {
                $oHash.$childname = @()
                foreach ($grandchild in $child.ChildNodes) {
                    $oHash.$childname += (ConvertFrom-PodeXml $grandchild)
                }
            }
            else {
                # create an array as a value  to the hashtable element
                $oHash.$childname += (ConvertFrom-PodeXml $child)
            }
        }
        return $oHash
    }
}

<#
.SYNOPSIS
Invokes the garbage collector.

.DESCRIPTION
Invokes the garbage collector.

.EXAMPLE
Invoke-PodeGC
#>
function Invoke-PodeGC {
    [CmdletBinding()]
    param()

    [System.GC]::Collect()
}
src\Public\Verbs.ps1
<#
.SYNOPSIS
Adds a Verb for a TCP data.

.DESCRIPTION
Adds a Verb for a TCP data.

.PARAMETER Verb
The Verb for the Verb.

.PARAMETER ScriptBlock
A ScriptBlock for the Verb's main logic.

.PARAMETER EndpointName
The EndpointName of an Endpoint(s) this Verb should be bound against.

.PARAMETER FilePath
A literal, or relative, path to a file containing a ScriptBlock for the Verb's main logic.

.PARAMETER ArgumentList
An array of arguments to supply to the Verb's ScriptBlock.

.PARAMETER UpgradeToSsl
If supplied, the Verb will auto-upgrade the connection to use SSL.

.PARAMETER Close
If supplied, the Verb will auto-close the connection.

.EXAMPLE
Add-PodeVerb -Verb 'Hello' -ScriptBlock { /* logic */ }

.EXAMPLE
Add-PodeVerb -Verb 'Hello' -ScriptBlock { /* logic */ } -ArgumentList 'arg1', 'arg2'

.EXAMPLE
Add-PodeVerb -Verb 'Quit' -Close

.EXAMPLE
Add-PodeVerb -Verb 'StartTls' -UpgradeToSsl
#>
function Add-PodeVerb {
    [CmdletBinding(DefaultParameterSetName = 'Script')]
    param(
        [Parameter(Mandatory = $true)]
        [ValidateNotNullOrEmpty()]
        [string]
        $Verb,

        [Parameter(ParameterSetName = 'Script')]
        [scriptblock]
        $ScriptBlock,

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

        [Parameter()]
        [object[]]
        $ArgumentList,

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

        [switch]
        $UpgradeToSsl,

        [switch]
        $Close
    )

    # find placeholder parameters in verb (ie: COMMAND :parameter)
    $Verb = Resolve-PodePlaceholder -Path $Verb

    # get endpoints from name
    $endpoints = Find-PodeEndpoint -EndpointName $EndpointName

    # ensure the verb doesn't already exist for each endpoint
    foreach ($_endpoint in $endpoints) {
        Test-PodeVerbAndError -Verb $Verb -Protocol $_endpoint.Protocol -Address $_endpoint.Address
    }

    # if scriptblock and file path are all null/empty, error
    if ((Test-PodeIsEmpty $ScriptBlock) -and (Test-PodeIsEmpty $FilePath) -and !$Close -and !$UpgradeToSsl) {
        # [Verb] Verb: No logic passed
        throw ($PodeLocale.verbNoLogicPassedExceptionMessage -f $Verb)
    }

    # if we have a file path supplied, load that path as a scriptblock
    if ($PSCmdlet.ParameterSetName -ieq 'file') {
        $ScriptBlock = Convert-PodeFileToScriptBlock -FilePath $FilePath
    }

    # check for scoped vars
    $ScriptBlock, $usingVars = Convert-PodeScopedVariables -ScriptBlock $ScriptBlock -PSSession $PSCmdlet.SessionState

    # add the verb(s)
    Write-Verbose "Adding Verb: $($Verb)"
    $PodeContext.Server.Verbs[$Verb] += @(foreach ($_endpoint in $endpoints) {
            @{
                Logic          = $ScriptBlock
                UsingVariables = $usingVars
                Endpoint       = @{
                    Protocol = $_endpoint.Protocol
                    Address  = $_endpoint.Address.Trim()
                    Name     = $_endpoint.Name
                }
                Arguments      = $ArgumentList
                Verb           = $Verb
                Connection     = @{
                    UpgradeToSsl = $UpgradeToSsl
                    Close        = $Close
                }
            }
        })
}

<#
.SYNOPSIS
Remove a specific Verb.

.DESCRIPTION
Remove a specific Verb.

.PARAMETER Verb
The Verb of the Verb to remove.

.PARAMETER EndpointName
The EndpointName of an Endpoint(s) bound to the Verb to be removed.

.EXAMPLE
Remove-PodeVerb -Verb 'Hello'

.EXAMPLE
Remove-PodeVerb -Verb 'Hello :username' -EndpointName User
#>
function Remove-PodeVerb {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $true)]
        [string]
        $Verb,

        [Parameter()]
        [string]
        $EndpointName
    )

    # ensure the verb placeholders are replaced
    $Verb = Resolve-PodePlaceholder -Path $Verb

    # ensure verb does exist
    if (!$PodeContext.Server.Verbs.Contains($Verb)) {
        return
    }

    # remove the verb's logic
    $PodeContext.Server.Verbs[$Verb] = @($PodeContext.Server.Verbs[$Verb] | Where-Object {
            $_.Endpoint.Name -ine $EndpointName
        })

    # if the verb has no more logic, just remove it
    if ((Get-PodeCount $PodeContext.Server.Verbs[$Verb]) -eq 0) {
        $null = $PodeContext.Server.Verbs.Remove($Verb)
    }
}

<#
.SYNOPSIS
Removes all added Verbs.

.DESCRIPTION
Removes all added Verbs.

.EXAMPLE
Clear-PodeVerbs
#>
function Clear-PodeVerbs {
    [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseSingularNouns', '')]
    [CmdletBinding()]
    param()

    $PodeContext.Server.Verbs.Clear()
}

<#
.SYNOPSIS
Get a Verb(s).

.DESCRIPTION
Get a Verb(s).

.PARAMETER Verb
A Verb to filter the verbs.

.PARAMETER EndpointName
The name of an endpoint to filter verbs.

.EXAMPLE
Get-PodeVerb -Verb 'Hello'

.EXAMPLE
Get-PodeVerb -Verb 'Hello :username' -EndpointName User
#>
function Get-PodeVerb {
    [CmdletBinding()]
    param(
        [Parameter()]
        [string]
        $Verb,

        [Parameter()]
        [string[]]
        $EndpointName
    )

    # start off with every verb
    $verbs = @()

    # if we have a verb, filter
    if (![string]::IsNullOrWhiteSpace($Verb)) {
        $Verb = Resolve-PodePlaceholder -Path $Verb
        $verbs = $PodeContext.Server.Verbs[$Verb]
    }
    else {
        foreach ($v in $PodeContext.Server.Verbs.Values) {
            $verbs += $v
        }
    }

    # further filter by endpoint names
    if (($null -ne $EndpointName) -and ($EndpointName.Length -gt 0)) {
        $verbs = @(foreach ($name in $EndpointName) {
                foreach ($v in $verbs) {
                    if ($v.Endpoint.Name -ine $name) {
                        continue
                    }

                    $v
                }
            })
    }

    # return
    return $verbs
}

<#
.SYNOPSIS
Automatically loads verb ps1 files

.DESCRIPTION
Automatically loads verb ps1 files from either a /verbs folder, or a custom folder. Saves space dot-sourcing them all one-by-one.

.PARAMETER Path
Optional Path to a folder containing ps1 files, can be relative or literal.

.EXAMPLE
Use-PodeVerbs

.EXAMPLE
Use-PodeVerbs -Path './my-verbs'
#>
function Use-PodeVerbs {
    [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseSingularNouns', '')]
    [CmdletBinding()]
    param(
        [Parameter()]
        [string]
        $Path
    )

    Use-PodeFolder -Path $Path -DefaultPath 'verbs'
}
src\Public\WebSockets.ps1
using namespace Pode

<#
.SYNOPSIS
Set the maximum number of concurrent WebSocket connection threads.

.DESCRIPTION
Set the maximum number of concurrent WebSocket connection threads.

.PARAMETER Maximum
The Maximum number of threads available to process WebSocket connection messages received.

.EXAMPLE
Set-PodeWebSocketConcurrency -Maximum 5
#>
function Set-PodeWebSocketConcurrency {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $true)]
        [int]
        $Maximum
    )

    # error if <=0
    if ($Maximum -le 0) {
        # Maximum concurrent WebSocket threads must be >=1 but got
        throw ($PodeLocale.maximumConcurrentWebSocketThreadsInvalidExceptionMessage -f $Maximum)

    }

    # add 1, for the waiting script
    $Maximum++

    # ensure max > min
    $_min = 1
    if ($null -ne $PodeContext.RunspacePools.WebSockets) {
        $_min = $PodeContext.RunspacePools.WebSockets.Pool.GetMinRunspaces()
    }

    if ($_min -gt $Maximum) {
        # Maximum concurrent WebSocket threads cannot be less than the minimum of $_min but got $Maximum
        throw ($PodeLocale.maximumConcurrentWebSocketThreadsLessThanMinimumExceptionMessage -f $_min, $Maximum)
    }

    # set the max tasks
    $PodeContext.Threads.WebSockets = $Maximum
    if ($null -ne $PodeContext.RunspacePools.WebSockets) {
        $PodeContext.RunspacePools.WebSockets.Pool.SetMaxRunspaces($Maximum)
    }
}

<#
.SYNOPSIS
Connect to an external WebSocket.

.DESCRIPTION
Connect to an external WebSocket.

.PARAMETER Name
The Name of the WebSocket connection.

.PARAMETER Url
The URL of the WebSocket. Should start with either ws:// or wss://.

.PARAMETER ScriptBlock
The ScriptBlock to invoke for processing received messages from the WebSocket. The ScriptBlock will have access to a $WsEvent variable with details of the received message.

.PARAMETER FilePath
A literal, or relative, path to a file containing a ScriptBlock for the WebSocket's logic.

.PARAMETER ContentType
An optional ContentType for parsing/converting received/sent messages. (default: application/json)

.PARAMETER ArgumentList
AN optional array of extra arguments, that will be passed to the ScriptBlock.

.EXAMPLE
Connect-PodeWebSocket -Name 'Example' -Url 'ws://example.com/some/socket' -ScriptBlock { ... }

.EXAMPLE
Connect-PodeWebSocket -Name 'Example' -Url 'ws://example.com/some/socket' -ScriptBlock { param($arg1, $arg2) ... } -ArgumentList 'arg1', 'arg2'

.EXAMPLE
Connect-PodeWebSocket -Name 'Example' -Url 'ws://example.com/some/socket' -FilePath './some/path/file.ps1'

.EXAMPLE
Connect-PodeWebSocket -Name 'Example' -Url 'ws://example.com/some/socket' -ScriptBlock { ... } -ContentType 'text/xml'
#>
function Connect-PodeWebSocket {
    [CmdletBinding(DefaultParameterSetName = 'Script')]
    param(
        [Parameter(Mandatory = $true)]
        [string]
        $Name,

        [Parameter(Mandatory = $true)]
        [string]
        $Url,

        [Parameter(ParameterSetName = 'Script')]
        [scriptblock]
        $ScriptBlock,

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

        [Parameter()]
        [string]
        $ContentType = 'application/json',

        [Parameter()]
        [object[]]
        $ArgumentList
    )

    # ensure we have a receiver
    New-PodeWebSocketReceiver

    # fail if already exists
    if (Test-PodeWebSocket -Name $Name) {
        # Already connected to websocket with name
        throw ($PodeLocale.alreadyConnectedToWebSocketExceptionMessage -f $Name)
    }

    # if we have a file path supplied, load that path as a scriptblock
    if ($PSCmdlet.ParameterSetName -ieq 'file') {
        $ScriptBlock = Convert-PodeFileToScriptBlock -FilePath $FilePath
    }

    # check for scoped vars
    $ScriptBlock, $usingVars = Convert-PodeScopedVariables -ScriptBlock $ScriptBlock -PSSession $PSCmdlet.SessionState

    # connect
    try {
        $null = Wait-PodeTask -Task $PodeContext.Server.WebSockets.Receiver.ConnectWebSocket($Name, $Url, $ContentType)
    }
    catch {
        # Failed to connect to websocket
        throw ($PodeLocale.failedToConnectToWebSocketExceptionMessage -f $ErrorMessage)
    }

    $PodeContext.Server.WebSockets.Connections[$Name] = @{
        Name           = $Name
        Url            = $Url
        Logic          = $ScriptBlock
        UsingVariables = $usingVars
        Arguments      = $ArgumentList
    }
}

<#
.SYNOPSIS
Disconnect from a WebSocket connection.

.DESCRIPTION
Disconnect from a WebSocket connection. These connections can be reconnected later using Reset-PodeWebSocket

.PARAMETER Name
The Name of the WebSocket connection (optional if in the scope where $WsEvent is available).

.EXAMPLE
Disconnect-PodeWebSocket -Name 'Example'
#>
function Disconnect-PodeWebSocket {
    [CmdletBinding()]
    param(
        [Parameter()]
        [string]
        $Name
    )

    if ([string]::IsNullOrWhiteSpace($Name) -and ($null -ne $WsEvent)) {
        $Name = $WsEvent.Request.WebSocket.Name
    }

    if ([string]::IsNullOrWhiteSpace($Name)) {
        # No Name for a WebSocket to disconnect from supplied
        throw ($PodeLocale.noNameForWebSocketDisconnectExceptionMessage)
    }

    if (Test-PodeWebSocket -Name $Name) {
        $PodeContext.Server.WebSockets.Receiver.DisconnectWebSocket($Name)
    }
}

<#
.SYNOPSIS
Remove a WebSocket connection.

.DESCRIPTION
Disconnects and then removes a WebSocket connection.

.PARAMETER Name
The Name of the WebSocket connection (optional if in the scope where $WsEvent is available).

.EXAMPLE
Remove-PodeWebSocket -Name 'Example'
#>
function Remove-PodeWebSocket {
    [CmdletBinding()]
    param(
        [Parameter()]
        [string]
        $Name
    )

    if ([string]::IsNullOrWhiteSpace($Name) -and ($null -ne $WsEvent)) {
        $Name = $WsEvent.Request.WebSocket.Name
    }

    if ([string]::IsNullOrWhiteSpace($Name)) {
        # No Name for a WebSocket to remove supplied
        throw ($PodeLocale.noNameForWebSocketRemoveExceptionMessage)
    }

    $PodeContext.Server.WebSockets.Receiver.RemoveWebSocket($Name)
    $PodeContext.Server.WebSockets.Connections.Remove($Name)
}

<#
.SYNOPSIS
Send a message back to a WebSocket connection.

.DESCRIPTION
Send a message back to a WebSocket connection.

.PARAMETER Name
The Name of the WebSocket connection (optional if in the scope where $WsEvent is available).

.PARAMETER Message
The Message to send. Can either be a raw string, hashtable, or psobject. Non-strings will be parsed to JSON, or the WebSocket's ContentType.

.PARAMETER Depth
An optional Depth to parse any JSON or XML messages. (default: 10)

.PARAMETER Type
An optional message Type. (default: Text)

.EXAMPLE
Send-PodeWebSocket -Name 'Example' -Message @{ message = 'Hello, there' }
#>
function Send-PodeWebSocket {
    [CmdletBinding()]
    param(
        [Parameter()]
        [string]
        $Name,

        [Parameter()]
        $Message,

        [Parameter()]
        [int]
        $Depth = 10,

        [Parameter()]
        [ValidateSet('Text', 'Binary')]
        [string]
        $Type = 'Text'
    )

    # get ws name
    if ([string]::IsNullOrWhiteSpace($Name) -and ($null -ne $WsEvent)) {
        $Name = $WsEvent.Request.WebSocket.Name
    }

    # do we have a name?
    if ([string]::IsNullOrWhiteSpace($Name)) {
        # No Name for a WebSocket to send message to supplied
        throw ($PodeLocale.noNameForWebSocketSendMessageExceptionMessage)
    }

    # do the socket exist?
    if (!(Test-PodeWebSocket -Name $Name)) {
        return
    }

    # get the websocket
    $ws = $PodeContext.Server.WebSockets.Receiver.GetWebSocket($Name)

    # parse message
    $Message = ConvertTo-PodeResponseContent -InputObject $Message -ContentType $ws.ContentType -Depth $Depth

    # send message
    $null = Wait-PodeTask -Task $ws.Send($Message, $Type)
}

<#
.SYNOPSIS
Reset an existing WebSocket connection.

.DESCRIPTION
Reset an existing WebSocket connection, either using it's current URL or a new one.

.PARAMETER Name
The Name of the WebSocket connection (optional if in the scope where $WsEvent is available).

.PARAMETER Url
An optional new URL to reset the connection to. If not supplied, the connection's original URL will be used.

.EXAMPLE
Reset-PodeWebSocket -Name 'Example'

.EXAMPLE
Reset-PodeWebSocket -Name 'Example' -Url 'ws://example.com/some/socket'
#>
function Reset-PodeWebSocket {
    [CmdletBinding()]
    param(
        [Parameter()]
        [string]
        $Name,

        [Parameter()]
        [string]
        $Url
    )

    if ([string]::IsNullOrWhiteSpace($Name) -and ($null -ne $WsEvent)) {
        $null = Wait-PodeTask -Task $WsEvent.Request.WebSocket.Reconnect($Url)
        return
    }

    if ([string]::IsNullOrWhiteSpace($Name)) {
        # No Name for a WebSocket to reset supplied
        throw ($PodeLocale.noNameForWebSocketResetExceptionMessage)
    }

    if (Test-PodeWebSocket -Name $Name) {
        $null = Wait-PodeTask -Task $PodeContext.Server.WebSockets.Receiver.GetWebSocket($Name).Reconnect($Url)
    }
}

<#
.SYNOPSIS
Test whether an WebSocket connection exists.

.DESCRIPTION
Test whether an WebSocket connection exists for the given Name.

.PARAMETER Name
The Name of the WebSocket connection.

.EXAMPLE
Test-PodeWebSocket -Name 'Example'
#>
function Test-PodeWebSocket {
    [CmdletBinding()]
    [OutputType([bool])]
    param(
        [Parameter(Mandatory = $true)]
        [string]
        $Name
    )

    $found = ($null -ne $PodeContext.Server.WebSockets.Receiver.GetWebSocket($Name))
    if ($found) {
        return $true
    }

    if ($PodeContext.Server.WebSockets.Connections.ContainsKey($Name)) {
        Remove-PodeWebSocket -Name $Name
    }

    return $false
}
src\VERIFICATION.txt
VERIFICATION
Verification is intended to assist the Chocolatey moderators and community
in verifying that this package's contents are trustworthy.

This embedded PowerShell module is packaged and distributed by the author.

The contents of which can be found on the releases pages at <https://github.com/Badgerati/Pode/releases>.

To verify contents, either:
1. Compare the Checksum on the release notes against the Module's source.
2. Download the zip from the release, run 'checksum -t sha256' on it, and compare.
tools\ChocolateyInstall.ps1
$ErrorActionPreference = 'Stop'


# create the module directory, and copy files over
function Install-PodeModule($path, $version) {
    # Create module
    $path = Join-Path $path 'Pode'
    if (![string]::IsNullOrWhiteSpace($version)) {
        $path = Join-Path $path $version
    }

    if (!(Test-Path $path)) {
        Write-Host "Creating module directory: $($path)"
        New-Item -ItemType Directory -Path $path -Force | Out-Null
        if (!$?) {
            throw "Failed to create: $path"
        }
    }

    # Copy contents to module
    Write-Host 'Copying scripts to module path'

    try {
        Push-Location (Join-Path $toolsDir 'src')

        # which folders do we need?
        $folders = @('Private', 'Public', 'Misc', 'Libs', 'licenses','Locales')
        $folders | ForEach-Object {
            New-Item -ItemType Directory -Path (Join-Path $path $_) -Force | Out-Null
            Copy-Item -Path "./$($_)/*" -Destination (Join-Path $path $_) -Force -Recurse | Out-Null
        }

        # copy general files
        $files = @('Pode.psm1', 'Pode.psd1', 'Pode.Internal.psm1', 'Pode.Internal.psd1', 'LICENSE.txt')
        $files | ForEach-Object {
            Copy-Item -Path "./$($_)" -Destination $path -Force | Out-Null
        }
    }
    finally {
        Pop-Location
    }
}



# Determine which Program Files path to use
$progFiles = [string]$env:ProgramFiles

# determine the path to choco tools
$toolsDir = Split-Path -Path (Split-Path -Parent $MyInvocation.MyCommand.Definition)

# Install PS Module
# Set the module path
$modulePath = Join-Path $progFiles (Join-Path 'WindowsPowerShell' 'Modules')

# Check to see if Modules path is in PSModulePaths
$psModules = $env:PSModulePath
if (!$psModules.Contains($modulePath)) {
    Write-Host 'Adding module path to PSModulePaths'
    $psModules += ";$modulePath"
    Install-ChocolateyEnvironmentVariable -VariableName 'PSModulePath' -VariableValue $psModules -VariableType Machine
    $env:PSModulePath = $psModules
}

# create the module
if ($PSVersionTable.PSVersion.Major -ge 5) {
    Install-PodeModule $modulePath '2.11.0'
}
else {
    Install-PodeModule $modulePath
}


# Install PS-Core Module
$def = (Get-Command pwsh -ErrorAction SilentlyContinue).Definition

if (![string]::IsNullOrWhiteSpace($def)) {
    # Set the module path
    $modulePath = Join-Path $progFiles (Join-Path 'PowerShell' 'Modules')

    # create the module
    Install-PodeModule $modulePath '2.11.0'
}
tools\ChocolateyUninstall.ps1
function Remove-PodeModule($path)
{
    $path = Join-Path $path 'Pode'
    if (Test-Path $path)
    {
        Write-Host "Deleting module directory: $($path)"
        Remove-Item -Path $path -Recurse -Force | Out-Null
        if (!$?) {
            throw "Failed to delete: $path"
        }
    }
}



# Determine which Program Files path to use
$progFiles = [string]$env:ProgramFiles

# Remove PS Module
# Set the module path
$modulePath = Join-Path $progFiles (Join-Path 'WindowsPowerShell' 'Modules')

# Delete module
Remove-PodeModule $modulePath


# Remove PS-Core Module
$def = (Get-Command pwsh -ErrorAction SilentlyContinue).Definition

if (![string]::IsNullOrWhiteSpace($def))
{
    # Set the module path
    $modulePath = Join-Path $progFiles (Join-Path 'PowerShell' 'Modules')

    # Delete module
    Remove-PodeModule $modulePath
}

Log in or click on link to see number of positives.

In cases where actual malware is found, the packages are subject to removal. Software sometimes has false positives. Moderators do not necessarily validate the safety of the underlying software, only that a package retrieves software from the official distribution point and/or validate embedded software against official distribution point (where distribution rights allow redistribution).

Chocolatey Pro provides runtime protection from possible malware.

Add to Builder Version Downloads Last Updated Status
Pode 2.11.1 13 Sunday, November 3, 2024
Responded
Pode 2.10.1 170 Monday, May 27, 2024 Approved
Pode 2.10.0 77 Monday, April 15, 2024 Approved
Pode 2.9.0 210 Monday, October 30, 2023 Approved
Pode 2.8.0 256 Friday, February 3, 2023 Approved
Pode 2.7.2 152 Tuesday, October 25, 2022 Approved
Pode 2.7.1 135 Thursday, July 21, 2022 Approved
Pode 2.7.0 142 Wednesday, June 22, 2022 Approved
Pode 2.6.2 184 Wednesday, March 2, 2022 Approved
Pode 2.6.1 106 Monday, February 21, 2022 Approved
Pode 2.6.0 109 Thursday, February 10, 2022 Approved
Pode 2.5.2 124 Tuesday, January 4, 2022 Approved
Pode 2.5.1 117 Tuesday, December 21, 2021 Approved
Pode 2.5.0 126 Saturday, November 13, 2021 Approved
Pode 2.4.2 152 Monday, September 13, 2021 Approved
Pode 2.4.1 138 Monday, August 9, 2021 Approved
Pode 2.4.0 115 Wednesday, July 21, 2021 Approved
Pode 2.3.0 142 Tuesday, June 1, 2021 Approved
Pode 2.2.3 159 Saturday, April 10, 2021 Approved
Pode 2.2.2 116 Friday, April 9, 2021 Approved
Pode 2.2.1 112 Saturday, March 27, 2021 Approved
Pode 2.2.0 136 Sunday, March 21, 2021 Approved
Pode 2.1.1 136 Friday, February 19, 2021 Approved
Pode 2.1.0 1181 Wednesday, February 3, 2021 Approved
Pode 2.0.3 173 Monday, December 21, 2020 Approved
Pode 2.0.2 137 Saturday, December 5, 2020 Approved
Pode 2.0.1 129 Sunday, November 29, 2020 Approved
Pode 2.0.0 199 Saturday, November 14, 2020 Approved
Pode 1.8.4 187 Friday, October 16, 2020 Approved
Pode 1.8.3 173 Sunday, September 20, 2020 Approved
Pode 1.8.2 211 Friday, July 31, 2020 Approved
Pode 1.8.1 191 Friday, June 26, 2020 Approved
Pode 1.8.0 205 Sunday, May 24, 2020 Approved
Pode 1.7.3 202 Sunday, May 10, 2020 Approved
Pode 1.7.2 188 Monday, April 27, 2020 Approved
Pode 1.7.1 182 Friday, April 17, 2020 Approved
Pode 1.7.0 193 Friday, April 10, 2020 Approved
Pode 1.6.1 242 Saturday, March 7, 2020 Approved
Pode 1.6.0 204 Tuesday, March 3, 2020 Approved
Pode 1.5.0 241 Sunday, February 2, 2020 Approved
Pode 1.4.0 219 Friday, January 10, 2020 Approved
Pode 1.3.0 204 Friday, December 27, 2019 Approved
Pode 1.2.1 221 Monday, December 2, 2019 Approved
Pode 1.2.0 205 Wednesday, November 13, 2019 Approved
Pode 1.1.0 222 Saturday, September 28, 2019 Approved
Pode 1.0.1 228 Wednesday, September 4, 2019 Approved
Pode 1.0.0 214 Monday, September 2, 2019 Approved
Pode 0.32.0 253 Friday, June 28, 2019 Approved
Pode 0.31.0 225 Tuesday, June 11, 2019 Approved
Pode 0.30.0 213 Sunday, May 26, 2019 Approved
Pode 0.29.0 211 Friday, May 10, 2019 Approved
Pode 0.28.1 253 Tuesday, April 16, 2019 Approved
Pode 0.28.0 198 Saturday, April 13, 2019 Approved
Pode 0.27.3 220 Thursday, April 4, 2019 Approved
Pode 0.27.2 239 Wednesday, March 27, 2019 Approved
Pode 0.27.1 230 Saturday, March 16, 2019 Approved
Pode 0.27.0 225 Thursday, March 14, 2019 Approved
Pode 0.26.0 253 Sunday, February 17, 2019 Approved
Pode 0.25.0 248 Tuesday, February 5, 2019 Approved
Pode 0.24.0 277 Friday, January 18, 2019 Approved
Pode 0.23.0 268 Monday, December 24, 2018 Approved
Pode 0.22.0 262 Friday, December 7, 2018 Approved
Pode 0.21.0 280 Friday, November 2, 2018 Approved
Pode 0.20.0 295 Saturday, October 20, 2018 Approved
Pode 0.19.1 242 Tuesday, October 9, 2018 Approved
Pode 0.19.0 269 Friday, September 14, 2018 Approved
Pode 0.18.0 252 Saturday, August 25, 2018 Approved
Pode 0.17.0 222 Sunday, August 19, 2018 Approved
Pode 0.16.0 275 Wednesday, August 8, 2018 Approved
Pode 0.15.0 290 Friday, July 13, 2018 Approved
Pode 0.14.0 269 Friday, July 6, 2018 Approved
Pode 0.13.0 270 Saturday, June 23, 2018 Approved
Pode 0.12.0 259 Friday, June 15, 2018 Approved
Pode 0.11.3 297 Sunday, June 10, 2018 Approved
Pode 0.11.2 291 Friday, June 8, 2018 Approved
Pode 0.11.1 315 Friday, June 1, 2018 Approved
Pode 0.11.0 276 Wednesday, May 30, 2018 Approved
Pode 0.10.1 343 Wednesday, May 16, 2018 Approved
Pode 0.9.0 357 Thursday, January 11, 2018 Approved

This package has no dependencies.

Discussion for the Pode Package

Ground Rules:

  • This discussion is only about Pode and the Pode package. If you have feedback for Chocolatey, please contact the Google Group.
  • This discussion will carry over multiple versions. If you have a comment about a particular version, please note that in your comments.
  • The maintainers of this Chocolatey Package will be notified about new comments that are posted to this Disqus thread, however, it is NOT a guarantee that you will get a response. If you do not hear back from the maintainers after posting a message below, please follow up by using the link on the left side of this page or follow this link to contact maintainers. If you still hear nothing back, please follow the package triage process.
  • Tell us what you love about the package or Pode, or tell us what needs improvement.
  • Share your experiences with the package, or extra configuration or gotchas that you've found.
  • If you use a url, the comment will be flagged for moderation until you've been whitelisted. Disqus moderated comments are approved on a weekly schedule if not sooner. It could take between 1-5 days for your comment to show up.
comments powered by Disqus