Powershell: Get-Content throws confusing error when targeting a container

Created on 12 Sep 2018  路  13Comments  路  Source: PowerShell/PowerShell

Rather than either producing a sensible error, or behaving in a reasonably predictable manner, Get-Content incorrectly throws an Access Denied error when pointed at a container object. This is also the case when using the variable accessor syntax.

Steps to reproduce

PS> Get-Content c:\

or

PS> ${c:\}

Expected behavior

Option #1: A sensible error is produced

Get-Content : Unable to access item contents as it is a container. You may be looking for Get-ChildItem instead.
At line:1 char:1
+ Get-Content c:\
+ ~~~~~~~~~~~~~~~
+ CategoryInfo          : ***: (C:\:String) [Get-Content], ***
+ FullyQualifiedErrorId : ***,Microsoft.PowerShell.Commands.GetContentCommand

Option #2: Route the call through to Get-ChildItem instead.
What is the "content" of a container if not its child items? It would make at least intuitive sense for Get-Content to simply call Get-ChildItem if passed a path to a container object instead of a leaf object.

Directory: C:\


Mode                LastWriteTime         Length Name
----                -------------         ------ ----
d-----       10/20/2017   2:55 PM                msys64
d-----        4/11/2018   7:38 PM                PerfLogs
d-r---        9/11/2018   2:11 PM                Program Files
d-r---        8/27/2018   6:53 AM                Program Files (x86)
d-----       10/17/2017   2:52 PM                tmp
d-r---        5/16/2018   3:10 AM                Users
d-----        11/6/2017   2:57 PM                usr
d-----         9/3/2018   5:02 AM                Windows

Actual behavior

(Cmdlet access)

Get-Content : Access to the path 'C:\' is denied.
At line:1 char:1
+ Get-Content c:\
+ ~~~~~~~~~~~~~~~
+ CategoryInfo          : PermissionDenied: (C:\:String) [Get-Content], UnauthorizedAccessException
+ FullyQualifiedErrorId : GetContentReaderUnauthorizedAccessError,Microsoft.PowerShell.Commands.GetContentCommand

(Variable access syntax)

Access to the path 'C:\' is denied.
At line:1 char:1
+ ${C:\}
+ ~~~~~~
+ CategoryInfo          : PermissionDenied: (C:\:String) [], UnauthorizedAccessException
+ FullyQualifiedErrorId : GetContentReaderUnauthorizedAccessError

Environment data

> $PSVersionTable

Name                           Value
----                           -----
PSVersion                      6.1.0-rc.1
PSEdition                      Core
GitCommitId                    6.1.0-rc.1
OS                             Microsoft Windows 10.0.17134
Platform                       Win32NT
PSCompatibleVersions           {1.0, 2.0, 3.0, 4.0...}
PSRemotingProtocolVersion      2.3
SerializationVersion           1.1.0.1
WSManStackVersion              3.0

In my opinion, given the fact that the variable access syntax is a bit... edge case at the best of times (although undoubtedly also very handy at times) it would seem to make sense that Get-ChildItem would be called in these cases, so that it's possible to explore a provider in this fashion without necessitating use of the cmdlets at all.

Area-Cmdlets-Management Issue-Enhancement Resolution-Duplicate

Most helpful comment

Did you mean surprising, @iSazonov, as in users won't expect it? Amazing sounds like you're in favor of such a change.

Yes, surprising :-)

All 13 comments

It's also worth noting that in the variable access syntax, {$C:\File.txt} = "string" will actually overwrite the file contents using Set-Content. Whether it makes the slightest lick of sense to allow Set-Content to have some kind of effect on container objects also, I don't know. I've a few ideas, but I suspect doing such a thing will just enable a far too simple way to wipe a whole hard drive of files. 馃槃

A user-mode process can't "read" the contents of a directory. We're just returning the underlying OS error message:

PSCore (2:9) >  [io.file]::ReadAllBytes('c:\')
Exception calling "ReadAllBytes" with "1" argument(s): "Access to the path 'c:\' is denied."
At line:1 char:1
+ [io.file]::ReadAllBytes('c:\')
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo          : NotSpecified: (:) [], MethodInvocationException
+ FullyQualifiedErrorId : UnauthorizedAccessException

We could probably do something with the error message (e.g. say access is denied because it's a container) but alternate file systems may in fact support reading the contents of the directory (e.g. old Unix file systems) so failing automatically _because_ it's a container is probably not ideal.

behaving in a reasonably predictable manner,

In what way is it not predictable? Does it not produce an error in some cases? (I suppose technically it's unpredictable because the underlying file systems may have varying semantics but that's not a common case and would be by design anyway.)

variable access syntax is a bit... edge case at the best of times (although undoubtedly also very handy at times) it would seem to make sense that Get-ChildItem would be called in these cases

Variable notation simply exposes what the underlying provider does. Since the provider produces an error, so does variable notation.

It's also worth noting that in the variable access syntax, {$C:\File.txt} = "string" will actually overwrite the file contents using Set-Content. Whether it makes the slightest lick of sense to allow Set-Content to have some kind of effect on container objects also, I don't know

Again, this operation is not supported by the operating system. (I only know a couple of (very old) operating systems that allowed user processes to manipulate directories. In one case it was a bug.)

Okay, so there's a disconnect here. What do you mean by 'read the contents of a directory'?

Is that a distinct thing from what Get-ChildItem does? It seems sensible (at least on the surface) for 'reading the content' of a directory' to be analogous to 'enumerating the contained objects'

It would be ~amazing~ surprising to turn Get-Content into dir.

There definitely needs to improve the error message.

Caution must be taken as changing the behavior to Get-ChildItems could be destructive beyond the intended scope. Imagine the following mistake a beginner might make.

Get-Content -Path "C:\$nonexistant-filename-suffix" | Set-Content -Value "bar"

Would actually be.

Get-ChildItem -Path "C:\" | Set-Content -Value "bar"

Setting ALL files to bar.

I could see other odd behaviors as well where cmdlet arguments have multiple types accepted from pipeline. This may be an edge case but something to keep in mind.

Agreed though the error message should be more clear.

Did you mean _surprising_, @iSazonov, as in _users won't expect it_? _Amazing_ sounds like you're _in favor of_ such a change.

Agreed re error message (a courtesy that PowerShell would have to extend, because it is just the messenger here, as @BrucePay points out).

@vexx32: A cmdlet fundamentally changing its behavior situationally sounds like a bad idea to me.

There's a clear separation of purpose between the *-*Item cmdlets and the *-Content cmdlets, and I think we should keep it that way.

*-*Item cmdlets return a provider's items (files, directories, in this case) _as a whole_, whereas *-Content cmdlets target _what's inside_ a _given item_.

While it makes sense _colloquially_ to refer to both the child items of a container and the data inside a file as _content_, it is important to maintain stricter definitions in the context of a shell / programming languages (even if those definitions are sometimes somewhat arbitrary).

Did you mean surprising, @iSazonov, as in users won't expect it? Amazing sounds like you're in favor of such a change.

Yes, surprising :-)

I'm not so much in favour of letting users do this with the cmdlets themselves, I think; there the intent can be specified. I'm thinking mostly in terms of the variable syntax itself:

${C:\Program Files}

Should, to me at least, call Get-ChildItem instead of Get-Content, because the targeted item is a container. Or, alternatively, there should be a similar syntax that can target Get-ChildItem as opposed to Get-Content.

I think an error is still sensible for an undefinable use like this:

${C:\Program Files} = "foo"

Rather than simply having Get-Content automatically call Get-ChildItem when targeting a container, I think it would simply be more sensible for the variable syntax to not be constrained to a single method, and be capable of calling Get-ChildItem when pointed at a container object. (And since there is no Set-ChildItem, nor would having one be very sensible in my opinion, attempting to set a container value like this should error.)

I agree that changing the behaviour of the cmdlets themselves in such a fashion is verging on insanity.

@vexx32:I think that it's equally important for namespace variable notation to exhibit consistency so that:

${<drive>:<path>}
${<drive>:<path>} = 'foo'

is _always_ the equivalent of Get-Content -Path <drive>:<path> and Set-Content -Path <drive>:<path> -Value 'foo'

(Note that it is indeed -Path, not -LiteralPath, but if you use a wildcard that resolves to more than 1 item, you get an error).

Ultimately I personally think it simply makes sense for it to treat an access to the contents of a container as a call to get-childitem.

Regardless of whether that is agreed upon or not, I think the error message definitely needs a rework (and as much as I might _wish_ for the namespace variable notation to exhibit better discoverability in and of itself, that's the main thing -- if it's going to throw an error, it should be a comprehensible one.)

I think we're all in agreement that the error message should be improved.


A tangent re namespace notation:

It is widely used with _one_ provider, but I suspect most people are unaware of the fact that they're using namespace notation:

$env:PATH  # short for: ${env:PATH} or ${env:\PATH} and therefore Get-Content Env:\PATH

My guess is that it never caught on as a general concept, because it isn't all that useful for any of the other built-in providers (only a subset of which support namespace notation, namely those implementing the IContentCmdletProvider interface).

With the filesystem provider its usefulness is hampered by:

  • being limited to _literal_ paths - you can't use variables; you can, however, use (literal) paths _relative to the current location_, though only on a literally specified drive.

  • lacking control over encoding (which is less of a problem in PS Core)

  • In PS Core _on Unix_, it doesn't work, because / as a drive name doesn't work; e.g., on Windows you can use ${C:t.txt} to target file t.txt in the current C: location, but on Unix the analogous ${/:t.txt} doesn't work - /:t.txt is actually treated as a _variable name_, as noted in #2268.

We have the dup #2686.

Interesting that the other fellow also stumbled briefly over the thought that Get-Content ~ should give a directory listing too, haha! Sure, it's out of pattern, but apparently it's somewhat intuitive. Interesting...

Was this page helpful?
0 / 5 - 0 ratings