Even in the most standardized networks, it's easy to specify the incorrect paths to special folders such as My Documents or temporary folders that have special roles in Windows. Fortunately, you can ask Windows where particular folders are located. After I explain the root of the problem, I'll show you how to make Microsoft PowerShell scripts consistently find the right path, even in nonstandard setups.

Understanding the Problem

Although working with files and folders is a standard task for network administrators, the specific paths to those folders aren't standard. Windows installations, upgrades, hardware installations, user policies, and machine policies can all lead to wildly varying paths for special folders. Even in standardized networks, variations are possible due to machine roles or user requirements.

Some of these paths are relatively easy to find with PowerShell variables. For example, the $home variable always contains the literal path to a user's home folder. However, finding other paths might require some guesswork. For example, if your users are mainly running Windows 7 or Windows Vista, you could try accessing the folder that contains temporary Internet files by using the path \$home\AppData\Local\Microsoft\Windows\Temporary Internet Files. However, this path will fail for users running an earlier Windows OS or if their temporary Internet files are redirected to a different location. Standardizing isn't always possible, and coding in all sorts of checks for special cases will end up being cumbersome and confusing.

Fortunately, there's an easier way. Instead of guessing, you can use the Microsoft .NET Framework's System.Environment class to obtain paths. By using the OS APIs, you have access to more information than you can get from environment variables alone. The information is also more reliable because Windows uses the same technique to determine paths.

 

Using System.Environment in PowerShell

In PowerShell, you specify a .NET class by using its name in square brackets, like this: [System.Environment]. To simplify using the System namespace, PowerShell lets you omit the System. portion, so you can use [Environment] for brevity.

To find the paths of special folders, the first step is to determine what special folders are exposed. The Environment class has a special enumerated list of the canonical names for these folders. A quick way to show these names is to use the command

[Environment+SpecialFolder]::GetNames([Environment+SpecialFolder])

In Windows 7, this command returns a list of names like that shown in Figure 1. You can find the meanings of these names in the "Environment.SpecialFolder Enumeration" web page. Note that some of the special folders listed in this web page aren't available within PowerShell.

Figure 1: Special folders’ canonical names
Figure 1: Special folders’ canonical names

At the bottom of the "Environment.SpecialFolder Enumeration" web page, you'll find a PowerShell script by Thomas Lee demonstrating how to list the special folders' names and their paths. There are two details you should know about accessing these folders. First, if a folder doesn't exist, you'll get an empty path back. Second, some folders might not be accessible from an unprivileged account.

Thomas Lee's script works well if you just want to see the names and paths. However, what can you do if you want to easily use those paths in scripts? There are many possible approaches. I'll show you three of them. Each is useful in different situations, but all of them require that you understand what the names like ProgramFiles or Startup mean, as defined in the "Environment.SpecialFolder Enumeration" web page.

Suppose you want to access a specific folder, such as the one named Desktop. In PowerShell syntax, the ID of this specific folder is [Environment+SpecialFolder]::Desktop. You provide this ID as an argument to the Environment class's static GetFolderPath method, like this

[Environment]::GetFolderPath("Desktop")

This command returns the exact path to the current user's Desktop folder.

To get an entire list of folders and their paths (which are accessible by name), you can use the code in Listing 1. It creates a hash table named $SpecialFolders, gets the names of all the special folders known to the .NET Framework, then gets the paths of those folders. The code adds each non-empty special folder path to the hash table, indexed by the folder name. You can see the contents of the hash table by simply entering $SpecialFolders after the PowerShell prompt and pressing Enter. Figure 2 shows sample results from running the code in Listing 1.

Figure 2: Special folders’ names and paths in a hash table
Figure 2: Special folders’ names and paths in a hash table

Turning Special Folders Into Drives

The technique that I like to use is to create PowerShell drives from each special folder path, using the canonical folder name as the drive name. The code in Listing 2 creates named drives from the special folder paths, producing output that will look similar to Figure 3, depending on your system. It's then possible to access items using these drive names without having to think about user context or actual paths. You can see the drives' actual paths by running the code

 Get-PSDrive | ft name,root -AutoSize
Figure 3: PowerShell drives with mappings for special folders
Figure 3: PowerShell drives with mappings for special folders

When you need to perform tasks in one of the special folders, you just need to specify the drive name. For example, if you need to copy the entire set of Favorites items in Microsoft Internet Explorer (IE) to a folder on the F drive, you'd run the command

Copy-Item -Path Favorites:\ -Destination F:\Favorites -Recurse 

 If you want to list the last 20 documents accessed in reverse chronological order, you'd use the command

gci Recent: | Sort-Object -Property LastWriteTime -Descending The code in Listing 3 finds the largest 10 files in the Documents folder and shows them in descending order based on size. The output includes the last time the files were modified (LastWriteTime) so that you can determine if any of the files are currently being edited. The code in Listing 4 retrieves the display name and URL for each Favorite item in IE, generating a scrollable list with spaces between items for easy reading.

Considering Other Methods

In my experience, I've found that using System.Environment is the most reliable technique for finding a path to a special folder. Creating PowerShell drives from these paths makes this technique very useful. However, sometimes you might not be able to find what you need this way. Although I won't go into detail about them, there are alternatives.

As I mentioned previously, you can try constructing a path using standard PowerShell variables (or shell variables accessible through the env: drive) and some guesswork. However, this technique is much less reliable. At the very least, you should use PowerShell's Test-Path cmdlet to confirm that the path actually exists.

Another technique that's a little more complicated but gives access to even more special folders is to use the Shell.Application COM object's NameSpace method. You can also use this method in Windows Script Host (WSH) scripts. To explore this technique, search the Internet using Shell.Application and NameSpace as the search terms.

Overall, my preferred method remains creating PowerShell drives using the code in Listing 2. It lets me concentrate on the task I'm trying to accomplish instead of the mechanics for performing it.

 

Listing 1: Code to Create a Hash Table That Contains Special Folders’ Names and Paths

$SpecialFolders = @{}

$names = [Environment+SpecialFolder]::GetNames( `

  [Environment+SpecialFolder])

foreach($name in $names)

{

  if($path = [Environment]::GetFolderPath($name)){

    $SpecialFolders[$name] = $path

  }

}

 

Listing 2: Code to Create Named Drives from Special Folders’ Paths

$names = [Environment+SpecialFolder]::GetNames( `

  [Environment+SpecialFolder])

foreach($name in $names)

{

  if($path = [Environment]::GetFolderPath($name)){

   New-PSDrive -Name $name -PSProvider FileSystem -Root $path

  }

}

 

Listing 3: Code to Find the 10 Largest Files in the Documents Folder

gci Personal: -Recurse -Force -ea SilentlyContinue |

  Sort-Object -Property Length -Descending |

  Select-Object -First 10 |

  Format-Table -AutoSize -Wrap -Property `

    Length,LastWriteTime,FullName

 

Listing 4: Code to Display the Name and URL for Each Favorite Item in IE

gci -Path Favorites:\ -Recurse |

  ?{$_.Length} |

  %{$_.BaseName; Get-Content $_.FullName |

  Select-String ^url=*|

  %{$_ -replace "^url=","" ;""}

}