I’m not sure how many PowerShell peeps were once Unix shell dorks, but I would imagine that there are some out there. One command that I relied on heavily was the which command. For one reason or another, finding where the execution path of a file came in handy. Sometimes you may have different versions of the file floating around and you would like to know which one is actually being executed.
The script itself was born from a tiny function I had setup to list my path environment variable in a readable format:
function Get-EnvPath
{
$env:Path.Split(";") | where {[System.IO.Directory]::Exists($_)}
}
Which produces output like the following:
PS> Get-EnvPath
C:\Program Files (x86)\Microsoft Visual Studio 9.0\Common7\IDE
C:\Program Files (x86)\Microsoft Visual Studio 9.0\VC\BIN
C:\Program Files (x86)\Microsoft Visual Studio 9.0\Common7\Tools
C:\Windows\Microsoft.NET\Framework\v3.5
C:\Windows\Microsoft.NET\Framework\v2.0.50727
C:\Program Files (x86)\Microsoft Visual Studio 9.0\VC\VCPackages
C:\Program Files\\Microsoft SDKs\Windows\v6.0A\bin
C:\Windows\System32\WindowsPowerShell\v1.0\
C:\Windows\system32
C:\Windows
C:\Windows\System32\Wbem
C:\Program Files\Microsoft SQL Server\100\Tools\Binn\
C:\Program Files (x86)\PowerShell Community Extensions
C:\Program Files (x86)\Perforce\
We can now write our which function that searches through each of these paths for some file. My first implementation of it looked like the following:
function which
{
param([string]$name)
[System.IO.FileInfo[]]$theFindings = $()
foreach( $thePathDir in Get-EnvPath )
{
$thePotentialFile = Join-Path $thePathDir $name
if ( [System.IO.File]::Exists( $thePotentialFile ) )
{
$theFindings += new-object System.IO.FileInfo $thePotentialFile
}
} $theFindings
}
Not too shabby I guess. It works, but you can definitely see that the mode in which this function was written fell victim to my normal procedural ways. Sometimes (for me at least) it’s hard to switch into the PowerShell pipeline way of thinking.
After some deep meditation, the pipeline gods smacked me across the face and somehow a much simpler version of the function came to be:
function which
{
param([string]$name)
Get-ChildItem -path (Get-EnvPath) | where { $_.Name -eq $name }
}
To my pleasure, Get-ChildItem can take an array of paths to search through and therefore we are able to utilize the list that Get-EnvPath returns, only listing items that match the incoming name. Definitely much cleaner, but the runtime on it takes longer than I’d like (you can count 1-1000, 2-1000, etc). This is because the where filter is applied after all items have been retrieved, which can be quite large. Would be much better to filter while things were being looked at. Let’s change things around a bit to use the built-in filter parameter of Get-ChildItem, instead of passing to where filter:
function which
{
param([string]$name)
Get-ChildItem -path (Get-EnvPath) –filter $name
}
This runs order of magnitudes faster as things are being filtered in real time.
I mean… are you kidding me? That’s it? At first I was embarrassed with the initial attempt. It is like seven times more code than the final solution. However, if there’s one thing I’ve learned, it’s that we never write something perfect the first time. The process that it took to come up with the final solution was born from what was learned with the first attempt.
-
If you learned anything from this post and would enjoy more at some point, please feel free to subscribe via rss.
Doug 17:18 on July 10, 2008 Permalink |
Get-Command also provides this.
Get-Command cmdkey.exe
Name : cmdkey.exe
CommandType : Application
Definition : C:\Windows\system32\cmdkey.exe
Scott 17:52 on July 10, 2008 Permalink |
Doug,
Wow! Thanks for pointing that out. Get-Command is a much better way as it looks for more than just files. I Can’t believe I’ve never seen that. Oh well, it was fun exercise in learning to sharpen my dormant pipeline skills. :)
Thanks again for pointing that out.
halr9000 20:01 on July 14, 2008 Permalink |
There’s a TON of stuff in get-command. Try examining the results of ‘gcm ‘ and look at the Definition property.
Scott 08:02 on August 13, 2008 Permalink |
Actually, after using this a bit I realized that Get-Command does not necessarily tell you “which” one is going to be executed if you have multiple in your path. At least that’s what I’m finding. But changing the implementation to use Get-Command would probably be a good idea.
Doug 06:26 on August 6, 2010 Permalink |
Good post. I like the way you walked through the process.
The PowerShell Get-Command cmdlet does this.