Stack Johan
Stack Johan

Reputation: 379

How to rename the most recent files in order?

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

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.

Upvotes: 1

Views: 955

Answers (2)

Stack Johan
Stack Johan

Reputation: 379

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

Upvotes: 1

justinf
justinf

Reputation: 1298

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
}

Upvotes: 1

Related Questions