I'm new to powershell. I want to read names of each file in a folder for eg. 900_CA_2022.pdf, remove the _ from the filename and create a new text file which has name 900CA2022900_CA_2022.txt
Basically, I want to remove the _ from the extension-less file name, append the latter as-is, and use a new extension, .txt
Easiest way is use the same name of source file + .txt for create the text files without doing much damage to the source filenames.
E.g.
900_CA_2022.pdf -----> 900_CA_2022.pdf.txt
My alternative solution
Initial files
Script Code
$files = Get-ChildItem "./*.pdf" -Recurse -Force
$files | ForEach-Object{
New-Item -Path "$($_ | Split-Path)/$($_ | Split-Path -Leaf).txt" -Force
}
Result files
Update:
The stated requirements are unusual, but the next section provides a solution to address them.
The fact that you later accepted Joma's answer indicates that simply appending .txt to each input file name is what you actually needed; this is most easily accomplished as follows:
Get-ChildItem -Filter *.pdf | New-Item -Path { $_.FullName + '.txt' } -WhatIf
Note: The -WhatIf common parameter in the command above previews the operation. Remove -WhatIf once you're sure the operation will do what you want.
Important: All solutions below create the new files in the current directory. If needed, construct the target file path with an explicit directory path, using Join-Path, e.g.:Join-Path C:\target (($_.BaseName -replace '_') + $_.BaseName + '.txt')
To create new, empty files whose names should be derived from the input files, use New-Item:
Get-ChildItem -Filter *.pdf |
New-Item -Path { ($_.BaseName -replace '_') + $_.BaseName + '.txt' } -WhatIf
Note: If the target file exists, an error occurs. If you add -Force, the existing file is truncated instead - use with caution.
$_.BaseName is the input file's name without the extension.
-replace '_' removes all _ chars. from it.
To create new files whose names should be derived from the input files and fill them, use ForEach-Object:
Get-ChildItem -Filter *.pdf |
ForEach-Object {
# Construct the new file path.
$newFilePath = ($_.BaseName -replace '_') + $_.BaseName + '.txt'
# Create and fill the new file.
# `>` acts like Out-File. To control the encoding, use
# something like `| Out-File -Encoding utf8 $newFilePath` instead.
"content for $newFilePath" > $newFilePath
}
Note that > / Out-File and Set-Content (for string data) all quietly replace the contents of an existing target file.
Related
Here is my problem.
I have files in subdirectories and I need the file renamed as such:
{TOP DIRECTORY}_{SECOND DIRECTORY}_{FILENAME.ext}
The folder structure is like such:
AB12345
EVENT1
report.xls
EVENT2
SUBFOLDER
report2.xls
EVENT3
SUBFOLDER
SUBFOLDER2
report3.xls
With the expected results below. We do not care about the folders up under the second level, if present.
AB12345_EVENT1_report.xls
AB12345_EVENT2_report2.xls
AB12345_EVENT3_report3.xls
I have this base code that I have started:
Get-ChildItem C:\Files -Recurse | Rename-Item -NewName { $_.Directory.name+'_'+$_.Name}
Because you're doing something with renaming multiple files, I would suggest that you use the -WhatIf switch in the following code and check the output before running it - once you're happy with what the command is going to do, take the -WhatIf off:
Firstly, you only want to rename files, so you need to get your Get-ChildItem to do that - the -File switch will do that for you
You can use Select-Object to build the new file name for you. Select-Object can simply pick members of the object by name, however it can also take a hash-table as an argument. We provide a hash-table that contain a member name "NewName" which is defined by the following Expression
$_.FullName | Resolve-Path -Relative - this part gets the fullpath for the file but relative to the current location. It returns values such as:
.\AB12345\EVENT1\report.xls
.\AB12345\EVENT2\SUBFOLDER\report2.xls
Now we use -split with '\\' (double-backslash is required as an escape for a single backslash which is a special character when using split)
The split returns this:
{., AB12345, EVENT1, report.xls}
{., AB12345, EVENT2, SUBFOLDER...}
{., AB12345, EVENT3, SUBFOLDER...}
Nearly there, we just need the 2nd and 3rd elements as our new filename (the first and second directory names), use [1..2] to get these elements out and then add on the filename. Finally join these by underscore to get the new filename.
Pass the new filename along with the full path to rename-item, using -WhatIf to review what the command will do:
Get-ChildItem -Recurse -File | `
Select-Object `
#{ `
Name="NewName"; `
Expression={$($($_.FullName | Resolve-Path -Relative) -split '\\')[1..2]+$_.Name -join '_'}; `
} `
,FullName | `
%{ `
rename-item $_.FullName -NewName $_.NewName -Whatif `
}
N.B. backticks have been used to display the command in a more readable manner
I want to merge many CSV-files into one (a few hundred files) removing the header row of the added CSVs.
As the files sit in several subfolders I need to start from the root traversing all the subfolders and process all CSVs in there. Before merging I want to archive them with zip deleting old CSVs. The new merged CSV-file and the zip-archive should be named like their parent folder.
In case the Script is started again for the same folder none of already processed files should be damaged or removed accidentally.
I am not a Powershell guy so I have been copying pasting from several resources in the web and came up with the following solution (Sorry don't remember the resources feel free to put references in the comment if you know).
This patch-work code does the job but it doesn't feel very bulletproof. For now it is processing the CSV files in the subfolders only. Processing the files within the given $targDir as well would also be nice.
I am wondering if it could be more compact. Suggestions for improvement are appreciated.
$targDir = "\\Servername\folder\"; #path
Get-ChildItem "$targDir" -Recurse -Directory |
ForEach-Object { #walkinthrough all subfolder-paths
#
Set-Location -Path $_.FullName
#remove existing AllInOne.csv (targed name for a merged file) in case it has been left over from a previous execution.
$FileName = ".\AllInOne.csv"
if (Test-Path $FileName) {
Remove-Item $FileName
}
#remove existing AllInOne.csv (targed name for archived files) in case it has been left over from a previous execution.
$FileName = ".\AllInOne.zip"
if (Test-Path $FileName) {
Remove-Item $FileName
}
#compressing all csv files in the current path, temporarily named AllInOne.zip. Doing that for each file adding it to the archive (with -Update)
# I wonder if there is a more efficient way to do that.
dir $_.FullName | where { $_.Extension -eq ".csv"} | foreach { Compress-Archive $_.FullName -DestinationPath "AllInOne.zip" -Update}
##########################################################
# This code is basically merging all the CSV files
# skipping the header of added files
##########################################################
$getFirstLine = $true
get-childItem ".\*.csv" | foreach {
$filePath = $_
$lines = $lines = Get-Content $filePath
$linesToWrite = switch($getFirstLine) {
$true {$lines}
$false {$lines | Select -Skip 1}
}
$getFirstLine = $false
Add-Content ".\AllInOne.csv" $linesToWrite
# Output file is named AllInOne.csv temporarily - this is not a requirement
# It was simply easier for me to come up with this temp file in the first place (symptomatic for copy&paste).
}
#########################################################
#deleting old csv files
dir $_.FullName | where { $_.Extension -eq ".csv" -and $_ -notlike "AllInOne.csv"} | foreach { Remove-Item $_.FullName}
# Temporarily rename AllinOne files with parent folder name
Get-ChildItem -Path $_.FullName -Filter *.csv | Rename-Item -NewName {$_.Basename.Replace("AllInOne",$_.Directory.Name) + $_.extension}
Get-ChildItem -Path $_.FullName -Filter *.zip | Rename-Item -NewName {$_.Basename.Replace("AllInOne",$_.Directory.Name) + $_.extension}
}
I have been executing it in the Powershell ISE. The Script is for a house keeping only, executed casually and not on a regular base - so performance doesn't matter so much.
I prefer to stick with a script that doesn't depend on additional libraries if possible (e.g. for Zip).
It may not be bulletproof, but I have seen worse cobbled together scripts. It'll definitely do the job you want it to, but here are some small changes that will make it a bit shorter and harder to break.
Since all your files are CSVs and all would have the same headers, you can use Import-CSV to compile all of the files into an array. You won't have to worry about stripping the headers or accidentally removing a row.
Get-ChildItem "*.csv" | Foreach-Object {
$csvArray += Import-CSV $_
}
Then you can just use Export-CSV -Path $_.FullName -NoTypeInformation to output it all in to a new CSV file.
To have it check the root folder and all the subfolders, I would throw all of the lines in the main ForEach loop into a function and then call it once for the root folder and keep the existing loop for all the subfolders.
function CompileCompressCSV {
param (
[string] $Path
)
# Code from inside the ForEach Loop
}
# Main Script
CompileCompressCSV -Path $targetDir
Get-ChildItem -Path $targetDir -Recurse -Directory | ForEach-Object {
CompileCompressCSV -Path $_.FullName
}
This is more of a stylistic choice, but I would do the steps of this script in a slightly different order:
Get Parent Folder Name
Remove old compiled CSVs and ZIPs
Compile CSVs into an array and output with Parent Folder Name
ZIP together CSVs into a file with the Parent Folder Name
Remove all CSV files
Personally, I'd rather name the created files properly the first time instead of having to go back and rename them unless there is absolutely no way around it. That doesn't seem the case for your situation so you should be able to create them with the right name on the first go.
Ultimately, I need a solid PowerShell script that will take a folder with several hundred video files, import the existing file names into the program, lookup the new file name in a CSV, and rename it. The old filename is simply (ie. File1.mp4, File2.mp4, etc.) I would like to appended a date to the front of the file in the format of (YYYY-MM-DD).
For testing, I created a folder on my desktop with (10) text files, each with a unique file name.
My CSV file appears as follows:
Image of CSV
The "newfilename" column, was created by using the Concatenate command in Excel.
`(=CONCATENATE(TEXT(A2, "yyyy-mm-dd")," ", B2)`
As much as I would just like PowerShell to handle everything, I feel using Excel for most of this might be the best way.
In my testing, everything was in one folder. However, at work, I will have video files on one drive, and the script will have to be in a folder on my desktop. Because I am in a corporate network, I need a special batch file to run my scripts, which is nothing new. I just modify the script name, and away it goes!
So what commands do I need to do in order to have the script separate from the video files AND the CSV file?
Here is the code that I have so far. Everything works when it's in one folder.
PS C:\Users\ceran\Desktop\Rename Project> Import-Csv -Path .\MyFileList.csv | ForEach-Object {
>> $Src = Join-Path -Path $TargetDir -ChildPath $_.filename
>> $Dst = Join-Path -Path $TargetDir -ChildPath $_.newfilename
>> Rename-Item -Path $Src -NewName $Dst
>> }
Thanks in advance for the help!
Chris
I'm not sure what the date column is in your Excel file and if you want to rename all files in the folder, but if that is the case, you don't need a csv file at all and can do this:
$sourceFolder = 'X:\Path\to\the\video\files' # change this to the real path
Get-ChildItem -Path $sourceFolder -Filter '*.mp4' -File | # iterate through the files in the folder
Where-Object {$_.Name -notmatch '^\d{4}-\d{2}-\d{2}'} | # don't rename files that already start with the date
Rename-Item -NewName { '{0:yyyy-MM-dd} {1}' -f $_.LastWriteTime, $_.Name } -WhatIf
This uses parameter -Filter '*.mp4', to get only files with an .mp4 extension. For the files in your testfolder (Desktop\Rename Project), change this to -Filter '*.txt'.
If you want all files renamed, no matter what the extension, simply remove the Filter from the cmdlet.
Because of the -WhatIf switch, no file is actually renamed and the code just shows in the console what would happen. Once satisfied that this is OK, remove the -WhatIf
Hope that helps.
$targetdir="C:\path\to\where\our\file\directory\is"
$pathtocsv="c:\path\to\csv.csv"
Import-Csv -Path $pathtocsv | ForEach-Object {
$Src = Join-Path -Path $TargetDir -ChildPath $_.filename
$Dst = Join-Path -Path $TargetDir -ChildPath $_.newfilename
Rename-Item -Path $Src -NewName $Dst
}
Why would this not work in any situation?
By the way, if the csv had the columns path and newname, it could be piped directly to rename-item:
path,newname
file.txt,file2.txt
import-csv ren.csv | Rename-Item -whatif
What if: Performing the operation "Rename File" on target "Item: /Users/js/foo/file.txt Destination: /Users/js/foo/file2.txt".
Have 2500 pdf files in a folder with fixed length name with a delimiter " _ "
200422028240000148_8393929.pdf
742022028240000014_4366273.pdf
Need to rename with first name available before the delimiter
200422028240000148.pdf
742022028240000014.pdf
How can i do with CMD as well powershell without currupting file and also cant use external utility or tool this being production server
This is really basic PowerShell.
Please, take some time to lookup the workings of Get-ChildItem and Rename-Item to figure out how below code works:
Get-ChildItem -Path 'X:\TheFolder' -Filter '*.pdf' -File | Rename-Item -NewName {
($_.BaseName -split '_')[0] + $_.Extension
}
I have found code on here which does what I need it to it
$files = gci -filter "*.txt" |select fullname
foreach ($file in $files) {
$filename = $file.fullname
$newFilename = $filename.Replace(".", " ")
rename-item $filename $newFilename
}
I require the code to change the file name and keep the file extension. The code above replaces "." with " ". I can code it to rename the file again to add the extension back. Just wanted to see if there was a better way to do it.
Cheers
Change 'old' & 'new' to whatever you want
Remove the -Whatif to apply the command.
Get-ChildItem -Path C:\temp\files -Filter *.txt |
Rename-Item -NewName {$_.Basename.Replace("old","new") + $_.extension} -WhatIf -Verbose
If you are already in the directory where you want to rename all the files regardless of their extensions, this should help.
Dir | foreach {$i=1}{Rename-Item $_ -NewName (('{0:D5}'+$_.Extension) -f $i++)}
Where the first block after foreach works as an initializing block(if you skip it, tha value will start from default value 0), {0:D5} stands for first parameter with 0 padded up to 5 spaces. You could also write it like {0:00000} but that D is just shorter for decimal values. Needless to say, if you don't want any padding just use {0}.
P.S.: You will probably need to add the -recurse parameter (after Dir if you want to rename files in any subfolder of the directory as well. You can also use a -whatif parameter at the end of the code to see the preview of the changes.