Skip to main content
Aureliu
Inspiring
November 26, 2018
Answered

Update linked smart objects broken file path

  • November 26, 2018
  • 3 replies
  • 8469 views

Our main image folder has been renamed, now many linked smart objects have a wrong path, and when opening the psd, photoshop asks me to update the path.

I would like to run a script and change the path to the new one. Is this even possible?

What I have so far, I can read the file path from a linked smart object like this:

var ref = new ActionReference();

ref.putIdentifier(charIDToTypeID('Lyr '), oObj.id );

var desc = executeActionGet(ref);

var smObj = desc.getObjectValue(stringIDToTypeID('smartObject'));

var sLocalFilePath = smObj.getPath(stringIDToTypeID('link'));

But this will give me the path if the file is correctly linked only. For broken file links getPath(stringIDToTypeID('link') is not available.

Is there a way to read the file path, if the linked file has been moved?

And is it possible to update the path via script then?

This topic has been closed for replies.
Correct answer r-bin

Try for active layer.

var new_path = "C:\\A1"; // YOUR REAL NEW PATH

var r = new ActionReference();   

r.putProperty(stringIDToTypeID("property"), stringIDToTypeID("smartObject"));

r.putEnumerated(stringIDToTypeID("layer"), stringIDToTypeID("ordinal"), stringIDToTypeID("targetEnum"));

var d = executeActionGet(r); 

if (d.hasKey(stringIDToTypeID("smartObject")))

    {

    d = d.getObjectValue(stringIDToTypeID("smartObject")); 

    if (d.hasKey(stringIDToTypeID("link")))

        {

        var type = d.getType(stringIDToTypeID("link"));

        var pth;

        switch(type)

            {

            case DescValueType.ALIASTYPE:

                pth = d.getPath(stringIDToTypeID("link"));

                break;

            case DescValueType.STRINGTYPE:

                pth = d.getString(stringIDToTypeID("link"));

                break;

            defualt:

                throw("\n\nHmmm!\n\n");

            }

        var file = new File(pth);

        if (!file.exists)

            {

            file = new File(new_path + "/" + file.name);

            var d = new ActionDescriptor();

            d.putPath(stringIDToTypeID("null"), file);

            executeAction(stringIDToTypeID("placedLayerRelinkToFile"), d, DialogModes.NO);

            alert("Done!")

            }

        else

            alert("File already exists");

        }

    else

        {

        alert("No link");

        }

    }

else

    {   

    alert("No smartObject");

    }

3 replies

Participating Frequently
December 1, 2020

executeActionGet is not working on macOS Catalina + Photoshop 2020. Below error is shown

Is this a known issue?
Please suggest if there is any other solution to update linked file path

 

infomamun
Inspiring
November 8, 2020

Error 8800: General Photoshop error occured. This functionality may not be avalilable in this version of Photoshop.

- The command "Relink to File" is not currently available;

Line 59

-> executeAction(stringIDToTypeID("placedLayerRelinkToFile"), d, DialogModes.NO);

 

I am using Photoshop CC 2017 2017.0.0 Release. Any update how to resolve the above issue?

Legend
November 8, 2020

Is the required layer active?

infomamun
Inspiring
November 8, 2020

Active means visible? If so, Yes...they are visible.

Legend
November 26, 2018

var sLocalFilePath = smObj.getString(stringIDToTypeID('link')); 

Aureliu
AureliuAuthor
Inspiring
November 26, 2018

getString() works! Do you know how to update the path of the smart object?

schroef
Inspiring
December 27, 2020

@Davide_Barranca12040269 or @r-bin 
Thanks for showing this script about relinking. I had also found this script in another forum post

 

 

 

// find files > auto relink
//  https://community.adobe.com/t5/photoshop/place-linked-using-script/td-p/10205968?page=1
var doc = app.activeDocument;
var fName = doc.name.split(".");
var fName = fName[0];
var layerN = doc.activeLayer.name;
var linkFolder = Folder.selectDialog("Selection prompt");
if(linkFolder !== null){
    var linkFolderPath = linkFolder.path;
    var linkName = linkFolder.name;
    var linkFullPath = linkFolderPath+"/"+linkName;
    //alert("Looking for " + layerN + " in\n" + linkFullPath);
    function traverseFolder(path, layerN)  { 
        var folder = new Folder(path); 
        suffix = new Array();
        suffix[0] = "tif";
        suffix[1] = "dng";
        suffix[2] = "psd";
        suffix[3] = "svg";
        suffix[4] = "jpg";
        var files = folder.getFiles(); 
        for (var i = 0; i < files.length; i++) 
        //alert(files[i] instanceof File)
        { 
            if (files[i] instanceof File) 
            {  
                var fullPathName = Folder.decode(files[i]);
                //alert(fullPathName)
                var fileName = files[i].name;
                fileName = decodeURI(fileName);
                //alert(fileName)
                for(var s = 0; s < suffix.length ; s++){
                    search_name = layerN + "." + suffix[s];
                    search_NAME = layerN + "." + suffix[s].toUpperCase();
                    if ((fileName == search_name) || (fileName == search_NAME)) 
                    { 
                        foundFile = fullPathName + "/" + fileName;
                        alert("Found: " + foundFile); 
                        relinkFile(foundFile)
                        return foundFile ;
                    }
                }
            } 
            else 
            { 
               traverseFolder(files[i], layerN); 
               // return traverseFolder(files, layerN); 
            } 
        } 
    }
    function cTID(s) { return app.charIDToTypeID(s); };
    function sTID(s) { return app.stringIDToTypeID(s); };
    function relinkFile(foundFile){
        alert(typeof(foundFile))
        file = new File(foundFile);
        //file = new File(new_path + "/" + file.name);
        var desc475 = new ActionDescriptor();
        //desc475.putPath( cTID('null'), new File( file ) );
        desc475.putPath( cTID('null'), new File( "D:\_MINDFLOW\CLIENTS\Allusion\Design\Recourses\Icons-24x\more.svg" ) );
        //executeAction( sTID('placedLayerRelinkToFile'), desc475, DialogModes.NO );// replace link
        executeAction( sTID('placedLayerReplaceContents'), desc475, DialogModes.NO );// replace content
        //https://community.adobe.com/t5/photoshop/place-linked-using-script/td-p/10205968?page=2
    //     var idplacedLayerRelinkToFile = stringIDToTypeID( "placedLayerRelinkToFile" );
    //     var desc121 = new ActionDescriptor();
    //     var idnull = charIDToTypeID( "null" );
    //     desc121.putPath( idnull, new File( foundFile ) );
    //     executeAction( idplacedLayerRelinkToFile, desc121, DialogModes.NO );
    }

    var foundFile = traverseFolder(linkFullPath, layerN);
    alert("Found: " + foundFile);
}

 

 

Minus my trial and error codes i added to catch patch etc.

In both of the version i believe no dialog should show right? i see DialogModes.NO. Does any one know if this perhaps due the nature of that function? 
Im also trying to bulk rellink images in a PSD file. My reason is that i work on both Windows as OSX since 2-3 weeks. Now lots of my design cause issues because the different paths 😞

The the great thing abou tthis example scripts is that it cycles through the files and if its the same it will relink. However it still promts for relinking the image, kinda destroys the use of the script. 

 

I also tried using scriptlistener and then fix the script making it read easier. Again it will show the dialog.

After typing this i tried on more go, this time using the original scriptlistener output. This however seems to work just fine. Now im wondering why the cleaned version does show a dialog?!?!

 

// =======================================================
var idplacedLayerReplaceContents = stringIDToTypeID( "placedLayerReplaceContents" );
    var desc539 = new ActionDescriptor();
    var idnull = charIDToTypeID( "null" );
    desc539.putPath( idnull, new File( "D:\\_MINDFLOW\\CLIENTS\\Allusion\\Design\\Recourses\\Icons-24x\\more.svg" ) );
executeAction( idplacedLayerReplaceContents, desc539, DialogModes.NO );

 

 

EDIT 2

ow now i see, the paths have double backslashes, is that to escape them or so?
So perhaps when i use some kind of regex or replace function i could fix the path. Lets hope that works.

Would be nice if i eventually end up with a version similar to one of InDesign, there you can point to a folder and will automatically link all the missing links.


okay got it working now. perhaps i need to tweak it a bit more and fine tune it. I used parts of the layercomps script from photoshop to go over the selected layers. Combined with parts of that other scripts, i striped out his method for getting path because that was actually causing the issues. The wrong usage of the back and forward slashes. 

 

i paste it here so it can perhaps help other in need. I have not done time trials on it to see where i could speed things up. Would be interesting to see. I believe that the function which gets the layer info already loops over the layers, we could already do the linking there i guess. Perhaps ill try a variant of this one and do some test with say 100 missing links or so. I got some interesting results on a different script i made. There i used the time trials to decide wether or not to show progress bar. On OSX thats one big headache since it needs to refresh the app and that slows it down A LOT!!

 

 

Ive added some extra parts so it show in the main menu of script sub menu

 

// Collection of scripts i gathered for quick relinken missing images
// Rombout Versluijs Dec 2020


/*
@@@BUILDINFO@@@ Restore Missing Links.jsx 0.0.0.7
*/
/*
// BEGIN__HARVEST_EXCEPTION_ZSTRING
/*
<javascriptresource>
<name>$$$/JavaScripts/RestoreMissingLinks/Menu=Restore Missing Links...</name>
<category>aaaThisPutsMeAtTheTopOfTheMenu</category>
//<category>Restore</category>
<enableinfo>true</enableinfo>
</javascriptresource>

// END__HARVEST_EXCEPTION_ZSTRING

*/


#target photoshop

// var docName = app.activeDocument.name;
// app.activeDocument = app.documents[docName];
docRef = app.activeDocument;

//var new_path = "D:\\_MINDFLOW\\CLIENTS\\Allusion\\Design\\Recourses\\Icons-24x"; // YOUR REAL NEW PATH
//alert(new_path)
function cTID(s) { return app.charIDToTypeID(s); };
function sTID(s) { return app.stringIDToTypeID(s); };


///////////////////////////////////////////////////
// Get Layer Kind
// https://www.ps-scripts.com/viewtopic.php?p=43242#p43242
// Get return layerkind by layerid
///////////////////////////////////////////////////
function getLayerKindByIndex(index) {
    var ref, desc, adjustmentDesc, layerSectionType;
    ref = new ActionReference();
    ref.putIndex(charIDToTypeID("Lyr "), index);
    desc = executeActionGet(ref);
    var layerType = typeIDToStringID(desc.getEnumerationValue(stringIDToTypeID('layerSection')));
    if (layerType != 'layerSectionContent') return; // return if layerSet
    if (desc.hasKey(stringIDToTypeID('textKey'))) return LayerKind.TEXT;
    if (desc.hasKey(stringIDToTypeID('smartObject'))) return LayerKind.SMARTOBJECT; // includes LayerKind.VIDEO
    if (desc.hasKey(stringIDToTypeID('layer3D'))) return LayerKind.LAYER3D;
    if (desc.hasKey(stringIDToTypeID('videoLayer'))) return LayerKind.VIDEO;
    if (desc.hasKey(stringIDToTypeID('adjustment'))) {
        switch (typeIDToStringID(desc.getList(stringIDToTypeID('adjustment')).getClass(0))) {
            case 'photoFilter':
                return LayerKind.PHOTOFILTER;
            case 'solidColorLayer':
                return LayerKind.SOLIDFILL;
            case 'gradientMapClass':
                return LayerKind.GRADIENTMAP;
            case 'gradientMapLayer':
                return LayerKind.GRADIENTFILL;
            case 'hueSaturation':
                return LayerKind.HUESATURATION;
            case 'colorLookup':
                return udefined; //this does not exist and errors with getting layer kind
            case 'colorBalance':
                return LayerKind.COLORBALANCE;
            case 'patternLayer':
                return LayerKind.PATTERNFILL;
            case 'invert':
                return LayerKind.INVERSION;
            case 'posterization':
                return LayerKind.POSTERIZE;
            case 'thresholdClassEvent':
                return LayerKind.THRESHOLD;
            case 'blackAndWhite':
                return LayerKind.BLACKANDWHITE;
            case 'selectiveColor':
                return LayerKind.SELECTIVECOLOR;
            case 'vibrance':
                return LayerKind.VIBRANCE;
            case 'brightnessEvent':
                return LayerKind.BRIGHTNESSCONTRAST;
            case 'channelMixer':
                return LayerKind.CHANNELMIXER;
            case 'curves':
                return LayerKind.CURVES;
            case 'exposure':
                return LayerKind.EXPOSURE;
                // if not one of the above adjustments return - adjustment layer type
            default:
                return typeIDToStringID(desc.getList(stringIDToTypeID('adjustment')).getClass(0));
        }
    }
    return LayerKind.NORMAL; // if we get here normal should be the only choice left.
};



/// ////////////////////////////////////////////////////////////////////////////
// Function: Combination of applyToAllLayersAMIdx & getLayerInfo
// Usage: extract a list of index values of all the selected layers & ID layernamer
// Input:: (active document.) s
// Return: array of indexes ID"s of selected layers.
// Mixed this so it only loops once over all layers
/// ////////////////////////////////////////////////////////////////////////////
function applyToAllLayersInfo(docRef) {
    // alert(docRef)
    var selectedLayers = new Array()
    var ref = new ActionReference()
    // get a number list of selected artLayers in the document
    ref.putProperty(app.charIDToTypeID("Prpr"), stringIDToTypeID("targetLayers"))
    ref.putEnumerated(charIDToTypeID("Dcmn"), charIDToTypeID("Ordn"), charIDToTypeID("Trgt"))
    // what do I want to do this this list? Define an description of an action.
    var desc = executeActionGet(ref)
    // if the selected object has the "Target Layers" key (only works CS4+)
    if (desc.hasKey(stringIDToTypeID("targetLayers"))) {
        desc = desc.getList(stringIDToTypeID("targetLayers"))
        var c = desc.count
        var selectedLayers = [] // for each
        for (var i = 0; i < c; i++) {
            var lyr = {}
            var lyrIndex;
            try {
                docRef.backgroundLayer // try to select a background layer, if I can then adjust the index counting. (Background layers change index counitng of all layers by 1)
                // selectedLayers.push(desc.getReference(i).getIndex())
                lyrIndex = desc.getReference(i).getIndex();
            } catch (e) {
                // selectedLayers.push(desc.getReference(i).getIndex() + 1)
                lyrIndex = desc.getReference(i).getIndex() + 1;
            }
            // Added from getLayerInfo
            var lyrRef = new ActionReference();
            lyrRef.putIndex(charIDToTypeID("Lyr "), lyrIndex)
            var lyrDesc = executeActionGet(lyrRef);
            // alert(lyrDesc.getInteger(stringIDToTypeID("layerKind")))
            var Id = lyrDesc.getInteger(stringIDToTypeID("layerID"));
            lyr.AMid = Id;
            lyr.lyrKind = getLayerKindByIndex(lyrIndex);
            lyr.lyrIndex = lyrIndex;
            selectedLayers.push(lyr);
        }
    }
    return selectedLayers
}



///////////////////////////////////////////////////
// Select Layer by LayerIndex
// Source: https://stackoverflow.com/questions/26295492/photoshop-script-new-layer-below-current-layer
// select [LayerNum], optionally [add] to selection (if add=2: with inclusion)
///////////////////////////////////////////////////
function selLyr(LyrN, add) {
    var adesc = new ActionDescriptor();
    var aref = new ActionReference();
    aref.putIndex(charIDToTypeID("Lyr "), LyrN);
    adesc.putReference(charIDToTypeID("null"), aref);
    if (add) {
        add = (add == 2) ? stringIDToTypeID("addToSelectionContinuous") : stringIDToTypeID("addToSelection");
        adesc.putEnumerated(stringIDToTypeID("selectionModifier"), stringIDToTypeID("selectionModifierType"), add);
    }
    adesc.putBoolean(charIDToTypeID("MkVs"), false);
    return executeAction(charIDToTypeID("slct"), adesc, DialogModes.NO);
}



///////////////////////////////////////////////////
// Select Layers by LayerID
// Source: https://graphicdesign.stackexchange.com/questions/130739/photoshop-scripting-applying-changes-only-to-selected-artboards
///////////////////////////////////////////////////
function selectById(AMid) {
    var desc1 = new ActionDescriptor();
    var ref1 = new ActionReference();
    ref1.putIdentifier(charIDToTypeID('Lyr '), AMid);
    desc1.putReference(charIDToTypeID('null'), ref1);
    executeAction(charIDToTypeID('slct'), desc1, DialogModes.NO);
}


/// ////////////////////////////////////////////////////////////////////////////
// Function: getSelectedLayersAMIdx
// Usage: extract a list of index values of all the selected layers.
// Input:: (active document.) s
// Return: array of indexes ID"s of selected layers.
/// ////////////////////////////////////////////////////////////////////////////
function getSelectedLayersAMIdx(docRef) {
    var selectedLayers = new Array()
    var ref = new ActionReference()
    // get a number list of selected artLayers in the document
    ref.putProperty(app.charIDToTypeID("Prpr"), stringIDToTypeID("targetLayers"))
    ref.putEnumerated(charIDToTypeID("Dcmn"), charIDToTypeID("Ordn"), charIDToTypeID("Trgt"))
    // what do I want to do this this list? Define an description of an action.
    var desc = executeActionGet(ref)
    // if the selected object has the "Target Layers" key (only works CS4+)
    if (desc.hasKey(stringIDToTypeID("targetLayers"))) {
        desc = desc.getList(stringIDToTypeID("targetLayers"))
        var c = desc.count
        var selectedLayers = [] // for each
        for (var i = 0; i < c; i++) {
            try {
                docRef.backgroundLayer // try to select a background layer, if I can then adjust the index counting. (Background layers change index counitng of all layers by 1)
                selectedLayers.push(desc.getReference(i).getIndex())
            } catch (e) {
                selectedLayers.push(desc.getReference(i).getIndex() + 1)
            }
        }
    }
    return selectedLayers
}



/// ////////////////////////////////////////////////////////////////////////////
// Function: relinkFiles
// Usage: relinks path to missing linked image.
// Input:: string of path to found image
// Return: correct linked image
// source: https://community.adobe.com/t5/photoshop/update-linked-smart-objects-broken-file-path/m-p/11707888?page=1#M213131
/// ////////////////////////////////////////////////////////////////////////////
function relinkFiles() {
    var r = new ActionReference();
    r.putProperty(stringIDToTypeID("property"), stringIDToTypeID("smartObject"));
    r.putEnumerated(stringIDToTypeID("layer"), stringIDToTypeID("ordinal"), stringIDToTypeID("targetEnum"));
    var d = executeActionGet(r);
    if (d.hasKey(stringIDToTypeID("smartObject"))) {
        d = d.getObjectValue(stringIDToTypeID("smartObject"));
        if (d.hasKey(stringIDToTypeID("link"))) {
            var type = d.getType(stringIDToTypeID("link"));
            var pth;
            switch (type) {
                case DescValueType.ALIASTYPE:
                    pth = d.getPath(stringIDToTypeID("link"));
                    break;
                case DescValueType.STRINGTYPE:
                    pth = d.getString(stringIDToTypeID("link"));
                    break;
                default:
                    throw ("\n\nHmmm!\n\n");
            }
            var file = new File(pth);
            if (!file.exists) {
                file = new File(new_path + "\\" + file.name);
                var d = new ActionDescriptor();
                d.putPath(stringIDToTypeID("null"), file);
                //executeAction(stringIDToTypeID("placedLayerRelinkToFile"), d, DialogModes.NO);// replace link
                executeAction(stringIDToTypeID("placedLayerReplaceContents"), d, DialogModes.NO); // replace content
                alert("Done!")
            } else
                alert("File already exists");
        } else {
            alert("No link");
        }
    } else {
        alert("No smartObject");
    }
}


/// ////////////////////////////////////////////////////////////////////////////
// Function: searchFolder
// Usage: prompt for folder and loop over files
// Input:: folder path
// Return: correct linked image
// source: https://community.adobe.com/t5/photoshop/place-linked-using-script/td-p/10205968?page=1
/// ////////////////////////////////////////////////////////////////////////////
var linkFolder = Folder.selectDialog("Selection prompt");
// alert(linkFolder)
function searchFolder(){
    var layerN = docRef.activeLayer.name;
    if(linkFolder !== null){
        var linkFolderPath = linkFolder.path;
        var linkName = linkFolder.name;
        var linkFullPath = linkFolderPath+"/"+linkName;
        function traverseFolder(path, layerN)  { 
            var folder = new Folder(path); 
            suffix = new Array();
            suffix[0] = "tif";
            suffix[1] = "dng";
            suffix[2] = "psd";
            suffix[3] = "svg";
            suffix[4] = "jpg";
            var files = folder.getFiles(); 
            for (var i = 0; i < files.length; i++) { 
                if (files[i] instanceof File) {  
                    var fullPathName = Folder.decode(files[i]);
                    //alert(fullPathName)
                    var fileName = files[i].name;
                    //alert(fileName)
                    fileName = decodeURI(fileName);
                    //alert(fileName)
                    for(var s = 0; s < suffix.length ; s++){
                        search_name = layerN + "." + suffix[s];
                        search_NAME = layerN + "." + suffix[s].toUpperCase();
                        if ((fileName == search_name) || (fileName == search_NAME)) 
                        { 
                            foundFile = fullPathName + "/" + fileName;
                            //alert("Found: " + foundFile); 
                            //alert("Found: " + files[i]); 
                            relinkFile(files[i])
                            return files[i] ;
                        }
                    }
                } else { 
                traverseFolder(files[i], layerN); 
                // return traverseFolder(files, layerN); 
                } 
            } 
        }
        
        function relinkFile(foundFile){
            var d = new ActionDescriptor();
            d.putPath( cTID('null'), new File( foundFile ) );
            executeAction(stringIDToTypeID("placedLayerRelinkToFile"), d, DialogModes.NO);// replace link
            // executeAction( sTID('placedLayerReplaceContents'), d, DialogModes.NO );// replace content
        }
        var foundFile = traverseFolder(linkFullPath, layerN);
        //alert(foundFile)
    }
}

var layerInfo = applyToAllLayersInfo(docRef);
for (var i = 0; i < layerInfo.length; i++) {
    selectById(layerInfo[i].AMid);
    searchFolder()
}