1

I'm attempting to find a users downloads directory in powershell using this line: $downloadDirectory = "$env:USERPROFILE\Downloads"

But I'm getting the following error:

Get-ChildItem : Cannot find path 'C:\Users\skewb\Downloads' because it does not exist.
At D:\Users\Skewb\Documents\repos\DotaMatchFinder\unzip_move_run.ps1:16 char:5
+     Get-ChildItem -Path $downloadDirectory
+     ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : ObjectNotFound: (C:\Users\skewb\Downloads:String) [Get-ChildItem], ItemNotFoundException
    + FullyQualifiedErrorId : PathNotFound,Microsoft.PowerShell.Commands.GetChildItemCommand

When I type the following into the file explorer %USERPROFILE%/Downloads I'm taken to the correct path D:\Users\Skewb\Downloads

Is this intended? Do I need to find this directory another way?

Compo
  • 36,585
  • 5
  • 27
  • 39
Skewjo
  • 379
  • 3
  • 12
  • Odd. How did you launch PowerShell? – Mathias R. Jessen Feb 03 '21 at 14:13
  • It's occurring when I launch the script by double-clicking and when I run it through ISE. – Skewjo Feb 03 '21 at 14:27
  • 4
    The downloads folder could be redirected, so hardcoding `$env:USERPROFILE\Downloads` is wrong. Try to use the known folder API instead. Here is a PowerShell cmdlet: https://github.com/proxb/PInvoke/blob/master/Get-KnownFolderPath.ps1 – zett42 Feb 03 '21 at 14:46
  • I agree with the previous comment (it is not correct to 'hard code' a common directory name), but in any case I cannot reproduce. – Bill_Stewart Feb 03 '21 at 14:55
  • Canonical reference: [SHGetKnownFolderPath](https://learn.microsoft.com/en-us/windows/win32/api/shlobj_core/nf-shlobj_core-shgetknownfolderpath). And yes, you do not retrieve a known folder location by concatenating strings. – IInspectable Feb 03 '21 at 15:12
  • Does this answer your question? [PowerShell Could Not Find Item - Path With Spaces IOException](https://stackoverflow.com/questions/17572541/powershell-could-not-find-item-path-with-spaces-ioexception) or [Powershell getting full path information](https://stackoverflow.com/q/26001631/1701026) – iRon Feb 03 '21 at 15:19
  • You could also read it from the registry: `Get-ItemPropertyValue -Path 'HKCU:\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\User Shell Folders' -Name '{374DE290-123F-4565-9164-39C4925E467B}'` – Theo Feb 03 '21 at 15:23
  • 1
    @Theo, that is tempting, but officially recommended against - see https://devblogs.microsoft.com/oldnewthing/20031103-00/?p=41973 – mklement0 Feb 03 '21 at 15:31
  • 1
    @the Unless you can link to official documentation, the registry is not a programming interface. It's an implementation detail. Though many will assure you, that Microsoft would never change things around, things have changed recently, and Microsoft **will** allow broken code to break as a result of updating the OS. Reading from unofficial registry keys is broken code. – IInspectable Feb 03 '21 at 16:30
  • Why are you trying to find the users `Downloads` directory? You may think I'm asking an irrelevant question but I do have a reason. Even web browsers from the without morals, data stealing, companies ask you if you want to use a specific/default location for all downloads, or if you would like to choose each time. You are not the end user, so should not be deciding where they must store your payload, or searching through the content of that location without requesting their permission in advance. You could simply provide a CLI input prompt, or GUI selection dialog, for them to identify it. – Compo Feb 03 '21 at 17:00
  • @Compo I understand. This is kind of a quick and dirty workaround I'm using to make the functionality I'm trying to achieve quickly and also relatively painless for the end user if possible. I've got a chrome extension that's downloading dota 2 replays. I'm then using a powershell script to unzip and redirect the replay file to the correct correct directory. My assumption so far has been that the easiest method without creating a UI is to handle a few common use cases automagically. – Skewjo Feb 03 '21 at 17:23
  • Use the end users pre-defined `TEMP` or `TMP` environment variables for your location then. That is exactly what I'd suggest it is used for, not one of the end users personal data storage areas! – Compo Feb 03 '21 at 17:30
  • @Compo I hear your concerns, but in the effort of making script require as minimal setup as possible, I need to fetch and use the users chrome download directory if possible because download output from a chrome extension is very difficult to redirect. Having the script attempt to point directly to the location the files should have just been downloaded to is the most convenient option at the moment. – Skewjo Feb 03 '21 at 17:42
  • You're still doing it wrong then! There should be absolutely no expectation that your end user is using the Downloads directory under their UserProfile, or that location redirected, if they have done so. What you should be probably be doing for your specific case, is parsing the JSON file, `\Google\Chrome\User Data\Default\Preferences` under their `LocalAppData` environment variable location, and retrieving it from `"default_directory":` within `"download": {`. There is no real need to use a 'quick and dirty workaround', when PowerShell already has the tools to achieve your programming goal! – Compo Feb 03 '21 at 18:07
  • I'm actually doing exactly that regarding chromes preferences JSON file, but in my case the "download" property's "default_directory" property is blank, so I'm using the users default download directory. – Skewjo Feb 03 '21 at 18:15
  • ```$chromePreferences = Get-Content "C:\Users\$env:USERNAME\AppData\Local\Google\Chrome\User Data\Default\preferences" $downloadPreference = $chromePreferences | ConvertFrom-Json | Select-Object -Property "download" $downloadDirectory = $downloadPreference.download.default_directory if($downloadDirectory){ Write-Output("User's default Chrome directory is set.") } else{ Write-Output("User's default Chrome directory is not set. Using default location.") $downloadDirectory = (New-Object -ComObject Shell.Application).NameSpace('shell:Downloads').Self.Path }``` – Skewjo Feb 03 '21 at 18:16
  • @Skewjo, at no point in your question or subsequent comment exchanges did you mention, or imply that you were doing that, otherwise I wouldn't have needed to join in! Also, what was wrong with using `$Env:LocalAppData\Google\Chrome\User Data\Default\preferences`, as I had previously suggested? – Compo Feb 03 '21 at 20:14

1 Answers1

4

Your symptom is mysterious (see bottom section), but can - probably - be bypassed with a solution that is conceptually preferable anyway:

Since, as zett42 points out, the so-called known or special folders (folders known to the system to have specific purposes) may be redirected to a location other than their default location, the robust solution is to ask the system for that location, via a symbolic name.

Unfortunately, that isn't quite as straightforward as one would hope:

$downloadDirectory = 
  (New-Object -ComObject Shell.Application).NameSpace('shell:Downloads').Self.Path
  • A third-party list of supported symbolic names can be found here; the official conceptual help topic about known folders is not shell-friendly, unfortunately.

  • As of PowerShell 7.1, there is no PowerShell-native way to query known folders, but there's pending GitHub proposal #6966 to change that.

  • While .NET does have an API, [System.Environment]::GetFolderPath($symbolicName), its set of special folders is incomplete, and notably doesn't include the Downloads folder.

  • (New-Object -ComObject WScript.Shell).SpecialFolders($symbolicName) is similarly incomplete with respect to the supported set of special folders.

Caveat: While it is tempting to try to get known-folder information from the registry, this is officially advised against - see this blog post by Raymond Chen.


As for your symptom:

My guess is that something in your PowerShell profile(s) accidentally redefines the USERPROFILE environment, which, while technically possible, is obviously ill-advised.

mklement0
  • 382,024
  • 64
  • 607
  • 775
  • Awesome, thank you @mklement0. You(I really) would think that fetching the symbolic name would be as easy as "$env:USERPROFILE/Downloads", but I'm certainly glad I didn't have time to try it before you provided your answer or I would've wasted a lot of time. I'll try to take a look for any manual powershell profile changes I've set up and get back to you. – Skewjo Feb 03 '21 at 16:56
  • 1
    Glad to hear it was helpful, @Skewjo. Note that `$env:USERPROFILE/Downloads` _should_ work in a _default_ configuration - but since it's possible to redefine the location of known folders, using the system API is the most robust solution. – mklement0 Feb 03 '21 at 17:00