There are many ways to skin a cat, and with PowerShell, that’s no exception. I’m going to show you three ways that I’m familiar with to skin check if a file exists.

© 2017 Microsoft; Nano Man 2: Server of the Realm
TL;DR
Here are the three methods I know in order of my personal preference:
1 2 3 |
PS > [IO.FileInfo] $foo = 'C:\Temp\foo.txt' PS > $foo.Exists True |
🔼📒 This is my personal preference because of how much other information you now have in the $foo variable. Read More …
1 2 |
PS > Test-Path 'C:\Temp\foo.txt' True |
🔼📒 This is great be of how simple, and feature rich this in. This supports wildcards and relational paths. Read More …
1 2 |
PS > [System.IO.File]::Exists('C:\Temp\foo.txt') True |
🔼📒 Just plain and simple check. No wildcard or relational path support. Read More …
Test-Path
This is probably the most common, and works well for a quick and simple solution to see if a file exists.
1 2 3 4 5 6 7 |
PS > Remove-Item 'C:\Temp\*' PS > Get-ChildItem 'C:\Temp\' PS > Test-Path 'C:\Temp\foo.txt' False PS > New-TemporaryFile | Move-Item -Destination 'C:\Temp\foo.txt' PS > Test-Path 'C:\Temp\foo.txt' True |
📒 I prefer quotes around strings like paths for cleanliness and readability in scripts, but you don’t need the quotes around the path names on any of those commands.
I don’t use quotes in the command line for speed. Powershell is expecting a string so it’ll figure it out:
1 2 3 4 5 6 7 |
PS > Remove-Item C:\Temp\* PS > Get-ChildItem C:\Temp\ PS > Test-Path C:\Temp\foo.txt False PS > New-TemporaryFile | Move-Item -Destination C:\Temp\foo.txt PS > Test-Path C:\Temp\foo.txt True |
This can also be used with piping. However, when piping, you definitely need the quotes because PowerShell has no expectations. This is another reason I prefer to just use quotes all the time:
1 2 3 4 5 6 7 8 |
PS > C:\Temp\foo.txt | Test-Path Cannot run a document in the middle of a pipeline: C:\Temp\foo.txt. At line:1 char:1 + C:\Temp\foo.txt | Test-Path + ~~~~~~~~~~~~~~~ + CategoryInfo : InvalidOperation: (C:\Temp\foo.txt:String) [], RuntimeException + FullyQualifiedErrorId : CantActivateDocumentInPipeline PS > 'C:\Temp\foo.txt' | Test-Path True |
If you want to test if there are any .txt files in a path or any files with a name that starts with a letter, this is doable with this function using a wildcard:
1 2 3 4 5 6 7 8 |
PS > Test-Path 'C:\Temp\*.txt' True PS > Test-Path 'C:\Temp\f*' True PS > Test-Path 'C:\Temp\f*.txt' True PS > Test-Path 'C:\Temp\bar*' False |
[System.IO.File]::Exists()
📒 [System] is the default root type, so things like [System.String] can be shortcutted to [String] .
I’ll be shortcutting [System.IO.File] to just [IO.File] . An alternative you can use is the [System.IO.File]::Exists() which accesses the .NET method to see if the file exists. This is the simplest (aka least feature rich) test, and I’m not really sure why you’d use this over Test-Path . However, it’s here for completeness.
1 2 3 4 5 6 7 |
PS > Remove-Item 'C:\Temp\*' PS > Get-ChildItem 'C:\Temp\' PS > [System.IO.File]::Exists('C:\Temp\foo.txt') False PS > New-TemporaryFile | Move-Item -Destination 'C:\Temp\foo.txt' PS > [System.IO.File]::Exists('C:\Temp\foo.txt') True |
Wildcard tests are not supported by this .NET method.
[System.IO.FileInfo]
📒
[System] is the default root type, so things like
[System.String] can be shortcutted to
[String] .
I’ll be shortcutting
[System.IO.FileInfo] to just
[IO.FileInfo] .
Using the [IO.FileInfo] type is my personal preference when working with files because it gives me a ton of information about the file with a single call. Information that I would have to use a host of tools to get, including:
- Get-Item
- Get-ItemProperty
- Split-Path
- Test-Path
- etc
1 2 3 4 5 6 7 8 9 10 11 |
PS > Remove-Item 'C:\Temp\*' PS > Get-ChildItem '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.Exists False PS > $foo.Refresh() PS > $foo.Exists True |
As you just saw, you can check if your referenced file exists with the .Exists property. However, the object isn’t dynamically updated so you do have to refresh the object to see changes to the file. Using the [IO.FileInfo] type, you have the added benefit of a bunch of additional file information; go figure:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 |
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 |
Additionally, DirectoryName is a [String] , but Directory is an [IO.DirectoryInfo] type:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
PS > $foo.Directory | Select-Object * Mode : d---- ModeWithoutHardLink : d---- BaseName : Temp Target : LinkType : Parent : C:\ Root : C:\ FullName : C:\Temp Extension : Name : Temp 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 : Directory |
Also, that object’s Parent property is also an [IO.DirectoryInfo] type:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
PS > $foo.Directory.Parent | Select-Object * Mode : d--hs- BaseName : C: Target : {} LinkType : Parent : Root : C: FullName : C: Extension : Name : C: Exists : True CreationTime : 3/18/2017 6:40:20 AM CreationTimeUtc : 3/18/2017 11:40:20 AM LastAccessTime : 2/25/2019 10:03:41 AM LastAccessTimeUtc : 2/25/2019 3:03:41 PM LastWriteTime : 2/25/2019 10:03:41 AM LastWriteTimeUtc : 2/25/2019 3:03:41 PM Attributes : Hidden, System, Directory |
Sure, you could just cd .. to get up a directory and get higher level information. What if you’re programmatically looking through files at a location that’s not your present working directory ( $pwd )? You can easily pull the file’s version of cd .. with this:
1 2 3 4 5 |
PS > $foo.Directory.Parent Mode LastWriteTime Length Name ---- ------------- ------ ---- d--hs 2/25/2019 10:03 AM C:\ |
If we were deeper in, we could traverse as high as we want:
1 2 3 4 5 |
PS > New-Item -Type Directory -Path 'C:\Temp\a\b\c\d\e\f\g\' -Force | Out-Null PS > New-TemporaryFile | Move-Item -Destination 'C:\Temp\a\b\c\d\e\f\g\foo.txt' PS > [IO.FileInfo] $foo = 'C:\Temp\a\b\c\d\e\f\g\foo.txt' PS > $foo.Directory.Parent.Parent.Parent.Parent.Parent.FullName C:\Temp\a\b |