Chapter 20. Files and Directories

Introduction

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.

Determine the Current Location

Problem

You want to determine the current location from a script or command.

Solution

To retrieve the current location, use the Get-Location cmdlet. The Get-Location cmdlet provides the Drive and Path as two common properties:

1 comment

  1. Tome Tanasovski Posted 12 days and 3 hours ago

    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.

Add a comment

$currentLocation = (Get-Location).Path

As a short-form for (Get-Location).Path, use the $pwd automatic variable.

Discussion

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.

Get the Files in a Directory

Problem

You want to get or list the files in a directory.

Solution

To retrieve the list of files in a directory, use the Get-ChildItem cmdlet. To get a specific item, use the Get-Item cmdlet:

Discussion

Although 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:.

Note

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”.

1 comment

  1. Johannes Rössel Posted 22 hours ago

    System.IO. DirectoryInfo” → “System.IO.DirectoryInfo” (superfluous space)

Add a comment

Find All Files Modified Before a Certain Date

Problem

You want to find all files last modified before a certain date.

Solution

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" }

Discussion

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.

Note

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”.

Clear the Content of a File

Problem

You want to clear the content of a file.

Solution

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.txt

Discussion

The (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.

Manage and Change the Attributes of a File

Problem

You want to update the ReadOnly, Hidden, or System attributes of a file.

Solution

Most of the time, you will want to use the familiar attrib.exe program to change the attributes of a file:

2 comments

  1. Tome Tanasovski Posted 12 days and 2 hours ago

    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.

  2. Lee Holmes Posted 10 days and 12 hours ago

    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.

Add a comment

attrib +r test.txt
attrib -s test.txt

To set only the ReadOnly attribute, you can optionally set the IsReadOnly property on the file:

$file = Get-Item test.txt
$file.IsReadOnly = $true

To apply a specific set of attributes, use the Attributes property on the file:

$file = Get-Item test.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

Discussion

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.

Note

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:

2 comments

  1. Tome Tanasovski Posted 12 days and 2 hours ago

    As mentioned earlier... I don't see the reason to use attrib.exe over the powershell/.net methods.

  2. Johannes Rössel Posted 12 days ago

    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.

Add a comment

$file = Get-Item test.txt
$readOnly = [IO.FileAttributes] "ReadOnly"
$file.Attributes = $file.Attributes -bor $readOnly

For more information about working with classes from the .NET Framework, see the section called “Work with .NET Objects”.

Find Files That Match a Pattern

Problem

You want to get a list of files that match a specific pattern.

Solution

Use the Get-ChildItem cmdlet for both simple and advanced wildcard support:

Use the Where-Object cmdlet for advanced regular expression support:

1 comment

  1. Johannes Rössel Posted 21 hours ago

    “Where-Object” should be formatted as code.

Add a comment

  • 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' }

Discussion

The Get-ChildItem cmdlet supports wildcarding through three parameters:

Path

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.

Include/Exclude

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.

Note

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
Filter

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.

Manage Files That Include Special Characters

Problem

You want to use a cmdlet that supports wildcarding but provide a filename that includes wildcard characters.

Solution

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'

Discussion

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.

1 comment

  1. Johannes Rössel Posted 21 hours ago

    The grave accent (back-tick) as well as the escapes for tab, newline and alarm should be typeset as code.

Add a comment

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.

Program: Get Disk Usage Information

Discussion

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.

Note

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”.

Monitor a File for Changes

Problem

You want to monitor the end of a file for new content.

Solution

To monitor the end of a file for new content, use the –Wait parameter of the Get-Content cmdlet.

Get-Content log.txt -Wait

Discussion

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.

1 comment

  1. Johannes Rössel Posted 21 hours ago

    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.

Add a comment

Note

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.

1 comment

  1. Johannes Rössel Posted 21 hours ago

    –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.

Add a comment

Get the Version of a DLL or Executable

Problem

You want to examine the version information of a file.

Solution

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

Discussion

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”.

Program: Get the MD5 or SHA1 Hash of a File

Discussion

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

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.

1 comment

  1. Johannes Rössel Posted 21 hours ago

    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 :-)

Add a comment

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”.

Create a Directory

Problem

You want to create a directory, or file folder.

1 comment

  1. Jose Santiago Oyervides Posted 7 days and 15 hours ago

    I think you could specify that: new-item c:folder -type directory is another way that accomplish the same.

Add a comment

Solution

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            NewDirectory

Discussion

The 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.

Remove a File or Directory

Problem

You want to remove a file or directory.

Solution

To remove a file or directory, use the Remove-Item cmdlet:

PS > Test-Path NewDirectory
True
PS > Remove-Item NewDirectory
PS > Test-Path NewDirectory
False

Discussion

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.

Note

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.

Rename a File or Directory

Problem

You want to rename a file or directory.

Solution

To rename an item in a provider, use the Rename-Item cmdlet:

PS > Rename-Item example.txt example2.txt

Discussion

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.txt

For 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.

Move a File or Directory

Problem

You want to move a file or directory.

Solution

To move a file or directory, use the Move-Item cmdlet:

PS > Move-Item example.txt c:\temp\example2.txt

Discussion

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.

Note

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.

Program: Move or Remove a Locked File

Discussion

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”.

Get the ACL of a File or Directory

Problem

You want to retrieve the ACL of a file or directory.

Solution

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...

Discussion

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”.

Set the ACL of a File or Directory

Problem

You want to change the ACL of a file or directory.

Solution

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.txt

Discussion

The 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”.

Program: Add Extended File Properties to Files

Discussion

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

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”.

Discussion

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”.

Program: Create a ZIP Archive

Discussion

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”.

You must sign in or register before commenting
*
*
*
*
*

Atom Icon Comments on this page or Comments on the whole book.