Powershell : Match file name with folder name - powershell

I'm trying to match file name with folder name before move them to an other directory.
For example, my script need to match if "Test.txt" match with a folder named "Test" and move them to a different directory.
Is it possible with the cmdlets Get-ChildItem ? I didn't find any examples :(
Thanks a lot.

PowerShell 3+
Gets all files recursively from the current directory whose names (without extension) matches its directory's name:
Get-ChildItem -Path . -File -Recurse |
Where-Object { $_.BaseName -eq $_.Directory.Name }
PowerShell 1, 2
There is no -File switch until PowerShell 3, so you have to filter out directories with an extra Where-Object.
Get-ChildItem -Path . -Recurse |
Where-Object { -not $_.PsIsContainer } |
Where-Object { $_.BaseName -eq $_.Directory.Name }
Once you've got all the files that match their parent directory name, you should be able to move them. I'm not sure what your logic is for the destination directory structure.

For starters you can use the Directory property of Get-ChildItem
So lets say you are looking for a file test.txt but only if it is in the directory Scripts
Get-ChildItem *.txt -recurse | Where-Object{($_.Name -eq "test.txt") -and ($_.Directory -like "*\scripts")} | Move-Item $_.Directory "C:\NewFolder"
The Where clause will look for a file called text.txt as long as its in a folder called c:\somepath\scripts. So this will not match c:\anotherpath\test.txt. When a match is found Move the located files directory to a new location.
Note I am not sure if the logic will hold if multiple file matches are found. If it fails then we could assign all the matches to a variable and then process the all the unique values.
$foundDirectories = Get-ChildItem *.txt -recurse | Where-Object{($_.Name -eq "test.txt") -and ($_.Directory -like "*\scripts")} | Select-Object -ExpandProperty Directory -Unique
$foundDirectories | ForEach-Object{Move-Item $_ "C:\newfolder"}

Related

Rename files in subfolders

I am trying to rename files in subfolders in a certain pattern, but I am stuck.
The situation is as follows: I have multiple folders which are sometimes named as the target filename depending on the length, but the name does not really matter.
In each folder are always 2 files: the Target-File with a random name and the correct extension, and the Source-File which is always the correct BaseName with a txt-extension.
For example:
Folder1\7393028473.docx
Folder1\January.txt
Folder2\9373930843.pdf
Folder2\February.txt
My goal is to rename every not-txt-file with the Basename of the txt-file. Executed, it should be like:
Folder1\January.docx
Folder1\January.txt
Folder2\February.pdf
Folder2\February.txt
With gci I was able to create both lists but didn't find a good way for the renaming.
$SourceName = gci -File -Recurse | Where {$_.Extension -ne ".txt"}
$TargetName = gci -File -Recurse | Where {$_.Extension -eq ".txt"}
I did also try to use gci for renaming, but was not able to tell it to use the newname based on the txt-file:
gci -File -Recurse | Where {$_.Extension -ne ".txt"} | Rename-Item -NewName {$_.extension -eq ".txt"}
This only renamed the .docx-file to "FALSE" because the filename already exists.
What I did not try (but would be ok) is to not only rename the file, but also move it to the parent directory.
This is one way to do it but it would fail as soon as there are 2 or more files with a different extension than .txt but having the same extension. It would also fail as soon as one folder has more than one .txt file.
# Get all folders under 'TargetDirectory'
Get-ChildItem TargetDirectory -Directory -Recurse | ForEach-Object {
# For each sub-folder, get their files
$childs = $_.EnumerateFiles()
# Filter and split the child files by their extension
$txt, $notTxt = $childs.Where({ $_.Extension -eq '.txt' }, 'Split')
# Use the BaseName of the '.txt' File but the Extension of
# the file being renamed
$notTxt | Rename-Item -NewName { $txt.BaseName + $_.Extension }
}
Thanks for your reply and sorry for my late reply.
I tried your code but its not working correctly:
The NewName is created correctly, but the problem is the rename-function or rather the notTxt list because it only contains the item itself but not hte full path.
When I copy the file which should be renamed into the parent-directory your code does work in the file in the parent-directory.
There was another answer which apperntly was deleted but did work.
I also tried a foreach-loop in one of my tries but didn't get the NewName to work.
I don't know why, but I didn't consider creating the NewName with a variable, which was done in the deleted answer:
$folders = gci -Directory -Recurse
foreach ($folder in $folders) {
$targetFile = gci $folder | Where {$_.Extension -ne ".txt"}
$sourceFile = gci $folder | Where {$_.Extension -eq ".txt"}
$newName = $sourceFile.BaseName + $targetFile.Extension
Rename-Item $targetFile.FullName $newName
}
Of course you can try and get your code to work, but I can make do with this code.
Thank you very much for your help.

Get ChildItem from only files of specific folders

I try to export files of specific named Folders:
Get-ChildItem -Path 'C:\Test' -Name -Recurse -File > C:\Test\Test.txt
I get a list like:
content.csv
Test.txt
Folder 1\INTERESTED_FOLDER\a - Kopie.txt
Folder 1\INTERESTED_FOLDER\a.txt
Folder 1\Neuer Ordner\ttt.txt
Folder 1\Neuer Ordner - Kopie\ttt.txt
Folder 2\INTERESTED_FOLDER\b - Kopie.txt
Folder 2\INTERESTED_FOLDER\b.txt
Folder 2\Neuer Ordner\ttt.txt
Folder 2\Neuer Ordner - Kopie\ttt.txt
But what i want is:
Folder 1\INTERESTED_FOLDER\a - Kopie.txt
Folder 1\INTERESTED_FOLDER\a.txt
Folder 2\INTERESTED_FOLDER\b - Kopie.txt
Folder 2\INTERESTED_FOLDER\b.txt
I tried with -Filter "INTERESTED" etc. but then i only get
C:\Test\Folder 1\INTERESTED_FOLDER
C:\Test\Folder 2\INTERESTED_FOLDER
What i do wrong?
If I read your question correctly, you want the FullNames of the files (so their names including the path).
If that is the case, remove the -Name switch and filter on the DirectoryName property like:
(Get-ChildItem -Path 'C:\Test' -Recurse -File |
Where-Object { $_.DirectoryName -match 'INTERESTED_FOLDER' }).FullName | # also matches 'MY_INTERESTED_FOLDER_123'
Set-Content -Path 'C:\Test\Test.txt'
Alternatives for the Where-Object clause:
# also matches 'MY_INTERESTED_FOLDER_123'
Where-Object { $_.DirectoryName -like '*INTERESTED_FOLDER*' }
or if you are looking for a precise match on the complete folder name
# does NOT match 'MY_INTERESTED_FOLDER_123'
Where-Object { $_.DirectoryName -split '[/\\]' -contains 'INTERESTED_FOLDER' }
You can perform a wildcard search using the -Filter parameter:
Get-ChildItem -Path 'C:\Test' -Name -Recurse -File -Filter *INTERESTED_FOLDER* > C:\Test\Test.txt
If for example, you were interested in finding the files in INTERESTED_FOLDER but also only the files that are .txt you could do:
-Filter *INTERESTED_FOLDER*.txt
Using the -Name parameter affects your capabilities because you are returning strings instead of FileInfoObjects. You may then use Where-Object to capture everything.
Get-ChildItem -Path 'C:\Test' -Name -Recurse -File |
Where {$_ -match '\\INTERESTED_FOLDER\\'} |
Set-Content c:\test\test.txt
Note that the matching above assumes INTERESTED_FOLDER is not a direct descendent of your path. If that is a possibility, then your match expression needs to be updated to \\?INTERESTED_FOLDER\\.

get-childitem find files in folders with same names

I need to check that all files in folders with the naming ".policy" is ether 'azureDeploy.parameters.json' or 'azureDeploy.json'
if i only want it to check one folder i just give the full path as here:
$azureDeployFiles = #('azureDeploy.parameters.json', 'azureDeploy.json')
(Get-ChildItem -path ..\..\fes\.policy -file -Recurse).name | Should -BeIn $azureDeployFiles
and that works. But i have multiple folders called something like '.\..\..\.policy' and i would like to check if all the folders with that naming only contains files with the naming 'azureDeploy.parameters.json' or 'azureDeploy.json'
You could specify 'only files in folders named X' with Where-Object on the Directory property:
$azureDeployFiles = #('azureDeploy.parameters.json', 'azureDeploy.json')
Get-ChildItem -path ..\..\ -Recurse -File |
Where Directory -Like "*\.policy" |
# example checking file names with -In
Foreach { $_.Name -In $azureDeployFiles }
Continuing from my comment. . .with a little help from the pipeline, we can use Where-Object to filter for directories with that specific name:
$azureDeployFiles = #('azureDeploy.parameters.json', 'azureDeploy.json')
Get-ChildItem -Path '.\MyPath' -Include $azureDeployFiles -Recurse |
Where-Object Directory -Like '*\.Policy'

How do I delete all .webp files in a directory based on if the duplicate .jpeg file (with the same .BaseName) has changed in the last 24 hours?

So, I have a directory of pictures for a website. There are pictures that are the same with both a .jpeg extension and a .webp extension. I want to write a PowerShell script that finds all the existing .jpeg files that changed in the last 24 hours, and then find the respective .webp file and delete the .webp file.
I've tried this to get all the .webp files that can be deleted but it doesn't seem to work:
$images = Get-ChildItem -Path $dir\*.jpg, $dir\*.webp |
Group-Object { $_.BaseName } |
Where-Object {($_.CreationTime -gt (Get-Date).AddDays(-1)) -or ($_.Group.Extension -notcontains '.jpg')} |
ForEach-Object Group
I think this is easier:
Get a list of basenames for the jpg files that were last modified as of yesterday, next get a list of files in the same directory with the .webp extension that have a BaseName matching one of the jpg basenames and then remove these.
$dir = 'D:\Test'
$refdate = (Get-Date).AddDays(-1).Date
$jpegs = (Get-ChildItem -Path $dir -Filter '*.jpg' -File | Where-Object { $_.LastWriteTime -gt $refdate }).BaseName
Get-ChildItem -Path $dir -Filter '*.webp' -File | Where-Object { $jpegs -contains $_.BaseName } | Remove-Item -WhatIf
Onece you are satisfied the correct files are getting deleted, remove the safety -WhatIf switch and run again.
Theo's helpful answer shows you an alternative approach; if you want to stick with the Group-Object approach:
$webpFilesToDelete =
Get-ChildItem -Path $dir\*.jpg, $dir\*.webp |
Group-Object BaseName | Where-Object Count -eq 2 |
ForEach-Object {
# Test the creation date of the *.jpg file.
if ($_.Group[0].CreationTime -gt (Get-Date).AddDays(-1)) {
$_.Group[1] # Output the corresponding *.webp file
}
}
Note that, as in your own attempt, .CreationTime is used, though note if there's a chance that the files are updated again after creation and that is the timestamp you care about, you should use .LastWriteTime.
The command relies on the fact that Group-Object sorts the elements of the groups it creates by the sort criteria, which in this case, due to grouping by a string property - means lexical sorting in which .jpg files are listed before .webp files.
Therefore, for groups that have 2 elements (Where-Object Count -eq 2), implying that for the given base name both a .jpg and a .webp file exist, $_.Group[0] refers to the .jpg file, and $_.Group[1] to the .webp file.
As for what you tried:
$_.CreationTime yields $null, because $_ in your command refers to the group-information object at hand (an instance of Microsoft.PowerShell.Commands.GroupInfo), as output by Group-Object, and this type has no such property.
Also, since you're using Where-Object, you're simply filtering groups, so that any group that passes the filter tests is passed through as-is, and ForEach-Object Group then outputs both files in the group.
Thanks for your helpfull answers and the clarification! I thougth initially that I needed .LastWriteTime, but I needed .CreationTime. This serves my needes. Here is the result:
$refdate = (Get-Date).AddDays(-1).Date
$jpegs = (Get-ChildItem -Path $dir -Filter '*.jpg' -File | Where-Object { $_.CreationTime -gt $refdate }).BaseName
$webp = Get-ChildItem -Path $dir -Filter '*.webp' -File | Where-Object { $jpegs -contains $_.BaseName }

Copy directories when their name matches -Like txt files

I am trying to copy a bunch of directories. I have text files which have a similar name and I need to match the directories to those text files to determine which directories to copy.
This is what I have done and I can't figure out how to fix it. I am going around in circles.
$destination = "..\..\$args\Images\"
$txtfiles = Get-ChildItem $destination -Include *.txt
$source = "..\..\..\Images\" | ?{ $_.PSIsContainer } | Where-Object { $_.Name -Like "*$txtfiles*" } | Copy-Item $destination
An example of a txt file: 1e03655b-0aac-48b2-82f3-75942084af7a.txt
and folder: name.1e03655b-0aac-48b2-82f3-75942084af7a
So I need to find the folders that match the txt files and copy the folder to the txt file directory.
Thanks
You want to expand the BaseName properties of those text files (to cut off the .txt part) and have an array of strings such as:
1e03655b-0aac-48b2-82f3-75942084af7a
1e03655b-0aac-48b2-82f3-75942084af7b
1e03655b-0aac-48b2-82f3-75942084af8g
1e03655b-0aac-48b2-82f3-75942084afba
Then your $source = line needs to pull a directory listing and not just pipe a string down the pipeline, and then I would suggest matching the directory names with a regex match, and checking to see if that match is -in $txtfiles.
$destination = "..\..\$args\Images\"
$txtfiles = Get-ChildItem $destination -Filter *.txt | Select -ExpandProperty BaseName
$source = "..\..\..\Images\"
GCI $source -Directory | ?{ $_.Name -match "(\w{8}-\w{4}-\w{4}-\w{4}-\w{12})$" } | %{If($Matches[1] -in $txtfiles){Copy-Item $_.FullName $destination}}