Finding n matching files by sub-path recursively inside a directory - powershell

I have a file structure like:
RootDir\
ADir
1.xml
2.xml
BDir
1.xml
2.xml
3.xml
CDir
2.xml
3.xml
I need to write script to recursively check if a file exists in more than one directory under RootDir, where the file path is identical from one level below RootDir.
For example RootDir\ADir\1.xml also exists in location RootDir\BDir\1.xml
The code below returns the path of matching entries, however it also continues to match with iteself, despite the -exclude switch
$rootCompare = "C:\Users\XXX\Documents\Compare\RootDir"
$rootItems = get-childitem -Path C:\Users\XXX\Documents\Compare\RootDir -Recurse
foreach ($i in $rootItems){
$dirItems = get-childitem $i -File -Recurse | ForEach-Object {
$fullName = $_.fullName
$baseName = $_.baseName
$baseExt = $_.Extension
Write-Host "fullname="$fullName
if (Test-Path $rootCompare\*\$baseName$baseExt -Exclude $fullName) {
$fileSearch = get-ChildItem -Path $rootCompare\*\$baseName$baseExt -Exclude "*$fullName*"
Write-Host "searching for file: "$fullName
Write-Host "file exists in another location: "$fileSearch
}
}
}
Example return:
fullname= C:\Users\XXX\Documents\Compare\RootDir\CDir\3.xml
searching for file: C:\Users\XXX\Documents\Compare\RootDir\CDir\3.xml
file exists in another location: C:\Users\XXX\Documents\Compare\RootDir\ADir\3.xml
C:\Users\XXX\Documents\Compare\RootDir\CDir\3.xml
1) How can I adapt the exclude condition to prevent it from matching with itself?
2) This version only works at 1 level below RootDir. How should it be adapted to work n levels below RootDir?
For example RootDir\ADir\DDir\1.xml should also match RootDir\BDir\DDir\1.xml but not RootDir\ADir\1.xml
Important point:
The number, name and paths the of directories are dynamic and cannot be hard-coded

The quick and dirty way of how I would do it is to do a single Get-ChildItem -Recurse into a variable. Then I could iterate through that variable twice. Once to get the first list of objects, and a second to compare to the first. Then in the comparison we want to match where the Name is the same, and the FullName doesn't match.
$rootCompare = "C:\Users\XXX\Documents\Compare\RootDir"
$rootItems = get-childitem -Path $rootCompare -Recurse
foreach ($i in $rootItems){
foreach ($j in $rootItems){
if($i.Name -eq $j.Name -and $i.fullName -ne $j.fullName)
{
Write-Host "file" $i.fullName
Write-Host "also exists in another location: "$j.fullName
}
}
}

Related

Bulk renaming files with different extensions in order using powershell

is there a way to bulk rename items such that a folder with the items arranged in order would have their name changed into numbers with zero padding regardless of extension?
for example, a folder with files named:
file1.jpg
file2.jpg
file3.jpg
file4.png
file5.png
file6.png
file7.png
file8.jpg
file9.jpg
file10.mp4
would end up like this:
01.jpg
02.jpg
03.jpg
04.png
05.png
06.png
07.png
08.jpg
09.jpg
10.mp4
i had a script i found somewhere that can rename files in alphabetical order. however, it seems to only accepts conventionally bulk renamed files (done by selecting all the files, and renaming them such that they read "file (1).jpg" etc), which messes up the ordering when dealing with differing file extensions. it also doesn't seem to rename files with variations in their file names. here is what the code looked like:
Get-ChildItem -Path C:\Directory -Filter file* | % {
$matched = $_.BaseName -match "\((?<number>\d+)\)"
if (-not $matched) {break;}
[int]$number = $Matches["number"]
Rename-Item -Path $_.FullName -NewName "$($number.ToString("000"))$($_.Extension)"
}
If your intent is to rename the files based on the ending digits of their BaseName you can use Get-ChildItem in combination with Where-Object for filtering them and then pipe this result to Rename-Item using a delay-bind script block.
Needles to say, this code does not handle file collision. If there is more than one file with the same ending digits and the same extension this will error out.
Get-ChildItem -Filter file* | Where-Object { $_.BaseName -match '\d+$' } |
Rename-Item -NewName {
$basename = '{0:00}' -f [int][regex]::Match($_.BaseName, '\d+$').Value
$basename + $_.Extension
}
To test the code you can use the following:
#'
file1.jpg
file2.jpg
file3.jpg
file4.png
file5.png
file6.png
file7.png
file8.jpg
file9.jpg
file10.mp4
'# -split '\r?\n' -as [System.IO.FileInfo[]] | ForEach-Object {
$basename = '{0:00}' -f [int][regex]::Match($_.BaseName, '\d+$').Value
$basename + $_.Extension
}
You could just use the number of files found in the folder to create the appropriate 'numbering' format for renaming them.
$files = (Get-ChildItem -Path 'D:\Test' -File) | Sort-Object Name
# depending on the number of files, create a formating template
# to get the number of leading zeros correct.
# example: 645 files would create this format: '{0:000}{1}'
$format = '{0:' + '0' * ($files.Count).ToString().Length + '}{1}'
# a counter for the index number
$index = 1
# now loop over the files and rename them
foreach ($file in $files) {
$file | Rename-Item -NewName ($format -f $index++, $file.Extension) -WhatIf
}
The -WhatIf switch is a safety measure. With this, no file gets actually renamed, you will only see in the console what WOULD happen. Once you are content with that, remove the -WhatIf switch from the code and run again to rename all your files in the folder

Bulk renaming photos and adding letters to duplicate file names in Powershell

I have a question about a powershell script. I want to rename a bunch of photos within a folder. I have a .csv file of the old names and the new names. This is a section of that file:
OldFile NewFile
{5858AA5A-DB1B-475A-808E-0BFF0B885E5B}.jpeg 975NNNN-AGUIRRESUGARASSOCSTACK-Notes-20200828.jpeg
{FA1E4CEE-0AD8-4B40-A5AD-4BB22C0EE4F0}.jpeg 975NNNN-AGUIRRESUGARASSOCSTACK-Other-20200828.jpeg
{FD20FA44-B3D2-4A6A-B73D-F3BADC2DDE71}.jpeg 975NNNN-AGUIRRESUGARASSOCSTACK-Vicinity-20200831.jpeg
{E0DDA4CD-7783-417C-9BE0-705FFA08CD17}.jpeg 975NNNN-AGUIRRESUGARASSOCSTACK-Vicinity-20200831.jpeg
{76DC6315-942D-444C-BA04-92FC9B9FF1A5}.jpeg 975NNNN-AGUIRRESUGARASSOCSTACK-Vicinity-20200831.jpeg
{3C853453-0A0D-40B5-B3B7-B0F84F92D512}.jpeg 975NNNN-AGUIRRESUGARASSOCSTACK-Vicinity-20200831.jpeg
Many of the new file names will be duplicates. For those files, I want to add a letter (A,B,C, so on) in the middle of the name at an exact location.
For example, if the file, 975NNNN-AGUIRRESUGARASSOCSTACK-Vicinity-20200831.jpeg, is a duplicate, I want to add "A" right after "Vicinity", so that the file is called 975NNNN-AGUIRRESUGARASSOCSTACK-VicinityA-20200831.jpeg. The letter will always be at that exact same location (right before the third -).
This is the script I have so far. I know it's not right and I haven't been able to even attempt at adding the letter within the script. (I'm a complete Powershell newbie.)
$filesToRename = Import-CSV C:\Users\clair\OneDrive\Documents\JOA\batch_photos\Rename_Central_Aguirre.csv
foreach ($file In $filesToRename) {
if (Test-Path $file.NewFile) {
$letter = -begin { $count= 1 } -Process { Rename-Item $file.OldFile
"file-$([char](96 + $count)).jpeg"; $count++}
} else {
Rename-Item $file.OldFile $file.NewFile
}
}
Could I get some guidance on how to achieve this file naming system?
Thanks!!!
When renaming files using a character from the alphabet will mean you will only have 26 options. If that is enough for you, you can do the following:
$alphabet = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
$folderPath = 'D:\Test'
$filesToRename = Import-CSV C:\Users\clair\OneDrive\Documents\JOA\batch_photos\Rename_Central_Aguirre.csv
foreach ($file In $filesToRename) {
$oldFile = Join-Path -Path $folderPath -ChildPath $file.OldFile
if (Test-Path $oldFile -PathType Leaf) {
# split the new filename into workable parts
$newName = $file.NewFile
$extension = [System.IO.Path]::GetExtension($newName)
$parts = [System.IO.Path]::GetFileNameWithoutExtension($newName) -split '-'
$suffix = $parts[-1]
$prefix = $parts[0..($parts.Count -2)] -join '-'
$charToAppend = 0 # counter to go through the characters in the alphabet. 0..25
while (Test-Path (Join-Path -Path $folderPath -ChildPath $newName) -PathType Leaf) {
if ($charToAppend -gt 25) {
# bail out if al characters have been used up
throw "Cannot rename file '$($file.OldFile)', because all characters A-Z are already used"
}
$newName = '{0}{1}-{2}{3}' -f $prefix, $alphabet[$charToAppend++], $suffix, $extension
}
Rename-Item -Path $oldFile -NewName $newName
}
else {
Write-Warning "File '$($file.OldFile)' not found"
}
}
Before:
D:\TEST
{3C853453-0A0D-40B5-B3B7-B0F84F92D512}.jpeg
{5858AA5A-DB1B-475A-808E-0BFF0B885E5B}.jpeg
{76DC6315-942D-444C-BA04-92FC9B9FF1A5}.jpeg
{E0DDA4CD-7783-417C-9BE0-705FFA08CD17}.jpeg
{FA1E4CEE-0AD8-4B40-A5AD-4BB22C0EE4F0}.jpeg
{FD20FA44-B3D2-4A6A-B73D-F3BADC2DDE71}.jpeg
After:
D:\TEST
975NNNN-AGUIRRESUGARASSOCSTACK-Notes-20200828.jpeg
975NNNN-AGUIRRESUGARASSOCSTACK-Other-20200828.jpeg
975NNNN-AGUIRRESUGARASSOCSTACK-Vicinity-20200831.jpeg
975NNNN-AGUIRRESUGARASSOCSTACK-VicinityA-20200831.jpeg
975NNNN-AGUIRRESUGARASSOCSTACK-VicinityB-20200831.jpeg
975NNNN-AGUIRRESUGARASSOCSTACK-VicinityC-20200831.jpeg
I think you need to use the method.Insert(). This is a small example how it works:
I ve created a txt named 975NNNN-AGUIRRERM1-Vicinity-20200829.txt in C:\Test just for testing purpose, in your example the first code line is to identify the duplicate(s)
#Code to identify duplicates (insert your code instead of mine)
$files=Get-ChildItem -Path C:\Test -File -Name
#The following line indentifies the location of the last "-" (I understand you always have 3 "-" right?)
$DashPos=($files).LastIndexOf("-")
#This inserts on the position $DashPos, the letter "A")
$files.Insert($DashPos,"A")

How to check all strings before doing the next step

I want to perform string-comparison. I use a foreach loop, but I want to do move-item after all strings have been compared.
My concept is " If all of strings not match, do move-item. "
How can I do that?
here is my present code but it is " if match,do move-item
$Myfile=dir *my file name*
$directory=dir D:\example (there are many directory)
foreach($dir in $directory){
if($dir match $Myfile.basename ){
move-item $Myfile.fullname -destination D:\example
}
}
I want to do move-item after string comparison to ensure that my file name doesn't exist $directory
here is what I want to achieve
if( ("all of the directory Name") -notmatch $myfile.basename ){
move-item XXX to XXX
}
if I use -notmatch , it will do move-item at the first time loop. So I need to do move-item after checking them all...
You can set a variable to true if something matches:
$Myfile=dir *my file name*
$directory=dir D:\example (there are many directory)
$move = $false
foreach($dir in $directory){
if($dir match $Myfile.basename ){
$move = $true
}
}
if ($move){
move-item $Myfile.fullname -destination D:\example
}
You can try using notcontains operator to check the file base name appears in the directory files :
$myfile = get-childitem "filepath"
$directory = #(get-childitem "D:\Example" | foreach {$_.basename})
if ($directory -notcontains $myfile.basename) {
move-item xxx to xxx
}

Searching for files in powershell

I've scoured all of the internet for this answer. Maybe it's right here, but alas, I'm out of time and we're on a time schedule from the wonderful boys over in legal.
We have some files which need to be retrieved based on particular names which appear in the directory path.
The person who stored and saved all of these files kept the same naming convention throughout. She's pretty awesome and a++ to her.
The file structure is as below:
Animals
-Dogs
-Folders With Breeds of Dogs
-<Breed of Dog>_MA_etc.pdf
-Cats
-Folders with Breeds of Cats
-<Breed of Cat>_MA_etc.pdf
-ETC
-etc
-etc
The person who saved the files was meticulous about file structure and naming convention, so you can expect c:\animals\dogs\GSD\GSD_MA.PDF or something like that.
While the original author was rather consistent, human error has occured so what I'm trying to do is look for "close enough", basically.
We might have:
Client Agreements\Netflix\files
Master Agreements\Netflix,Inc\files
Rental Agreements\Netflix\files
What I want to do is grab the file structure of all of those and move them to my "E:\sorted" directory maintaining the file structure it has.
So stepping way from animals, we've got a client list from legal with names they're interested. If I look for name:name, i get 27 results. So far not good.
I've tried partial and I get zero results. So here's my terrible code below. Maybe you can make fun of me and show me where I went wrong.
$a = Import-CSV C:\scripts\Clients.csv
$a = #($a.Client)
#$a = $a | %{ $_.SubString(0,6) }
$c = Get-ChildItem E:\Legal\ -include ($a) -recurse # | Where-Object {($_ -match $a)}
ForEach($file in $c){
$dest = Split-Path -path $file.FullName -Parent | Split-path -NoQualifier
#Copy-Item -path $file -recurse -Destination "e:\sorted\11\$dest" -force -Verbose
}
I expect that there is a more PowerShell-ish way to do it, but I used a more procedural-type approach.
Using a HashSet, I create a set of directories which need to be copied. A HashSet has only one of an entry, so if it contains "C:\A\B", then adding "C:\A\B" again will not add another entry.
The .contains method is the .NET one, not the PS one, and similarly for .replace.
$src = "C:\temp\a"
$dest = "F:\temp\b"
$CsvFile = Join-Path -Path $src -ChildPath "findthese.csv"
$sought = (Import-Csv $CsvFile).Client
$dirs = Get-ChildItem -Path $src -Directory -Recurse
$set = New-Object System.Collections.Generic.HashSet[string]
# get the directories with a client name in the path anywhere
foreach($dir in $dirs) {
foreach($client in $sought) {
if ($dir.FullName.contains($client)) {
$temp = $set.Add($dir.FullName)
}
}
}
# copy the selected directory structures to the destination
foreach($dir in $set) {
Copy-Item -path $dir -Destination $dir.replace($src, $dest) -Recurse -WhatIf
}
I left the -WhatIf in there so you can quickly check it's going to do the right thing.
If the names in $a don't exactly match the names of files, using that as input to the include parameter won't help you find just those files you want.
I've got a file named clients.csv with the follwong
client,gender,fun
fred,m,y
barney,m,y
wilma,f,y
navneet,n,y
kumar,f,y
konda,m,y
In my current directory, I've got a directory named clients with the following contents:
C:
├───clients
├───losers
│ barney_loser.txt
│ kumar_loser.txt
│
└───winners
fred_winner.txt
konda_winner.txt
wilma_winner.txt
Case-1:
ls .\clients\ -Filter *.txt -Recurse
Returns all the text files.
Case-2:
$people = import-csv -path .\people.csv
$clients = $people.client
ls .\clients\ -Filter *.txt -Recurse -Include $clients
Returns me nothing.
Case-3:
$people = import-csv -path .\people.csv
$clients = $people.client
$clients += 'kumar_loser.txt'
ls .\clients\ -Filter *.txt -Recurse -Include $clients
Returns me one record for "kumar_loser.txt".
I'm asserting the pattern in your list ($a) don't match the file names.
If I wanted to fix that in my example, I could do something like this...
$people = import-csv -path .\people.csv
$clients = $people.client
for($i = 0; $i -lt $clients.length; $i++) {
$clients[$i] = '*{0}*' -f $clients[$i]
}
ls .\clients\ -Filter *.txt -Recurse -Include $clients
Hope this helps.
Thanks for the help guys.
I took a less scripty approach and procedural approach, as suggested above. Here's the code I used that mostly worked, a colleague and I went through and verified the results and some outlier files. I had to double check the errors that popped up and found a few more files that I wanted. Wasn't perfect, but definitely cut down looking through 700 folders and 3000 files. Include is great but filter is what I really wanted. Furthermore, Include doesn't like index values and Filter Especially doesn't, so I had to save it to a variable and filter by that with a * wildcard which did work.
Here's what I did:
$people = Import-CSV C:\scripts\HelenClients.csv
$clients = $people.Client| %{$_.SubString(0,5)}
for($i=0; $i -lt $clients.Length; $i++){
$name = $clients[$i]
Write-Host "Searching for $name"
$file = Get-ChildItem 'E:\Legal\' -Include "$name*" -recurse
if($file -ne $null){
$dest = Split-Path -path $file -Parent
$dest1 = $dest | Split-Path -NoQualifier
$from = $dest[0]
$to = $dest1[0]
$too = $file.BaseName[0]
Copy-Item $file -Destination e:\sorted\16\$to\$too\ -force -Verbose
}
else{
Write-Output "No results found"
}
}
I found when you store the results into a variable, if there's more than one, it'll list all of the locations and names, etc. Not pretty. See below:
PS C:\Users\me> $ff
Directory: E:\ParentDir\subfolder\redacted
Mode LastWriteTime Length Name
---- ------------- ------ ----
-a---- 6/4/2018 1:47 PM 50485 redacted.docx
-a---- 6/4/2018 1:47 PM 155579 redacted.pdf
PS C:\Users\me> $ff.Basename
Redacted Basename 0
Redacted Basename 1
PS C:\Users\me> $ff.BaseName[0]
Redacted Basename 0
So I just wanted the first indexed value. I also wanted to maintain the file structure without copying everything over, so I used split-path to kind of take it apart. It's very hodgepodge and not pretty to look at, but it works.

Using PowerShell to move a matching set of files with same name but different extensions to two different destinations

I would like to use PowerShell to move a matching name set of files (1 job file and 1 trigger file both havening the same name just different extensions) from one directory to another. See example below.
Source directory contains job1.zip, job1.trg, job2.zip, and job2.trg. I would like to take matching job names job1.zip and job1.trg and move it to dest1folder, only if it is empty, if not move it to dest2folder. Then loop back to perform the same logic for job2.zip and job2.trg. One thing I also have to take into consideration is the Source directory may only contain job1.zip waiting for job1.trg to be transferred. I am a newbie to PowerShell and blown hours on trying to get it working with no success. Is it possible?
This is what I have so far. I get the files to move to each destination folder using IF logic, but it moves all of the files in the source directory.
$doirun = (get-childItem "d:\ftproot\pstest\").Count
$filecount = (get-childItem "d:\ftproot\ps2\").Count
if ($doirun -le 1) {exit}
$dir = get-childitem "d:\ftproot\pstest\" | Where-Object {($_.extension -eq ".zip") -or ($_.extension -eq ".trg")}
foreach ($file in $dir)
{
if ($filecount -le 2) {Move-item "d:\ftproot\pstest\$file" "d:\ftproot\ps2\"}
else {Move-item "d:\ftproot\pstest\$file" "d:\ftproot\ps3\"}
}
Not tested extensively, but I believe this should work:
$jobs = gci d:\ftproot\pstest\* -include *.zip,*.trg |
select -expand basename | sort -unique
$jobs |foreach-object {
if (test-path d:\ftproot\pstest\$_.zip -and test-path d:\ftproot\pstest\$_.trg){
if (test-path d:\ftproot\pstest\ps2\*){
move-item d:\ftproot\pstest\$_.zip d:\ftproot\pstest\ps3
move-item d:\ftproot\pstest\$_.trg d:\ftproot\pstest\ps3
}
else {
move-item d:\ftproot\pstest\$_.zip d:\ftproot\pstest\ps2
move-item d:\ftproot\pstest\$_.trg d:\ftproot\pstest\ps2
}
}