One of the most common tasks when administering a system is working with its files and directories. This is true when you administer the computer at the command line, and it is true when you write scripts to administer it automatically.
Fortunately, PowerShell makes scripting files and directories as easy as working at the command line—a point that many seasoned programmers and scripters often miss. A perfect example of this comes when you wrestle with limited disk space and need to find the files taking up the most space.
A typical programmer might approach this task by writing functions to scan a specific directory of a system. For each file, they check whether the file is big enough to care about. If so, they add it to a list. For each directory in the original directory, the programmer repeats this process (until there are no more directories to process).
As the saying goes, though, "you can write C in any programming language." The habits and preconceptions you bring to a language often directly influence how open you are to advances in that language.
Being an administrative shell, PowerShell directly supports tasks such as visiting all the files in a subdirectory or moving a file from one directory to another. That complicated programmer-oriented script turns into a one-liner:
Get-ChildItem -Recurse | Sort-Object -Descending Length | Select -First 10
Before diving into your favorite programmer's toolkit, check to see what PowerShell supports in that area. In many cases, it can handle it without requiring your programmer's bag of tricks.
You want to determine the current location from a script or command.
To retrieve the current location, use the
Get-Location cmdlet. The Get-Location cmdlet provides the Drive and
Path as two common properties:
You can also get this with the $pwd special variable. I think that's much simpler than the method used.... it's also much simpler than the method in the discussion.
$currentLocation = (Get-Location).Path
As a short-form for (Get-Location).Path, use the $pwd automatic variable.
The Get-Location cmdlet returns information about
the current location. From the information it returns, you can access
the current drive, provider, and path.
This current location affects PowerShell commands and programs that you launch from PowerShell. It does not apply when you interact with the .NET Framework, however. If you need to call a .NET method that interacts with the filesystem, always be sure to provide fully qualified paths:
[System.IO.File]::ReadAllText("c:\temp\file.txt")If you are sure that the file exists, the
Resolve-Path cmdlet lets you
translate a relative path to an absolute path:
$filePath = (Resolve-Path file.txt).Path
If the file does not exist, use the Join-Path cmdlet in combination with the
Get-Location cmdlet to specify the
file:
$filePath = Join-Path (Get-Location) file.txt
Another alternative that combines the
functionality of both approaches is a bit more advanced but also lets
you specify relative locations. It comes from methods in the PowerShell
$executionContext variable, which
provides functionality normally used by cmdlet and provider
authors:
$executionContext.SessionState.Path.`
GetUnresolvedProviderPathFromPSPath("..\file.txt")For more information about the Get-Location cmdlet, type Get-Help Get-Location.
To retrieve the list of files in a directory,
use the Get-ChildItem cmdlet. To get
a specific item, use the Get-Item
cmdlet:
To list all items in the current
directory, use the Get-ChildItem
cmdlet:
Get-ChildItem
To list all items that match a wildcard,
supply a wildcard to the Get-ChildItem cmdlet:
Get-ChildItem *.txtTo list all files that match a wildcard in
the current directory (and all its children), use the –Include and –Recurse parameters of the
Get-ChildItem cmdlet:
Get-ChildItem -Include *.txt -RecurseTo list all directories in the current
directory, use the Where-Object
cmdlet to test the PsIsContainer
property:
Get-ChildItem | Where { $_.PsIsContainer }To get information about a specific item,
use the Get-Item cmdlet:
Get-Item test.txtAlthough most commonly used on the filesystem,
the Get-ChildItem and Get-Item cmdlets in fact work against any
items in any of the PowerShell drives. In addition to A: through Z: (the standard file system
drives), they also work on Alias:,
Cert:, Env:, Function:, HKLM:, HKCU:, and
Variable:.
One example lists files that match a wildcard in a directory and all its children. That example works on any PowerShell provider. However, PowerShell can retrieve your results more quickly if you use a provider-specific filter, as described in the section called “Find Files That Match a Pattern”.
The solution demonstrates some simple wildcard
scenarios that the Get-ChildItem
cmdlet supports, but PowerShell in fact enables several more advanced
scenarios. For more information about these scenarios, see the section called “Find Files That Match a Pattern”.
In the filesystem, these cmdlets return
objects from the .NET Framework that represent files and
directories—instances of the System.IO.FileInfo and System.IO. DirectoryInfo classes,
respectively. Each provides a great deal of useful information:
attributes, modification times, full name, and more. Although the
default directory listing exposes a lot of information, PowerShell
provides even more. For more information about working with classes from
the .NET Framework, see the section called “Work with .NET Objects”.
“System.IO. DirectoryInfo” → “System.IO.DirectoryInfo” (superfluous space)
You want to find all files last modified before a certain date.
To find all files modified before a certain
date, use the Get-ChildItem cmdlet to
list the files in a directory, and then use the Where-Object cmdlet to compare the LastWriteTime property to the date you are
interested in. For example, to find all files created before this
year:
Get-ChildItem -Recurse | Where-Object { $_.LastWriteTime -lt "01/01/2007" }A common reason to compare files against a certain date is to find recently modified (or not recently modified) files. This looks almost the same as the example given by the solution, but your script can't know the exact date to compare against.
In this case, the AddDays() method in the .NET Framework's
DateTime class gives you a way to
perform some simple calendar arithmetic. If you have a DateTime object, you can add or subtract time
from it to represent a different date altogether. For example, to find
all files modified in the last 30 days:
$compareDate = (Get-Date).AddDays(-30)
Get-ChildItem -Recurse | Where-Object { $_.LastWriteTime -ge $compareDate }Similarly, to find all files more than 30 days old:
$compareDate = (Get-Date).AddDays(-30)
Get-ChildItem -Recurse | Where-Object { $_.LastWriteTime -lt $compareDate }In this example, the Get-Date cmdlet returns an object that
represents the current date and time. You call the AddDays() method to subtract 30 days from that
time, which stores the date representing "30 days ago" in the $compareDate variable. Next, you compare that
date against the LastWriteTime
property of each file that the Get-ChildItem cmdlet returns.
The DateTime class is the administrator's
favorite calendar!
PS > [DateTime]::IsLeapYear(2008) True PS > $daysTillChristmas = [DateTime] "December 25" - (Get-Date) PS > $daysTillChristmas.Days 327
For more information about the Get-ChildItem cmdlet, type Get-Help Get-ChildItem. For more information
about the Where-Object cmdlet, see
the section called “Filter Items in a List or Command Output”.
To clear the content from a file, use the
Clear-Content cmdlet, as shown by
Example 20.1, “Clearing content from a file”.
Example 20.1. Clearing content from a file
PS > Get-Content test.txt
Hello World
PS > Clear-Content test.txt
PS > Get-Content test.txt
PS > Get-Item test.txt
Directory: C:\temp
Mode LastWriteTime Length Name
---- ------------- -----------
-a--- 4/23/2007 8:05 PM 0 test.txtThe (aptly named) Clear-Content cmdlet clears the content from
an item. Although the solution demonstrates this only for files in the
filesystem, it in fact applies to any PowerShell providers that support
the concepts of "content." Examples of other drives that support these
content concepts are the Function:,
Alias:, and Variable:.
For information on how to remove an item entirely, see the section called “Remove a File or Directory”.
For more information about the Remove-Item or Clear-Content cmdlets, type Get-Help Remove-Item or Get-Help Clear-Content.
You want to update the ReadOnly, Hidden, or System attributes of a file.
Most of the time, you will want to use the familiar attrib.exe program to change the attributes of a file:
I don't understand the author's preference to use attrib. I'm reading a powershell book, and would much rather know a powershell or .net method than a commandline utility that may or may not be in future versions of the os. Not to say that the .net or powershell methods would be in future versions, but this rubs me the wrong way for some reason.
Oh, it rubs us the wrong way to :) There are several .NET alternatives listed below, but they aren't nearly as clear as Attrib. However, always remember: PowerShell is an extension to what you know, not a replacement.
attrib +rtest.txtattrib -stest.txt
To set only the ReadOnly attribute, you can optionally set the
IsReadOnly property on the
file:
$file = Get-Item test.txt
$file.IsReadOnly = $trueTo apply a specific set of attributes, use the
Attributes property on the
file:
$file = Get-Itemtest.txt$file.Attributes = "ReadOnly,NotContentIndexed"
Directory listings show the attributes on a
file, but you can also access the Mode or Attributes property directly:
PS > $file.Attributes = "ReadOnly","System","NotContentIndexed" PS > $file.Mode --r-s PS > $file.Attributes ReadOnly, System, NotContentIndexed
When the Get-Item or Get-ChildItem cmdlets retrieve a file, the
resulting output has an Attributes
property. This property doesn't offer much in addition to the regular
attrib.exe program, although it does make it easier
to set the attributes to a specific state.
Be aware that setting the Hidden attribute on a file removes it from
most default views. If you want to retrieve it after hiding it, most
commands require a –Force
parameter. Similarly, setting the ReadOnly attribute on a file causes most
write operations on that file to fail unless you call that command
with the –Force parameter.
If you want to add an attribute to a file
using the Attributes property (rather
than attrib.exe for some reason), this is how you
would do that:
As mentioned earlier... I don't see the reason to use attrib.exe over the powershell/.net methods.
attrib has a more concise syntax, for a start. But yes, it feels awkward to use it from PowerShell.
For read-only one can use Set-ItemProperty:
Set-ItemProperty x.txt IsReadOnly $true
but that's also not too nice and won't work for other attributes.
$file = Get-Item test.txt
$readOnly = [IO.FileAttributes] "ReadOnly"
$file.Attributes = $file.Attributes -bor $readOnlyFor more information about working with classes from the .NET Framework, see the section called “Work with .NET Objects”.
Use the Get-ChildItem cmdlet for both simple and
advanced wildcard support:
To find all items in the current directory
that match a PowerShell wildcard, supply that wildcard to the
Get-ChildItem cmdlet:
Get-ChildItem *.txtTo find all items in the current directory
that match a provider-specific filter, supply
that filter to the -Filter
parameter:
Get-ChildItem -Filter *~2*To find all items in the current directory
that do not match a PowerShell wildcard, supply that wildcard to the
-Exclude parameter:
Get-ChildItem -Exclude *.txtTo find all items in subdirectories that
match a PowerShell wildcard, use the –Include and –Recurse parameters:
Get-ChildItem -Include *.txt -RecurseTo find all items in subdirectories that
match a provider-specific filter, use the
–Filter and –Recurse parameters:
Get-ChildItem -Filter *.txt -RecurseTo find all items in subdirectories that
do not match a PowerShell wildcard, use the –Exclude and –Recurse parameters:
Get-ChildItem -Exclude *.txt -Recurse
Use the Where-Object cmdlet for advanced regular expression support:
“Where-Object” should be formatted as code.
To find all items with a filename that
matches a regular expression, use the Where-Object cmdlet to compare the
Name property to the regular
expression:
Get-ChildItem | Where-Object { $_.Name -match '^KB[0-9]+\.log$' }To find all items with a directory name
that matches a regular expression, use the Where-Object cmdlet to compare the
DirectoryName property to the
regular expression:
Get-ChildItem -Recurse | Where-Object { $_.DirectoryName -match 'Release' }To find all items with a directory name or
filename that matches a regular expression, use the Where-Object cmdlet to compare the
FullName property to the regular
expression:
Get-ChildItem -Recurse | Where-Object { $_.FullName -match 'temp' }The Get-ChildItem cmdlet supports wildcarding
through three parameters:
The -Path parameter is the first (and
default) parameter. While you can enter simple paths such as
., C:\ or D:\Documents, you can also supply paths
that include wildcards—such as *, *.txt,
[a-z]???.log, or even C:\win*\*.N[a-f]? F*\v2*\csc.exe.
The –Include and –Exclude parameters act as a filter on
wildcarding that happens on the –Path parameter. If you specify the
–Recurse parameter, the
–Include and –Exclude wildcards apply to all items
returned.
The most common mistake with the
–Include parameter comes when
you use it against a path with no wildcards. For example, this
doesn't seem to produce the expected results:
Get-ChildItem $env:WINDIR -Include *.log
That command produces no results, as you have not supplied an item wildcard to the path. Instead, the correct command is:
Get-ChildItem $env:WINDIR\* -Include *.log
The –Filter parameter lets you filter
results based on the provider-specific
filtering language of the provider from which you retrieve items.
Since PowerShell's wildcarding support closely mimics filesystem
wildcards, and most people use the –Filter parameter only on the
filesystem, this seems like a redundant (and equivalent)
parameter. A SQL provider, however, would use SQL syntax in its
–Filter parameter. Likewise, an
Active Directory provider would use LDAP paths in its –Filter parameter.
Although it may not be obvious, the filesystem
provider's filtering language is not exactly the same as the PowerShell
wildcard syntax. For example, the –Filter parameter matches against the short
filenames, too:
PS > Get-ChildItem | Select-Object Name Name ---- A Long File Name With Spaces Also.txt A Long File Name With Spaces.txt PS > Get-ChildItem *1* | Select-Object Name PS > Get-ChildItem -Filter *1* | Select-Object Name Name ---- A Long File Name With Spaces.txt
On the other hand, PowerShell's wildcard
syntax supports far more than the filesystem's native filtering
language. For more information about the PowerShell's wildcard syntax,
type Get-Help
About_WildCard.
When you want to perform filtering even more
advanced than what PowerShell's wildcarding syntax offers, the Where-Object cmdlet provides infinite
possibilities. For example, to exclude certain directories from a
search:
Get-ChildItem -Rec | Where-Object { $_.DirectoryName -notmatch "Debug" }or, to list all directories:
Get-ChildItem | Where-Object { $_.PsIsContainer }Since the syntax of the Where-Object cmdlet can sometimes be
burdensome for simple queries, the Compare-Property script provided in the section called “Program: Simplify Most Where-Object Filters” provides an
attractive alternative:
Get-ChildItem -Rec | Compare-Property DirectoryName notmatch Debug
For a filter that is difficult (or impossible)
to specify programmatically, the Select-FilteredObject script provided by the section called “Program: Interactively Filter Lists of Objects” lets you
interactively filter the output.
Because of PowerShell's pipeline model, an
advanced file set generated by Get-ChildItem automatically turns into an
advanced file set for other cmdlets to operate on:
PS > Get-ChildItem -Rec | Where-Object { $_.Length -gt 20mb } |
>> Sort-Object -Descending Length | Select-FilteredObject |
>> Remove-Item -WhatIf
>>
What if: Performing operation "Remove File" on Target "C:\temp\backup092300
.zip".
What if: Performing operation "Remove File" on Target "C:\temp\sp-tricking_
iT2.zip".
What if: Performing operation "Remove File" on Target "C:\temp\slime.mov".
What if: Performing operation "Remove File" on Target "C:\temp\hello-world.
mov".For more information about the Get-ChildItem cmdlet, type Get-Help Get-ChildItem.
For more information about the Where-Object cmdlet, type Get-Help Where-Object.
You want to use a cmdlet that supports wildcarding but provide a filename that includes wildcard characters.
To prevent PowerShell from treating those
characters as wildcard characters, use the cmdlet's –LiteralPath (or similarly named) parameter if
it defines one:
Get-ChildItem -LiteralPath '[My File].txt'
One consequence of PowerShell's advanced wildcard support is that the square brackets used to specify character ranges sometimes conflict with actual filenames. Consider the following example:
PS > Get-ChildItem | Select-Object Name Name ---- [My File].txt PS > Get-ChildItem '[My File].txt' | Select-Object Name PS > Get-ChildItem -LiteralPath '[My File].txt' | Select-Object Name Name ---- [My File].txt
The first command clearly demonstrates that we
have a file called [My File].txt. When we try to
retrieve it (passing its name to the Get-ChildItem cmdlet), we see no results.
Since square brackets are wildcard characters in PowerShell (like
* and ?), the text we provided turns into a search
expression rather than a filename.
The –LiteralPath parameter (or a similarly named
parameter in other cmdlets) tells PowerShell that the filename is named
exactly—not a wildcard search term.
In addition to wildcard matching, filenames may sometimes run afoul of another topic—PowerShell escape sequences. For example, the back-tick character (`) in PowerShell means the start of an escape sequence, such as `t (tab), `n (newline), or `a (alarm). To prevent PowerShell from interpreting a back-tick as an escape sequence, surround that string in single quotes instead of double quotes.
The grave accent (back-tick) as well as the escapes for tab, newline and alarm should be typeset as code.
For more information about the Get-ChildItem cmdlet, type Get-Help Get-ChildItem.
For more information about PowerShell's
special characters, type Get-Help About_
Special_Characters.
When disk space starts running low, you'll naturally want to find out where to focus your cleanup efforts. Sometimes, you may tackle this by looking for large directories (including the directories in them), but other times, you may solve this by looking for directories that are large simply from the files they contain.
To review the disk usage statistics for an
entire drive, use the Get-PSDrive cmdlet.
Example 20.2, “Get-DiskUsage.ps1” collects
both types of data. It also demonstrates an effective use of
calculated properties. Like the Add-Member cmdlet, calculated properties let
you add properties to output objects by specifying the expression that
generates their data.
For more information about the calculated
properties and the Add-Member cmdlet,
see the section called “Add Custom Methods and Properties to Objects”.
Example 20.2. Get-DiskUsage.ps1
##############################################################################
param(
[switch] $includeSubdirectories
)
if($includeSubdirectories)
{
Get-ChildItem | Where-Object { $_.PsIsContainer } |
Select-Object Name,
@{ Name="Size";
Expression={ ($_ | Get-ChildItem -Recurse |
Measure-Object -Sum Length).Sum + 0 } }
}
else
{
Get-ChildItem -Recurse | Where-Object { $_.PsIsContainer } |
Select-Object FullName,
@{ Name="Size";
Expression={ ($_ | Get-ChildItem |
Measure-Object -Sum Length).Sum + 0 } }
}
For more information about running scripts, see the section called “Run Programs, Scripts, and Existing Tools”.
To monitor the end of a file for new content,
use the –Wait parameter of the
Get-Content cmdlet.
Get-Content log.txt -Wait
The –Wait
parameter on the Get-Content cmdlet
acts much like the traditional Unix tail command with the –follow parameter. If you provide the –Wait parameter, the Get-Content cmdlet reads the content of the
file but doesn't exit. When a program appends new content to the end of
the file, the Get-Content cmdlet
returns that content and continues to wait.
Wouldn't it be --follow for tail? At least that's the long option for the tail implementation in GNU coreutils. Non-GNU implementations usually don't have to adhere to GNU's -- syntax for long options and instead don't provide one, such as FreeBSD's tail which only accepts the short option -f.
Unlike the Unix tail command, the Get-Content cmdlet does not support a
feature to let you start reading from the end of a file. If you need
to monitor the end of an extremely large file, a specialized file
monitoring utility is a valid option.
For more information about the Get-Content cmdlet, type Get-Help Get-Content. For more information
about the –Wait parameter, type
Get-Help FileSystem.
–Wait uses an en dash (U+2013) instead of U+002D Hyphen-Minus. This appears to be pretty common in this section at least. Maybe happened a few times elsewhere too; it's pretty subtle to spot.
Use the Get-Item cmdlet to
retrieve the file, and then access the VersionInfo
property to retrieve its version information:
PS > $file = Get-Item $pshome\powershell.exe PS > $file.VersionInfo ProductVersion FileVersion FileName -------------- ----------- -------- 6.0.6002.18139 6.0.6002.1813... C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe
One common task in system administration is
identifying file and version information of installed software.
PowerShell makes this simple through the VersionInfo
property that it automatically attaches to files that you retrieve
through the Get-Item cmdlet. To generate a report for
a directory, simply pass the output of Get-ChildItem
to the Select-Object cmdlet, and use the
-ExpandProperty parameter to expand the
VersionInfo property.
PS > Get-ChildItem $env:WINDIR | Select -Expand VersionInfo -ErrorAction SilentlyContinue
ProductVersion FileVersion FileName
-------------- ----------- --------
C:\Windows\autologon.log
6.0.6000.16386 6.0.6000.1638... C:\Windows\bfsvc.exe
C:\Windows\bootstat.dat
C:\Windows\DtcInstall.log
6.0.6000.16386 6.0.6000.1638... C:\Windows\explorer.exe
6.0.6000.16386 6.0.6000.1638... C:\Windows\fveupdate.exe
6.0.6000.16386 6.0.6000.1638... C:\Windows\HelpPane.exe
6.0.6000.16386 6.0.6000.1638... C:\Windows\hh.exe
(...)For more information about the Get-ChildItem cmdlet, see the section called “Get the Files in a Directory”.
File hashes provide a useful way to check for damage or modification to a file. A digital hash acts like the fingerprint of a file and detects even minor modifications. If the content of a file changes, then so does its hash. Many online download services provide the hash of a file on that file's download page so that you can determine whether the transfer somehow corrupts the file (see Figure 20.1, “File hashes as a verification mechanism”).
Figure 20.1. File hashes as a verification mechanism

There are three common ways to generate the hash of a file: MD5, SHA1, SHA256. The two most common are MD5, followed by SHA1. While popular, these hash types can be trusted to detect only accidental file modification. They can be fooled if somebody wants to tamper with the file without changing its hash. The SHA256 algorithm can be used to protect against even intentional file tampering.
It might be fun to read this again in a decade or two as cryptographic hash implementations tend not to survive that long and the statement about SHA256 may be obsolete until then :-)
No suggestion to change anything, I think the paragraph says enough while side-stepping the whole issue of explaining the crypto stuff and why MD5 is broken, so that's ok. It was just an observation :-)
Example 20.3, “Get-FileHash.ps1” lets you determine the hash of a file (or of multiple files if provided by the pipeline).
Example 20.3. Get-FileHash.ps1
##############################################################################
param(
$path,
$hashAlgorithm = "MD5"
)
if($hashAlgorithm -eq "MD5")
{
$hasher = [System.Security.Cryptography.MD5]::Create()
}
elseif($hashAlgorithm -eq "SHA1")
{
$hasher = [System.Security.Cryptography.SHA1]::Create()
}
elseif($hashAlgorithm -eq "SHA256")
{
$hasher = [System.Security.Cryptography.SHA256]::Create()
}
else
{
$errorMessage = "Hash algorithm $hashAlgorithm is not valid. Valid " +
"algorithms are MD5, SHA1, and SHA256."
Write-Error $errorMessage
return
}
$files = @()
if($path)
{
$files += $path
}
else
{
$files += @($input | Foreach-Object { $_.FullName })
}
foreach($file in $files)
{
$filename = (Resolve-Path $file -ErrorAction SilentlyContinue).Path
if((-not $filename) -or (-not (Test-Path $filename -Type Leaf)))
{
continue
}
$inputStream = New-Object IO.StreamReader $filename
$hashBytes = $hasher.ComputeHash($inputStream.BaseStream)
$inputStream.Close()
$builder = New-Object System.Text.StringBuilder
$hashBytes | Foreach-Object { [void] $builder.Append($_.ToString("X2")) }
$output = New-Object PsObject -Property @{
Path = ([IO.Path]::GetFileName($file));
HashAlgorithm = $hashAlgorithm;
HashValue = $builder.ToString()
}
$output
}For more information about running scripts, see the section called “Run Programs, Scripts, and Existing Tools”.
You want to create a directory, or file folder.
I think you could specify that: new-item c:folder -type directory is another way that accomplish the same.
To create a directory, use the md or mkdir
function:
PS > md NewDirectory
Directory: C:\temp
Mode LastWriteTime Length Name
---- ------------- -----------
d---- 4/29/2007 7:31 PM NewDirectoryThe md and
mkdir functions are simple wrappers
around the more sophisticated New-Item cmdlet. As you might guess, the
New-Item cmdlet creates an item at
the location you provide. To create a directory using the
New-Item cmdlet directly, supply
Directory to the -Type
parameter.
New-Item -Path C:\Temp\NewDirectory -Type Directory
The
New-Item cmdlet doesn't work only
against the filesystem, however. Any providers that support the concept
of items automatically support this cmdlet as well.
For more information about the New-Item cmdlet, type Get-Help New-Item.
To remove a file or directory, use the
Remove-Item cmdlet:
PS > Test-PathNewDirectoryTrue PS > Remove-ItemNewDirectoryPS > Test-PathNewDirectoryFalse
The Remove-Item cmdlet removes an item from the
location you provide. The RemoveItem
cmdlet doesn't work only against the filesystem, however. Any providers
that support the concept of items automatically support this cmdlet as
well.
The Remove-Item cmdlet lets you specify multiple
files through its Path, Include,
Exclude, and Filter
parameters. For information on how to use these parameters
effectively, see the section called “Find Files That Match a Pattern”.
If the item is a container (for example, a
directory), PowerShell warns you that your action will also remove
anything inside that container. You can provide the –Recurse flag if you want to prevent this
message.
For more information about the Remove-Item cmdlet, type Get-Help Remove-Item.
To rename an item in a provider, use the
Rename-Item cmdlet:
PS > Rename-Item example.txt example2.txt
The Rename-Item cmdlet changes the name of an
item. While that may seem like pointing out the obvious, a common
mistake is:
PS > Rename-Item c:\temp\example.txt c:\temp2\example2.txt Rename-Item : Cannot rename because the target specified is not a path. At line:1 char:12 + Rename-Item <<<< c:\temp\example.txt c:\temp2\example2.txt
In this situation, PowerShell provides a (not very helpful) error message because we specified a path for the new item, rather than just its name.
Some shells let you rename multiple files at the same time. In those shells, the command looks like this:
ren *.gif *.jpg
PowerShell does not support this syntax, but
provides even more power through its –replace operator. As a simple example, we can
emulate the preceding command:
Get-ChildItem *.gif | Rename-Item -NewName { $_.Name -replace '.gif$','.jpg' }This syntax provides an immense amount of power. Consider removing underscores from filenames and replacing them with spaces:
Get-ChildItem *_* | Rename-Item -NewName { $_.Name -replace '_',' ' }or restructuring files in a directory with the
naming convention of Report_Project_
Quarter.txt:
PS > Get-ChildItem | Select Name Name ---- Report_Project1_Q3.txt Report_Project1_Q4.txt Report_Project2_Q1.txt
You might want to change that to
Quarter_Project.txt with an advanced replacement
pattern:
PS > Get-ChildItem |
>> Rename-Item -NewName { $_.Name -replace '.*_(.*)_(.*)\.txt','$2_$1.txt' }
>>
PS > Get-ChildItem | Select Name
Name
----
Q1_Project2.txt
Q3_Project1.txt
Q4_Project1.txtFor more information about the –replace operator, see the section called “Replace Text in a String”.
Like the other *–Item cmdlets, the Rename-Item doesn't work only against the
filesystem. Any providers that support the concept of items
automatically support this cmdlet as well. For more information about
the Rename-Item cmdlet, type
Get-Help Rename-Item.
To move a file or directory, use the Move-Item cmdlet:
PS > Move-Item example.txt c:\temp\example2.txt
The Move-Item cmdlet moves an item from one
location to another. Like the other *–Item cmdlets, the Move-Item doesn't work only against the
filesystem. Any providers that support the concept of items
automatically support this cmdlet as well.
The Move-Item cmdlet lets you specify multiple
files through its Path, Include, Exclude, and Filter parameters. For information on how to
use these parameters effectively, see the section called “Find Files That Match a Pattern”.
Although the Move-Item cmdlet works in every provider, you
cannot move items between providers. For more information about the
Move-Item cmdlet, type Get-Help Move-Item.
Once in a while, you'll run into a file that's been locked by the operating system, and you want to move it or delete it.
This is a common problem run into by patches, installers, and hotfixes, so Windows has a special mechanism that lets it move files before any process has the chance to lock it. If a file is locked that an installer needs to change, it uses this special mechanism to complete its setup tasks. Windows can only do this during a reboot, which is why you sometimes receive warnings from installers about locked files requiring a restart.
The underlying mechanism that enables this is
the MoveFileEx Windows API. Calling this API with the
MOVEFILE_DELAY_UNTIL_REBOOT flag tells Windows to
move (or delete) your file at the next boot. If you specify a source and
destination path, Windows moves the file. If you specify
$null as a destination path, Windows deletes the
file.
Example 20.4, “Move-LockedFile.ps1” uses the
Add-Type cmdlet to expose this functionality through
PowerShell. While it exposes only the functionality to move locked
files, you can easily rename it and modify it to delete locked
files.
Example 20.4. Move-LockedFile.ps1
##############################################################################
<#
.SYNOPSIS
Registers a locked file to be moved at the next system restart.
.EXAMPLE
PS >Move-LockedFile c:\temp\locked.txt c:\temp\locked.txt.bak
#>
param(
$path,
$destination)
$path = (Resolve-Path $path).Path
$destination = $executionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath($destination)
$MOVEFILE_DELAY_UNTIL_REBOOT = 0x00000004
$memberDefinition = @'
[DllImport("kernel32.dll", SetLastError=true, CharSet=CharSet.Auto)]
public static extern bool MoveFileEx(
string lpExistingFileName, string lpNewFileName, int dwFlags);
'@
$type = Add-Type -Name MoveFileUtils `
-MemberDefinition $memberDefinition -PassThru
$type::MoveFileEx($path, $destination, $MOVEFILE_DELAY_UNTIL_REBOOT) For more information about interacting with the Windows API, see the section called “Access Windows API Functions”. For more information about running scripts, see the section called “Run Programs, Scripts, and Existing Tools”.
To retrieve the ACL of a file, use the
Get-Acl cmdlet:
PS > Get-Acl example.txt
Directory: C:\temp
Path Owner Access
---- ----- ------
example.txt LEE-DESK\Lee BUILTIN\Administrator...The Get-Acl
cmdlet retrieves the security descriptor of an item. This cmdlet doesn't
work only against the filesystem, however. Any provider (for example,
the Registry provider) that supports the concept of security descriptors
also supports the Get-Acl
cmdlet.
The Get-Acl
cmdlet returns an object that represents the security descriptor of the
item and is specific to the provider that contains the item. In the
filesystem, this returns a .NET System.Security.AccessControl.FileSecurity
object that you can explore for further information. For example, Example 20.5, “Get-AclMisconfiguration.ps1” searches a directory for
possible ACL misconfigurations by ensuring that each file contains an
Administrators, Full Control ACL.
Example 20.5. Get-AclMisconfiguration.ps1
##############################################################################
foreach($file in Get-ChildItem)
{
$acl = Get-Acl $file
if(-not $acl)
{
continue
}
$foundAdministratorAcl = $false
foreach($accessRule in $acl.Access)
{
if(($accessRule.IdentityReference -like "*Administrator*") -and
($accessRule.FileSystemRights -eq "FullControl"))
{
$foundAdministratorAcl = $true
}
}
if(-not $foundAdministratorAcl)
{
"Found possible ACL Misconfiguration: $file"
}
}
For more information about the Get-Acl command, type Get-Help Get-Acl. For more information about
working with classes from the .NET Framework, see the section called “Work with .NET Objects”. For more information about running
scripts, see the section called “Run Programs, Scripts, and Existing Tools”.
To change the ACL of a file, use the Set-Acl cmdlet. This example prevents the
Guest account from accessing a
file:
$acl = Get-Acl example.txt
$arguments = "LEE-DESK\Guest","FullControl","Deny"
$accessRule =
New-Object System.Security.AccessControl.FileSystemAccessRule $arguments
$acl.SetAccessRule($accessRule)
$acl | Set-Acl example.txtThe Set-Acl
cmdlet sets the security descriptor of an item. This cmdlet doesn't work
only against the filesystem, however. Any provider (for example, the
Registry provider) that supports the concept of security descriptors
also supports the Set-Acl
cmdlet.
The Set-Acl
cmdlet requires that you provide it with an ACL to apply to the item.
While it is possible to construct the ACL from scratch, it is usually
easiest to retrieve it from the item beforehand (as demonstrated in the
solution). To retrieve the ACL, use the Get-Acl cmdlet. Once you've modified the
access control rules on the ACL, simply pipe them to the Set-Acl cmdlet to make them permanent.
In the solution, the $arguments list that we provide to the
FileSystemAccessRule constructor
explicitly sets a Deny rule on the
Guest account of the LEE-DESK computer for FullControl permission. For more information
about working with classes (such as the FileSystemAccessRule class) from the .NET
Framework, see the section called “Work with .NET Objects”.
Although the Set-Acl command is powerful, you may already
be familiar with command-line tools that offer similar functionality
(such as cacls.exe). Although these
tools generally do not work on the registry (or other providers that
support PowerShell security descriptors), you can of course continue to
use these tools from PowerShell.
For more information about the Set-Acl cmdlet, type Get-Help Set-Acl. For more information about
the Get-Acl cmdlet, see the section called “Get the ACL of a File or Directory”.
The Explorer shell provides useful information about a file when you click on its Properties dialog. It includes the authoring information, image information, music information, and more (see Figure 20.2, “Extended file properties in Windows Explorer”).
Figure 20.2. Extended file properties in Windows Explorer

PowerShell doesn't expose this information by
default, but it is possible to obtain these properties from the Shell.Application COM object. Example 20.6, “Add-ExtendedFileProperties.ps1” does just that—and adds this
extended information as properties to the files returned by the Get-ChildItem cmdlet.
Example 20.6. Add-ExtendedFileProperties.ps1
##############################################################################
begin
{
$shellObject = New-Object -Com Shell.Application
$itemProperties = @{
1 = "Size"; 2 = "Type"; 3 = "Date Modified";
4 = "Date Created"; 5 = "Date Accessed";
7 = "Status"; 8 = "Owner";
9 = "Author"; 10 = "Title"; 11 = "Subject";
12 = "Category"; 13 = "Pages"; 14 = "Comments";
15 = "Copyright"; 16 = "Artist"; 17 = "Album Title";
19 = "Track Number"; 20 = "Genre"; 21 = "Duration";
22 = "Bit Rate"; 23 = "Protected"; 24 = "Camera Model";
25 = "Date Picture Taken"; 26 = "Dimensions";
30 = "Company"; 31 = "Description"; 32 = "File Version";
33 = "Product Name"; 34 = "Product Version" }
}
process
{
$fileItem = $_ | Get-Item
if($fileItem.PsIsContainer)
{
$fileItem
return
}
$directoryName = $fileItem.DirectoryName
$filename = $fileItem.Name
$folderObject = $shellObject.NameSpace($directoryName)
$item = $folderObject.ParseName($filename)
foreach($itemProperty in $itemProperties.Keys)
{
$fileItem | Add-Member NoteProperty $itemProperties[$itemProperty] `
$folderObject.GetDetailsOf($item, $itemProperty)
}
$fileItem
}
For more information about running scripts, see the section called “Run Programs, Scripts, and Existing Tools”.
It is sometimes useful to refer to the same file by two different names or locations. You can't solve this problem by copying the item, because modifications to one file do not automatically affect the other.
The solution to this is called a hard link, an item of a new name that points to the data of another file. The Windows operating system supports hard links, but only Windows Vista includes a utility that lets you create them.
Example 20.7, “New-FilesystemHardLink.ps1” lets you create hard links without needing to install additional tools. It uses (and requires) the Invoke-WindowsApi.ps1 script provided in the section called “Program: Invoke Simple Windows API Calls”.
Example 20.7. New-FilesystemHardLink.ps1
##############################################################################
param(
[string] $filename,
[string] $existingFilename
)
$filename = $executionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath($filename)
$existingFilename = Resolve-Path $existingFilename
$parameterTypes = [string], [string], [IntPtr]
$parameters = [string] $filename, [string] $existingFilename, [IntPtr]::Zero
$currentDirectory = Split-Path $myInvocation.MyCommand.Path
$invokeWindowsApiCommand = Join-Path $currentDirectory Invoke-WindowsApi.ps1
$result = & $invokeWindowsApiCommand "kernel32" `
([bool]) "CreateHardLink" $parameterTypes $parameters
if(-not $result)
{
$message = "Could not create hard link of $filename to " +
"existing file $existingFilename"
Write-Error $message
}
For more information about running scripts, see the section called “Run Programs, Scripts, and Existing Tools”.
When transporting or archiving files, it is useful to store those files in an archive. ZIP archives are the most common type of archive, so it would be useful to have a script to help manage them.
For many purposes, traditional command-line ZIP archive utilities may fulfill your needs. If they do not support the level of detail or interaction that you need for administrative tasks, a more programmatic alternative is attractive.
Example 20.8, “New-ZipFile.ps1” lets you
create ZIP archives simply by piping files into them. It requires that
you have the SharpZipLib installed,
which you can obtain from http://www.icsharpcode.net/OpenSource/SharpZipLib/.
Example 20.8. New-ZipFile.ps1
##############################################################################
param(
$zipName = $(throw "Please specify the name of the file to create."),
$libPath = $(throw "Please specify the path to ICSharpCode.SharpZipLib.dll.")
)
[void] [Reflection.Assembly]::LoadFile($libPath)
$namespace = "ICSharpCode.SharpZipLib.Zip.{0}"
$zipName =
$executionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath($zipName)
$zipFile = New-Object ($namespace -f "ZipOutputStream") ([IO.File]::Create($zipName))
$zipFullName = (Resolve-Path $zipName).Path
[byte[]] $buffer = New-Object byte[] 4096
foreach($file in $input)
{
if($file.FullName -eq $zipFullName)
{
continue
}
$replacePath = [Regex]::Escape( (Get-Location).Path + "\" )
$zipName = ([string] $file) -replace $replacePath,""
$zipEntry = New-Object ($namespace -f "ZipEntry") $zipName
$zipFile.PutNextEntry($zipEntry)
$fileStream = [IO.File]::OpenRead($file.FullName)
[ICSharpCode.SharpZipLib.Core.StreamUtils]::Copy($fileStream, $zipFile, $buffer)
$fileStream.Close()
}
$zipFile.Close()
For more information about running scripts, see the section called “Run Programs, Scripts, and Existing Tools”.
No comments yet
Add a comment