PATH.psm1

[Cmdletbinding()]
param()

$scriptName = $MyInvocation.MyCommand.Name
Write-Verbose "[$scriptName] Importing subcomponents"

#region - Data import
Write-Verbose "[$scriptName] - [data] - Processing folder"
$dataFolder = (Join-Path $PSScriptRoot 'data')
Write-Verbose "[$scriptName] - [data] - [$dataFolder]"
Get-ChildItem -Path "$dataFolder" -Recurse -Force -Include '*.psd1' -ErrorAction SilentlyContinue | ForEach-Object {
    Write-Verbose "[$scriptName] - [data] - [$($_.Name)] - Importing"
    New-Variable -Name $_.BaseName -Value (Import-PowerShellDataFile -Path $_.FullName) -Force
    Write-Verbose "[$scriptName] - [data] - [$($_.Name)] - Done"
}

Write-Verbose "[$scriptName] - [data] - Done"
#endregion - Data import

#region - From /public
Write-Verbose "[$scriptName] - [/public] - Processing folder"

#region - From /public/Add-EnvironmentPath.ps1
Write-Verbose "[$scriptName] - [/public/Add-EnvironmentPath.ps1] - Importing"

#Requires -Modules Utilities

function Add-EnvironmentPath {
    <#
    .SYNOPSIS
    Add a path to the PATH environment variable.

    .DESCRIPTION
    Add a path to the PATH environment variable. This command will normalize the path separators.

    .EXAMPLE
    Add-EnvironmentPath -Scope CurrentUser -Path 'C:\Program Files\Git\cmd'

    Add the path 'C:\Program Files\Git\cmd' to the PATH environment variable for the current user.

    .EXAMPLE
    Add-EnvironmentPath -Scope AllUsers -Path 'C:\Program Files\Git\cmd'

    Add the path 'C:\Program Files\Git\cmd' to the PATH environment variable for all users.

    .EXAMPLE
    Add-EnvironmentPath -Scope CurrentUser -Path 'C:\Program Files\Git\cmd', 'C:\Program Files\Git\bin'

    Add the paths 'C:\Program Files\Git\cmd' and 'C:\Program Files\Git\bin' to the PATH environment variable for the current user.

    .EXAMPLE
    Add-EnvironmentPath -Scope CurrentUser -Path 'C:\Program Files\Git\cmd', 'C:\Program Files\Git\bin' -Force

    Add the paths 'C:\Program Files\Git\cmd' and 'C:\Program Files\Git\bin' to the PATH environment variable for the current user. Any invalid paths will be removed.

    .EXAMPLE
    'C:\Program Files\Git\cmd', 'C:\Program Files\Git\bin' | Add-EnvironmentPath -Scope CurrentUser

    Add the paths 'C:\Program Files\Git\cmd' and 'C:\Program Files\Git\bin' to the PATH environment variable for the current user.
    #>

    [CmdletBinding()]
    param(
        # The scope of the environment variable.
        [Parameter()]
        [ValidateSet('AllUsers', 'CurrentUser')]
        [string] $Scope = 'CurrentUser',

        # The path to add to the environment variable.
        [Parameter(
            Mandatory,
            ValueFromPipeline,
            ValueFromPipelineByPropertyName
        )]
        [ValidateNotNullOrEmpty()]
        [Alias('FullName')]
        [string[]] $Path,

        # Remove any invalid paths.
        [Parameter()]
        [switch] $Force
    )

    begin {
        $separatorChar = [IO.Path]::DirectorySeparatorChar

        $target = if ($Scope -eq 'CurrentUser') {
            [System.EnvironmentVariableTarget]::User
        } else {
            [System.EnvironmentVariableTarget]::Machine
            if (-not (IsAdmin)) {
                throw "Administrator rights are required to modify machine PATH. Please run the command again with elevated rights (Run as Administrator) or provide '-Scope CurrentUser' to your command."
            }
        }
        $environmentPath = Get-EnvironmentPath -Scope $Scope -AsArray
        Write-Verbose "Add PATH - [$target]"
    }

    process {
        foreach ($envPath in $Path) {
            Write-Verbose "Add PATH - [$target] - [$envPath]"

            $envPathExists = Test-Path $envPath
            if ($envPathExists) {
                Write-Verbose "Add PATH - [$target] - [$envPath] - Path exists - Yes"

                $envPathObject = Get-Item -Path $envPath -ErrorAction SilentlyContinue
                if ($envPath -ceq $envPathObject.FullName) {
                    Write-Verbose "Add PATH - [$target] - [$envPath] - Verify path - Ok"
                } else {
                    $envPath = $envPathObject.FullName
                    Write-Verbose "Add PATH - [$target] - [$envPath] - Verify path - Updated"
                }
            } else {
                if ($Force) {
                    Write-Verbose "Add PATH - [$target] - [$envPath] - Path exists - No - Continuing (Force)"
                } else {
                    Write-Warning "Add PATH - [$target] - [$envPath] - Path exists - No - Skipping (Use -Force to add)"
                    continue
                }
            }

            Write-Verbose "Add PATH - [$target] - [$envPath] - Normalize path"
            $envPath = $envPath.Replace('\', $separatorChar)
            $envPath = $envPath.Replace('/', $separatorChar)
            $envPath = $envPath.TrimEnd($separatorChar)

            $environmentPath += $envPath
            Write-Verbose "Add PATH - [$target] - [$envPath] - Added"
        }
    }

    end {
        if ($IsWindows) {
            $pathSeparator = ';'
        } else {
            $pathSeparator = ':'
        }
        $environmentPath = $environmentPath -join $pathSeparator

        [System.Environment]::SetEnvironmentVariable('PATH', $environmentPath, [System.EnvironmentVariableTarget]::$target)
        Write-Verbose "Add PATH - [$target] - Done"
    }
}

Write-Verbose "[$scriptName] - [/public/Add-EnvironmentPath.ps1] - Done"
#endregion - From /public/Add-EnvironmentPath.ps1
#region - From /public/Get-EnvironmentPath.ps1
Write-Verbose "[$scriptName] - [/public/Get-EnvironmentPath.ps1] - Importing"

function Get-EnvironmentPath {
    <#
    .SYNOPSIS
    Get the PATH environment variable.

    .DESCRIPTION
    Get the PATH environment variable for the current user or all users.

    .EXAMPLE
    Get-EnvironmentPath -Scope CurrentUser

    Get the PATH environment variable for the current user.

    .EXAMPLE
    Get-EnvironmentPath -Scope AllUsers -AsArray

    Get the PATH environment variable for the current user as an array.
    #>

    [OutputType([string[]], ParameterSetName = 'AsArray')]
    [OutputType([string], ParameterSetName = 'AsString')]
    [CmdletBinding(DefaultParameterSetName = 'AsString')]
    param(
        # The scope of the environment variable.
        [Parameter()]
        [ValidateSet('AllUsers', 'CurrentUser')]
        [string] $Scope = 'CurrentUser',

        # Return the environment variable as an array.
        [Parameter(ParameterSetName = 'AsArray')]
        [switch] $AsArray
    )

    $target = if ($Scope -eq 'CurrentUser') {
        [System.EnvironmentVariableTarget]::User
    } else {
        [System.EnvironmentVariableTarget]::Machine
    }

    $environmentPath = [System.Environment]::GetEnvironmentVariable('PATH', [System.EnvironmentVariableTarget]::$target)
    if (-not $AsArray) {
        return $environmentPath
    }
    if ($IsWindows) {
        $pathSeparator = ';'
    } else {
        $pathSeparator = ':'
    }
    $environmentPath = $environmentPath.Split($pathSeparator)
    $environmentPath = $environmentPath | Sort-Object

    return $environmentPath
}

Write-Verbose "[$scriptName] - [/public/Get-EnvironmentPath.ps1] - Done"
#endregion - From /public/Get-EnvironmentPath.ps1
#region - From /public/Remove-EnvironmentPath.ps1
Write-Verbose "[$scriptName] - [/public/Remove-EnvironmentPath.ps1] - Importing"

#Requires -Modules Utilities

function Remove-EnvironmentPath {
    <#
    .SYNOPSIS
    Remove a path from the PATH environment variable.

    .DESCRIPTION
    Remove a path from the PATH environment variable. This command will normalize the path separators.

    .EXAMPLE
    Remove-EnvironmentPath -Scope CurrentUser -Path 'C:\Program Files\Git\cmd'

    Remove the path 'C:\Program Files\Git\cmd' from the PATH environment variable for the current user.

    .EXAMPLE
    Remove-EnvironmentPath -Scope AllUsers -Path 'C:\Program Files\Git\cmd'

    Remove the path 'C:\Program Files\Git\cmd' from the PATH environment variable for all users.

    .EXAMPLE
    Remove-EnvironmentPath -Scope CurrentUser -Path 'C:\Program Files\Git\cmd', 'C:\Program Files\Git\bin'

    Remove the paths 'C:\Program Files\Git\cmd' and 'C:\Program Files\Git\bin' from the PATH environment variable for the current user.

    .EXAMPLE
    'C:\Program Files\Git\cmd', 'C:\Program Files\Git\bin' | Remove-EnvironmentPath -Scope CurrentUser

    Remove the paths 'C:\Program Files\Git\cmd' and 'C:\Program Files\Git\bin' from the PATH environment variable for the current user.

    .EXAMPLE
    (Get-EnvironmentPath -Scope AllUsers -AsArray) | where {$_ -like "$env:USERPROFILE*"} | Remove-EnvironmentPath -Scope AllUsers -Verbose

    Remove all paths from the PATH environment variable for all users that start with the current user's profile path.

    .EXAMPLE
    (Get-EnvironmentPath -Scope CurrentUser -AsArray) | where {$_ -like "$env:windir*" -or $_ -like "$env:ProgramFiles*" -or $_ -like "${env:ProgramFiles(x86)}*"} | Remove-EnvironmentPath -Scope CurrentUser -Verbose

    Remove all paths from the PATH environment variable for the current user that start with the Windows directory, Program Files directory or Program Files (x86) directory.
    #>

    [CmdletBinding(SupportsShouldProcess)]
    param(
        # The scope of the environment variable.
        [Parameter()]
        [ValidateSet('AllUsers', 'CurrentUser')]
        [string] $Scope = 'CurrentUser'
    )

    DynamicParam {
        $runtimeDefinedParameterDictionary = New-Object System.Management.Automation.RuntimeDefinedParameterDictionary
        $attributeCollection = New-Object System.Collections.ObjectModel.Collection[System.Attribute]

        $parameterName = 'Path'
        $parameterAttribute = New-Object System.Management.Automation.ParameterAttribute
        $parameterAttribute.Mandatory = $true
        $parameterAttribute.Position = 1
        $parameterAttribute.HelpMessage = 'Name of the font to uninstall.'
        $parameterAttribute.ValueFromPipeline = $true
        $parameterAttribute.ValueFromPipelineByPropertyName = $true
        $attributeCollection.Add($parameterAttribute)

        $parameterValidateSet = Get-EnvironmentPath -Scope $Scope -AsArray
        $validateSetAttribute = New-Object System.Management.Automation.ValidateSetAttribute($parameterValidateSet)
        $attributeCollection.Add($validateSetAttribute)

        # Adding a parameter alias
        $parameterAlias = 'FullName'
        $aliasAttribute = New-Object System.Management.Automation.AliasAttribute -ArgumentList $parameterAlias
        $attributeCollection.Add($aliasAttribute)

        $runtimeDefinedParameter = New-Object System.Management.Automation.RuntimeDefinedParameter($parameterName, [string[]], $attributeCollection)
        $runtimeDefinedParameterDictionary.Add($parameterName, $runtimeDefinedParameter)
        return $runtimeDefinedParameterDictionary
    }

    begin {
        $target = if ($Scope -eq 'CurrentUser') {
            [System.EnvironmentVariableTarget]::User
        } else {
            [System.EnvironmentVariableTarget]::Machine
            if (-not (IsAdmin)) {
                throw "Administrator rights are required to modify machine PATH. Please run the command again with elevated rights (Run as Administrator) or provide '-Scope CurrentUser' to your command."
            }
        }
        $environmentPath = Get-EnvironmentPath -Scope $Scope -AsArray
        Write-Verbose "Remove PATH - [$target]"
    }

    process {
        $Path = $PSBoundParameters['Path']
        foreach ($envPath in $Path) {
            Write-Verbose "Remove PATH - [$target] - [$envPath]"
            $environmentPath = $environmentPath | Where-Object { $_ -ne $envPath }
            Write-Warning "Remove PATH - [$target] - [$envPath] - Removed"
        }
    }

    end {
        if ($IsWindows) {
            $pathSeparator = ';'
        } else {
            $pathSeparator = ':'
        }
        $environmentPath = $environmentPath -join $pathSeparator
        if ($PSCmdlet.ShouldProcess($environmentPath, 'Remove')) {
            [System.Environment]::SetEnvironmentVariable('PATH', $environmentPath, [System.EnvironmentVariableTarget]::$target)
        }
        Write-Verbose "Remove PATH - [$target] - Done"
    }
}

Write-Verbose "[$scriptName] - [/public/Remove-EnvironmentPath.ps1] - Done"
#endregion - From /public/Remove-EnvironmentPath.ps1
#region - From /public/Repair-EnvironmentPath.ps1
Write-Verbose "[$scriptName] - [/public/Repair-EnvironmentPath.ps1] - Importing"

#Requires -Modules Utilities

function Repair-EnvironmentPath {
    <#
    .SYNOPSIS
    Repair the PATH environment variable.

    .DESCRIPTION
    Repair the PATH environment variable. This command will remove any invalid paths and normalize the path separators.

    .EXAMPLE
    Repair-EnvironmentPath -Scope CurrentUser

    Repair the PATH environment variable for the current user.

    .EXAMPLE
    Repair-EnvironmentPath -Scope AllUsers

    Repair the PATH environment variable for all users.

    .EXAMPLE

    Repair-EnvironmentPath -Scope CurrentUser -Force

    Repair the PATH environment variable for the current user. Any invalid paths will be removed.

    .EXAMPLE

    Repair-EnvironmentPath -Scope AllUsers -Force

    Repair the PATH environment variable for all users. Any invalid paths will be removed.

    .NOTES
    General notes
    #>

    [CmdletBinding()]
    param(
        # The scope of the environment variable.
        [Parameter()]
        [ValidateSet('AllUsers', 'CurrentUser')]
        [string] $Scope = 'CurrentUser',

        # Remove any invalid paths.
        [switch] $Force
    )

    begin {
        $separatorChar = [IO.Path]::DirectorySeparatorChar

        $target = if ($Scope -eq 'CurrentUser') {
            [System.EnvironmentVariableTarget]::User
        } else {
            [System.EnvironmentVariableTarget]::Machine
            if (-not (IsAdmin)) {
                throw "Administrator rights are required to modify machine PATH. Please run the command again with elevated rights (Run as Administrator) or provide '-Scope CurrentUser' to your command."
            }
        }
        $environmentPaths = Get-EnvironmentPath -Scope $Scope -AsArray
        Write-Verbose "Repair PATH - [$target]"
        $repairedEnvironmentPaths = @()
    }

    process {
        foreach ($envPath in $environmentPaths) {
            Write-Verbose "Repair PATH - [$target] - [$envPath]"
            $environmentPathExists = Test-Path $envPath

            if ($environmentPathExists) {
                Write-Verbose "Repair PATH - [$target] - [$envPath] - Path exists - Yes"
                $envPathObject = Get-Item -Path $envPath -ErrorAction SilentlyContinue
                if ($envPath -ceq $envPathObject.FullName) {
                    Write-Verbose "Repair PATH - [$target] - [$envPath] - Verify path - Ok"
                } else {
                    $envPath = $envPathObject.FullName
                    Write-Verbose "Repair PATH - [$target] - [$envPath] - Verify path - Updated"
                }
            } else {
                if ($Force) {
                    Write-Warning "Repair PATH - [$target] - [$envPath] - Path exists - No - Removeing (Force)"
                    continue
                } else {
                    Write-Warning "Repair PATH - [$target] - [$envPath] - Path exists - No - Continuing (Use -Force to remove))"
                }
            }

            Write-Verbose "Repair PATH - [$target] - [$envPath] - Normalize path"
            $envPath = $envPath.Replace('\', $separatorChar)
            $envPath = $envPath.Replace('/', $separatorChar)
            $envPath = $envPath.TrimEnd($separatorChar)

            $repairedEnvironmentPaths += $envPath
            Write-Verbose "Repair PATH - [$target] - [$envPath] - Repaired"
        }
    }

    end {
        if ($IsWindows) {
            $pathSeparator = ';'
        } else {
            $pathSeparator = ':'
        }
        $repairedEnvironmentPaths = $repairedEnvironmentPaths | Sort-Object -Unique
        $repairedEnvironmentPaths = $repairedEnvironmentPaths -join $pathSeparator

        [System.Environment]::SetEnvironmentVariable('PATH', $repairedEnvironmentPaths, [System.EnvironmentVariableTarget]::$target)
        Write-Verbose "Repair PATH - [$target] - Done"
    }
}

Write-Verbose "[$scriptName] - [/public/Repair-EnvironmentPath.ps1] - Done"
#endregion - From /public/Repair-EnvironmentPath.ps1

Write-Verbose "[$scriptName] - [/public] - Done"
#endregion - From /public

Export-ModuleMember -Function 'Add-EnvironmentPath','Get-EnvironmentPath','Remove-EnvironmentPath','Repair-EnvironmentPath' -Cmdlet '' -Variable '' -Alias ''