Insert creation date into filename if there isn't one already

Hi, I have do do some bulk renaming of filenames.
The filenames should have a certain pattern like
6442.1_15_07_06_Aufgabe altes Schulhaus

But sometimes the timestamp is missing in the filename. So I have to find them and insert the formatted timestamp when this file was created, i.e.
6442.1_Aufgabe altes Schulhaus - kopie

I came up with this powershell trial:

Get-ChildItem -File -Recurse | Rename-Item -PassThru -NewName { $_.Name -replace '([0-9]{4}\.[0-9]{1,2}-?([0-9]{1,2})?_(?![0-9]{2,4}_[0-9]{2}_[0-9]{2}))',"$1 "+ ($_.CreationTime.toString("yy_MM_dd"))}

But it gives me this error:

Rename-Item : Fehler bei der Eingabe für den Skriptblock für den Parameter "NewName". Der Operator "-ireplace" lässt
nur zwei nachgestellte Elemente zu, nicht 3.
In Zeile:1 Zeichen:63
+ ... ru -NewName { $_.Name -replace '([0-9]{4}\.[0-9]{1,2}-?([0-9]{1,2})?_ ...
+                 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : InvalidArgument: (6442.1_Aufgabe ...IMG - Kopie.pdf:PSObject) [Rename-Item], ParameterBi
   ndingException
    + FullyQualifiedErrorId : ScriptBlockArgumentInvocationFailed,Microsoft.PowerShell.Commands.RenameItemCommand

Is there any help in the universe?

Bernard

@TaifunOrkan Welcome to PowerShell.org forums.

This forum supports only English, it would be great if you can translate the error and update the thread.

Bernard,
Welcome to the forum. :wave:t4:

I don’t know how to do it with your replace logic but if your files always have the same pattern this could be solution:

Get-ChildItem -File -Recurse | 
Rename-Item -PassThru -NewName { ($_.Name -split '_')[0, -1] -join "$($_.CreationTime.toString('_yy_MM_dd_'))" } 

You simply split the file name on each underscore, use the first and last element of the resulting array and join them with the creation date in between. :wink:

Warum wollt Ihr denn eigentlich das Alte Schulhaus aufgeben? :wink: :crazy_face: :laughing: :laughing:

Dear kvprasoon,

I am sorry, but my computersystem understands only english, so I have to manually translate the error message.

Input error for script block for parameter 'NewName'. The -ireplace operator lets
only two trailing elements to, not 3.

I found out the reason myself, it has to do with
"$1 "+ ($_.CreationTime.toString(“yy_MM_dd”))}
and regex replace syntax.

I will post a new proposal later in answer to Olaf’s reply.

Thank you for your hint.

Dear Olaf!

You simply split the file name on each underscore, use the first and last element of the resulting array and join them with the creation date in between. :wink:

Thank you for your quick reply. You are right. I have to run a bunch of similar commands to shape the filenames step by step. But it is a time consuming process. At least the way I do it.

My solution is not ideal but seems to work:

# Missing dates. Replace missing dates with creation dates of files
# Don't forget the backtick` in front of $1
# Does not work with moved or copied files
# ignore folders
Get-ChildItem -File -Recurse | Rename-Item -PassThru -NewName { $_.Name -replace '([0-9]{4}\.[0-9]{1,2}-?([0-9]{1,2})?)_(?![0-9]{2,4}_[0-9]{2}_[0-9]{2})',"`$1_$($_.CreationTime.toString('yy_MM_dd'))"}

Thanx for Your reply!

By the way:
Alte Schulhäuser sind oft sehr schön und es ist jammerschade, wenn sie verkauft werden.
Man dachte früher, dass Architektur die Menschen erhebt.

Old school houses are often very beautiful and it is a pity when they are sold.
Former people thought that architecture makes better people.

Here is my proposal, seems to be working.

# Missing dates. Replace missing dates with creation dates of files
# Don't forget the backtick` in front of $1
# Does not work with moved or copied files
# ignore folders
Get-ChildItem -File -Recurse | Rename-Item -PassThru -NewName { $_.Name -replace '([0-9]{4}\.[0-9]{1,2}-?([0-9]{1,2})?)_(?![0-9]{2,4}_[0-9]{2}_[0-9]{2})',"`$1_$($_.CreationTime.toString('yy_MM_dd'))"}**strong text**

Greetings

Because there is no strict pattern the filenames follow, I’ve made a regex to find all filenames containing a correct datestring such as
19_05_29

The regex is:

(?<!(\.))(1|2){0,1}[0-9]{2}_[0-9]{2}_[0-9]{2}

Here you can see the expeceted result:
regex to find datestrings

But I need the negation, that means select all filenames that do not have the correct datestring pattern.

I am close but don’t find the solution.

Any idea?

Hmmm … I’m unsure if I got it right but what about the -notmatch operator? Simply pipe your Get-ChildItem to a Where-Object and use the -notmatch for the $_.BaseName or the $_.Name

Hello Olaf,
thank you very much. That is a good aproach.

Get-ChildItem -File -Recurse | Where-Object {$_.Name -NotMatch "(?<!(\.))(1|2){0,1}[0-9]{2}_[0-9]{2}_[0-9]{2}"} | Rename-Item -PassThru -NewName { "$($_.CreationTime.toString('yy_MM_dd'))_" + $_.Name }

Here are my testfiles, some missing date in filename.

6450.10 19_07_22_ Brief fr Fau Maier.docx
6450.10 19_07_22_ Brief von Fau Maier.docx
6450.10 Brief fr Fau Maier.docx
6450.10_19_07_22_ Brief an Fau Maier.docx
6450.10_Brief an Fau Maier (2).docx
6450.10_Brief an Fau Maier.docx
Mail an Frau Müller.docx


The reuslt is pretty good. The only Problem is, that the date is placed before the reference number if any. It should be behind this number (ie 6450.10_21_05_01):

22_03_156450.10 Brief fr Fau Maier.docx
22_03_15_6450.10_Brief an Fau Maier (2).docx
22_03_156450.10_Brief an Fau Maier.docx
22_03_15_Mail an Frau Mller.docx
6450.10 19_07_22
Brief fr Fau Maier.docx
6450.10 19_07_22
Brief von Fau Maier.docx
6450.10_19_07_22_ Brief an Fau Maier.docx

Why did you change the code for the replace part? I thought you had a working solution before. You just had to insert the Where-Object in between!? :thinking: :man_shrugging:t4:

Olaf!

Why did you change the code for the replace part?

I got an error-message: “Path too long or filename too long …” or so.

But anyway I must improve the placement part with a conditional.
If there is a verification number then return it else return an anchor where the start is.

I cannot prove this at the moment, but the regex for the replacement could be:

(?(?=[0-9]{4}\.[0-9-]{1,4}_)([0-9]{4}\.[0-9-]{1,4}_) | \w)

Or if possible:

$date="(?<!(\.))(1|2){0,1}[0-9]{2}_[0-9]{2}_[0-9]{2}"

# (?(?=regex)then|else)
$replace= "(?(?=[0-9]{4}\.[0-9-]{1,4}_)([0-9]{4}\.[0-9-]{1,4}_) | \w)"

Get-ChildItem -File -Recurse | Where-Object {$_.Name -NotMatch $date} | Rename-Item -PassThru -NewName { $_.Name -replace $replace,"$($_.CreationTime.toString('yy_MM_dd'))_`$1"}

My God?

Hmmm … sorry, but you confuse me more and more.

According to one of your answers above a seemingly working solution was this:

Get-ChildItem -File -Recurse | 
Rename-Item -PassThru -NewName { $_.Name -replace '([0-9]{4}\.[0-9]{1,2}-?([0-9]{1,2})?)_(?![0-9]{2,4}_[0-9]{2}_[0-9]{2})', "`$1_$($_.CreationTime.toString('yy_MM_dd'))" }

Now, when you want to exclude files having already a date in their name you simply add the Where-Object in between …

Get-ChildItem -File -Recurse | 
Where-Object {$_.Name -NotMatch "(?<!(\.))(1|2){0,1}[0-9]{2}_[0-9]{2}_[0-9]{2}"} |
Rename-Item -PassThru -NewName { $_.Name -replace '([0-9]{4}\.[0-9]{1,2}-?([0-9]{1,2})?)_(?![0-9]{2,4}_[0-9]{2}_[0-9]{2})', "`$1_$($_.CreationTime.toString('yy_MM_dd'))" }

… the rest can stay the same … or did I get something wrong?

BTW: It would be helpful when you start to add line breaks to your code and when you make sure you format code or sample data as code here in the forum.

Dear Olaf!
Thank you for your quick replies.
When I said the code was working, I didn’t mean that this is already a solution for my problem.
There are no errors,
Sometimes more obstacles are ocurring on my way, when made a fist step.

I will explain my problem in detail:

There are filenames that have a verification number and no date.
Others neither have verification number nor date.

All filenames need to contain dates.
But if there is a verification number the date have to be inserted afterwards.
Otherwise insert it at the beginning of filename.

I am sorry, the confusion comes from the difficulty of my task.
If it is too complicated, I will do it file by file with hands, even though I hate io open hundreds of document. But I will get retired in a few years.

I am no buddhist.

Bernd

OK, but you don’t have to do it in one run. If you have three types of file names you can create three specialized commands if that’s easier. You don’t have to do it the hard way. :wink:

Dear Olaf,

yes that is true. I have to think over it. Indeed I already have a whole bunch of different runs.

But they consume lots of time and ressources. Because there are more than 4000 files. And I don’t have to do it only once but regularily.

##########################################
# Mehrere Dateien umbenenennen mit Regex #
##########################################
# Convert Whitespace after Az to _
Get-ChildItem -File -Recurse | Rename-Item -PassThru -NewName { $_.Name -replace '(?<=[0-9]{4}.[0-9]{1,2})\s','_'}
# Convert Whitespace after Date
Get-ChildItem -File -Recurse | Rename-Item -PassThru -NewName { $_.Name -replace '(?<=[0-9]{4}.[0-9]{1,2}(\s|_)[0-9]{2,4}(_|-|\.)[0-9]{2}(_|-|\.)[0-9]{2})\s','_'}
# Bindestrich im Datum ersetzen durch _
Get-ChildItem -File -Recurse | Rename-Item -PassThru -NewName { $_.Name -replace '(?<=[0-9]{4}\.[0-9]{1,4}(_|\s)[0-9]{2})-','_'}
Get-ChildItem -File -Recurse | Rename-Item -PassThru -NewName { $_.Name -replace '(?<=[0-9]{4}\.[0-9]{1,4}(_|\s)[0-9]{2}(_|-|\s)[0-9]{2})-','_'}
# Replace 4 digit year by two digits
# Convert 4 digit year to 2 digit year
# first two digits from 4 digits year format
$regex= "(2|1)[0-9]{1}(?=([0-9]{2}_[0-9]{2}_[0-9]{2}))"
Get-ChildItem -File -Recurse | Rename-Item -PassThru -NewName { $_.Name -replace $regex,''}
# Missing dates. Replace missing dates with creation dates of files
# Don't forget the backtick` in front of $1
# Does not work with moved or copied files
# ignore folders
Get-ChildItem -File -Recurse | Rename-Item -PassThru -NewName { $_.Name -replace '([0-9]{4}\.[0-9]{1,2}-?([0-9]{1,2})?)_(?![0-9]{2,4}_[0-9]{2}_[0-9]{2})',"`$1_$($_.CreationTime.toString('yy_MM_dd'))_"}
# Replace uncommon shortcuts in files and directories
### koko, DB, DAB
Get-ChildItem -File -Recurse | Rename-Item -PassThru -NewName { $_.Name -replace '(?<=(\s|_|-))(ko-ko|koko)(?=(\s|_|-|\.))','konfessionell-kooperativ'}
Get-ChildItem -File -Recurse | Rename-Item -PassThru -NewName { $_.Name -replace '(?<=(\s|_|-))(BH|BDH)(?=(\s|_|-|\.))','Bildungshaus'}
###################################### bis hier 10.03.22 09:21 ######################################################
################ Probleme mit DB da mehrere verschiedene Sachverhalte ######################################
Get-ChildItem -File -Recurse | Rename-Item -PassThru -NewName { $_.Name -replace '(?<=(\s|_|-))(JÜL)(?=(\s|_|-|\.))','jahrgangsübergr Klasse'}
Get-ChildItem -File -Recurse | Rename-Item -PassThru -NewName { $_.Name -replace '(?<=(\s|_|-))(SBW)(?=(\s|_|-|\.))','Schulbezirkswechsel'}
Get-ChildItem -File -Recurse | Rename-Item -PassThru -NewName { $_.Name -replace '(?<=(\s|_|-))(UK)(?=(\s|_|-|\.))','unterstützte Kommun'}
Get-ChildItem -File -Recurse | Rename-Item -PassThru -NewName { $_.Name -replace '(?<=(\s|_|-))(BWK)(?=(\s|_|-|\.))','Bildungswegekonf'}
Get-ChildItem -File -Recurse | Rename-Item -PassThru -NewName { $_.Name -replace '(?<=(\s|_|-))(TOL)(?=(\s|_|-|\.))','techn Oberlehrer'}
Get-ChildItem -File -Recurse | Rename-Item -PassThru -NewName { $_.Name -replace '(?<=(\s|_|-))(FOL)(?=(\s|_|-|\.))','Fachoberlehrer'}
Get-ChildItem -File -Recurse | Rename-Item -PassThru -NewName { $_.Name -replace '(?<=(\s|_|-))(FB)(?=(\s|_|-|\.))','Fachbetreuer'}
Get-ChildItem -File -Recurse | Rename-Item -PassThru -NewName { $_.Name -replace '(?<=(\s|_|-))(FOL)(?=(\s|_|-|\.))','Fachoberlehrer'}
##### 10.03.2022 läuft ####
Get-ChildItem -File -Recurse | Rename-Item -PassThru -NewName { $_.Name -replace '(?<=(\s|_|-))(JB)(?=(\s|_|-|\.))','Jugendbegleiter'}
##### Noch nicht zu Ende #####

# directories
# check [string]::IsNullOrEmpty(...)
# if (-not ([string]::IsNullOrEmpty($version)))
#-erroraction 'silentlycontinue'
Get-ChildItem -Directory -Recurse | Rename-Item -PassThru -NewName { $_.Name -replace '(?<=(\s|_|-))(ko-ko|koko)(?=(\s|_|-|\.))','konfessionell-kooperativ'} -erroraction 'silentlycontinue'
Get-ChildItem -Directory -Recurse | Rename-Item -PassThru -NewName { $_.Name -replace '(?<=(\s|_|-))(BH|BDH)(?=(\s|_|-|\.))','Bildungshaus'} -erroraction 'silentlycontinue'
Get-ChildItem -Directory -Recurse | Rename-Item -PassThru -NewName { $_.Name -replace '(?<=(\s|_|-))(JÜL)(?=(\s|_|-|\.))','jahrgangsübergr Klasse'} -erroraction 'silentlycontinue'
Get-ChildItem -Directory -Recurse | Rename-Item -PassThru -NewName { $_.Name -replace '(?<=(\s|_|-))(SBW)(?=(\s|_|-|\.))','Schulbezirkswechsel'} -erroraction 'silentlycontinue'
Get-ChildItem -Directory -Recurse | Rename-Item -PassThru -NewName { $_.Name -replace '(?<=(\s|_|-))(UK)(?=(\s|_|-|\.))','unterstützte Kommun'} -erroraction 'silentlycontinue'
Get-ChildItem -Directory -Recurse | Rename-Item -PassThru -NewName { $_.Name -replace '(?<=(\s|_|-))(BWK)(?=(\s|_|-|\.))','Bildungswegekonf'} -erroraction 'silentlycontinue'
Get-ChildItem -Directory -Recurse | Rename-Item -PassThru -NewName { $_.Name -replace '(?<=(\s|_|-))(TOL)(?=(\s|_|-|\.))','techn Oberlehrer'} -erroraction 'silentlycontinue'
Get-ChildItem -Directory -Recurse | Rename-Item -PassThru -NewName { $_.Name -replace '(?<=(\s|_|-))(FOL)(?=(\s|_|-|\.))','Fachoberlehrer'} -erroraction 'silentlycontinue'
Get-ChildItem -Directory -Recurse | Rename-Item -PassThru -NewName { $_.Name -replace '(?<=(\s|_|-))(FB)(?=(\s|_|-|\.))','Fachbetreuer'} -erroraction 'silentlycontinue'
Get-ChildItem -Directory -Recurse | Rename-Item -PassThru -NewName { $_.Name -replace '(?<=(\s|_|-))(JB)(?=(\s|_|-|\.))','Jugendbegleiter'} -erroraction 'silentlycontinue'

Thank you, good man! Should I mark the question as solved? By the way howto propperly logout?

Wow.

That’s nothing. :wink: But the way you apoproach it is the worst I can think of. :wink:

PowerShell is quite slow anyway when it comes to file system operations but you make it worse when you run the same query again and again and again. And probably you write some files even more than once.
And your code is repetitive, hard to read and hard to maintain this way.

Instead you should save the result of the first run of Get-ChildItem -Directory -Recurse to a variable and work with that.
Instead of iterating over all files again and again you should use a loop and treat every single file only once. That will save you probably the vast amount of the runtime of your script.

But the way you apoproach it is the worst I can think of.
I know you are right, in addition I love to take warm showers :wink:

Do you have an example? That could help me a lot.

Get-ChildItem -File -Recurse | Rename-Item -PassThru 
-NewName { $_.Name -replace '(?<=(\s|_|-))(JÜL)(?=(\s|_|-|\.))','jahrgangsübergr Klasse'}
Get-ChildItem -File -Recurse | Rename-Item -PassThru 
-NewName { $_.Name -replace '(?<=(\s|_|-))(SBW)(?=(\s|_|-|\.))','Schulbezirkswechsel'}
Get-ChildItem -File -Recurse | Rename-Item -PassThru 
-NewName { $_.Name -replace '(?<=(\s|_|-))(UK)(?=(\s|_|-|\.))','unterstützte Kommun'}
Get-ChildItem -File -Recurse | Rename-Item -PassThru 
-NewName { $_.Name -replace '(?<=(\s|_|-))(BWK)(?=(\s|_|-|\.))','Bildungswegekonf'}
Get-ChildItem -File -Recurse | Rename-Item -PassThru 
-NewName { $_.Name -replace '(?<=(\s|_|-))(TOL)(?=(\s|_|-|\.))','techn Oberlehrer'}

I know php and loops, but I am not firm with piping and powershell.
Maybe I could work with arrays.

Thank you!

Hmmm … but you change the condition every time. Before it was about dates - now it’s about abbreviations. :smirk:

$ChildItemList = Get-ChildItem -File -Recurse
$UnChangedItemList = New-Object System.Collections.Generic.List[System.IO.FileSystemInfo]

foreach ($ChildItem in $ChildItemList) {
    $ChildItem
    switch -Regex ($ChildItem.BaseName) {
        '(?<=(\s|_|-))(JÜL)(?=(\s|_|-|\.))' { 
            Rename-Item -Path $ChildItem.FullName -NewName { $ChildItem.Name -replace '(?<=(\s|_|-))(JÜL)(?=(\s|_|-|\.))', 'jahrgangsübergr Klasse' }
            break
        }
        '(?<=(\s|_|-))(SBW)(?=(\s|_|-|\.))' { 
            Rename-Item -Path $ChildItem.FullName -NewName { $ChildItem.Name -replace '(?<=(\s|_|-))(SBW)(?=(\s|_|-|\.))', 'Schulbezirkswechsel' }
            break
        }
        '(?<=(\s|_|-))(UK)(?=(\s|_|-|\.))' { 
            Rename-Item -Path $ChildItem.FullName -NewName { $ChildItem.Name -replace '(?<=(\s|_|-))(UK)(?=(\s|_|-|\.))', 'unterstützte Kommun' }
            break
        }
        '(?<=(\s|_|-))(BWK)(?=(\s|_|-|\.))' { 
            Rename-Item -Path $ChildItem.FullName -NewName { $ChildItem.Name -replace '(?<=(\s|_|-))(BWK)(?=(\s|_|-|\.))', 'Bildungswegekonf' }
            break
        }
        '(?<=(\s|_|-))(TOL)(?=(\s|_|-|\.))' { 
            Rename-Item -Path $ChildItem.FullName -NewName { $ChildItem.Name -replace '(?<=(\s|_|-))(TOL)(?=(\s|_|-|\.))', 'techn Oberlehrer' }
            break
        }
        Default {
            $UnChangedItemList.Add($ChildItem)
        }
    }
}

$UnChangedItemList

The key point is to query the folder ONCE and to prepare the new filename completely before you start the rename command inside the loop.

So if you need more than just to expand the abbreviations you should use another regex and another logic for the rename command. It does not have to be one regex or one replace. You can actually built the new filename with a few separate steps and concatenate it before the rename command. The key is to process every single file only once. :wink:

In the example I prepared an empty list for the filenames not fitting any of your patterns. After you run this loop you can output the unchanged files and treat them as needed.

!!! Please test before with test files before you target your production environment !!!

That is a lot of help, and the kind of solution I was looking for.

!!! Please test before with test files before you target your production environment !!!

I will backup all files and test the code in a test folder.

If I finish some test runs I will give a feedback.

It does not really metter if to replace abbreviations or insert creation time for looping.

But what I wonder is, can I open the powershell console from the windows explorer copy and past you code and press enter. Or du I have to save a script?

When I paste cour code in the powershell window from context menu nothin happens,

Do I have to make the whole thing recursivle?

Get-ChildItem –File -Recurse |

foreach ($ChildItem in $ChildItemList) {
    $ChildItem
    switch -Regex ($ChildItem.BaseName) {
        # Bildungshaus
	'(?<=(\s|_|-))(BH|BDH)(?=(\s|_|-|\.))' { 
            Rename-Item -Path $ChildItem.FullName -NewName { $ChildItem.Name -replace '(?<=(\s|_|-))(BH|BDH)(?=(\s|_|-|\.))', 'Bildungshaus' }
            break
        }
	# konfessionell kooperativer Reliunterricht
        '(?<=(\s|_|-))(ko-ko|koko|ko\sko)(?=(\s|_|-|\.))' { 
            Rename-Item -Path $ChildItem.FullName -NewName { $ChildItem.Name -replace '(?<=(\s|_|-))(ko\sko)(?=(\s|_|-|\.))', 'konfessionell-kooperativ' }
            break
        }

        Default {
            $UnChangedItemList.Add($ChildItem)
        }
    }
}

$UnChangedItemList

Bu then I get errors,

In line:3 character:21
+ foreach ($ChildItem in $ChildItemList) {
+                     ~~
Unexpected Token "in" in expression or command
In line:3 char:20
+ foreach ($ChildItem in $ChildItemList) {
+                    ~
Schließende ")" fehlt in einem Ausdruck.
In line:3 char:38
+ foreach ($ChildItem in $ChildItemList) {
+                                      ~
Unerwartetes Token ")" in Ausdruck oder Anweisung.
    + CategoryInfo          : ParserError: (:) [], ParentContainsErrorRecordException
    + FullyQualifiedErrorId : UnexpectedToken

Mercy!

Hmmm … it depends pretty much on the complexity I’d say.

Sometimes the console is a little picky when you drop some code. I experienced something like this when I had line breaks in if-else statements.

What do you usually use to write your code? I’d recommend using at least the PowerSehll_ISE or VSCode. There you can remote into the target computer and can run either complete scripts or selected code.

What exactly do you mean?