2

I'm new to powershell and I want to check if file in readable and regular. In unix we can do it in one line by using -f & -r. For example the following shell script function accepts filename as argument and checks the readability and regularity of file, whats the powershell equivalent for this?

_ChkRegularFile_R()        # checks whether a file is regular and readable
{   
    _CRFRfilename=$1                    # name of the file to be checked

_CRFRsts=1                          # default is error

if [ -f "$_CRFRfilename" ]
then
    if [ -r "$_CRFRfilename" ]
    then
        _CRFRsts=0        # success: regular file is readable
    fi
fi

return $_CRFRsts
}
Mayank
  • 113
  • 3
  • 15
  • 2
    Well, windows doesn't really work the same as *nix. The idea of a file being regular doesn't exist as such in windows. Are you doing this in windows or *nix? – EBGreen Feb 26 '18 at 20:01
  • 1
    fyi, your function can be written like `_ChkRegularFile_R() { [ -f "$1" ] && [ -r "$1" ]; }` – glenn jackman Feb 26 '18 at 20:25
  • To clarify: The file-test operator `-f` in POSIX-like shells performs implicit symlink resolution; that is, it returns true if the argument is a regular file, or, if it is a _symlink_, whether its (ultimate) _target_ is a regular file. – mklement0 Feb 27 '18 at 22:59
  • Basically I'm coverting shell scripts to equivalent powershell scripts, so these powershell scripts will be running inwindows only @EBGreen – Mayank Feb 28 '18 at 17:39
  • Then you don't need to worry about whether it is a regular file or not. – EBGreen Feb 28 '18 at 17:40

3 Answers3

4

To test if a file is readable, you try to open it. If you get an error, then it's not readable. You need to either trap or catch exceptions or stop on errors, as appropriate. Remember, Windows locks files that are open for writing, so applications need to expect that they sometimes can't open a file.

If you absolutely have to, you can use something like this to test if you can read a file:

try {
    [System.IO.File]::OpenRead($FullPathName).Close()
    $Readable = $true
}
catch {
    $Readable = $false        
}

And this to test if you can write to a file:

try {
    [System.IO.File]::OpenWrite($FullPathName).Close()
    $Writable = $true
}
catch {
    $Writable = $false        
}

That logic is fairly easy to wrap into a function if you really need it.

As far as file types, nearly everything in the file system in Windows is a plain file or a directory, since Windows doesn't have the "everything is a file" convention. So, normally you can test as follows:

# Test if file-like
Test-Path -Path $Path -Leaf

# Test if directory-like
Test-Path -Path $Path -Container

If you're working with a FileInfo or DirectoryInfo object (i.e., the output of Get-Item, Get-ChildItem, or a similar object representing a file or directory) you'll have the PSIsContainer property which will tell you if the item is a file or a directory.

That covers probably 99.999% of cases.


However, if you need to know if something is an NTFS hard link to a file (rare, but oldest), an NTFS junction to a directory, an NTFS symlink, an NTFS volume mount point, or any type of NTFS reparse point, it gets much more complicated. [This answer does a good job describing the first three.]

Let's create a simple NTFS folder to test with:

# Create a test directory and change to it.
New-Item -Path C:\linktest -ItemType Directory | Select-Object -ExpandProperty FullName | Push-Location

# Create an empty file
New-Item -Path .\file1 -ItemType file -Value $null | Out-Null
New-Item -Path .\file2 -ItemType file -Value $null | Out-Null

# Create a directory
New-Item -Path .\dir1 -ItemType Directory | Out-Null

# Create a symlink to the file
New-Item -ItemType SymbolicLink -Path .\sfile1 -Value .\file1 | Out-Null

# Create a symlink to the folder
New-Item -ItemType SymbolicLink -Path .\sdir1 -Value .\dir1 | Out-Null

# Create a hard link to the file
New-Item -ItemType HardLink -Path .\hfile1 -Value .\file1 | Out-Null

# Create a junction  to the folder
New-Item -ItemType Junction -Path .\jdir1 -Value .\dir1 | Out-Null

# View the item properties
Get-ChildItem -Path . | Sort-Object Name | Format-Table -Property Name, PSIsContainer, LinkType, Target, Attributes -AutoSize

Your output will be:

Name   PSIsContainer LinkType     Target                            Attributes
----   ------------- --------     ------                            ----------
dir1            True              {}                                 Directory
file1          False HardLink     {C:\linktest\hfile1}                 Archive
file2          False              {}                                   Archive
hfile1         False HardLink     {C:\linktest\file1}                  Archive
jdir1           True Junction     {C:\linktest\dir1}   Directory, ReparsePoint
sdir1           True SymbolicLink {C:\linktest\dir1}   Directory, ReparsePoint
sfile1         False SymbolicLink {C:\linktest\file1}    Archive, ReparsePoint

Note that both file1 and hfile1 are hard links, even though file1 wasn't created as such.

To clean up the above garbage, do:

Get-ChildItem -Path C:\linktest\ | ForEach-Object { $_.Delete() }

There's a bug in Remove-Item with deleting some container links which prevents the command from removing the items.

The general solution would be to get the item and test it:

# Get the item. Don't use Get-ChildItem because that will get a directory's contents
$Item = Get-Item -Path $Path

# Is it a container
$Item.PSIsContainer

# Is it a link of some kind?
[System.String]::IsNullOrWhiteSpace($Item.LinkType)
$Item.LinkType -eq 'Junction'

# Is it a Reparse Point?
($Item.Attributes -band [System.IO.FileAttributes]::ReparsePoint) -eq [System.IO.FileAttributes]::ReparsePoint

There are several other potential attributes, too:

PS> [System.Enum]::GetNames([System.IO.FileAttributes])
ReadOnly
Hidden
System
Directory
Archive
Device
Normal
Temporary
SparseFile
ReparsePoint
Compressed
Offline
NotContentIndexed
Encrypted
IntegrityStream
NoScrubData

Note that Device is documented as reserved for future use. Ain't no device file type in Windows.

For volume mount points, I'm not 100% sure how those look. I know you can create them on Windows 8.1 and later with Get-Partition followed by an appropriate Add-PartitionAccessPath, but I'm on Windows 7 currently. I'm afraid I have no means of testing this at the moment.

Finally, I have no idea how exactly PowerShell Core 6.0 on Linux handles file types.

Bacon Bits
  • 30,782
  • 5
  • 59
  • 66
  • Nicely done; I suggest using `-LiteralPath` in your commands (except for the `New-Item` calls, which support _only_ `-Path`, but with `-LiteralPath` semantics) lest the paths be interpreted as wildcard patterns (the OP's example doesn't involve globbing). A simpler - though less obvious - way to get a Boolean indicating whether a file is a link: `[bool] $Item.LinkType`. – mklement0 Feb 26 '18 at 22:49
  • 1
    @mklement0 When `-LiteralPath` is the default parameter instead of `-Path`, I will consider switching. As it is, my answers are intended to be educational. I specify the parameters and avoid aliases in my answers for clarity. `-LiteralPath` is designed to be the special case, not the default. – Bacon Bits Feb 27 '18 at 04:19
0

Soooo,,,,

This is not something I regulary do, but if memory serves. In *nix, a regular file contains data, is a direcotry,
Again, not somehting I do/have to worry about under normal PoSH stuff.

So you are testing for where the object is a writable file (and / or non-zero) or a directory or binary?

So, in PoSH, prior to v3... you do something like this...

$IsDir = {$_.PsIsContainer}
$IsFile = {!$_.PsIsContainer}

ls D:\Temp | Where $IsDir

    lsectory: D:\Temp


Mode                LastWriteTime         Length Name
----                -------------         ------ ----
d-----         1/4/2018   2:31 PM                ArchiveDestination
d-----         1/4/2018   1:40 PM                ArchiveSource
d-----         1/1/2018   3:34 PM                diff
...    



ls D:\Temp | Where $IsFile

Mode                LastWriteTime         Length Name
----                -------------         ------ ----
-a----         6/7/2017   5:28 PM            512 CombinedSources07Jun2017.txt
-a----        2/24/2018   6:29 PM            115 EmpData.csv
-a----       11/18/2017   6:47 PM          11686 fsoVolume.docx
...

PoSH V3 and higher. This is supported natively e.g.:

ls -directory
ls -ad
ls -file
ls -af

Of course any of the above can be set to just return true or false using if/then or try/catch.

If all the above is a bit more typing than you'd like then you can create your own function and give it whatever alias you choose, well, as long as it's not an alias already in use.

See the help files ...

# Get parameters, examples, full and Online help for a cmdlet or function

(Get-Command -Name Get-ChildItem).Parameters
Get-help -Name Get-ChildItem -Examples
Get-help -Name Get-ChildItem -Full
Get-help -Name Get-ChildItem -Online

Get-Help about_*
Get-Help about_Functions
Get-Alias -Definition Get-ChildItem

# Find all cmdlets / functions with a target parameter
Get-Help * -Parameter Append

# All Help topics locations
explorer "$pshome\$($Host.CurrentCulture.Name)"

Of course you can check / modify file attributes as well. See this article on the topic:

File Attributes in PowerShell

Fun with file and folder attributes, via PowerShell and the DIR command.

https://mcpmag.com/articles/2012/03/20/powershell-dir-command-tricks.aspx

So, you could do something like this, to achieve the same attribute check

Get-ChildItem -Path $FilePath -File -Force | Where {$_.Attributes -notmatch 'ReadOnly'}

Or a function wiht an alias.

Function Test-RegularFile
{
    [CmdletBinding()]
    [Alias('trf')]

    Param
    (
        [string]$FilePath
    )

    try
    {
        Get-ChildItem -Path $FilePath -File -Force `
        | Where {$_.Attributes -notmatch 'ReadOnly'}
        "$FilePath is a regular file" # success: regular file is readable
    }
    catch 
    {
        Write-Warning -Message "$FilePath is not a Regular file."
    }
}

trf -FilePath D:\Temp\fsoVolume.txt

Since you are new to PoSH, it reall important / vital that you get a base understanding before looking at conversion comparisons. See this post for folks providing some paths for learning PowerShell. https://www.reddit.com/r/PowerShell/comments/7oir35/help_with_teaching_others_powershell

postanote
  • 15,138
  • 2
  • 14
  • 25
0

To test whether it's a regular file:

Test-Path -PathType Leaf foo.txt

To test whether it's readable:

Get-ChildItem foo.txt | ? { $_.Mode -match 'r'}

To test whether it's hidden:

Get-ChildItem -force foo.txt | ? { $_.Mode -match 'h'}
Phydeau
  • 57
  • 6
  • 1
    You probably meant `Get-ChildItem foo.txt | ? { $_.Mode -notmatch 'r '}` (`r` is the _read-only_ attribute). However, that is not sufficient to determine whether the file can actually be read by the current user, as its ACL may prevent that. – mklement0 Feb 27 '18 at 20:21
  • I wanted to know if a file is a regular file, and this answer happens to show the most natural-looking way of doing it, even though it may not be answering the original question correctly. – Tatiana Racheva Oct 11 '18 at 21:38