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
- Accessing Files and Directories
- Navigating the File System
- Working with Files and Directories
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 |
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 |
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”) |
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 |