A better way to check if a path exists or not in PowerShell

Powershell

Powershell Problem Overview


Is there a more concise and less error-prone way in PowerShell to check if a path DOES NOT exist?

This is objectively too verbose for such a common use case:

if (-not (Test-Path $path)) { ... }
if (!(Test-Path $path)) { ... }

It needs too many parenthesis and is not very readable when checking for "not exist". It's also error-prone because a statement like:

if (-not $non_existent_path | Test-Path) { $true } else { $false }

will actually return False, when the user may expect True.

What is a better way to do this?

Update 1: My current solution is to use aliases for exist and not-exist as explained here.

Update 2: A proposed syntax that will also fix this is to allow the following grammar:

if !(expr) { statements* }
if -not (expr) { statements* }

Here's the related issue in PowerShell repository (please vote up ): https://github.com/PowerShell/PowerShell/issues/1970

Powershell Solutions


Solution 1 - Powershell

If you just want an alternative to the cmdlet syntax, specifically for files, use the File.Exists() .NET method:

if(![System.IO.File]::Exists($path)){
    # file with path $path doesn't exist
}

If, on the other hand, you want a general purpose negated alias for Test-Path, here is how you should do it:

# Gather command meta data from the original Cmdlet (in this case, Test-Path)
$TestPathCmd = Get-Command Test-Path
$TestPathCmdMetaData = New-Object System.Management.Automation.CommandMetadata $TestPathCmd

# Use the static ProxyCommand.GetParamBlock method to copy 
# Test-Path's param block and CmdletBinding attribute
$Binding = [System.Management.Automation.ProxyCommand]::GetCmdletBindingAttribute($TestPathCmdMetaData)
$Params  = [System.Management.Automation.ProxyCommand]::GetParamBlock($TestPathCmdMetaData)

# Create wrapper for the command that proxies the parameters to Test-Path 
# using @PSBoundParameters, and negates any output with -not
$WrappedCommand = { 
    try { -not (Test-Path @PSBoundParameters) } catch { throw $_ }
}

# define your new function using the details above
$Function:notexists = '{0}param({1}) {2}' -f $Binding,$Params,$WrappedCommand

notexists will now behave exactly like Test-Path, but always return the opposite result:

PS C:\> Test-Path -Path "C:\Windows"
True
PS C:\> notexists -Path "C:\Windows"
False
PS C:\> notexists "C:\Windows" # positional parameter binding exactly like Test-Path
False

As you've already shown yourself, the opposite is quite easy, just alias exists to Test-Path:

PS C:\> New-Alias exists Test-Path
PS C:\> exists -Path "C:\Windows"
True

Solution 2 - Powershell

The alias solution you posted is clever, but I would argue against its use in scripts, for the same reason I don't like using any aliases in scripts; it tends to harm readability.

If this is something you want to add to your profile so you can type out quick commands or use it as a shell, then I could see that making sense.

You might consider piping instead:

if ($path | Test-Path) { ... }
if (-not ($path | Test-Path)) { ... }
if (!($path | Test-Path)) { ... }

Alternatively, for the negative approach, if appropriate for your code, you can make it a positive check then use else for the negative:

if (Test-Path $path) {
    throw "File already exists."
} else {
   # The thing you really wanted to do.
}

Solution 3 - Powershell

Add the following aliases. I think these should be made available in PowerShell by default:

function not-exist { -not (Test-Path $args) }
Set-Alias !exist not-exist -Option "Constant, AllScope"
Set-Alias exist Test-Path -Option "Constant, AllScope"

With that, the conditional statements will change to:

if (exist $path) { ... }

and

if (not-exist $path) { ... }
if (!exist $path) { ... }

Solution 4 - Powershell

This is my powershell newbie way of doing this

if ((Test-Path ".\Desktop\checkfile.txt") -eq $true) {
	Write-Host "Yay"
} 
else {
	Write-Host "Damn it"
}

Solution 5 - Powershell

Another option is to use IO.FileInfo which gives you so much file info it make life easier just using this type:

PS > mkdir C:\Temp
PS > dir C:\Temp\
PS > [IO.FileInfo] $foo = 'C:\Temp\foo.txt'
PS > $foo.Exists
False
PS > New-TemporaryFile | Move-Item -Destination C:\Temp\foo.txt
PS > $foo.Refresh()
PS > $foo.Exists
True
PS > $foo | Select-Object *


Mode              : -a----
VersionInfo       : File:             C:\Temp\foo.txt
                    InternalName:
                    OriginalFilename:
                    FileVersion:
                    FileDescription:
                    Product:
                    ProductVersion:
                    Debug:            False
                    Patched:          False
                    PreRelease:       False
                    PrivateBuild:     False
                    SpecialBuild:     False
                    Language:

BaseName          : foo
Target            : {}
LinkType          :
Length            : 0
DirectoryName     : C:\Temp
Directory         : C:\Temp
IsReadOnly        : False
FullName          : C:\Temp\foo.txt
Extension         : .txt
Name              : foo.txt
Exists            : True
CreationTime      : 2/27/2019 8:57:33 AM
CreationTimeUtc   : 2/27/2019 1:57:33 PM
LastAccessTime    : 2/27/2019 8:57:33 AM
LastAccessTimeUtc : 2/27/2019 1:57:33 PM
LastWriteTime     : 2/27/2019 8:57:33 AM
LastWriteTimeUtc  : 2/27/2019 1:57:33 PM
Attributes        : Archive

More details on my blog.

Solution 6 - Powershell

To check if a Path exists to a directory, use this one:

$pathToDirectory = "c:\program files\blahblah\"
if (![System.IO.Directory]::Exists($pathToDirectory))
{
 mkdir $path1
}

To check if a Path to a file exists use what @Mathias suggested:

[System.IO.File]::Exists($pathToAFile)

Solution 7 - Powershell

if (Test-Path C:\DockerVol\SelfCertSSL) {
    write-host "Folder already exists."
} else {
   New-Item -Path "C:\DockerVol\" -Name "SelfCertSSL" -ItemType "directory"
}

Solution 8 - Powershell

After looking at @Mathias R. Jessen's excellent answer, it occurred to me that you don't need to create two new functions. Instead, you can create a wrapper around the native Test-Path function with the same name that adds a -Not switch:

$TestPathCmd = Get-Command Test-Path
$TestPathCmdMetaData = New-Object System.Management.Automation.CommandMetadata $TestPathCmd
$Binding = [System.Management.Automation.ProxyCommand]::GetCmdletBindingAttribute($TestPathCmdMetaData)
$Params  = [System.Management.Automation.ProxyCommand]::GetParamBlock($TestPathCmdMetaData)

$Params += ', [Switch]${Not}'
$WrappedCommand = {
	$PSBoundParameters.Remove('Not') | Out-Null
	[Bool]($Not.ToBool() -bxor (Microsoft.PowerShell.Management\Test-Path @PSBoundParameters))
}

${Function:Test-Path} = '{0} Param({1}) {2}' -f $Binding,$Params,$WrappedCommand

E.g.:

Test-Path -Path 'C:\Temp'      # True
Test-Path -Path 'C:\Temp' -Not # False
Test-Path -Path 'C:\Txmp'      # False
Test-Path -Path 'C:\Txmp' -Not # True

This has a couple of advantages:

  1. Familiar syntax: when you're not using the custom switch, syntax is identical to the native command, and when you are it's pretty intuitive what's happening, which means less cognitive burden for the user, and more compatibility when sharing.
  2. Because the wrapper is calling the native function under the hood, it will work anywhere the native function does, e.g.:
    Test-Path -Path 'HKLM:\SOFTWARE'      # True
    Test-Path -Path 'HKLM:\SOFTWARE' -Not # False
    Test-Path -Path 'HKLM:\SXFTWARE'      # False
    Test-Path -Path 'HKLM:\SXFTWARE' -Not # True
    

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionoradView Question on Stackoverflow
Solution 1 - PowershellMathias R. JessenView Answer on Stackoverflow
Solution 2 - PowershellbriantistView Answer on Stackoverflow
Solution 3 - PowershelloradView Answer on Stackoverflow
Solution 4 - PowershellDavid BohbotView Answer on Stackoverflow
Solution 5 - PowershellVertigoRayView Answer on Stackoverflow
Solution 6 - Powershellshaheen gView Answer on Stackoverflow
Solution 7 - PowershellVivek RajView Answer on Stackoverflow
Solution 8 - PowershellnmbellView Answer on Stackoverflow