How do I check if a file is under a given directory, in PowerShell?
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".
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.
Something like this?
Get-ChildItem -Recurse $directory | Where-Object { $_.PSIsContainer -and ` $_.FullName -match "^$($file.Parent)" } | Select-Object -First 1