Links Relink Subfolders

Script for Adobe InDesign

Search a folder and all subfolders below it for missing links, and update all.

To rename links, see related script Links GREP Rename.
For files renamed outside of InDesign, see related script Links GREP Relink.

  • Replace links located in subfolders
  • Specify folder to search for links
  • Alerts user when multiple files the same name
Download
Links Relink Subfolders

FREE to download
Please make a contribution

Many scripts are free to download thanks to the support of users. Help me keep developing new scripts by supporting my work. Click the button to make a contribution of any amount. Thank you.

How-to Video

How to use the script

The interface is a single section with button to select a folder. The path to the folder selected appears beside the button, which defaults to the Links folder where the document is located. Click the Folder button to select another location. Click the OK button to search the folder for missing links, including all subfolders below it. Any found are relinked and updated in the open InDesign document.

Because subfolders could allow files of the same name to exist, only in different folders, the script checks the file list for duplicates. For any links that have duplicate files the same name, the link is not updated, and the condition is reported in a log file at the location of the InDesign document. An alert appears on screen with the option to open the log file, which is saved as plain text format. Examine the duplicate files listed in the log file to determine which file is the correct link, and then relink it manually.

Source code

(download button below)

/*

Links Relink Subfolders
Copyright 2022 William Campbell
All Rights Reserved
https://www.marspremedia.com/contact

Permission to use, copy, modify, and/or distribute this software
for any purpose with or without fee is hereby granted.

THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.

*/

(function () {

    var title = "Links Relink Subfolders";

    if (!/indesign/i.test(app.name)) {
        alert("Script for InDesign", title, false);
        return;
    }

    // Script variables.
    var count;
    var doc;
    var doneMessage;
    var error; // Error
    var folderLinks; // Folder
    var log;
    var progress;

    // Reusable UI variables.
    var g; // group
    var p; // panel
    var w; // window

    // Permanent UI variables.
    var btnCancel;
    var btnFolderLinks;
    var btnOk;
    var txtFolderLinks;

    // SETUP

    // Script requires open document.
    if (!app.documents.length) {
        alert("Open a document", title, false);
        return;
    }
    doc = app.activeDocument;
    if (!doc.saved) {
        alert("Save document first", title, false);
        return;
    }

    // LOG

    log = {
        entries: [],
        file: null,
        // Default write log to user desktop.
        // Preferably set 'log.path' to a meaningful
        // location in later code once location exists.
        path: Folder.desktop,
        add: function (message) {
            this.entries.push(message);
        },
        write: function () {
            var contents;
            var d;
            var fileName;
            var padZero = function (v) {
                return ("0" + v).slice(-2);
            };
            if (!this.entries.length) {
                // No log entries to report.
                this.file = null;
                return;
            }
            contents = this.entries.join("\r");
            // Create file name.
            d = new Date();
            fileName =
                title +
                " " +
                "Log" +
                " " +
                d.getFullYear() +
                "-" +
                padZero(d.getMonth() + 1) +
                "-" +
                padZero(d.getDate()) +
                "-" +
                padZero(d.getHours()) +
                padZero(d.getMinutes()) +
                padZero(String(d.getSeconds()).substr(0, 2)) +
                ".txt";
            // Open and write log file.
            this.file = new File(this.path + "/" + fileName);
            this.file.encoding = "UTF-8";
            try {
                if (!this.file.open("w")) {
                    throw new Error("Failed to open log file.");
                }
                if (!this.file.write(contents)) {
                    throw new Error("Failed to write log file.");
                }
            } catch (e) {
                this.file = null;
                throw e;
            } finally {
                this.file.close();
            }
            // Log successfully written.
            // log.file == true (not null) indicates a log was written.
        }
    };

    // CREATE PROGRESS WINDOW

    progress = new Window("palette", "Progress", undefined, {
        "closeButton": false
    });
    progress.t = progress.add("statictext");
    progress.t.preferredSize = [450, -1];
    progress.b = progress.add("progressbar");
    progress.b.preferredSize = [450, -1];
    progress.display = function (message) {
        message && (this.t.text = message);
        this.show();
        this.update();
    };
    progress.increment = function () {
        this.b.value++;
    };
    progress.set = function (steps) {
        this.b.value = 0;
        this.b.minvalue = 0;
        this.b.maxvalue = steps;
    };

    // CREATE USER INTERFACE

    w = new Window("dialog", title);
    w.alignChildren = "fill";
    p = w.add("panel", undefined, "Search for links in folder");
    p.alignChildren = "left";
    p.margins = [24, 24, 24, 18];
    g = p.add("group");
    btnFolderLinks = g.add("button", undefined, "Folder...");
    txtFolderLinks = g.add("statictext", undefined, "", {
        truncate: "middle"
    });
    txtFolderLinks.preferredSize = [320, -1];
    g = w.add("group");
    g.alignment = "center";
    btnOk = g.add("button", undefined, "OK");
    btnCancel = g.add("button", undefined, "Cancel");

    // Panel Copyright
    p = w.add("panel");
    p.add("statictext", undefined, "Copyright 2022 William Campbell");

    // SET DEFAULTS

    folderLinks = new Folder(doc.fullName.path + "/Links");
    if (folderLinks.exists) {
        txtFolderLinks.text = Folder.decode(folderLinks.fsName);
    } else {
        folderLinks = null;
    }

    // UI ELEMENT EVENT HANDLERS

    btnFolderLinks.onClick = function () {
        var f = Folder.selectDialog("Select links folder", txtFolderLinks.text);
        if (f) {
            folderLinks = f;
            txtFolderLinks.text = Folder.decode(folderLinks.fsName);
        }
    };
    btnOk.onClick = function () {
        if (!(folderLinks && folderLinks.exists)) {
            alert("Select links folder", " ", false);
            return;
        }
        w.close(1);
    };
    btnCancel.onClick = function () {
        w.close(0);
    };

    // DISPLAY THE DIALOG

    if (w.show() == 1) {
        doneMessage = "";
        try {
            app.doScript(process, ScriptLanguage.JAVASCRIPT, undefined, UndoModes.ENTIRE_SCRIPT, title);
            doneMessage = count + " links relinked";
        } catch (e) {
            error = error || e;
            doneMessage = "An error has occurred.\nLine " + error.line + ": " + error.message;
        }
        progress.close();
        try {
            log.path = doc.fullName.path;
            log.write();
        } catch (e) {
            alert("Error writing log:\n" + e.message, title, true);
        }
        if (log.file) {
            if (
                confirm(doneMessage +
                    "\nAlerts reported. See log for details:\n" +
                    File.decode(log.file.fsName) +
                    "\n\nOpen log?", false, title)
            ) {
                log.file.execute();
            }
        } else {
            doneMessage && alert(doneMessage, title, error);
        }
    }

    //====================================================================
    //               END PROGRAM EXECUTION, BEGIN FUNCTIONS
    //====================================================================

    function getFiles(folder, subfolders, extensions) {
        // folder = folder object, not folder name.
        // subfolders = true to include subfolders.
        // extensions = string, extensions to include.
        // Combine multiple extensions with RegExp OR i.e. jpg|psd|tif
        // extensions case-insensitive.
        // extensions undefined = any.
        // Ignores hidden files and folders.
        var f;
        var files;
        var i;
        var pattern;
        var results = [];
        if (extensions) {
            pattern = new RegExp("\." + extensions + "$", "i");
        } else {
            // Any extension.
            pattern = new RegExp(".*");
        }
        files = folder.getFiles();
        for (i = 0; i < files.length; i++) {
            f = files[i];
            if (!f.hidden) {
                if (f instanceof Folder && subfolders) {
                    // Recursive (function calls itself).
                    results = results.concat(getFiles(f, subfolders, extensions));
                } else if (f instanceof File && pattern.test(f.name)) {
                    results.push(f);
                }
            }
        }
        return results;
    }

    function process() {
        var files = [];
        var fileDupes = [];
        var fileNames = [];
        var i;
        var ii;
        var iii;
        var link;
        var name;
        progress.display("Initializing...");
        try {
            files = getFiles(folderLinks, true);
            // Make array of file names.
            for (i = 0; i < files.length; i++) {
                fileNames[i] = File.decode(files[i].name).toLowerCase();
            }
            // Make array of duplicates.
            for (i = 0; i < fileNames.length; i++) {
                fileDupes[i] = [];
                for (ii = 0; ii < fileNames.length; ii++) {
                    if (i != ii && fileNames[i] == fileNames[ii]) {
                        fileDupes[i].push(ii);
                    }
                }
            }
            progress.set(doc.links.length);
            count = 0;
            // Find the links...
            for (i = 0; i < doc.links.length; i++) {
                link = doc.links[i];
                name = link.name.toLowerCase();
                progress.display(link.name);
                for (ii = 0; ii < fileNames.length; ii++) {
                    if (fileNames[ii] == name) {
                        if (fileDupes[ii].length) {
                            // There are duplicates of this link.
                            // Log it, and don't relink.
                            log.add("Multiple files the same name. Manually update the link to the desired file.");
                            log.add("  " + File.decode(files[ii].fullName));
                            for (iii = 0; iii < fileDupes[ii].length; iii++) {
                                log.add("  " + File.decode(files[fileDupes[ii][iii]].fullName));
                            }
                            break;
                        }
                        // Match. Relink and update.
                        link.relink(files[ii]);
                        link.update();
                        count++;
                        break;
                    }
                }
                progress.increment();
            }
        } catch (e) {
            error = e;
            throw e;
        }
    }

})();
Many scripts are free to download thanks to the support of users. Help me keep developing new scripts by supporting my work. Click the button to make a contribution of any amount. Thank you.
Download
Links Relink Subfolders

For help installing scripts, see How to Install and Use Scripts in Adobe Creative Cloud Applications.

Also available for hire to program custom solutions. Contact William for more information.

IMPORTANT: by downloading any of the scripts on this page you agree that the software is provided without any warranty, express or implied. USE AT YOUR OWN RISK. Always make backups of important data.