SeanKilleen
SeanKilleen

Reputation: 8977

Powershell: how to remove multiple & directories with exceptions?

The Setup

I have a folder structure that looks like:

C:\RootFolder
            \file1.txt
            \file2.txt
            \license.txt
            \Settings
                     \fileA.txt
                     \fileB.txt
                     \settings.txt
            \OtherFolders

The Goal

Delete all of the files except for license.txt and settings.txt.

I would like in the end for only the following to remain:

  C:\RootFolder
                \license.txt
                \Settings
                         \settings.txt

The Script to Far

$exclude = @('license.txt', 'settings.txt')
Get-ChildItem C:\RootFolder -recurse -exclude $exclude | foreach ($_) {remove-item $_.fullname -recurse:$false}

The Problem

Even though I specify -recurse:$false specifically, it always generates a message for each folder indicating that "recurse isn't specified" and saying it will delete all child items.

After this, the license.txt file remains, but the settings.txt file (in a subdirectory) does not.

Upvotes: 1

Views: 8324

Answers (2)

SeanKilleen
SeanKilleen

Reputation: 8977

And almost as soon as I posted, the answer came to me -- I had to find only files, not directories.

Since I'm on PowerShell 2.0, I couldn't use the -File attribute that's new in powershell 3. Instead, I have to check every object to see if it's a container.

Solution:

$exclude = @('license.txt', 'settings.txt')

Get-ChildItem C:\RootFolder -recurse -exclude $exclude | Where-Object {!($_.PSIsContainer)} | foreach ($_) {remove-item $_.fullname}

This worked perfectly to achieve what I needed.

Improving on the Solution

Thanks to several comments / posts here, there are some more elegant ways that taught me a bit more about powershell:

  • Remove-Item can take commands from a pipe-line. No foreach is necessary.
  • Where-Object can just be shortened to "?"
  • Get-ChildItem can be shortened to "gci" (alias)
  • We don't need a ($_) after the Foreach. Foreach understands that automatically.
  • If we wanted to put the excludes on one line, we can do it via an array right inline

Given these, an elegant solution that fits into a one-liner would be:

gci C:\RootFolder -Recurse -Exclude @('license.txt', 'settings.txt') | ? { ! $_.PSIsContainer } | Remove-Item -Force

Upvotes: 3

Peter
Peter

Reputation: 306

You code works great.

Just a one-liner so we can do it another way:

Get-ChildItem C:\RootFolder -recurse | Where-Object {!($_.PSIsContainer)} | Foreach { if ( $_.Name -ne "license.txt" -and $_.Name -ne "settings.txt") {remove-item $_.FullName -force}}

If there are a lot of files need to be excluded then this one liner doesn't scale - in that case I will use your code:)

@Sean Killeen by the way: Is there a ($_) after foreach? Sounds like you are using Foreach-Object Cmdlet instead of Foreach construct. My understanding is for Cmdlet we don't need ($something in $sometingElse) after "foreach-object". But in Foreach construct we need ($something in $somethingElse). Correct me if I am wrong.

Upvotes: 1

Related Questions