Oh my god. It's full of code!

Latest

They said it couldn’t be done. Reflection in Apex

There has been holy grail I’ve chased a few times since I started writing really dynamic Apex, or trying to. That is the ability to have some kind of reflection in Apex. For those who don’t know reflection is basically the ability for code to describe code. In this case specifically getting proper descriptions of your custom classes, including properties and data types. It’s been known for a while that by using the JSON serialize, deserialize you can at least get the properties of your classes but no real way to get data types. Sure you can dynamically instantiate the class using the Type.forName() method, and you can check values using instanceOf but together they still don’t really make a complete solution. You can only run instanceOf on actual values not the properties themselves so you have to populate the property to test it, and if you don’t know it’s type you don’t know how to populate it, very chicken and egg problem.

I’ve seen numerous attempts at solving this, usually using some level of brute force or extracting information from errors using try/catch but nothing that quite nailed it. There are some that require you to modify your classes to maintain an internal mapping of values, etc. I didn’t want that. I wanted a stand alone method that can describe your classes without needing to modify them at all. This is that. Mostly.

You might be asking yourself, what’s the point? Why even bother with this? Honestly, in most cases that’s a fair question, I could see some use cases for being able to dynamically populate object properties at run time with placeholder values for test classes and such but, yeah it’s kind of a weird goal that at first glance seems pointless. In this instance however, I actually have a pretty good reason, I think. I wanted to make object definitions for my wrapper classes and sObjects that could be used to create Javascript objects so that my Lightning Web Components can have the same ‘types’ as my Apex classes. This should theoretically help with ensuring the right data is coming from and going to my AuraEnabled methods as well as hopefully IDE auto complete and such. Plus being able to instantiate ‘real objects’ instead of just generics feels more pro, and that’s half of what development is all about right? What I want(ed) was some method that I can just feed in my list of classes/sObjects I care about and it spits out a Javascript file with object definitions, exports, constructors, etc. Now what I’ve come up with I just got to work last night and it’s still a little rough around the edges (and badly needs recursion to follow the object property train of nested objects) but it’s a proof of concept that works well enough to be a jumping off point.

So to keep your attention, here is a sample of what the output looks like.

And the Apex source input



You can see it’s a Javascript object with all properties defined and the datatypes annotated. If I were using typescript (I don’t normally, as a consultant I try to not introduce frameworks and such to keep the deployments light and easier to manage). But now my IDE can know what a AttendanceTrackingWrapper is, I can easily see what data types are, I can easily construct one and if needed extend it with other methods. You can see the constructor which accepts either an object with matching properties, or even a JSON string that can be parsed into one of these objects, and can be told to suppress nulls (so if you use one of these in an update statement you don’t end up wiping out existing field values). It even generates your export and import statements.


How does it handle regular sObjects? Even better.

Pretty cool eh? So now you can do something like

And end up with a real object, with a type and everything! Pretty cool eh?

So if you want to check it out, here is the GitHub link. Full disclosure, as I mentioned this is super hot off the presses stuff, I just got it working last night and have been tweaking it this morning.

https://github.com/Kenji776/ApexToJavascriptObjectGenerator

I’m definitely open to suggestions/improvements/forks as I am pushing my knowledge in proper javascript here and could probably be doing things better. To use it, simply load the script into your org, either through execute anonymous or in a class. Then call

buildJavascriptClassDefinitions(list classNames)

With your list of class names (custom/inner classes need to have their full qualified names, like ApexClassFile.MyClassName). Then open the debug log and then use file to ‘view raw log’. In there, near the bottom it should spit our all the contents of what will become your objects definition file. In a future release (like probably this weekend) I’ll set it up so it emails you with the results so you don’t have to dig them out of the log.

Normally this is the point where I’d explain how it works but uh,

A) I’m not really proud of the approach that ended up working. It’s a gross hack.
B) I’ve been chasing how to do this for literally years, and now despite sharing the output I almost want to guard my secret lol.

So there you have it, well at least the first incarnation of ‘it’. Till next time.

-Kenji

LWC Development Debug Tip

So this is one of those little revelations that’s either going to be super helpful for a lot of folks, or show that I’ve been lacking some fundamental knowledge for far too long and everyone already knows this or has a better way already…

When developing Lightning Web Components, for me one of the largest annoyances is that you can’t really interact with your component much through the browsers debug console, or at least I couldn’t figure out how to. You were sort of limited to just console.log statements to get data about the state of things and you couldn’t quickly/easily test things like this.template.querySelector() to try and find tune the selection criteria without having to just trial and error slowly through it. None of the functions or variables are available so your hands are pretty tied for debugging. I always figured there had to be a better way I just couldn’t figure out how to look for it and eventually got used to slow tedious debugging. Change something, add a console.log, refresh, evaluate results.

Recently, I found out that if you interrupt the chain of execution while LWC is processing, such as by adding break points or catching an error using the ‘sources’ tab in chrome you can now interact with your LWC component methods and variables. This in itself was already pretty helpful and it also made it clear that you can get at LWC information from the console, you just have to sort of do while still in the Lightning call stack (I think that’s what that result means anyway, I dunno, LWC’s magic is of the dark variety).

Which then brings us to the actual meat of this article, the ‘debugger’ command. I was looking for an easy way to interrupt the call stack so I could do my debugging without having to throw errors. I ran across the ‘debugger’ command which basically acts like a callable break point. This immediately stops execution and leaves your console in the LWCs “scope” (I think), so you can call your functions, query elements, etc. The only little ‘problem’ is that once you move past the debugger statement your back in the dark, so my final little way to make this the most useful is just to create a button on my LWC as a I develop can call a debugger, like so.

<!– Component HTML –>
<lightning-button variant=”brand-outline” label=”Debug Console” title=”” onclick={loadDebugConsole} class=”slds-m-left_x-small”></lightning-button>

<!– Controller –>
loadDebugConsole(){
debugger;
}

Click but button, immediately your console is now in the LWC’s world so all it’s goodness is available to you. You can call your methods, you can print your variables you can… do other cool stuff probably. So anyway, I hope that proves as useful to others as it had for me because I can’t believe I went this long without being able to do proper interactive debugging. Till next time, and happy holidays!

-Kenji

Salesforce/Github Project Helper Thingy

Announcing, a new a tool that probably nobody will use and I put way too much time into! It’s another nodeJS utility, this time to help setup SFDX projects, link them with Github and retrieve/deploy changesets/package.xml files as branches!

Now your asking, wait, what problem does this solve? Why does this exist? Well here is the deal, in my opinion setting up a new SFDX project and correlated Github repo is kind of a pain. Also when dealing with a project where some folks are making changes in your Salesforce org, and you need to get those changes into Github that can also be a bit of work. You either have to teach them to use git, and sfdx, deal with Github, etc or do it for them. Even for us developers it can be annoying. So what this does is automate that all away (hopefully). You can download the script, modify the config file with your project information and it will take care of setting up your project for you. Then you can do your work in your salesforce org and either create a change set, or a package XML containing all your changes. You can then tell the program to download said change set, or feed it the package.xml and it will do the work of downloading the content, creating a branch, committing the changes, and pushing them to Github. This will enable BA/Admin folks on your team to easily get their work into your Github project without needing a developer to help them.

This is still very much in development, but I wanted to get it out there in the world before I lose interest and abandon it. Feel free to play with it and provide any feedback as I barely know what I’m doing. Make sure you have NodeJS, SFDX CLI, and Git installed. Then populate the config.json file with your information to connect to SF and Github and marvel as it’s command line goodness while it connects everything and automagically deploys your code into Github.

https://github.com/Kenji776/SF-Github-Project-Helper

Shamless plug

Hey all,

I finally setup my buymeacoffee page. If I’ve helped you and you’d like to show a little extra appreciation beyond your readership consider a small contribution at

https://www.buymeacoffee.com/7ItHrAHzRV

of course absolutely no obligation, I’m just happy to share my mostly useless creations and thoughts but its there if you feel like it. Thanks so much for reading. Till next time.

-Kenji776

Salesforce Class With Reflection / Dynamic Property Access

So the other day I was working on an API that created a somewhat complicated nested class that had sObjects as properties that needed to be inserted. So from those objects I needed to extract all the sObjects (some individual sObjects, and some lists of sObjects) and put them into lists that could be inserted via DML. To extract all those sObjects I needed a separate function that would loop over a property in the object, extract the goods and return them. I didn’t like how I had to have a different function to deal with each property in the object since Apex doesn’t support dynamic/run-time evaluation of class properties. Like how in javascript you can specify a property at runtime with

Object[propertyName] = ‘some value’;

there isn’t an equivalent in Apex. So if I wanted to get all the sObjects in my object.account properties I needed a different loop/function than the one that would get all my sObjects in my object.childObjects.contacts property etc. After a bit of thinking and digging around I came up with a way to allow for getting object properties dynamically. This is composed of a class that basically serializes itself into JSON and uses that to dynamically extract the desired property name. I also wrote another function for extracting all the sObjects from said object even if the property name is deeply nested. To use it, simply create your object and make it extend DynamicStruct. It will then have access to the methods:

getProp(propName) Gets a specific property from the class.
getProperties() Gets all the properties of the class
getSobjectType(propName) Gets the sObject type of a property if it is an sObject
getSObjectInstanceOfProp(propName) Gets an sObject instance of the type of the given property
getClassName() Gets the name of the class that extended the DynamicStruct

Now for the record, this solution ended up being much more complicated than just have a couple of loops. I can’t say I’d recommend using this code for that purpose but it may find a reason to exist in some other project. After putting in all the work to make this I didn’t just want to throw it out. To weird to live, to rare to die etc.

Here is the class and the related methods. (sorry about the lack of color, still trying to figure out a good code posting solution for wordpress since this new block editing system is messing everything up).

public with sharing class DynamicStructExample {
    /**
    * @Description abstract class that allows for dynamic getting of objects properties which eliminates the needs for specific functions to iterate and get all the class data.
    * Because we have a list of wrapper objects, each of which contain sObjects and lists of sObjects that need to be all collected into a list for upsert we needed looping functions
    * to iterate over each property in the wrappers, extract the objects and add them to a list. Due to not being able to make dynamic references to object properties (like in
    * javascript how you can do object[variablePropertyName]) we had to have a different function for each list that needed to be built. So this class uses JSON serialization/deserialization
    * to dynamically cast the object to a map<string,object> which can be referenced dynamically to get property names at runtime and reduce the overall number of needed functions
    * for record collection and eventual DML operation.
    */
    public abstract class DynamicStruct{
        //gets the value of a specific property from the object
        public Object getProp(String propName){
            Map<String, Object> untyped_instance = getDynamicObjectRef();
            return untyped_instance.get(propName);
        }

        //gets all the properties on the object
        public set<string> getProperties(){
            Map<String, Object> untyped_instance = getDynamicObjectRef();         
            return untyped_instance.keySet();
        }

        //gets the sObject type of a property if it is an sObject
        public string getSobjectType(String propName){
            Map<String, Object> untyped_instance = getDynamicObjectRef();
            Map<String, Object> objectProperty = (Map<String, Object>) untyped_instance.get(propName);
            Map<String, Object> attributes = (Map<string,Object>) objectProperty.get('attributes');
            return (string) attributes.get('type');
        }

        //gets the sObject at the given property
        public sObject getSobjectInstanceOfProp(String propName){         
            string typeName = this.getSobjectType(propName);
            sObject thisObj = (Sobject) JSON.deserialize(JSON.serialize( this.getProp(propName) ), Type.forName('Schema.'+typeName));
            return thisObj;
        }

        public map<string,Object> toMap(){
            return (map<string,Object>) JSON.deserializeUntyped(JSON.serialize(this));
        }

        public Matcher generateMatcher(String firstLine)
        {
            return Pattern.compile(
                '(?i)^(?:class\\.)?([^.]+)\\.?([^\\.\\:]+)?[\\.\\:]?([^\\.\\:]*): line (\\d+), column (\\d+)$'
            ).matcher(firstLine);
        }

        public string getClassName(){
            Matcher m = generateMatcher(
            new DmlException()
                .getStackTraceString()
                .substringAfter('\n')
                .substringBefore('\n')
            );
            if (m.find()){
                if (String.isBlank(m.group(3)))  return m.group(1);
                else return m.group(1) + '.' + m.group(2);
            } 
            return '';      
        }

        //iternal serialized/deserializer needed for object reflection
        private Map<String, Object> getDynamicObjectRef(){
            String json_instance = Json.serialize(this);
            return (Map<String, Object>)JSON.deserializeUntyped(json_instance);
        }
    }
	
    /**
    * @Description Gets all the sObjects from all the given DynamicStructs from the given property name. Supports sub properties using . notation. Such as grandParent.parent.child.value
    * @Param a list of objects that extend the DynamicStruct class that have lists of sObjects or single sObjects to get and return as one big list
    * @Param the name of the property in the objects that contains your list or singular sObject using . notation.
    * @Return all the sObjects in all the wrapperObjects in the given propertyName
    */
    private static list<sObject> getDynamicStructSObjects(list<DynamicStruct> wrapperObjects, string propertyName, string objectType){
        list<sObject> returnObjects = new list<sObject>();
        for(DynamicStruct thisWrapper : wrapperObjects){
            //returnObjects.addAll( getSobjectsFromWrapper( thisWrapper, propertyName) );
            returnObjects.addAll( sObjectsFromWrapper( thisWrapper, propertyName, objectType) );
        }
        return returnObjects;
    }

    /**
    * @Description gets all the sObjects from a given DynamicStruct extended object in the given property name.
    * NOTE: Reading of sub-objects is currently not functional. I had it working in the past somehow (I think) but I'm really not sure how I did it. 
    * @Param wrapperObject a single object that extends the DynamicStruct class that contains sObject(s) to create a list from. This is mean to be invoked recursivly.
    * @Param propertyName the name of the property in the objects that contains your list or singular sObject using . notation.
    * @Return all the sObjects in that property of the wrapperObject
    */
    private static list<sObject> sObjectsFromWrapper(object inputObject, string propertyName, string objectType){
        list<sObject> returnObjects = new list<sObject>();
        list<string> propertyPath = propertyName.split('\\.');
        if(inputObject == null) return returnObjects; 
        map<string,object> objectData = (map<string,object>) JSON.deserializeUntyped(JSON.serialize(inputObject));
        string propertyToRead = propertyPath[0];

        if(!objectData.containsKey(propertyToRead)) throw new DynamicStructException('Property Named ' + propertyToRead + ' is either not an sObject or list of sObjects or does not exist in object. Valid property names are ' + objectData.keySet() + '.');
        //if we are trying to access a sub property we need to recurse and pass this object back into this functions
        if(propertyPath.size() > 1){     
            object subObject = objectData.get(propertyToRead);
            propertyPath.remove(0);
            string newPath = String.join(propertyPath,'.');
            returnObjects = sObjectsFromWrapper(subObject, newPath, objectType);            
        }
        else{
            object thisProp = objectData.get(propertyName);

            //if this object is a map<string,object> that means its a single sObject so cast it and add it to the list
            if(thisProp instanceof map<String,Object>) {              
                //dynamically cast the property to the correct sObject type
                returnObjects.add((Sobject) JSON.deserialize(JSON.serialize( thisProp ), Type.forName('Schema.'+objectType) ));
            }

            //if this object is a list<map,string,object>> that means its a list of sObjects so add them all to the list
            else if(thisProp instanceof list<Object> || thisProp instanceOf list<sObject>) {
                returnObjects.addAll( (list<Sobject>) JSON.deserialize(JSON.serialize( thisProp ), Type.forName('List<Schema.'+objectType+'>') ));    
            }        
        }
        return returnObjects; 
    }

	public class wrapperClass extends DynamicStruct{
        public string objectLabel = 'Wrapper Class';
		public Account accountRecord;
		public subClass subObjects = new subClass();
        public string className = this.getClassName();
	}
	
	public class subClass extends DynamicStruct{
        public string objectLabel = 'Sub Class';
        public string className = this.getClassName();
		public list<Contact> contacts = new list<Contact>();
	}
	
	public static void getObjectsTest(){
		list<wrapperClass> examples = new list<wrapperClass>();
		
        //create some sample accounts
		for(integer i = 0; i < 1; i++){
			wrapperClass thisExample = new wrapperClass();
			thisExample.accountRecord = new Account(Name='Test Account ' + i);


            //create some sample contacts
            for(integer j = 0; j < 3; j++){
                Contact newContact = new Contact(FirstName='John',Lastname='Doe '+ i +'-'+j,Account=thisExample.accountRecord);
                thisExample.subObjects.contacts.add(newContact);                
            }
            examples.add(thisExample);
		}

        //get all of the accounts on all of our wrapper class objects
        list<Account>  accounts = (list<Account>) getDynamicStructSObjects(examples,'accountRecord', 'Account');
        system.debug('\n\n\n---- Got all accounts!');
        system.debug(accounts);

        //get all of the contacts on all of our wrapper class objects
		list<Contact>  contacts = (list<Contact>) getDynamicStructSObjects(examples,'subObjects.contacts', 'Contact');
        system.debug('\n\n\n---- Got all contacts!');
        system.debug(contacts);




        system.debug('\n\n All class properties (Should be accountRecord, objectLabel, subObjects): ' + examples[0].getProperties());
        system.debug('\n\n sObject type of accountRecord property (should be "Account"): ' + examples[0].getSobjectType('accountRecord'));

        system.debug(examples[0]);
	}

    public class DynamicStructException extends Exception {} 
}


So as you’ll probably notice my functions for extracting sObjects from the wrapper classes don’t even care if you pass in one of my fancy DynamicStructs or not. That’s because as I was writing this and putting together my example code I stumbled across an issue with my recursion code. Basically I wasn’t able to cast the fetched sub-property to the DynamicStruct object type (it kept coming back as a list<string,object> and there was no way to coerce it to the right type since DynamicStruct is abstract and I didn’t know what the actual type of the object was). Anyway after fooling around with the getObjectsFromWrapper method I decided it was actually easier to just pass in a plain ol’ object and do my serializing/getting/parsing from there.

So there, it’s two nifty things in one. You’ve got an Apex class with some basic reflection abilities and a method for extracting sObjects from a generic object with the ability to extract them from arbitrarily determined depth in your wrapper class. Stick that in your dynamic pipe and recursively smoke it.

Till next time.
-Kenji

Salesforce Connected App JWT oAuth Tool

I recently had to configure a custom Apex REST API I wrote to be secured by a Salesforce Connected app using certificate/JWT flow. I followed the excellent guide over at

https://salesforcecentral.com/accessing-salesforce-with-jwt-oauth-flow/

as well as the complimentary article at

https://mannharleen.github.io/2020-03-03-salesforce-jwt/

and while doing so it seemed to be there were a lot of manual steps and places that I, a dumb human could mess up some value or just not do something right. I fought with it a bit more than I felt I should have to and it seemed like others might have some of the same problems. So I said,

“Hey, me you’re kind dumb at following directions, but you are pretty okay at writing code. You should do that instead.”

So I did. As a result I’ve ended up with this.

It’s a simple node.js app that helps you authorize your user so you can avoid the dreaded

{"error":"invalid_grant","error_description":"user hasn't approved this consumer"}

error. It can also generate your JWT for you and perform a sample call to make sure it works. It logs all the callouts it makes so you can see what it’s doing and replicate those steps anywhere you need to. I just tossed this together in a couple hours so it’s not perfect but if you are attempting to configure a Salesforce connected app with certificates and JWT it might help you out a bit if you are stuck. Grab it over at


https://github.com/Kenji776/Salesforce-JWT-oAuth-Tool

Till next time.
-Kenji



Bulk Delete/Truncate Salesforce Custom Metadata Object

Dealing with Salesforce custom metadata records is a pain, there just isn’t any other way to say it. Everything is harder when dealing with them. Thankfully I’ve got at least one little thing here to make it easier to work with them, an easy to use bulk delete helper!

The other night I was importing some metadata records and I failed to notice that the importer had added quotes around one of my fields (this was because the field value had a comma in it, so the quotes were being used to denote it was all one field. I ended up having to change the comma to a colon and modify the code that used that field because I didn’t know how to escape the comma). Anyway, after the botched import I needed a way to delete all 400 some records and had no easy way to do it. All the other guides on-line pretty much say you have to deploy a destructiveChanges.xml file with each item listed individually. Obviously I wasn’t going to sit and manually create that file. So what I did is put together a little apex script that will construct the destructiveChanges.xml file and the needed empty package.xml file and email them to me. Then it’s just a simple matter of dropping them in my vs code project folder and running the deploy command.

sfdx force:mdapi:deploy -d [your folder name here, no brackets] -w -1'

list<string> emailRecips = new list<string>{your_email_here@somewhere.com'};
list<string> objectTypes = new list<string>();
objectTypes.add(your_custom_metadata_sObject_type_here__mdt.getSObjectType().getDescribe().getName());

string destructiveXML = '<?xml version="1.0" encoding="UTF-8"?>\n<Ppckage xmlns="http://soap.sforce.com/2006/04/metadata">\n';
string packageXml = '<?xml version="1.0" encoding="UTF-8"?> '+
    '<package xmlns="http://soap.sforce.com/2006/04/metadata">'+
        '<version>41.0</version>'+
    '</package>';
    
destructiveXML += '<types>\n';
for (String objectType : objectTypes) {
    list<sobject> customMetadataRecords = Database.query('SELECT DeveloperName FROM ' + objectType);
    
    for (SObject customMetadataRecord : customMetadataRecords) {
        String recordName = (String)customMetadataRecord.get('DeveloperName');
        destructiveXML += '<members>' + objectType + '.' + recordName + '</members>\n';
    }
}
destructiveXML += '<name>CustomMetadata</name>\n';
destructiveXML += '</types>\n';
destructiveXML += '</package>';

Messaging.SingleEmailMessage email = new Messaging.SingleEmailMessage();
email.setToAddresses(emailRecips);    
email.setSubject('Destructive changes for ' + objectTypes);

string emailBody = 'Here is the destructive changes files you need to delete all this metadata. Put these two files in a folder then run this command. \n';
emailBody += 'sfdx force:mdapi:deploy -d [your folder name here, no brackets] -w -1';

email.setPlainTextBody(emailBody);

list<Messaging.EmailFileAttachment> attachments = new list<Messaging.EmailFileAttachment>();
Messaging.EmailFileAttachment file1 = new Messaging.EmailFileAttachment();
file1.setFileName('destructiveChanges.xml');
file1.setBody(blob.valueOf(destructiveXML));
attachments.add(file1);

Messaging.EmailFileAttachment file2 = new Messaging.EmailFileAttachment();
file2.setFileName('package.xml');
file2.setBody(blob.valueOf(packageXml));
attachments.add(file2);

email.setFileAttachments(attachments);

Messaging.sendEmail(new Messaging.SingleEmailMessage[] { email });

Hope this helps. Till next time.
-Kenji

Copy Fields From One Salesforce Object To Another

Problem: While working with our client they decided they wanted to move about 30 random fields from their account object to another object because the account object already had too many fields and they wanted to re-organize. They only sent over a list of field labels they wanted moved. I needed to find a way to easily move the fields and I didn’t want to do it manually.

Solution: Using the labels query the system for matching field names on the source object to get the field names. Use VS code to pull the field metadata for the source objects fields (which now comes as a list of files, one for each field instead of the massive XML blob it used to). Then run a small script that finds files with matching names from our list of provided field names and copy them to our new object. Use the VS code deploy to push them into the object. Then use the script from my last post to set security on the fields as needed.

So there are a couple steps here, none of them terribly difficult. You’ll need just a few things.
1) Access to execute anonymous code
2) Your Salesforce project setup in VS code with the metadata for your source and target objects pulled down.
3) Node Js Installed.

First, since our provided data only has field labels we need to get the names. A quick bit of apex script makes short work of that. If you already have a list of field names you can skip this, though you’ll need to get them into a JSON array and save that into a file.

string sObjectType = 'Account'; //your source object name here
list<string> fields = new list<string>{'Some field label 1','Some field label 2','field3__c'}; //your list of field labels you need to get names for here
list<string> emailRecips = new list<string>{'your_email_here@somewhere.com'};

Map<String, Schema.SObjectType> globalDescribe = Schema.getGlobalDescribe();
list<string> fieldNames = new list<string>();
list<string> missingFields = new list<string>();
list<string> cleanedFields = new list<string>();
system.debug('\n\n\n----- There are ' + fields.size() + ' Defined fields');


Map<String, Schema.SObjectField> objectFields = globalDescribe.get(sObjectType).getDescribe().fields.getMap();

for(string thisField : fields){
    cleanedFields.add((string) thisField.toLowerCase().replaceAll('[^a-zA-Z0-9\\s+]', ''));
}
system.debug('\n\n\n----- Cleaned field labels');
system.debug(cleanedFields);

integer matches = 0;
for(Schema.SObjectField thisField : objectFields.values()){
    string cleanedLabel = thisField.getDescribe().getLabel();

    system.debug('\n------ Looking for field with label: ' + cleanedLabel + ' in cleaned fields list');
    cleanedLabel = cleanedLabel.toLowerCase().replaceAll('[^a-zA-Z0-9\\s+]', '');
    
    if(cleanedFields.contains(cleanedLabel)) {
        fieldNames.add(thisField.getDescribe().getName());
        matches++;
        integer indexPos = cleanedFields.indexOf(cleanedLabel);
        cleanedFields.remove(indexPos);
    }
}
missingFields = cleanedFields;
string jsonFieldNames = JSON.serialize(fieldNames);
string jsonMissingFields = JSON.serialize(missingFields);


Messaging.SingleEmailMessage email = new Messaging.SingleEmailMessage();
email.setToAddresses(emailRecips);    
email.setSubject('Field Labels to Field Names');
string emailBody = 'Found '+matches+'/'+fields.size()+ ' fields with matching labels to names\n\nFOUND FIELDS\n\n';
emailBody += jsonFieldNames;
emailBody += '\n\n\n MISSING FIELDS\n';
emailBody += jsonMissingFields;
email.setPlainTextBody(emailBody);

list<Messaging.EmailFileAttachment> attachments = new list<Messaging.EmailFileAttachment>();
Messaging.EmailFileAttachment efa = new Messaging.EmailFileAttachment();
efa.setFileName('FieldNames.json');
efa.setBody(blob.valueOf(jsonFieldNames));
attachments.add(efa);

Messaging.EmailFileAttachment efa1 = new Messaging.EmailFileAttachment();
efa1.setFileName('MissingFields.json');
efa1.setBody(blob.valueOf(jsonMissingFields));
attachments.add(efa1);

email.setFileAttachments(attachments);

Messaging.sendEmail(new Messaging.SingleEmailMessage[] { email });

system.debug('\n\n\n---- Found Fields');
system.debug(jsonFieldNames);
system.debug('\n\n\n---- Missing Fields');
system.debug(jsonMissingFields);


Alright, that’s easy enough. Now from either the debug log or the email you got sent you should now have a JSON file of all the field names which we need for the next step.

What we are going to do now is run a small node script to find matching file names from the JSON in the source objects fields folder and copy them into the destination objects fields folder. Create a javascript file and put this in there

/* File extractor
Description: Given a list of salesforce field API names in a JSON file, copies matching meta data config files from one object 
to another to easily move fields from object to another. 

For example, say there are 25 fields you want to move from your account object to an object called plan__c. Instead of having to manually 
re-create them or manually having to copy and paste the metadata files which is prone to error you could instead run this utility
and provide Account as your input object and Plan__c as your output object with a JSON file which is just an array of field names to copy.
This utility will copy any matches and move them into the target folder so you can easily deploy them.

*/

//modify these variables to match your objects and filename
var inputObjectName = 'Account';
var outputObjectName = 'Plan__c';
var fieldFileName = 'FieldNames.json'

const fs = require('fs');
var fieldJson;

function init(){
    fieldJson = JSON.parse(fs.readFileSync( fieldFileName ));
    copyObjectFields(inputObjectName, outputObjectName);
}

function copyObjectFields(inputObject, outputObject){
    var inputPath = inputObject+'\\\\fields\\'
    var outputPath = outputObjectName+'\\\\fields\\';
    var files=fs.readdirSync(inputPath);

    for(var i=0;i<files.length;i++)
    {
        var filename=files[i].replace('.field-meta.xml','');
        if(fieldJson.includes(filename)){
            console.log('Found matching file!');
            console.log(filename);

            fs.copyFile( inputPath+'\\'+files[i], outputPath+'\\'+files[i],(err) => {
                if (err) {
                    console.log("Error Found:", err);
                }
                else {
                    console.log('Copied File!');          
                }
            });
        }
    }
}

init();
 

Now in from your Salesforce project folder copy the source object and destination object from your force-app\main\default\objects\ folder and paste them in the same folder with this script (you could run this from directly inside your project folder but I wouldn’t recommend it). It should look something like this.

folder

Open a command line and navigate to the script and run it. You should see something like

output

Now with all your fields copied into that temp folder, copy them back into your actual project folder. In VS code you should now be able to navigate to your target object, right click and deploy to org.
deploy
You may have to make a few small adjustments if there are conflicting relationship names or whatever but you should be able to work through those pretty easily. To set field level security, see my last post for a utility to easily configure that.

Hope this helps. Till next time.
-Kenji

Bulk Set Field Level Security On Objects for Profiles

Problem: A large number of fields were created across several objects and we needed to ensure that the proper read/edit access was set on all of them. Two profiles need to have read but no edit access on every field on every object. This could obviously be done manually but it's tedious and I don't want to. I'm a programmer dammit not some data entry dude. Queue rapid coding session to try and see if I can write something to do this faster than I could do it manually so I don't totally defeat the purpose of doing this. I also wanted to be able to review the potential changes before committing them. Solution: Write a apex method/execute anonymous script to modify the FLS to quickly set the permissions required. The trick here is knowing that every profile has an underlying permission set that controls its access. So it becomes a reasonably simple task of getting the permission sets related to the profiles, getting all the fields on all the objects, checking for existing permissions, modifying them where needed and writing them back. I also needed to generate reports that would show exactly what was changed so our admin folks could look it over and it could be provided for documentation/deployment reasons. So yeah, here that is.

*Note* I threw this together in like two hours so it's not the most elegant thing in the world. It's got some room for improvement definitely, but it does the job.
/* To easily call this as an execute anonymous scripts remove the method signature and brackets and set these variables as desired.
list<string> sObjects = new list<string>{'account','object2__c'};
list<string> profileNames = new list<string>{'System Administrator','Standard User'};
boolean canView; //set to true or false
boolean canEdit; //set to true or false
boolean doUpdate = false;
list<string> emailRecips = new list<string>{'your_email_here@somewhere.com'};
*/

/**
*@Description sets the permissions on all fields of all given objects for all given profiles to the given true or false values for view and edit. Additionally can be set to 
* rollback changes to only generate the report of what would be modified. Emails logs of proposed and completed changes to addresses specified. Only modififies permissions that
  do not match the new settings so un-needed changes are not performed and could theoretically be called repeatedly to chip away at changes if the overall amount of DML ends up being
  too much for one operation (chunking/limit logic does not currently exist so doing too many changes at once could cause errors).
*@Param sObjects list of sObjects to set permissions for all fields on
*@Param profileNames a list of names of profiles for which to modify the permissions for
*@Param canView set the view permission to true or false for all fields on all provided objects for all provided profiles
*@Param canEdit set the edit permission to true or false for all fields on all provided objects for all provided profiles
*@Param doUpdate should the changes to field level security actually be performed or no? If not the reports for proposed changes and what the results would be are still generated
        and sent because a database.rollback is used to undo the changes.
*@Param emailRecips a list of email addresses to send the results to. If in a sandbox ensure email deliverability is turned on to receive the reports.
**/
public static string setPermissionsOnObjects(list<string> sObjects, list<string> profileNames, boolean canView, boolean canEdit, boolean doUpdate, list<string> emailRecips){
        
    system.debug('\n\n\n----- Setting Permissions for profiles');
    list<FieldPermissions> updatePermissions = new list<FieldPermissions>();
    string csvUpdateString = 'Object Name, Field Name, Profile, Could Read?, Could Edit?, Can Read?, Can Edit, What Changed\n';
    map<Id,Id> profileIdToPermSetIdMap = new map<Id,Id>();
    map<Id,Id> permSetToProfileIdMap = new map<Id,Id>();
    map<Id,String> profileIdToNameMap = new map<Id,String>();

    //Every profile has an underlying permission set. We have to query for that permission set id to make a new field permission as 
    //those are related to permission sets not profiles.
    for(PermissionSet thisPermSet : [Select Id, IsOwnedByProfile, Label, Profile.Name from PermissionSet  where Profile.Name in :profileNames]){
        profileIdToPermSetIdMap.put(thisPermSet.ProfileId,thisPermSet.Id);
        permSetToProfileIdMap.put(thisPermSet.Id,thisPermSet.ProfileId);
    }

    Map<String, Schema.SObjectType> globalDescribe = Schema.getGlobalDescribe();
    Map<String,Profile> profilesMap = new Map<String,Profile>();

    //map of profile id to object type to field name to field permission
    map<id,map<string,map<string,FieldPermissions>>> objectToFieldPermissionsMap = new map<id,map<string,map<string,FieldPermissions>>>();

    for(Profile  thisProfile : [select name, id from Profile where name in :profileNames]){
        profilesMap.put(thisProfile.Name,thisProfile);
        profileIdToNameMap.put(thisProfile.Id,thisProfile.Name);
    }

    List<FieldPermissions> fpList = [SELECT SobjectType, 
                                            Field, 
                                            PermissionsRead, 
                                            PermissionsEdit, 
                                            Parent.ProfileId 
                                    FROM FieldPermissions 
                                    WHERE SobjectType IN :sObjects  and
                                    Parent.Profile.Name IN :profileNames
                                    ORDER By SobjectType];

    for(FieldPermissions thisPerm : fpList){
        //gets map of object types to fields to permission sets for this permission sets profile
        map<string,map<string,FieldPermissions>> profilePerms = objectToFieldPermissionsMap.containsKey(thisPerm.parent.profileId) ?
                                                                objectToFieldPermissionsMap.get(thisPerm.parent.profileId) :
                                                                new map<string,map<string,FieldPermissions>>();
        //gets map of field names for this object to permissions
        map<string,FieldPermissions> objectPerms = profilePerms.containsKey(thisPerm.sObjectType) ?
                                                   profilePerms.get(thisPerm.sObjectType) :
                                                   new map<string,FieldPermissions>();

        //puts this field and its permission into the object permission map
        objectPerms.put(thisPerm.Field,thisPerm);

        //puts this object permission map into the object permissions map
        profilePerms.put(thisPerm.sObjectType,objectPerms);

        //write profile permissions back to profile permissions map
        objectToFieldPermissionsMap.put(thisPerm.parent.profileId,profilePerms);
    }
    system.debug('\n\n\n----- Built Object Permission Map');
    system.debug(objectToFieldPermissionsMap);

    for(string thisObject : sObjects){

        system.debug('\n\n\n------ Setting permissions for ' + thisObject);
        Map<String, Schema.SObjectField> objectFields = globalDescribe.get(thisObject).getDescribe().fields.getMap();

        for(string thisProfile : profileNames){

            Id profileId = profilesMap.get(thisProfile).Id;

            //gets map of object types to fields to permission sets for this permission sets profile
            map<string,map<string,FieldPermissions>> profilePerms = objectToFieldPermissionsMap.containsKey(profileId) ?
            objectToFieldPermissionsMap.get(profileId) :
            new map<string,map<string,FieldPermissions>>();            

            //gets map of field names for this object to permissions
            map<string,FieldPermissions> objectPerms = profilePerms.containsKey(thisObject) ?
            profilePerms.get(thisObject) :
            new map<string,FieldPermissions>();

            system.debug('\n\n\n---- Setting permissions for profile: ' + thisProfile);
            
            Id permissionSetId = profileIdToPermSetIdMap.get(profileId);
            for(Schema.SObjectField thisField : objectFields.values()){
                string fieldName = thisField.getDescribe().getName();
                boolean canPermission = thisField.getDescribe().isPermissionable();

                if(!canPermission) {
                    system.debug('\n\n\n---- Cannot change permissions for field: ' + thisField + '. Skipping');
                    continue;
                }

                string fieldObjectName = thisObject+'.'+fieldName;
                FieldPermissions thisPermission = objectPerms.containsKey(fieldObjectName) ?
                                                  objectPerms.get(fieldObjectName) :
                                                  new FieldPermissions(Field=fieldObjectName,
                                                                       SobjectType=thisObject,
                                                                       ParentId=permissionSetId);

                if(thisPermission.PermissionsRead != canView || thisPermission.PermissionsEdit != canEdit){      
                    system.debug('------------------- Adjusting Permission for field: ' + fieldName); 
                    
                    csvUpdateString += thisObject+','+fieldName+','+thisProfile+','+thisPermission.PermissionsRead+','+thisPermission.PermissionsEdit+','+canView+','+canEdit+',';

                    if(thisPermission.PermissionsRead != canView) csvUpdateString += 'Read Access ';
                    if(thisPermission.PermissionsEdit != canEdit) csvUpdateString += 'Edit Access ';
                    csvUpdateString+='\n';

                    thisPermission.PermissionsRead = canView;
                    thisPermission.PermissionsEdit = canEdit;
                    updatePermissions.add(thisPermission);
                }
            }
        }
    }

    system.debug('\n\n\n----- Ready to update ' + updatePermissions.size() + ' permissions');

    Savepoint sp = Database.setSavepoint();

    string upsertResults = 'Object Name, Field Name, Permission Set Id, Profile Name, Message\n';

    Database.UpsertResult[] results = Database.upsert(updatePermissions, false);

    for(Integer index = 0, size = results.size(); index < size; index++) {
        FieldPermissions thisObj = updatePermissions[index];

        string thisProfileName = profileIdToNameMap.get(permSetToProfileIdMap.get(thisObj.ParentId));
        if(results[index].isSuccess()) {
            if(results[index].isCreated()) {
                upsertResults += thisObj.sObjectType +',' + thisObj.Field +','+ thisObj.ParentId +',' +thisProfileName+',permission was created\n';
            } else {
                upsertResults += thisObj.sObjectType +',' + thisObj.Field +','+ thisObj.ParentId +',' +thisProfileName+',permission was edited\n';
            }
        }
        else {
            upsertResults +=thisObj.sObjectType +',' + thisObj.Field + ','+ thisObj.ParentId +',' +thisProfileName+'ERROR: '+results[index].getErrors()[0].getMessage()+'\n';                
        }
    }
    if(!doUpdate) Database.rollback(sp);

    system.debug('\n\n\n------- Update Results');
    system.debug(upsertResults);

    Messaging.SingleEmailMessage email = new Messaging.SingleEmailMessage();
    email.setToAddresses(emailRecips);    
    email.setSubject('Object Security Update Result');
    
    string emailBody = 'Updated permissions for objects: ' + sObjects + '\n\n';
    emailBody += 'For profiles: ' + profileNames+'\n\n';
    emailBody += 'CSV Update Plan:\n\n\n\n';
    emailBody += csvUpdateString;
    emailBody += '\n\n\n\n';
    emailBody += 'CSV Update Results: \n\n\n\n';
    emailBody += upsertResults;

    email.setPlainTextBody(emailBody);

    list<Messaging.EmailFileAttachment> attachments = new list<Messaging.EmailFileAttachment>();
    Messaging.EmailFileAttachment efa = new Messaging.EmailFileAttachment();
    efa.setFileName('Update Plan.csv');
    efa.setBody(blob.valueOf(csvUpdateString));
    attachments.add(efa);

    Messaging.EmailFileAttachment efa1 = new Messaging.EmailFileAttachment();
    efa1.setFileName('Update Results.csv');
    efa1.setBody(blob.valueOf(upsertResults));
    attachments.add(efa1);

    email.setFileAttachments(attachments);
    Messaging.sendEmail(new Messaging.SingleEmailMessage[] { email });


    return csvUpdateString;
}

Hope this helps. Till next time. -Kenji

Salesforce Destructive Changes Generator/Deployer

So I was going through some of my old projects and getting them stored in github like some kind of real developer and stumbled across this one which I figured might be worth a post. This is a utility for building a destructiveChanges.xml file that can be deployed to multiple orgs automatically. It uses an execute anonymous script to query your org in whatever way you like to generate a list of metadata to delete and then can deploy it for you to all those orgs. Again I’ll explain a little about why this exists and maybe you’ll find a use for it.

Problem: We have multiple orgs where through testing/development and users being users we have lots of unwanted list views and other types of metadata. We want to remove them from multiple orgs without having to do it manually and refresh sandboxes etc. We also wanted to do this once or twice a week (long story, just trust me on this).

Solution: Write an apex script that can query for the metadata (yes, unfortunately the metadata has to be queryable for this to work), generate a destructive changes file and deploy it to a list of orgs. Then use a nodeJS application to deploy those files to each org automatically.

Now this is more of a developer resource than a full application. It comes with some sample code to show how to use it, but for it to be really useful you are going to have to do some coding of your own most likely (pretty much just writing queries to feed into the sObjectListToStringList function). You’ll need nodeJs and SFDX setup for this to work.

The buildPackage.apex file is where you’ll be adding logic to actually generate the destructive changes file. In the buildPackage function there is a map called packageContents, that is where you will need to add the metadata you want to remove with the key being the metadata type and the value being a list of developerNames of that object type. You’ll write functions to create those queries and then store those values in that map before it is passed to the buildPackageString() function.

The buld_and_deploy_changes.js file is where you can control the behavior such as ignoreDeployErrors for one org and continuing to the next, doDeployment dictates if any kind of deployment should be attempted (validate only or actually deploy) or just generate the XML files, checkOnlyMode so changes are only validated but not deployed (true by default to stop you accidentally deleting stuff while testing), and setting the list of usernames to use as org credentials. Of course for any org you want this to run against you’ll need to have authorized it via SFDX.

Once the script has been run in the destructiveChanges folder a new folder will be created for each org with the destructiveChanges.xml file saved there. After the files are created the automatic deployment will run if you’ve set doDeployment to true and push those changes into your orgs.

You can check it out over at https://github.com/Kenji776/SFDestructiveChangesGenerator and let me know if you have any questions or suggestions on how to improve. Just fair warning I take absolutly no responsibility for the results of using this. It’s up to you to ensure the destructiveChanges files look okay before deploying them so absolutely use the doDeployment=false and/or checkOnlyMode=true until you are sure things look like you want.