Copy file/folder names | Check complete multi-part archives

Hello,

I’ve recently started using Directory Opus and really like it so far, but there are two things I'd like to do and I’m not sure how. I would really appreciate any help or advice.

First button
I want to select some folders and files, then click a button to copy their names to the clipboard.

• For folders, I want the full folder name.
(e.g., Folder name: Data.System.Files → copied text: Data.System.Files)

• For files, I want only the name without the file extension.
(e.g., File name: Backup.System.Archive.rar → copied text: Backup.System.Archive)

Second button
I have several folders, and inside each folder there are multi-part archive files, usually .zip or .rar (for .rar files, parts may be numbered either with or without leading zeros (e.g., part1 or part01).

For example:
Folder: Photo.Backups
├── Photo2024.part01.rar
├── Photo2024.part02.rar
├── Photo2024.part03.rar
└── Photo2024.part04.rar

What I want to do is select some folders and then click a button to confirm that each folder is complete. A folder is considered complete if it has all of the following conditions:
1. It contains only multi-part archive files.
2. All multi-part archive files have the same base name (e.g., Photo2024).
3. No multi-part archive files are missing (e.g., if there is only one part or if some part numbers are missing, like part2 missing between part1 and part3, the archive set is incomplete).
4. All multi-part archive files have the same size, except for the last part, which is smaller.

If a folder fails any of these conditions, it is considered incomplete.

I would like the button to create a simple text report (.txt) listing those folders so I can check them manually. If everything is fine, the report should say that all selected folders are complete.

I would really appreciate any help or examples of how this could be done in Directory Opus.

The second task seems complicated. If i were you, i'd probably get an AI to write a python script and then just pass the folder or folders to that script using Dopus. Python because the AI has enough examples of python scripts to actually be relatively good at writing them.

1 Like

For the first button, use

Clipboard COPYNAMES=nopaths,noexts
1 Like

The multi-part archive task could be done using Opus’s scripting and should be easy if you’re familiar with Javascript.

But please Ask one question per thread

1 Like

Thanks for your advice.

Thank you, it works perfectly!

Thanks for your reply. I didn’t know about the “one question per thread” rule, which is why I asked two things at once. I’ll remember that for next time.

About the archive task, I’m not familiar with JavaScript, so I tried to make it work with AI help, but it doesn’t work properly. Sometimes it marks folders as complete when they are actually incomplete, and the report lists both complete and incomplete folders, while I only wanted the incomplete ones. Also, when I click the button, I get some errors in Directory Opus. I tried many times to fix it, but couldn’t get it to work correctly.

Here’s the code I tried to use. This is the best I could make.

function OnClick(clickData) {
    var tab = clickData.func.sourcetab;
    var selected = tab.selected_dirs;
    if (selected.count == 0) {
        DOpus.Output("No folders selected.");
        return;
    }
    var fso = new ActiveXObject("Scripting.FileSystemObject");
    // Get the path of the "parent" location
    var parentPath = fso.GetParentFolderName(selected(0).realpath);
    var reportPath = parentPath + "\\MultiPart_Report.txt";
    var report = fso.CreateTextFile(reportPath, true);
    report.WriteLine("Multi-Part Archive Check Report");
    report.WriteLine("Generated: " + new Date());
    report.WriteLine("------------------------------------");
    var incompleteFolders = [];
    var e = new Enumerator(selected);
    for (; !e.atEnd(); e.moveNext()) {
        var folderItem = e.item();
        var folderPath = folderItem.realpath;
        DOpus.Output("Checking folder: " + folderPath);
        if (isFolderComplete(folderPath)) {
            report.WriteLine(folderItem.name + " - COMPLETE");
        } else {
            report.WriteLine(folderItem.name + " - INCOMPLETE");
            incompleteFolders.push(folderItem.name);
        }
    }
    report.WriteLine("------------------------------------");
    if (incompleteFolders.length == 0) {
        report.WriteLine("✅ All selected folders are complete.");
    } else {
        report.WriteLine("⚠️ Incomplete folders found:");
        for (var i = 0; i < incompleteFolders.length; i++) {
            report.WriteLine(" - " + incompleteFolders[i]);
        }
    }
    report.Close();
    DOpus.Output("Report saved to: " + reportPath);
}
// === Helper ===
function isFolderComplete(folderPath) {
    var fso = new ActiveXObject("Scripting.FileSystemObject");
    if (!fso.FolderExists(folderPath)) return false;
    var folder = fso.GetFolder(folderPath);
    var files = new Enumerator(folder.Files);
    var parts = [];
    for (; !files.atEnd(); files.moveNext()) {
        var file = files.item();
        var name = file.Name.toLowerCase();
        // Accepts .part01.rar or .part1.rar
        if (name.match(/\.part\d+\.rar$/i)) {
            parts.push(file);
        } else {
            return false; // contains an unacceptable file
        }
    }
    if (parts.length < 2) return false;
    // Check same base name
    var base = parts[0].Name.replace(/\.part\d+\.rar$/i, "");
    for (var i = 1; i < parts.length; i++) {
        if (parts[i].Name.indexOf(base) !== 0) return false;
    }
    // Check sequence of parts
    var nums = [];
    for (var i = 0; i < parts.length; i++) {
        var m = parts[i].Name.match(/\.part(\d+)\.rar$/i);
        if (m) nums.push(parseInt(m[1]));
    }
    nums.sort(function(a, b) { return a - b; });
    for (var i = 0; i < nums.length - 1; i++) {
        if (nums[i + 1] !== nums[i] + 1) return false;
    }
    // Check size (all the same except the last one)
    var normalSize = parts[0].Size;
    for (var i = 0; i < parts.length - 1; i++) {
        if (parts[i].Size !== normalSize) return false;
    }
    if (parts[parts.length - 1].Size > normalSize) return false;
    return true;
}

Just for future reference, posting Ai code is prohibited here since it almost always writes bad, made-up code that the user is now asking coders to help fix the Ai slop. And I agree with that. We (non-coders) shouldn't waste coders time by posting non-working Ai code for someone else to fix. We (non-coders) think it'll be a good starting point, when it's not.

For someone like myself, who knows zero coding and doesn't have the correct brain to learn it (I've tried for decades), I think it's ok if working Ai code is posted since I'm not asking for any help. Please correct me if I'm wrong.

With that being said, I've had great success with about 4 JScript/VBscript buttons now, using Grok. Grok works best if you tell it to 'think harder' so it reasons with itself. And also to tell it that you're coding a Directory Opus button. I used 'Grok 4 Fast Beta' for this and finished it within an hour, less than 20 questions for troubleshooting and tweaks. I started with your description of what you wanted (just copied and pasted).

You can paste the below JScript code into Grok if you want to make changes OR want each section explained in detail to learn about it, it does a great job explaining things.

I used 6 test folders. Only the test6 folder had clean files, hence why it's not listed.

Example Output:

Multi-Part Archive Folder Completeness Check
Generated: Wednesday, November 5, 2025 2:29:09 PM
Selected folders checked: 6

Incomplete folders:
O:\.Temp\test\test1 - contains 4 non-archive files
O:\.Temp\test\test2 - inconsistent part sizes in 1 part: 3
O:\.Temp\test\test3 - missing 2 parts: 6, 7; inconsistent part sizes in 1 part: 3
O:\.Temp\test\test4 - multiple archive sets detected
O:\.Temp\test\test5 - contains 1 subdirectory; contains 2 non-archive files; no multi-part archives found

The Button:
Create a button and set it to "Script Function", Type is "JScript" and paste in this code:

function OnClick(clickData)
{
    var tab = clickData.func.sourcetab;
    var sel = tab.selected;
    var folders = [];
    var enumSel = new Enumerator(sel);
    for (;!enumSel.atEnd(); enumSel.moveNext()) {
        var item = enumSel.item();
        if (item.is_dir) {
            folders.push(item.realpath);
        }
    }
    if (folders.length == 0) {
        DOpus.Output("No folders selected.");
        return;
    }
    var incomplete = [];
    for (var f=0; f<folders.length; f++) {
        var folderpath = folders[f];
        var fldr = DOpus.FSUtil.ReadDir(folderpath, false);
        var archives = [];
        var issues = [];
        var subdirCount = 0;
        var nonArchiveCount = 0;
        var invalidArchiveCount = 0;
        while (!fldr.complete) {
            var it = fldr.Next();
            if (!it) continue;
            if (it.is_dir) {
                subdirCount++;
                continue;
            }
            var extl = it.ext.toLowerCase();
            if (!(extl == ".rar" || extl == ".zip" || /^\.z\d+$/.test(extl))) {
                nonArchiveCount++;
                continue;
            }
            var name = it.name;
            var base = "";
            var part = 0;
            var archType = "";
            var isValid = false;
            if (extl == ".rar") {
                var match = name.match(/^(.*)\.part([0-9]+)\.rar$/i);
                if (match) {
                    base = match[1];
                    part = parseInt(match[2], 10);
                    archType = "rar";
                    isValid = true;
                }
            } else if (extl == ".zip") {
                var match = name.match(/^(.*)\.zip$/i);
                if (match) {
                    base = match[1];
                    part = 1;
                    archType = "zip";
                    isValid = true;
                }
            } else { // .zNN
                var match = name.match(/^(.*)\.z([0-9]+)$/i);
                if (match) {
                    base = match[1];
                    part = parseInt(match[2], 10);
                    archType = "zip";
                    isValid = true;
                }
            }
            if (!isValid) {
                invalidArchiveCount++;
                continue;
            }
            archives.push({
                base: base,
                part: part,
                type: archType,
                size: it.size.val,
                ext: extl
            });
        }
        if (subdirCount > 0) {
            var subdirText = subdirCount > 1 ? "subdirectories" : "subdirectory";
            issues.push("contains " + subdirCount + " " + subdirText);
        }
        if (nonArchiveCount > 0) {
            var nonArchText = nonArchiveCount > 1 ? "non-archive files" : "non-archive file";
            issues.push("contains " + nonArchiveCount + " " + nonArchText);
        }
        if (invalidArchiveCount > 0) {
            var invArchText = invalidArchiveCount > 1 ? "non-multi-part archives" : "non-multi-part archive";
            issues.push("contains " + invalidArchiveCount + " " + invArchText);
        }
        if (archives.length == 0) {
            issues.push("no multi-part archives found");
        } else if (archives.length == 1) {
            issues.push("insufficient number of parts");
        } else {
            // Check same base
            var base = archives[0].base;
            var baseOk = true;
            for (var i=1; i<archives.length; i++) {
                if (archives[i].base !== base) {
                    baseOk = false;
                    break;
                }
            }
            if (!baseOk) {
                issues.push("multiple archive sets detected");
            } else {
                // Check same type
                var typ = archives[0].type;
                var typeOk = true;
                for (var i=1; i<archives.length; i++) {
                    if (archives[i].type !== typ) {
                        typeOk = false;
                        break;
                    }
                }
                if (!typeOk) {
                    issues.push("mixed archive types");
                }
                // Adjust parts for split ZIP
                if (typ == "zip") {
                    var zipEntry = null;
                    var zMax = 0;
                    for (var k=0; k<archives.length; k++) {
                        if (archives[k].ext == ".zip") {
                            zipEntry = archives[k];
                        } else {
                            zMax = Math.max(zMax, archives[k].part);
                        }
                    }
                    if (zipEntry && zMax > 0) {
                        zipEntry.part = zMax + 1;
                    }
                }
                // Check parts
                var parts = [];
                var dupOk = true;
                for (var i=0; i<archives.length; i++) {
                    var p = archives[i].part;
                    var exists = false;
                    for (var j=0; j<parts.length; j++) {
                        if (parts[j] === p) {
                            exists = true;
                            break;
                        }
                    }
                    if (exists) {
                        dupOk = false;
                    } else {
                        parts.push(p);
                    }
                }
                if (!dupOk) {
                    issues.push("duplicate part numbers");
                }
                if (dupOk) {
                    parts.sort(function(a,b){return a-b;});
                    var maxPart = parts[parts.length-1];
                    var seqOk = (parts[0] === 1 && parts.length === maxPart);
                    if (!seqOk) {
                        var missing = [];
                        for (var p=1; p<=maxPart; p++) {
                            var found = false;
                            for (var j=0; j<parts.length; j++) {
                                if (parts[j] === p) {
                                    found = true;
                                    break;
                                }
                            }
                            if (!found) {
                                missing.push(p);
                            }
                        }
                        var missText = missing.length > 1 ? "parts" : "part";
                        issues.push("missing " + missing.length + " " + missText + ": " + missing.join(", "));
                    }
                }
                // Check sizes
                archives.sort(function(a,b){return a.part - b.part;});
                var common = archives[0].size;
                var inconsistent = [];
                for (var i=0; i<archives.length - 1; i++) {
                    if (archives[i].size !== common) {
                        inconsistent.push(archives[i].part);
                    }
                }
                if (inconsistent.length > 0) {
                    var incText = inconsistent.length > 1 ? "parts" : "part";
                    issues.push("inconsistent part sizes in " + inconsistent.length + " " + incText + ": " + inconsistent.join(", "));
                }
                if (archives.length > 1 && archives[archives.length-1].size > common) {
                    issues.push("last part larger than others");
                }
            }
        }
        if (issues.length > 0) {
            var reason = issues.join("; ");
            incomplete.push(folderpath + " - " + reason);
        }
    }
    var now = new Date();
    function pad(num, size) {
        var s = num + "";
        while (s.length < size) s = "0" + s;
        return s;
    }
    var datestr = now.getFullYear() + "-" + pad(now.getMonth()+1, 2) + "-" + pad(now.getDate(), 2);
    var reportName = "ArchiveCheck_" + datestr + ".txt";
    var reportPath = tab.path + "\\" + reportName;
    var fso = new ActiveXObject("Scripting.FileSystemObject");
    if (fso.FileExists(reportPath)) {
        fso.DeleteFile(reportPath);
    }
    var txtFile = fso.CreateTextFile(reportPath, true);
    txtFile.WriteLine("Multi-Part Archive Folder Completeness Check");
    txtFile.WriteLine("Generated: " + now.toLocaleString());
    txtFile.WriteLine("Selected folders checked: " + folders.length);
    txtFile.WriteLine("");
    if (incomplete.length === 0) {
        txtFile.WriteLine("All selected folders contain complete multi-part archives.");
    } else {
        txtFile.WriteLine("Incomplete folders:");
        for (var i=0; i<incomplete.length; i++) {
            txtFile.WriteLine(incomplete[i]);
        }
        txtFile.WriteLine("");
    }
    txtFile.Close();
    // DOpus.Output("Report created: " + reportPath);
}