I have a bunch of directories with files in them and my goal is to loop through a directory structure and write each of the files one by one to a new location.
The files on disk look like this:
- DATAFILE1_DATE_20210101_RUNDATE_20210101.csv
- DATAFILE1_DATE_20210102_RUNDATE_20210102.csv
- DATEFILE2_DATE_20210103_RUNDATE_20210103.json
- DATEFILE2_DATE_20210104_RUNDATE_20210104.json
I'm trying to pass the contents of the directory to a variable $fileSystemItems and then to remove everything after _DATE so that I could build a new directory structure in the target as:
- /target/DATAFILE1/DATAFILE1_DATE_20210101_RUNDATE_20210101.csv
- /target/DATAFILE1/DATAFILE1_DATE_20210102_RUNDATE_20210102.csv
- /target/DATAFILE2/DATEFILE2_DATE_20210103_RUNDATE_20210103.json
- /target/DATAFILE2/DATEFILE2_DATE_20210104_RUNDATE_20210104.json
The PS code I have so far takes the files from a specified directory and outputs them:
$sourcePath = "\\files\data"
$fileSystemItems = Get-ChildItem $sourcePath -Recurse | Where { ! $_.PSIsContainer }
foreach ($file in $fileSystemItems) {
Write-Host "Writing name of the file is $($file.BaseName)"
}
I have tried using the Rename-Item and regex but renaming the files the source is not an option as other programs are accessing the same data, for example:
Get-ChildItem $sourcePath -Recurse | Rename-Item -NewName { $_.Name -replace "^_DATE+","" }
How do I modify the filename in the $file variable in the foreach loop to output both a edited version of the file name (e.g. DATEFILE1) and also the full file name DATAFILE1_DATE_20210101_RUNDATE_20210101.csv ?
Rename-Item is a great tool to begin with! First, let's optimize your code snippet a little bit:
Instead of piping Get-ChildItems output to Where { ! $_.PSIsContainer }, we'll use the -File switch.
Let's also change your regex from ^_DATE+ to _DATE.* and use a pipeline with ForEach-Object:
Get-ChildItem $sourcePath -Recurse -File | ForEach-Object {
Rename-Item -Path $_.Fullname -NewName (($_.Name -replace "_DATE.*","") + $_.Extension)
}
Looking at your destination structure it looks like you might also want to move files to a target directory.
Related
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.
I have an exported CSV file with the below table
"Directory","BaseName"
"E:\Movie Files\Movie (2000)","Movie"
"E:\Movie Files\Movie (2001)","Movie 2"
My code is very close to what I want and that is basically rename the cover.jpg in each folder to the basename of the movie and add -poster.jpg after it, e.g. Movie-poster.jpg, Movie 2-poster.jpg.
It works for the first folder but every folder after this uses the same basename from the first folder. It doesn't recurse for every folder within the root location like I thought it would. So for Movie (2001) I end up having Movie-poster.jpg, not Movie 2-poster.jpg.
This is the code that's not quite there:
$lines = Import-Csv 'E:\Output.csv'
foreach ($line in $lines) {
Get-ChildItem -Path .\* -File -Filter '*Cover*' -Recurse |
Rename-Item -NewName ($line.BaseName + '-poster.jpg')
}
Expected results as mentioned above is that each cover.jpg in each folder and sub-folders will be renamed to match the basename according to their locations.
You end up with all covers being renamed to Movie-poster.jpg because your code renames all covers under the current working directory (.) in the first iteration of your loop, so there are no files with the string "Cover" in their name left when the loop goes into the second iteration.
Process the directories from your CSV instead of processing the current working directory for each of them, and use the current object from the pipeline ($_) for renaming the files. For the latter you also must replace the parentheses with curly brackets.
Change this:
foreach ($line in $lines) {
Get-ChildItem -Path .\* -File -Filter '*Cover*' -Recurse |
Rename-Item -NewName ($line.BaseName + '-poster.jpg')
}
into this:
foreach ($line in $lines) {
Get-ChildItem -Path $line.Directory -File -Filter '*Cover*' -Recurse |
Rename-Item -NewName {$_.BaseName + '-poster.jpg'}
}
Thank you Ansgar for the comment. It didn't work in my instance, what it did was rename ALL the files (including those that already had the correct naming convention) to be cover-poster.jpg however the code did point me in the right direction with regards to $line.Directory.
The code that worked for me in the end was this;
# Renames Cover.jpg to match BaseName file with basename-poster.jpg
$lines = Import-Csv 'E:\Output.csv'
ForEach ($line in $lines) {
Get-ChildItem -Path $line.Directory -file -filter '*Cover*' -Recurse | Rename-Item -NewName {$line.BaseName + '-poster.jpg'}
}
I'm having big troubles combining the rename with an if-condition. PS cannot rename when the file has the same name as before and won't do anything afterwards.
Plus it's unable to rename recursively.
I wanna do sth like:
$entrys = Get-ChildItem -Recurse $myPath
foreach ($name in $entrys) {
$newName=$name.FullName -replace('[\,\/\*\+\#\~\-\=\^\°\$\%\&\<\>\|]','')
if (-not ($newName -like $name)) {
Rename-Item $name -NewName $newName
}
}
I found something similar here but they dont have any conditions there.
So how do I pipe into an if-condition before renaming, so I can pipe directly from get-childitem -recurse to if() then rename()? Or is there another possibility?
Thanks for help
When I try your code I have the same issue that it reports it's unable to rename the file. However, I modified a bit of your code, and now it seems to work:
$entrys = Get-ChildItem -Recurse $myPath -File
foreach ($name in $entrys) {
$newName = $name.Name -replace('[\,\/\*\+\#\~\-\=\^\°\$\%\&\<\>\|]','')
if ($newName -ne $name.Name) {
Rename-Item -LiteralPath $name.FullName -NewName $newName
}
}
First you have to only check for files and not folders. Because once you renamed a folder the files are no longer found when you recurse into a non existing folder name (one that has changed).
If you need to rename folders also. It needs some more work as the folder names might change.
Hope this helps you out.
My PowerShell doesn't throw any error on the Rename-Item cmdlet if the file has the same name. But you could try to use -ErrorAction 0.
However, this oneliner works for me:
gci $myPath -r | % { Rename-Item $_.FullName ($_.FullName -replace('[\,\/\*\+\#\~\-\=\^\°\$\%\&\<\>\|]','')) }
Need to compare source directory $source to $dest to see what .wav files are in source but not target. I want to copy those files that are not in target to the target AND to a third directory $sharefolder. Here is what I have so far. It appears that the copy-item is putting all of the differences into one copy string:
$sharefolder = '\\isilon1\blob\partial_dictation'
$comparefolder = '\\isilon1\blob\partial_dictation_compare'
$source = Get-childitem \\isilon1\blob\dcbld\pv -filter *.wav -recurse
$dest = Get-ChildItem \\isilon1\blob\Partial_Dictation_Compare -filter *.wav -recurse
$diff = Compare-Object $dest $source -passthru | Where-Object {$_.SideIndicator -EQ "=>"} | % {$_.FullName}
ForEach-Object {
Copy-Item "$diff" "$comparefolder" -WhatIf
Copy-Item "$diff" "$sharefolder" -WhatIf
}
This is what I get if I echo $diff ;
\\isilon1\blob\dcbld\pv\-D\CB\LD\-9\94\1.wav
\\isilon1\blob\dcbld\pv\-D\CB\LD\-9\94\2.wav
This is correct as these files are not in the destination. When the copy-item runs, it gives this:
Copy-Item : Cannot find path '\isilon1\blob\dcbld\pv-D\CB\LD-9\94\1.wav \isilon1\blob\dcbld\pv-D\CB\LD-9\94\2.wav' because it does not exist.
I am coming from Unix so this is new to me.
That's not quite how ForEach-Object works. You need to pipe objects to it, and it will iterate through them for you. You used it correctly the line above (% is an alias for ForEach-Object).
$diff | ForEach-Object {
Copy-Item "$_" "$comparefolder" -WhatIf
Copy-Item "$_" "$sharefolder" -WhatIf
}
See Get-Help ForEach-Object.
You could also use the foreach statement, which does a similar thing but isn't a cmdlet:
foreach ($file in $diff) {
Copy-Item "$file" "$comparefolder" -WhatIf
Copy-Item "$file" "$sharefolder" -WhatIf
}
See Get-Help about_foreach.
They function slightly differently and have their own benefits -- foreach being faster for small collections, and ForEach-Object accepting input from a pipe and being capable of streaming objects as soon as they enter the pipe -- but here they're basically identical.
Beware that Compare-Object just compares the name without path of file and directory objects, as far as I'm aware. Like it doesn't look at the file size or date, let alone content. That might be just fine for your needs, but it throws people, IMX.
I have 10K documents in a directory with this type of naming convention:
1050_14447_Letter Extension.pdf, 1333_14444_Letter.docx, etc...
I tried using this script to remove all characters before the 2nd underscore (including the 2nd underscore):
Get-ChildItem -Filter *.pdf | Rename-Item -NewName {$_.Name -replace '^[0-9_]+'}
This worked, but revealed there would be duplicate file names.
Wondering if there is some way a script can create a subfolder based on the filename (minus the extension)? So there would be 10K subfolders in my main folder. Each subfolder would just have the one file.
This should work. It creates a new folder for each item, then moves it, renaming it in the process.
gci | ? {!$_.PSIsContainer} | % {New-Item ".\$($_.BaseName)" -Type Directory; Move-Item $_ ".\$($_.BaseName)\$($_.Name -replace '^[0-9_]+')"}
Note that if there are two files with the same name, but different extensions, you'll see an error when trying to create the directory, but both files will wind up in the same folder.
Alternately, if you want something more readable to save in a script, this is functionally identical:
$files = Get-ChildItem | Where-Object {!$_.PSIsContainer}
foreach ($file in $files)
{
$pathName = ".\" + $file.BaseName
New-Item $pathName -Type Directory
$newFileName = $pathName + "\" + ($file.Name -replace '^[0-9_]+')
Move-Item $file $newFileName
}