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

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)

Related

Powershell: How to extract part a file name

I have a file that follows this naming convention: project.customer.version-number.zip
So for example i might have a file called: project.customer.1.1.889.zip
The "version-number" will change.
"project" and "customer" will stay the same. The zip file extension will always be the same.
I long way to get it I have found is to use multiple Split paths and then concatenate together to get the full version number. So i would have:
$1 = (Split-Path -Path $filePath -Leaf).Split(".")[2];
$2 = (Split-Path -Path $filePath -Leaf).Split(".")[3];
$3 = (Split-Path -Path $filePath -Leaf).Split(".")[4];
$version = $1 + "." + $2 "." + $3
Is there a quicker / better way to extract just the version number (i.e. the 1.1.889) if i don't know what it will be every time with powershell?
You can limit the number of item that split returns. In this case you just want to split it into Project, Customer, and Version. First we'll reduce it to just the file name with Split-Path -leaf, then we remove the file extension with -replace, and then we'll split the remaining file base name 3 times.
$project,$customer,$version = ((Split-Path -Path $filePath -Leaf) -replace '\.zip$').Split(".",3)

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

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 }

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
}

Rename file - delete all characters AFTER 2nd underscore

I need to replace the time\date stamp that's included in the filename after 2nd underscore (needs to be in the same format yyyyMMddHHmmss)
example file: 123456_123456_20190716163001.xml
sometimes the file in question gets created with an additional character which invalidates the file, in this case I need to replace this with the current timestamp.
example: 123456_123456_current Timestamp here.xml
The file should never exceed 32 characters(including extension)
I found a script but it deletes everything after the 1st underscore not the 2nd and I'm struggling to find a way to replace the text with the current timestamp.
Get-ChildItem c:\test -Filter 123456_123456*.xml | Foreach-Object -Process {
$NewName = [Regex]::Match($_.Name,"^[^_]*").Value + '.xml' $_ | Rename-Item -NewName $NewName
}
timestamp after 2nd underscore to be updated to the current timestamp if original file exceeds 32 characters
123456_123456_current Timestamp here.xml
this takes advantage of the way a [fileinfo] object is structured. the .BaseName is easy to get to & use .Split() on. then one can use -join to put it back into one basename & finally add the extension onto the basename.
# fake reading in a file info object
# in real life, use Get-ChildItem or Get-Item
$FileObject = [System.IO.FileInfo]'123456_123456_current Timestamp here.xml'
$NewName = -join (($FileObject.BaseName.Split('_')[0,1] -join '_'), $FileObject.Extension)
$NewName
output = 123456_123456.xml
Sticking with the regex theme, you can do the following:
$CurrentTime = Get-Date -Format 'yyyyMMddHHmmss'
$RegexReplace = "(.*?_.*?_).*(\..*)"
Get-ChildItem c:\test -Filter 123456_123456*.xml |
Rename-Item -NewName {$_.Name -replace $RegexReplace,"`${1}$CurrentTime`${2}"}
If duplicate file names are a concern, you can build in an increment to $CurrentTime.
$CurrentTime = Get-Date -Format 'yyyyMMddHHmmss'
$RegexReplace = "(.*?_.*?_).*(\..*)"
Get-ChildItem c:\test -Filter 123456_123456*.xml |
Rename-Item -NewName {
$NewName = $_.Name -replace $RegexReplace,"`${1}$CurrentTime`${2}"
if (test-path $NewName) {
$CurrentTime = [double]$CurrentTime + 1
$NewName = $_.Name -replace $RegexReplace,"`${1}$CurrentTime`${2}"
}
$NewName
}
Explanation:
$RegexReplace contains the regex expression that will need to be matched for the ideal rename operation to happen. The regex mechanisms are explained below:
.*?_.*?_: Matches a minimal number of characters (lazy matching) followed by an underscore and then another minimal number of characters followed by an underscore.
.*: Greedily matches any characters
\.: Literally matches the dot character (.).
(): The parentheses here represent capture groups with the first set being 1 and the second set being 2. These are later referenced as ${1} and ${2} in the -replace operation.
Since Rename-Item -NewName supports delayed script binding, we can just pipe Get-ChildItem output directly to it. The current pipeline object is $_.
The -replace operation uses the variable $CurrentTime, which must be expanded in order for a successful outcome. For that reason, we use double quotes around the replacement. Since we do not want capture groups ${1} and ${2} expanded, we backtick escape them.

Combine path, file strings and literals for path

Trying to combine a path, filename, and add some text along with a variable for Out-File log.
I've tried many alternatives unsuccessfully and need assistance;
FormattedDate = Get-Date -Format "yyyy-MM-dd HH:mm:ss"
$OldVersion = C:\Temp\TestFile.txt
$OldPath = (Get-Item $OldVersion).DirectoryName
$OldBaseName = (Get-Item $OldVersion).BaseName
ErrFile = Join-Path $OldPath OldBaseName
Out-File -FilePath "$ErrFile_$FormattedDate Error.txt"
Out-File -FilePath "$($OldPath)$($OldBaseName)_$($FormattedDate)_Error.txt"
...just two examples.
I've tried many other combinations and driving me crazy.
Basically I want it to be.
C:\Temp\TestFile_2017-08-24 16:51:36_Error.txt
Update:
I've tried both
$filename = '{0}_{1:s}_Error{2}' -f $basename, (Get-Date), $extension
I get _2017-08-25T13:02:17_Error.txt but no basename (TestFile).
$newpath = "${dirname}\${basename}_${date}_Error${extension}"
I get
A drive with the name '_2017-08-25 13' does not exists.
Can you also explain or provide a resource of what '{0}_{1:s}_Error{2}' and/or '{0}_{1:yyyy-MM-dd HH:mm:ss}_Error{2}' does?
Use the format operator (-f) for constructing the filename and Join-Path for building the path.
$oldpath = 'C:\Temp\TestFile.txt'
$basename = [IO.Path]::GetFileNameWithoutExtension($oldpath)
$extension = [IO.Path]::GetExtension($oldpath)
$filename = '{0}_{1:yyyy-MM-dd HH:mm:ss}_Error{2}' -f $basename, (Get-Date), $extension
$newpath = Join-Path ([IO.Path]::GetDirectoryName($oldpath)) $filename
Unless you must have the space in the date format you could simplify the format string by using the standard sortable format specifier (s) that will produce date strings like 2017-08-24T23:58:25 instead of 2017-08-24 23:58:25.
$filename = '{0}_{1:s}_Error{2}' -f $basename, (Get-Date), $extension
If you want to construct the path as a string with inline variables you need to make sure that the underscores in your file name are kept separate from the variable name. Because underscores are valid name components for variable names $var_ is the variable var_, not the variable var followed by a literal underscore. Use curly braces to ensure that variables and literal underscores don't get mixed up.
$oldpath = 'C:\Temp\TestFile.txt'
$date = (Get-Date).ToString('yyyy-MM-dd HH:mm:ss')
$dirname = [IO.Path]::GetDirectoryName($oldpath)
$basename = [IO.Path]::GetFileNameWithoutExtension($oldpath)
$extension = [IO.Path]::GetExtension($oldpath)
$newpath = "${dirname}\${basename}_${date}_Error${extension}"
Addendum: Your file names should not contain colons. Colons in Windows paths either terminate a drive name or separate the file name from the name of an alternate data stream. Your date format string should rather be something like yyyy-MM-dd HH-mm-ss or yyyy-MM-dd_HH-mm-ss to avoid this pitfall.