PSubShell.ps1
#Requires -PSEdition Core <#PSScriptInfo .VERSION 0.1.0 .GUID dbd31207-825d-4cdc-8e52-7c575e0ca5d9 .AUTHOR wekem .COMPANYNAME .COPYRIGHT .TAGS .LICENSEURI .PROJECTURI .ICONURI .EXTERNALMODULEDEPENDENCIES .REQUIREDSCRIPTS .EXTERNALSCRIPTDEPENDENCIES .RELEASENOTES .PRIVATEDATA #> <# .DESCRIPTION Creates a sub shell configured to use locally installed scripts, modules and packages. #> [CmdletBinding(DefaultParameterSetName = 'EnterShell')] Param( [Parameter(ParameterSetName = 'EnterShell', Position = 0)] [object]$Command, [Parameter(ParameterSetName = 'Initialize')] [switch]$Initialize, [Parameter(ParameterSetName = 'EnterShell')] [switch]$NoProfile, [Parameter(ParameterSetName = 'EnterShell')] [switch]$NoExit, [Parameter(ParameterSetName = 'Update')] [switch]$Update, [Parameter(ParameterSetName = 'AddModule')] [switch]$AddModule, [Parameter(ParameterSetName = 'RemoveModule')] [switch]$RemoveModule, [Parameter(ParameterSetName = 'AddScript')] [switch]$AddScript, [Parameter(ParameterSetName = 'RemoveScript')] [switch]$RemoveScript, [Parameter(ParameterSetName = 'AddPackage')] [switch]$AddPackage, [Parameter(ParameterSetName = 'RemovePackage')] [switch]$RemovePackage, [Parameter(ParameterSetName = 'AddModule', Position = 1, Mandatory)] [Parameter(ParameterSetName = 'RemoveModule', Position = 1, Mandatory)] [Parameter(ParameterSetName = 'AddScript', Position = 1, Mandatory)] [Parameter(ParameterSetName = 'RemoveScript', Position = 1, Mandatory)] [Parameter(ParameterSetName = 'AddPackage', Position = 1, Mandatory)] [Parameter(ParameterSetName = 'RemovePackage', Position = 1, Mandatory)] [string]$Name, [Parameter(ParameterSetName = 'AddModule')] [Parameter(ParameterSetName = 'AddScript')] [Parameter(ParameterSetName = 'AddPackage')] [string]$MinimumVersion, [Parameter(ParameterSetName = 'AddModule')] [Parameter(ParameterSetName = 'AddScript')] [Parameter(ParameterSetName = 'AddPackage')] [string]$MaximumVersion, [Parameter(ParameterSetName = 'AddModule')] [Parameter(ParameterSetName = 'AddScript')] [Parameter(ParameterSetName = 'AddPackage')] [string]$RequiredVersion, [Parameter(ParameterSetName = 'AddModule')] [Parameter(ParameterSetName = 'AddScript')] [Parameter(ParameterSetName = 'AddPackage')] [string[]]$Repository, [Parameter(ParameterSetName = 'AddModule')] [Parameter(ParameterSetName = 'AddPackage')] [Parameter(ParameterSetName = 'AddScript')] [switch]$AllowPrerelease, [Parameter(ParameterSetName = 'AddModule')] [Parameter(ParameterSetName = 'AddScript')] [Parameter(ParameterSetName = 'AddPackage')] [switch]$AcceptLicense, [Parameter(ParameterSetName = 'CreateBuildScript')] [switch]$CreateBuildScript, [Parameter(ParameterSetName = 'CreateBuildScript', Position = 1)] [string]$Path = (Join-Path $PSScriptRoot 'build.ps1'), [Parameter(ParameterSetName = 'CreateBuildScript')] [switch]$Force ) $PSubShellPath = Join-Path $PSScriptRoot '.psubshell' $PSubShellLockFile = Join-Path $PSScriptRoot '.psubshell.lock.json' if (Test-Path $PSubShellLockFile) { $PSubShellVersions = Get-Content $PSubShellLockFile -ErrorAction SilentlyContinue | ConvertFrom-Json -AsHashtable } else { $PSubShellVersions = @{} } function *GetParameters([hashtable]$Given, [string[]]$Include, [string[]]$Exclude) { $parms = @{} foreach ($kv in $Given.GetEnumerator()) { if ((-not $Include) -or ($Include -contains $kv.Key)) { if ((-not $Exclude) -or (-not ($Exclude -contains $kv.Key))) { $parms.Add($kv.Key, $kv.Value) } } } $parms } switch ($PSCmdlet.ParameterSetName) { 'EnterShell' { if ($PSubShellInstance -eq $PSubShellPath) { Write-Error 'Cannot enter PSubShell from within PSubShell.' return } $pssubshellscript = Join-Path ([IO.Path]::GetTempPath()) ((Get-Item .).Name + '.ps1') Set-Content $pssubshellscript @" Set-Variable -Name Old -Value `$ErrorActionPreference -Scope Global `$ErrorActionPreference = 'SilentlyContinue' $PSCommandPath -Initialize $Command `$PSubShell='$PSubShellPath' `$ErrorActionPreference = `$Old Remove-Variable -Name Old -Scope Global "@ Invoke-Expression "pwsh -Interactive $(((-not $Command) -or $NoExit) ? '-NoExit' : '') $($NoProfile ? '-NoProfile' : '') -File $pssubshellscript" } 'Initialize' { Write-Host 'Initializing PSubShell...' $modules = $PSubShellVersions.modules ?? @{} foreach ($kv in $modules.GetEnumerator()) { $name = $kv.Key $version = $kv.Value $parms = *GetParameters $version -Exclude 'Version', 'MinimumVersion', 'MaximumVersion' $parms.RequiredVersion = $version.Version $module = Get-Module -Name $name -ListAvailable -ErrorAction SilentlyContinue | Where-Object { $_.Version -eq $version.Version } if (-not $module) { $module = Join-Path $PSubShellPath $name -AdditionalChildPath $version.Version, "$name.psd1" if (-not (Test-Path $module)) { New-Item -ItemType Directory -Path $PSubShellPath -Force | Out-Null Save-Module $name -Path $PSubShellPath -RequiredVersion $version.Version @parms } } Import-Module $module -Force } $scripts = $PSubShellVersions.scripts ?? @{} foreach ($kv in $scripts.GetEnumerator()) { $name = $kv.Key $version = $kv.Value $script = Join-Path $PSubShellPath "$name.ps1" $info = Test-ScriptFileInfo -Path $script -ErrorAction SilentlyContinue if ((-not $info) -or ($info.Version -ne $version.Version)) { $parms = *GetParameters $version -Exclude 'Version', 'MinimumVersion', 'MaximumVersion' $parms.RequiredVersion = $version.Version Save-Script $name -Path $PSubShellPath -Force @parms } Set-Alias -Name $name -Value $script -Scope Global Write-Host "$(Get-Alias -Name $name)" } $packages = $PSubShellVersions.packages ?? @{} foreach ($kv in $packages.GetEnumerator()) { $name = $kv.Key $version = $kv.Value $package = Join-Path $PSubShellPath $name -AdditionalChildPath $version.Version if (-not (Test-Path $package)) { $parms = *GetParameters $version -Exclude 'Version', 'MinimumVersion', 'MaximumVersion' $parms.RequiredVersion = $version.Version Save-Package $name -Path $PSubShellPath -RequiredVersion $version.Version @parms | Out-Null $nupkg = (Join-Path $PSubShellPath "$name.$($version.Version).nupkg") Expand-Archive $nupkg $package -Force | Out-Null Remove-Item $nupkg -Force } $tools = Join-Path $package 'tools' if (Test-Path $tools) { $env:PATH = "$tools$([IO.Path]::PathSeparator)$env:PATH" } } } 'AddModule' { $parms = *GetParameters $PSBoundParameters -Exclude 'AddModule' $module = Find-Module -ErrorAction Stop @parms $parms.Version = $module.Version $parms.Remove('Name') if (-not $PSubShellVersions.modules) { $PSubShellVersions.modules = @{} } $PSubShellVersions.modules.$Name = $parms ConvertTo-Json $PSubShellVersions | Set-Content $PSubShellLockFile } 'RemoveModule' { $PSubShellVersions.modules.Remove($Name) ConvertTo-Json $PSubShellVersions | Set-Content $PSubShellLockFile } 'AddScript' { $parms = *GetParameters $PSBoundParameters -Exclude 'AddScript' $script = Find-Script -ErrorAction Stop @parms $parms.Version = $script.Version $parms.Remove('Name') if (-not $PSubShellVersions.scripts) { $PSubShellVersions.scripts = @{} } $PSubShellVersions.scripts.$Name = $parms ConvertTo-Json $PSubShellVersions | Set-Content $PSubShellLockFile } 'RemoveScript' { $PSubShellVersions.scripts.Remove($Name) ConvertTo-Json $PSubShellVersions | Set-Content $PSubShellLockFile } 'AddPackage' { $parms = *GetParameters $PSBoundParameters -Exclude 'AddPackage' $package = Find-Package -ErrorAction Stop @parms $parms.Version = $package.Version $parms.Remove('Name') if (-not $PSubShellVersions.packages) { $PSubShellVersions.packages = @{} } $PSubShellVersions.packages.$Name = $parms ConvertTo-Json $PSubShellVersions | Set-Content $PSubShellLockFile } 'RemovePackage' { $PSubShellVersions.packages.Remove($Name) ConvertTo-Json $PSubShellVersions | Set-Content $PSubShellLockFile } 'Update' { $modules = $PSubShellVersions.modules ?? @{} foreach ($kv in $modules.GetEnumerator()) { $name = $kv.Key $version = $kv.Value $parms = *GetParameters $version -Exclude 'Version' $module = Find-Module $name -ErrorAction SilentlyContinue @parms $PSubShellVersions.modules.$name.Version = $module.Version } $scripts = $PSubShellVersions.scripts ?? @{} foreach ($kv in $scripts.GetEnumerator()) { $name = $kv.Key $version = $kv.Value $parms = *GetParameters $version -Exclude 'Version' $script = Find-Script $name -ErrorAction SilentlyContinue @parms $PSubShellVersions.scripts.$name.Version = $script.Version } $packages = $PSubShellVersions.packages ?? @{} foreach ($kv in $packages.GetEnumerator()) { $name = $kv.Key $version = $kv.Value $parms = *GetParameters $version -Exclude 'Version' $package = Find-Package $name -ErrorAction SilentlyContinue @parms $PSubShellVersions.packages.$name.Version = $package.Version } ConvertTo-Json $PSubShellVersions | Set-Content $PSubShellLockFile } 'CreateBuildScript' { if ((-not $Force) -and (Test-Path $Path)) { Write-Error "File '$Path' already exists. Use -Force to overwrite." return } Set-Content -Path $Path -Value @" param( [Parameter(Position = 0)] [ValidateSet('?', '.')] [string[]]$Tasks ) if ($MyInvocation.ScriptName -notlike '*Invoke-Build.ps1') { $c = "Invoke-Build $($Tasks -join ',') -File $($MyInvocation.MyCommand.Path)" foreach ($kv in $PSBoundParameters) { $c += " $($kv.Key) $($kv.Value)" } ./PSubShell.ps1 -NoProfile -Command $c return } task . { Write-Build Green 'Hello world!' } "@ } } |