public/Get-Up.ps1

<#
.SYNOPSIS
Gets the path of an ancestor directory, either by name or by traversing upwards
by the given number of levels.
 
.PARAMETER n
Number of levels above the starting location. (One by default.)
 
.PARAMETER NamePart
Partial directory name for which to search.
 
.PARAMETER From
The directory from which to start. $PWD by default.
 
.EXAMPLE
# Get the parent of the current location
C:\Windows\System32> Get-Up
C:\Windows
C:\Windows\System32> _
 
.EXAMPLE
# Get the grandparent of the current location
C:\Windows\System32\drivers\etc> Get-Up 2
C:\Windows\System32
C:\Windows\System32\drivers\etc> _
 
.EXAMPLE
# Get the first ancestor containing the term 'win'
C:\Windows\System32\drivers\etc> Get-Up win
C:\Windows
C:\Windows\System32\drivers\etc> _
 
.LINK
Undo-Location
#>

function Get-Up {
  [CmdletBinding(DefaultParameterSetName = 'levels')]
  param(
    [Parameter(ParameterSetName = 'levels', Position = 0)] [byte]$n = 1,
    [Parameter(ParameterSetName = 'named', Position = 0)] [string]$NamePart,
    [string] $From = $PWD
  )

  $next = $From | Resolve-Path

  try {

    if ($PSCmdlet.ParameterSetName -eq 'levels' -and $n -ge 1) {

      1..$n | % {
        if ($parent = $next | Split-Path) { $next = $parent }
        else { $next = ($From | Resolve-Path).Drive.Root } # fixes issue on Linux
      }

      return $next
    }

    if ($PSCmdlet.ParameterSetName -eq 'named') {

      if ($next.Drive.Root -eq $NamePart) { return $NamePart }

      while ($next = $next | Split-Path) {
        if (($next | Split-Path -Leaf) -match (NormaliseAndEscape $NamePart)) { return $next }
      }

      # if we couldn't match by leaf name then match by complete path
      # this is only really used for completion when MenuCompletion is off
      $next = $From | Resolve-Path | select -Expand Path
      $resolvedTarget = Resolve-Path $NamePart -ErrorAction Ignore | Select -Expand Path

      do {
        if ($next -eq $resolvedTarget) { return $next }
      } while ($next = $next | Split-Path)
    }
  }

  catch [Management.Automation.PSArgumentException] {
    WriteLog "$_"
    $Global:Error.RemoveAt(0)
  }

  Write-Error "Could not find '$NamePart' as an ancestor of '$From'." -ErrorAction Stop
}