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
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:
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.
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')
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With