Skip to content

Commit 289b6d3

Browse files
sharpninjaCopilot
andcommitted
Add module auto-loader from YAML configuration
- Add module-loader.ps1: reads modules.yml, auto-installs missing modules from PSGallery, and imports them during profile init - Add modules.yml: default config with powershell-yaml bootstrap entry - Update _common.ps1: call module-loader.ps1 from Initialize-Snippets - Update README.md: document module auto-loader schema and behavior Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent 774d420 commit 289b6d3

4 files changed

Lines changed: 249 additions & 0 deletions

File tree

README.md

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,12 +24,61 @@ Execute this script...
2424
curl 'https://raw.githubusercontent.com/PS-Services/Snippets/master/linux-setup.sh' -v | /bin/bash
2525
```
2626

27+
## Module Auto-Loader
28+
29+
Modules can be declaratively defined in a `modules.yml` file and will be automatically installed (from PSGallery) and imported during profile initialization. The `powershell-yaml` module is used for YAML parsing and will be auto-installed if missing.
30+
31+
### Configuration Path
32+
33+
By default, the loader reads `$env:Snippets\modules.yml`. To use a user-specific configuration (recommended), set `$env:SnippetsModulesYaml` in your `$PROFILE` **before** the Snippets block:
34+
35+
```powershell
36+
$env:Snippets = "$env:OneDrive\Documents\PowerShell\Snippets"
37+
$env:SnippetsModulesYaml = "$env:USERPROFILE\modules.yml"
38+
```
39+
40+
### `modules.yml` Schema
41+
42+
```yaml
43+
modules:
44+
- name: ModuleName # Required. Module name.
45+
version: "1.0.0" # Optional. Minimum required version.
46+
source: PSGallery # Optional. "PSGallery" (default) or a file path to a .psd1/.psm1.
47+
required: true # Optional. Default true. If false, failure is non-fatal.
48+
parameters: [] # Optional. Arguments passed to Import-Module -ArgumentList.
49+
```
50+
51+
### Behavior
52+
53+
- **PSGallery modules**: Installed automatically to `CurrentUser` scope if missing or below the specified version.
54+
- **Path-based modules**: Imported directly from the specified `.psd1` or `.psm1` file path.
55+
- **Required modules** (`required: true`): Raise an error if they fail to load.
56+
- **Optional modules** (`required: false`): Fail silently with a verbose message.
57+
- **`powershell-yaml`**: Always loaded first as the YAML parser; include it in your `modules.yml` to make the dependency explicit.
58+
59+
### Example
60+
61+
```yaml
62+
modules:
63+
- name: powershell-yaml
64+
required: true
65+
- name: posh-git
66+
required: false
67+
- name: Terminal-Icons
68+
required: false
69+
version: "0.11.0"
70+
- name: MsixTools
71+
source: "E:\\github\\remote-agent\\scripts\\MsixTools\\MsixTools.psd1"
72+
required: false
73+
```
74+
2775
| Win | *nix | Script | Description |
2876
|-----|------|------------------|------------------------------------------------------------------------------------------------------------------------------|
2977
| :white_check_mark: | :white_check_mark: | bing.ps1 | Search Bing from Powershell. |
3078
| :white_check_mark: | :white_check_mark: | clean&#x2011;folder.ps1 | Remove all `bin` and `obj` folders in current path. |
3179
| :white_check_mark: | :white_check_mark: | github.ps1 | **_Set `$env:GITHUB` first to the root of your github repositories._** Use `hub` or `hub <repository>` to go to those folders. |
3280
| :white_check_mark: | :white_check_mark: | oh&#x2011;my&#x2011;posh.ps1 | Initializes Oh-My-Posh for the current PowerShell
81+
| :white_check_mark: | :white_check_mark: | module&#x2011;loader.ps1 | Auto-loads PowerShell modules defined in `modules.yml`. |
3382
| :white_check_mark: | :white_check_mark: | _repos.ps1 | A unified repository query system. |
3483
| :white_check_mark: | | chocolatey.ps1 | Setup Chocolatey profile in PowerShell. |
3584
| :white_check_mark: | | devmode.ps1 | Startup VS 2022 Dev Mode Tools. |
@@ -42,6 +91,7 @@ All scripts work in both PowerShell Core and Windows PowerShell 5.1!
4291

4392
```powershell
4493
$env:Snippets="$env:OneDrive\Documents\PowerShell\Snippets"
94+
$env:SnippetsModulesYaml = "$env:USERPROFILE\modules.yml"
4595
4696
if ($env:VerboseStartup -eq 'true') {
4797
[switch]$Verbose = $true

_common.ps1

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,13 @@ function Initialize-Snippets {
8383

8484
$env:SnippetsInitialized="$true"
8585

86+
# Auto-load modules from modules.yml
87+
$moduleLoaderPath = Join-Path $path -ChildPath 'module-loader.ps1'
88+
if (Test-Path $moduleLoaderPath) {
89+
Write-Verbose "[$script] Running module auto-loader..." -Verbose:$Verbose
90+
. $moduleLoaderPath -VerboseSwitch:$Verbose
91+
}
92+
8693
if($env:IsUnix -eq "$true") { return "Powershell ready for Unix-like System."}
8794
elseif($env:IsDesktop -eq "$true") { return "Windows Powershell is ready."}
8895
else { return "Powershell Core is ready." }

module-loader.ps1

Lines changed: 172 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,172 @@
1+
param([switch]$VerboseSwitch = $false)
2+
3+
# $Verbose=$true -or $VerboseSwitch
4+
$Verbose=$VerboseSwitch
5+
$script = $MyInvocation.MyCommand
6+
7+
<#
8+
.SYNOPSIS
9+
Loads PowerShell modules defined in a YAML configuration file.
10+
11+
.DESCRIPTION
12+
Reads modules.yml (or the path in $env:SnippetsModulesYaml), installs any
13+
missing modules from PSGallery, and imports them into the current session.
14+
Requires the powershell-yaml module for YAML parsing (auto-installed if missing).
15+
#>
16+
17+
function Install-YamlModuleIfMissing {
18+
param([switch]$Verbose = $false)
19+
20+
$yamlMod = Get-Module -ListAvailable -Name 'powershell-yaml' -ErrorAction SilentlyContinue
21+
if (-not $yamlMod) {
22+
Write-Verbose "[$script] Installing powershell-yaml from PSGallery..." -Verbose:$Verbose
23+
Install-Module -Name 'powershell-yaml' -Scope CurrentUser -Force -AllowClobber -AcceptLicense -ErrorAction Stop
24+
}
25+
Import-Module 'powershell-yaml' -ErrorAction Stop -Verbose:$false
26+
}
27+
28+
function Import-SnippetsModules {
29+
param([switch]$Verbose = $false)
30+
31+
# Determine YAML config path
32+
$yamlPath = $env:SnippetsModulesYaml
33+
if (-not $yamlPath -or -not (Test-Path $yamlPath)) {
34+
$yamlPath = Join-Path $env:Snippets -ChildPath 'modules.yml'
35+
}
36+
37+
if (-not (Test-Path $yamlPath)) {
38+
Write-Verbose "[$script] No modules.yml found at [$yamlPath]. Skipping module auto-load." -Verbose:$Verbose
39+
return "No modules.yml found."
40+
}
41+
42+
Write-Verbose "[$script] Loading module definitions from [$yamlPath]" -Verbose:$Verbose
43+
44+
# Ensure powershell-yaml is available
45+
Install-YamlModuleIfMissing -Verbose:$Verbose
46+
47+
# Parse YAML
48+
$yamlContent = Get-Content -Path $yamlPath -Raw -ErrorAction Stop
49+
$config = ConvertFrom-Yaml $yamlContent -ErrorAction Stop
50+
51+
if (-not $config -or -not $config.modules) {
52+
Write-Verbose "[$script] modules.yml is empty or has no 'modules' key." -Verbose:$Verbose
53+
return "No modules defined."
54+
}
55+
56+
$loaded = 0
57+
$failed = 0
58+
59+
foreach ($entry in $config.modules) {
60+
$moduleName = $entry.name
61+
$moduleVersion = $entry.version
62+
$moduleSource = if ($entry.source) { $entry.source } else { 'PSGallery' }
63+
$moduleRequired = if ($null -ne $entry.required) { $entry.required } else { $true }
64+
$moduleParams = $entry.parameters
65+
66+
if (-not $moduleName) {
67+
Write-Verbose "[$script] Skipping entry with no 'name' field." -Verbose:$Verbose
68+
continue
69+
}
70+
71+
# Skip powershell-yaml since we already loaded it above
72+
if ($moduleName -ieq 'powershell-yaml') {
73+
$loaded++
74+
continue
75+
}
76+
77+
try {
78+
Write-Verbose "[$script] Processing module [$moduleName]..." -Verbose:$Verbose
79+
80+
# Check if already imported
81+
$existing = Get-Module -Name $moduleName -ErrorAction SilentlyContinue
82+
if ($existing) {
83+
if (-not $moduleVersion -or $existing.Version -ge [Version]$moduleVersion) {
84+
Write-Verbose "[$script] Module [$moduleName] already imported." -Verbose:$Verbose
85+
$loaded++
86+
continue
87+
}
88+
}
89+
90+
# Check if installed locally
91+
$installed = Get-Module -ListAvailable -Name $moduleName -ErrorAction SilentlyContinue
92+
93+
$needsInstall = $false
94+
if (-not $installed) {
95+
$needsInstall = $true
96+
}
97+
elseif ($moduleVersion) {
98+
$bestVersion = ($installed | Sort-Object Version -Descending | Select-Object -First 1).Version
99+
if ($bestVersion -lt [Version]$moduleVersion) {
100+
$needsInstall = $true
101+
}
102+
}
103+
104+
# Install if needed
105+
if ($needsInstall) {
106+
if ($moduleSource -ine 'PSGallery' -and (Test-Path $moduleSource)) {
107+
Write-Verbose "[$script] Importing [$moduleName] from path [$moduleSource]" -Verbose:$Verbose
108+
}
109+
else {
110+
Write-Verbose "[$script] Installing [$moduleName] from PSGallery..." -Verbose:$Verbose
111+
$installParams = @{
112+
Name = $moduleName
113+
Scope = 'CurrentUser'
114+
Force = $true
115+
AllowClobber = $true
116+
AcceptLicense = $true
117+
ErrorAction = 'Stop'
118+
}
119+
120+
if ($moduleVersion) {
121+
$installParams['MinimumVersion'] = $moduleVersion
122+
}
123+
124+
Install-Module @installParams
125+
}
126+
}
127+
128+
# Import the module
129+
$importParams = @{
130+
Name = if ($moduleSource -ine 'PSGallery' -and (Test-Path $moduleSource)) { $moduleSource } else { $moduleName }
131+
ErrorAction = 'Stop'
132+
Verbose = $false
133+
}
134+
135+
if ($moduleVersion -and $moduleSource -ieq 'PSGallery') {
136+
$importParams['MinimumVersion'] = $moduleVersion
137+
}
138+
139+
if ($moduleParams) {
140+
$importParams['ArgumentList'] = $moduleParams
141+
}
142+
143+
Import-Module @importParams
144+
Write-Verbose "[$script] Loaded [$moduleName] successfully." -Verbose:$Verbose
145+
$loaded++
146+
}
147+
catch {
148+
$failed++
149+
if ($moduleRequired) {
150+
Write-Error "[$script] Failed to load required module [$moduleName]: $_"
151+
}
152+
else {
153+
Write-Verbose "[$script] Optional module [$moduleName] failed to load: $_" -Verbose:$Verbose
154+
}
155+
}
156+
}
157+
158+
return "Module auto-load complete: $loaded loaded, $failed failed."
159+
}
160+
161+
try {
162+
$result = Import-SnippetsModules -Verbose:$Verbose
163+
Write-Verbose "[$script] $result" -Verbose:$Verbose
164+
return $result
165+
}
166+
catch {
167+
Write-Error "[$script] Module auto-loader error: $_"
168+
}
169+
finally {
170+
Write-Verbose "[$script] Leaving..." -Verbose:$Verbose
171+
$Verbose = $VerboseSwitch
172+
}

modules.yml

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
# Snippets Module Auto-Loader Configuration
2+
# Modules listed here are automatically installed (if missing) and imported during profile initialization.
3+
#
4+
# Schema:
5+
# modules:
6+
# - name: <string> # Required. Module name.
7+
# version: "<string>" # Optional. Minimum required version (e.g. "1.0.0").
8+
# source: <string> # Optional. "PSGallery" (default) or a file path to a .psd1/.psm1.
9+
# required: <bool> # Optional. Default true. If false, load failure is non-fatal.
10+
# parameters: [] # Optional. Arguments passed to Import-Module -ArgumentList.
11+
#
12+
# Override this file's location by setting $env:SnippetsModulesYaml to an absolute path.
13+
14+
modules:
15+
- name: powershell-yaml
16+
required: true
17+
# - name: posh-git
18+
# required: false
19+
# - name: Terminal-Icons
20+
# required: false

0 commit comments

Comments
 (0)