Powershell: Add support for special folders (known folders) via a separate provider / namespace

Created on 31 May 2018  Â·  74Comments  Â·  Source: PowerShell/PowerShell

The filesystem has a number of special folders (a.k.a. known folders): usually preexisting folders that have special meaning to the OS.

It would be convenient to be able to target them in the abstract - both for convenience and for platform abstraction - similar to what .NET provides; e.g.:

# E.g., on Windows:
PS> [Environment]::GetFolderPath('Desktop')
C:\Users\jdoe\Desktop

'Desktop' translates to an enum value from the [Environment+SpecialFolder] enumeration.

Supporting these enum values via a new -SpecialFolder (alias -sf) parameter natively in PowerShell would be handy:
(Example removed.)

_Update_: A better way to surface this functionality is via a sf: or SpecialFolder: _drive_, which would enable the following syntax:

Set-Location $sf:Desktop  # wishful thinking: changes to, e.g., C:\Users\jdoe\Desktop
$sf:Desktop  # wishful thinking; returns that path; short for: Get-Content sf:Desktop

Environment data

Written as of:

PowerShell Core v6.1.0-preview.2
Area-Cmdlets-Management Committee-Reviewed Issue-Enhancement Up-for-Grabs WG-Engine-Providers

Most helpful comment

If sf: is a file drive why should it work another than Temp: drive?

sf: reminds me Windows Library concept - collect folders in an entity.

All 74 comments

Seems using a prefix for special folders is better UX then parameter. We can not use ~, maybe ~~MyPictures?

@iSazonov:

I know that -SpecialFolder (or -SpecialDirectory, given that PowerShell uses the term _directory_) is lengthy, but the -sf alias would mitigate that.

~~MyPictures is, at least hypothetically, a breaking change, because nothing stops you from creating a folder literally named ~~MyPictures.
(Note that POSIX-like shells support ~<username> as a shortcut for referring to a different user's home folder (but only if used _unquoted_), but it's an obscure feature that I don't think is worth adopting in PowerShell.)

More generally, the meaning of ~ is relative to the provider underlying the _current location_ and therefore generally not suitable to unconditionally refer to a filesystem location.

A more PowerShell-like solution would be to:

  • Implement a SpecialFolder drive provider that exposes, say, drive sf:,

  • which would then allow you to use namespace notation to access its items; e.g.,
    Set-Location $sf:MyPictures

Also, cd ~\pictures is less to type. Ditto with cd ~\desktop. With tab completion there is even less to type cd ~\de<tab>.

@rkeithhill:

Yes, but that (a) already works and (b) only if the current location is a filesystem location.

While in a filesystem location, ~ is a convenient shortcut (that, unfortunately, is not fully semantically equivalent with (unquoted) ~ in POSIX-like shells such as bash due to its current-location sensitivity).

The stated limitation aside, you can therefore think of ~ as the _one_ special-folder shorthand in PowerShell that is already available, and for other special folders that _happen to be_ in the home folder's _subtree_, something like ~/Desktop works well.

However, just like ~ is a useful abstraction - it may refer to, say, C:\Users\jdoe, /home/jdoe, or /Users/jdoe, for instance - having abstractions for other well-known folders is equally useful, and that's where the special folders come in.

For instance, the ApplicationData special folder is something like C:\Users\jdoe\AppData\Roaming on Windows, and $env:HOME/.config on Unix-like platforms.

Even ~ has its conceptual limitations: as @iSazonov pointed out in https://github.com/PowerShell/PowerShell/issues/6791#issuecomment-393465147, you can think of ~ on _Unix_ corresponding to ~\Documents on _Windows_, given that Windows discourages creating user content directly in the profile folder;
Unlike, ~, the MyDocuments (alias: Personal) special folder provides exactly this abstraction: it returns the equivalent of ~ ($HOME) on Unix, and ~/Documents on Windows.

The ~ shorthand doesn't scale well: something like ~~MyDocuments and ~MyDocuments, as stated, is a breaking change, but, perhaps more importantly, it would be self-contradictory in the case of _user-independent_ folders, such as CommonApplicationData (which is C:\ProgramData on Windows, and /usr/share on Unix).

but that (a) already works

Yup, I'm questioning the value of the proposed feature.

the ApplicationData special folder is something like C:UsersjdoeAppDataRoaming on Windows, and $env:HOME/.config on Unix-like platforms.

Trying to map Windows oriented locations to *nix doesn't sound like a good idea to me.

~~MyPictures is, at least hypothetically, a breaking change, because nothing stops you from creating a folder literally named ~~MyPictures.

Why stops? We can use escaping and LiteralPath.
Seems on Unix the ~ is valid name too. And all the other options too.
I think we could enhance ~ and use ~CommonApplicationData.

Trying to map Windows oriented locations to *nix doesn't sound like a good idea to me.

This has already been implemented in CoreFX and PowerShell Core.

This has already been implemented in CoreFX and PowerShell Core.

Well, except that a lot of those enumeration values don't make sense on Linux/macOS. As such, most of those enumerations result in empty string results:

17:16ms>  $ht = @{}; [Enum]::GetValues('System.Environment+SpecialFolder') | % { $ht[$_] = [Environment]::GetFolderPath($_) }; $ht

Name                           Value
----                           -----
CDBurning
CommonOemLinks
LocalizedResources
Resources
CommonVideos
CommonPictures
CommonMusic
AdminTools
CommonAdminTools
CommonDocuments
CommonTemplates
CommonProgramFilesX86
CommonProgramFiles
ProgramFilesX86
SystemX86
UserProfile                    /home/hillr
MyPictures
ProgramFiles
System
Windows
CommonApplicationData          /usr/share
History
Cookies
InternetCache
LocalApplicationData           /home/hillr/.local/share
PrinterShortcuts
ApplicationData                /home/hillr/.config
CommonDesktopDirectory
CommonStartup
CommonPrograms
CommonStartMenu
Templates
Fonts
NetworkShortcuts
MyComputer
DesktopDirectory
MyVideos
MyMusic
StartMenu
SendTo
Recent
Startup
Favorites
MyDocuments                    /home/hillr
Programs
Desktop

Consequently, this would be a Windows-centric feature IMO.

@rkeithhill:

Yup, I'm questioning the value of the proposed feature.

On a meta note: If opposing the cross-platform nature of the feature was your argument to begin with, can I ask you to cut out the "middle comment" in the future? "Also, cd ~\pictures is less to type..." doesn't quite convey that argument, and my lengthy response wouldn't have been necessary (though it's hopefully of interest for the bigger picture).

As @iSazonov's statement implies, CoreFx sees value in the feature, given that an explicit decision was made to include it on non-Windows platforms:

It is useful for the same reasons that it has always been useful, even while it was Windows-only, working across Windows versions and configuration variations: it provides _reliable abstractions_, given that hard-coded paths may fail.

In a _cross-platform_ world, abstractions are even more important to refer to _conceptually equivalent locations_, IF such equivalents exists, because special-casing determination of the value for each platform in cross-platform code is obviously very cumbersome.

A related conversation was the need for a platform-abstracted version of the %TEMP% Windows environment variable - see #4216
(Again, CoreFx has led the way there with [System.IO.Path]::GetTempPath(). As an aside: arguably, the temporary-files location too should be a special folder, which it currently isn't.)

Well, except that a lot of those enumeration values don't make sense on Linux/macOS.

True, many special folders only make sense only on Windows, but, fortunately, CoreFx has already done the work for us by identifying the ones that _do_ make sense across platforms: returning an _empty string_ is [Environment]::GetFolderPath()'s way of telling us that equivalent locations do _not_ exist.

When authoring cross-platform code, there is no way around knowing what subset makes sense.

@rkeithhill

Consequently, this would be a Windows-centric feature IMO.

Indeed, and since it hasn't really come up much in over a decade of _Windows_ PowerShell (it's been proposed but there wasn't much interest), I'm not sure it adds much value. Most of the interesting folders are more conveniently available off ~. But I suppose it wouldn't hurt.

@mklement0

While in a filesystem location, ~ is a convenient shortcut (that, unfortunately, is not fully semantically equivalent with (unquoted) ~ in POSIX-like shells such as bash due to its current-location sensitivity).

In what way is it not fully semantically equivalent? Do you mean the fact that it takes you to the home location on the current drive?

you can think of ~ on Unix corresponding to ~Documents on Windows, given that Windows discourages creating user content directly in the profile folder;

These days, most Linux distros as well as MacOS use the same folder structure as Windows, with Documents directory, Pictures, Movies, etc. So ~ in *sh and ~ in PowerShell are pretty much equivalent.

@BrucePay:

Indeed, and since it hasn't really come up much in over a decade of Windows PowerShell (it's been proposed but there wasn't much interest)

On Windows, you could typically get away with (semi-)hard-coded paths.
In a cross-platform world, that's no longer an option, so the special-folder abstractions become more valuable.

In what way is it not fully semantically equivalent? Do you mean the fact that it takes you to the home location on the current drive?

Yes: If you come from a Unix background and expect cd ~\Desktop to _always_ take you to $HOME\Desktop, you'll be surprised:

PS> Set-Location Alias:
PS Alias:/> cd ~\Desktop  # boom
cd : Home location for this provider is not set. To set the home location, call "(get-psprovider 'Alias').Home = 'path'".
...

These days, most Linux distros as well as MacOS use the same folder structure as Windows, with Documents directory, Pictures, Movies, etc. So ~ in *sh and ~ in PowerShell are pretty much equivalent.

Fortunately, yes (though CoreFx has chosen to consider just ~ the equivalent of Windows ~/Documents via [Environment]::GetFolderPath('MyDocuments', but that wouldn't affect explicit use of ~/Documents).

However, as implied by the above, scripts should only ever use $HOME, not ~ , in order to _reliably_ refer to the user's home folder.

@iSazonov:

Why stops? We can use escaping and LiteralPath.

Unlike in POSIX-like shells, where the _shell_ expands ~, and _only if unquoted_, ~ in PowerShell _is_ a literal path, and it is _up to the target provider_ to interpret it; the following statements are all equivalent:

Set-Location ~    # implied -Path
Set-Location '~'  # ditto, with quoting
Set-Location -LiteralPath ~
Set-Location -LiteralPath '~'

I think the idea of a special PSDrive is a really good one

@mklement0

Yes: If you come from a Unix background and expect cd ~Desktop to always take you to $HOMEDesktop, you'll be surprised:

Coming from a Unix/POSIX background, I'll admit that this was a decision I was not thrilled with. We have considered changing it a few times but did not. I wish we'd done it for 6.0.

Unlike in POSIX-like shells, where the shell expands ~

True. There is a difference in layers and responsibilities. The upside is this allows us to glob against things like processes without needing to quote.

On Windows, you could typically get away with (semi-)hard-coded paths.
In a cross-platform world, that's no longer an option, so the special-folder abstractions become more valuable.

The whole reason for special folders on Windows is because it wasn't possible to have hard-code paths. For example, it used to be recommended that you have your documents on a different drive than the system. This sort of thing necessitated redirection. Unix, on the other hand, has been pretty consistent with directory naming (/etc is always /etc). And the various distributions seem to have settled on more or less the same set of user directories.

Fortunately, yes (though CoreFx has chosen to consider just ~ the equivalent of Windows ~/Documents via [Environment]::GetFolderPath('MyDocuments', but that wouldn't affect explicit use of ~/Documents).

I didn't quite follow this. ~ processing is a PowerShell thing, not a Windows or CoreFX thing. At least I'm not aware of them doing anything with ~.

@iSazonov

We can not use ~, maybe ~~MyPictures?

Actually that's pretty much the proposal @JamesWTruher and I had way back in V1. ~/<directory> would have been relative to the home directory but ~documents, ~ApplicationData etc. would have been the special folders, And something like ~ApplicationData/npm would be a special-folder relative path. The nice thing with this approach is that you could allow user-defined short cuts e.g. ~src or ~test/scripts.

These days, most Linux distros as well as MacOS use the same folder structure as Windows, with Documents directory, Pictures, Movies, etc. So ~ in *sh and ~ in PowerShell are pretty much equivalent.

So I expect that CoreFX will map special folder name wider. Any ported music player can use "MyMusic" special folder on any platform.

Other thoughts about ~.

  • I don't like that it conflict with Unix ~user. Can we use another prefix?
  • If we look Windows applications like SCCM (system center configuration manager) or GPP (group policy preferences) they use internally %specialfolder% format.
    CoreFX has Environment.ExpandEnvironmentVariables to expand environment variables and
    Environment.GetFolderPath() to expand special folder names in paths.
    Also in cmd.exe dir %temp% works but not in PowerShell.
    Do we want both expansions to work in PowerShell?

@BrucePay:

For example, it used to be recommended that you have your documents on a different drive than the system. This sort of thing necessitated redirection.

On a related note: It looks like what PowerShell reports as $HOME is %USERPROFILE%, which is _typically_, but not necessarily the same as %HOMEDRIVE%%HOMEPATH%; the former is the roaming part of the user profile and the latter is where the user's personal _files_ go.

If they do differ, something like ~/Desktop will not work as intended.

So, given that redirection (abstract locations whose machine-specific instantiation is obtained programmatically) is still a necessity in the Windows world, it doesn't even help that Linux distros have standardized locations such as ~/Desktop - for reliable operation you still need the redirection.


I didn't quite follow this. ~ processing is a PowerShell thing, not a Windows or CoreFX thing. At least I'm not aware of them doing anything with ~

I was using ~ merely as shorthand alias for $HOME (Unix) and %HOMEDRIVE%%HOMEPATH% (Windows) in my example (I'll call both $HOME below).

I was trying to point out that if you ask the .NET API for the path to the user's documents - [Environment]::GetFolderPath('MyDocuments'):

  • you get $HOME _itself_ on Unix
  • you get $HOME\Documents on Windows

In other words: from the perspective of CoreFx, location $HOME/Documents - even though it does _exist_ on all platforms - is not considered _equivalent_ across platforms.


To summarize:

The upshot:

  • Even Windows-only scripts benefit from using abstractions, because something like ~\Desktop or $HOME\Desktop doesn't always work.

  • Fully cross-platform scripts are limited to a relatively small subset of the special folders.

  • More abstractions become available for scripts that target Windows + macOS, or Windows + Linux only.

Specifically, here is the list of which special folders are defined on what platforms:

  • Windows: Since the feature originated there, _all_ special folders are defined.

  • Shared - defined on _all_ platforms (note: Desktop and DesktopDir, though technically distinct, seem to refer to the same location in practice; the former is distinguished from the latter as being the "logical Desktop rather than the physical file system location" - not sure what that means).

    • ApplicationData
    • CommonApplicationData
    • Desktop
    • DesktopDirectory
    • LocalApplicationData
    • MyDocuments
    • MyMusic
    • MyPictures
    • UserProfile
  • additionally, unique to macOS (not also on Linux)

    • Favorites
    • Fonts
    • InternetCache
    • ProgramFiles
    • System
  • additionally, unique to Linux (not also on macOS)

    • MyVideos
    • Templates

The above was obtained with a modified version of @rkeithhill's helpful command:

$ht = @{}; [Enum]::GetValues([Environment+SpecialFolder]) | % { $ht[$_] = [Environment]::GetFolderPath($_) }; $ht.getEnumerator() | ? Value | Sort-Object {[string] $_.key}

Re syntax:

Can we use another prefix?
And something like ~ApplicationData/npm would be a special-folder relative path.

Generally, using a special symbol (prefix) - while conveniently concise - is arcane (something you'd expect from Perl, not from PowerShell), even though it builds on a well-known symbol in the _Unix_ world.

  • Also, as stated, given the established meaning of ~ as the _user's_ home folder, something like ~ProgramFiles - a user-independent location - is self-contradictory.
  • (The ~<username> syntax in shells such as bash is a more natural extension of ~, but is not widely used.)

Similarly, we should stay away from %specialfolder%, given that it looks like cmd.exe's environment variables, whereas PowerShell uses $env:<name> to surface environment variables.

By contrast, using _namespace notation_ still allows for concision while using established PowerShell idioms; e.g.:

Set-Location $sf:Desktop  # tab-completion would work too

Implementing a _provider_ just for this purpose may seem heavy-handed, but doing so is currently a prerequisite for namespace notation.

So, the wider question is: perhaps we should support defining namespaces in a more lightweight manner, without needing to implement a full-blown drive provider?

@mklement0 Thanks! I always like your analysis.

I like the idea. Only without $

Set-Location SpecialFolder:Desktop # full name
Set-Location sf:Desktop  # short name

the wider question is: perhaps we should support defining namespaces in a more lightweight manner, without needing to implement a full-blown drive provider?

Seems currently no - this will require the alteration of internal interfaces. We have a similar request for providers.
However, we could implement this new drive even today :-) - we only need RFC. It was great if you did. Seems it will be very short.

Also I would not leave without attention my question about environment variables. They are often used in other shells. We should think about how to use them more easily in PowerShell.

Thanks, @iSazonov; similarly, I appreciate your tireless efforts and countless contributions.

On second thought, you're right: implementing a drive provider is the way to go, and the right solution is to make _implementing_ one more "lightweight", i.e.: easier: #5789.

As for how that new drive's items must be accessed, let me start with environment variables, because how they are surfaced exemplifies how I think the special folders should be surfaced too:

Also I would not leave without attention my question about environment variables. They are often used in other shells. We should think about how to use them more easily in PowerShell.

I think that PowerShell's way of exposing environment variables works fine and is sufficient:

  • The env: drive enumerates all environment variables (e.g., (Get-Item Env:PATH).Value reports the value of environment variable PATH)

  • The generic namespace-notation feature therefore allows accessing these items using, e.g., $env:PATH; in other words: $<drive-name>:<item-name> is the concise (and presumably more efficient) equivalent of (Get-Item <drive-name>:<item-name>).Value)

Namespace notation makes for a concise, yet friendly notation that supports tab completion and even direct embedding in expandable strings (without the need for enclosing $(...)); e.g.:
"PATH: $env:PATH"

It's a powerful concept, and even though use of the $env: namespace is widespread - because it's the simplest way to access environment variables - my sense is that not many people know how generic the underlying mechanism is.

If we implement a SpecialFolders drive provider and surface the special folders via a new drive named sf: (we could name it SpecialFolder:, but note that there's no concept of drive-name aliases, so from what I understand, if we wanted to support both sf: and SpecialFolder:, we'd have to define _two_ drives), accessing it via namespace notation looks the way I proposed:

Set-Location $sf:Desktop 

That would actually obviate the need to extend Get-Location, becauseGet-Item sf:Desktop Get-Content sf:Desktop would be sufficient - or simply $sf:Desktop by itself.

By contrast, the syntax you propose would _break_:

Set-Location sf:Desktop   # !! breaks, if sf: is not a file-system provider drive

This would attempt to navigate to the sf: drive's Desktop _container_, but Desktop isn't a container: it's a _leaf-type item_ analogous to _files_ in the filesystem provider. [_update_: see [below](https://github.com/PowerShell/PowerShell/issues/6966#issuecomment-663890154)]

(Similarly, Set-Location Env:PATH breaks, as would analogous call for all _non-hierarchical_ providers that have just _one_ level of invariably leaf-type items, namely: Alias:, Function:, Variable: - the only path you can navigate to on drives of such providers is the _root_ location; e.g., Set-Location Env:.)

This may be related. I've just installed PowerShell 6.1.2 and when I cd \Users\<user>\Desktop, I can't create a folder using mkdir test. I can with PowerShell 5.1.17763.124. This is with and without admin access.

The error is:

````
â–¶ $error[0] |fl * -f

writeErrorStream : True
PSMessageDetails :
Exception : System.IO.FileNotFoundException: Could not find file
'C:usersprafulDesktoptest'.
File name: 'C:usersprafulDesktoptest'
at System.IO.FileSystem.CreateDirectory(String fullPath)
at System.IO.Directory.CreateDirectory(String path)
at Microsoft.PowerShell.Commands.FileSystemProvider.Creat
eDirectory(String path, Boolean streamOutput)
TargetObject : C:usersprafulDesktoptest
CategoryInfo : WriteError: (C:usersprafulDesktoptest:String)
[New-Item], FileNotFoundException
FullyQualifiedErrorId : CreateDirectoryIOError,Microsoft.PowerShell.Commands.NewItem
Command
ErrorDetails :
InvocationInfo : System.Management.Automation.InvocationInfo
ScriptStackTrace : at , : line 52
at , : line 1
PipelineIterationInfo : {0, 1}
````

Making a directory in \Users\<user> works. The problems seems to be with the special folders (Desktop, Favourites, Documents, etc) on Windows 10 1809.

Has something changed?!

@Praful Please open new Issue with repo steps.

@mklement0 Is the discussion ready for PowerShell Committee review? Could you please summorize the discussion?

Thanks for the follow-up, @iSazonov: Yes, I think it's ready; to summarize, the proposal is:

Provide convenient cross-platform abstractions for well-known folders in a PowerShell-idiomatic manner by surfacing the functionality of [Environment]::GetFolderPath() via namespace variable notation $sf:<known-folder-name>, where sf stands for _special folder_ and <known-folder-name> is an Environment.SpecialFolder enum value - e.g., $sf:Desktop would be the equivalent of [environment]::GetFolderPath('Desktop')

Note: Not all well-known folders exist on all platforms - see the bottom section of this comment for details; if they don't, [Environment]::GetFolderPath() returns the _empty string_ (e.g., [environment]::GetFolderPath('CDBurning') on macOS); we could mirror that practice.

To that end:

  • Implement a non-hierarchical, read-only provider that implements the IContentCmdletProvider interface and whose items are named for the Environment.SpecialFolder enumeration values and whose content is the value of passing their name to [Environment]::GetFolderPath()

  • Define a drive named sf for that provider.

As an optional extension to the proposal above:

  • Accept an additional enum value named, say, Temp, to return the platform-specific path to the folder for temporary files that surfaces the value of [System.IO.Path]::GetTempPath(), PowerShell currently has no analog - see #4216; support for $sf:Temp would resolve that.

@SteveL-MSFT The issue is ready for reviewing by PowerShell committee.

@PowerShell/powershell-committee started discussing this but didn't reach a conclusion. One proposal that came up is a folder under env: rather than a new PSDrive. So it would look like env:/SpecialFolder/Fonts, for example. Although SpecialFolders are not environment variables, they are folders that are part of the user's environment, so conceptually, it works.

I wouldn't recommend that. Requiring slash separators to access them means that accessing them as though they were variables (like you can with $env:TEMP) becomes cumbersome. Not everyone is aware of braced syntax for variables, after all, it's a bit of a niche thing.

It appears the intent here is to make it easier to access these folders, and I don't think burying it in a special subtree of Env: makes any sense towards that end. Everything else in there is single-level, a flat one-dimensional array of strings, effectively... Unless there are further plans to abstract that drive to increase usability and make it more robust as a whole, I very much doubt that creating a special case here will help anything.

SpecialFolder-s is still not environment variables.

@vexx32 to reiterate some of the concern I had last week, it was really around how people have mentally mapped $env in their head. To me (and I'd expect most of the community as well) $env and the Environment provider == "environment variable". According to @JamesWTruher, $env was really supposed to contain any useful information about the environment, more akin to [System.Environment] which would then rightfully include the special folders.

I argued that the original intent is likely not clear to users and that it would make sense to distinguish from $env in some way, and was more in favor of something like $sf, but that means we need a PSDrive, or it'll be an automatic variable. As @mklement0 argued above, we also felt that a PSDrive would be a little heavy-handed, and it's especially wonky to me because these aren't settable items. You're just accessing some read-only values from the system.

If it's an automatic variable, then we have to special-case it as one that somehow parses :foo. From an "I can see what's going on under the hood" perspective, this is wonky too. I know we have lots of magic in PowerShell, but we're doing our best not to add more (cue people calling me out for wanting magic in the PSModulePath RFC :) )

So that's why we landed in a place where we wanted to move towards the original intent: to move more information about the environment into the Environment provider. In the future, we could want more there beyond special folders, and it would make sense to set a precedent for that now so we're not arguing for more read-only providers in the future.

Additionally, I'd argue that this is feature is way more cross-platform abstraction than it is about ease of typing. There's all sorts of ways you can alias folders you care about to shorthand for your interactive sessions, but this is more about providing a way for PS users to natively abstract these special folders (whether it's cross-platform the ones that happen to have values defined on Linux, as well as to mitigate potentially weird folder locations within pure Windows environments).

Out of curiosity, is there any reason no one raised a Get-SpecialFolder cmdlet? Are there some parse vs. runtime concerns that crop up if we go that route? (Upon which everyone could just alias it to gsf or something so that e.g. (gsf TEMP) just works.)

Hm. A cmdlet has a bit more overhead than a provider or some such, but otherwise I see nothing wrong with it.

I think I can understand that perspective with the Environment PSProvider. The biggest thing at the moment is that it doesn't currently appear to support anything like a container node -- trying to create Env:\MyThing\Var at the moment (from memory, at least) creates just a single item named MyThing\Var, doesn't it?

I have no qualm with _changing_ that and making it work, though. Easy access to special folders would be lovely! 😄

To me (and I'd expect most of the community as well) $env and the Environment provider == "environment variable".

How resolve this? Docs says

Provides access to the Windows environment variables.

And we have a conflict - all env variables is in root of the namespace although I would expect:

$env:/specialfolders/
$env:/variables/system/
$env:/variables/user/
$env:/computerinfo/
$env:/osinfo/

this look like /proc in Linux.

@PowerShell/powershell-committee reviewed this, we would also be ok with cmdlets for Get-SpecialFolder

@PowerShell/powershell-committee reviewed this, we would also be ok with cmdlets for Get-SpecialFolder

IMO the provider approach (i.e. sf:MyPictures) would make for less typing.

This would attempt to navigate to the sf: drive's Desktop _container_, but Desktop isn't a container: it's a _leaf-type item_ analogous to _files_ in the filesystem provider.

TEMP: is a container now, why can’t sf:Desktop be?

I phrased that poorly:

The idea is for $sf:Desktop (which is the implicit equivalent of Get-Content sf:Desktop - not Get-Item sf:Desktop) to directly return a _native path string_ that represents the platform-specific Desktop folder, so that you can use it _directly_ in arguments; e.g., [IO.File]::CreateText("$sf:Desktop/foo.txt")

This behavior is the primary utility of the $sf:Desktop syntax, given that a mere provider-path string such as sf:Desktop _by itself_ is only meaningful to _PowerShell's provider cmdlets_ (unlike native file-system paths such as C:\Users\Desktop).

So my thinking was that the drive's items do not themselves need to represent _directory_ (container) items, and the only practical need to explicitly call provider cmdlets would be for _discovery_: e.g., Get-ChildItem sf: or Get-Item sf:*music*

I suppose we could do _both_, and also treat drive sf: like any other PS-drive-based _file-system_ drive, but there's a conceptual problem:

  • What file-system location should sf:/ - the root of all special folders - represent? There is no natural candidate, yet we don't want to disallow Set-Location sf:/ altogether, given that it works with other providers (e.g., Set-Location alias:/ works just fine); if location sf:/ doesn't represent an actual file-system item - just as alias:/ doesn't - the problem doesn't arise.

Also, $PWD will then report sf:Desktop rather than C:\Users\jdoe\Desktop, for instance - as with custom PS drives.

What file system location does My Computer represent? You can explore it, you can link to it but you cannot pass it to an application that expects a directory. It is the same situation.

It is not, because My Computer (This PC) is a concept of the Windows _GUI_ shell's namespace, and its abstract locations aren't directly usable in PowerShell, so _the problem doesn't currently exist_ - we would be introducing it to PowerShell.

(If you try launching a PowerShell console from File Explorer from an abstract location such as This PC, you'll get set-location : Cannot find path '::{20D04FE0-3AEA-1069-A2D8-08002B30309D}' because it does not exist., and, similarly, you can't query its children with Get-ChildItem.)

That said, I'd be fine with, say, mapping location sf:/ to the root of the system drive (/ on Unix, and typically C:\ on Windows).

Again: What matters at the end of the day is that $sf:Desktop directly expands to the platform-specific, native file-system path underlying the abstract location.

In keeping with existing providers, it would make the most sense if SF: itself mapped to an abstract location from where you can call Get-ChildItem and get a list of special folders and their locations:

PS> Get-ChildItem sf:

Name          Location
----          --------
Desktop       C:\Users\currentUser\Desktop
My Documents  C:\Users\currentUser\My Documents
( ... ) 

[_Update_: See [conclusion below](https://github.com/PowerShell/PowerShell/issues/6966#issuecomment-663910975)]

Yes, @vexx32, but we would get that behavior even if the items themselves do not map directly to file-system locations; as I said above:

the drive's items do not themselves need to represent directory (container) items, and the only practical need to explicitly call provider cmdlets would be for _discovery_: e.g., Get-ChildItem sf: or Get-Item sf:*music*

This would work just like Get-ChildItem alias:/, for the alias: drive, which also does _not_ map to file-system locations.

To summarize:

  • What's important is that namespace variable notation - e.g., $sf:Desktop - directly expands to the platform-specific, native file-system path underlying the abstract location.

  • I'm personally fine with _also_ making the drive navigable as a special _file-system_ provider (rather than a provider that returns _path strings_), but I think that is far less important:

    • If we _don't_ make sf: a special file-system provider, not much is lost: instead of Set-Location sf:Desktop you'd then have to use Set-Location $sf:Desktop (analogously for Get-Item and Get-ChildItem, if you want [System.IO.DirectoryInfo] output), which doesn't strike me as a hardship - though there may be potential for confusion.

    • If we _do_ do that:

      • We need to decide how to handle Set-Location sf:/, i.e. navigating to the drive's _root location_, which all providers currently support.
      • Either: Prevent navigating directly to sf:/, with a meaningful error message.
      • Or: Map sf:/ to an ultimnately arbitrary file-system locations, such as the root of the system drive.
      • Either way, we need to be mindful that after, say, Set-Location sf:Desktop, $PWD would stringify as 'sf:/Desktop', which you cannot directly use (to compose paths with) in calls that expect native file-system paths. (This applies to _custom_ file-system PS drives equally, but creating those is a deliberate act, whereas in this case users may not expect this behavior; $PWD.ProviderPath is the workaround)

@mklement0 I believe he's just answering your question of:

  • What file-system location should sf:/ - the root of all special folders - represent? There is no natural candidate, yet we don't want to disallow Set-Location sf:/ altogether, given that it works with other providers (e.g., Set-Location alias:/ works just fine); if location sf:/ doesn't represent an actual file-system item - just as alias:/ doesn't - the problem doesn't arise.

with "it doesn't, it's like alias:/".


Also I agree with you that making the content of the leaf be the path as a string is the only way to implement it as a provider. It is gonna be weird though that Get-ChildItem sf:Desktop won't work like folks expect. I know it can't do that with how providers work currently, but still maybe too confusing.

Thanks, @SeeminglyScience, but I was trying to point out that only if you conceive of the provider underlying the sf drive as a _file-system_ provider do you have to solve the problem of what file-system location sf:/ maps to. So, _if_ we want Get-ChildItem sf:Desktop to work - to avoid confusion - we indeed need to implement the provider as a file-system provider - and then we need to solve that problem.

I really don't think you can in a way that doesn't cause more confusion. Unless you just special case all path resolution starting in sf in the FileSystemProvider itself.

The biggest obstacle in any provider implementation that is "sorta like X provider but with this one difference" is that you can't do cross provider operations. So Copy-Item sf:\Desktop C:\temp is hardwired to throw. Also how does path resolution transition between providers like gi sf:\Desktop\Chrome.lnk. The provider API just isn't built to handle any of that.

Sorry if that's all already been mentioned ITT.

Sorry if that's all already been mentioned ITT.

I don't think it has, @SeeminglyScience, so thanks for clarifying.

So it sounds like the conclusion is:

  • A provider underlying the proposed sf drive currently cannot be a special-cased file-system provider, so we currently cannot support calls such as Set-Location sf:Desktop or Get-Item sf:Desktop ([_update_: while expecting a [System.IO.DirectoryInfo] instance as output), even though users may expect it.

  • As a provider whose items simply represent _path strings_, namespace variable notation (e.g, $sf:Desktop) works as intended - which is what matters - and it still supports discovery via Get-ChildItem / Get-Item.
    Set-Location sf:/ - in the absence of needing to map to a file-system location - then presents no conceptual problem.

  • A provider underlying the proposed sf drive currently cannot be a special-cased file-system provider, so we currently cannot support calls such as Set-Location sf:Desktop or Get-Item sf:Desktop, even though users may expect it.

Maybe clarify that Get-Item will work, just not return a DirectoryInfo object.

Interesting, we expect that sf:Desktop returns a value but alias:% does not return ForEach-Object. It looks like a fundamental problem.

@iSazonov ${alias:%} does return ForEach-Object

But we want Set-Location $sf:Desktop, not Set-Location ${sf:Desktop}

$sf:Desktop will work just fine, the curly brackets are because % isn't allowed in variable names.

>alias:gcm
alias:gcm: The term 'alias:gcm' is not recognized as the name of a cmdlet, function, script file, or operable program.
Check the spelling of the name, or if a path was included, verify that the path is correct and try again.

It is not expanded to Get-Command.

alias:gcm is a bareword, there is nothing to expand here.

@iSazonov, that is expected, because alias:gcm isn't a _file-system_ path that allows _direct execution_. Only the alias _provider_ understands this path, so you'll have to use Get-Item alias:gcm (or Get-Content alias:gcm to just get the _definition_ of the alias, which is the _string_ 'Get-Command').

To execute an alias by its PS-provider path (which is obviously a needlessly circuitous way of doing it), you'd have to do this (where $alias:gcm is the equivalent of Get-Content alias:gcm):

& $alias:gcm

If we allowed direct execution of variables, we would not need temporary files. However, such variables should still be named *.PS1. alias:gcm does not have this extension, so by no stretch can it be treated as a script.

that is expected, because alias:gcm isn't a file-system path

What do we want - sf:Desktop or $sf:Desktop in Set-Location?

What do we want - sf:Desktop or $sf:Desktop in Set-Location?

The only way that can work with how providers currently work is $sf:Desktop.

Though I want to state again that I think this design is too confusing for folks who don't know why it needs to be like that. Personally I think this should wait until cross provider path resolution/copy operations work.

@SeeminglyScience

I get the potential for confusion, but there's no telling if and when cross provider path resolution/copy operation will ever be implemented (or are there specific plans?), so I don't think we should wait for that.

I see the primary utility of the proposed feature in _composing path strings via string interpolation / concatenation_, where $sf:Desktop is the necessary form (e.g., "$sf:Desktop/subdir" (however, with Join-Path the confusion could again arise).

If we implement a provider now and make its items _containers_ of type [System.IO.DirectoryInfo] (and the item _content_ their .FullName property), then we could conceivably later transition to a fully integrated specialized file-system provider - or am I missing something?

Come to think of it: Would that alone enable Set-Location sf:Desktop, given that there's no _cross_-provider operation happening? However, you then wouldn't actually change to a location _on drive sf:_; instead, you'd change directly to the _native file-system path_ underlying the item - but that is arguably preferable.

[_Update_: See [below](https://github.com/PowerShell/PowerShell/issues/6966#issuecomment-664603897).]

@yecril71pl

If we allowed direct execution of variables

There is no _variable_ here (whose _value_ you can invoke with &), only a _literal path_ referring to an item exposed by the Alias provider (via its one and only alias: drive).

@iSazonov

PowerShell _conceptually could_ - but currently doesn't - accept path alias:gcm as a _command_ to execute, even though the System.Management.Automation.AliasInfo instance the path refers to is by definition a command (derives from System.Management.Automation.CommandInfo). Given that just gcm is sufficient for invocation, however, I don't think that's a shortcoming in practice.
Out of curiosity, @SeeminglyScience: is this problem solvable with how providers currently work?

By contrast, for paths such as sf:Desktop to become first-class PS file-system citizens, alongside custom PS filesystem drives, we'd need a _specialized file-system provider_, which
according to @SeeminglyScience's analysis a problem that cannot be solved with how providers currently work.

but there's no telling if and when cross provider path resolution/copy operation will ever be implemented

Yeah I know, still. A different route should be pursued imo.

If we implement a provider now and make its items _containers_ of type [System.IO.DirectoryInfo] (and the item _content_ their .FullName property), then we could conceivably later transition to a fully integrated specialized file-system provider - or am I missing something?

I think is a bigger understandability challenge. The Get-Item object is a DirectoryInfo, but it's a leaf, and the content is the path. Then later we're going to make them no longer a leaf and throw on Get-Content? Even if that was intuitive, that makes it even harder for a user to determine why gci sf:/Desktop or gi sf:/Desktop/pwsh.lnk don't work.

Come to think of it: Would that alone enable Set-Location sf:Desktop, given that there's no _cross_-provider operation happening? However, you then wouldn't actually change to a location _on drive sf:_; instead, you'd change directly to the _native file-system path_ underlying the item - but that is arguably preferable.

Nah, you can't set your location to a leaf.

PowerShell _conceptually could_ - but currently doesn't - accept path alias:gcm as a _command_ to execute, even though the System.Management.Automation.AliasInfo instance the path refers to is by definition a command (derives from System.Management.Automation.CommandInfo). Given that just gcm is sufficient for invocation, however, I don't think that's a shortcoming in practice.
Out of curiosity, @SeeminglyScience: is this problem solvable with how providers currently work?

Well & (gi alias:gcm) works. I imagine a really basic version could maybe be hooked up via ItemCmdletProvider.InvokeDefaultAction if the breaking change was accepted. But execution in the same way it works for the filesystem provider could be a big ol' can of worms.

Thanks, @SeeminglyScience.

The Get-Item object is a DirectoryInfo, but it's a leaf

The proposal was to make it a _container_, but, yes, allowing Get-Content to report the container's _path_ would be an anomaly (something like Get-Content $HOME currently fails, as does ${C:\Windows}, with Unable to get content because it is a directory:)

However, conversely that means that if we wait until we can make the sf: drive's provider a specialized, full-fledged file-system provider, it is $sf:Desktop that won't work - which takes away what I perceive to be the primary use case: I want to be able to use $sf:Desktop/file.txt, and not be forced to use "$(Get-Item sf:Desktop)/file.txt" or (Get-Item sf:Desktop/file.txt).FullName - which is both cumbersome and inefficient.

So, to me the pragmatic solution is: implement a provider whose items are _leaf_ items that represent _path strings_ - and make it clear in the documentation that sf:-based paths cannot serve directly as file-system items.

Well & (gi alias:gcm) works

Sure, and so does & $alias:gcm (though a _string_ is returned in that case), but the question was whether the detour via Get-Item / namespace variable notation can be avoided in this case.

The question was mostly academic, though: given the negligible benefit from enabling this and the potential for pitfalls, I agree that it's not worth tackling.

The proposal was to make it a _container_, but, yes, allowing Get-Content to report the container's _path_ would be an anomaly (something like Get-Content $HOME currently fails, as does ${C:\Windows}, with Unable to get content because it is a directory:)

That is again pretty confusing imo. As an aside I don't think it's a good idea to allow a container to work with Get-Content just for this workaround.

However, conversely that means that if we wait until we can make the sf: drive's provider a specialized, full-fledged file-system provider, it is $sf:Desktop that won't work - which takes away what I perceive to be the primary use case: I want to be able to use $sf:Desktop/file.txt

Yeah I totally get the use case. It's still gonna confuse the hell out of folks though.

and not be forced to use "$(Get-Item sf:Desktop)/file.txt" or (Get-Item sf:Desktop/file.txt).FullName - which is both cumbersome and inefficient.

Same way you resolve file system paths though. Also Resolve-Path.

So, to me the pragmatic solution is: implement a provider whose items are _leaf_ items that represent _path strings_ - and make it clear in the documentation that sf:-based paths cannot serve directly as file-system items.

If it had to be added as it's own provider, that'd be the only way that it could be implemented currently. I don't think it should be though, it'd be way too difficult to understand.

Honestly I'd rather see it as special casing the FileSystemProvider's path resolution. I'm not really sure if it's worth the extra complication, but that would at least be very easy to understand.

Sure, and so does & $alias:gcm (though a _string_ is returned in that case), but the question was whether the detour via Get-Item / namespace variable notation can be avoided in this case.

Yeah I gotcha, the rest of the sentence was the direct answer.

I don't think it's a good idea to allow a container to work with Get-Content just for this workaround.

Agreed.

Same way you resolve file system paths though. Also Resolve-Path

Yes, but that is cumbersome for this use case.

If it had to be added as its own provider, that'd be the only way that it could be implemented currently. I don't think it should be though, it'd be way too difficult to understand.

If the primary usage is $sf:Desktop in path strings, I don't think people would think twice about it, just like they don't think about $env:HOME being underpinned by a provider operation that is the equivalent of Get-Content Env:HOME.
With proper tab-completion, you may never need to touch the provider cmdlets, and if you think of $sf:Desktop as just a special variable (value), Set-Content $sf:Desktop isn't a stretch either.

Yes, but that is cumbersome for this use case.

Could you elaborate? You'd only need to do this if you were passing the path to something that didn't understand PSPath's. Otherwise it'd work similar to a PSDrive.

If the primary usage is $sf:Desktop in path strings, I don't think people would think twice about it, just like they don't think about $env:HOME being underpinned by a provider operation that is the equivalent of Get-Content Env:HOME.

Environment variables aren't just paths though, users already think of them like variables.

Could you elaborate?

I want to be able to use $sf:Desktop/file.txt, which I can't if sf:Desktop is a container (directory) item.

Environment variables aren't just paths though, users already think of them like variables.

That's my point: users can and should think of $sf:Desktop as (special) variables that contain path strings.
Underpinning this with a provider is simply a technical requirement to enable this syntax.

I want to be able to use $sf:Desktop/file.txt, which I can't if sf:Desktop is a container (directory) item.

Right I got that, but why do you want that instead of working it into actual path resolution.

Why do you need to do:

Copy-Item $sf:Desktop/pwsh.lnk ./pwsh.lnk

Instead of

Copy-Item sf:/Desktop/pwsh.lnk ./pwsh.lnk

That's my point: users can and should think of $sf:Desktop as (special) variables that contain path strings.
Underpinning this with a provider is simply a technical requirement to enable this syntax.

I understand how you want it to be viewed, but I think that's challenging. Introducing a provider whose content is a file system path as a string that you then pass to a different provider to be resolved to an item is inherently difficult to understand.

I understand how you _want_ it to be viewed

It's more than that: I'm proposing that it be _actively framed this way_: to de-emphasize the provider aspect _as an implementation detail_, which avoids the conceptual problems. Instead, we should frame $sf:* as _special variables_ that return _path strings_.

In that vein:

Copy-Item sf:/Desktop/pwsh.lnk ./pwsh.lnk

I have no idea what sf:/Desktop is (I'm playing ignorant to make a point) - all I know is that $sf:Desktop returns the platform-appropriate Desktop _path string_ that I can use as part of larger path strings - just like I would use $env: variable notation; e.g.:

Copy-Item $env:ProgramFiles/foo/bar.exe ./bar.exe

If sf: is a file drive why should it work another than Temp: drive?

sf: reminds me Windows Library concept - collect folders in an entity.

Because that would require sf: to be a drive in the file system. And some people dislike this solution as too dirty, intrusive or whatever. Also, it smells of My Computer, which is a Windows GUI thing, which means we would have a killer in the room 😲

If we allowed direct execution of variables

There is no _variable_ here (whose _value_ you can invoke with &), only a _literal path_ referring to an item exposed by the Alias provider (via its one and only alias: drive).

A variable named SCRIPT.PS1 has a literal path of VARIABLE:SCRIPT.PS1. So, when I pass VARIABLE:/SCRIPT.PS1 as a command, I expect the script in the variable to be executed. But that is not allowed and I have to put the script onto the file system in order to be able to execute it.

@iSazonov

sf: would be a _PowerShell_ drive, but not a PowerShell _file-system_ drive - it currently _cannot_ be the latter, for the technical reasons explained by @SeeminglyScience, but even if it could (it's unclear if and when), it would introduce more problems than it solves.

(I've come full circle on this: the original proposal was to _not_ conceive of it as a file-system drive, then we discussed what it would take to do so, now I'm back to the original proposal).

Just like alias: or env: aren't _file-system_ drives, sf: wouldn't be either: it would be a drive that exposes items representing _strings_ that happen to be _file-system path strings_ (just like many environment variables on Windows contain file-system paths, such as $env:ProgramFiles).

The primary purpose of the sf: drive would be to _enable namespace variable notation_, so that something like $sf:Desktop expands to the _path string_ that is the platform-appropriate native file-system Desktop path, and can be used as-is in building longer paths ($sf:Desktop/file.txt) and calls that expect native file-system paths _both in argument and in expression mode_.

Just like most users rarely, if ever, interact with the Env: drive via provider _cmdlets_ - they instead rely on namespace variable notation such as $env:ProgramFiles - they would just use $sf:Desktop, or tab-completion for discovery. (Of course, they _can_ use Get-ChildItem sf: for more convenient and sophisticated discovery, if they choose to.)

The only technical reason I can think of is that SF:/, unlike TEMP:/, would not correspond to any actual location, so it would be a virtual drive with effective links inside, a bit like \\.\, which is an interesting idea better left to be implemented by the underlying OS than by ourselves.

@mklement0

[...] (note: Desktop and DesktopDir, though technically distinct, seem to refer to the same location in practice; the former is distinguished from the latter as being the "logical Desktop rather than the physical file system location" - not sure what that means).

  • Desktop
  • DesktopDirectory

_Note to the uninitiated: Just to be confusing, "the shell" refers to an entirely different component of Windows than PowerShell or cmd.exe. You probably know it as explorer.exe, though naturally most of it is actually implemented in DLLs ..._

I think this distinction is/was only relevant with SHGetSpecialFolderLocation and SHGetFolderLocation, which build paths in the shell's namespace (ITEMIDLIST structures, also called PIDLs for some reason). CSIDL_DESKTOP would give you the thing that's actually represented on your desktop; CSIDL_DESKTOPDIRECTORY would give you the place you get to when you type e.g. %USERPROFILE%Desktop into Explorer's address bar.

[The documentation seems to imply that both values do the same thing since Vista, but I'm not going to assume that's actually true.]

In particular, CSIDL_DESKTOP would have whatever extra items [Computer, Recycling Bin, etc.] the user has configured to appear on the desktop, as well as items contributed from CSIDL_COMMON_DESKTOPDIRECTORY (i.e. the All Users desktop shortcuts).

Naturally, none of this is in any way relevant to the return value of System.Environment.GetFolderPath.

is there any reason no one raised a Get-SpecialFolder cmdlet?

I just got pointed to this issue. The whole subject certainly deserves careful consideration and design, but this may perhaps help someone now:

Several years ago I had created a PS module for inspecting and manipulating known folders using the COM API (which provides much more functionality than the legacy API used internally by the .NET Environment class). I wanted to ease my pain of mass redirecting the folders (including the Public ones) out of the system drive when setting up multiple computers for several members of my family - by default possible only by clicking in the GUI (and requiring temporarily disabling UAC to move the Public folders). The goal was to be able to write

# as any user
Get-KnownFolder | Move-KnownFolder -Destination F:\Profiles\$Env:UserName
# single folder, with rename
Move-KnownFolder -SingleFolder (Get-KnownFolder -Name Personal) -NewPath D:\Data\$Env:UserName\Docs
# as admin
Get-KnownFolder -Public | Move-KnownFolder -Destination F:\Profiles\Public

The module is fully functional (I tend to use it any time I set up a new computer); I just ran out of free time and motivation to make the finishing touches - writing some docs/examples and publishing to PSGallery. I could probably make that effort if there is interest.

For inspecting the paths we would use:

PS C:\> (Get-KnownFolder -Name Personal).Path
F:\Profiles\jberezanski\Documents

My implementation is obviously Windows-only; I don't know how many of the Windows Shell concepts are applicable to other platforms. When I thought about exposing more of those concepts in PS, I tended to think in terms of a provider + drive - Shell:\, with the root representing the Shell namespace root (the "logical" Desktop). However, I did not come upon actual use cases for navigating that namespace and all my practical needs were satisfied by the two cmdlets mentioned above.

(Personally, I'd love to see a cross platform way for referencing the AppData/Local AppData known folders, so that applications could stop polluting the root of the user profile directory with their config/data files and folders, but that is a different rant altogether.)

Thanks, @jberezanski.

I don't know how many of the Windows Shell concepts are applicable to other platforms

I've summarized which known folders apply to which platforms above.

While I think that Get-KnownFolder would make a nice addition (I've created a function of the same name for myself a while back), I think that something like $sf:Desktop is _also_ called for:

Specifically, it would lessen the temptation to write code such as 'hi' > $HOME/Desktop/foo.txt, given that the only fully robust formulation would be the more verbose 'hi' > "$(Get-KnownFolder Desktop)/foo.txt" (note the required double quotes; a Join-Path command would be even more verbose).

Being able to use 'hi' > $sf:Desktop/foo.txt is the best of both worlds: robust _and_ convenient.

Being able to use 'hi' > $sf:Desktop/foo.txt is the best of both worlds: robust and convenient.

Fully agreed.

I also don't see the syntax as confusing; many commonly used environment variables contain paths and we do things like mkdir $Env:TEMP\xyz all the time. I think it's not that difficult to learn that another prefix gives access to an additional set of paths - that a new provider is in the works there is an implementation detail, not important in the typical use case of just wanting to know the path.

And, possibly, _writing_ to $sf:Something could perform the equivalent of my Move-KnownFolder...
...or maybe not, because then it would be too easy to do it by accident and the operation may be dangerous to user data.

Yes, I think that would be too dangerous, so a Move-KnownFolder cmdlet is preferable, although - unlike Get-KnownFolder - it would be Windows-specific.

By the way, the Environment+SpecialFolder enum, being based on the legacy pre-Vista SHGetFolderPath function, is missing a lot of Known Folders added in Vista and later (some of them rather useful), like Downloads, CommonDownloads, SavedPictures, Camera Roll, UserProgramFiles, OneDrive and many more.

In fact, the Known Folders API allows applications to register new folders with the system, so the list is not fixed.

I ran this on my system to see which Known Folders with non-empty paths were covered by the SpecialFolder enum/GetFolderPath method and out of 95 such Known Folders 46 are not exposed by GetFolderPath:

$specialFolderPaths = [enum]::GetValues([Environment+SpecialFolder]) | % { $p = [Environment]::GetFolderPath($_); [pscustomobject]@{SFName=$_; Path=$p}}
$allKnownFolders = Get-KnownFolder -All
$knownFoldersWithNonEmptyPath = $allKnownFolders | ? Path -ne $null | select Category,Name,CanRedirect,Path
$knownFoldersJoinedWithSpecialFolderPaths = $knownFoldersWithNonEmptyPath | % { $kf = $_; $sfp = $specialFolderPaths | ? Path -eq $kf.Path; $kf | select *,@{Name='SFP';Expression={$sfp}}}
$allKnownFolders | measure | select -expand Count
139
$knownFoldersWithNonEmptyPath | measure | select -expand Count
95
$knownFoldersJoinedWithSpecialFolderPaths | ? SFP -eq $null | measure | select -expand Count
46

On Windows, I'd like to be able to access all Known Folders present on the system (the implementation could enumerate them during initialization). On other platforms a certain basic set should probably be defined, as described earlier in this thread (but not necessarily limited to [Environment+SpecialFolder] values).

One thing to note - some Known Folder names contain spaces ("Camera Roll"); this should not be a problem with the proposed syntax, because we already need to deal with problematic characters in environment variable names (${Env:ProgramFiles(x86)}).

Was this page helpful?
0 / 5 - 0 ratings