processing-bootstrap/start.ps1
2025-02-25 00:02:36 +00:00

272 lines
No EOL
6 KiB
PowerShell

<#
.Synopsis
Simple folder based script execution environment.
.DESCRIPTION
Simple folder based script execution environment designed for modularity.
Suitable for the automation tasks as a better alternative to a Behemoth-class monolithic scripts.
Script execution order is a simple alphanumeric sort by the file name.
.EXAMPLE
./start.ps1
# execute in the current directory
.EXAMPLE
./start.ps1 -MainScriptRoot /mnt/tukayyid
# expect everything are located at the specified path
.EXAMPLE
./start.ps1 -PSModule /data/ironhold/psmodule
# load the PS modules form the specified path
#>
[CmdletBinding()]
param (
# Path where the scripts and data for the execution are located
$Root = $PSScriptRoot,
# Configuration storage path
$Config,
# Data storage path
$Data,
# .NET assemblies directory
$Lib,
# PowerShell modules path (would be added to PSModulesPath for the auto-magik)
$PSModule,
# Custom arguments
[Parameter(
ValueFromRemainingArguments=$true
)]
$CustomArgs
)
# this is a global variable which would be accessbile anywhere in the session
$global:BS = @{
Path = @{
PSScriptRoot = $PSScriptRoot
Root = $null
}
Config = @{
}
}
# workaround for the interactive script launch (ie no defined $Root)
if ($PSBoundParameters['Root']) {
$global:BS.Path.Root = $PSBoundParameters['Root']
}
else {
$global:BS.Path.Root = $PSScriptRoot
}
# add any custom args to the configuration var
if ($PSBoundParameters['CustomArgs']) {
$global:BS.Config.Add('CustomArgs', $PSBoundParameters['CustomArgs'])
}
# auto configure the main paths
# list of the variables
@(
'config'
'data'
'lib'
'psmodule'
) | % {
if ($value = $PSBoundParameters[$_]) {
# if this path was supplied at the start then add it as is
$global:BS.Path.Add($_, $value)
}
else {
# otherwise assume it's under the main script root path
# no attemps are made for the path validation, it's upon you
$global:BS.Path.Add($_, (Join-Path $BS.Path.Root $_))
}
}
# Adding ./psmodule to $PSModulePath allows the module auto loading without explicitly
# calling Import-Module with the absolute or relative paths
# Note, if you prefer for the bundled modules not to be loaded before the ones available in the system,
# swap '{0}{1}{2}' to '{2}{1}{0}'
if (Test-Path $BS.Path.PSModule) {
# if the path exists
if ($Env:PSModulePath -match [regex]::Escape($BS.Path.PSModule)) {
# nothing to do, the path was already in PSModulePath
}
else {
$Env:PSModulePath = '{0}{1}{2}' -f $BS.Path.PSModule, [System.IO.Path]::PathSeparator, $Env:PSModulePath
}
}
# load any configiguration to the global variable
if (Test-Path $BS.Path.config) {
foreach ($file in (gci $BS.Path.config -Filter *.json)) {
try {
$json = Get-Content $file.fullname | ConvertFrom-json
$BS.config.add($file.basename, $json)
}
catch {
write-warning ('Failed to load configuration data from the file "{0}"' -f $file.fullname)
}
}
}
# auto load .NET assemblies
function Register-Assembly {
<#
.Synopsis
Load .NET assembly
.DESCRIPTION
Load .NET assembly or assemblies
.EXAMPLE
Register-Assembly
Load any .dll in the current path
.EXAMPLE
Register-Assembly -Path ./lib/nestandard99/kewllib.dll
Load a dll from the explicit path
#>
[CmdletBinding()]
[Alias('Register-Assemblies')]
Param
(
# Path to *.dll
[Alias('AssemblyPath')]
$Path = $PWD
)
function loadassembly {
param ($pathToDll)
try {
[System.Reflection.Assembly]::LoadFile($pathToDll)
Write-Verbose ("Loaded assembly: {0}" -f $pathToDll)
}
catch {
Write-Error $_
}
}
try {
$thisPath = Get-Item $Path -ErrorAction Stop
if ($thisPath.PSIsContainer) {
Get-ChildItem -Filter '*.dll' | % {
loadassembly $_.FullName
}
}
# this whould be triggered both for when PSIsContainer is false (ie this is a file) and when this property doesn't exists
else {
loadassembly $thisPath.FullName
}
}
Catch {
Write-Error $_
}
} # end function Register-Assembly
# $PSVersionTable.PSVersion
# Windows PowerShell 5.x
if ($PSVersionTable.PSVersion.Major -eq 5) {
$subFolder = 'net4'
}
# PowerShell 6
elseif ($PSVersionTable.PSVersion.Major -eq 6) {
$subFolder = 'netstandard2'
}
# PowerShell 7.0
elseif ($PSVersionTable.PSVersion -lt [version]'7.2.0') {
$subFolder = 'netstandard2'
}
# PowerShell 7.2
elseif ($PSVersionTable.PSVersion -lt [version]'7.3.0') {
$subFolder = 'net6'
}
# PowerShell 7.3+
else {
$subFolder = 'net7'
}
$pathToLoadFrom = Join-Path $global:BS.Path.lib $subFolder
if (Test-Path $pathToLoadFrom) {
Write-Verbose ("Loading .NET assemblies from the path: {0}" -f $pathToLoadFrom)
Register-Assembly -AssemblyPath $pathToLoadFrom
}
Remove-Variable subFolder, pathToLoadFrom -ErrorAction SilentlyContinue
# end load .NET assemblies
# use only dirs named like 01-Something etc
$dirsToProcess = gci -Path $BS.Path.Root -Directory | ? Name -Match '\d+-\w+' | Sort-Object Name
foreach ($thisDir in $dirsToProcess) {
$scriptFiles = gci $thisDir.FullName -Filter '*.ps1' | Sort-Object Name
foreach ($thisFile in $scriptFiles) {
'Processing {0}\{1}' -f $thisDir.Name, $thisFile.Name | Write-Verbose
. $thisFile.FullName
}
}