272 lines
No EOL
6 KiB
PowerShell
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
|
|
|
|
}
|
|
|
|
} |