Rename files in a specific way. Target nth string between symbols - powershell

Apologies in advance for a bit vague question (no coding progress).
I have files (they can be .csv but dont have .csv, but that I can add via script easy). The files' name is something like this:
TRD_123456789_ABC123456789_YYMMDD_HHMMSS_12345678_12345_blabla_blabla_blabla_blabla
Now I would need a script that renames the file in a way that it keeps original name except:
It would cut off the ending (blabla_blabla_blabla_blabla) part.
Changes the 12345 before blabla to random 5 characters (can be numbers too)
Change timestamp of HHMMSS to current Hours, minutes, seconds.
In regards to point 3. I think that I can insert arbitary powershell script to any string in " " queotes. So when renaming the files, I was thinking I could just add
Rename-Item -NewName {... + $(get-date -f hhmmss) + ...}
However, I am lost how to write renaming script that renames parts between 4th & 5th _ symbol. And removes string part after 7th _ symbol.
Can somebody help me with the script or help me how to in powershell script target string between Nth Symbols?
Kind Regards,
Kamil.

Split the string on _:
$string = 'TRD_123456789_ABC123456789_YYMMDD_HHMMSS_12345678_12345_blabla_blabla_blabla_blabla'
$parts = $string -split '_'
Then discard all but the first 6 substrings (eg. drop the 12345 part and anything thereafter):
$parts = $parts[0..5]
Now add your random 5-digit number:
$parts = #($parts; '{0:D5}' -f $(Get-Random -Maximum 100000))
Update the string at index 4 (the HHMMSS string):
$parts[4] = Get-Date -Format 'HHmmss'
And finally join all the substrings together with _ again:
$newString = $parts -join '_'
Putting it all together, you could write a nice little helper function:
function Get-NewName {
param(
[string]$Name
)
# split and discard
$parts = $Name -split '_' |Select -First 6
# add random number
$parts = #($parts; '{0:D5}' -f $(Get-Random -Maximum 100000))
# update timestamp
$parts[4] = Get-Date -Format 'HHmmss'
# return new string
return $parts -join '_'
}
And then do:
Get-ChildItem -File -Filter TRD_* |Rename-Item -NewName { Get-NewName $_.Name }

Related

PowerShell insert a string into a filename, just before the extension

A general thing that I need to do a lot with files is to create/reference a backup version of a file. e.g. I might want to have a date/time stamp version of a file so that I can reference both the original report and the backup version throughout a script:
$now = $(Get-Date -format "yyyy-MM-dd__HH-mm")
$ReportName = "MyReport-en.csv"
$Backups = "D:\Backups\Reports"
I found that using -join or + always inserted a space before the date/time stamp:
$ReportBackup = "$Backups\$($ReportName -split ".csv")" + "_$now.csv"
$ReportBackup = "$Backups\$($ReportName -split ".csv")" -join "_$now.csv"
I found a way to do this, but it looks feels inefficient with the triple $ and duplication of the .csv
$ReportBackup = "$Backups\$($($ReportName -split ".csv")[0])$_now.csv"
which results in:
$ReportBackup => D:\Backups\Reports\MyReport-en_2022-04-15__07-55.csv
Can you think of simpler/cleaner way to achieve the generic goal of inserting a piece of text before the extension, without the triple $ or duplication of the extension? ("Use a $name = "MyReport-en"" is not so useful because often I am reading a file object and get the name complete with extension.
$now = Get-Date -Format "yyyy-MM-dd__HH-mm"
$reportName = "MyReport-en.csv"
$backups = "D:\Backups\Reports"
$reportBackup = Join-Path $backups $reportName.Replace(".csv","_$now.csv")
$reportBackup
P.S.
There is no risk in using .Replace(), if you know how it works.
This method is case sensitive and replaces all occurrences. In this particular case, we know exactly the name in advance, so we can use this method safely.
The name "My.csvReport-en.csv" is an nonsense, but if the problem explicitly referred to the universal solution "any-name.ext":
$reportName = "My.aSd_Report-en.aSD"
$backups = "D:\Backups\Reports"
$reportBackup = Join-Path $backups ($reportName -replace "(?=\.[^.]+$)", (Get-Date -Format "_yyyy-MM-dd__HH-mm"))
$reportBackup
If you have obtained the report file as FileInfo object by perhaps using Get-Item or Get-ChildItem, you'll find that object has convenient properties you can use to create a new filename with the date included:
# assume $ReportName is a FileInfo object
$Backups = "D:\Backups\Reports"
# I'm creating a new filename using the '-f' Format operator
$NewName = '{0}_{1:yyyy-MM-dd__HH-mm}{2}' -f $ReportName.BaseName, (Get-Date), $ReportName.Extension
$ReportBackup = Join-Path -Path $Backups -ChildPath $NewName
If however $ReportName is just a string that only holds the filename, you can do:
$ReportName = "MyReport-en.csv"
$Backups = "D:\Backups\Reports"
$baseName = [System.IO.Path]::GetFileNameWithoutExtension($ReportName)
$extension = [System.IO.Path]::GetExtension($ReportName)
$NewName = '{0}_{1:yyyy-MM-dd__HH-mm}{2}' -f $baseName, (Get-Date), $extension
$ReportBackup = Join-Path -Path $Backups -ChildPath $NewName
P.S. It is always risky to simply use .Replace() on a filename because that doesn't allow you to anchor the substring to replace, which is needed, because that substring may very well also be part of the name itself.
Also, the string .Replace() method works case-sensitive.
This means that
'My.csvReport-en.csv'.Replace(".csv", "_$(Get-Date -Format 'yyyy-MM-dd__HH-mm').csv")
would fail (returns My_2022-04-15__13-36.csvReport-en_2022-04-15__13-36.csv)
and
'MyReport-en.CSV'.Replace(".csv", "$(Get-Date -Format 'yyyy-MM-dd__HH-mm').csv")
would simply not replace anything because it cannot find the uppercase .CSV
..
If you really want to do this by replacing the extension into a date+extension, go for a more complex case-insensitive regex -replace like:
$ReportName -replace '^(.+)(\.[^.]+)$', "`$1_$(Get-Date -Format 'yyyy-MM-dd__HH-mm')`$2"
Regex details:
^ Assert position at the beginning of the string
( Match the regular expression below and capture its match into backreference number 1
. Match any single character that is not a line break character
+ Between one and unlimited times, as many times as possible, giving back as needed (greedy)
)
( Match the regular expression below and capture its match into backreference number 2
\. Match the character “.” literally
[^.] Match any character that is NOT a “.”
+ Between one and unlimited times, as many times as possible, giving back as needed (greedy)
)
$ Assert position at the end of the string (or before the line break at the end of the string, if any)

How to reformat the date on files in bulk using powershell

I need to format the file name from ...
2639423_3_30_56 PM_9_4_2020.txt
... to ...
2639423-15-30-56-09-04-2020.txt
i.e. Need to change date in Military time format and replace '_' with '-', Also append with “0” for single digit months and single digit days
Please advise I need to perform this in powershell & need to perform this in bulk.
Start by splitting the file name into two parts - the prefix, which remains the same, and the timestamp, which you want to re-format:
$basename = '2639423_3_30_56 PM_9_4_2020'
$prefix,$timestamp = $basename -split '_',2
Next, parse the timestamp according to it's specific format:
$inputFormat = 'h_mm_ss tt_d_M_yyyy'
$parsedDateTime = [datetime]::ParseExact($timestamp,$inputFormat,$null)
Finally convert the parsed [datetime] object back to a string with the desired output format, and then join the prefix and (updated) timestamp together again:
$outputFormat = 'HH-mm-ss-dd-MM-yyyy'
$timestamp = $parsedDateTime.ToString($outputFormat)
# or
$timestamp = Get-Date $parsedDateTime -Format $outputFormat
$newFileName = $prefix,$timestamp -join '-'
# 2639423-15-30-56-09-04-2020
To rename the files in bulk, pipe the files to Rename-Item and use the parameter binder to generate the new name of each file based on the existing name:
Get-ChildItem -Path .\folder\with\files -Filter *.txt |Rename-Item -NewName {
$prefix,$timestamp = $_.BaseName -split '_',2
$parsedDateTime = [datetime]::ParseExact($timestamp, 'h_mm_ss tt_d_M_yyyy', $null)
$timestamp = $parsedDateTime.ToString('HH-mm-ss-dd-MM-yyyy')
$newBaseName = $prefix,$timestamp -join '-'
$newBaseName + $_.Extension
}

Parse out date from filename and sort by date

I have a series of files named as such in a folder:
- myFile201801010703.file
I'm trying to parse out the yyyymmdd portion of each filename in the folder and sort them based on the date into an array.
So if I had the following files:
myFile201801200000.file (01/20/2018)
myFile201800100000.file (01/01/2018)
myFile201801100000.file (01/10/2018)
It would sort them into an array as such:
myFile201800100000.file (01/01/2018)
myFile201801100000.file (01/10/2018)
myFile201801200000.file (01/20/2018)
I have a process that works for file with timestamps included in the name, though have been unable to tweak it for work with only a date:
# RegEx pattern to parse the timestamps
$Pattern = '(\d{4})(\d{2})(\d{2})*\' + ".fileExtension"
$FilesList = New-Object System.Collections.ArrayList
$Temp = New-Object System.Collections.ArrayList
Get-ChildItem $SourceFolder | ForEach {
if ($_.Name -match $Pattern) {
Write-Verbose "Add $($_.Name)" -Verbose
$Date = $Matches[2],$Matches[3],$Matches[1] -join '/'
$Time = $Matches[4..6] -join ':'
[void]$Temp.Add(
(New-Object PSObject -Property #{
Date = [datetime]"$($Date) $($Time)" #If I comment out $($Time)it doesn't work.
File = $_
}
))
}
}
} catch {
Write-Host "`n*** $Error ***`n"
}
# Sort the files by the parsed timestamp and add to $FilesList
$FilesList.AddRange(#($Temp | Sort Date | Select -Expand File))
# Clear out the temp collection
$Temp.Clear()
The two lines in particular that I think might be culprit are:
$Time = $Matches[4..6] -join ':' Since I'm not parsing any time
Date = [datetime]"$($Date) $($Time)" Again, no time is parsed. Can't change the type to date either it seems?
With this format:
myFileYYYYMMddHHmm.file
the individual parts of the date and time is already arranged from largest (the year) to smallest (the minute) - this makes the string sortable!
Only thing we need to do is grab the last 12 digits of the file name before the extension:
$SortedArray = Get-ChildItem *.file |Sort-Object {$_.BaseName -replace '^.*(\d{12})$','$1'}
The regex pattern used:
^.*(\d{12})$
Can be broken down as follows:
^ # start of string
.* # any character, 0 or more times
( # capture group
\d{12} # any digit, 12 times
) # end of capture group
$ # end of string
The regex engine will expand $1 in the substitution string to "capture group #1", ie. the 12 digits we picked up at the end.

Using Rename-Item to make numeric identifiers & names consistent

I have several thousand file names delimited as such:
Last, First-000000-Title-MonYYYY.pdf
Probem 1: Some files conform to the 6-digit convention while others need leading zeroes for consistency.
Problem 2: Some names are entered with dashes (which are, problematically, delimiters) which need to be joined as such:
Last-Last, First > LastLast, First
I'm able to perform a simple Rename-Item function for each file but have not been able to create a broader Get-ChildItem function taking into account the several iterations of file names to generate a consistent output.
Apologies for the entry-level question but I cannot seem to coherently draw together the required functions.
Based on your explanations:
Set-Location -Path "C:\path" # replace this with actual path to where the files are
$cFiles = Get-ChildItem -Filter "*.pdf" # Getting all PDFs in the folder
foreach ($oFile in $cFiles) {
$sName = $oFile.Name
# This regex captures 1-5 digits number between two dashes.
$sPattern = '(?:(?<=-))(\d{1,5})(?:(?=-))'
if ($sName -match $sPattern) {
# Extracting number.
[UInt32]$iNumber = $sName -replace (".*" + $sPattern + ".*"), '$1'
# Padding number with zeros.
$sNumber = "{0:D6}" -f $iNumber
# Updating filename string.
$sName = $sName -replace $sPattern, $sNumber
} else {
# This regex captures 6 digits number between two dashes.
$sPattern = '.*-(\d{6})-.*'
# Extracting number.
$sNumber = $sName -replace $sPattern, '$1'
}
# Splitting filename string on 6 digits number.
$cParts = $sName -split $sNumber
# Removing dashes from first/last names and re-assembling filename string.
$sName = ($cParts[0] -replace '-') + '-' + $sNumber + $cParts[1]
Rename-Item -Path $oFile.Name -NewName $sName
}
Tested on the following sample:
Last, First-000000-Title-Jan1900.pdf
One-Two, Three-123-Title-Feb2000.pdf
Four, Five-Six-456-Title-Mar2010.pdf
Seven-Eight, Nine-Ten-7890-Title-Sep2012.pdf
May not work if there are more complicated cases.

Reversing a file name based on delimiter then truncating part

I need to rename many hundreds of files to follow a new naming convention, but I'm having awful trouble. This really needs to be scripted in powershell or VBS so we can automate the task in a regular basis.
Original File Name
Monday,England.txt
New File Name
EnglanMo
Convention Rules:
The file name is reversed around the delimiter (,) to England,Monday and then truncated to 6/2 char
Englan,Mo
The Delimiter is then removed
englanmo.txt
Say we had Wednesday,Spain.txt spain being 5 char, this is not subject to any reduction
SpainWe.txt
All the txt files can be accessed in one directory, or from a CSV, whatever is easiest.
Without having the exact details of your file paths, where it'll run, etc. you'll have to adapt this to point at the appropriate path(s).
$s= "Monday,England.txt";
#$s = "Wednesday,Spain.txt";
$nameparts = $s.split(".")[0].split(",");
if ($nameparts[1].length -gt 6) {
$newname = $nameparts[1].substring(0,6);
} else {
$newname = $nameparts[1];
}
if ($nameparts[0].length -gt 2) {
$newname += $nameparts[0].substring(0,2);
} else {
$newname += $nameparts[0];
}
$newname = $newname.toLower() + "."+ $s.split(".")[1];
$newname;
get-item $s |rename-item -NewName $newname;
I'm certain this isn't the most efficient/elegant way to do this, but it works with both of your test cases.
Use Get-ChildItem to grab the files, then on files that match your criteria, use regular expressions to capture the first two characters of the day of the week and the first six characters of the location, then use those captures to create a new filename. This is my best guess. Use -WhatIf on the Move-Item cmdlet until you get the regular expression and the destination path correct.
Get-ChildItem C:\Path\To\Files *.txt |
Where-Object { $_.BaseName -matches '^([^,]{2})[^,]*,(.{1,6})' } |
Move-Item -WhatIf -Destination {
$newFileName = '{0}{1}.txt' -f $matches[1],$matches[2]
Join-Path C:\Path\To\Files $newFileName.ToLower()
}
I think you should be able to achieve this by splitting the string into arrays in powershell and then recording the array to get your reverse.
For example:
$fileNameExtension = "Monday,England.txt";
$fileName = $fileNameExtension.split("."); // gets you an array [Monday,England][txt]
$fileparts = $fileName.split(","); // gets you an array [Monday][England]
//Create the new substring parts, notice you can now pull items from the array in any order you need,
//You will need to check the length before using substringing
$part1 = $fileparts[1].substring(0,5);
$part2 = $fileparts[0].substring(0,2);
//Now construct the new file name by rebuilding the string
$newfileName = $part1 + $part2 + “.” + $fileName[1];