EnvironmentVariableItems.psm1

#Region '.\_PrefixCode.ps1' -1

# Code in here will be prepended to top of the psm1-file.
#EndRegion '.\_PrefixCode.ps1' 2
#Region '.\Classes\EnvironmentVariableItems.ps1' -1

class EnvironmentVariableItems {

    ### Class variables

    [ValidatePattern("^[^=]+$")] [String] $Name;
    [System.EnvironmentVariableTarget] $Scope;
    [String] $Separator;
    [String] $Value;
    [System.Collections.ArrayList] $Items;

    ### Hidden variables

    hidden $defaultSeparator = ';'
    hidden $defaultScope = [System.EnvironmentVariableTarget]::Process

    ### Constructors

    # Name
    EnvironmentVariableItems(
        [String] $Name
    ) {
        $this.Init($Name, $this.defaultScope, $this.defaultSeparator)
    } 

    # Name, Scope
    EnvironmentVariableItems(
        [String] $Name, 
        [System.EnvironmentVariableTarget] $Scope
    ) {
        $this.Init($Name, $Scope, $this.defaultSeparator)
    } 

    # Name, Separator
    EnvironmentVariableItems(
        [String] $Name, 
        [String] $Separator
    ) {
        $this.Init($Name, $this.defaultScope, $Separator)
    } 

    # Name, Scope, Separator
    EnvironmentVariableItems(
            [String] $Name, 
            [System.EnvironmentVariableTarget] $Scope,
            [String] $Separator
    ) {
        $this.Init($Name, $Scope, $Separator)
    }

    ### Methods
    
    ### Getter & setter methods

    # Name
    [String] GetName() {
        return $this.Name
    }

    [void] SetName(
        [String] $Name
    ) {
        $this.Name = $Name
    }

    # Scope
    [String] GetScope() {
        return $this.Scope
    }

    [void] SetScope(
        [System.EnvironmentVariableTarget] $Scope
    ) {
        $this.Scope = $Scope
    }

    # Separator
    [String] GetSeparator() {
        return $this.Separator
    }

    [void] SetSeparator(
        [String] $Separator
    ) {
        $this.Separator = $Separator
    }

    # Value
    [String] GetValue() {
        return $this.Value
    }

    [String] GetValue(
        [String] $Name,
        [System.EnvironmentVariableTarget] $Scope
    ) {
        $this.SetValue($Name, $Scope)
        return $this.Value
    }

    [void] SetValue(
        [String] $Value
    ) {
        $this.Value = $Value
    }

    [void] SetValue(
        [String] $Name,
        [System.EnvironmentVariableTarget] $Scope
    ) {
        $this.Value = $this.GetEnvironmentVariable($Name, $Scope)
    }

    # Items
    [System.Collections.ArrayList] GetItems() {
        return $this.Items
    }

    [void] SetItems(
        [String] $Name,
        [System.EnvironmentVariableTarget] $Scope,
        [String] $Separator
    ) {
        # tidy (trim) local copy of value
        $val = $this.GetValue($Name, $Scope)
        if ($val) {$val = $val.Trim($Separator)}

        $this.Items = [System.Collections.ArrayList] @()
        if ($val) {
            $this.Items = $val -split $Separator
        }
    }

    ### Hidden methods

    hidden [bool] AddItem(
        [String] $Item
    ) {
        $this.GetItems().add($Item)
        return $True
    }

    hidden [bool] AddItem(
        [String] $Item,        
        [int] $Index
    ) {
        # Add 1 to items count reflecting length after addition
        $items_ = $this.GetItems()
        if (($ind = $this.GetPositiveIndex($Index, $items_.count + 1)) -is [int]) {
            $items_.insert($ind, $Item)
            return $True
        }                    
        return $False
    }

    hidden [String] GetEnvironmentVariable($Name, $Scope) {
        return [Environment]::GetEnvironmentVariable($Name, $Scope)
    }

    hidden [System.Collections.ArrayList] GetItemsForScope($Scope) {
        if ($Scope -eq $this.GetScope()) {
            return $this.GetItems()
        } else {
            return [EnvironmentVariableItems]::new($this.Name, $Scope, $this.Separator).GetItems()
        }
    }


    # check index is within range and return (as positive value if required)
    hidden [int] GetPositiveIndex(
        [int] $Index,
        [int] $ItemsCount
    ) {
        if ($Index -lt $ItemsCount -and $(-($Index) -le $ItemsCount)) {
            if ($Index -lt 0) {
                return $ItemsCount + $Index
            } else {
                return $Index
            }
        } else {
            Write-Host
            Write-Host  -ForegroundColor Red "Index $Index is out of range"
            Write-Host
        }
        return $False
    }

    hidden [void] Init(
            [String] $Name, 
            [System.EnvironmentVariableTarget] $Scope,
            [String] $Separator
    ) {
        #$this.Name = $Name
        $this.SetName($Name)
        $this.SetScope($Scope)
        $this.SetSeparator($Separator)
        $this.SetValue($this.Name, $this.Scope)
        $this.SetItems($this.Name, $this.Scope, $this.Separator)
    }

    hidden [bool] RemoveItemByIndex(
            [int] $Index
    ) {
        $items_ = $this.GetItems()
        if (($ind = $this.GetPositiveIndex($Index, $items_.count)) -is [int]) {
            $items_.RemoveAt($ind)
            return $True
        }                    
        return $False
    }
    
    hidden [bool] RemoveItemByItem(
            [String] $Item
    ) {
        $items_ = $this.GetItems()
        if (($items_.IndexOf($Item)) -ge 0) {
            $items_.Remove($Item)
            return $True
        }
        Write-Host
        Write-Host  -ForegroundColor Red "Item $Item not found"
        Write-Host
        return $False
    }

    hidden [void] SetEnvironmentVariable(
            [String] $Name,        
            [String] $Value,        
            [System.EnvironmentVariableTarget] $Scope = [System.EnvironmentVariableTarget]::Process
    ) {
        [Environment]::SetEnvironmentVariable($Name, $Value, $Scope)
        $this.SetValue($Value)
    }


    ### Public methods

    [void] ShowIndex(
            [System.EnvironmentVariableTarget] $Scope
    ) {
        $items_ = $this.GetItemsForScope($Scope)
        $this.ShowIndex($Scope, $items_)
    }

    [void] ShowIndex(
        [System.EnvironmentVariableTarget] $Scope,
        [System.Collections.ArrayList] $items_
    ) {
        Write-Host $Scope
        for ($i = 0; $i -lt $items_.count; $i++) {
            Write-Host -ForegroundColor Blue "${i}: $($items_[$i].ToString())"
        }
        Write-Host
    }

    [void] ShowIndexes() {
        Write-Host 
        $this.ShowIndex([System.EnvironmentVariableTarget]::Machine)
        $this.ShowIndex([System.EnvironmentVariableTarget]::User)
        $this.ShowIndex([System.EnvironmentVariableTarget]::Process)
        Write-Host
        Write-Host
    }

    [String] ToString() { 
        $s = ''
        $items_ = $this.GetItems()
        for ($i = 0; $i -lt $items_.count; $i++) {
            if ($i) { $s += $this.Separator}
            $s += $items_[$i]
        }
        return $s
    }
}
#EndRegion '.\Classes\EnvironmentVariableItems.ps1' 274
#Region '.\Private\ConfirmAction.ps1' -1

function ConfirmAction {
    param (
        [String] $Message,
        [switch] $NoConfirmationRequired
    )
    Write-Host $Message
    if ($NoConfirmationRequired) { return $True }
    $choices = [System.Management.Automation.Host.ChoiceDescription[]] @(
        [System.Management.Automation.Host.ChoiceDescription]::new('&Yes'),
        [System.Management.Automation.Host.ChoiceDescription]::new('&No')
    )
    $Host.UI.PromptForChoice('Confirm', 'Are you sure you want to perform this action?', $choices, 0) -eq 0
}
#EndRegion '.\Private\ConfirmAction.ps1' 14
#Region '.\Private\GetWhatIf.ps1' -1

function GetScopeWhatIf {
    param ($evis)
    @"
 
    [$($evis.Scope)]
    Current Value:
        $($evis.Value)
    New Value:
        $($evis.ToString())
"@

}

#EndRegion '.\Private\GetWhatIf.ps1' 13
#Region '.\Private\Resolve-ScopeParameter.ps1' -1

function Resolve-ScopeParameter {
    param (
        [Parameter(Mandatory)]
        [string] $Scope
    )
    switch ($Scope) {
        { $_ -in 'ProcessAndMachine', 'pam' } {
            return @([System.EnvironmentVariableTarget]::Process, [System.EnvironmentVariableTarget]::Machine)
        }
        { $_ -in 'ProcessAndUser', 'pau' } {
            return @([System.EnvironmentVariableTarget]::Process, [System.EnvironmentVariableTarget]::User)
        }
        'MachineOnly' { return @([System.EnvironmentVariableTarget]::Machine) }
        'UserOnly'    { return @([System.EnvironmentVariableTarget]::User) }
        default       { return @([System.EnvironmentVariableTarget]::Process) }
    }
}
#EndRegion '.\Private\Resolve-ScopeParameter.ps1' 18
#Region '.\Public\Add-EnvironmentVariableItem.ps1' -1

<#
.SYNOPSIS
Adds an environment variable item for given Name, Item, Scope (default: 'ProcessOnly') and Separator (';') and optional Index.
 
.PARAMETER Name
Environment variable name
 
.PARAMETER Item
An item of an environment variable (eg., 'C:\foo' in $env:Path of 'C:\foo;C:\bar')
 
.PARAMETER Scope
Target scope(s) for the operation. Valid values:
  ProcessOnly - updates Process scope only [default]
  ProcessAndMachine (pam) - updates both Process and Machine scopes
  ProcessAndUser (pau) - updates both Process and User scopes
  MachineOnly - updates Machine scope only
  UserOnly - updates User scope only
 
.PARAMETER Separator
Environment variable item separator (eg., ';' in $env:Path of 'C:\foo;C:\bar')
 
.PARAMETER Index
Item index position (negative values work backwards through collection, -1 being the last item)
 
.EXAMPLE
 
Add 'C:\foo' to $env:Path in both Process and Machine scopes (default)
 
PS> aevi path C:\foo -NoConfirmationRequired
 
.EXAMPLE
 
Add 'C:\foo' to $env:Path in both Process and User scopes
 
PS> aevi path C:\foo -Scope pau -NoConfirmationRequired
 
.EXAMPLE
 
Insert 'C:\foo' as first item in $env:Path Machine scope only
 
PS> Add-EnvironmentVariableItem -Name path -Item C:\foo -Scope MachineOnly -Index 0 -NoConfirmationRequired
 
.EXAMPLE
 
Add 'cake' as second item of $env:foo user environment variable
 
PS> aevi foo cake -Scope UserOnly -Index 1 -Separator '#' -NoConfirmationRequired
 
.INPUTS
 
.OUTPUTS
EnvironmentVariableItems PSCustomObject
 
#>

function Add-EnvironmentVariableItem {
    [CmdletBinding()]
    [Alias('aevi')]
    param (
        [Parameter(
            Mandatory,
            Position = 0
        )]
        [ValidatePattern("^[^=]+$")]
            [String] $Name,
        [Parameter(
            Mandatory,
            Position = 1
        )]
            [String] $Item,
        [Parameter()]
        [ValidateSet('ProcessAndMachine', 'pam', 'ProcessAndUser', 'pau', 'ProcessOnly', 'MachineOnly', 'UserOnly')]
            [String] $Scope = 'ProcessOnly',
        [Parameter()]
            [String] $Separator = ';',
        [Parameter()]
            [int] $Index,
        [Parameter()]
            [switch] $NoConfirmationRequired
    )
    process {
        $pending = [System.Collections.Generic.List[object]]::new()
        foreach ($target in (Resolve-ScopeParameter $Scope)) {
            $evis = [EnvironmentVariableItems]::new($Name, $target, $Separator)
            $result = if ($PSBoundParameters.ContainsKey('Index')) {
                $evis.AddItem($Item, $Index)
            } else {
                $evis.AddItem($Item)
            }
            if ($result -eq $True) { $pending.Add($evis) }
        }
        if ($pending.Count -gt 0) {
            $message = ($pending | ForEach-Object { GetScopeWhatIf $_ }) -join "`n"
            if (ConfirmAction -Message $message -NoConfirmationRequired:$NoConfirmationRequired) {
                foreach ($evis in $pending) {
                    $evis.SetEnvironmentVariable($evis.Name, $evis.ToString(), $evis.Scope)
                    $evis
                }
            }
        }
    }
}
#EndRegion '.\Public\Add-EnvironmentVariableItem.ps1' 102
#Region '.\Public\Get-EnvironmentVariableItems.ps1' -1


<#
.SYNOPSIS
Gets EnvironmentVariableItems PSCustomObject(s) for a given Name, Scope (default: 'ProcessOnly') and Separator (';').
Returns one object per resolved scope.
 
.PARAMETER Name
Environment variable name
 
.PARAMETER Scope
Target scope(s) for the operation. Valid values:
  ProcessOnly - returns object for Process scope only [default]
  ProcessAndMachine (pam) - returns objects for both Process and Machine scopes
  ProcessAndUser (pau) - returns objects for both Process and User scopes
  MachineOnly - returns object for Machine scope only
  UserOnly - returns object for User scope only
 
.PARAMETER Separator
Environment variable item separator (eg., ';' in $env:Path of 'C:\foo;C:\bar')
 
.EXAMPLE
 
Get $env:Path EnvironmentVariableItems for Process and Machine scopes (default)
 
PS> Get-EnvironmentVariableItems -Name Path
 
Name : Path
Scope : Process
Separator : ;
Value : C:\Program Files\PowerShell\7;C:\WINDOWS\system32
Items : {C:\Program Files\PowerShell\7, C:\WINDOWS\system32}
 
Name : Path
Scope : Machine
Separator : ;
Value : C:\WINDOWS\system32;C:\WINDOWS
Items : {C:\WINDOWS\system32, C:\WINDOWS}
 
.EXAMPLE
 
Get user $env:Path EnvironmentVariableItems PSCustomObject
 
PS> Get-EnvironmentVariableItems -Name Path -Scope UserOnly
 
Name : Path
Scope : User
Separator : ;
Value : C:\foo;C:\Users\michaelf\AppData\Local\Microsoft\WindowsApps
Items : {C:\foo, C:\Users\michaelf\AppData\Local\Microsoft\WindowsApps}
 
.EXAMPLE
 
Get user $env:foo EnvironmentVariableItems PSCustomObject
 
PS> gevis foo -Scope UserOnly -Separator '#'
 
Name : foo
Scope : User
Separator : #
Value : foo#cake#bar#cup
Items : {foo, cake, bar, cup}
 
.INPUTS
 
.OUTPUTS
EnvironmentVariableItems PSCustomObject
#>

function Get-EnvironmentVariableItems {
    [CmdletBinding()]
    [Alias('gevis')]
    param (
        [Parameter(Mandatory)]
        [ValidatePattern("^[^=]+$")]
            [String] $Name,
        [Parameter()]
        [ValidateSet('ProcessAndMachine', 'pam', 'ProcessAndUser', 'pau', 'ProcessOnly', 'MachineOnly', 'UserOnly')]
            [String] $Scope = 'ProcessOnly',
        [Parameter()]
            [String] $Separator = ';'
    )
    process {
        foreach ($target in (Resolve-ScopeParameter $Scope)) {
            [EnvironmentVariableItems]::new($Name, $target, $Separator)
        }
    }
}
#EndRegion '.\Public\Get-EnvironmentVariableItems.ps1' 87
#Region '.\Public\Remove-EnvironmentVariableItem.ps1' -1

<#
.SYNOPSIS
Removes an environment variable item for given Name, Item or Index, Scope (default: 'ProcessOnly') and Separator (';').
 
.PARAMETER Name
Environment variable name
 
.PARAMETER Item
An item of an environment variable (eg., 'C:\foo' in $env:Path of 'C:\foo;C:\bar')
 
.PARAMETER Scope
Target scope(s) for the operation. Valid values:
  ProcessOnly - updates Process scope only [default]
  ProcessAndMachine (pam) - updates both Process and Machine scopes
  ProcessAndUser (pau) - updates both Process and User scopes
  MachineOnly - updates Machine scope only
  UserOnly - updates User scope only
 
.PARAMETER Separator
Environment variable item separator (eg., ';' in $env:Path of 'C:\foo;C:\bar')
 
.PARAMETER Index
Item index position (negative values work backwards through collection, -1 being the last item)
 
.EXAMPLE
 
Remove 'C:\foo' from $env:Path in both Process and Machine scopes (default)
 
PS> revi path C:\foo -NoConfirmationRequired
 
.EXAMPLE
 
Remove 'C:\foo' from $env:Path in User scope only
 
PS> Remove-EnvironmentVariableItem -Name path -Item 'C:\foo' -Scope UserOnly -NoConfirmationRequired
 
.EXAMPLE
 
Remove last item from $env:Path in both Process and User scopes
 
PS> revi path -Index -1 -Scope pau -NoConfirmationRequired
 
.INPUTS
 
.OUTPUTS
EnvironmentVariableItems PSCustomObject
 
#>

function Remove-EnvironmentVariableItem {
    [CmdletBinding()]
    [Alias('revi')]
    param (
        [Parameter(
            Mandatory,
            Position = 0
        )]
        [ValidatePattern("^[^=]+$")]
            [String] $Name,
        [Parameter(
            Mandatory,
            ParameterSetName = 'ByItem',
            Position = 1
        )]
            [String] $Item,
        [Parameter(
            ParameterSetName = 'ByIndex',
            Position = 1,
            Mandatory
        )]
            [int] $Index,
        [Parameter()]
        [ValidateSet('ProcessAndMachine', 'pam', 'ProcessAndUser', 'pau', 'ProcessOnly', 'MachineOnly', 'UserOnly')]
            [String] $Scope = 'ProcessOnly',
        [Parameter()]
            [String] $Separator = ";",
        [Parameter()]
            [switch] $NoConfirmationRequired
    )
    process {
        $pending = [System.Collections.Generic.List[object]]::new()
        foreach ($target in (Resolve-ScopeParameter $Scope)) {
            $evis = [EnvironmentVariableItems]::new($Name, $target, $Separator)
            $result = if ($PSCmdlet.ParameterSetName -eq 'ByIndex') {
                $evis.RemoveItemByIndex($Index) -ne $False
            } elseif ($PSCmdlet.ParameterSetName -eq 'ByItem') {
                $evis.RemoveItemByItem($Item) -ne $False
            }
            if ($result -ne $False) { $pending.Add($evis) }
        }
        if ($pending.Count -gt 0) {
            $message = ($pending | ForEach-Object { GetScopeWhatIf $_ }) -join "`n"
            if (ConfirmAction -Message $message -NoConfirmationRequired:$NoConfirmationRequired) {
                foreach ($evis in $pending) {
                    $evis.SetEnvironmentVariable($evis.Name, $evis.ToString(), $evis.Scope)
                    $evis
                }
            }
        }
    }
}
#EndRegion '.\Public\Remove-EnvironmentVariableItem.ps1' 101
#Region '.\Public\Show-EnvironmentVariableItems.ps1' -1

<#
.SYNOPSIS
Shows indexed list of environment variable items for given Name, Scope and Separator (default: ';').
Omitting Scope shows all three scopes (Machine, User, Process).
 
.PARAMETER Name
Environment variable name
 
.PARAMETER Scope
Target scope(s) for the operation. Omit to show all three scopes. Valid values:
  ProcessAndMachine (pam) - shows both Process and Machine scopes
  ProcessAndUser (pau) - shows both Process and User scopes
  ProcessOnly - shows Process scope only
  MachineOnly - shows Machine scope only
  UserOnly - shows User scope only
 
.PARAMETER Separator
Environment variable item separator (eg., ';' in $env:Path of 'C:\foo;C:\bar')
 
.EXAMPLE
 
Show $env:PSModulePath items for all scopes
 
PS> Show-EnvironmentVariableItems PSModulePath
 
Machine
0: C:\Program Files\WindowsPowerShell\Modules
1: C:\WINDOWS\system32\WindowsPowerShell\v1.0\Modules
2: N:\lib\pow\mod
 
User
0: H:\lib\pow\mod
 
Process
0: C:\Users\michaelf\Documents\PowerShell\Modules
1: C:\Program Files\PowerShell\Modules
2: c:\program files\powershell\7\Modules
3: H:\lib\pow\mod
4: C:\Program Files\WindowsPowerShell\Modules
5: C:\WINDOWS\system32\WindowsPowerShell\v1.0\Modules
6: N:\lib\pow\mod
 
.EXAMPLE
 
Show $env:PSModulePath items for Process and Machine scopes
 
PS> Show-EnvironmentVariableItems PSModulePath -Scope pam
 
Machine
0: C:\Program Files\WindowsPowerShell\Modules
1: C:\WINDOWS\system32\WindowsPowerShell\v1.0\Modules
2: N:\lib\pow\mod
 
Process
0: C:\Users\michaelf\Documents\PowerShell\Modules
1: C:\Program Files\PowerShell\Modules
 
.EXAMPLE
 
Show $env:PSModulePath items for Machine scope only
 
PS> Show-EnvironmentVariableItems PSModulePath -Scope MachineOnly
 
Machine
0: C:\Program Files\WindowsPowerShell\Modules
1: C:\WINDOWS\system32\WindowsPowerShell\v1.0\Modules
2: N:\lib\pow\mod
 
#>

function Show-EnvironmentVariableItems {
    [CmdletBinding()]
    [Alias('sevis')]
    param (
        [Parameter(Mandatory)]
        [ValidatePattern("^[^=]+$")]
            [String] $Name,
        [Parameter()]
        [ValidateSet('ProcessAndMachine', 'pam', 'ProcessAndUser', 'pau', 'ProcessOnly', 'MachineOnly', 'UserOnly')]
            [String] $Scope,
        [Parameter()]
            [String] $Separator = ';'
    )
    process {
        if (-not $PSBoundParameters.ContainsKey('Scope')) {
            [EnvironmentVariableItems]::new($Name, $Separator).ShowIndexes()
        } else {
            $targets = Resolve-ScopeParameter $Scope
            $evis = [EnvironmentVariableItems]::new($Name, $targets[0], $Separator)
            Write-Host
            foreach ($target in $targets) {
                $evis.ShowIndex($target)
            }
            Write-Host
            Write-Host
        }
    }
}
#EndRegion '.\Public\Show-EnvironmentVariableItems.ps1' 98