Finding Programs In PowerShell

PowerShell is pretty powerful, but of course a lot of what you do is put other commands and programs together to build a solution. Writing scripts that can find what they need in the environment you run them on is quite handy.

Paths

Current directory

First, let's get oriented. Whenever something needs to resolve a relative path, typically it'll be done from the current path. This is %CD% in cmd, and in PowerShell you can get this through Get-Command.

PS > Get-Location

Path
----
C:\Users\theuser

Note that this returns a PathInfo object which has with a Path property. If you want to do something with that path, for example append a string, you'll need to get that property.

Write-Host ((Get-Location).Path + "\foo")
C:\Users\theuser\foo

If you care for it, the alias for Get-Location is pwd, the Unix command to print the working directory.

Script directory

While this is handy, often what you need to know is where the script that you're running is on the file system, so you can find other scripts next to it or perhaps look for other files you've placed related to it.

What you want to use here is the $PSScriptRoot variable. Note that this won't be set if you're running interactively, so try it out by writing Write-Host $PSScriptRoot into a .ps1 file, then run it.

In my case, I named the file C:\Users\theuser\AppData\Local\Temp\ps.scratch\foo.ps1.

PS > .\foo.ps1
C:\Users\theuser\AppData\Local\Temp\ps.scratch

Files

Let's say you want to find all markdown files under a directory.

PS > Get-ChildItem somedir\*.md -Recurse

You can start off the current directory by avoiding the somedir\ bit, and you can look in a single directory by removing -Recurse.

The alias for Get-ChildItem is, perhaps unsurprisingly, dir.

Now, just listing everything isn't very useful. Typically you'll want to do something with these. You can pipe the output, or you can assign the results to a variable. This will yield an array of System.IO.FileSystemInfo objects.

The most common properties you'll access are Name (without any directory information) and FullName (including the directories).

You can also access the Directory.Name property and the Directory.FullName property, which give you the respective information but for the directory the file is in.

Path manipulation

There are a few ways of doing path manipulation in powershell. Let's cover the ones you're most likely to use.

If you have a proper object like a FileSystemInfo object, then you have a number of properties like Name, FullName and Extension. Those can come quite handy.

PowerShell has a Join-Path command, but it's quite a bit trickier to use than you probably care to. While in principle it has the benefit of working on things other than file systems and a few extra features, if you are working with files, it's probably not what you want for simple operations.

My recommendation is to use the Path commands, for example Combine to concatenate paths, and GetDirectoryName with GetFileName to get the parts of a filename.

PS > [IO.Path]::Combine('a', 'b', 'c')
a\b\c

Commands

Finding commands

Batch files have where, and PowerShell has Get-Command. This will look at available PowerShell commands, but also scan the PATH directories for executable.

PS > (get-command find) | format-list

Name            : find.exe
CommandType     : Application
Definition      : C:\windows\system32\find.exe
Extension       : .exe
Path            : C:\windows\system32\find.exe
...

The Path property is the one you will care the most about.

Now, the script gets quite angry if it doesn't find what you're looking for. Here's a way of making that happier.

$c = (get-command blargh -ErrorAction SilentlyContinue).Path

if ($c) {
  write-host hello $c
} else {
  write-host "oh no"
}

OK, let's break this down.

All together

If you have installed git on Windows, it turns out there are a lot of utilities that are installed alongside that are very handy.

This little function can be used to find diff for example, putting together a few of the commands we've used above.

$ErrorActionPreference = "Stop"

function Find-Diff() {

$diff = (get-command diff -ErrorAction SilentlyContinue).Path
if (-Not $diff) {
  $git = (get-command git -ErrorAction SilentlyContinue).Path
  if (-Not $git) {
    throw "Unable to find diff or git on the path"
  }
  $gitcmddir = [IO.Path]::GetDirectoryName($git)
  $gitusrbindir = [IO.Path]::Combine($gitcmddir, "..", "usr", "bin")
  $diff = [IO.Path]::Combine($gitusrbindir, "diff.exe")
  If (-Not (Test-Path $diff)) {
    throw "Unable to find diff, expected it at $diff"
  }
}

return $diff

}

$diff = Find-Diff

& $diff --version

If you save and run this script, you should get the diff banner printed out, or possibly an error message if it cannot be found on your system.

One last word: for a list of common I/O tasks, make sure you check the Common I/O Tasks page.

Happy search!

Tags:  codingpowershell

Home