Tom Nijs
Tom Nijs

Reputation: 3962

PowerShell file existence check returns false negative

Quite simply, I need to check if a file exists in the user's home drive using PowerShell. This script will be executed on a fleet of machines so the path needs to be relative.

Current output:

# Create file named 'foo' in home dir
New-Item '~/foo'

# Check if the file exists
[System.IO.File]::Exists('~/foo')
# Returns false

Listing the file shows it definitely exists:

ls '~/foo'

Directory: C:\Users\tom_n


Mode                LastWriteTime         Length Name
----                -------------         ------ ----
-a----           20/02/2018 14:13              0 foo

Am I missing something obvious here? I also tested this with a file that has an actual size, also to no avail.

I'm appreciating any input

Upvotes: 3

Views: 844

Answers (3)

mklement0
mklement0

Reputation: 440162

tl;dr:

Don't use ~ - use $HOME to refer to the current user's home directory, typically inside "...":

[System.IO.File]::Exists("$HOME/foo")

Or, preferably, use PowerShell's Test-Path cmdlet:

Test-Path -LiteralPath "$HOME/foo"

Note:
* PowerShell and .NET types accept / and \ interchangeably as the path separators; with a view toward potential cross-platform compatibilty, choose /.
* Strictly speaking, since "$HOME/foo" is passed to Test-Path in argument mode (command-line style), enclosing in "..." isn't necessary in this case (try Write-Output $HOME/foo), but enclosing in "..." is a good habit to form, because it works in a wider range of scenarios.


PowerShell's ~ is not fully equivalent to ~ on Unix (in POSIX-like shells) and may not work the way you expect it to in PowerShell:

As explained in this TechNet article[1], ~ in PowerShell refers to the home location as defined by the current location's drive provider, which:

  • may or may not be the filesystem.
  • may or may not be defined.

On Windows, consider the following example, which uses the registry drive provider:

Set-location HKCU:\Software
Set-Location ~

which yields the following error:

Set-Location : Home location for this provider is not set. To set the home location, call "(get-psprovider 'Registry').Home = 'path'".
...

As you can see, because the current location was on a drive of the registry provider, ~ was interpreted as that provider's idea of the home location, which, however, happens not to be defined.

An additional crucial difference:

  • On Unix, ~ is a shell feature: it must be used unquoted, in which case it is expanded to the full, literal home-directory by the shell, before the target command sees the path, so that the target command sees a literal path and doesn't need to know about ~, and, in fact, the standard utilities do not know about ~, which you can verify by contrasting ls ~ (OK) with ls '~' (tries to list a file/dir literally named ~).

  • In PowerShell, ~ is a PowerShell drive-provider feature: ~ is passed as-is to drive-provider cmdlets such as Get-ChildItem and they interpret ~ as referring to the current drive's home location. External utilities (e.g., findstr.exe on Windows) and the .NET Framework do not follow this convention and therefore interpret the ~ as a literal filename.


By contrast, automatic variable $HOME is the PowerShell equivalent of Unix ~, with added flexibility:

While Unix ~ has to be unquoted in order to expand to the user's home directory, PowerShell's automatic $HOME variable can be referenced inside double-quoted strings as well (as part of normal string expansion (interpolation)).


Finally, .NET types such as [System.IO.File] themselves support neither ~ nor $HOME, but by using "$HOME/..." it is PowerShell that ensures that $HOME is replaced with the actual, literal home-directory path before the path string is passed to a .NET method.


[1] Get-Help about_Locations and Get-Help about_Path_Syntax, the official help topics on the subject, should contain information about ~, but as of this writing do not.

Upvotes: 3

Michele Federici
Michele Federici

Reputation: 1835

I think the problem is that the Exists() does not resolve the home dir specified with ~. Try using relative or absolute path

Upvotes: 1

JPBlanc
JPBlanc

Reputation: 72680

The PowerShell CmdLet for that is Test-Path :

Test-Path '~/foo'

You can use the Windows file represention with .NET classes

[System.IO.File]::Exists('.\foo')

Upvotes: 2

Related Questions