PSEnvironment.psm1

#requires -Version 2
Set-StrictMode -Version 2

##############################
#.SYNOPSIS
# Gets an environment variable value.
#
#.DESCRIPTION
# Gets an environment variable value.
#
#.PARAMETER Name
# Name of the Environment variable
#
#.PARAMETER Scope
# The environment scope to change. By default only the current
# process is affected.
#
#.EXAMPLE
# PS> Set-EnvironmentVariable -Name MyVar -Value MyValue -Scope user
#
#.NOTES
# When the scope is set to "process" this command is equivalent to
# $env:<Name>
#
# By comparing process & user/machine scopes it's possible to see if
# the current process has changed a value from it's default.
##############################
function Get-EnvironmentVariable
{
  [CmdletBinding()]
  param(
    [Parameter(Mandatory = $true,ValueFromPipeline = $true)]
    [String]$Name,

    [Parameter(Mandatory = $false)]
    [ValidateSet('process','machine','user')]
    [String]$Scope = 'process'
  )

  [Environment]::GetEnvironmentVariable($Name, $Scope)
}

##############################
#.SYNOPSIS
# Sets an envrionment variable.
#
#.DESCRIPTION
# Allows setting an environment variable for any scope.
#
#.PARAMETER Name
# Name of the variable.
#
#.PARAMETER Value
# Value to be set
#
#.PARAMETER Scope
# The environment scope to change. By default only the current
# process is affected.
#
#.EXAMPLE
# PS> Set-EnvironmentVariable -Name MyVar -Value MyValue -Scope user
#
#.NOTES
# When the scope is set to "process" this command is equivalent to
# $env:<Name> = <Value>
##############################
function Set-EnvironmentVariable
{
  [CmdletBinding(SupportsShouldProcess = $true)]
  param(
    [Parameter(Mandatory = $true,ValueFromPipelineByPropertyName = $true)]
    [String]$Name,

    [Parameter(Mandatory = $true,ValueFromPipelineByPropertyName = $true)]
    [String]$Value,

    [Parameter(Mandatory = $false)]
    [ValidateSet('process','machine','user')]
    [String]$Scope = 'process'
  )

  Write-Verbose -Message "Setting '$Name' to '$Value' on '$Scope'"
  if ($PSCmdlet.ShouldProcess("[$Scope]::$Name", $Value))
  {
    [Environment]::SetEnvironmentVariable($Name, $Value, $Scope)
  }
}

##############################
#.SYNOPSIS
# Get the current PATH environement value.
#
#.DESCRIPTION
# Get the current PATH environement value formatted as a PSCustomObject
# with Path, Scope, and Exists.
#
#.PARAMETER Scope
# One or more scopes. By default all scopes are returned.
#
 #.EXAMPLE
# PS> Get-EnvironmentPath
#
# Path Scope Exists
# ---- ----- ------
# C:\tools\go\bin process True
# ...
# C:\Program Files\Java\jdk1.8.0_74\bin machine True
# C:\Go\bin machine False
# ...
# C:\Program Files\Git\cmd user True
##############################
function Get-EnvironmentPath
{
  [CmdletBinding()]
  param(
    [Parameter(Mandatory = $false)]
    [ValidateSet('process','machine','user')]
    [String[]]$Scope
  )

  if (-not $Scope)
  {
    $Scope = @('process', 'machine', 'user')
  }

  foreach ($s in $Scope)
  {
    $pathss = (Get-EnvironmentVariable -Name 'PATH' -Scope $s) -split ';'
    foreach ($paths in $pathss)
    {
      if ([String]::IsNullOrWhiteSpace($paths)) { continue }
      [pscustomobject]@{
        Path   = $paths
        Scope  = $s
        Exists = Test-Path($paths)
      }
    }
  }
}

##############################
#.SYNOPSIS
# Tries to clean up the PATH environment variable.
#
#.DESCRIPTION
# Tries to clean up the PATH environment variable by looking
# for bad entries, e.g. empty entries, invalid paths, duplicates.
#
#.PARAMETER Scope
# The environment scope to change. By default only the current
# process is affected.
#
#.EXAMPLE
# PS> Repair-EnvironmentPath -Scope process
#
##############################
function Repair-EnvironmentPath
{
  [CmdletBinding(SupportsShouldProcess = $true)]
  param(
    [Parameter(Mandatory = $false)]
    [ValidateSet('process','machine','user')]
    [String]$Scope = 'process'
  )
  if ($Scope -eq 'process')
  {
    Write-Warning -Message 'This will change current-process value only. This may not be what you intended; see -Scope'
  }

  $verbose = ($PSCmdlet.MyInvocation.BoundParameters['Verbose'].IsPresent -eq $true)

  # Ensure unique paths only
  $paths = Get-EnvironmentPath -Scope $Scope
  $result = @()
  foreach ($path in ($paths | Select-Object -ExpandProperty Path))
  {
    if ([string]::IsNullOrWhiteSpace($path))
    {
      Write-Verbose -Message 'Found empty path. Removing.'
      continue
    }

    $path = $path.Trim()
    if ($path -in $result)
    {
      Write-Warning -Message "Found duplicate path [$path]. Removing."
      if ($PSCmdlet.ShouldProcess($path, 'Removing duplicate path entry?'))
      {
        continue
      }
    }

    if (-not (Test-Path $path -PathType Container))
    {
      Write-Warning -Message "Found invliad path [$path]. Removing."
      if ($PSCmdlet.ShouldProcess($path, 'Removing invalid path entry?'))
      {
        continue
      }
    }

    $result += $path
  }

  if ($PSCmdlet.ShouldProcess("`n$($result -join "`n")`n", 'Update environment with paths'))
  {
    Set-EnvironmentVariable -Scope $Scope -Name PATH -Value ($result -join ';')
  }
}

##############################
#.SYNOPSIS
# Test if the specified path is defined in the PATH
# Environment variable.
#
#.DESCRIPTION
# Test if the specified path is defined in the PATH
# Environment variable.
#
#.PARAMETER Path
# Path to a directory.
#
#.PARAMETER Scope
# The environment scope to change. By default only the current
# process is affected.
#
#.EXAMPLE
# PS> Test-EnvironmentPath -Scope process -Path c:\windows\system32
# True
#
##############################
function Test-EnvironmentPath
{
  [CmdletBinding()]
  param(
    [Parameter(Mandatory = $true,ValueFromPipeline = $true)]
    [String]$Path,

    [Parameter(Mandatory = $false)]
    [ValidateSet('process','machine','user')]
    [String]$Scope = 'process'
  )

  (Get-EnvironmentPath -Scope $Scope | Where-Object -FilterScript {
      $_.path -ieq $path
  }).Count -gt 0
}

##############################
#.SYNOPSIS
# Add the specified path to the PATH environment variable.
#
#.DESCRIPTION
# Add the specified path to the PATH environment variable.
#
#.PARAMETER Path
# Path to a directory
#
#.PARAMETER Scope
# The environment scope to change. By default only the current
# process is affected.
#
#.EXAMPLE
# PS> Add-EnvrionmentPath -Scope Process -Path c:\path\to\my\cool\tools
##############################
function Add-EnvironmentPath
{
  [CmdletBinding(SupportsShouldProcess = $true)]
  param(
    [Parameter(Mandatory = $true,ValueFromPipeline = $true)]
    [String]$Path,

    [Parameter(Mandatory = $false)]
    [ValidateSet('process','machine','user')]
    [String]$Scope = 'process'
  )

  $Path = $path.TrimEnd('\')
  if (!(Test-Path -Path $Path -PathType container))
  {
    throw 'Invalid Directory'
  }

  if (Test-EnvironmentPath -Scope $Scope -Path $Path)
  {
    throw 'Path already in PATH variable'
  }

  $envPath = Get-EnvironmentPath -Scope $Scope | Select-Object -ExpandProperty path
  $result = $envPath + $paths
  Write-Verbose -Message "New Path: $($result -join ';')"
  if ($PSCmdlet.ShouldProcess('PATH', 'Update Environment Variable'))
  {
    Set-EnvironmentVariable -Scope $Scope -Name PATH -Value ($result -join ';')
  }
}

##############################
#.SYNOPSIS
# Removes the specified path from the environmet PATH variable.
#
#.DESCRIPTION
# Removes the specified path from the environmet PATH variable.
#
#.PARAMETER path
# Path to be removed.
#
#.PARAMETER Scope
# The environment scope to change. By default only the current
# process is affected.
#
#.EXAMPLE
# PS> Remove-EnvironmentPath -Scope process -Path c:\a\misbehaving\tool
##############################
function Remove-EnvironmentPath
{
  [CmdletBinding(SupportsShouldProcess = $true)]
  param(
    [Parameter(Mandatory = $true,ValueFromPipeline = $true)]
    [String]$path,

    [Parameter(Mandatory = $false)]
    [ValidateSet('process','machine','user')]
    [String]$Scope = 'process'
  )

  $envPath = Get-EnvironmentPath -Scope $Scope
  if (!(Test-EnvironmentPath -Scope $Scope -Path ($Path | Select-Object -ExpandProperty Path)))
  {
    throw 'Path not in PATH variable'
  }

  $result = $envPath | Where-Object -FilterScript {
    $_ -ine $Path
  }
  if ($PSCmdlet.ShouldProcess('PATH', "remove: $Path"))
  {
    Set-EnvironmentVariable -Name PATH -Value ($result -join ';')
  }
}