diff --git a/.gitattributes b/.gitattributes
new file mode 100644
index 0000000..96c2e0d
--- /dev/null
+++ b/.gitattributes
@@ -0,0 +1,15 @@
+# Needed for publishing of examples, build worker defaults to core.autocrlf=input.
+* text eol=autocrlf
+
+*.mof text eol=crlf
+*.sh text eol=lf
+*.svg eol=lf
+
+# Ensure any exe files are treated as binary
+*.exe binary
+*.jpg binary
+*.xl* binary
+*.pfx binary
+*.png binary
+*.dll binary
+*.so binary
diff --git a/.github/workflows/Action-Test.yml b/.github/workflows/Action-Test.yml
index 6ff2f13..ae765ba 100644
--- a/.github/workflows/Action-Test.yml
+++ b/.github/workflows/Action-Test.yml
@@ -1,32 +1,45 @@
-name: Action-Test
-
-run-name: "Action-Test - [${{ github.event.pull_request.title }} #${{ github.event.pull_request.number }}] by @${{ github.actor }}"
-
-on:
- workflow_dispatch:
- pull_request:
- schedule:
- - cron: '0 0 * * *'
-
-concurrency:
- group: ${{ github.workflow }}-${{ github.ref }}
- cancel-in-progress: true
-
-permissions:
- contents: read
- pull-requests: read
-
-jobs:
- ActionTestBasic:
- name: Action-Test - [Basic]
- runs-on: ubuntu-latest
- steps:
- # Need to check out as part of the test, as its a local action
- - name: Checkout repo
- uses: actions/checkout@v4
-
- - name: Action-Test
- uses: ./
- with:
- working-directory: ./tests
- subject: PSModule
+name: Action-Test
+
+run-name: "Action-Test - [${{ github.event.pull_request.title }} #${{ github.event.pull_request.number }}] by @${{ github.actor }}"
+
+on:
+ workflow_dispatch:
+ pull_request:
+ schedule:
+ - cron: '0 0 * * *'
+
+env:
+ GH_TOKEN: ${{ github.token }}
+
+concurrency:
+ group: ${{ github.workflow }}-${{ github.ref }}
+ cancel-in-progress: true
+
+permissions: {}
+
+jobs:
+ ActionTestDefault:
+ name: Action-Test - [Default]
+ runs-on: ubuntu-latest
+ steps:
+ - name: Checkout repo
+ uses: actions/checkout@v4
+
+ - name: Initialize environment
+ uses: PSModule/Initialize-PSModule@main
+
+ - name: Upload module artifact
+ uses: actions/upload-artifact@v4
+ with:
+ name: module
+ path: tests/outputs/modules
+ if-no-files-found: error
+ retention-days: 1
+
+ - name: Action-Test
+ uses: ./
+ with:
+ Name: PSModuleTest
+ Path: tests/src
+ ModulesOutputPath: tests/outputs/modules
+ DocsOutputPath: tests/outputs/docs
diff --git a/.github/workflows/Auto-Configure.yml b/.github/workflows/Auto-Configure.yml
deleted file mode 100644
index e2321e4..0000000
--- a/.github/workflows/Auto-Configure.yml
+++ /dev/null
@@ -1,34 +0,0 @@
-name: Auto-Configure
-
-run-name: "Auto-Configure - [${{ github.event.pull_request.title }} #${{ github.event.pull_request.number }}] by @${{ github.actor }}"
-
-on:
- pull_request_target:
- branches:
- - main
- types:
- - closed
- - opened
- - reopened
- - synchronize
- - labeled
-
-concurrency:
- group: ${{ github.workflow }}-${{ github.ref }}
- cancel-in-progress: true
-
-permissions:
- contents: write # Required to create releases
- pull-requests: write # Required to create comments on the PRs
-
-jobs:
- Auto-Configure:
- runs-on: ubuntu-latest
- steps:
- - name: Checkout Code
- uses: actions/checkout@v4
-
- - name: Auto-Configure
- uses: PSModule/Auto-Configure@v1
- env:
- GITHUB_TOKEN: ${{ github.token }}
diff --git a/.github/workflows/Auto-Document.yml b/.github/workflows/Auto-Document.yml
deleted file mode 100644
index 6a62053..0000000
--- a/.github/workflows/Auto-Document.yml
+++ /dev/null
@@ -1,31 +0,0 @@
-name: Auto-Document
-
-run-name: "Auto-Document - [${{ github.event.pull_request.title }} #${{ github.event.pull_request.number }}] by @${{ github.actor }}"
-
-on:
- pull_request_target:
- branches:
- - main
- types:
- - opened
- - reopened
- - synchronize
-
-concurrency:
- group: ${{ github.workflow }}-${{ github.ref }}
- cancel-in-progress: true
-
-permissions:
- contents: write # Required to push to the branch
-
-jobs:
- Auto-Document:
- runs-on: ubuntu-latest
- steps:
- - name: Checkout Code
- uses: actions/checkout@v4
-
- - name: Auto-Document
- uses: PSModule/Auto-Document@v1
- env:
- GITHUB_TOKEN: ${{ github.token }}
diff --git a/.github/workflows/Auto-Release.yml b/.github/workflows/Auto-Release.yml
index d6c477b..1a580b8 100644
--- a/.github/workflows/Auto-Release.yml
+++ b/.github/workflows/Auto-Release.yml
@@ -1,34 +1,34 @@
-name: Auto-Release
-
-run-name: "Auto-Release - [${{ github.event.pull_request.title }} #${{ github.event.pull_request.number }}] by @${{ github.actor }}"
-
-on:
- pull_request_target:
- branches:
- - main
- types:
- - closed
- - opened
- - reopened
- - synchronize
- - labeled
-
-concurrency:
- group: ${{ github.workflow }}-${{ github.ref }}
- cancel-in-progress: true
-
-permissions:
- contents: write # Required to create releases
- pull-requests: write # Required to create comments on the PRs
-
-jobs:
- Auto-Release:
- runs-on: ubuntu-latest
- steps:
- - name: Checkout Code
- uses: actions/checkout@v4
-
- - name: Auto-Release
- uses: PSModule/Auto-Release@v1
- env:
- GITHUB_TOKEN: ${{ github.token }}
+name: Auto-Release
+
+run-name: "Auto-Release - [${{ github.event.pull_request.title }} #${{ github.event.pull_request.number }}] by @${{ github.actor }}"
+
+on:
+ pull_request_target:
+ branches:
+ - main
+ types:
+ - closed
+ - opened
+ - reopened
+ - synchronize
+ - labeled
+
+concurrency:
+ group: ${{ github.workflow }}-${{ github.ref }}
+ cancel-in-progress: true
+
+permissions:
+ contents: write # Required to create releases
+ pull-requests: write # Required to create comments on the PRs
+
+jobs:
+ Auto-Release:
+ runs-on: ubuntu-latest
+ steps:
+ - name: Checkout Code
+ uses: actions/checkout@v4
+
+ - name: Auto-Release
+ uses: PSModule/Auto-Release@v1
+ env:
+ GITHUB_TOKEN: ${{ github.token }}
diff --git a/.github/workflows/Linter.yml b/.github/workflows/Linter.yml
index 187c17e..bb47b67 100644
--- a/.github/workflows/Linter.yml
+++ b/.github/workflows/Linter.yml
@@ -1,29 +1,33 @@
-name: Linter
-
-run-name: "Linter - [${{ github.event.pull_request.title }} #${{ github.event.pull_request.number }}] by @${{ github.actor }}"
-
-on: [pull_request]
-
-concurrency:
- group: ${{ github.workflow }}-${{ github.ref }}
- cancel-in-progress: true
-
-permissions:
- contents: read
- packages: read
- statuses: write
-
-jobs:
- Lint:
- name: Lint code base
- runs-on: ubuntu-latest
- steps:
- - name: Checkout repo
- uses: actions/checkout@v4
- with:
- fetch-depth: 0
-
- - name: Lint code base
- uses: super-linter/super-linter/slim@latest
- env:
- GITHUB_TOKEN: ${{ github.token }}
+name: Linter
+
+run-name: "Linter - [${{ github.event.pull_request.title }} #${{ github.event.pull_request.number }}] by @${{ github.actor }}"
+
+on: [pull_request]
+
+concurrency:
+ group: ${{ github.workflow }}-${{ github.ref }}
+ cancel-in-progress: true
+
+permissions:
+ contents: read
+ packages: read
+ statuses: write
+
+jobs:
+ Lint:
+ name: Lint code base
+ runs-on: ubuntu-latest
+ steps:
+ - name: Checkout repo
+ uses: actions/checkout@v4
+ with:
+ fetch-depth: 0
+
+ - name: Lint code base
+ uses: super-linter/super-linter/slim@latest
+ env:
+ GITHUB_TOKEN: ${{ github.token }}
+ VALIDATE_MARKDOWN_PRETTIER: false
+ VALIDATE_JSON_PRETTIER: false
+ VALIDATE_YAML_PRETTIER: false
+ VALIDATE_JSCPD: false
diff --git a/.gitignore b/.gitignore
index 93352b0..af4061f 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,11 +1,11 @@
-## Ignore Visual Studio Code temporary files, build results, and
-## files generated by popular Visual Studio Code add-ons.
-##
-## Get latest from https://github.com/github/gitignore/blob/master/Global/VisualStudioCode.gitignore
-.vscode/*
-!.vscode/settings.json
-!.vscode/extensions.json
-*.code-workspace
-
-# Local History for Visual Studio Code
-.history/
+## Ignore Visual Studio Code temporary files, build results, and
+## files generated by popular Visual Studio Code add-ons.
+##
+## Get latest from https://github.com/github/gitignore/blob/master/Global/VisualStudioCode.gitignore
+.vscode/*
+!.vscode/settings.json
+!.vscode/extensions.json
+*.code-workspace
+
+# Local History for Visual Studio Code
+.history/
diff --git a/LICENSE b/LICENSE
index 9a9cbf4..75789b6 100644
--- a/LICENSE
+++ b/LICENSE
@@ -1,21 +1,21 @@
-MIT License
-
-Copyright (c) 2025 PSModule
-
-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.
+MIT License
+
+Copyright (c) 2025 PSModule
+
+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.
diff --git a/action.yml b/action.yml
index 0aea80e..a890336 100644
--- a/action.yml
+++ b/action.yml
@@ -1,27 +1,79 @@
-name: '{{ NAME }}'
-description: '{{ DESCRIPTION }}'
-author: PSModule
-branding:
- icon: upload-cloud
- color: white
-
-inputs:
- working-directory:
- description: The working directory where Terraform will be executed
- required: false
- subject:
- description: The subject to greet
- required: false
- default: World
-
-runs:
- using: composite
- steps:
- - name: '{{ NAME }}'
- uses: PSModule/GitHub-Script@v1
- env:
- GITHUB_ACTION_INPUT_subject: ${{ inputs.subject }}
- with:
- Script: |
- # '{{ NAME }}'
- ${{ github.action_path }}\scripts\main.ps1
+name: Build-PSModuleDocumentation (by PSModule)
+description: Build documentation for a PowerShell module.
+author: PSModule
+branding:
+ icon: package
+ color: gray-dark
+
+inputs:
+ Name:
+ description: Name of the module to process.
+ required: false
+ Path:
+ description: Path to the folder where the modules are located.
+ required: false
+ default: src
+ ModulesOutputPath:
+ description: Path to the folder where the built modules are outputted.
+ required: false
+ default: outputs/modules
+ DocsOutputPath:
+ description: Path to the folder where the built docs are outputted.
+ required: false
+ default: outputs/docs
+ ModuleArtifactName:
+ description: Name of the module artifact to upload.
+ required: false
+ default: module
+ DocsArtifactName:
+ description: Name of the docs artifact to upload.
+ required: false
+ default: docs
+ Debug:
+ description: Enable debug output.
+ required: false
+ default: 'false'
+ Verbose:
+ description: Enable verbose output.
+ required: false
+ default: 'false'
+ Version:
+ description: Specifies the version of the GitHub module to be installed. The value must be an exact version.
+ required: false
+ Prerelease:
+ description: Allow prerelease versions if available.
+ required: false
+ default: 'false'
+
+runs:
+ using: composite
+ steps:
+ - name: Download module artifact
+ uses: actions/download-artifact@v4
+ with:
+ name: ${{ inputs.ModuleArtifactName }}
+ path: ${{ inputs.ModulesOutputPath }}
+
+ - name: Run Build-PSModuleDocumentation
+ uses: PSModule/GitHub-Script@v1
+ env:
+ GITHUB_ACTION_INPUT_Name: ${{ inputs.Name }}
+ GITHUB_ACTION_INPUT_Path: ${{ inputs.Path }}
+ GITHUB_ACTION_INPUT_ModulesOutputPath: ${{ inputs.ModulesOutputPath }}
+ GITHUB_ACTION_INPUT_DocsOutputPath: ${{ inputs.DocsOutputPath }}
+ with:
+ Debug: ${{ inputs.Debug }}
+ Prerelease: ${{ inputs.Prerelease }}
+ Verbose: ${{ inputs.Verbose }}
+ Version: ${{ inputs.Version }}
+ Script: |
+ # Build-PSModuleDocumentation
+ ${{ github.action_path }}\scripts\main.ps1
+
+ - name: Upload docs artifact
+ uses: actions/upload-artifact@v4
+ with:
+ name: ${{ inputs.DocsArtifactName }}
+ path: ${{ inputs.DocsOutputPath }}
+ if-no-files-found: error
+ retention-days: 1
diff --git a/scripts/helpers/Build-PSModuleDocumentation.ps1 b/scripts/helpers/Build-PSModuleDocumentation.ps1
new file mode 100644
index 0000000..09ef6cd
--- /dev/null
+++ b/scripts/helpers/Build-PSModuleDocumentation.ps1
@@ -0,0 +1,126 @@
+#Requires -Modules @{ ModuleName = 'GitHub'; ModuleVersion = '0.13.2' }
+#Requires -Modules @{ ModuleName = 'Utilities'; ModuleVersion = '0.3.0' }
+
+function Build-PSModuleDocumentation {
+ <#
+ .SYNOPSIS
+ Builds a module.
+
+ .DESCRIPTION
+ Builds a module.
+ #>
+ [CmdletBinding()]
+ [Diagnostics.CodeAnalysis.SuppressMessageAttribute(
+ 'PSReviewUnusedParameter', '', Scope = 'Function',
+ Justification = 'LogGroup - Scoping affects the variables line of sight.'
+ )]
+ param(
+ # Name of the module.
+ [Parameter(Mandatory)]
+ [string] $ModuleName,
+
+ # Path to the folder where the modules are located.
+ [Parameter(Mandatory)]
+ [string] $ModuleSourceFolderPath,
+
+ # Path to the folder where the built modules are outputted.
+ [Parameter(Mandatory)]
+ [string] $ModulesOutputFolderPath,
+
+ # Path to the folder where the documentation is outputted.
+ [Parameter(Mandatory)]
+ [string] $DocsOutputFolderPath
+ )
+
+ LogGroup "Documenting module [$ModuleName]" {
+ Write-Host "Source path: [$ModuleSourceFolderPath]"
+ if (-not (Test-Path -Path $ModuleSourceFolderPath)) {
+ Write-Error "Source folder not found at [$ModuleSourceFolderPath]"
+ exit 1
+ }
+ $moduleSourceFolder = Get-Item -Path $ModuleSourceFolderPath
+ Write-Host "Module source folder: [$moduleSourceFolder]"
+
+ $moduleOutputFolder = New-Item -Path $ModulesOutputFolderPath -Name $ModuleName -ItemType Directory -Force
+ Write-Host "Module output folder: [$moduleOutputFolder]"
+
+ $docsOutputFolder = New-Item -Path $DocsOutputFolderPath -ItemType Directory -Force
+ Write-Host "Docs output folder: [$docsOutputFolder]"
+ }
+
+ LogGroup 'Build docs - Generate markdown help' {
+ Add-PSModulePath -Path (Split-Path -Path $ModuleOutputFolder -Parent)
+ $ModuleName | Remove-Module -Force -ErrorAction SilentlyContinue
+ Import-PSModule -Path $ModuleOutputFolder -ModuleName $ModuleName
+ Write-Host ($ModuleName | Get-Module)
+ $null = New-MarkdownHelp -Module $ModuleName -OutputFolder $DocsOutputFolder -Force -Verbose
+ }
+
+ LogGroup 'Build docs - Fix markdown code blocks' {
+ Get-ChildItem -Path $DocsOutputFolder -Recurse -Force -Include '*.md' | ForEach-Object {
+ $content = Get-Content -Path $_.FullName
+ $fixedOpening = $false
+ $newContent = @()
+ foreach ($line in $content) {
+ if ($line -match '^```$' -and -not $fixedOpening) {
+ $line = $line -replace '^```$', '```powershell'
+ $fixedOpening = $true
+ } elseif ($line -match '^```.+$') {
+ $fixedOpening = $true
+ } elseif ($line -match '^```$') {
+ $fixedOpening = $false
+ }
+ $newContent += $line
+ }
+ $newContent | Set-Content -Path $_.FullName
+ }
+ }
+
+ LogGroup 'Build docs - Fix markdown escape characters' {
+ Get-ChildItem -Path $DocsOutputFolder -Recurse -Force -Include '*.md' | ForEach-Object {
+ $content = Get-Content -Path $_.FullName -Raw
+ $content = $content -replace '\\`', '`'
+ $content = $content -replace '\\\[', '['
+ $content = $content -replace '\\\]', ']'
+ $content = $content -replace '\\\<', '<'
+ $content = $content -replace '\\\>', '>'
+ $content = $content -replace '\\\\', '\'
+ $content | Set-Content -Path $_.FullName
+ }
+ }
+
+ LogGroup 'Build docs - Structure markdown files to match source files' {
+ $PublicFunctionsFolder = Join-Path $ModuleSourceFolder.FullName 'functions\public' | Get-Item
+ Get-ChildItem -Path $DocsOutputFolder -Recurse -Force -Include '*.md' | ForEach-Object {
+ $file = $_
+ Write-Host "Processing: $file"
+
+ # find the source code file that matches the markdown file
+ $scriptPath = Get-ChildItem -Path $PublicFunctionsFolder -Recurse -Force | Where-Object { $_.Name -eq ($file.BaseName + '.ps1') }
+ Write-Host "Found script path: $scriptPath"
+ $docsFilePath = ($scriptPath.FullName).Replace($PublicFunctionsFolder.FullName, $DocsOutputFolder.FullName).Replace('.ps1', '.md')
+ Write-Host "Doc file path: $docsFilePath"
+ $docsFolderPath = Split-Path -Path $docsFilePath -Parent
+ New-Item -Path $docsFolderPath -ItemType Directory -Force
+ Move-Item -Path $file.FullName -Destination $docsFilePath -Force
+ }
+ # Get the MD files that are in the public functions folder and move them to the same place in the docs folder
+ Get-ChildItem -Path $PublicFunctionsFolder -Recurse -Force -Include '*.md' | ForEach-Object {
+ $file = $_
+ Write-Host "Processing: $file"
+ $docsFilePath = ($file.FullName).Replace($PublicFunctionsFolder.FullName, $DocsOutputFolder.FullName)
+ Write-Host "Doc file path: $docsFilePath"
+ $docsFolderPath = Split-Path -Path $docsFilePath -Parent
+ New-Item -Path $docsFolderPath -ItemType Directory -Force
+ Move-Item -Path $file.FullName -Destination $docsFilePath -Force
+ }
+ }
+
+ Get-ChildItem -Path $DocsOutputFolder -Recurse -Force -Include '*.md' | ForEach-Object {
+ $fileName = $_.Name
+ $hash = (Get-FileHash -Path $_.FullName -Algorithm SHA256).Hash
+ LogGroup " - [$fileName] - [$hash]" {
+ Show-FileContent -Path $_
+ }
+ }
+}
diff --git a/scripts/helpers/Import-PSModule.ps1 b/scripts/helpers/Import-PSModule.ps1
new file mode 100644
index 0000000..2797069
--- /dev/null
+++ b/scripts/helpers/Import-PSModule.ps1
@@ -0,0 +1,50 @@
+#Requires -Modules @{ ModuleName = 'Utilities'; ModuleVersion = '0.3.0' }
+
+function Import-PSModule {
+ <#
+ .SYNOPSIS
+ Imports a build PS module.
+
+ .DESCRIPTION
+ Imports a build PS module.
+
+ .EXAMPLE
+ Import-PSModule -SourceFolderPath $ModuleFolderPath -ModuleName $ModuleName
+
+ Imports a module located at $ModuleFolderPath with the name $ModuleName.
+ #>
+ [CmdletBinding()]
+ param(
+ # Path to the folder where the module source code is located.
+ [Parameter(Mandatory)]
+ [string] $Path,
+
+ # Name of the module.
+ [Parameter(Mandatory)]
+ [string] $ModuleName
+ )
+
+ $moduleName = Split-Path -Path $Path -Leaf
+ $manifestFileName = "$moduleName.psd1"
+ $manifestFilePath = Join-Path -Path $Path $manifestFileName
+ $manifestFile = Get-ModuleManifest -Path $manifestFilePath -As FileInfo -Verbose
+
+ Write-Host "Manifest file path: [$($manifestFile.FullName)]" -Verbose
+ $existingModule = Get-Module -Name $ModuleName -ListAvailable
+ $existingModule | Remove-Module -Force -Verbose
+ $existingModule.RequiredModules | ForEach-Object { $_ | Remove-Module -Force -Verbose -ErrorAction SilentlyContinue }
+ $existingModule.NestedModules | ForEach-Object { $_ | Remove-Module -Force -Verbose -ErrorAction SilentlyContinue }
+ # Get-InstalledPSResource | Where-Object Name -EQ $ModuleName | Uninstall-PSResource -SkipDependencyCheck -Verbose:$false
+ Resolve-PSModuleDependency -ManifestFilePath $manifestFile
+ Import-Module -Name $ModuleName -RequiredVersion '999.0.0'
+
+ Write-Host 'List loaded modules'
+ $availableModules = Get-Module -ListAvailable -Refresh -Verbose:$false
+ $availableModules | Select-Object Name, Version, Path | Sort-Object Name | Format-Table -AutoSize
+ Write-Host 'List commands'
+ Write-Host (Get-Command -Module $moduleName | Format-Table -AutoSize | Out-String)
+
+ if ($ModuleName -notin $availableModules.Name) {
+ throw 'Module not found'
+ }
+}
diff --git a/scripts/helpers/Resolve-PSModuleDependency.ps1 b/scripts/helpers/Resolve-PSModuleDependency.ps1
new file mode 100644
index 0000000..4924d3e
--- /dev/null
+++ b/scripts/helpers/Resolve-PSModuleDependency.ps1
@@ -0,0 +1,64 @@
+#Requires -Modules @{ ModuleName = 'Retry'; ModuleVersion = '0.1.3' }
+
+function Resolve-PSModuleDependency {
+ <#
+ .SYNOPSIS
+ Resolve dependencies for a module based on the manifest file.
+
+ .DESCRIPTION
+ Resolve dependencies for a module based on the manifest file, following PSModuleInfo structure
+
+ .EXAMPLE
+ Resolve-PSModuleDependency -Path 'C:\MyModule\MyModule.psd1'
+
+ Installs all modules defined in the manifest file, following PSModuleInfo structure.
+
+ .NOTES
+ Should later be adapted to support both pre-reqs, and dependencies.
+ Should later be adapted to take 4 parameters sets: specific version ("requiredVersion" | "GUID"), latest version ModuleVersion,
+ and latest version within a range MinimumVersion - MaximumVersion.
+ #>
+ [Alias('Resolve-PSModuleDependencies')]
+ [CmdletBinding()]
+ param(
+ # The path to the manifest file.
+ [Parameter(Mandatory)]
+ [string] $ManifestFilePath
+ )
+
+ Write-Host 'Resolving dependencies'
+
+ $manifest = Import-PowerShellDataFile -Path $ManifestFilePath
+ Write-Host "Reading [$ManifestFilePath]"
+ Write-Host "Found [$($manifest.RequiredModules.Count)] modules to install"
+
+ foreach ($requiredModule in $manifest.RequiredModules) {
+ $installParams = @{}
+
+ if ($requiredModule -is [string]) {
+ $installParams.Name = $requiredModule
+ } else {
+ $installParams.Name = $requiredModule.ModuleName
+ $installParams.MinimumVersion = $requiredModule.ModuleVersion
+ $installParams.RequiredVersion = $requiredModule.RequiredVersion
+ $installParams.MaximumVersion = $requiredModule.MaximumVersion
+ }
+ $installParams.Force = $true
+ $installParams.Verbose = $false
+
+ Write-Host "[$($installParams.Name)] - Installing module"
+ $VerbosePreferenceOriginal = $VerbosePreference
+ $VerbosePreference = 'SilentlyContinue'
+ Retry -Count 5 -Delay 10 {
+ Install-Module @installParams -AllowPrerelease:$false
+ }
+ $VerbosePreference = $VerbosePreferenceOriginal
+ Write-Host "[$($installParams.Name)] - Importing module"
+ $VerbosePreferenceOriginal = $VerbosePreference
+ $VerbosePreference = 'SilentlyContinue'
+ Import-Module @installParams
+ $VerbosePreference = $VerbosePreferenceOriginal
+ Write-Host "[$($installParams.Name)] - Done"
+ }
+ Write-Host 'Resolving dependencies - Done'
+}
diff --git a/scripts/main.ps1 b/scripts/main.ps1
index 2779f38..20ef6a1 100644
--- a/scripts/main.ps1
+++ b/scripts/main.ps1
@@ -1,24 +1,53 @@
-#Requires -Modules GitHub
+#Requires -Modules Utilities
[CmdletBinding()]
-param(
- [Parameter()]
- [string] $Subject = $env:GITHUB_ACTION_INPUT_subject
-)
-
-begin {
- $scriptName = $MyInvocation.MyCommand.Name
- Write-Debug "[$scriptName] - Start"
+param()
+
+$path = (Join-Path -Path $PSScriptRoot -ChildPath 'helpers') | Get-Item | Resolve-Path -Relative
+LogGroup "Loading helper scripts from [$path]" {
+ Get-ChildItem -Path $path -Filter '*.ps1' -Recurse | Resolve-Path -Relative | ForEach-Object {
+ Write-Host "$_"
+ . $_
+ }
+}
+
+LogGroup 'Loading inputs' {
+ $moduleName = ($env:GITHUB_ACTION_INPUT_Name | IsNullOrEmpty) ? $env:GITHUB_REPOSITORY_NAME : $env:GITHUB_ACTION_INPUT_Name
+ Write-Host "Module name: [$moduleName]"
+
+ $moduleSourceFolderPath = Join-Path -Path $env:GITHUB_WORKSPACE -ChildPath $env:GITHUB_ACTION_INPUT_Path/$moduleName
+ if (-not (Test-Path -Path $moduleSourceFolderPath)) {
+ $moduleSourceFolderPath = Join-Path -Path $env:GITHUB_WORKSPACE -ChildPath $env:GITHUB_ACTION_INPUT_Path
+ }
+ Write-Host "Source module path: [$moduleSourceFolderPath]"
+ if (-not (Test-Path -Path $moduleSourceFolderPath)) {
+ throw "Module path [$moduleSourceFolderPath] does not exist."
+ }
+
+ $modulesOutputFolderPath = Join-Path $env:GITHUB_WORKSPACE $env:GITHUB_ACTION_INPUT_ModulesOutputPath
+ Write-Host "Modules output path: [$modulesOutputFolderPath]"
+ $docsOutputFolderPath = Join-Path $env:GITHUB_WORKSPACE $env:GITHUB_ACTION_INPUT_DocsOutputPath
+ Write-Host "Docs output path: [$docsOutputFolderPath]"
}
-process {
- try {
- Write-Output "Hello, $Subject!"
- } catch {
- throw $_
+LogGroup 'Build local scripts' {
+ Write-Host 'Execution order:'
+ $scripts = Get-ChildItem -Filter '*build.ps1' -Recurse | Sort-Object -Property Name | Resolve-Path -Relative
+ $scripts | ForEach-Object {
+ Write-Host " - $_"
+ }
+ $scripts | ForEach-Object {
+ LogGroup "Build local scripts - [$_]" {
+ . $_
+ }
}
}
-end {
- Write-Debug "[$scriptName] - End"
+$params = @{
+ ModuleName = $moduleName
+ ModuleSourceFolderPath = $moduleSourceFolderPath
+ ModulesOutputFolderPath = $modulesOutputFolderPath
+ DocsOutputFolderPath = $docsOutputFolderPath
}
+
+Build-PSModuleDocumentation @params
diff --git a/tests/README.md b/tests/README.md
deleted file mode 100644
index a570e4d..0000000
--- a/tests/README.md
+++ /dev/null
@@ -1,3 +0,0 @@
-# Placeholder for tests
-
-Location for tests of the action.
diff --git a/tests/outputs/modules/PSModuleTest/PSModuleTest.psd1 b/tests/outputs/modules/PSModuleTest/PSModuleTest.psd1
new file mode 100644
index 0000000..e09d34e
--- /dev/null
+++ b/tests/outputs/modules/PSModuleTest/PSModuleTest.psd1
@@ -0,0 +1,75 @@
+@{
+ RootModule = 'PSModuleTest.psm1'
+ ModuleVersion = '999.0.0'
+ CompatiblePSEditions = @(
+ 'Core'
+ 'Desktop'
+ )
+ GUID = '20b37221-db1c-43db-9cca-f22b33123548'
+ Author = 'PSModule'
+ CompanyName = 'PSModule'
+ Copyright = '(c) 2024 PSModule. All rights reserved.'
+ Description = 'Process a module from source code to published module.'
+ PowerShellVersion = '5.1'
+ ProcessorArchitecture = 'None'
+ RequiredModules = @(
+ @{
+ ModuleVersion = '1.1.4'
+ ModuleName = 'PSSemVer'
+ }
+ 'Utilities'
+ )
+ RequiredAssemblies = 'assemblies/LsonLib.dll'
+ ScriptsToProcess = 'scripts/loader.ps1'
+ TypesToProcess = @(
+ 'types/DirectoryInfo.Types.ps1xml'
+ 'types/FileInfo.Types.ps1xml'
+ )
+ FormatsToProcess = @(
+ 'formats/CultureInfo.Format.ps1xml'
+ 'formats/Mygciview.Format.ps1xml'
+ )
+ NestedModules = @(
+ 'modules/OtherPSModule.psm1'
+ )
+ FunctionsToExport = @(
+ 'Get-PSModuleTest'
+ 'New-PSModuleTest'
+ 'Set-PSModuleTest'
+ 'Test-PSModuleTest'
+ )
+ CmdletsToExport = @()
+ VariablesToExport = @()
+ AliasesToExport = '*'
+ ModuleList = @(
+ 'modules/OtherPSModule.psm1'
+ )
+ FileList = @(
+ 'PSModuleTest.psd1'
+ 'PSModuleTest.psm1'
+ 'assemblies/LsonLib.dll'
+ 'data/Config.psd1'
+ 'data/Settings.psd1'
+ 'formats/CultureInfo.Format.ps1xml'
+ 'formats/Mygciview.Format.ps1xml'
+ 'modules/OtherPSModule.psm1'
+ 'scripts/loader.ps1'
+ 'types/DirectoryInfo.Types.ps1xml'
+ 'types/FileInfo.Types.ps1xml'
+ )
+ PrivateData = @{
+ PSData = @{
+ Tags = @(
+ 'workflow'
+ 'powershell'
+ 'powershell-module'
+ 'PSEdition_Desktop'
+ 'PSEdition_Core'
+ )
+ LicenseUri = 'https://github.com/PSModule/Process-PSModule/blob/main/LICENSE'
+ ProjectUri = 'https://github.com/PSModule/Process-PSModule'
+ IconUri = 'https://raw.githubusercontent.com/PSModule/Process-PSModule/main/icon/icon.png'
+ }
+ }
+}
+
diff --git a/tests/outputs/modules/PSModuleTest/PSModuleTest.psm1 b/tests/outputs/modules/PSModuleTest/PSModuleTest.psm1
new file mode 100644
index 0000000..dfe05df
--- /dev/null
+++ b/tests/outputs/modules/PSModuleTest/PSModuleTest.psm1
@@ -0,0 +1,382 @@
+[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidLongLines', '', Justification = 'Contains long links.')]
+[CmdletBinding()]
+param()
+
+$scriptName = $MyInvocation.MyCommand.Name
+Write-Verbose "[$scriptName] Importing module"
+
+#region - Data import
+Write-Verbose "[$scriptName] - [data] - Processing folder"
+$dataFolder = (Join-Path $PSScriptRoot 'data')
+Write-Verbose "[$scriptName] - [data] - [$dataFolder]"
+Get-ChildItem -Path "$dataFolder" -Recurse -Force -Include '*.psd1' -ErrorAction SilentlyContinue | ForEach-Object {
+ Write-Verbose "[$scriptName] - [data] - [$($_.Name)] - Importing"
+ New-Variable -Name $_.BaseName -Value (Import-PowerShellDataFile -Path $_.FullName) -Force
+ Write-Verbose "[$scriptName] - [data] - [$($_.Name)] - Done"
+}
+
+Write-Verbose "[$scriptName] - [data] - Done"
+#endregion - Data import
+
+#region - From /init
+Write-Verbose "[$scriptName] - [/init] - Processing folder"
+
+#region - From /init/initializer.ps1
+Write-Verbose "[$scriptName] - [/init/initializer.ps1] - Importing"
+
+Write-Verbose '-------------------------------' -Verbose
+Write-Verbose '--- THIS IS AN INITIALIZER ---' -Verbose
+Write-Verbose '-------------------------------' -Verbose
+
+Write-Verbose "[$scriptName] - [/init/initializer.ps1] - Done"
+#endregion - From /init/initializer.ps1
+
+Write-Verbose "[$scriptName] - [/init] - Done"
+#endregion - From /init
+
+#region - From /classes
+Write-Verbose "[$scriptName] - [/classes] - Processing folder"
+
+#region - From /classes/Book.ps1
+Write-Verbose "[$scriptName] - [/classes/Book.ps1] - Importing"
+
+class Book {
+ # Class properties
+ [string] $Title
+ [string] $Author
+ [string] $Synopsis
+ [string] $Publisher
+ [datetime] $PublishDate
+ [int] $PageCount
+ [string[]] $Tags
+ # Default constructor
+ Book() { $this.Init(@{}) }
+ # Convenience constructor from hashtable
+ Book([hashtable]$Properties) { $this.Init($Properties) }
+ # Common constructor for title and author
+ Book([string]$Title, [string]$Author) {
+ $this.Init(@{Title = $Title; Author = $Author })
+ }
+ # Shared initializer method
+ [void] Init([hashtable]$Properties) {
+ foreach ($Property in $Properties.Keys) {
+ $this.$Property = $Properties.$Property
+ }
+ }
+ # Method to calculate reading time as 2 minutes per page
+ [timespan] GetReadingTime() {
+ if ($this.PageCount -le 0) {
+ throw 'Unable to determine reading time from page count.'
+ }
+ $Minutes = $this.PageCount * 2
+ return [timespan]::new(0, $Minutes, 0)
+ }
+ # Method to calculate how long ago a book was published
+ [timespan] GetPublishedAge() {
+ if (
+ $null -eq $this.PublishDate -or
+ $this.PublishDate -eq [datetime]::MinValue
+ ) { throw 'PublishDate not defined' }
+
+ return (Get-Date) - $this.PublishDate
+ }
+ # Method to return a string representation of the book
+ [string] ToString() {
+ return "$($this.Title) by $($this.Author) ($($this.PublishDate.Year))"
+ }
+}
+
+Write-Verbose "[$scriptName] - [/classes/Book.ps1] - Done"
+#endregion - From /classes/Book.ps1
+#region - From /classes/BookList.ps1
+Write-Verbose "[$scriptName] - [/classes/BookList.ps1] - Importing"
+
+class BookList {
+ # Static property to hold the list of books
+ static [System.Collections.Generic.List[Book]] $Books
+ # Static method to initialize the list of books. Called in the other
+ # static methods to avoid needing to explicit initialize the value.
+ static [void] Initialize() { [BookList]::Initialize($false) }
+ static [bool] Initialize([bool]$force) {
+ if ([BookList]::Books.Count -gt 0 -and -not $force) {
+ return $false
+ }
+
+ [BookList]::Books = [System.Collections.Generic.List[Book]]::new()
+
+ return $true
+ }
+ # Ensure a book is valid for the list.
+ static [void] Validate([book]$Book) {
+ $Prefix = @(
+ 'Book validation failed: Book must be defined with the Title,'
+ 'Author, and PublishDate properties, but'
+ ) -join ' '
+ if ($null -eq $Book) { throw "$Prefix was null" }
+ if ([string]::IsNullOrEmpty($Book.Title)) {
+ throw "$Prefix Title wasn't defined"
+ }
+ if ([string]::IsNullOrEmpty($Book.Author)) {
+ throw "$Prefix Author wasn't defined"
+ }
+ if ([datetime]::MinValue -eq $Book.PublishDate) {
+ throw "$Prefix PublishDate wasn't defined"
+ }
+ }
+ # Static methods to manage the list of books.
+ # Add a book if it's not already in the list.
+ static [void] Add([Book]$Book) {
+ [BookList]::Initialize()
+ [BookList]::Validate($Book)
+ if ([BookList]::Books.Contains($Book)) {
+ throw "Book '$Book' already in list"
+ }
+
+ $FindPredicate = {
+ param([Book]$b)
+
+ $b.Title -eq $Book.Title -and
+ $b.Author -eq $Book.Author -and
+ $b.PublishDate -eq $Book.PublishDate
+ }.GetNewClosure()
+ if ([BookList]::Books.Find($FindPredicate)) {
+ throw "Book '$Book' already in list"
+ }
+
+ [BookList]::Books.Add($Book)
+ }
+ # Clear the list of books.
+ static [void] Clear() {
+ [BookList]::Initialize()
+ [BookList]::Books.Clear()
+ }
+ # Find a specific book using a filtering scriptblock.
+ static [Book] Find([scriptblock]$Predicate) {
+ [BookList]::Initialize()
+ return [BookList]::Books.Find($Predicate)
+ }
+ # Find every book matching the filtering scriptblock.
+ static [Book[]] FindAll([scriptblock]$Predicate) {
+ [BookList]::Initialize()
+ return [BookList]::Books.FindAll($Predicate)
+ }
+ # Remove a specific book.
+ static [void] Remove([Book]$Book) {
+ [BookList]::Initialize()
+ [BookList]::Books.Remove($Book)
+ }
+ # Remove a book by property value.
+ static [void] RemoveBy([string]$Property, [string]$Value) {
+ [BookList]::Initialize()
+ $Index = [BookList]::Books.FindIndex({
+ param($b)
+ $b.$Property -eq $Value
+ }.GetNewClosure())
+ if ($Index -ge 0) {
+ [BookList]::Books.RemoveAt($Index)
+ }
+ }
+}
+
+Write-Verbose "[$scriptName] - [/classes/BookList.ps1] - Done"
+#endregion - From /classes/BookList.ps1
+
+Write-Verbose "[$scriptName] - [/classes] - Done"
+#endregion - From /classes
+
+#region - From /private
+Write-Verbose "[$scriptName] - [/private] - Processing folder"
+
+#region - From /private/Get-InternalPSModule.ps1
+Write-Verbose "[$scriptName] - [/private/Get-InternalPSModule.ps1] - Importing"
+
+Function Get-InternalPSModule {
+ <#
+ .SYNOPSIS
+ Performs tests on a module.
+
+ .EXAMPLE
+ Test-PSModule -Name 'World'
+
+ "Hello, World!"
+ #>
+ [CmdletBinding()]
+ param (
+ # Name of the person to greet.
+ [Parameter(Mandatory)]
+ [string] $Name
+ )
+ Write-Output "Hello, $Name!"
+}
+
+Write-Verbose "[$scriptName] - [/private/Get-InternalPSModule.ps1] - Done"
+#endregion - From /private/Get-InternalPSModule.ps1
+#region - From /private/Set-InternalPSModule.ps1
+Write-Verbose "[$scriptName] - [/private/Set-InternalPSModule.ps1] - Importing"
+
+Function Set-InternalPSModule {
+ <#
+ .SYNOPSIS
+ Performs tests on a module.
+
+ .EXAMPLE
+ Test-PSModule -Name 'World'
+
+ "Hello, World!"
+ #>
+ [Diagnostics.CodeAnalysis.SuppressMessageAttribute(
+ 'PSUseShouldProcessForStateChangingFunctions', '', Scope = 'Function',
+ Justification = 'Reason for suppressing'
+ )]
+ [CmdletBinding()]
+ param (
+ # Name of the person to greet.
+ [Parameter(Mandatory)]
+ [string] $Name
+ )
+ Write-Output "Hello, $Name!"
+}
+
+Write-Verbose "[$scriptName] - [/private/Set-InternalPSModule.ps1] - Done"
+#endregion - From /private/Set-InternalPSModule.ps1
+
+Write-Verbose "[$scriptName] - [/private] - Done"
+#endregion - From /private
+
+#region - From /public
+Write-Verbose "[$scriptName] - [/public] - Processing folder"
+
+#region - From /public/Get-PSModuleTest.ps1
+Write-Verbose "[$scriptName] - [/public/Get-PSModuleTest.ps1] - Importing"
+
+#Requires -Modules Utilities
+
+function Get-PSModuleTest {
+ <#
+ .SYNOPSIS
+ Performs tests on a module.
+
+ .EXAMPLE
+ Test-PSModule -Name 'World'
+
+ "Hello, World!"
+ #>
+ [CmdletBinding()]
+ param (
+ # Name of the person to greet.
+ [Parameter(Mandatory)]
+ [string] $Name
+ )
+ Write-Output "Hello, $Name!"
+}
+
+Write-Verbose "[$scriptName] - [/public/Get-PSModuleTest.ps1] - Done"
+#endregion - From /public/Get-PSModuleTest.ps1
+#region - From /public/New-PSModuleTest.ps1
+Write-Verbose "[$scriptName] - [/public/New-PSModuleTest.ps1] - Importing"
+
+#Requires -Modules @{ModuleName='PSSemVer'; ModuleVersion='1.1.4'}
+
+function New-PSModuleTest {
+ <#
+ .SYNOPSIS
+ Performs tests on a module.
+
+ .EXAMPLE
+ Test-PSModule -Name 'World'
+
+ "Hello, World!"
+ #>
+ [Diagnostics.CodeAnalysis.SuppressMessageAttribute(
+ 'PSUseShouldProcessForStateChangingFunctions', '', Scope = 'Function',
+ Justification = 'Reason for suppressing'
+ )]
+ [CmdletBinding()]
+ param (
+ # Name of the person to greet.
+ [Parameter(Mandatory)]
+ [string] $Name
+ )
+ Write-Output "Hello, $Name!"
+}
+
+Write-Verbose "[$scriptName] - [/public/New-PSModuleTest.ps1] - Done"
+#endregion - From /public/New-PSModuleTest.ps1
+#region - From /public/Set-PSModuleTest.ps1
+Write-Verbose "[$scriptName] - [/public/Set-PSModuleTest.ps1] - Importing"
+
+function Set-PSModuleTest {
+ <#
+ .SYNOPSIS
+ Performs tests on a module.
+
+ .EXAMPLE
+ Test-PSModule -Name 'World'
+
+ "Hello, World!"
+ #>
+ [Diagnostics.CodeAnalysis.SuppressMessageAttribute(
+ 'PSUseShouldProcessForStateChangingFunctions', '', Scope = 'Function',
+ Justification = 'Reason for suppressing'
+ )]
+ [CmdletBinding()]
+ param (
+ # Name of the person to greet.
+ [Parameter(Mandatory)]
+ [string] $Name
+ )
+ Write-Output "Hello, $Name!"
+}
+
+Write-Verbose "[$scriptName] - [/public/Set-PSModuleTest.ps1] - Done"
+#endregion - From /public/Set-PSModuleTest.ps1
+#region - From /public/Test-PSModuleTest.ps1
+Write-Verbose "[$scriptName] - [/public/Test-PSModuleTest.ps1] - Importing"
+
+function Test-PSModuleTest {
+ <#
+ .SYNOPSIS
+ Performs tests on a module.
+
+ .EXAMPLE
+ Test-PSModule -Name 'World'
+
+ "Hello, World!"
+ #>
+ [CmdletBinding()]
+ param (
+ # Name of the person to greet.
+ [Parameter(Mandatory)]
+ [string] $Name
+ )
+ Write-Output "Hello, $Name!"
+}
+
+Write-Verbose "[$scriptName] - [/public/Test-PSModuleTest.ps1] - Done"
+#endregion - From /public/Test-PSModuleTest.ps1
+
+Write-Verbose "[$scriptName] - [/public] - Done"
+#endregion - From /public
+
+#region - From /finally.ps1
+Write-Verbose "[$scriptName] - [/finally.ps1] - Importing"
+
+Write-Verbose '------------------------------' -Verbose
+Write-Verbose '--- THIS IS A LAST LOADER ---' -Verbose
+Write-Verbose '------------------------------' -Verbose
+Write-Verbose "[$scriptName] - [/finally.ps1] - Done"
+#endregion - From /finally.ps1
+
+$exports = @{
+ Cmdlet = ''
+ Alias = '*'
+ Variable = ''
+ Function = @(
+ 'Get-PSModuleTest'
+ 'New-PSModuleTest'
+ 'Set-PSModuleTest'
+ 'Test-PSModuleTest'
+ )
+}
+Export-ModuleMember @exports
+
diff --git a/tests/outputs/modules/PSModuleTest/assemblies/LsonLib.dll b/tests/outputs/modules/PSModuleTest/assemblies/LsonLib.dll
new file mode 100644
index 0000000..3661807
Binary files /dev/null and b/tests/outputs/modules/PSModuleTest/assemblies/LsonLib.dll differ
diff --git a/tests/outputs/modules/PSModuleTest/data/Config.psd1 b/tests/outputs/modules/PSModuleTest/data/Config.psd1
new file mode 100644
index 0000000..fea4466
--- /dev/null
+++ b/tests/outputs/modules/PSModuleTest/data/Config.psd1
@@ -0,0 +1,3 @@
+@{
+ RandomKey = 'RandomValue'
+}
diff --git a/tests/outputs/modules/PSModuleTest/data/Settings.psd1 b/tests/outputs/modules/PSModuleTest/data/Settings.psd1
new file mode 100644
index 0000000..bcfa7b4
--- /dev/null
+++ b/tests/outputs/modules/PSModuleTest/data/Settings.psd1
@@ -0,0 +1,3 @@
+@{
+ RandomSetting = 'RandomSettingValue'
+}
diff --git a/tests/outputs/modules/PSModuleTest/formats/CultureInfo.Format.ps1xml b/tests/outputs/modules/PSModuleTest/formats/CultureInfo.Format.ps1xml
new file mode 100644
index 0000000..a715e08
--- /dev/null
+++ b/tests/outputs/modules/PSModuleTest/formats/CultureInfo.Format.ps1xml
@@ -0,0 +1,37 @@
+
+
+
+
+ System.Globalization.CultureInfo
+
+ System.Globalization.CultureInfo
+
+
+
+
+ 16
+
+
+ 16
+
+
+
+
+
+
+
+ LCID
+
+
+ Name
+
+
+ DisplayName
+
+
+
+
+
+
+
+
diff --git a/tests/outputs/modules/PSModuleTest/formats/Mygciview.Format.ps1xml b/tests/outputs/modules/PSModuleTest/formats/Mygciview.Format.ps1xml
new file mode 100644
index 0000000..4c972c2
--- /dev/null
+++ b/tests/outputs/modules/PSModuleTest/formats/Mygciview.Format.ps1xml
@@ -0,0 +1,65 @@
+
+
+
+
+ mygciview
+
+ System.IO.DirectoryInfo
+ System.IO.FileInfo
+
+
+ PSParentPath
+
+
+
+
+
+ 7
+ Left
+
+
+
+ 26
+ Right
+
+
+
+ 26
+ Right
+
+
+
+ 14
+ Right
+
+
+
+ Left
+
+
+
+
+
+
+
+ ModeWithoutHardLink
+
+
+ LastWriteTime
+
+
+ CreationTime
+
+
+ Length
+
+
+ Name
+
+
+
+
+
+
+
+
diff --git a/tests/outputs/modules/PSModuleTest/modules/OtherPSModule.psm1 b/tests/outputs/modules/PSModuleTest/modules/OtherPSModule.psm1
new file mode 100644
index 0000000..9e4353b
--- /dev/null
+++ b/tests/outputs/modules/PSModuleTest/modules/OtherPSModule.psm1
@@ -0,0 +1,19 @@
+Function Get-OtherPSModule {
+ <#
+ .SYNOPSIS
+ Performs tests on a module.
+
+ .DESCRIPTION
+ A longer description of the function.
+
+ .EXAMPLE
+ Get-OtherPSModule -Name 'World'
+ #>
+ [CmdletBinding()]
+ param(
+ # Name of the person to greet.
+ [Parameter(Mandatory)]
+ [string] $Name
+ )
+ Write-Output "Hello, $Name!"
+}
diff --git a/tests/outputs/modules/PSModuleTest/scripts/loader.ps1 b/tests/outputs/modules/PSModuleTest/scripts/loader.ps1
new file mode 100644
index 0000000..29ad42f
--- /dev/null
+++ b/tests/outputs/modules/PSModuleTest/scripts/loader.ps1
@@ -0,0 +1,3 @@
+Write-Verbose '-------------------------' -Verbose
+Write-Verbose '--- THIS IS A LOADER ---' -Verbose
+Write-Verbose '-------------------------' -Verbose
diff --git a/tests/outputs/modules/PSModuleTest/types/DirectoryInfo.Types.ps1xml b/tests/outputs/modules/PSModuleTest/types/DirectoryInfo.Types.ps1xml
new file mode 100644
index 0000000..aef538b
--- /dev/null
+++ b/tests/outputs/modules/PSModuleTest/types/DirectoryInfo.Types.ps1xml
@@ -0,0 +1,21 @@
+
+
+
+ System.IO.FileInfo
+
+
+ Status
+ Success
+
+
+
+
+ System.IO.DirectoryInfo
+
+
+ Status
+ Success
+
+
+
+
diff --git a/tests/outputs/modules/PSModuleTest/types/FileInfo.Types.ps1xml b/tests/outputs/modules/PSModuleTest/types/FileInfo.Types.ps1xml
new file mode 100644
index 0000000..4cfaf6b
--- /dev/null
+++ b/tests/outputs/modules/PSModuleTest/types/FileInfo.Types.ps1xml
@@ -0,0 +1,14 @@
+
+
+
+ System.IO.FileInfo
+
+
+ Age
+
+ ((Get-Date) - ($this.CreationTime)).Days
+
+
+
+
+
diff --git a/tests/src/assemblies/LsonLib.dll b/tests/src/assemblies/LsonLib.dll
new file mode 100644
index 0000000..3661807
Binary files /dev/null and b/tests/src/assemblies/LsonLib.dll differ
diff --git a/tests/src/classes/private/SecretWriter.ps1 b/tests/src/classes/private/SecretWriter.ps1
new file mode 100644
index 0000000..1b1732a
--- /dev/null
+++ b/tests/src/classes/private/SecretWriter.ps1
@@ -0,0 +1,15 @@
+class SecretWriter {
+ [string] $Alias
+ [string] $Name
+ [string] $Secret
+
+ SecretWriter([string] $alias, [string] $name, [string] $secret) {
+ $this.Alias = $alias
+ $this.Name = $name
+ $this.Secret = $secret
+ }
+
+ [string] GetAlias() {
+ return $this.Alias
+ }
+}
diff --git a/tests/src/classes/public/Book.ps1 b/tests/src/classes/public/Book.ps1
new file mode 100644
index 0000000..8917d9a
--- /dev/null
+++ b/tests/src/classes/public/Book.ps1
@@ -0,0 +1,147 @@
+class Book {
+ # Class properties
+ [string] $Title
+ [string] $Author
+ [string] $Synopsis
+ [string] $Publisher
+ [datetime] $PublishDate
+ [int] $PageCount
+ [string[]] $Tags
+ # Default constructor
+ Book() { $this.Init(@{}) }
+ # Convenience constructor from hashtable
+ Book([hashtable]$Properties) { $this.Init($Properties) }
+ # Common constructor for title and author
+ Book([string]$Title, [string]$Author) {
+ $this.Init(@{Title = $Title; Author = $Author })
+ }
+ # Shared initializer method
+ [void] Init([hashtable]$Properties) {
+ foreach ($Property in $Properties.Keys) {
+ $this.$Property = $Properties.$Property
+ }
+ }
+ # Method to calculate reading time as 2 minutes per page
+ [timespan] GetReadingTime() {
+ if ($this.PageCount -le 0) {
+ throw 'Unable to determine reading time from page count.'
+ }
+ $Minutes = $this.PageCount * 2
+ return [timespan]::new(0, $Minutes, 0)
+ }
+ # Method to calculate how long ago a book was published
+ [timespan] GetPublishedAge() {
+ if (
+ $null -eq $this.PublishDate -or
+ $this.PublishDate -eq [datetime]::MinValue
+ ) { throw 'PublishDate not defined' }
+
+ return (Get-Date) - $this.PublishDate
+ }
+ # Method to return a string representation of the book
+ [string] ToString() {
+ return "$($this.Title) by $($this.Author) ($($this.PublishDate.Year))"
+ }
+}
+
+class BookList {
+ # Static property to hold the list of books
+ static [System.Collections.Generic.List[Book]] $Books
+ # Static method to initialize the list of books. Called in the other
+ # static methods to avoid needing to explicit initialize the value.
+ static [void] Initialize() { [BookList]::Initialize($false) }
+ static [bool] Initialize([bool]$force) {
+ if ([BookList]::Books.Count -gt 0 -and -not $force) {
+ return $false
+ }
+
+ [BookList]::Books = [System.Collections.Generic.List[Book]]::new()
+
+ return $true
+ }
+ # Ensure a book is valid for the list.
+ static [void] Validate([book]$Book) {
+ $Prefix = @(
+ 'Book validation failed: Book must be defined with the Title,'
+ 'Author, and PublishDate properties, but'
+ ) -join ' '
+ if ($null -eq $Book) { throw "$Prefix was null" }
+ if ([string]::IsNullOrEmpty($Book.Title)) {
+ throw "$Prefix Title wasn't defined"
+ }
+ if ([string]::IsNullOrEmpty($Book.Author)) {
+ throw "$Prefix Author wasn't defined"
+ }
+ if ([datetime]::MinValue -eq $Book.PublishDate) {
+ throw "$Prefix PublishDate wasn't defined"
+ }
+ }
+ # Static methods to manage the list of books.
+ # Add a book if it's not already in the list.
+ static [void] Add([Book]$Book) {
+ [BookList]::Initialize()
+ [BookList]::Validate($Book)
+ if ([BookList]::Books.Contains($Book)) {
+ throw "Book '$Book' already in list"
+ }
+
+ $FindPredicate = {
+ param([Book]$b)
+
+ $b.Title -eq $Book.Title -and
+ $b.Author -eq $Book.Author -and
+ $b.PublishDate -eq $Book.PublishDate
+ }.GetNewClosure()
+ if ([BookList]::Books.Find($FindPredicate)) {
+ throw "Book '$Book' already in list"
+ }
+
+ [BookList]::Books.Add($Book)
+ }
+ # Clear the list of books.
+ static [void] Clear() {
+ [BookList]::Initialize()
+ [BookList]::Books.Clear()
+ }
+ # Find a specific book using a filtering scriptblock.
+ static [Book] Find([scriptblock]$Predicate) {
+ [BookList]::Initialize()
+ return [BookList]::Books.Find($Predicate)
+ }
+ # Find every book matching the filtering scriptblock.
+ static [Book[]] FindAll([scriptblock]$Predicate) {
+ [BookList]::Initialize()
+ return [BookList]::Books.FindAll($Predicate)
+ }
+ # Remove a specific book.
+ static [void] Remove([Book]$Book) {
+ [BookList]::Initialize()
+ [BookList]::Books.Remove($Book)
+ }
+ # Remove a book by property value.
+ static [void] RemoveBy([string]$Property, [string]$Value) {
+ [BookList]::Initialize()
+ $Index = [BookList]::Books.FindIndex({
+ param($b)
+ $b.$Property -eq $Value
+ }.GetNewClosure())
+ if ($Index -ge 0) {
+ [BookList]::Books.RemoveAt($Index)
+ }
+ }
+}
+
+enum Binding {
+ Hardcover
+ Paperback
+ EBook
+}
+
+enum Genre {
+ Mystery
+ Thriller
+ Romance
+ ScienceFiction
+ Fantasy
+ Horror
+}
diff --git a/tests/src/data/Config.psd1 b/tests/src/data/Config.psd1
new file mode 100644
index 0000000..fea4466
--- /dev/null
+++ b/tests/src/data/Config.psd1
@@ -0,0 +1,3 @@
+@{
+ RandomKey = 'RandomValue'
+}
diff --git a/tests/src/data/Settings.psd1 b/tests/src/data/Settings.psd1
new file mode 100644
index 0000000..bcfa7b4
--- /dev/null
+++ b/tests/src/data/Settings.psd1
@@ -0,0 +1,3 @@
+@{
+ RandomSetting = 'RandomSettingValue'
+}
diff --git a/tests/src/finally.ps1 b/tests/src/finally.ps1
new file mode 100644
index 0000000..d8fc207
--- /dev/null
+++ b/tests/src/finally.ps1
@@ -0,0 +1,3 @@
+Write-Verbose '------------------------------'
+Write-Verbose '--- THIS IS A LAST LOADER ---'
+Write-Verbose '------------------------------'
diff --git a/tests/src/formats/CultureInfo.Format.ps1xml b/tests/src/formats/CultureInfo.Format.ps1xml
new file mode 100644
index 0000000..a715e08
--- /dev/null
+++ b/tests/src/formats/CultureInfo.Format.ps1xml
@@ -0,0 +1,37 @@
+
+
+
+
+ System.Globalization.CultureInfo
+
+ System.Globalization.CultureInfo
+
+
+
+
+ 16
+
+
+ 16
+
+
+
+
+
+
+
+ LCID
+
+
+ Name
+
+
+ DisplayName
+
+
+
+
+
+
+
+
diff --git a/tests/src/formats/Mygciview.Format.ps1xml b/tests/src/formats/Mygciview.Format.ps1xml
new file mode 100644
index 0000000..4c972c2
--- /dev/null
+++ b/tests/src/formats/Mygciview.Format.ps1xml
@@ -0,0 +1,65 @@
+
+
+
+
+ mygciview
+
+ System.IO.DirectoryInfo
+ System.IO.FileInfo
+
+
+ PSParentPath
+
+
+
+
+
+ 7
+ Left
+
+
+
+ 26
+ Right
+
+
+
+ 26
+ Right
+
+
+
+ 14
+ Right
+
+
+
+ Left
+
+
+
+
+
+
+
+ ModeWithoutHardLink
+
+
+ LastWriteTime
+
+
+ CreationTime
+
+
+ Length
+
+
+ Name
+
+
+
+
+
+
+
+
diff --git a/tests/src/functions/private/Get-InternalPSModule.ps1 b/tests/src/functions/private/Get-InternalPSModule.ps1
new file mode 100644
index 0000000..89f053c
--- /dev/null
+++ b/tests/src/functions/private/Get-InternalPSModule.ps1
@@ -0,0 +1,18 @@
+function Get-InternalPSModule {
+ <#
+ .SYNOPSIS
+ Performs tests on a module.
+
+ .EXAMPLE
+ Test-PSModule -Name 'World'
+
+ "Hello, World!"
+ #>
+ [CmdletBinding()]
+ param (
+ # Name of the person to greet.
+ [Parameter(Mandatory)]
+ [string] $Name
+ )
+ Write-Output "Hello, $Name!"
+}
diff --git a/tests/src/functions/private/Set-InternalPSModule.ps1 b/tests/src/functions/private/Set-InternalPSModule.ps1
new file mode 100644
index 0000000..cf870ba
--- /dev/null
+++ b/tests/src/functions/private/Set-InternalPSModule.ps1
@@ -0,0 +1,22 @@
+function Set-InternalPSModule {
+ <#
+ .SYNOPSIS
+ Performs tests on a module.
+
+ .EXAMPLE
+ Test-PSModule -Name 'World'
+
+ "Hello, World!"
+ #>
+ [Diagnostics.CodeAnalysis.SuppressMessageAttribute(
+ 'PSUseShouldProcessForStateChangingFunctions', '', Scope = 'Function',
+ Justification = 'Reason for suppressing'
+ )]
+ [CmdletBinding()]
+ param (
+ # Name of the person to greet.
+ [Parameter(Mandatory)]
+ [string] $Name
+ )
+ Write-Output "Hello, $Name!"
+}
diff --git a/tests/src/functions/public/PSModule/Get-PSModuleTest.ps1 b/tests/src/functions/public/PSModule/Get-PSModuleTest.ps1
new file mode 100644
index 0000000..be5afc0
--- /dev/null
+++ b/tests/src/functions/public/PSModule/Get-PSModuleTest.ps1
@@ -0,0 +1,22 @@
+#Requires -Modules Store
+#Requires -Modules @{ ModuleName = 'PSSemVer'; RequiredVersion = '1.1.4' }
+#Requires -Modules @{ ModuleName = 'DynamicParams'; ModuleVersion = '1.1.8' }
+
+function Get-PSModuleTest {
+ <#
+ .SYNOPSIS
+ Performs tests on a module.
+
+ .EXAMPLE
+ Test-PSModule -Name 'World'
+
+ "Hello, World!"
+ #>
+ [CmdletBinding()]
+ param (
+ # Name of the person to greet.
+ [Parameter(Mandatory)]
+ [string] $Name
+ )
+ Write-Output "Hello, $Name!"
+}
diff --git a/tests/src/functions/public/PSModule/New-PSModuleTest.ps1 b/tests/src/functions/public/PSModule/New-PSModuleTest.ps1
new file mode 100644
index 0000000..5fa16bc
--- /dev/null
+++ b/tests/src/functions/public/PSModule/New-PSModuleTest.ps1
@@ -0,0 +1,37 @@
+#Requires -Modules @{ModuleName='PSSemVer'; ModuleVersion='1.1.4'}
+
+function New-PSModuleTest {
+ <#
+ .SYNOPSIS
+ Performs tests on a module.
+
+ .EXAMPLE
+ Test-PSModule -Name 'World'
+
+ "Hello, World!"
+
+ .NOTES
+ Testing if a module can have a [Markdown based link](https://example.com).
+ !"#¤%&/()=?`´^¨*'-_+§½{[]}<>|@£$€¥¢:;.,"
+ \[This is a test\]
+ #>
+ [Diagnostics.CodeAnalysis.SuppressMessageAttribute(
+ 'PSUseShouldProcessForStateChangingFunctions', '', Scope = 'Function',
+ Justification = 'Reason for suppressing'
+ )]
+ [Alias('New-PSModuleTestAlias1')]
+ [Alias('New-PSModuleTestAlias2')]
+ [CmdletBinding()]
+ param (
+ # Name of the person to greet.
+ [Parameter(Mandatory)]
+ [string] $Name
+ )
+ Write-Output "Hello, $Name!"
+}
+
+New-Alias New-PSModuleTestAlias3 New-PSModuleTest
+New-Alias -Name New-PSModuleTestAlias4 -Value New-PSModuleTest
+
+
+Set-Alias New-PSModuleTestAlias5 New-PSModuleTest
diff --git a/tests/src/functions/public/PSModule/PSModule.md b/tests/src/functions/public/PSModule/PSModule.md
new file mode 100644
index 0000000..79741cf
--- /dev/null
+++ b/tests/src/functions/public/PSModule/PSModule.md
@@ -0,0 +1 @@
+# This is PSModule
diff --git a/tests/src/functions/public/SomethingElse/Set-PSModuleTest.ps1 b/tests/src/functions/public/SomethingElse/Set-PSModuleTest.ps1
new file mode 100644
index 0000000..a87ac11
--- /dev/null
+++ b/tests/src/functions/public/SomethingElse/Set-PSModuleTest.ps1
@@ -0,0 +1,22 @@
+function Set-PSModuleTest {
+ <#
+ .SYNOPSIS
+ Performs tests on a module.
+
+ .EXAMPLE
+ Test-PSModule -Name 'World'
+
+ "Hello, World!"
+ #>
+ [Diagnostics.CodeAnalysis.SuppressMessageAttribute(
+ 'PSUseShouldProcessForStateChangingFunctions', '', Scope = 'Function',
+ Justification = 'Reason for suppressing'
+ )]
+ [CmdletBinding()]
+ param (
+ # Name of the person to greet.
+ [Parameter(Mandatory)]
+ [string] $Name
+ )
+ Write-Output "Hello, $Name!"
+}
diff --git a/tests/src/functions/public/SomethingElse/SomethingElse.md b/tests/src/functions/public/SomethingElse/SomethingElse.md
new file mode 100644
index 0000000..d9f7e9e
--- /dev/null
+++ b/tests/src/functions/public/SomethingElse/SomethingElse.md
@@ -0,0 +1 @@
+# This is SomethingElse
diff --git a/tests/src/functions/public/Test-PSModuleTest.ps1 b/tests/src/functions/public/Test-PSModuleTest.ps1
new file mode 100644
index 0000000..26be2b9
--- /dev/null
+++ b/tests/src/functions/public/Test-PSModuleTest.ps1
@@ -0,0 +1,18 @@
+function Test-PSModuleTest {
+ <#
+ .SYNOPSIS
+ Performs tests on a module.
+
+ .EXAMPLE
+ Test-PSModule -Name 'World'
+
+ "Hello, World!"
+ #>
+ [CmdletBinding()]
+ param (
+ # Name of the person to greet.
+ [Parameter(Mandatory)]
+ [string] $Name
+ )
+ Write-Output "Hello, $Name!"
+}
diff --git a/tests/src/header.ps1 b/tests/src/header.ps1
new file mode 100644
index 0000000..cc1fde9
--- /dev/null
+++ b/tests/src/header.ps1
@@ -0,0 +1,3 @@
+[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidLongLines', '', Justification = 'Contains long links.')]
+[CmdletBinding()]
+param()
diff --git a/tests/src/init/initializer.ps1 b/tests/src/init/initializer.ps1
new file mode 100644
index 0000000..28396fb
--- /dev/null
+++ b/tests/src/init/initializer.ps1
@@ -0,0 +1,3 @@
+Write-Verbose '-------------------------------'
+Write-Verbose '--- THIS IS AN INITIALIZER ---'
+Write-Verbose '-------------------------------'
diff --git a/tests/src/modules/OtherPSModule.psm1 b/tests/src/modules/OtherPSModule.psm1
new file mode 100644
index 0000000..5d6af8e
--- /dev/null
+++ b/tests/src/modules/OtherPSModule.psm1
@@ -0,0 +1,19 @@
+function Get-OtherPSModule {
+ <#
+ .SYNOPSIS
+ Performs tests on a module.
+
+ .DESCRIPTION
+ A longer description of the function.
+
+ .EXAMPLE
+ Get-OtherPSModule -Name 'World'
+ #>
+ [CmdletBinding()]
+ param(
+ # Name of the person to greet.
+ [Parameter(Mandatory)]
+ [string] $Name
+ )
+ Write-Output "Hello, $Name!"
+}
diff --git a/tests/src/scripts/loader.ps1 b/tests/src/scripts/loader.ps1
new file mode 100644
index 0000000..973735a
--- /dev/null
+++ b/tests/src/scripts/loader.ps1
@@ -0,0 +1,3 @@
+Write-Verbose '-------------------------'
+Write-Verbose '--- THIS IS A LOADER ---'
+Write-Verbose '-------------------------'
diff --git a/tests/src/types/DirectoryInfo.Types.ps1xml b/tests/src/types/DirectoryInfo.Types.ps1xml
new file mode 100644
index 0000000..aef538b
--- /dev/null
+++ b/tests/src/types/DirectoryInfo.Types.ps1xml
@@ -0,0 +1,21 @@
+
+
+
+ System.IO.FileInfo
+
+
+ Status
+ Success
+
+
+
+
+ System.IO.DirectoryInfo
+
+
+ Status
+ Success
+
+
+
+
diff --git a/tests/src/types/FileInfo.Types.ps1xml b/tests/src/types/FileInfo.Types.ps1xml
new file mode 100644
index 0000000..4cfaf6b
--- /dev/null
+++ b/tests/src/types/FileInfo.Types.ps1xml
@@ -0,0 +1,14 @@
+
+
+
+ System.IO.FileInfo
+
+
+ Age
+
+ ((Get-Date) - ($this.CreationTime)).Days
+
+
+
+
+
diff --git a/tests/src/variables/private/PrivateVariables.ps1 b/tests/src/variables/private/PrivateVariables.ps1
new file mode 100644
index 0000000..f1fc2c3
--- /dev/null
+++ b/tests/src/variables/private/PrivateVariables.ps1
@@ -0,0 +1,47 @@
+$script:HabitablePlanets = @(
+ @{
+ Name = 'Earth'
+ Mass = 5.97
+ Diameter = 12756
+ DayLength = 24.0
+ },
+ @{
+ Name = 'Mars'
+ Mass = 0.642
+ Diameter = 6792
+ DayLength = 24.7
+ },
+ @{
+ Name = 'Proxima Centauri b'
+ Mass = 1.17
+ Diameter = 11449
+ DayLength = 5.15
+ },
+ @{
+ Name = 'Kepler-442b'
+ Mass = 2.34
+ Diameter = 11349
+ DayLength = 5.7
+ },
+ @{
+ Name = 'Kepler-452b'
+ Mass = 5.0
+ Diameter = 17340
+ DayLength = 20.0
+ }
+)
+
+$script:InhabitedPlanets = @(
+ @{
+ Name = 'Earth'
+ Mass = 5.97
+ Diameter = 12756
+ DayLength = 24.0
+ },
+ @{
+ Name = 'Mars'
+ Mass = 0.642
+ Diameter = 6792
+ DayLength = 24.7
+ }
+)
diff --git a/tests/src/variables/public/Moons.ps1 b/tests/src/variables/public/Moons.ps1
new file mode 100644
index 0000000..dd0f33c
--- /dev/null
+++ b/tests/src/variables/public/Moons.ps1
@@ -0,0 +1,6 @@
+$script:Moons = @(
+ @{
+ Planet = 'Earth'
+ Name = 'Moon'
+ }
+)
diff --git a/tests/src/variables/public/Planets.ps1 b/tests/src/variables/public/Planets.ps1
new file mode 100644
index 0000000..736584b
--- /dev/null
+++ b/tests/src/variables/public/Planets.ps1
@@ -0,0 +1,20 @@
+$script:Planets = @(
+ @{
+ Name = 'Mercury'
+ Mass = 0.330
+ Diameter = 4879
+ DayLength = 4222.6
+ },
+ @{
+ Name = 'Venus'
+ Mass = 4.87
+ Diameter = 12104
+ DayLength = 2802.0
+ },
+ @{
+ Name = 'Earth'
+ Mass = 5.97
+ Diameter = 12756
+ DayLength = 24.0
+ }
+)
diff --git a/tests/src/variables/public/SolarSystems.ps1 b/tests/src/variables/public/SolarSystems.ps1
new file mode 100644
index 0000000..acbcedf
--- /dev/null
+++ b/tests/src/variables/public/SolarSystems.ps1
@@ -0,0 +1,17 @@
+$script:SolarSystems = @(
+ @{
+ Name = 'Solar System'
+ Planets = $script:Planets
+ Moons = $script:Moons
+ },
+ @{
+ Name = 'Alpha Centauri'
+ Planets = @()
+ Moons = @()
+ },
+ @{
+ Name = 'Sirius'
+ Planets = @()
+ Moons = @()
+ }
+)