pipEnv.psm1
#!/usr/bin/env pwsh using namespace System.IO using namespace System.Collections.Generic using namespace System.Management.Automation #Requires -RunAsAdministrator #Requires -Modules clihelper.env, cliHelper.core #Requires -Psedition Core #region Classes enum EnvState { Inactive Active } enum PackageManager { pip poetry } class DependencyInfo { [string]$Name [string]$Version } class InstallException : Exception { InstallException() {} InstallException([string]$message) : base($message) {} InstallException([string]$message, [Exception]$innerException) : base($message, $innerException) {} } class InstallFailedException : InstallException { InstallFailedException() {} InstallFailedException([string]$message) : base($message) {} InstallFailedException([string]$message, [Exception]$innerException) : base($message, $innerException) {} } class Requirement { [string] $Name [version] $Version [string] $Description [string] $InstallScript Requirement() {} Requirement([array]$arr) { $this.Name = $arr[0] $this.Version = $arr.Where({ $_ -is [version] })[0] $this.Description = $arr.Where({ $_ -is [string] -and $_ -ne $this.Name })[0] $__sc = $arr.Where({ $_ -is [scriptblock] })[0] $this.InstallScript = ($null -ne $__sc) ? $__sc.ToString() : $arr[-1] } Requirement([string]$Name, [scriptblock]$InstallScript) { $this.Name = $Name $this.InstallScript = $InstallScript.ToString() } Requirement([string]$Name, [string]$Description, [scriptblock]$InstallScript) { $this.Name = $Name $this.Description = $Description $this.InstallScript = $InstallScript.ToString() } [bool] IsInstalled() { try { Get-Command $this.Name -Type Application return $? } catch [CommandNotFoundException] { return $false } catch { throw [InstallException]::new("Failed to check if $($this.Name) is installed", $_.Exception) } } [bool] Resolve() { return $this.Resolve($false, $false) } [bool] Resolve([switch]$Force, [switch]$What_If) { $is_resolved = $true if (!$this.IsInstalled() -or $Force.IsPresent) { Write-Console "[Resolve requrement] $($this.Name) " -f Green -NoNewLine if ($this.Description) { Write-Console "($($this.Description)) " -f BlueViolet -NoNewLine } Write-Console "$($this.Version) " -f Green if ($What_If.IsPresent) { Write-Console "Would install: $($this.Name)" -f Yellow } else { [ScriptBlock]::Create("$($this.InstallScript)").Invoke() } $is_resolved = $? } return $is_resolved } } Class InstallRequirements { [Requirement[]] $list [bool] $resolved = $false [string] $jsonPath = [IO.Path]::Combine($(Resolve-Path .).Path, 'requirements.json') InstallRequirements() {} InstallRequirements([array]$list) { $this.list = $list } InstallRequirements([List[array]]$list) { $this.list = $list.ToArray() } InstallRequirements([hashtable]$Map) { $Map.Keys | ForEach-Object { $Map[$_] ? ($this.$_ = $Map[$_]) : $null } } [void] Resolve() { $this.Resolve($false, $false) } [void] Resolve([switch]$Force, [switch]$What_If) { $res = $true; $this.list.ForEach({ $res = $res -and $_.Resolve($Force, $What_If) }) $this.resolved = $res } [void] Import() { $this.Import($this.JsonPath, $false) } [void] Import([switch]$throwOnFail) { $this.Import($this.JsonPath, $throwOnFail) } [void] Import([string]$JsonPath, [switch]$throwOnFail) { if ([IO.File]::Exists($JsonPath)) { $this.list = Get-Content $JsonPath | ConvertFrom-Json }; return if ($throwOnFail) { throw [FileNotFoundException]::new("Requirement json file not found: $JsonPath") } } [void] Export() { $this.Export($this.JsonPath) } [void] Export([string]$JsonPath) { $this.list | ConvertTo-Json -Depth 1 -Verbose:$false | Out-File $JsonPath } [string] ToString() { return $this | ConvertTo-Json } } class EnvManager { [validateNotNullOrEmpty()][string]$BinPath [string]$CurrentEnvironment [Dictionary[string, string]]$Environments = @{} static [validateNotNullOrEmpty()][InstallRequirements]$req EnvManager() { $this.LoadEnvironments() } EnvManager([string]$Path) { $this.BinPath = $Path $this.LoadEnvironments() } [Object[]] Run([string[]]$commands) { $res = @() foreach ($c in $commands) { $n = [venv]::manager.BinPath | Split-Path -Leaf if ($n -eq "pipenv" -and $c -eq "shell") { $e = [venv]::Create([venv]::Config.ProjectPath); $s = [IO.Path]::Combine($e.Path, 'bin', 'activate.ps1') $(![string]::IsNullOrWhiteSpace($e) -and ![string]::IsNullOrWhiteSpace($s)) ? $( return &$s ) : $( throw "Failed to get activation script") } $res += & ($this.BinPath) $commands } return $res } [bool] CreateEnvironment([string]$Name, [string]$Path) { try { if (!(Test-Path $Path)) { New-Item -ItemType Directory -Path $Path -Force | Out-Null } & "$($this.BinPath)\python.exe" -m venv "$Path\$Name" $this.Environments[$Name] = $Path $this.SaveEnvironments() return $true } catch { Write-Error "Failed to create environment: $_" return $false } } [bool] ActivateEnvironment([string]$Name) { try { if (!$this.Environments.ContainsKey($Name)) { throw "Environment '$Name' does not exist." } $envPath = "$($this.Environments[$Name])\$Name\Scripts\Activate.ps1" if (Test-Path $envPath) { & $envPath $this.CurrentEnvironment = $Name return $true } else { throw "Activation script not found." } } catch { Write-Error "Failed to activate environment: $_" return $false } } [bool] DeactivateEnvironment() { try { if ($null -eq $this.CurrentEnvironment) { throw "No environment is currently active." } & "$($this.BinPath)\deactivate.ps1" $this.CurrentEnvironment = $null return $true } catch { Write-Error "Failed to deactivate environment: $_" return $false } } [bool] InstallPackage([string]$Package, [string]$Version) { try { if ($null -eq $this.CurrentEnvironment) { throw "No environment is currently active." } if ($Version) { & "$($this.Environments[$this.CurrentEnvironment])\$($this.CurrentEnvironment)\Scripts\pip.exe" install "$Package==$Version" } else { & "$($this.Environments[$this.CurrentEnvironment])\$($this.CurrentEnvironment)\Scripts\pip.exe" install $Package } return $true } catch { Write-Error "Failed to install package: $_" return $false } } [List[string]] ListEnvironments() { return [List[string]]$this.Environments.Keys } [bool] DeleteEnvironment([string]$Name) { try { if (!$this.Environments.ContainsKey($Name)) { throw "Environment '$Name' does not exist." } Remove-Item -Path "$($this.Environments[$Name])\$Name" -Recurse -Force $this.Environments.Remove($Name) $this.SaveEnvironments() return $true } catch { Write-Error "Failed to delete environment: $_" return $false } } ### Medium Pain Points (Helpful Features) [List[Hashtable]] ListPackages([string]$Environment) { try { if (!$this.Environments.ContainsKey($Environment)) { throw "Environment '$Environment' does not exist." } $packages = & "$($this.Environments[$Environment])\$Environment\Scripts\pip.exe" list --format=json return $packages | ConvertFrom-Json | ForEach-Object { @{ Name = $_.name Version = $_.version } } } catch { Write-Error "Failed to list packages: $_" return @() } } [bool] CloneEnvironment([string]$Source, [string]$Destination) { try { if (!$this.Environments.ContainsKey($Source)) { throw "Source environment '$Source' does not exist." } $sourcePath = $this.Environments[$Source] $destinationPath = "$sourcePath\..\$Destination" Copy-Item -Path "$sourcePath" -Destination $destinationPath -Recurse $this.Environments[$Destination] = $destinationPath $this.SaveEnvironments() return $true } catch { Write-Error "Failed to clone environment: $_" return $false } } [bool] UpdatePackage([string]$Package, [string]$Version) { try { if ($null -eq $this.CurrentEnvironment) { throw "No environment is currently active." } if ($Version) { & "$($this.Environments[$this.CurrentEnvironment])\$($this.CurrentEnvironment)\Scripts\pip.exe" install --upgrade "$Package==$Version" } else { & "$($this.Environments[$this.CurrentEnvironment])\$($this.CurrentEnvironment)\Scripts\pip.exe" install --upgrade $Package } return $true } catch { Write-Error "Failed to update package: $_" return $false } } [bool] ExportEnvironment([string]$Name, [string]$OutputFile) { try { if (!$this.Environments.ContainsKey($Name)) { throw "Environment '$Name' does not exist." } & "$($this.Environments[$Name])\$Name\Scripts\pip.exe" freeze > $OutputFile return $true } catch { Write-Error "Failed to export environment: $_" return $false } } [bool] ImportEnvironment([string]$InputFile) { try { $packages = Get-Content $InputFile foreach ($package in $packages) { $this.InstallPackage($package, $null) } return $true } catch { Write-Error "Failed to import environment: $_" return $false } } ### Low Pain Points (Nice-to-Have Features) [bool] CheckCompatibility([string]$Package, [string]$Version) { try { $result = & "$($this.BinPath)\pip.exe" check "$Package==$Version" return ($result -eq "No broken dependencies") } catch { Write-Error "Failed to check compatibility: $_" return $false } } [Hashtable] GetDetails([string]$Name) { try { if (!$this.Environments.ContainsKey($Name)) { throw "Environment '$Name' does not exist." } $details = @{ Name = $Name Path = $this.Environments[$Name] Packages = $this.ListPackages($Name) Active = ($this.CurrentEnvironment -eq $Name) } return $details } catch { Write-Error "Failed to get details: $_" return @{} } } [bool] SyncWithGlobal([List[string]]$Exclusions) { try { if ($null -eq $this.CurrentEnvironment) { throw "No environment is currently active." } $globalPackages = & "$($this.BinPath)\pip.exe" list --format=json | ConvertFrom-Json | ForEach-Object { $_.name } foreach ($package in $globalPackages) { if (!($Exclusions -contains $package)) { $this.InstallPackage($package, $null) } } return $true } catch { Write-Error "Failed to sync with global: $_" return $false } } [EnvState] CheckStatus([string]$Name) { if (!$this.Environments.ContainsKey($Name)) { throw "Environment '$Name' does not exist." } $status = '' try { $status = switch ($true) { ($this.CurrentEnvironment -eq $Name) { "active"; break } default { "inactive" } } } catch { throw "Failed to check status: $_" } return $status } # Helper Methods # LoadEnvironments [void] LoadEnvironments() { # Load environments from a configuration file or registry # This is a placeholder for actual implementation # For example, reading from a JSON file # $config = Get-Content -Path "EnvManagerConfig.json" | ConvertFrom-Json # $this.Environments = $config.Environments } # SaveEnvironments [void] SaveEnvironments() { # Save environments to a configuration file or registry # This is a placeholder for actual implementation # For example, writing to a JSON file # $config = @{ Environments = $this.Environments } # $config | ConvertTo-Json | Set-Content -Path "EnvManagerConfig.json" } } # .SYNOPSIS # Python virtual environment manager class pipEnv : EnvManager { static [venv] $env # [venv]::Create((Resolve-Path .).Path) static [PsRecord]$data = [pipEnv].data #starts $null until any instance is created pipEnv() { $this.__init__() } static [pipEnv] Create() { return [pipEnv]::new() } hidden [void] __init__() { [pipEnv].PsObject.Properties.Add([PsScriptproperty]::new('CONSTANTS', { return [scriptblock]::Create("@{ # Add your constant primitives here: validversionregex = '^(0|[1-9]\d*)(\.(0|[1-9]\d*)){0,3}$' }").InvokeReturnAsIs() }, { throw [SetValueException]::new("CONSTANTS is read-only") } ) ) [pipEnv].PsObject.Properties.Add([PsNoteproperty]::new('session', $([ref]$this).Value)) $1st_run = $null -eq [pipEnv]::data if ($1st_run) { [pipEnv]::req = [pipEnv]::get_default_requirements(); $r = [pipEnv]::req; !$r.resolved ? $r.Resolve() : $null [pipEnv].PsObject.Properties.Add([PsNoteproperty]::new('data', [PsRecord]::new())) [pipEnv].data.set(@{ SelectedVersion = [version]$(python --version).Split(" ").Where({ $_ -match [pipEnv].CONSTANTS.validversionregex })[0] Home = [pipEnv]::get_work_home() Os = [xcrypt]::Get_Host_Os() } ) } [pipEnv].data.PsObject.Properties.Add([PsScriptproperty]::new('PythonVersions', { return [pipEnv]::get_python_versions() }, { throw [SetValueException]::new("PythonVersions is read-only") })) [pipEnv].session.BinPath = (Get-Command pipenv -Type Application).Source if ($1st_run) { [pipEnv]::data = ([ref][pipEnv].data).Value } } [void] Install() { & ($this.BinPath) install -q } [void] Install([string]$package) { & ($this.BinPath) install -q $package } [void] Upgrade() { pip install --user --upgrade pipenv } [void] Remove() { & ($this.BinPath) --rm } static hidden [version[]] get_python_versions() { return ((pyenv versions).Split("`n").Trim() | Select-Object @{l = "version"; e = { $l = $_; if ($l.StartsWith("*")) { $l = $l.Substring(1).TrimStart().Split(' ')[0] }; $m = $l -match [pipEnv].CONSTANTS.validversionregex; $m ? $l : "not-a-version" } } | Where-Object { $_.version -ne "not-a-version" }).version } static hidden [string] get_work_home() { $xdgDataHome = [Environment]::GetEnvironmentVariable("XDG_DATA_HOME", [EnvironmentVariableTarget]::User) # For Unix-like systems $whm = $xdgDataHome ? ([IO.Path]::Join($xdgDataHome, "virtualenvs")) : ([IO.Path]::Combine([Environment]::GetFolderPath("UserProfile"), ".local", "share", "virtualenvs")) $exp = [IO.Path]::Combine([Environment]::ExpandEnvironmentVariables($whm), "") $exp = [IO.Path]::GetFullPath([Environment]::ExpandEnvironmentVariables($exp)) if (![IO.Directory]::Exists($exp)) { try { New-Item -Path $exp -ItemType Directory -Force | Out-Null } catch { throw "Failed to create directory '$exp': $_" } } return $exp } static hidden [List[array]] get_default_requirements() { # Add default requirements here. each array contains ("packageName", "description", { Install_script }) return @( ("pip", "The package installer for Python", { switch ([pipEnv]::data.Os) { 'Windows' { py -m ensurepip --upgrade } default { python -m ensurepip --upgrade } } pip install --user --upgrade pip } ), ("pyenv", "Python version manager", { switch ([pipEnv]::data.Os) { 'Windows' { Write-Warning "Pyenv does not officially support Windows and does not work in Windows outside the Windows Subsystem for Linux." } default { curl -L https://github.com/pyenv/pyenv-installer/raw/master/bin/pyenv-installer | bash } } } ), ("pipenv", "Python virtualenv management tool", { pip install pipenv --user $sitepackages = python -m site --user-site $sitepackages = [pipEnv]::data.Os.Equals('Windows') ? $sitepackages.Replace('site-packages', 'Scripts') : $sitepackages # add $sitepackages to $env:PATH $env:PATH = "$env:PATH;$sitepackages" } ) ) } } # .SYNOPSIS # python virtual environment implementation class venv { [string]$Path [string]$CreatedAt [version]$PythonVersion [PackageManager]$PackageManager [Dictionary[string, DependencyInfo]]$dependencies static [PsRecord]$Config = @{ ProjectPath = (Resolve-Path .).Path SharePipcache = $False RequirementsFile = "requirements.txt" } static hidden [EnvManager]$manager = [venv]::GetEnvManager("pipEnv") hidden [string]$__name venv() {} venv([string]$dir) { [void][venv]::From([IO.DirectoryInfo]::new($dir), [ref]$this) } venv([IO.DirectoryInfo]$dir) { [void][venv]::From($dir, [ref]$this) } static [venv] Create() { return [venv]::Create([IO.DirectoryInfo]::new([venv]::Config.ProjectPath)) } static [venv] Create([string]$dir) { [ValidateNotNullOrWhiteSpace()][string]$dir = $dir return [venv]::Create([IO.DirectoryInfo]::new($dir)) } static [venv] Create([IO.DirectoryInfo]$dir) { # .NOTES # $dir.FullName can be ProjectPath or the exact path for the venv # Option1: check if the venv was already created: if (!$dir.Exists) { throw [Argumentexception]::new("Please provide a valid path!", [DirectoryNotFoundException]::new("Directory not found: $dir")) } if ([venv]::IsValid($dir.FullName)) { return [venv]::new($dir) } $_env_paths = [pipEnv]::get_work_home() | Get-ChildItem -Directory -ea Ignore if ($null -ne $_env_paths) { $reslt = [venv]::GetEnvPath($dir.FullName) $reslt = ($reslt.count -eq 0) ? $null : [venv]::Create($reslt) return $reslt } # Option2: check in the current directory $reslt = $null; $_env_paths = $dir.EnumerateDirectories("*", [SearchOption]::TopDirectoryOnly).Where({ [venv]::IsValid($_.FullName) }) if ($null -ne $_env_paths) { $reslt = switch ($_env_paths.count) { 0 { $null; break } 1 { [venv]::new($_env_paths[0].FullName); break } Default { throw [InvalidOperationException]::new("Multiple environments found for project: $($dir.BaseName)") } } } if ($null -ne $reslt) { return $reslt } Push-Location $dir.FullName; [void][venv]::SetLocalVersion() Write-Console "[pipEnv] " -f SlateBlue -NoNewLine; Write-Console "Creating env ... "-f LemonChiffon -NoNewLine; # https://pipenv.pypa.io/en/latest/virtualenv.html#virtual-environment-name $usrEnvfile = [FileInfo]::new([Path]::Combine($dir.FullName, ".env")) $name = ($dir.BaseName -as [version] -is [version]) ? ("{0}_{1}" -f $dir.Parent.BaseName, $dir.BaseName) : $dir.BaseName if (![string]::IsNullOrWhiteSpace($name)) { "PIPENV_CUSTOM_VENV_NAME=$name" >> $usrEnvfile.FullName } [venv]::Run(("install", "check")) $usrEnvfile.Exists ? ($usrEnvfile.FullName | Remove-Item -Force -ea Ignore) : $null Pop-Location; Write-Console "Done" -f Green $p = [venv]::GetEnvPath($dir.FullName) $p = [Directory]::Exists($p) ? $p : ([Path]::Combine($dir.FullName, "env")) return [venv]::new($p) } static hidden [venv] From([IO.DirectoryInfo]$dir) { return [venv]::From($dir, [ref]([venv]::new())) } static hidden [venv] From([IO.DirectoryInfo]$dir, [ref]$o) { [IO.Directory]::Exists($dir.FullName) ? ($dir | Set-ItemProperty -Name Attributes -Value ([IO.FileAttributes]::Hidden)) : $null if (![venv]::IsValid($dir.FullName)) { [InvalidOperationException]::new("Failed to create a venv Object", [Argumentexception]::new("$dir is not a valid venv folder", $dir)) | Write-Error } $o.Value.PsObject.Properties.Add([Psscriptproperty]::new('Name', { $v = [venv]::IsValid($this.Path) $has_deact_command = $null -ne (Get-Command deactivate -ea Ignore); $this.PsObject.Properties.Add([Psscriptproperty]::new('State', [scriptblock]::Create("return [EnvState][int]$([int]$($has_deact_command -and $v))"), { throw [SetValueException]::new("State is read-only") })); $this.PsObject.Properties.Add([Psscriptproperty]::new('IsValid', [scriptblock]::Create("return [IO.Path]::Exists(`$this.Path) -and [bool]$([int]$v)"), { throw [SetValueException]::new("IsValid is read-only") })); return ($v ? $this.__name : [string]::Empty); }, { Param([string]$n) [string]::IsNullOrWhiteSpace("$($this.__name) ".Trim()) ? ($this.__name = $n) : $null } ) ) $o.Value.Name = $dir.Name; $o.Value.Path = $dir.FullName; #the exact path for the venv $o.Value.CreatedAt = [Datetime]::Now.ToString(); $o.Value.PythonVersion = [pipEnv].data.SelectedVersion; if (![string]::IsNullOrWhiteSpace($o.Value.Name) -and $o.Value.IsValid) { $venvconfig = Read-Env -File ([IO.Path]::Combine($dir.FullName, 'pyvenv.cfg')); $c = @{}; $venvconfig.Name.ForEach({ $n = $_; $c[$n] = $venvconfig.Where({ $_.Name -eq $n }).value }); [venv]::Config.Set($c) } return $o.Value } static [Object[]] Run([string[]]$commands) { return [venv]::manager.Run($commands) } static [EnvManager] GetEnvManager([string]$name) { $m = switch ($name) { "pipenv" { [pipEnv].session ? ([pipEnv].session) : ([pipEnv]::Create()) } Default { throw [Argumentexception]::new("Unknown environment manager: $name") } } return $m } static [Object[]] SetLocalVersion() { return [venv]::SetLocalVersion([Path]::Combine((Resolve-Path .).Path, ".python-version")) } static [Object[]] SetLocalVersion([string]$str = "versionfile_or_version") { [ValidateNotNullOrWhiteSpace()][string]$str = $str; $res = $null; $ver = switch ($true) { ([IO.File]::Exists($str)) { $ver_in_file = Get-Content $str; $localver = pyenv local ($localver -ne $ver_in_file) ? $ver_in_file : $null break } ($str -as [version] -is [version]) { $str; break } Default { $null } } if ($null -ne $ver) { $sc = [scriptblock]::Create("pyenv install $ver") Write-Console "[Python v$ver] " -f SlateBlue -NoNewLine; $res = [progressUtil]::WaitJob("Installing", (Start-Job -Name "Install python $ver" -ScriptBlock $sc)); } return $res } static [string] GetActivationScript() { return [venv]::GetActivationScript((Resolve-Path .).Path) } static [string] GetActivationScript([string]$ProjectPath) { $e = [venv]::Create($ProjectPath) return ([venv]::IsValid($e.Path) ? ([IO.Path]::Combine($e.Path, 'bin', 'activate.ps1')) : '') } static [string] GetEnvPath() { return [venv]::GetEnvPath([venv]::Config.ProjectPath) } static [string] GetEnvPath([string]$ProjectPath) { $reslt = $null; $_env_paths = [pipEnv]::get_work_home() | Get-ChildItem -Directory -ea Ignore if ($null -ne $_env_paths) { $reslt = $_env_paths.Where({ [IO.File]::ReadAllLines([IO.Path]::Combine($_.FullName, ".project"))[0] -eq $ProjectPath }) $reslt = ($reslt.count -eq 0) ? $null : $reslt[0] } return $reslt } static [bool] IsValid([string]$dir) { $v = $true; $d = [IO.DirectoryInfo]::new($dir); ("bin", "lib").ForEach{ $_d = $d.EnumerateDirectories($_); $v = $v -and (($_d.count -eq 1) ? $true : $false) if ($_ -eq 'bin') { $v = $v -and (($_d[0].EnumerateFiles("activate*").Count -gt 0) ? $true : $false) } }; $v = $v -and (($d.EnumerateFiles("pyvenv.cfg").Count -eq 1) ? $true : $false); return $v } [Object[]] verify() { return [venv]::Run("verify") } [Object[]] upgrade() { return [venv]::Run("upgrade") } [Object[]] sync() { return [venv]::Run("sync") } [Object[]] lock() { return [venv]::Run("lock") } [Object[]] install() { return [venv]::Run("install") } [void] Activate() { $spath = Resolve-Path ([IO.Path]::Combine($this.Path, 'bin', 'activate.ps1')) -ea Ignore if (![IO.File]::Exists($spath)) { throw [FileNotFoundException]::new("env activation script not found: $spath") } &$spath } [void] Delete() { $this.Path | Remove-Item -Force -Recurse -Verbose:$false -ea Ignore } } #endregion Classes # Types that will be available to users when they import the module. $typestoExport = @( [InstallRequirements], [DependencyInfo], [Requirement], [EnvState], [pipEnv], [venv] ) $TypeAcceleratorsClass = [PsObject].Assembly.GetType('System.Management.Automation.TypeAccelerators') foreach ($Type in $typestoExport) { if ($Type.FullName -in $TypeAcceleratorsClass::Get.Keys) { $Message = @( "Unable to register type accelerator '$($Type.FullName)'" 'Accelerator already exists.' ) -join ' - ' [System.Management.Automation.ErrorRecord]::new( [System.InvalidOperationException]::new($Message), 'TypeAcceleratorAlreadyExists', [System.Management.Automation.ErrorCategory]::InvalidOperation, $Type.FullName ) | Write-Warning } } # Add type accelerators for every exportable type. foreach ($Type in $typestoExport) { $TypeAcceleratorsClass::Add($Type.FullName, $Type) } # Remove type accelerators when the module is removed. $MyInvocation.MyCommand.ScriptBlock.Module.OnRemove = { foreach ($Type in $typestoExport) { $TypeAcceleratorsClass::Remove($Type.FullName) } }.GetNewClosure(); $scripts = @(); $Public = Get-ChildItem "$PSScriptRoot/Public" -Filter "*.ps1" -Recurse -ErrorAction SilentlyContinue $scripts += Get-ChildItem "$PSScriptRoot/Private" -Filter "*.ps1" -Recurse -ErrorAction SilentlyContinue $scripts += $Public foreach ($file in $scripts) { Try { if ([string]::IsNullOrWhiteSpace($file.fullname)) { continue } . "$($file.fullname)" } Catch { Write-Warning "Failed to import function $($file.BaseName): $_" $host.UI.WriteErrorLine($_) } } $Param = @{ Function = $Public.BaseName Cmdlet = '*' Alias = '*' Verbose = $false } Export-ModuleMember @Param |