PathUtils.psm1

<#
.Synopsis
finds the specified command on system PATH
.Description
uses `where` command to find commands on PATH
#>

function Find-CommandOnPath {
    [CmdletBinding()]
    param([Parameter(Mandatory=$true)]$wally, [switch][bool]$useShellExecute = $true) 
    $usePsFallback = $false
    if ($PSVersionTable.PSVersion.Major -lt 5) {
        $useShellExecute = $true       
        $usePsFallback = $true 
    }
    if ($useShellExecute) {
        $p = cmd /c "where $wally"
        if ($p -ne $null) { 
            return @($p) | % { 
                new-object pscustomobject -property @{
                    CommandType = "Application"
                    Name = (split-path $_ -leaf)
                    Source = $_
                    Version = $null
                }
            }
        }
    }
    if (!$useShellExecute -or $usePsFallback)  
    {
        # todo: use pure-powershell method
        return Get-Command $wally -ErrorAction Ignore
    }

    return $null
}

<#
.Synopsis
Set Environment variable
.Parameter name
variable name
.Parameter val
variable value
.Parameter user
set variable in user scope (persistent)
.Parameter machine
set variable in machine scope (persistent)
.Parameter current
(default=true) set variable in current's process scope
#>

function Set-EnvVar([Parameter(Mandatory=$true)][string]$name, [Parameter(Mandatory=$true)] $val, [switch][bool]$user, [switch][bool]$machine, [switch][bool]$current = $true){
    if ($current) {
        write-host "scope=Process: setting env var '$name' to '$val'" 
        [System.Environment]::SetEnvironmentVariable($name, $val, [System.EnvironmentVariableTarget]::Process);
    }
    if ($user) {
        write-host "scope=User: setting env var '$name' to '$val'"
        [System.Environment]::SetEnvironmentVariable($name, $val, [System.EnvironmentVariableTarget]::User);
    }
    if ($machine) {
        write-host "scope=Machine setting env var '$name' to '$val'"
        [System.Environment]::SetEnvironmentVariable($name, $val, [System.EnvironmentVariableTarget]::Machine);
    }
}

<#
.Synopsis
Get Environment variable value
.Parameter name
variable name
.Parameter user
get variable from user scope (persistent)
.Parameter machine
get variable from machine scope (persistent)
.Parameter current
(default=true) get variable from current process scope
#>

function Get-EnvVar([Parameter(Mandatory=$true)][string]$name, [switch][bool]$user, [switch][bool]$machine, [Alias("process")][switch][bool]$current){
    $val = @()
    if ($user) {
        $val += [System.Environment]::GetEnvironmentVariable($name, [System.EnvironmentVariableTarget]::User);
    }
    if ($machine) {
        $val += [System.Environment]::GetEnvironmentVariable($name, [System.EnvironmentVariableTarget]::Machine);
    }
    if (!$user.IsPresent -and !$machine.IsPresent) {
        $current = $true
    }
    if ($current) {
        $val = invoke-expression "`$env:$name"
    }
    if ($val -ne $null) {
        $p = $val.Split(';')
    } else {
        $p = @()
    }
    
    return $p
}


<#
.Synopsis
Add s value to enviroment variable (like PATH)
.Description
assumes that lists are separated by `;`
.Parameter name
variable name
.Parameter path
path to add to the list
.Parameter persistent
save the variable in machine scope
.Parameter first
preppend the value instead of appending
#>

function Add-ToEnvVar {
    [CmdletBinding()]
param(
    [Parameter(Mandatory=$true)][string]$name, 
    [Parameter(valuefrompipeline=$true)]$path, 
    [switch][bool] $persistent, 
    [switch][bool] $first
)
  
process {
      
    $p = get-envvar $name
    $p = @($p | % { $_.trimend("\") })

    $paths = @($path) 
    foreach ($_ in $paths) { 
        $path = $_.replace("/","\").trimend("\")
        if ($p -contains $path) {
            write-verbose "Env var '$name' already contains path '$path'"
            continue
        }
        write-verbose "adding $path to $name"
        if ($first) {
            if ($path.length -eq 0 -or $path[0] -ine $path) {
                $p = @($path) + $p
            }
        }
        else {
            if ($path -inotin $p) {
                $p += $path
            }
        }
    }
    
    $val = [string]::Join(";",$p)
    
    Invoke-Expression "`$env:$name = `$val"

    [System.Environment]::SetEnvironmentVariable($name, $val, [System.EnvironmentVariableTarget]::Process);
    if ($persistent) {
          write-warning "saving global $name"
          [System.Environment]::SetEnvironmentVariable($name, $val, [System.EnvironmentVariableTarget]::Machine);
    }
}
}


<#
.Synopsis
Gets PATH env variable
.Parameter user
Get the value from user scope
.Parameter machine
(default) Get the value from machine scope
.Parameter process
Get the value from process scope
.Parameter all
Return values for each scope
#>

function Get-PathEnv {
[CmdLetBinding(DefaultParameterSetName="scoped")]
param(
    [Parameter(ParameterSetName="scoped")]
    [switch][bool]$user,
    [Parameter(ParameterSetName="scoped")] 
    [switch][bool]$machine, 
    [Alias("process")]
    [Parameter(ParameterSetName="scoped")]
    [switch][bool]$current, 
    [Parameter(ParameterSetName="all")][switch][bool]$all
)
 
    $scopespecified = $user.IsPresent -or $machine.IsPresent -or $current.IsPresent
    $path = @()
    $userpath = get-envvar "PATH" -user 
    if ($user) {
        $path += $userpath
    }
    $machinepath = get-envvar "PATH" -machine
    if ($machine -or !$scopespecified) {
        $path += $machinepath
    }
    if (!$user.IsPresent -and !$machine.IsPresent) {
        $current = $true
    }
    $currentPath = get-envvar "PATH" -current
    if ($current) {
        $path = $currentPath
    }
    
    if ($all) {
        $h = @{
            user = $userpath
            machine = $machinepath
            process = $currentPath
        }
        return @(
            "`r`n USER",
            " -----------",
            $h.user, 
            "`r`n MACHINE",
            " -----------",
            $h.machine, 
            "`r`n PROCESS",
            " -----------",
            $h.process
            )
    }
    
    return $path
}


<#
.SYNOPSIS
Adds the specified path to PATH env variable
.DESCRIPTION
assumes that paths on PATH are separated by `;`
.PARAMETER path
path to add to the list
.PARAMETER persistent
save the variable in machine scope
.PARAMETER first
preppend the value instead of appending
.PARAMETER user
save to user scope
#>

function Add-ToPath {
[CmdletBinding()]
param([Parameter(valuefrompipeline=$true)]$path, [Alias("p")][switch][bool] $persistent, [switch][bool]$first, [switch][bool] $user) 

process { 
    if ($user) {
        $p = Get-Pathenv -user
    } elseif ($persistent) {
        $p = Get-Pathenv -machine
    } else {
        $p = Get-Pathenv -process
    }
    $p = $p | % { $_.trimend("\") }
    $p = @($p)
    $paths = @($path) 
    $paths | % { 
        $path = $_.trimend("\")
        write-verbose "adding $path to PATH"
        if ($first) {
            if ($p.length -eq 0 -or $p[0] -ine $path) {
                $p = @($path) + $p
            }
        }
        else {
            if ($path -inotin $p) {
                $p += $path
            }
        }
    }
    
    if ($user) {
          write-warning "saving user PATH and adding to current proc: [string]::Join(";",$p)"
          [System.Environment]::SetEnvironmentVariable("PATH",  [string]::Join(";",$p), [System.EnvironmentVariableTarget]::User);
          #add also to process PATH
          add-topath $path -persistent:$false -first:$first
    } elseif ($persistent) {            
          write-warning "saving global PATH"
          [System.Environment]::SetEnvironmentVariable("PATH", [string]::Join(";",$p), [System.EnvironmentVariableTarget]::Machine);
          #add also to process PATH
          add-topath $path -persistent:$false -first:$first
    } else {
        $env:path = [string]::Join(";",$p);
        [System.Environment]::SetEnvironmentVariable("PATH", $env:path, [System.EnvironmentVariableTarget]::Process);
    }
}
}

<#
.Synopsis
removes path from PATH env variable
.Parameter path
path to remove-frompath
.Parameter persistent
save modified path in machine scope
#>

function Remove-FromPath {
[CmdletBinding()]
param(
    [Parameter(Mandatory=$true,ValueFromPipeline=$true)]$path, 
    [Alias("p")][switch][bool] $persistent
)

    $paths = @($path) 
    $p = $env:Path.Split(';')
    $defaultSlash = "\"
    $altSlash = "/" 
    $p = $p | % { $_.replace($altSlash, $defaultSlash).trimEnd($defaultSlash) }
    $removed = @()
    $paths | % { 
        $path = $_.replace($altSlash, $defaultSlash).trimEnd($defaultSlash)
        $found = $p | ? { $_ -ieq $path }
        if ($found -ne $null) {
            write-verbose "found $($found.count) matches"
            $p = @($p | ? { !($_ -ieq $path) })    
            $removed += $found
        }
    }

    if ($removed.length -eq 0) {
        write-warning "path '$paths' not found in PATH"
    }

  
    $env:path = [string]::Join(";",$p)

    [System.Environment]::SetEnvironmentVariable("PATH", $env:Path, [System.EnvironmentVariableTarget]::Process);
    if ($persistent) {
        write-warning "saving global PATH"
        [System.Environment]::SetEnvironmentVariable("PATH", $env:Path, [System.EnvironmentVariableTarget]::Machine);
    }
}

<#
    .Synopsis
    tests if specified path is in PATH env variable
    .Parameter path
    path to test-envpath
    .Parameter show
    should return the found path value

#>

function Test-EnvPath([Parameter(Mandatory=$true)]$path, [switch][bool]$show) {
    $paths = @($path) 
    $p = $env:Path.Split(';')
    $p = $p | % { $_.trimend("\") }
    $r = $true
    $path = $null
    $paths | % { 
        $path = $_       
        $found = $p | ? { $_ -imatch (escape-regex "$path") }
        if (@($found).Count -le 0) { 
            write-verbose "$path not found in PATH"
            $r = $false 
        }
        else {
            write-verbose "$path found in PATH"
        }
    }

    if ($show) {
        return $found
    }
    return $r
}


<#
    .synopsis
    Reloads specified env variable from Registry
    .parameter name
    variable name
#>

function Update-EnvVar {
    [CmdletBinding()]
    param([Parameter(Mandatory=$true)]$name, [switch][bool] $pathmode, [switch][bool] $force) 
    
    if ($name -ieq "PATH" -or $name -ieq "PATHEXT" -or $name -ieq "PSMODULEPATH") { 
        $pathmode = $true
    }
    $m = get-envvar $name -machine
    $u = get-envvar $name -user
    $p = get-envvar $name -current

    write-verbose " # machine $name :"
    write-verbose "$m"
    write-verbose " # user $name :"
    write-verbose "$u"
    write-verbose " # proc $name :"
    write-verbose "$p"

    if($p -ne $null -and !$force -and !$pathmode) {
         write-verbose "not overriding process variable $name"
         return 
    }

    if ($pathmode) {
        if ($force) {
            # will ignore current process paths and read from registry
            $path = @()
        } else {
            # will append paths from registry to current paths
            $path = @($p)
        }
        $toadd = $m | ? { $_ -cnotin $path }        
        $path += $toadd
        $toadd = $u | ? { $_ -cnotin $path }        
        $path += $toadd        
        $val = $path
    }
    else {
        if ($u -ne $null) { $val = $u }
        else { $val = $m }
    }

    if ($val -is [Array]) {
        $val = [string]::Join(";",$val)
        $val = $val.Trim(";")
    }

    
    set-item env:/$name -value $val
}

<#
    .synopsis
    reloads PATH and PsModulePath variables fro registry
#>

function Update-Env {
[CmdletBinding()]
param([Alias("all")][switch][bool] $force)
    $vars = [System.Environment]::GetEnvironmentVariables([EnvironmentVariableTarget]::Machine)
    foreach($v in $vars.GetEnumerator()) {
        update-envvar $v.name -force:$force
    }

    $vars = [System.Environment]::GetEnvironmentVariables([EnvironmentVariableTarget]::User)
    foreach($v in $vars.GetEnumerator()) {
        update-envvar $v.name -force:$force
    }
}

function Get-EscapedRegex($pattern) {
    return [Regex]::Escape($pattern)
}

<#
    .synopsis
    returns a string that is ecaped for REGEX use
#>

function Get-EscapedRegex([Parameter(ValueFromPipeline=$true,Position=0)]$pattern) {
    process {
        return [Regex]::Escape($pattern)
    }
}

<#
    .synopsis
    tests if given path is relative
#>

function Test-IsRelativePath([Parameter(Mandatory=$true)]$path) {
    if ([System.IO.Path]::isPathRooted($path)) { return $false }
    if ($path -match "(?<drive>^[a-zA-Z]*):(?<path>.*)") { return $false }
    return $true
}

<#
    .synopsis
    joins two paths and returns absolute path
    .parameter from
    base directory or file
    .parameter to
    second path to join

    .example
    Get full path for a file
    Get-AbsolutePath . file.txt
       
       c:\test\file.txt
#>

function Get-AbsolutePath([Parameter(Mandatory=$true)][Alias("dir")][string] $from,
[Parameter(Mandatory=$true)][string][Alias("fullname")] $to
) {
    if (test-path $from) { 
        $it = (gi $from)
        $dir = $it.fullname;  
        if (!$it.psiscontainer) {
            #this is a file, we need a directory
            $dir = split-path -Parent $dir
        }
    } 
    else { 
        $dir = $from 
    }
    
    $p = join-path $dir $to
    if (test-path $p) {
        return (gi $p).FullName
    }
    else {
        return $p
    }
}

<#
    .synopsis
    returns drive symbol for path (i.e. `c`)
#>

function Get-DriveSymbol([Parameter(Mandatory=$true)]$path) {
    if ($path -match "(?<drive>^[a-zA-Z]*):(?<path>.*)") { return $matches["drive"] }
    return $null
}

function Join-PathSeparated {
param([Parameter(Mandatory=$true)][string] $Path,
        [Parameter(Mandatory=$true)][string] $ChildPath,
        [string] $separator = "\"
     ) 
    return $path.TrimEnd($separator) + $separator + $ChildPath.TrimStart($separator)
}

<#
    .synopsis
    calculates relative path
    .description
    when given a base path, calculates relative path to the other file location
    .parameter from
    base path (will calculate path relative to this location)
    .parameter to
    target file or directory
#>

function Get-RelativePath {
[CmdletBinding()]
param(
[Parameter(Mandatory=$true)][Alias("dir")][string] $from,
[Parameter(Mandatory=$true)][string][Alias("fullname")] $to,
$separator = "\"
) 
    
    try {
        write-verbose "get relative paths of:"
        write-verbose "$from"
        write-verbose "$to"
    $dir = $from 
    $bothabsolute = !(test-ispathrelative $from) -and !(test-ispathrelative $to)
    if ($bothabsolute) { Write-Verbose "Both paths are absolute" }
    if (test-path $from) { 
        $it = (gi $from)
        if ((test-ispathrelative $from) -or $bothabsolute) {
             Write-Verbose "using full path for comparison: $($it.fullname)"
             $dir = $it.fullname 
            }  
        if (!$it.psiscontainer) {
            #this is a file, we need a directory
            $dir = split-path -Parent $dir
            write-verbose "changed 'from' from file to directory: $dir"
        }
    } else {
        write-verbose "path '$from' does not exist"
    }
    
    $FullName = $to 
    if ((test-path $to)) {
        if (((test-ispathrelative $to) -or $bothabsolute)) {
            $it = gi $to
            Write-Verbose "using full path for comparison: $($it.fullname)"
            $FullName = $it.fullname 
        }
        if ((get-drivesymbol $from) -ne (get-drivesymbol $to)) {
            $it = gi $to
            #maybe the drive symbol is just an alias?
            Write-Verbose "different drive symbols '$(get-drivesymbol $from)' and '$(get-drivesymbol $to)'. using full path for comparison: $($it.fullname)"
            $FullName = $it.fullname 
        }
    } else {
        write-verbose "path '$to' does not exist"
    }
    
    

    $issubdir = $FullName -match (escape-regex $dir)
    
    if ($issubdir) {
        $p = $FullName.Substring($Dir.length).Trim($separator)
    } 
    else {
        $commonPartLength = 0
        $lastslashidx = -10
        for($i = 0; $i -lt ([MAth]::Min($dir.Length, $FullName.Length)) -and $dir[$i] -ieq $FullName[$i]; $i++) {
            $commonPartLength++
            if($dir[$i] -eq $separator -or $dir[$i] -eq "/") {
                $lastslashidx = $i
            }
        }
        $commonPartLength = $lastslashidx + 1
        if ($commonPartLength -le 0) {
            throw "Items '$dir' and '$fullname' have no common path"
        }

        $commonDir = $FullName.Substring(0, $commonPartLength)
        $curdir = $dir.Substring($commonPartLength)
        $filerel = $fullname.Substring($commonPartLength)
        $level = $curdir.Trim($separator).Split($separator).Length    
        $val = ""
        $dots = $val
        1..$level | % { $dots += "$separator.." }

        $p = Join-PathSeparated $dots $filerel -separator $separator
    }

    return $p.Trim($separator)
    } catch {
        throw "failed to get relative path from '$from' to '$to': $($_.Exception)`r`n$($_.ScriptStackTrace)"
    }
}

<#

#>

Function Get-ShortPath ([Parameter(ValueFromPipeline=$true)]$path)
{
 BEGIN { 
    $fso = New-Object -ComObject Scripting.FileSystemObject 
}

PROCESS {
  if ($path -is [string]) {
    $path = gi $path
  }
  If ($path.psiscontainer)
  {
    $fso.getfolder($path.fullname).ShortPath
  }

  ELSE {
    $fso.getfile($path.fullname).ShortPath
    } 
} 
}

<#
    .synopsis
    creates a Junction (File system directory link) at $path, targeting $target
#>

function New-Junction($path, $target) {
    cmd /C mklink /J $path $target
}

<#
    .synopsis
    checks if given path is a Junction (File system directory link)
#>

function Test-Junction([Parameter(Mandatory=$true)]$path) {
    $_ = get-item $path
    $mode = "$($_.Mode)$(if($_.Attributes -band [IO.FileAttributes]::ReparsePoint) {'J'})"
    return $mode -match "J"        
}

<#
    .synopsis
    return junction taget directory
#>

function Get-JunctionTarget([Parameter(Mandatory=$true)]$p_path)
{
    fsutil reparsepoint query $p_path | where-object { $_ -imatch 'Print Name:' } | foreach-object { $_ -replace 'Print Name\:\s*','' }
}

<#
    .synopsis
    installs a module as a linked directory on `PsModulePath`
    .description
    this function creates a link to a module
    in `C:\Program Files\WindowsPowershell\Modules` directory
    .parameter modulepath
    path to installed module (may be a adirectory or .psm1 file)
    .parameter modulename
    if `modulepath` contains multiple modules, specify a module name
#>

function Install-ModuleLink {
    [CmdletBinding(SupportsShouldProcess=$true)]
    param([Parameter(mandatory=$true)][string]$modulepath,
        [Parameter(mandatory=$false)]$modulename) 
    
    $target = $modulepath
    if ($target.EndsWith(".psm1")) {
        $target = split-path -parent ((get-item $target).FullName)    
    }
    $target = (get-item $target).FullName
    if ($modulename -eq $null) {
        $modulename = split-path -leaf $target
    }
    $path = "C:\Program Files\WindowsPowershell\Modules\$modulename"
    if (test-path $path) {
        if ($PSCmdlet.ShouldProcess("removing path $path")) {
            # packagemanagement module may be locking some files in existing module dir
            if (gmo powershellget) { rmo powershellget }
            if (gmo packagemanagement) { rmo packagemanagement }
            remove-item -Recurse $path -force
            if (test-path $path) { remove-item -Recurse $path -force }
        }
    }
    write-host "executing mklink /J $path $target"
    cmd /C "mklink /J ""$path"" ""$target"""
}

<#
    .synopsis
    updates specified linked module from source control
    .description
    if a module is a linked directory, that is under
    GIT or HG source control, then pull the newest changes and
    update the repo
#>

function Update-ModuleLink {
    [CmdletBinding(SupportsShouldProcess=$false)]
    param([Parameter(mandatory=$false)]$module)
    $modulename = $module
    if ($modulename -eq $null) {
        $modules = Get-ChildItem "C:\Program Files\WindowsPowershell\Modules" | ? {
             $_.PsIsContainer -and (Test-Junction $_.FullName) 
        }

        $modules | %{
            Update-ModuleLink $_.name
        }
        return
    }
    $path = "C:\Program Files\WindowsPowershell\Modules\$modulename"
    if (test-path $path) {
        pushd
        try {
            if (!(test-junction $path)) {
                throw "'$path' does not seem like a junction to me"
            }
            $target = get-junctiontarget $path
            cd $target
            if (".hg","..\.hg","..\..\.hg" | ? { test-path $_ } ) {
                write-host "running hg pull in '$target'"
                hg pull
                hg update
            } elseif (".git","..\.git","..\..\.git" | ? { test-path $_ } ) {
                write-host "running git pull in '$target'"
                git pull
            } else {
                throw "path '$target' does not contain any recognizable VCS repo (git, hg)"
            }
        }
        finally {
            popd
        }
    }
    else {
        throw "Module '$module' not found at path '$path'"
    }
    
}
function Get-Listing
(
    [string] $Path = ".", 
    $Excludes = @(), 
    [Alias("Recurse")]
    [switch] $Recursive,
    
    [string] $Filter = $null,
    $include = @(),
    [switch][bool] $Files,
    [switch][bool] $Dirs,
    $maxLevel = $null
) {

    $r = _GetListing @PSBoundParameters
    @($r) | write-output
}

function _GetListing
(
    [string] $Path = ".", 
    $Excludes = @(), 
    [Alias("Recurse")]
    [switch] $Recursive,
    
    [string] $Filter = $null,
    $include = @(),
    [switch][bool] $Files = $false,
    [switch][bool] $Dirs = $false,
    $level = 0,
    $total = @(),
    $maxLevel = $null,
    $OriginalPath = $null
)
{
try {
    if ($OriginalPath -eq $null) { $OriginalPath =  (get-item $path).FullName.Replace("\","/") }
    $result = @()
    if (($Path -eq $null) -or ($Path.Trim() -eq "")) {
        #return $result
    } 
   if (!$dirs.ispresent -and !$files.ispresent) {
       # get dirs by default
       $dirs = $true
       $files = $true
   }
   #$Excludes = @($Excludes | % { $_ -replace "\\","/" })

    if ($Recurse) { $Recursive = $Recurse }
    
    Write-Progress -Activity "getting subdirs. Items Found = $($total.length)" -Status "path=$Path"
    $fullpath = (get-item $path).FullName
    
    if (($fullpath -eq $null) -or ($fullpath.Trim() -eq "")) {
        write-warning "cannot find or resolve path '$path'"
        #return $result
    } 
    $path = $fullpath
    try {
        $topDirs = Get-ChildItem $Path -ErrorAction Stop
    } catch {
        write-error "failed to get child items for path '$path': $_"
        $topDirs = @()
    }
    $topDirs = $topDirs | where { 
        $a = $_
        $dirname = "$($a.FullName.Replace("\","/").Substring($OriginalPath.length).Trim("/"))/"
        $matchingExcludes = ($Excludes | where { 
                $dirname -match "$_"
              })
        return $_.PSIsContainer `
        -and ($_.Name -ne $null) `
        -and ($matchingExcludes -eq $null)
    } 
    
    if ($Dirs) {
        $f = $topDirs | where { 
            $a = $_
            $dirname = "$($a.FullName.Replace("\","/").Substring($OriginalPath.length).Trim("/"))/"      
            $_ -ne $null `
            -and ([string]::IsNullOrEmpty($Filter) -or $_.Name -like $Filter) `
            -and ([string]::IsNullOrEmpty($include) -or $dirname -match $include) `
            }
        if (@($f).Length -gt 0) {
            $result += $f
            $total += $f
            @($f) | write-output 
        }
    }
    if ($Files) {
        try {
            if (!(test-path $Path)) {
                write-warning "path '$Path' not found"
            }
            $ls = Get-ChildItem $path -Filter:$Filter -Recurse:$false | ? { !$_.PSIsContainer }   
            if ($include -ne $null -and $include.length -gt 0) {
                $ls = $ls | ? {
                    $it = $_
                    $name = $it.name
                    $matchingIncludes = $include | ? {
                        ## handle simple globbing case (i.e. *.exe)
                        if ($_.Startswith("*")) {
                            $_ = [Regex]::Escape($_.Substring(1))
                            $_ = ".*" + $_
                        }
                        $name -match $_
                    }
                    return $matchingIncludes -ne $null
                }
            }
            if (@($ls).Count -gt 0) {      
                $result += $ls
                $total += $ls
                @($ls) | write-output
            }
        } catch {
            write-error "failed to get child items for path '$path': $_"
        }
    }
    
    if ($Recursive -and ($maxlevel -eq $null -or $maxlevel -gt $level)) {
        foreach($dir in $topDirs) {
            if ([string]::IsNullOrEmpty($dir)) {
                Write-Warning "empty dir!"
            }
            if ($dirs `
                -and (![string]::IsNullOrEmpty($Filter) -and $dir.Name -like $Filter) `
                -and (![string]::IsNullOrEmpty($include) -and $dir.Name -match $include)) {
                #do not recurse into matching dirs
                continue
            }
            else {
                try {
                    $f = _GetListing -Path $dir.FullName -Excludes $Excludes -Recursive:$Recursive -Filter:$Filter -Files:$Files -Dirs:$Dirs -include:$include -level ($level+1) -total $total -maxLevel $maxlevel -OriginalPath $OriginalPath
                  
                    if (@($f).Length -gt 0) {
                        $result += $f
                        $total += $f
                        @($f) | write-output
                    }
                    
                } catch {
                    throw
                }
            }
        }
    }

    #return $result | where { $_ -ne $null }
    }
    catch {
        throw
    }
    finally {
        if ($level -eq 0) { 
            Write-Progress -Activity "getting subdirs. Items Found = $($total.length)" -Status "DONE" -PercentComplete 100 -Completed 
        }
    }
}


function find-upwards($pattern, $path = "." ) {
        $path = (get-item $path).FullName
        $foundfile = $null
        if (!(get-item $path).PsIsContainer) {
            $dir = split-path -Parent $path
        }
        else {
            $dir = $path
        }
        while(![string]::IsNullOrEmpty($dir)) {
            if (($pattern | % { "$dir/$_" } | test-path) -eq $true) {
                $reporoot = $dir
                $foundfile = $pattern | % { "$dir/$_" } | ? { test-path $_ } | select -First 1
                break;
            }
            $dir = split-path -Parent $dir
        }
        
        return $foundfile
}

new-alias get-childitemsfiltered get-listing
new-alias Where-Is Find-CommandOnPath
new-alias Refresh-Env update-env
# refreshenv alias might be already declared by chocolatey
if ((get-alias refreshenv -erroraction Ignore) -eq $null) {
    new-alias RefreshEnv refresh-env
}
#new-alias Contains-Path test-envpath
new-alias Escape-Regex get-escapedregex
new-alias Test-IsPathRelative Test-IsRelativePath
new-alias Get-PathRelative Get-RelativePath
new-alias Test-IsJunction Test-Junction

Export-moduleMember -Function * -Alias *