views:

357

answers:

4

I want to check if a file path is in a given directory (or one of its subdirectories), from PowerShell.

Right now I'm doing:

$file.StartsWith(  $directory, [StringComparison]::InvariantCultureIgnoreCase )

but I'm sure there are better ways.

I could do take $file.Directory and iterate over all .Parents, but I was hoping for something simpler.

EDIT: the file may not exist; I'm just looking at the path.

A: 

Something real quick:

14:47:28 PS>pwd

C:\Documents and Settings\me\Desktop

14:47:30 PS>$path = pwd

14:48:03 PS>$path

C:\Documents and Settings\me\Desktop

14:48:16 PS>$files = Get-ChildItem $path -recurse | Where {$_.Name -match "thisfiledoesnt.exist"}

14:50:55 PS>if($files) {write-host "the file exists in this path somewhere"}else{write-host "no it doesn't"} no it doesn't

(create new file on desktop or in a folder on the desktop and name it "thisfileexists.txt")

14:51:03 PS>$files = Get-ChildItem $path -recurse | Where {$_.Name -match "thisfileexists.txt"}

14:52:07 PS>if($files) {write-host "the file exists in this path somewhere"}else{write-host "no it doesn't"} the file exists in this path somewhere

Of course iterating is still happening, but PS is doing it for you. You also might need -force if looking for system/hidden files.

Bratch
I'm not interested in whether the file exists, but whether its path is under a directory. Also, your formatting is unclear.
Jay Bazuzi
Yeah, the code formatting here doesn't work well for PS. This is not a full PS1 file, just stuff typed on the PS cmd line. Your question asked "How do I check if a file is under a given directory..." It looks like the other answer is also checking for a file, as you asked. I see your edit, but how can a file have a path if it doesn't even exist?
Bratch
I think I get it now, you have this file path stored somewhere, and you want to see if the path part exists beneath some other specified path?
Bratch
+3  A: 

How about something as simple as:

PS> gci . -r foo.txt

This implicitly uses the -filter parameter (by position) specifying foo.txt as the filter. You could also specify *.txt or foo?.txt. The problem with StartsWith is that while you handle the case-insensitive compare there is still the issue that both / and \ are valid path separators in PowerShell.

Assuming the file may not exist and both $file and $directory are absolute paths, you can do this the "PowerShell" way:

(Split-Path $file -Parent) -replace '/','\' -eq (Get-Item $directory).FullName

But that isn't great since you still have to canonical the path / -> \ but at least the PowerShell string compare is case-insensitive. Another option is to use IO.Path to canonicalize the path e.g.:

[io.path]::GetDirectoryName($file) -eq [io.path]::GetFullPath($directory)

One issue with this is that GetFullPath will also make a relative path an absolute path based on the process's current dir which more times than not, is not the same as PowerShell's current dir. So just make sure $directory is an absolute path even if you have to specify it like "$pwd\$directory".

Keith Hill
+1. Being able to specify -path and -fil separately is very convenient. Learn it, love it.
Richard Berg
A: 

Something like this?

Get-ChildItem -Recurse $directory | Where-Object { $_.PSIsContainer -and `
    $_.FullName -match "^$($file.Parent)" } | Select-Object -First 1
guillermooo
A: 

Since the path might not exist, using string.StartsWith is fine for doing this type of test (though OrdinalIgnoreCase is a better representation of how the file system compares paths).

The only caveat is that the paths need to be in a canonical form. Otherwise, paths like C:\x\..\a\b.txt and C:/a/b.txt would fail the "is this under the C:\a\ directory" test. You can use the static Path.GetFullPath method to get the full names of the paths before you do the test:

function Test-SubPath( [string]$directory, [string]$subpath ) {
  $dPath = [IO.Path]::GetFullPath( $directory )
  $sPath = [IO.Path]::GetFullPath( $subpath )
  return $sPath.StartsWith( $dPath, [StringComparison]::OrdinalIgnoreCase )
}

Also note that this does not cover logical containment (e.g. if you have \\some\network\path\ mapped to Z:\path\, testing whether \\some\network\path\b.txt is under Z:\ will fail, even though the file can be accessed through Z:\path\b.txt). If you need to support this behavior, these questions might help.

Emperor XLII