1

I have a powershell script (triggered by automation software). It effectively renames files by the directory, followed by an underscore, leading zero and sequential numbering.

$i = 0
$folder = Any-path
Get-ChildItem -Path $folder |ForEach-Object 
{$extension = $_.Extension
$newName = $_.Directory.Name + '_{0:d3}{1}' -f  $i, $extension
$i++
Rename-Item -Path $_.FullName -NewName $newName}

The problem is it renames every file in the directory, every time the script is triggered, which is inefficient.

I only want to rename files that don't fit the naming scheme (FolderName_01.ext), and rename the anomalies by the order they were added, continuing from the apex.

So

  • FolderName_01
  • FolderName_02
  • FolderName_03
  • Anomaly1-Latest
  • Anomaly2-Earliest

Would become

FolderName_01 through FolderName_05 , with Anomaly2 renamed to FolderName_04, because it was created sooner.

I know that I can use

Get-ItemProperty '$filename' | select CreationTime

to see the dates for each file, but my brain starts to fry, as I try figuring out how to use that info to rename the files, by date created, picking up from the last properly named file.

I definitely appreciate any help!


I did a lot of work on this. I got help and did a lot of reading. This solution is probably rough in some areas, but it does the job and can run in the background to perform actions automatically - with one caveat: the Powershell console must be left open.

I haven't figured out how to make this a permanent WMI event binding.

Solution:

#REQUIRES -Version 4.0

#This is a string of folders you'd like to monitor
$Monitored = 'C:\Users\Me\Desktop\Pic Test\Tom Hiddleston', 'C:\Users\Me\Desktop\Pic Test\Kate Bosworth'

#This part is selecting the folder paths to be processed indvidually
$MatchPath = @($Monitored | Where-Object { Test-Path -Path $_ } | ForEach-Object {
$Path = $_;

$Watcher = New-Object System.IO.FileSystemWatcher -Property @{
    Path = $Path;
    Filter = '*.*';
    IncludeSubdirectories = $True;
    NotifyFilter = [System.IO.NotifyFilters]'FileName, LastWrite'}

#This may be unnecessary, but it's there to add the folder name to the -#SourceIdentifier, which makes the automation easier to shut down.
$ID = Split-Path $Path -Leaf

#Here it's important that the variable have the same name as the delete section, because it passes to the New-Object creation at the bottom.
#In this case, four events will be created. Two folders x two actions.
$FileAction = Register-ObjectEvent -InputObject $Watcher Created -SourceIdentifier FileCreated_$ID -Action {

    $Local = Split-Path ($Event.SourceEventArgs.FullPath)
    $i = 0

    #Selects which file extentions to include in renaming
    Filter Where-Extension {
    Param( [String[]] $Extension = ('.bmp', '.jpg', '.png', '.gif', '.jpeg', '.avi')) $_ |
    Where-Object { $Extension -contains $_.Extension }}

    #Orders all the files by their CreationTime
    Function Global:File-Order ($Local) {Get-ChildItem -Path $Local | 
    Where-Extension .jpg,.bmp,.png,.jpeg,.gif,.avi |
    Select-Object Name,FullName,CreationTime|Sort-Object -Property CreationTime}

    #Returns a file's extension
    Function Global:File-Ext ($File) {Get-ItemProperty $File| Select-Object -Expand Extension}

    #Returns the name the file should be - the NewName
    $NewBase = ((Split-Path $Local -Leaf) + '_{0:d2}{1}' -f  $i, $Ext).Split("\.")


    File-Order ($Local) | ForEach-Object -Begin {$i = 0} `
    -Process {
        $Ext = File-Ext ($_.FullName) 
        $NewName = $NewBase[0] + $i

        if (!(Test-Path (-Join ($Local,'\',$NewName,'.*'))))
        {Rename-Item -Path $_.FullName -NewName (-Join ($NewName,$Ext))}
        $i++ }
        };       

$FileAction = Register-ObjectEvent -InputObject $Watcher Deleted -SourceIdentifier FileDeleted_$ID -Action  {

    $Local = Split-Path ($Event.SourceEventArgs.FullPath)
    $i = 0

    #Selects which file extentions to include in renaming
    Filter Where-Extension {
    Param( [String[]] $Extension = ('.bmp', '.jpg', '.png', '.gif', '.jpeg', '.avi')) $_ |
    Where-Object { $Extension -contains $_.Extension }}

    #Orders all the files by their CreationTime
    Function Global:File-Order ($Local) {Get-ChildItem -Path $Local | 
    Where-Extension .jpg,.bmp,.png,.jpeg,.gif,.avi |
    Select-Object Name,FullName,CreationTime|Sort-Object -Property CreationTime}

    #Returns a file's extension
    Function Global:File-Ext ($File) {Get-ItemProperty $File| Select-Object -Expand Extension}

    #Returns the name the file should be - the NewName
    $NewBase = ((Split-Path $Local -Leaf) + '_{0:d2}{1}' -f  $i, $Ext).Split("\.")


    File-Order ($Local) | ForEach-Object -Begin {$i = 0} `
    -Process {
        $Ext = File-Ext ($_.FullName) 
        $NewName = $NewBase[0] + $i

        if (!(Test-Path (-Join ($Local,'\',$NewName,'.*'))))
        {Rename-Item -Path $_.FullName -NewName (-Join ($NewName,$Ext))}
        $i++ }
        }; 

New-Object PSObject -Property @{ Watcher = $Watcher; OnCreated = $FileAction };

}); 

#These will the stop the scripts
#Unregister-Event FileCreated_Pic Test -Verbose
#Unregister-Event FileDeleted_Pic Test -Verbose
#Unregister-Event FileCreated_Another Test -Verbose
#Unregister-Event FileDeleted_Another Test -Verbose

#This will stop all the scripts at once, including any others you may have running; so use Get-EventSubscriber first, to see what's running, then.
#Get-EventSubscriber | Unregister-Event -Verbose

And I'd like to thank @justinf and give credit to the most influential sources I used.

Community
  • 1
  • 1
Stack Johan
  • 379
  • 1
  • 6
  • 23

2 Answers2

1

Thought this was quit an interesting problem, so I thought i would give it a crack at lunch . This is what I came up with . I tried to make it as readable as possible so that the logic was easy to follow.

These are the logic steps

Get a list of files from a directory that do not end with _some number

Get a list of all files named _come number and find the highest number e.g if there are 3 files called FileA_01.txt,SomeDocument_03.txt,FileB_02.txt it will return 3 cause that is the highest number .

Now add 1 to the highest number so that we can use this to rename the files

Now we loop thought the list of files we need to rename in the order of their LastWriteTime and rename them .

This is what my folder of test files started out looking like.

enter image description here

This is what it looked like after I ran the script.

enter image description here

$filesLocation = 'c:\Dump\test'

#Step 1# Get a list of files we need to rename , any files that do not end with _number 
$FilesToRename = Get-ChildItem -Path $filesLocation | Where-Object {$_.Name -notmatch ".*_\d*"} | select-object Name,FullName,LastWriteTime

#Step 2 # Get highest file number so if the highest file name is SomeDocument_03.txt it will return 3 
$HighestNumber = (((Get-ChildItem -Path $filesLocation | Where-Object {$_.Name -match ".*_\d*"}).name -replace '(.*)(\d\d)(..*)', "`$2") | measure -Maximum).Maximum

#Step 3 # Get the next number that we will use , becasue measure -Maximum).Maximum returns 3 we need to add the 0 infront of the number if it is shorter than two digits 
[int]$NextNumber =  IF ($HighestNumber -lt 9) {"0"+ ($HighestNumber+1).tostring()} else {($HighestNumber+1).tostring()}

#Step 4 # Rename the files that need renaming in the order of their last write time 
foreach ($File in ($FilesToRename | Sort-Object -Property LastWriteTime) )
{
  $SplitName = ($File.Name.Split("."))

  $NewName = $SplitName[0] + '_' +$NextNumber +'.'+$SplitName[1]

  Rename-Item -LiteralPath $File.FullName -NewName $NewName

  $NextNumber = ++$NextNumber
}
justinf
  • 1,246
  • 2
  • 19
  • 39
  • Thanks for the help :) The code has some issues. - It only appends the file name with _1 . I want the $ParentFolder_001 and If a file is already named FileC_1 an error is thrown `"Cannot convert value "FileC_1.txt1" to type "System.Int32". Error: "Input string was not in a correct format." At 'c:\Dump\test + ... tring()} else {($HighestNumber+1).tostring()} + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + CategoryInfo : MetadataError: (:) [], ArgumentTransformationMetadataException + FullyQualifiedErrorId : RuntimeException"` But it still renames the others – Stack Johan May 08 '15 at 16:56
  • Please can you explain a bit more eg give me 10 file names before they are renamed and what you want them to be renamed. Then also give me an example of 10 other file names that are named correctly . I obviously mis interpreted your post :) in future try be as specific as possible will help people answer your questions more precisely :) – justinf May 08 '15 at 17:28
  • An example would be if I had a folder named Blue - and I want every file in the folder to be named Blue_000 Blue_001 Blue_002 Blue_003 etc. -- and I want the order of the 00* numbering to be by the date I created the file. So, if I had a folder of files with random names -- ballonhrie.jpg, hhaaiiw.jpg, jietowl_22.jpg , etc. the files would be renamed like this Blue_000, Blue_001 - etc. , depending on when the file was created. But, I think I have either solved the problem or come very close. What I have seems to work; I will share it. – Stack Johan May 09 '15 at 02:17
  • 1
    Ok I understand now , I will try change the code later to day when I get to a computer . The naming 000 thing is an easy change . I also have a good idea how to get the files name to be the same as the folders . – justinf May 09 '15 at 05:51
  • I think I have solved it, but it may benefit from some optimization. There may be ways of shortening the code and things I'm missing. I'll edit my previous answer to include the new code. – Stack Johan May 09 '15 at 07:33
1

This seems to handle the problem, but I want to run more tests, to be certain, @justinf

What I wanted was to rename files - like so: ParentFolder_000 - and keep them in order by the date I created the file. So...here you can see I have a bunch of files, randomly named, and inside a folder named "Kate Bosworth"

here you can see I have a bunch of files, randomly named, and inside a folder named "Kate Bosworth"

I wanted to check the CreationTime property and rename the files KateBosworth_00#

enter image description here

The script I came up with, will re-order the files by CreationTime, even if I delete or rename one of the files...

enter image description here

So, finally, this is the script I came up with.

#This is a string of folders you'd like to monitor
$Monitored = 'C:\Users\Me\Desktop\Pic Test\Tom Hiddleston', 'C:\Users\Me\Desktop\Pic Test\Kate Bosworth'

#This part is selecting the folder paths to be processed indvidually
$MatchPath = @($Monitored | ? { Test-Path -Path $_ } | % {
$Path = $_;

$Watcher = New-Object System.IO.FileSystemWatcher -Property @{
    Path = $Path;
    Filter = '*.*';
    IncludeSubdirectories = $True;
    NotifyFilter = [System.IO.NotifyFilters]'FileName, LastWrite'}

#This may be unnecessary, but it's there to add the folder name to the -#SourceIdentifier, which makes the automation easier to shut down.
$ID = Split-Path $Path -Leaf

#Here it's important that the variable have the same name as the delete section, because it passes to the New-Object creation at the bottom.
#In this case, four events will be created. Two folders x two actions.
$FileAction = Register-ObjectEvent -InputObject $Watcher Created -SourceIdentifier FileCreated_$ID -Action {

    $Local = Split-Path ($Event.SourceEventArgs.FullPath)
    $i = 0

    #Selects which file extentions to include in renaming
    Filter Where-Extension {
    Param( [String[]] $Extension = ('.bmp', '.jpg', '.png', '.gif', '.jpeg', '.avi')) $_ |
    Where-Object { $Extension -contains $_.Extension }}

    #Orders all the files by their CreationTime
    Function Global:File-Order ($Local) {Get-ChildItem -Path $Local | 
    Where-Extension .jpg,.bmp,.png,.jpeg,.gif,.avi |
    Select-Object Name,FullName,CreationTime|Sort-Object -Property CreationTime}

    #Returns a file's extension
    Function Global:File-Ext ($File) {Get-ItemProperty $File| Select-Object -Expand Extension}

    #Returns the name the file should be - the NewName
    $NewBase = ((Split-Path $Local -Leaf) + '_{0:d2}{1}' -f  $i, $Ext).Split("\.")


    File-Order ($Local) | ForEach-Object -Begin {$i = 0} `
    -Process {
        $Ext = File-Ext ($_.FullName) 
        $NewName = $NewBase[0] + $i

        if (!(Test-Path (-Join ($Local,'\',$NewName,'.*'))))
        {Rename-Item -Path $_.FullName -NewName (-Join ($NewName,$Ext))}
        $i++ }
        };       

$FileAction = Register-ObjectEvent -InputObject $Watcher Deleted -SourceIdentifier FileDeleted_$ID -Action  {

    $Local = Split-Path ($Event.SourceEventArgs.FullPath)
    $i = 0

    #Selects which file extentions to include in renaming
    Filter Where-Extension {
    Param( [String[]] $Extension = ('.bmp', '.jpg', '.png', '.gif', '.jpeg', '.avi')) $_ |
    Where-Object { $Extension -contains $_.Extension }}

    #Orders all the files by their CreationTime
    Function Global:File-Order ($Local) {Get-ChildItem -Path $Local | 
    Where-Extension .jpg,.bmp,.png,.jpeg,.gif,.avi |
    Select-Object Name,FullName,CreationTime|Sort-Object -Property CreationTime}

    #Returns a file's extension
    Function Global:File-Ext ($File) {Get-ItemProperty $File| Select-Object -Expand Extension}

    #Returns the name the file should be - the NewName
    $NewBase = ((Split-Path $Local -Leaf) + '_{0:d2}{1}' -f  $i, $Ext).Split("\.")


    File-Order ($Local) | ForEach-Object -Begin {$i = 0} `
    -Process {
        $Ext = File-Ext ($_.FullName) 
        $NewName = $NewBase[0] + $i

        if (!(Test-Path (-Join ($Local,'\',$NewName,'.*'))))
        {Rename-Item -Path $_.FullName -NewName (-Join ($NewName,$Ext))}
        $i++ }
        }; 

New-Object PSObject -Property @{ Watcher = $Watcher; OnCreated = $FileAction };

}); 

#These will the stop the scripts
#Unregister-Event FileCreated_Pic Test
#Unregister-Event FileDeleted_Pic Test
#Unregister-Event FileCreated_Another Test
#Unregister-Event FileDeleted_Another Test

This handles:

  • Renaming the files in order of CreationTime
  • Keeping the files in order, even if one is deleted (doesn't handle renaming instances, but anything renamed will be fixed when something is added or deleted)
  • It runs with Powershell, so no third party software is required. (I'm not sure what versions this is compatible with.)
  • It can watch multiple directories.
  • Filters by the extension. (And it's possible to add different naming schemes for different file extensions, if you want to add some if statements).

If you want to stop the automation, call Unregister-Event $SourceIdentifier

Stack Johan
  • 379
  • 1
  • 6
  • 23
  • 1
    Cool glad you got some thing to work , hope my solution helped a bit . One comment I have about your solution is that i see you call $NewName = File-Naming ($Local) in the loop this will scan the folder each time to get the next number and file name . This might cause higher IO on the disk than my solution . So may be look at combining my method of finding the number to append upfront . I can help with this if you like. Just a side note when posting code try not should aliases in your script it might make it harder for new people trying to learn to understand what is going on :) – justinf May 09 '15 at 08:47
  • Yes, your solution was very helpful! I referenced it a lot, while working. I appreciate all the help! :) I will look at the code again and try replacing all the aliases. Thanks for the IO tip! I think I improved that part, by using your method of creating a string variable and only adding the integer and extension in the loop. `#Returns the name the file should be - the NewName $NewBase = ((Split-Path $Local -Leaf) + '_{0:d2}{1}' -f $i, $Ext).Split("\.")` then, in the loop `$NewName = $NewBase[0] + $i` – Stack Johan May 09 '15 at 16:33
  • I added another "upgrade", borrowing from another StackOverflow user. I've included the option to add multiple file paths to monitor. – Stack Johan May 09 '15 at 23:32