PS script to sequentially rename newly created sub-folders

Hi, I have used Chat GPT to create a PS script that will Split a folder of images into sub-folders containing a chosen number of images, to split them into batches.

My script does work, but it creates twice as many sub-folders than needed, and the images go in the second half of them. So I added to the scrip so it deletes the empty folders.

Now I’m left with the second half e.g. Folder_7, 8, 9 etc. I have tried to get the script to then rename them in their sequential order for Batch 1, 2, 3 etc.

It is naming them "Batch ", but the number is not starting at 1, it is starting with the largest number present on the “Folder_” name, and renaming sequentially from there. For Example: I am getting 12 sub-folders in total, 1-6 are empty so are deleted. I’m left with 7-12 (Named “Folder_7” etc.). Once renamed they are renamed to “Batch 12” to “Batch 17”. But I want them as “Batch 1” to “Batch 6”.

Ideally not creating the empty folders in the first place will resolve this but I was stuck with that approach.

Can anyone help?
Thanks!

Script:

Add-Type -AssemblyName System.Windows.Forms
Add-Type -AssemblyName System.Drawing

function Show-FolderDialog($description) {
    $folderDialog = New-Object System.Windows.Forms.FolderBrowserDialog
    $folderDialog.Description = $description

    if ($folderDialog.ShowDialog() -eq 'OK') {
        return $folderDialog.SelectedPath
    }

    return $null
}

function Show-InputDialog($message, $defaultValue) {
    $inputBox = New-Object System.Windows.Forms.Form
    $inputBox.Text = 'Input Box'
    $inputBox.Width = 800  # Set the width to your desired value
    $inputBox.Height = 400  # Set the height to your desired value
    $inputBox.StartPosition = 'CenterScreen'

    $label = New-Object System.Windows.Forms.Label
    $label.Text = $message
    $label.AutoSize = $true
    $label.Allign = Center
    $label.Top = 20
    $label.Font = New-Object System.Drawing.Font('Microsoft Sans Serif', 18)  # Set the font size
    $inputBox.Controls.Add($label)

    $textBox = New-Object System.Windows.Forms.TextBox
    $textBox.Left = 10
    $textBox.Top = $label.Bottom + 10
    $textBox.Width = $inputBox.Width - 40
    $textBox.Text = $defaultValue
    $textBox.Font = New-Object System.Drawing.Font('Microsoft Sans Serif', 20)  # Set the font size
    $inputBox.Controls.Add($textBox)

    $okButton = New-Object System.Windows.Forms.Button
    $okButton.Text = 'OK'
    $okButton.Width = 100  # Set the width to your desired value
    $okButton.Height = 40  # Set the height to your desired value
    $okButton.Font = New-Object System.Drawing.Font('Microsoft Sans Serif', 16)  # Set the font size
    $okButton.DialogResult = [System.Windows.Forms.DialogResult]::OK
    $okButton.Left = ($inputBox.Width - $okButton.Width) / 2
    $okButton.Top = $textBox.Bottom + 10
    $inputBox.AcceptButton = $okButton
    $inputBox.Controls.Add($okButton)

    # Event handler for resizing
    $inputBox.add_Resize({
        $label.Left = ($inputBox.Width - $label.PreferredWidth) / 2
        $textBox.Width = $inputBox.Width - 40
        $okButton.Left = ($inputBox.Width - $okButton.Width) / 2
        $okButton.Top = $textBox.Bottom + 10
    })

    $result = $inputBox.ShowDialog()

    if ($result -eq 'OK') {
        return $textBox.Text
    }

    return $null
}


# Show source folder dialog using File Explorer
$sourceFolder = Show-FolderDialog 'Select Source Folder'
if ($sourceFolder -eq $null) {
    exit
}

# Input number of images per folder
$filesPerFolder = Show-InputDialog 'Enter the number of images per folder' '100'
if ($filesPerFolder -eq $null) {
    exit
}

# Actual script logic
$count = 0
$folderCount = 1
$createNewFolder = $true

Get-ChildItem -Path $sourceFolder -Filter *.jpg -Recurse | ForEach-Object {
    if ($createNewFolder) {
        $destinationSubfolder = Join-Path $sourceFolder "Folder_$folderCount"
        if (-not (Test-Path -Path $destinationSubfolder)) {
            New-Item -ItemType Directory -Path $destinationSubfolder -Force
        }
    }

    Move-Item $_.FullName $destinationSubfolder

    $count++
    if ($count -eq $filesPerFolder) {
        $count = 0
        $folderCount++
        $createNewFolder = $true
    } else {
        $createNewFolder = $false
    }
}

Write-Host 'All .jpg files have been moved into subfolders.'

# Remove empty folders within the source folder
Get-ChildItem -Path $sourceFolder -Directory | Where-Object { $_.GetFiles().Count -eq 0 } | ForEach-Object {
    Remove-Item $_.FullName -Force
}

Write-Host 'Empty folders have been removed.'

# Rename folders as "Batch 1", "Batch 2", ...
$folders = Get-ChildItem -Path $sourceFolder -Directory | Sort-Object { [int]($_.Name -replace 'Folder_', '') }

foreach ($folder in $folders) {
    $newName = "Batch $folderCount"
    Rename-Item $folder.FullName $newName
    $folderCount++
}

Write-Host 'Folders have been renamed as "Batch 1", "Batch 2", ...'

So… a lot to unravel there… I’m not sure what the forum policy is for rewriting AI-generated code, but I will say:

  • all of your renaming can be accomplished pretty quickly in the original ForEach-Object loop
  • your problem with the number in your new “Batch *” names is because the variable has already been used and you never reset it

Hey Connor!

Welcome. So with ChatGPT and other AI generated code, there’s typically going to be hiccups and being able to manipulate the code or giving it additional parameters is needed. AI can make strange decisions sometimes. I see duplicative effort where it wasn’t really needed.

I’m not sure if you wanted to have a GUI here or not, but I will say just getting rid of the GUI part is going to simplify the code, and then you can easily just create functions to run.

I’d personally just have a function that takes two parameters The parameters would be:

  1. The Path where the images are located
  2. How many things you want in a folder.

Then I’d just call the function with the parameters.

function Invoke-BatchImages {
    [CmdletBinding()]
    param(
    
        [Parameter(Mandatory, ValueFromPipeline)]
        [Alias('Path')]
        [ValidateScript({ Test-Path -Path $_ })]
        [String]$SourceFolder,

        [Alias('ImageCount')]
        [int]$FilesPerFolder = 100
    )

    process {
        $count = 0
        $folderCount = 1
        $createNewFolder = $true

        $Images = Get-ChildItem -Path $sourceFolder -Filter *.jpg -Recurse
        
        $Images | ForEach-Object {
            if ($createNewFolder) {
                $destinationSubfolder = Join-Path $sourceFolder "Batch $FolderCount"
                if (-not (Test-Path -Path $destinationSubfolder)) {
                    New-Item -ItemType Directory -Path $destinationSubfolder -Force
                }
            }

            Move-Item $_.FullName $destinationSubfolder

            $count++
            if ($count -eq $filesPerFolder) {
                $count = 0
                $folderCount++
                $createNewFolder = $true
            } else {
                $createNewFolder = $false
            }
        }
    }
}

Then I can run it:

Invoke-BatchImages -Path ".\" -FilesPerFolder 100

I tried to adjust as little as possible but wound up adding in a few things like aliases to make it work for my own brain while writing :smile: . You can adjust as needed or rename, but from very brief testing it looks to work. I’d encourage more testing though. I created a bunch of fake images (340) and ran it a couple times and it created the proper number of folders with files moved. No deletions necessary. You may want to modify and add/cleanup stuff as needed.

Feel free to re-write, though there’s no obligation to do so. I find myself maybe writing more code than I should maybe sometimes, but I play it by ear based on each situation :slight_smile:

I’ll confess I know nothing about PS scripts, which is why I have been using AI. I already have a script without the GUI where you just edit the script to add the file path.

This version is trying to make it more user-friendly for colleagues to use. My intention was to open file explorer so they can simply find and choose their folder, then fill out how many images they want per folder. However, I discovered PS can’t open the actual File Explorer app :frowning:

If you plan on doing more with PS and generating code with AI, It’ll probably benefit you to learn a bit about PS. If you want the GUI you should be able to add it back in for your input. You can also remove the function wrapper if desired. The output of the code I wrote looks like this:

Also note that, it’s not necessarily designed for ‘subsequent runs’. It’s checking files recursively, so it will look in those batch folders If you used a different ‘numbers per file’ that would impact and potentially you’d get empty folders, depending on the number chosen and the current set of numbers. Those empty folders (at least with my code) will be at the end, not the start though. You can code around that in a few ways depending on what you want to do. You’ll also note that I completely got rid of the file renaming process, as you can simply name the folder what you want during creation.

Lastly, while I did re-write the code, that’s not something folks are always going to do. I happened to have some time to re-write yesterday but most of the time, folks here won’t completely re-write the script. Just want to manage expectations is all :slight_smile: .

1 Like

I likely will be learning more, as it has already brought some efficiencies to our team, however it’s not something that is actually part of my job, so it will be learned personally when I have time.

I appreciate people won’t usually write a script for me and much learning is required, I came here as a last ditch effort after getting nowhere for ages haha.

I just can’t quite get your script to run, I assume my file path needs replacing in the script somewhere but after messing around for a little bit, I can’t quite figure out where it goes :confused:

You pass it dynamically. The big code block I provided needs to be copied/pasted into your terminal and that ‘loads’ the function into memory, then you call it using the name:

Invoke-BatchImages -Path ".\" -FilesPerFolder 100

the path there is just a shortcut for ‘this folder’ you could pass a folder named folder on your desktop by doing:

Invoke-BatchImages -Path "C:\users\username\desktop\folder" -FilesPerFolder 100

Oh, I think I’ve been doing things differently on the scripts I’ve made so far.

I have been saving them as .ps1 files that I can share with my team, then running with powershell.

Most of them just require a quick edit to put your desired file path in the script, save it, then run with powershell for it to execute.

I’m guessing that’s not the process here :man_shrugging:

Edit: I figured it out haha. Thank you so much!!

Hey Connor!

Glad you figured it out!

Yeah - lots of different ways to do things, you can modify mine to sort of work like yours. you can save my file as a ps1, but its the function keyword that makes it a little different. Take a look at: Functions - PowerShell | Microsoft Learn. Your original script had functions in it as well, but they were essentially loaded in initially then called.

Depends on the person but many will create functions as that’s usually a good practice. You can modify it to not be a function by removing function Invoke-BatchImages { and then also the last } . Once you do that it’s just a script (that has parameters). If you prefer to use the GUI components You can simply add those back in, just have to make sure the variable you save the GUI selections to are lined up with the commands that are ran.

Hopefully that’s enough to get you started on modifications of your original code (or the code I wrote)!