replacing files names with split output - powershell

I am trying to use PowerShell to read filenames from a dir;
then within a for loop:
split names using a delimiter; store desired output in a new variable. Now I want to replace the original filenames in the directory with this new variable. So far I have gathered the following with the expected outputs shown:
$files = Get-ChildItem -Path C:\Test
write-output $files
Directory: C:\Test
1_N04532L_LEFT.JPG
2_N04532R_RIGHT.JPG
code continues
foreach ($file in $files)
{
$nameArray = $file -split "_"
$newName = $nameArray[1]
write-output $newName
}
N04532L
N04532R
Any Ideas on how to accomplish this. I am not a programmer and there is lots of data on this, but it's not working for me.

As both commenters already explained, there is the Rename-Item cmdlet for renaming files.
Since this cmdlet can take a scriptblock in its NewName parameter, you can use that to create a new filename.
# adding switch -File makes sure you do not also try to rename subfolders
$files = Get-ChildItem -Path 'C:\Test' -File
foreach ($file in $files) {
$file | Rename-Item -NewName { '{0}{1}' -f ($file.BaseName -split '_')[1], $file.Extension }
}
You can shorten this by piping the results from Get-ChildItem trhough one-by-one to the Rename-Item cmdlet.
Because we're piping the FileInfo objects here, we can make use of the $_ automatic variable
# enclose the Get-ChildItem cmd in brackets so this will enumerate the files to completion
# before passing them on to te Rename-Item cmdlet.
# if you don't, files you already have renamed could be picked up and processed again..
(Get-ChildItem -Path 'C:\Test' -File) |
Rename-Item -NewName { '{0}{1}' -f ($_.BaseName -split '_')[1], $_.Extension }
Note: when renaming files, you can always run into naming collisions, upon which you will receive an exception

Related

Recursive search for string within files on a drive

I am trying to do a search for specific file within a directory with the following command:
gci -recurse -path "E:\" | select-string "searchContent" | select path
doing so gave me an insufficient memory error. I have seen other posts recommending piping it into foreach-object, but I couldn't figure out how to get it to work in my scenario. Any assistance appreciated!
When reading the file as a whole (single multiline string), your seach can be much faster than by testing line-by-line.
Also, you could speed up significantly if you could use a filename pattern as filter for the Get-ChildItem cmdlet. If you for instance only want to search through .txt files, add -Filter '*.txt'.
In any case, append switch -File so Get-ChildItem won't try to pass DirectoryInfo objects to the rest of the code.
Try:
# since we use regular expression operator `-match`, escape the word or phrase you need to find
$searchContent = [regex]::Escape('whateveryouarelookingfor')
$result = Get-ChildItem -Path 'E:\' -Recurse -File | ForEach-Object {
if ((Get-Content -Path $_.FullName -Raw) -match $searchContent) { $_.FullName }
}
A bit faster than using ForEach-Object{..} would be to use a foreach() instead (skipping processing time needed to pipe results)
# since we use regular expression operator `-match`, escape the word or phrase you need to find
$searchContent = [regex]::Escape('whateveryouarelookingfor')
$result = foreach ($file in (Get-ChildItem -Path 'E:\' -Recurse -File)) {
if ((Get-Content -Path $file.FullName -Raw) -match $searchContent) { $file.FullName }
}
Now you can display the full path and filenames on screen
$result
and save it as text file on disk
$result | Set-Content -Path ('X:\FilesContaining_{0}.txt' -f $searchContent)
Just assign it to a variable, and then have a foreach loop that assigns each one to another variable.
$files = gci -recurse -path "E:\"
foreach ($fileName in $files)
{
if ($fileName.Name -like "*searchContent*")
{
write-host $fileName.Name
}
}
I feel this should consume less memory. Can't tell for sure but you can let me know. The concept is the same but using [System.IO.StreamReader].
Note: This will keep on looking for all files it can find, if you need the loop to stop at first finding then a new condition should be added.
foreach($file in Get-ChildItem -Recurse -path "E:\" -File)
{
$reader = [System.IO.StreamReader]::new($file.FullName)
while(-not $reader.EndOfStream)
{
if($reader.ReadLine() -match 'searchContent')
{
$file.FullName
break
}
}
$reader.Dispose()
}

Renaming multiple files with different names

I am a programmer by no means and am brand new to using powershell, but have been tasked with setting up some batch export processes for daily files we FTP. I need to come up with a script that will take changing file names and change them within the same directory to new names;
Example: files will come in as below (YYYYMMDD will be the changing variable)
YYYYMMDD_Share_Support.txt
YYYYMMDD_Person_Support.txt
We need them to be stripped from the above to:
Share.txt
Person.txt
so on and so forth.
I have found ways to make this work, but only on an as needed basis for one file at a time with specific names, not names that will change daily.
So far I am using:
Get-ChildItem -Filter *.txt
Dir | %{Rename-Item $_ -NewName ("NEWFILENAME.txt" -f $nr++)}
You could use the regex -replace operator inside a pipeline-bound scriptblock:
$files = Get-ChildItem -filter *.txt
$files |Rename-Item -NewName { $_.Name -replace '^\d{8}_(.*)_Support\.txt$', '$1.txt' }
As suggested by TheIncorrigible1, if you know the relative position of the word you need, you can also use -split:
$files |Rename-Item -NewName {'{0}.txt' -f ($_.Name -split '_')[-2]} # grab 2nd last word
How about:
dir *.txt |
rename-item -newname { $null,$base,$null = $_.basename -split '_'; "$base.txt" } -whatif
Probably a longer version of the answer. An alternative mentioned by #TheIncorrigible1
$logDir = "D:\Satish\TestFolders"
cd $logDir
$files = ls
foreach ($file in $files){
$fileSplit=($file.ToString()).split("_")
ren $file "$($fileSplit[1]).txt"
}
And for Share.txt to YYYYMMDD_Share_Support.txt
$logDir = "D:\Satish\TestFolders"
cd $logDir
$files = ls
$date = Get-Date -Format "yyyyMMdd"
foreach ($file in $files){
$fileSplit=($file.ToString()).split(".")
ren $file "$($date)_$($fileSplit[0])_Support.txt"
}

PowerShell - Loop through files and rename

newbie here. I am trying to write a PowerShell script to:
loop through all files in directory
List item
Get all .pdf files ONLY
Rename them-the file names are long - over 30 chars
-They contain 2 numbers which I need to extract
-Example:
Cumulative Update 11 for Microsoft Dynamics NAV 2018 (Build 25480).pdf ->
RESULT : = 18CU11.pdf
I tried examples from bunch of sites and I can't seem to even loop successfully.
Either get an error - that path doesn't exist or that can't rename files as somehow loop gets a filepath and that I can't rename
Get-ChildItem "C:\Users\******\Desktop\PowerShell Practice" -Filter *.pdf | #create list of files
ForEach-Object{
$oldname = $_.FullName;
$newname = $_.FullName.Remove(0,17);
#$newname = $_.FullName.Insert(0,"CU")
Rename-Item $oldname $newname;
$oldname;
$newname; #for testing
}
That's just latest attempt, but any other ways of doing it will be fine - as long as it does the job.
Try this logic:
[string]$rootPathForFiles = Join-Path -Path $env:USERPROFILE -ChildPath 'Desktop\PowerShell Practice'
[string[]]$listOfFilesToRename = Get-ChildItem -Path $rootPathForFiles -Filter '*.PDF' | Select-Object -ExpandProperty FullName
$listOfFilesToRename | ForEach-Object {
#get the filename wihtout the directory
[string]$newName = Split-Path -Path $_ -Leaf
#use regex replace to apply the new format
$newName = $newName -replace '^Cumulative Update (\d+) .*NAV 20(\d+).*$', '$2CU$1.pdf' # Assumes a certain format; if the update doesn't match this expectation the original filename is maintained
#Perform the rename
Write-Verbose "Renaming '$_' to '$newName'" -Verbose #added the verbose switch here so you'll see the output without worrying about the verbose preference
Rename-Item -Path $_ -NewName $newName
}
Check the Help for Rename-Item. The Parameter -NewName requires the name of the file only, not the full path.
Try out this:
Get-ChildItem "C:\Users\******\Desktop\PowerShell Practice-Filter" -Filter *.pdf | #create list of files
ForEach-Object{
$oldname = $_.FullName
$newname = $_.Name.Remove(0,17)
Rename-Item -Path $oldname -NewName $newname
$oldname
$newname #for testing
}
Please try this
Get-ChildItem -Path "C:\Users\******\Desktop\PowerShell Practice-Filter" -Filter *.pdf | Rename-Item -NewName $newname

PowerShell bulk replacing a specific character and rename file extension

I need to bulk rename files in a file share that
contain a specific character, namely a tilde ~ and
have the file extension in capital letters or none at all.
The goal would be to replace the tilde with a simple -, keep the file extension, if there is one, but transform it into lowercase letters.
I've had success with the first part of the script that finds the files
$PATH = "\\<Fileservername>\<Folder>\"
$pattern = "*~*"
Get-ChildItem $PATH -Recurse | where {$_.Name -like $pattern}
What I'm struggling with is the second part of the script the renaming.
I've found two topics here:
Powershell renaming a specific Character
PowerShell Regex Bulk Replace Filenames
I haven't been able to adapt those solutions to my need plus there may be additional steps to take in order to convert the given file name from capital letters to lowercase letters or skip this if the file has no file extension.
An example would be to rename ACUJLH~H to ACUJLH-H and KYA3BM~Q.PDF to KYA3BM-Q.pdf.
Here's my contribution. I have added the -File switch to the Get-ChildItem cmdlet so it will look for files only and will not try and handle directory names.
Also, I have changed the replace pattern to ~+ so files that have repeating tildes will be replaced with a single - character. (KYA3BM~~~~Q.PDF becomes KYA3BM-Q.pdf)
$path = "D:\Code\PowerShell\StackOverflow"
$pattern = "*~*"
Get-ChildItem $path -Recurse -File | Where-Object {$_.Name -like $pattern} |
ForEach-Object {
$directory = $_.DirectoryName # or [System.IO.Path]::GetDirectoryName($_.FullName) or use Split-Path $_.FullName -Parent
$filename = $_.BaseName -replace '~+', '-' # or [System.IO.Path]::GetFileNameWithoutExtension($_.Name) -replace '~+', '-'
$extension = $_.Extension # or [System.IO.Path]::GetExtension($_.Name)
if (![string]::IsNullOrEmpty($extension)) { $filename += $extension.ToLower() }
$newname = Join-Path -Path $directory -ChildPath $filename
Rename-Item -LiteralPath $_.FullName -NewName $newName -Force
}
You will need to filter files those meets your criteria. Then using ForEach-Object compare for extensions and build new file names for every found item. Finally, using Rename-Item cmdlet you make the change.
$PATH = '\\<Fileservername>\<Folder>\'
Get-ChildItem $PATH -Recurse -Include '*~*' | ForEach-Object {
[String]$Extension = [System.IO.Path]::GetExtension($_)
[String]$NewFileName = [System.IO.Path]::GetFileNameWithoutExtension($_.Name) -replace '~','-'
if ($Extension){ $NewFileName += $Extension.ToLower() }
Rename-Item $_.FullName $(Join-Path ([System.IO.Path]::GetDirectoryName($_)) $NewFileName) -Force
}

How to rename files using a list file?

I have 100s of files that I would like to rename in a specific manner according to a correlated list. Rather than repeating the same cmdlets, as below, I would like to point the cmdlet to a couple of list files (below) containing the old and new names, say old.txt and new.txt. How do I do this using PowerShell?
Example:
Rename-Item -Path "E:\MyFolder\old1.pdf" -NewName new1.pdf
Rename-Item -Path "E:\MyFolder\old2.tif" -NewName new2.pdf
Rename-Item -Path "E:\MyFolder\old3.pdf" -NewName new3.pdf
List Files:
old.txt =
old1.pdf
old2.tif
old3.pdf
new.txt =
new1.pdf
new2.tif
new3.pdf
To avoid misalignment between the entries in old.txt and new.txt files, you could also put the files into a CSV file:
renames.csv =
old1.pdf,new1.pdf
old2.tif,new2.tif
old3.pdf,new3.pdf
Now you can use Import-Csv to import your "translation sets":
$Renames = Import-Csv '.\renames.csv' -Header "OldName","NewName"
foreach($File in $Renames) {
Rename-Item $File.OldName $File.NewName
}
Simply read your two files into two lists and process them like this:
$old = Get-Content 'old.txt'
$new = Get-Content 'new.txt'
Set-Location '$:\MyFolder'
for ($i = 0; $i -lt $old.Count; $i++) {
Rename-Item $old[$i] $new[$i]
}
So just for yucks, here is the one-liner version. It does depend on you being in the C:\MyFolder dir when you run it but that could easily be changed to a variable:
Get-Content new.txt -PipelineVariable NewName |
Foreach {$oldNames = Get-Content old.txt;$i=0} {Get-ChildItem $oldNames[$i++]} |
Rename-Item -NewName {$NewName} -WhatIf