PowerShell: Rename/delete files in a folder that can appear at different places in the file hierarchy

1

Currently trying to figure out a way to rename PDF files in a certain folder (/root) that are directly or indirectly inside a purchases folder that is itself directly or indirectly inside the /rootfolder. I would like to achieve this with PowerShell but cannot reach any further than the examples I found on the numerous topics out there.

For example, with this directory structure:

/root/<subfolder1>/purchases/invoice1.pdf
/root/<subfolder1>/order.pdf
/root/<subfolder2>/purchases/invoice2.pdf

The intention is to only rename or delete the PDF files that are residing in the "purchases" folders. The PDF files in any other folder must not be touched.

The <subfolderX> names are unknown (i.e. they can be anything, e.g. "a" or "b").

The script needs to run down all the folders recursively.

Any guidance towards achieving this would be appreciated.

loewie1984

Posted 2018-09-17T13:28:45.733

Reputation: 11

1instead of making two gci calls as the other answers suggest, you could simply do this: gci C:\Root *.pdf -Recurse | ? DirectoryName -imatch 'purchase' this will only return PDF files that have the string purchaseanywhere in their directoryname (path) – SimonS – 2018-09-18T09:36:20.030

You're right @SimonS! One can use gci -Recurse \root\*.pdf | ? { $_.DirectoryName -imatch '.*\\purchases(\\.*|$)' } | % FullName instead of my answer, but it is not necessarily faster (depending on the exact file structure) because it doesn't use the PowerShell FileSystem Provider filtering capabilities (i.e. efficient support for globbing). – Youssef Abidi – 2018-09-19T09:33:30.060

Answers

2

Short answer

To display the names of the PDF files directly or indirectly contained in a direct or indirect subdirectory of d:\root named purchases, you can use:

gci -Directory -Recurse d:\root\purchases | % { gci -Recurse "$_\*.pdf" } | % FullName

Short explanation: the command recursively finds all direct and indirect subdirectories of d:\root named purchases, then recursively finds all PDF files in each one of these subdirectories, then returns the full path of each one of these PDF files.

Other use cases

To remove these PDF files:

gci -Directory -Recurse d:\root\purchases | % { gci -Recurse "$_\*.pdf" } | ri

To rename these PDF files (e.g. by adding a prefix and a suffix to the base name):

gci -Directory -Recurse d:\root\purchases | % { gci -Recurse "$_\*.pdf" } | % { rni $_ "pref-$($_.BaseName)-suff.pdf" }

To remove the purchases directories along with their entire content:

gci -Directory -Recurse d:\root\purchases | ri -Recurse

Detailed explanation

  • gci is an alias for Get-ChildItem.
  • gci -Directory -Recurse d:\root\purchases returns all directories (-Directory) whose name is purchases that are direct or indirect (-Recurse) subfolders of d:\root\. In other words, it would return d:\root\purchases (if it existed) as well as d:\root\b\c\purchases and d:\root\a\purchases. This is not very intuitive and does not seem to be explained in the documentation, but it is the current behavior (as of PowerShell 6).
  • % is an alias for ForEach-Object. It executes a script block (enclosed in curly braces ({}) for each object in the pipeline.
  • gci -Recurse "$_\*.pdf" retrieves the list of PDF files that are (directly or indirectly) contained in the previously found purchases directories.
  • % FullName is just used to display the path of these PDF files.
  • ri is an alias for Remove-Item. It removes the previously computed list of PDF files (because the -Path parameter of the Remove-Item command accepts pipeline input).
  • ri -Recurse allows to remove the previously computed list of purchases directories along with their content.
  • rni $_ "pref-$($_.BaseName)-suff.pdf" adds a prefix (pref-) and a suffix (-suff) to the base name $_.BaseName of the current file in the pipeline (i.e. each one of the PDF files that we are looking for). The base name of a file is the name of the file without its extension and rni is an alias for Rename-Item.

Testing this solution (online or locally)

The easiest is to try this solution online.

Otherwise, you can create a sample directory structure in a test folder of the current working directory, with the following snippet (be careful: change .\test to something else if you already have a test folder in the current directory):

mkdir -ErrorAction SilentlyContinue .\test\a\purchases, .\test\a\e\, .\test\b\c\purchases\
Out-File .\test\a\purchases\invoice1.pdf
Out-File .\test\a\order1.pdf
Out-File .\test\a\e\order2.pdf
Out-File .\test\b\c\purchases\invoice2.pdf
Out-File .\test\b\c\purchases\invoice3.pdf

To view all files in this directory structure, you can use:

gci -Recurse -File .\test | % FullName

Which would return something like:

D:\test\a\order1.pdf
D:\test\a\e\order2.pdf
D:\test\a\purchases\prefix-invoice1-suffix.pdf
D:\test\b\c\purchases\prefix-invoice2-suffix.pdf
D:\test\b\c\purchases\prefix-invoice3-suffix.pdf

(Where D: is replaced with your current working directory.)

Executing the command provided as an answer (gci -Directory -Recurse .\test\purchases | % { gci -Recurse "$_\*.pdf" } | % FullName), will display the files that need to be removed or deleted:

D:\test\a\purchases\invoice1.pdf
D:\test\b\c\purchases\invoice2.pdf
D:\test\b\c\purchases\invoice3.pdf

To clean up after your test:

rm -Recurse .\test

Youssef Abidi

Posted 2018-09-17T13:28:45.733

Reputation: 120

1

Youssef Abidi gave you an answer targeting a single drive, but it seems that you are saying the folder path to .\purchases, is unknown?

Yet, the rule of the forum is that you must provide what you've tried and errors you encountered so the you show what you end goal is, vs making us guess, and as most would say 'do your work for you'. Folks here are pretty helpful, but, well, you know.

All that being said, if my assumption is correct. Essentially, do a filter for the purchases folder on any drive on the host, and then the pdf in that folder and do as you will.

It's really just a one-liner pipelined command - if the above is a valid assumption

(Get-PSDrive).Root -like '*:\' | 
% { (Get-ChildItem -Path ((Get-ChildItem -Path $_ -recurse -Filter Purchases -Directory -ErrorAction SilentlyContinue).FullName) -Filter '*.pdf' -Verbose).FullName }

# Results

C:\purchases\powershell-cheat-sheet.pdf
C:\purchases\PSPunctuationWallChart_1_0_4.pdf
D:\Temp\purchases\powershell-cheat-sheet.pdf
D:\Temp\purchases\PSPunctuationWallChart_1_0_4.pdf

Of course you can break that one-liner into something different.

postanote

Posted 2018-09-17T13:28:45.733

Reputation: 1 783

0

I agree doing my homework is essential for the community. I figured out as much with gci and filter but could not improvise the part of the double gci in between quotes.

Will do my best for my next question to show some efforts. Regardless both your answers have helped me solve this problem.

So again many thanks!

loewie1984

Posted 2018-09-17T13:28:45.733

Reputation: 11

No worries, we've all done something similar in our career and one point or the other. Be sure to chose one or the other as you accepted answer for the benefit of others who have similar queries and come across this one. Take care and happy scripting. – postanote – 2018-09-19T20:22:24.353