Migration from Salesforce Data Mask to Cloud Compliance DataMasker (Optional)

Migrating from Salesforce Data Mask to DataMasker is simple. We have an Apex script that does the job. The migration process must be executed in the Sandbox org where both Salesforce Data Mask and Cloud Compliance DataMasker are installed. Here are the steps to migrate

1. Create Apex Class

Login to the Salesforce sandbox org where Salesforce Data Mask and Cloud Compliance DataMasker is installed. Navigate to Setup->Custom Code->Apex Classes and click the ‘New’ button. Copy & Paste the appended code. And save the Apex class.

public class DataMaskerMigrationScript {
	
    public static void migrate(){
        
        Set<String> objectsWithNonBulkApi = new Set<String>{'User', 'Attachment'};
        
        Map<String, Schema.SObjectType> schemaMap = Schema.getGlobalDescribe();
        
        Map<String, pcldm__Configuration__c> mapOfSourceIdToConfiguration = new Map<String, pcldm__Configuration__c>();
        for(datamask__DataMask_Configuration__mdt conf : [select id, MasterLabel, datamask__Anonymize_Case_Comments__c, datamask__Delete_Chatter__c, datamask__Delete_Email__c, 
                                                          datamask__Notes__c from datamask__DataMask_Configuration__mdt]){
            pcldm__Configuration__c c = new pcldm__Configuration__c();
            c.Name = conf.MasterLabel;
            c.pcldm__Description__c = 'Automatically imported configuration from SFDC DM.';
            c.pcldm__Chatter__c = conf.datamask__Delete_Chatter__c;
            c.pcldm__Emails__c = conf.datamask__Delete_Email__c;
            if(!mapOfSourceIdToConfiguration.containsKey(conf.Id)){
                mapOfSourceIdToConfiguration.put(conf.Id, c);
            }                                                  
        }
        
        if(!mapOfSourceIdToConfiguration.isEmpty()){
            insert mapOfSourceIdToConfiguration.values();
            
            Set<String> nonMaskingObjects = new Set<String>();
            for(pcldm__DM_App_Config__mdt mdt : [SELECT Id, pcldm__Value__c FROM pcldm__DM_App_Config__mdt WHERE pcldm__Module_Name__c = 'ObjectMaskingUnselectable']){
                nonMaskingObjects.add(mdt.pcldm__Value__c);
            }
            
            Map<String, pcldm__Object_Masking__c> mapOfKeyToObjectMasking = new Map<String, pcldm__Object_Masking__c>();
            Map<String, Map<String, Schema.SObjectField>> mapOfObjectNameTofieldMap = new Map<String, Map<String, Schema.SObjectField>>();
            for(datamask__Masking_Object__mdt obj : [select id, datamask__API_Name__c, datamask__DataMask_Configuration__c, datamask__Where_Criteria_LT__c 
                                                     FROM datamask__Masking_Object__mdt 
                                                     WHERE datamask__DataMask_Configuration__c IN :mapOfSourceIdToConfiguration.keySet()
                                                     AND datamask__API_Name__c NOT IN :nonMaskingObjects]){        
                String key = obj.datamask__DataMask_Configuration__c +'~'+ obj.datamask__API_Name__c;                                         
                if(schemaMap.containsKey(obj.datamask__API_Name__c) && mapOfSourceIdToConfiguration.containsKey(obj.datamask__DataMask_Configuration__c) && !mapOfKeyToObjectMasking.containsKey(key)){
                    pcldm__Object_Masking__c o = new pcldm__Object_Masking__c();
                    o.pcldm__Active__c = true;
                    o.Name = schemaMap.get(obj.datamask__API_Name__c).getDescribe().getLabel();
                    o.pcldm__Batch_Size__c = 2000;
                    o.pcldm__Configuration__c = mapOfSourceIdToConfiguration.get(obj.datamask__DataMask_Configuration__c).Id;
                    o.pcldm__Filter_Criteria__c = obj.datamask__Where_Criteria_LT__c;
                    o.pcldm__Group__c = '1';
                    o.pcldm__Object_API__c = obj.datamask__API_Name__c;
                    o.pcldm__Object_Label__c = schemaMap.get(obj.datamask__API_Name__c).getDescribe().getLabel();
                    o.pcldm__Sequence__c = 10;
                    o.pcldm__Use_Bulk_Api__c = true;
                    o.RecordTypeId = Schema.SObjectType.pcldm__Object_Masking__c.getRecordTypeInfosByDeveloperName().get('Mask_Records').getRecordTypeId();
                    if(objectsWithNonBulkApi.contains(o.pcldm__Object_API__c)){
                        o.pcldm__Use_Bulk_Api__c = false;
                        o.pcldm__Batch_Size__c = 200;
                    }
                    mapOfKeyToObjectMasking.put(key, o);
                    if(!mapOfObjectNameTofieldMap.containsKey(o.pcldm__Object_API__c)){
                        mapOfObjectNameTofieldMap.put(obj.datamask__API_Name__c, schemaMap.get(obj.datamask__API_Name__c).getDescribe().fields.getMap());
                    }
                }
            }
            if(!mapOfKeyToObjectMasking.isEmpty()){
                insert mapOfKeyToObjectMasking.values();
                
                //map of SF-DM to CC-DM pattern mappings
                Map<String, String> mapOfSourceToTargetPattern = new Map<String, String>{
                    'First Name' => 'First Name-List', 
                    'Last Name' => 'Last Name-List', 
                    'Company Name' => 'Account Name-List',
                    'Email' => 'Email-Regex',     
                    'Street' => 'Street Address-List', 
                    'City' => 'City-List(US)', 
                    'State' => 'State-List(US)',
                    'Postal Code' => 'Postal Code-Regex(US)',
                    'Postal Code (Canada)' => 'Postal Code-Regex(US)',    
                    'Country' => 'Country Name-List',
                    'Phone N/A' => 'Phone-Regex(US)',
                    'Social Security No' => 'SSN-Regex(US)',
                    'Social Security No Values' => 'SSN-Regex(US)',
                    'Country (abbr)' => 'Country ISO Code-List',
                    'Full Name' => 'First Name-List'    
                };     
                    
                Map<String, pcldm__Pattern__c> mapOfDmPatternNameToPattern = new Map<String, pcldm__Pattern__c>();    
                for(pcldm__Pattern__c p : [SELECT Id, Name, pcldm__Type__c FROM pcldm__Pattern__c]){
                    mapOfDmPatternNameToPattern.put(p.Name, p);
                }    
                
                List<datamask__Masking_Field__mdt> lstOfFieldMaskings = [SELECT Id, datamask__API_Name__c, datamask__Custom_Pattern__c, datamask__Field_Length__c,
                                                                         datamask__Field_Type__c, datamask__Masking_Category__c, datamask__Masking_Object__c, datamask__Masking_Type__c, 
                                                                         datamask__Pattern_Data__c, datamask__Required__c, datamask__Unique__c, datamask__Masking_Object__r.datamask__API_Name__c,
                                                                         datamask__Masking_Object__r.datamask__DataMask_Configuration__c
                                                                         FROM datamask__Masking_Field__mdt 
                                                                         WHERE datamask__Masking_Object__r.datamask__DataMask_Configuration__c IN :mapOfSourceIdToConfiguration.keySet()
                                                                         AND datamask__Masking_Object__r.datamask__API_Name__c != 'User'];
                
                Map<String, pcldm__Pattern__c> mapOfPatternsToInsert = new Map<String, pcldm__Pattern__c>();
                for(datamask__Masking_Field__mdt fm : lstOfFieldMaskings){
                    if(fm.datamask__Masking_Type__c == 'Patternize' && String.isNotBlank(fm.datamask__Custom_Pattern__c) && !mapOfDmPatternNameToPattern.containsKey(fm.datamask__Custom_Pattern__c)){
                        pcldm__Pattern__c p = new pcldm__Pattern__c();
                        p.Name = fm.datamask__Custom_Pattern__c;
                        p.RecordTypeId = Schema.SObjectType.pcldm__Pattern__c.getRecordTypeInfosByDeveloperName().get('Regex').getRecordTypeId();
                        p.pcldm__Data_Type__c = 'Text';
                        p.pcldm__Type__c = 'Regex';
                        p.pcldm__Value__c = parsePattern(fm.datamask__Custom_Pattern__c);
                        mapOfPatternsToInsert.put(p.name, p);
                    }
                }
                if(!mapOfPatternsToInsert.isEmpty()){
                    insert mapOfPatternsToInsert.values();
                    
                    for(pcldm__Pattern__c p : mapOfPatternsToInsert.values()){
                        mapOfDmPatternNameToPattern.put(p.Name, p);
                    }
                }
                
                 
                
                List<pcldm__Field_Masking__c> lstOfFieldMaskingToInsert = new List<pcldm__Field_Masking__c>();
                for(datamask__Masking_Field__mdt fm : lstOfFieldMaskings){
                    String key = fm.datamask__Masking_Object__r.datamask__DataMask_Configuration__c+'~'+fm.datamask__Masking_Object__r.datamask__API_Name__c;
                    if(mapOfKeyToObjectMasking.containsKey(key) && mapOfObjectNameTofieldMap.containsKey(fm.datamask__Masking_Object__r.datamask__API_Name__c)){
                        Map<String, Schema.SObjectField> mapOffieldDescribes = mapOfObjectNameTofieldMap.get(fm.datamask__Masking_Object__r.datamask__API_Name__c);
                        if(mapOffieldDescribes.containsKey(fm.datamask__API_Name__c)){
                            Schema.DescribeFieldResult dfield = mapOffieldDescribes.get(fm.datamask__API_Name__c).getDescribe();
                            pcldm__Field_Masking__c f = new pcldm__Field_Masking__c();
                            f.pcldm__Active__c = true;    
                            f.pcldm__Field_API__c = fm.datamask__API_Name__c;  
                            f.pcldm__Field_Label__c = dfield.getLabel();
                            f.Name = dfield.getLabel();
                            f.pcldm__Field_Type__c = String.valueOf(dfield.getType()); 
                            f.pcldm__Unique__c = false;
                            f.pcldm__Object_Masking__c = mapOfKeyToObjectMasking.get(key).Id;  
                            if((f.pcldm__Field_Type__c == 'TEXTAREA' || f.pcldm__Field_Type__c == 'STRING') && fm.datamask__Unique__c){
                                f.pcldm__Unique__c = true;
                            }   
                            f.pcldm__Action__c = 'Replace';
                            if(fm.datamask__Masking_Type__c == 'Delete'){
                                f.pcldm__Action__c = 'Erase';
                            }else if(fm.datamask__Masking_Type__c == 'Anonymize'){
                                f.pcldm__Further_Action__c = 'Random';
                                f.pcldm__Value__c = 'Auto-Generated';
                            }else if(fm.datamask__Masking_Type__c == 'Patternize'){
                                if(String.isNotBlank(fm.datamask__Custom_Pattern__c) && mapOfDmPatternNameToPattern.containsKey(fm.datamask__Custom_Pattern__c)){
                                    pcldm__Pattern__c ptrn = mapOfDmPatternNameToPattern.get(fm.datamask__Custom_Pattern__c);
                                    f.pcldm__Further_Action__c = 'Pattern - '+ptrn.pcldm__Type__c;
                                    f.pcldm__Pattern__c = ptrn.Id;
                                    f.pcldm__Value__c = ptrn.Name;
                                }else{
                                    f.pcldm__Further_Action__c = 'Random';
                               		f.pcldm__Value__c = 'Auto-Generated';
                                }
                            }else{
                                if(String.isNotBlank(fm.datamask__Masking_Category__c) && mapOfSourceToTargetPattern.containsKey(fm.datamask__Masking_Category__c)){
                                    String dmPattern = mapOfSourceToTargetPattern.get(fm.datamask__Masking_Category__c);
                                    if(String.isNotBlank(dmPattern) && mapOfDmPatternNameToPattern.containsKey(dmPattern)){
                                        pcldm__Pattern__c ptrn = mapOfDmPatternNameToPattern.get(dmPattern);
                                        f.pcldm__Further_Action__c = 'Pattern - '+ptrn.pcldm__Type__c;
                                        f.pcldm__Pattern__c = ptrn.Id;
                                        f.pcldm__Value__c = ptrn.Name;
                                    }else{
                                        f.pcldm__Further_Action__c = 'Random';
                                		f.pcldm__Value__c = 'Auto-Generated';
                                    }
                                }else{
                                    f.pcldm__Further_Action__c = 'Random';
                                	f.pcldm__Value__c = 'Auto-Generated';
                                }
                            }
                            lstOfFieldMaskingToInsert.add(f);
                        }
                    }                                                                       
                }
                
                List<pcldm__Object_Masking__c> lst = [select id FROM pcldm__Object_Masking__c WHERE pcldm__object_Api__c = 'User' 
                                                      AND Id IN :mapOfKeyToObjectMasking.values()];
                if(lst != null && !lst.isEmpty()){
                    for(pcldm__Object_Masking__c obj : lst){
                        pcldm__Field_Masking__c f = new pcldm__Field_Masking__c();
                        f.pcldm__Active__c = true;    
                        f.pcldm__Field_API__c = 'isActive';  
                        f.pcldm__Field_Label__c = 'Active';
                        f.Name = 'Active';
                        f.pcldm__Field_Type__c = 'BOOLEAN'; 
                        f.pcldm__Unique__c = false;
                        f.pcldm__Object_Masking__c = obj.Id;  
                        f.pcldm__Action__c = 'Replace';
                        f.pcldm__Further_Action__c = '	Hardcoded Text';
                        f.pcldm__Value__c = 'false';
                        lstOfFieldMaskingToInsert.add(f);
                    }
                }
                
                if(!lstOfFieldMaskingToInsert.isEmpty()){
                    insert lstOfFieldMaskingToInsert;
                }
            }
        }
    }
    
    public static String parsePattern(String pattern){
        while(pattern.containsIgnoreCase('%%')){
            pattern = pattern.replace('%%', '[percent]');
        }
        String currentPattern = '';
        Boolean patternRunning = false;
        for(Integer i = 0; i < pattern.length(); i++){
            String currentChar = pattern.substring(i, i+1);
            if(currentChar == '%'){
                patternRunning = true;
            }else if(currentChar == 'd' && patternRunning){
                patternRunning = false;
                String randomInt = '[1-9]{'+Integer.valueOf(currentPattern)+'}';
                pattern = pattern.substring(0, (i-1-currentPattern.length()))+randomInt+pattern.substring(i+1, pattern.length());
                currentPattern = '';
            }else if(currentChar == 'c' && patternRunning){
                patternRunning = false;
               	String randomStr = '[a-z]{'+Integer.valueOf(currentPattern)+'}';
                pattern = pattern.substring(0, (i-1-currentPattern.length()))+randomStr+pattern.substring(i+1, pattern.length());
                currentPattern = '';
            }else if(patternRunning){
                currentPattern += currentChar;
            }
        }
        while(pattern.containsIgnoreCase('[percent]')){
            pattern = pattern.replace('[percent]', '%');
        }
        return pattern;
    }

}

2. Run Execute Anonymous

Navigate to the developer console. Select Debug->Open Execute Anonymous Window. Copy & Paste the appended code and hit the ‘Execute’ Button

DataMaskerMigrationScript.migrate();

3. Verify the Migrated Configuration

The migration script will create a new configuration in DataMasker, the name of this configuration will be the same as the one in Salesforce Data Mask. Verify this configuration and if it looks good, you are ready to test masking your sandbox.