Chapter 15. Working with the File System

by Mar 23, 2012

Working with files and folders is traditionally one of the most popular areas for administrators. PowerShell eases transition from classic shell commands with the help of a set of predefined "historic" aliases and functions. So, if you are comfortable with commands like "dir" or "ls" to list folder content, you can still use them. Since they are just aliases – references to PowerShell’s own cmdlets – they do not necessarily work exactly the same anymore, though.

In this chapter, you'll learn how to use PowerShell cmdlets to automate the most common file system tasks.

Topics Covered:

Getting to Know Your Tools

One of the best ways to get to know your set of file system-related PowerShell cmdlets is to simply list all aliases that point to cmdlets with the keyword "item" in their noun part. That is so because PowerShell calls everything "item" that lives on a drive.

PS> Get-Alias -Definition *-item*

CommandType     Name                  ModuleName           Definition
-----------     ----                  ----------           ----------
Alias           cli                                        Clear-Item
Alias           clp                                        Clear-ItemProperty
Alias           copy                                       Copy-Item
Alias           cp                                         Copy-Item
Alias           cpi                                        Copy-Item
Alias           cpp                                        Copy-ItemProperty
Alias           del                                        Remove-Item
Alias           erase                                      Remove-Item
Alias           gi                                         Get-Item
Alias           gp                                         Get-ItemProperty
Alias           ii                                         Invoke-Item
Alias           mi                                         Move-Item
Alias           move                                       Move-Item
Alias           mp                                         Move-ItemProperty
Alias           mv                                         Move-Item
Alias           ni                                         New-Item
Alias           rd                                         Remove-Item
Alias           ren                                        Rename-Item
Alias           ri                                         Remove-Item
Alias           rm                                         Remove-Item
Alias           rmdir                                      Remove-Item
Alias           rni                                        Rename-Item
Alias           rnp                                        Rename-ItemProperty
Alias           rp                                         Remove-ItemProperty
Alias           si                                         Set-Item
Alias           sp                                         Set-ItemProperty

In addition, PowerShell provides a set of cmdlets that help dealing with path names. They all use the noun "Path", and you can use these cmdlets to construct paths, split paths into parent and child, resolve paths or check whether files or folders exist.

PS> Get-Command -Noun path

CommandType     Name                  ModuleName           Definition
-----------     ----                  ----------           ----------
Cmdlet          Convert-Path          Microsoft.PowerSh... ...
Cmdlet          Join-Path             Microsoft.PowerSh... ...
Cmdlet          Resolve-Path          Microsoft.PowerSh... ...
Cmdlet          Split-Path            Microsoft.PowerSh... ...
Cmdlet          Test-Path             Microsoft.PowerSh... ...

Accessing Files and Directories

Use Get-ChildItem to list the contents of a folder. There are two historic aliases: Dir and ls. Get-ChildItem handles a number of important file system-related tasks:

  • Searching the file system recursively and finding files
  • Listing hidden files
  • Accessing files and directory objects
  • Passing files to other cmdlets, functions, or scripts

Listing Folder Contents

If you don't specify a path, Get-ChildItem lists the contents of the current directory. Since the current directory can vary, it is risky to use Get-Childitem in scripts without specifying a path. Omit a path only when you use PowerShell interactively and know where your current location actually is.

Time to put Get-ChildItem to work: to get a list of all PowerShell script files stored in your profile folder, try this:

PS> Get-ChildItem -Path $home -Filter *.ps1

Most likely, this will not return anything because, typically, your own files are not stored in the root of your profile folder. To find script files recursively (searching through all child folders), add the switch parameter -Recurse:

PS> Get-ChildItem -Path $home -Filter *.ps1 -Recurse

This may take much longer. If you still get no result, then maybe you did not create any PowerShell script file yet. Try searching for other file types. This line will get all Microsoft Word documents in your profile:

PS> Get-ChildItem -Path $home -Filter *.doc* -Recurse

When searching folders recursively, you may run into situations where you do not have access to a particular subfolder. Get-ChildItem then raises an exception but continues its search. To hide such error messages, add the common parameter -Erroraction SilentlyContinue which is present in all cmdlets, or use its short form -ea 0:

PS> Get-ChildItem -Path $home -Filter *.doc* -Recurse -ea 0

The -Path parameter accepts multiple comma-separated values, so you could search multiple drives or folders in one line. This would find all .log-files on drives C: and D: (and takes a long time because of the vast number of folders it searches):

PS> Get-ChildItem c:, d: -Filter *.log -Recurse -ea 0

If you just need the names of items in one directory, use the parameter -Name:

PS> Get-ChildItem -Path $env:windir -Name

To list only the full path of files, use a pipeline and send the results to Select-Object to only select the content of the FullName property:

PS> Get-ChildItem -Path $env:windir | Select-Object -ExpandProperty FullName

Some characters have special meaning to PowerShell, such as square brackets or wildcards such as '*'. If you want PowerShell to ignore special characters in path names and instead take the path literally, use the -LiteralPath parameter instead of -Path.

Choosing the Right Parameters

In addition to -Filter, there is a parameter that seems to work very similar: -Include:

PS> Get-ChildItem $home -Include *.ps1 -Recurse

You'll see some dramatic speed differences, though: -Filter works significantly faster than -Include.

PS> (Measure-Command {Get-ChildItem $home -Filter *.ps1 -Recurse}).TotalSeconds
4,6830099
PS> (Measure-Command {Get-ChildItem $home -Include *.ps1 -Recurse}).TotalSeconds
28,1017376

You also see functional differences because -Include only works right when you also use the -Recurse parameter.

The reason for these differences is the way these parameters work. -Filter is implemented by the underlying drive provider, so it is retrieving only those files and folders that match the criteria in the first place. That's why -Filter is fast and efficient. To be able to use -Filter, though, the drive provider must support it.

-Include on the contrary is implemented by PowerShell and thus is independent of provider implementations. It works on all drives, no matter which provider is implementing that drive. The provider returns all items, and only then does -Include filter out the items you want. This is slower but universal. -Filter currently only works for file system drives. If you wanted to select items on Registry drives like HKLM: or HKCU:, you must use -Include.

-Include has some advantages, too. It understands advanced wildcards and supports multiple search criteria:

# -Filter looks for all files that begin with "[A-F]" and finds none:
PS> Get-ChildItem $home -Filter [a-f]*.ps1 -Recurse

# -Include understands advanced wildcards and looks for files that begin with a-f and
# end with .ps1:
PS> Get-ChildItem $home -Include [a-f]*.ps1 -Recurse

The counterpart to -Include is -Exclude. Use -Exclude if you would like to suppress certain files. Unlike -Filter, the -Include and -Exclude parameters accept arrays, which enable you to get a list of all image files in your profile or the windows folder:

Get-Childitem -Path $home, $env:windir -Recurse -Include *.bmp,*.png,*.jpg, *.gif -ea 0

If you want to filter results returned by Get-ChildItem based on criteria other than file name, use Where-Object (Chapter 5).

For example, to find the largest files in your profile, use this code – it finds all files larger than 100MB:

PS> Get-ChildItem $home -Recurse | Where-Object { $_.length -gt 100MB }

If you want to count files or folders, pipe the result to Measure-Object:

PS> Get-ChildItem $env:windir -Recurse -Include *.bmp,*.png,*.jpg, *.gif -ea 0 |
>> Measure-Object | Select-Object -ExpandProperty Count
>>
6386

You can also use Measure-Object to count the total folder size or the size of selected files. This line will count the total size of all .log-files in your windows folder:

PS> Get-ChildItem $env:windir -Filter *.log -ea 0 | Measure-Object -Property Length -Sum |
>>
Select-Object -ExpandProperty Sum

Getting File and Directory Items

Everything on a drive is called "Item", so to get the properties of an individual file or folder, use Get-Item:

PS> Get-Item $env:windirexplorer.exe | Select-Object *


PSPath            : Microsoft.PowerShell.CoreFileSystem::C:Windowsexplorer.e
                    xe
PSParentPath      : Microsoft.PowerShell.CoreFileSystem::C:Windows
PSChildName       : explorer.exe
PSDrive           : C
PSProvider        : Microsoft.PowerShell.CoreFileSystem
PSIsContainer     : False
VersionInfo       : File:             C:Windowsexplorer.exe
                    InternalName:     explorer
                    OriginalFilename: EXPLORER.EXE.MUI
                    FileVersion:      6.1.7600.16385 (win7_rtm.090713-1255)
                    FileDescription:  Windows Explorer
                    Product:          Microsoft® Windows® Operating System
                    ProductVersion:   6.1.7600.16385
                    Debug:            False
                    Patched:          False
                    PreRelease:       False
                    PrivateBuild:     False
                    SpecialBuild:     False
                    Language:         English (United States)

BaseName          : explorer
Mode              : -a---
Name              : explorer.exe
Length            : 2871808
DirectoryName     : C:Windows
Directory         : C:Windows
IsReadOnly        : False
Exists            : True
FullName          : C:Windowsexplorer.exe
Extension         : .exe
CreationTime      : 27.04.2011 17:02:33
CreationTimeUtc   : 27.04.2011 15:02:33
LastAccessTime    : 27.04.2011 17:02:33
LastAccessTimeUtc : 27.04.2011 15:02:33
LastWriteTime     : 25.02.2011 07:19:30
LastWriteTimeUtc  : 25.02.2011 06:19:30
Attributes        : Archive

You can even change item properties provided the file or folder is not in use, you have the proper permissions, and the property allows write access. Take a look at this piece of code:

"Hello" > $env:temptestfile.txt
$file = Get-Item $env:temptestfile.txt
$file.CreationTime
$file.CreationTime = '1812/4/11 09:22:11'
Explorer $env:temp

This will create a test file in your temporary folder, read its creation time and then changes the creation time to November 4, 1812. Finally, explorer opens the temporary file so you can right-click the test file and open its properties to verify the new creation time. Amazing, isn't it?

Passing Files to Cmdlets, Functions, or Scripts

Because Get-ChildItem returns individual file and folder objects, Get-ChildItem can pass these objects to other cmdlets or to your own functions and scripts. This makes Get-ChildItem an important selection command which you can use to recursively find all the files you may be looking for, across multiple folders or even drives.

For example, the next code snippet finds all jpg files in your Windows folder and copies them to a new folder:

PS> New-Item -Path c:WindowsPics -ItemType Directory -ea 0
PS> Get-ChildItem $env:windir -Filter *.jpg -Recurse -ea 0 |
>>
Copy-Item -Destination c:WindowsPics

Get-ChildItem first retrieved the files and then handed them over to Copy-Item which copied the files to a new destination.

You can also combine the results of several separate Get-ChildItem commands. In the following example, two separate Get-ChildItem commands generate two separate file listings, which PowerShell combines into a total list and sends on for further processing in the pipeline. The example takes all the DLL files from the Windows system directory and all program installation directories, and then returns a list with the name, version, and description of DLL files:

PS> $list1 = @(Get-ChildItem $env:windirsystem32*.dll)
PS> $list2 = @(Get-ChildItem $env:programfiles -Recurse -Filter *.dll)
PS> $totallist = $list1 + $list2
PS> $totallist | Select-Object -ExpandProperty VersionInfo | Sort-Object -Property FileName

ProductVersion   FileVersion      FileName
--------------   -----------      --------
3,0,0,2          3,0,0,2          C:Program FilesBonjourmdnsNSP.dll
2, 1, 0, 1       2, 1, 0, 1       C:Program FilesCommon FilesMicrosoft Sh...
2008.1108.641... 2008.1108.641... C:Program FilesCommon FilesMicrosoft Sh... 
(...)

Selecting Files or Folders Only

Because Get-ChildItem does not differentiate between files and folders, it may be important to limit the result of Get-ChildItem to only files or only folders. There are several ways to accomplish this. You can check the type of returned object, check the PowerShell PSIsContainer property, or examine the mode property:

# List directories only:
PS> Get-ChildItem | Where-Object { $_ -is [System.IO.DirectoryInfo] }
PS> Get-ChildItem | Where-Object { $_.PSIsContainer }
PS> Get-ChildItem | Where-Object { $_.Mode -like 'd*' }

# List files only:
PS> Get-ChildItem | Where-Object { $_ -is [System.IO.FileInfo] }
PS> Get-ChildItem | Where-Object { $_.PSIsContainer -eq $false}
PS> Get-ChildItem | Where-Object { $_.Mode -notlike 'd*' }

Where-Object can filter files according to other criteria as well. For example, use the following pipeline filter if you'd like to locate only files that were created after May 12, 2011:

PS> Get-ChildItem $env:windir | Where-Object { $_.CreationTime -gt [datetime]::Parse("May 12, 2011") }

You can use relative dates if all you want to see are files that have been changed in the last two weeks:

PS> Get-ChildItem $env:windir | Where-Object { $_.CreationTime -gt (Get-Date).AddDays(-14) }

Navigating the File System

Unless you changed your prompt (see Chapter 9), the current directory is part of your input prompt. You can find out the current location by calling Get-Location:

PS> Get-Location

Path
----
C:UsersTobias

If you want to navigate to another location in the file system, use Set-Location or the Cd alias:

# One directory higher (relative):
PS> Cd ..

# In the parent directory of the current drive (relative):
PS> Cd 

# In a specified directory (absolute):
PS> Cd c:windows

# Take directory name from environment variable (absolute):
PS> Cd $env:windir

# Take directory name from variable (absolute):
PS> Cd $home

Relative and Absolute Paths

Paths can either be relative or absolute. Relative path specifications depend on the current directory, so .test.txt always refers to the test.txt file in the current directory. Likewise, ..test.txt refers to the test.txt file in the parent directory.

Relative path specifications are useful, for example, when you want to use library scripts that are located in the same directory as your work script. Your work script will then be able to locate library scripts under relative paths—no matter what the directory is called. Absolute paths are always unique and are independent of your current directory.

Character Meaning Example Result
. Current directory ii . Opens the current directory in Windows Explorer
.. Parent directory Cd .. Changes to the parent directory
Root directory Cd Changes to the topmost directory of a drive
~ Home directory Cd ~ Changes to the directory that PowerShell initially creates automatically

Table 15.2: Important special characters used for relative path specifications

Converting Relative Paths into Absolute Paths

Whenever you use relative paths, PowerShell must convert these relative paths into absolute paths. That occurs automatically when you submit a relative path to a cmdlet. You can resolve relative paths manually, too, by using Resolve-Path.

PS> Resolve-Path .test.txt
Path
----
C:UsersTobias Weltnertest.txt

Be careful though: Resolve-Path only works for files that actually exist. If there is no file in your current directory that's called test.txt, Resolve-Path errors out.

Resolve-Path can also have more than one result if the path that you specify includes wildcard characters. The following call will retrieve the names of all ps1xml files in the PowerShell home directory:

PS> Resolve-Path $PsHome*.ps1xml
Path
----
C:WindowsSystem32WindowsPowerShellv1.0Certificate.format.ps1xml
C:WindowsSystem32WindowsPowerShellv1.0DotNetTypes.format.ps1xml
C:WindowsSystem32WindowsPowerShellv1.0FileSystem.format.ps1xml
C:WindowsSystem32WindowsPowerShellv1.0Help.format.ps1xml
C:WindowsSystem32WindowsPowerShellv1.0PowerShellCore.format.ps1xml
C:WindowsSystem32WindowsPowerShellv1.0PowerShellTrace.format.ps1xml
C:WindowsSystem32WindowsPowerShellv1.0Registry.format.ps1xml
C:WindowsSystem32WindowsPowerShellv1.0types.ps1xml

Pushing and Popping Directory Locations

The current directory can be “pushed” onto a “stack” by using Push-Location. Each Push-Location adds a new directory to the top of the stack. Use Pop-Location to get it back again.

So, to perform a task that forces you to temporarily leave your current directory, first type Push-Location to store your current location. Then, you can complete your task and when use Pop-Location to return to where you were before.

Cd $home will always take you back to your home directory. Also, both Push-Location and Pop-Location support the -Stack parameter. This enables you to create as many stacks as you want, such as one for each task. Push-Location -Stack job1 puts the current directory not on the standard stack, but on the stack called “job1”; you can use Pop-Location -Stack job1 to restore the initial directory from this stack.

Special Directories and System Paths

There are many standard folders in Windows, for example the Windows folder itself, your user profile, or your desktop. Since the exact location of these paths can vary depending on your installation setup, it is bad practice to hard-code these paths into your scripts – hardcoded system paths may run well on your machine and break on another.

That's why it is important to understand where you can find the exact location of these folders. Some are covered by the Windows environment variables, and others can be retrieved via .NET methods.

Special directory Description Access
Application data Application data locally stored on the machine $env:localappdata
User profile User directory $env:userprofile
Data used in common Directory for data used by all programs $env:commonprogramfiles
Public directory Common directory of all local users $env:public
Program directory Directory in which programs are installed $env:programfiles
Roaming Profiles Application data for roaming profiles $env:appdata
Temporary files (private) Directory for temporary files of the user $env:tmp
Temporary files Directory for temporary files $env:temp
Windows directory Directory in which Windows is installed $env:windir

Table 15.3: Important Windows directories that are stored in environment variables

Environment variables cover only the most basic system paths. If you'd like to put a file directly on a user’s Desktop, you'll need the path to the Desktop which is missing in the list of environment variables. The GetFolderPath() method of the System.Environment class of the .NET framework (Chapter 6) can help. The following code illustrates how you can put a link on the Desktop.

PS> [Environment]::GetFolderPath("Desktop")
C:UsersTobias WeltnerDesktop

# Put a link on the Desktop:
PS> $path = [Environment]::GetFolderPath("Desktop") + "EditorStart.lnk"
PS> $comobject = New-Object -ComObject WScript.Shell
PS> $link = $comobject.CreateShortcut($path)
PS> $link.targetpath = "notepad.exe"
PS> $link.IconLocation = "notepad.exe,0"
PS> $link.Save()

To get a list of system folders known by GetFolderPath(), use this code snippet:

PS> [System.Environment+SpecialFolder] | Get-Member -Static -MemberType Property
   TypeName: System.Environment+SpecialFolder

Name                  MemberType Definition
----                  ---------- ----------
ApplicationData       Property   static System.Environment+SpecialFolder ApplicationData {get;}
CommonApplicationData Property   static System.Environment+SpecialFolder CommonApplicationData ...
CommonProgramFiles    Property   static System.Environment+SpecialFolder CommonProgramFiles {get;}
Cookies               Property   static System.Environment+SpecialFolder Cookies {get;}
Desktop               Property   static System.Environment+SpecialFolder Desktop {get;}
DesktopDirectory      Property   static System.Environment+SpecialFolder DesktopDirectory {get;}
Favorites             Property   static System.Environment+SpecialFolder Favorites {get;}
History               Property   static System.Environment+SpecialFolder History {get;}
InternetCache         Property   static System.Environment+SpecialFolder InternetCache {get;}
LocalApplicationData  Property   static System.Environment+SpecialFolder LocalApplicationData {...
MyComputer            Property   static System.Environment+SpecialFolder MyComputer {get;}
MyDocuments           Property   static System.Environment+SpecialFolder MyDocuments {get;}
MyMusic               Property   static System.Environment+SpecialFolder MyMusic {get;}
MyPictures            Property   static System.Environment+SpecialFolder MyPictures {get;}
Personal              Property   static System.Environment+SpecialFolder Personal {get;}
ProgramFiles          Property   static System.Environment+SpecialFolder ProgramFiles {get;}
Programs              Property   static System.Environment+SpecialFolder Programs {get;}
Recent                Property   static System.Environment+SpecialFolder Recent {get;}
SendTo                Property   static System.Environment+SpecialFolder SendTo {get;}
StartMenu             Property   static System.Environment+SpecialFolder StartMenu {get;}
Startup               Property   static System.Environment+SpecialFolder Startup {get;}
System                Property   static System.Environment+SpecialFolder System {get;}
Templates             Property   static System.Environment+SpecialFolder Templates {get;}

And this would get you a list of all system folders covered plus their actual paths:

PS> [System.Environment+SpecialFolder] | Get-Member -Static -MemberType Property |
>>
ForEach-Object {"{0,-25}= {1}" -f $_.name, [Environment]::GetFolderPath($_.Name) }
>>
ApplicationData = C:UsersTobias WeltnerAppDataRoaming CommonApplicationData = C:ProgramData CommonProgramFiles = C:Program FilesCommon Files Cookies = C:UsersTobias WeltnerAppDataRoamingMicrosoftWindowsCookies Desktop = C:UsersTobias WeltnerDesktop DesktopDirectory = C:UsersTobias WeltnerDesktop Favorites = C:UsersTobias WeltnerFavorites History = C:UsersTobias WeltnerAppDataLocalMicrosoftWindowsHistory InternetCache = C:UsersTobias WeltnerAppDataLocalMicrosoftWindowsTemporary Internet Files LocalApplicationData = C:UsersTobias WeltnerAppDataLocal MyComputer = MyDocuments = C:UsersTobias WeltnerDocuments MyMusic = C:UsersTobias WeltnerMusic MyPictures = C:UsersTobias WeltnerPictures Personal = C:UsersTobias WeltnerDocuments ProgramFiles = C:Program Files Programs = C:UsersTobias WeltnerAppDataRoamingMicrosoftWindowsStart MenuPrograms Recent = C:UsersTobias WeltnerAppDataRoamingMicrosoftWindowsRecent SendTo = C:UsersTobias WeltnerAppDataRoamingMicrosoftWindowsSendTo StartMenu = C:UsersTobias WeltnerAppDataRoamingMicrosoftWindowsStart Menu Startup = C:UsersTobias WeltnerAppDataRoamingMicrosoftWindows Start MenuProgramsStartup System = C:Windowssystem32 Templates = C:UsersTobias WeltnerAppDataRoamingMicrosoftWindowsTemplates

You can use this to create a pretty useful function that maps drives to all important file locations. Here it is:

function Map-Profiles {
 [System.Environment+SpecialFolder] | Get-Member -Static -MemberType Property | 
  ForEach-Object {
    New-PSDrive -Name $_.Name -PSProvider FileSystem -Root ([Environment]::GetFolderPath($_.Name)) `
-Scope Global } } Map-Profiles

When you run this function, it adds a bunch of new drives. You can now easily take a look at your browser cookies – or even get rid of them:

PS> Get-ChildItem cookies:
PS> Get-ChildItem cookies: | del -WhatIf

You can check content of your desktop:

PS> Get-ChildItem desktop:

And if you'd like to see all the drives accessible to you, run this command:

PS> Get-PSDrive

Note that all custom drives are added only for your current PowerShell session. If you want to use them daily, make sure you add Map-Profiles and its call to your profile script:

PS> if ((Test-Path $profile) -eq $false) { New-Item $profile -ItemType File -Force }
PS> Notepad $profile

Constructing Paths

Path names are plain-text, so you can set them up any way you like. To put a file onto your desktop, you could add the path segments together using string operations:

PS> $path = [Environment]::GetFolderPath("Desktop") + "file.txt"
PS> $path
C:UsersTobias WeltnerDesktopfile.txt

A more robust way is using Join-Path because it keeps track of the backslashes:

PS> $path = Join-Path ([Environment]::GetFolderPath("Desktop")) "test.txt"
PS> $path
C:UsersTobias WeltnerDesktoptest.txt

Or, you can use .NET framework methods:

PS> $path = [System.IO.Path]::Combine([Environment]::GetFolderPath("Desktop"), "test.txt")
PS> $path
C:UsersTobias WeltnerDesktoptest.txt

The System.IO.Path class includes a number of additionally useful methods that you can use to put together paths or extract information from paths. Just prepend [System.IO.Path]:: to methods listed in Table 15.4, for example:

PS> [System.IO.Path]::ChangeExtension("test.txt", "ps1")
test.ps1

Method Description Example
ChangeExtension() Changes the file extension ChangeExtension("test.txt", "ps1")
Combine() Combines path strings; corresponds to Join-Path Combine("C:test", "test.txt")
GetDirectoryName() Returns the directory; corresponds to Split-Path -parent GetDirectoryName("c:testfile.txt")
GetExtension() Returns the file extension GetExtension("c:testfile.txt")
GetFileName() Returns the file name; corresponds to Split-Path -leaf GetFileName("c:testfile.txt")
GetFileNameWithoutExtension() Returns the file name without the file extension GetFileNameWithoutExtension("c:testfile.txt")
GetFullPath() Returns the absolute path GetFullPath(".test.txt")
GetInvalidFileNameChars() Lists all characters that are not allowed in a file name GetInvalidFileNameChars()
GetInvalidPathChars() Lists all characters that are not allowed in a path GetInvalidPathChars()
GetPathRoot() Gets the root directory; corresponds to Split-Path -qualifier GetPathRoot("c:testfile.txt")
GetRandomFileName() Returns a random file name GetRandomFileName()
GetTempFileName() Returns a temporary file name in the Temp directory GetTempFileName()
GetTempPath() Returns the path of the directory for temporary files GetTempPath()
HasExtension() True, if the path includes a file extension HasExtension("c:testfile.txt")
IsPathRooted() True, if the path is absolute; corresponds to Split-Path -isAbsolute IsPathRooted("c:testfile.txt")

Table 15.4: Methods for constructing paths

Working with Files and Directories

The cmdlets Get-ChildItem and Get-Item can get you file and directory items that already exist. In addition, you can create new files and directories, rename them, fill them with content, copy them, move them, and, of course, delete them.

Creating New Directories

The easiest way to create new directories is to use the Md function, which invokes the cmdlet New-Item internally and specifies as -ItemType parameter the Directory value:

# "md" is the predefined function and creates new directories:
PS> md Test1
    Directory: Microsoft.PowerShell.CoreFileSystem::C:usersTobias Weltner

Mode                LastWriteTime     Length Name
----                -------------     ------ ----
d----        12.10.2011     17:14            Test1

# "New-Item" can do that, too, but takes more effort:
PS> New-Item Test2 -ItemType Directory
    Directory: Microsoft.PowerShell.CoreFileSystem::C:usersTobias Weltner

Mode                LastWriteTime     Length Name
----                -------------     ------ ----
d----        12.10.2011     17:14            Test2

You can also create several sub-directories in one step as PowerShell automatically creates all the directories that don't exist yet in the specified path:

PS> md testsubdirectorysomethingelse

Three folders will be created with one line.

Creating New Files

You can use New-Item to also create new files. Use -Value if you want to specify text to put into the new file, or else you create an empty file:

PS> New-Item "new file.txt" -ItemType File
    Directory: Microsoft.PowerShell.CoreFileSystem::C:usersTobias Weltner

Mode                LastWriteTime     Length Name
----                -------------     ------ ----
-a---        10.12.2011     17:16          0 new file.txt

If you add the -Force parameter, creating new files with New-Item becomes even more interesting – and a bit dangerous, too. The -Force parameter will overwrite any existing file, but it will also make sure that the folder the file is to be created it exists. So, New-Item can create several folders plus a file if you use -Force.

Another way to create files is to use old-fashioned redirection using the ">" and ">>" operators, Set-Content or Out-File.

Get-ChildItem > info1.txt
.info1.txt
Get-ChildItem | Out-File info2.txt
.info2.txt
Get-ChildItem | Set-Content info3.txt
.info3.txt
Set-Content info4.txt (Get-Date)
.info4.txt

As it turns out, redirection and Out-File work very similar: when PowerShell converts pipeline results, file contents look just like they would if you output the information in the console. Set-Content works differently: it does not use PowerShell’s sophisticated ETS (Extended Type System) to convert objects into text. Instead, it converts objects into text by using their own private ToString() method – which provides much less information. That is because Set-Content is not designed to convert objects into text. Instead, this cmdlet is designed to write text to a file.

You can use all of these cmdlets to create text files. For example, ConvertTo-HTML produces HTML but does not write it to a file. By sending that information to Out-File, you can create HTML- or HTA-files and display them.

PS> Get-ChildItem | ConvertTo-HTML | Out-File report1.hta
PS> .report1.hta
PS> Get-ChildItem | ConvertTo-HTML | Set-Content report2.hta
PS> .report2.htm

If you want to control the "columns" (object properties) that are converted into HTML, simply use Select-Object (Chapter 5):

Get-ChildItem | Select-Object name, length, LastWriteTime | ConvertTo-HTML | Out-File report.htm
.report.htm

If you rather want to export the result as a comma-separated list, use Export-Csv cmdlet instead of ConvertTo-HTML | Out-File. Don't forget to use its -UseCulture parameter to automatically use the delimiter that is right for your culture.

To add content to an existing file, again you can use various methods. Either use the appending redirection operator ">>", or use Add-Content. You can also pipe results to Out-File and use its -Append parameter to make sure it does not overwrite existing content.

There is one thing you should keep in mind, though: do not mix these methods, stick to one. The reason is that they all use different default encodings, and when you mix encodings, the result may look very strange:

PS> Set-Content info.txt "First line"
PS> "Second line" >> info.txt
PS> Add-Content info.txt "Third line" 
PS> Get-Content info.txt
First Line
S e c o n d  L i n e

Third line

All three cmdlets support the -Encoding parameter that you can use to manually pick an encoding. In contrast, the old redirection operators have no way of specifying encoding which is why you should avoid using them.

Reading the Contents of Text Files

Use Get-Content to retrieve the contents of a text-based file:

PS> Get-Content $env:windirwindowsupdate.log

There is a shortcut that uses variable notation if you know the absolute path of the file:

PS> ${c:windowswindowsupdate.log}

However, this shortcut usually isn’t very practical because it doesn’t allow any variables inside curly brackets. You would have to hardcode the exact path to the file into your scripts.

Get-Content reads the contents of a file line by line and passes on every line of text through the pipeline. You can add Select-Object if you want to read only the first 10 lines of a very long file:

PS> Get-Content $env:windirwindowsupdate.log | Select-Object -First 10

You can also use -Wait with Get-Content to turn the cmdlet into a monitoring mode: once it read the entire file, it keeps monitoring it, and when new content is appended to the file, it is immediately processed and returned by Get-Content. This is somewhat similar to "tailing" a file in Unix.

Finally, you can use Select-String to filter information based on keywords and regular expressions. The next line gets only those lines from the windowsupdate.log file that contain the phrase " successfully installed ":

PS> Get-Content $env:windirwindowsupdate.log | Select-String "successfully installed"

Note that Select-String will change the object type to a so-called MatchInfo object. That's why when you forward the filtered information to a file, the result lines are cut into pieces:

PS> Get-Content $env:windirwindowsupdate.log -Encoding UTF8 | Select-String "successfully installed" |
>>
Out-File $env:tempreport.txt
>>
PS> Invoke-Item $env:tempreport.txt

To turn the results delivered by Select-String into real text, make sure you pick the property Line from the MatchInfo object which holds the text line that matched your keyword:

PS> Get-Content $env:windirwindowsupdate.log -Encoding UTF8 | Select-String "successfully installed" |
>>
Select-Object -ExpandProperty Line | Out-File $env:tempreport.txt
>>
PS> Invoke-Item $env:tempreport.txt

Processing Comma-Separated Lists

Use Import-Csv if you want to process information from comma-separated lists in PowerShell. For example, you could export an Excel spreadsheet as CSV-file and then import the data into PowerShell. When you use Get-Content to read a CSV-file, you'd see the plain text. A much better way is to use Import-CSV. It honors the delimiter and returns objects. Each column header turns into an object property.

To successfully import CSV files, make sure to use the parameter -UseCulture or -Delimiter if the list is not comma-separated. Depending on your culture, Excel may have picked a different delimiter than the comma, and -UseCulture automatically uses the delimiter that Excel used.

Moving and Copying Files and Directories

Move-Item and Copy-Item perform moving and copying operations. You may use wildcard characters with them. The following line copies all PowerShell scripts from your home directory to the Desktop:

PS> Copy-Item $home*.ps1 ([Environment]::GetFolderPath("Desktop"))

Use Get-Childitem to copy recursively. Let it find the PowerShell scripts for you, and then pass the result on to Copy-Item: Before you run this line you should be aware that there may be hundreds of scripts, and unless you want to completely clutter your desktop, you may want to first create a folder on your desktop and then copy the files into that folder.

PS> Get-ChildItem -Filter *.ps1 -Recurse |
>>
Copy-Item -Destination ([Environment]::GetFolderPath("Desktop"))}

Renaming Files and Directories

Use Rename-Item if you want to rename files or folders. Renaming files or folders can be dangerous, so do not rename system files or else Windows may stall.

PS> Set-Content $env:temptestfile.txt "Hello,this,is,an,enumeration"

# file opens in notepad:
PS> Invoke-Item $env:temptestfile.txt

# file opens in Excel now:
PS> Rename-Item $env:temptestfile.txt testfile.csv
PS> Invoke-Item $env:temptestfile.csv

Bulk Renames

Because Rename-Item can be used as a building block in the pipeline, it provides simple solutions to complex tasks. For example, if you wanted to remove the term “-temporary” from a folder and all its sub-directories, as well as all the included files, this instruction will suffice:

PS> Get-ChildItem | ForEach-Object { Rename-Item $_.Name $_.Name.Replace('-temporary', '') }

This line would now rename all files and folders, even if the term '"-temporary" you're looking for isn't even in the file name. So, to speed things up and avoid errors, use Where-Object to focus only on files that carry the keyword in its name:

PS> Get-ChildItem | Where-Object { $_.Name -like "*-temporary" } |
>>
ForEach-Object { Rename-Item $_.Name $_.Name.replace('-temporary', '') }

Rename-Item even accepts a script block, so you could use this code as well:

PS> Get-ChildItem | $_.Name -like '*-temporary' } | Rename-Item { $_.Name.replace('-temporary', '') }

When you look at the different code examples, note that ForEach-Object is needed only when a cmdlet cannot handle the input from the upstream cmdlet directly. In these situations, use ForEach-Object to manually feed the incoming information to the appropriate cmdlet parameter.

Most file system-related cmdlets are designed to work together. That's why Rename-Item knows how to interpret the output from Get-ChildItem. It is "Pipeline-aware" and does not need to be wrapped in ForEach-Object.

Deleting Files and Directories

Use Remove-Item or the Del alias to remove files and folders. If a file is write-protected, or if a folder contains data, you'll have to confirm the operation or use the -Force parameter.

# Create an example file:
PS> $file = New-Item testfile.txt -ItemType file

# There is no write protection:
PS> $file.isReadOnly
False

# Activate write protection:
PS> $file.isReadOnly = $true
PS> $file.isReadOnly
True

# Write-protected file may be deleted only by using the –Force parameter:
PS> del testfile.txt
Remove-Item : Cannot remove item C:UsersTobias Weltnertestfile.txt:
Not enough permission to perform operation. At line:1 char:4 + del <<<< testfile.txt PS> del testfile.txt -Force

Deleting Directory Contents

Use wildcard characters if you want to delete a folder content but not the folder itself. This line, for example, will empty the Recent folder that keeps track of files you opened lately and – over time – can contain hundreds of lnk-files.

Because deleting files and folders is irreversible, be careful. You can always simulate the operation by using -WhatIf to see what happens – which is something you should do often when you work with wildcards because they may affect many more files and folders than you initially thought.

PS> $recents =  [Environment]::GetFolderPath('Recent')
PS> Remove-Item $recents*.* -WhatIf

You can as well put this in one line, too:

PS> Get-Childitem ([Environment]::GetFolderPath('Recent')) | Remove-Item -WhatIf

This however would also delete subfolders contained in your Recent folder because Get-ChildItem lists both files and folders.

If you are convinced that your command is correct, and that it will delete the correct files, repeat the statement without -WhatIf. Or, you could use -Confirm instead to manually approve or deny each delete operation.

Deleting Directories Plus Content

PowerShell requests confirmation whenever you attempt to delete a folder that is not empty. Only the deletion of empty folders does not require confirmation:

# Create a test directory:
md testdirectory
    Directory: Microsoft.PowerShell.CoreFileSystem::C:UsersTobias WeltnerSourcesdocs

Mode                LastWriteTime     Length Name
----                -------------     ------ ----
d----        13.10.2011     13:31            testdirectory

# Create a file in the directory:
PS> Set-Content .testdirectorytestfile.txt "Hello"

# Delete directory:
PS> del testdirectory
Confirm
The item at "C:UsersTobias WeltnerSourcesdocstestdirectory" has children and the Recurse
parameter was not specified. if you continue, all children will be removed with the item.
Are you sure you want to continue? [Y] Yes [A] Yes to All [N] No [K] No to All [H] Suspend [?] Help (default is "Y"):

To delete folders without confirmation, add the parameter -Recurse:

PS> Remove-Item testdirectory -Recurse

Alias Description Cmdlet
ac Adds the contents of a file Add-Content
cls, clear Clears the console window Clear-Host
cli Clears file of its contents, but not the file itself Clear-Item
copy, cp, cpi Copies file or directory Copy-Item
Dir, ls, gci Lists directory contents Get-Childitem
type, cat, gc Reads contents of text-based file Get-Content
gi Accesses specific file or directory Get-Item
gp Reads property of a file or directory Get-ItemProperty
ii Invokes file or directory using allocated Windows program Invoke-Item
Joins two parts of a path into one path, for example, a drive and a file name Join-Path
mi, mv, move Moves files and directories Move-Item
ni Creates new file or new directory New-Item
ri, rm, rmdir, del, erase, rd Deletes empty directory or file Remove-Item
rni, ren Renames file or directory Rename-Item
rvpa Resolves relative path or path including wildcard characters Resolve-Path
sp Sets property of file or directory Set-ItemProperty
Cd, chdir, sl Changes to specified directory Set-Location
Extracts a specific part of a path like the parent path, drive, or file name Split-Path
Returns True if the specified path exists Test-Path

Table 15.1: Overview of the most important file system commands