If you are a SFDC admin or developer, sandbox refreshes are a normal part of your life. The question is what are things needed to be done when you do a sandbox refresh prior to refresh or after the sandbox refresh. I could not find an answer for this as each company has its own way of things on sandbox refreshes. I want to thank Carol Enevoldsen and Ben Lorenz for their contribution on a question to an answer in the success community which made me to create this blog.
- Before refresh.
- Communicate proposed schedule/timeline to impacted audience including integration teams, development teams who might be doing any testing on your full copy or other sandboxes.
- Full sandboxes can only be refreshed every 29 days.
- For full sandboxes plan for a minimum of 48 hours as the timing of the refresh is dependent on the salesforce Queue and also amount of data.
- If you have multiple lines of business using one salesforce org, create a sandbox template for each business depending on business requirement.
- For full copy sandboxes contact Salesforce.com premier support in advance to identify place in refresh queue in order to get a better perspective on expected timing/completion of the data copy.
- Perform a full metadata backup of the instance being refreshed. If you do not have a back up tool, it is very important to get a backup of your sandbox. You can do this using eclipse if you have development teams. If you do not have developers, use the Data export feature to at least do a weekly export of your salesforce data.
- If you are using ant scripts, you can use the below xml file to backup all your data.
APPENDIX A - unpackaged-all.xml for use in full metadata backup via ant <?xml version="1.0" encoding="UTF-8"?> <Package xmlns="http://soap.sforce.com/2006/04/metadata"> <version>35.0</version> <types> <name>ActionLinkGroupTemplate</name> <members>*</members> </types> <!-- BOTH OF THESE THROW UNKNOWN <types> <name>ActionLinkTemplate</name> <members>*</members> </types> <types> <name>ArticleType</name> <members>*</members> </types> --> <types> <name>ApexClass</name> <members>*</members> </types> <types> <name>ApexComponent</name> <members>*</members> </types> <types> <name>ApexPage</name> <members>*</members> </types> <types> <name>ApexTrigger</name> <members>*</members> </types> <types> <name>AppMenu</name> <members>*</members> </types> <types> <name>ApprovalProcess</name> <members>*</members> </types> <types> <name>AssignmentRules</name> <members>*</members> </types> <types> <name>AuraDefinitionBundle</name> <members>*</members> </types> <types> <name>AuthProvider</name> <members>*</members> </types> <types> <name>AutoResponseRules</name> <members>*</members> </types> <types> <name>CallCenter</name> <members>*</members> </types> <types> <name>Community</name> <members>*</members> </types> <types> <name>CorsWhitelistOrigin</name> <members>*</members> </types> <types> <name>CustomApplication</name> <members>*</members> </types> <types> <name>CustomApplicationComponent</name> <members>*</members> </types> <types> <name>CustomObject</name> <!-- includes ActionOverride,BusinessProcess,CompactLayout,CustomField,FieldSet, HistoryRetentionPolicy,ListView,RecordType,SearchLayouts,SharingReason, SharingRecalculation,ValidationRule,WebLink --> <members>*</members> </types> <types> <name>CustomObjectTranslation</name> <members>*</members> </types> <types> <name>CustomPageWebLink</name><!-- Defined within a home page component --> <members>*</members> </types> <types> <name>ConnectedApp</name> <members>*</members> </types> <types> <name>CustomLabels</name><!-- Name of component must be CustomLabel when explicitly naming members --> <members>*</members> </types> <types> <name>CustomPermission</name> <members>*</members> </types> <types> <name>CustomSite</name> <members>*</members> </types> <types> <name>CustomTab</name> <members>*</members> </types> <types> <name>DataCategoryGroup</name> <!-- HIGHLY RECOMMENDED TO DO THESE MANUALLY VIA CONFIG https://developer.salesforce.com/docs/atlas.en-us.api_meta.meta/api_meta/meta_datacategorygroup.htm--> <members>*</members> </types> <types> <name>EntitlementProcess</name> <members>*</members> </types> <types> <name>EntitlementTemplate</name> <members>*</members> </types> <types> <name>EscalationRules</name> <!-- might be EscalationRule when using .notation --> <members>*</members> </types> <types> <name>ExternalDataSource</name> <members>*</members> </types> <types> <name>FlexiPage</name> <members>*</members> </types> <types> <name>Flow</name> <members>*</members> </types> <types> <name>FlowDefinition</name> <members>*</members> </types> <types> <name>Group</name> <members>*</members> </types> <types> <name>HomePageComponent</name> <members>*</members> </types> <types> <name>HomePageLayout</name> <members>*</members> </types> <types> <name>InstalledPackage</name> <members>*</members> </types> <types> <name>Layout</name> <members>*</members> </types> <types> <name>LiveChatAgentConfig</name> <members>*</members> </types> <types> <name>LiveChatButton</name> <members>*</members> </types> <types> <name>LiveChatDeployment</name> <members>*</members> </types> <types> <name>ManagedTopics</name> <members>*</members> </types> <types> <name>MatchingRules</name> <!-- could be MatchingRule when using .notation --> <members>*</members> </types> <types> <name>NamedCredential</name> <members>*</members> </types> <types> <name>AccountCriteriaBasedSharingRule</name> <members>*</members> </types> <types> <name>AccountOwnerSharingRule</name> <members>*</members> </types> <types> <name>AccountTerritorySharingRule</name> <members>*</members> </types> <types> <name>CampaignCriteriaBasedSharingRule</name> <members>*</members> </types> <types> <name>CampaignOwnerSharingRule</name> <members>*</members> </types> <types> <name>CaseCriteriaBasedSharingRule</name> <members>*</members> </types> <types> <name>CaseOwnerSharingRule</name> <members>*</members> </types> <types> <name>CompactLayout</name> <members>*</members> </types> <types> <name>ContactCriteriaBasedSharingRule</name> <members>*</members> </types> <types> <name>ContactOwnerSharingRule</name> <members>*</members> </types> <types> <name>CustomObjectCriteriaBasedSharingRule</name> <!-- docs say * not supported for this but doesn't cause errors either... --> <members>*</members> </types> <types> <name>CustomObjectOwnerSharingRule</name> <!-- docs say * not supported for this but doesn't cause errors either... --> <members>*</members> </types> <types> <name>FieldSet</name> <members>*</members> </types> <types> <name>LeadCriteriaBasedSharingRule</name> <members>*</members> </types> <types> <name>LeadOwnerSharingRule</name> <members>*</members> </types> <types> <name>MilestoneType</name> <members>*</members> </types> <types> <name>Network</name> <members>*</members> </types> <types> <name>OpportunityCriteriaBasedSharingRule</name> <members>*</members> </types> <types> <name>OpportunityOwnerSharingRule</name> <members>*</members> </types> <types> <name>PermissionSet</name> <!-- Only includes permissions for types included in deployment --> <members>*</members> </types> <types> <name>Portal</name> <members>*</members> </types> <types> <name>PostTemplate</name> <members>*</members> </types> <types> <name>Profile</name> <!-- Only includes permissions for types included in deployment --> <members>*</members> </types> <types> <name>Queue</name> <members>*</members> </types> <types> <name>QuickAction</name> <members>*</members> </types> <types> <name>RecordType</name> <members>*</members><!--Deployed as part of custom object file. Must dot qualify with object name--> </types> <types> <name>RemoteSiteSetting</name> <members>*</members> </types> <types> <name>ReportType</name> <members>*</members> </types> <types> <name>Role</name> <members>*</members> </types> <types> <name>SamlSsoConfig</name> <members>*</members> </types> <types> <name>Settings</name> <!-- retrieves all available settings metadata e.g. account, case...etc --> <members>*</members> </types> <types> <name>Skill</name> <members>*</members> </types> <types> <name>StaticResource</name> <members>*</members> </types> <types> <name>Territory</name> <members>*</members> </types> <types> <name>Translations</name> <members>*</members> </types> <types> <name>UserCriteriaBasedSharingRule</name> <members>*</members> </types> <types> <name>UserMembershipSharingRule</name> <members>*</members> </types> <types> <name>Workflow</name> <members>*</members> </types> <types> <name>WorkflowAlert</name> <members>*</members> </types> <types> <name>WorkflowFieldUpdate</name> <members>*</members> </types> <types> <name>WorkflowKnowledgePublish</name> <members>*</members> </types> <types> <name>WorkflowOutboundMessage</name> <members>*</members> </types> <types> <name>WorkflowRule</name> <members>*</members> </types> <types> <name>WorkflowTask</name> <members>*</members> </types> <!-- The following metadata types cannot be retrieved with * notation as of winter '16 <types> <name>AnalyticSnapshot</name> unverified <members></members> </types> <types> <name>Dashboard</name> <members></members> </types> <types> <name>Document</name> <members></members> </types> <types> <name>EmailTemplate</name> <members></members> </types> <types> <name>Folder</name> document email report and dashboard could be sub types <members></members> </types> <types> <name>Letterhead</name> <members></members> </types> <types> <name>Metadata</name> unverified <members></members> </types> <types> <name>MetadataWithContent</name> unverified <members></members> </types> <types> <name>ActionOverride</name> <members></members> //Deployed as part of custom object file. Must dot qualify with object name </types> <types> <name>BusinessProcess</name> <members></members> //Deployed as part of custom object file. Must dot qualify with object name </types> <types> <name>CustomField</name> <members></members> //Deployed as part of custom object file. Must dot qualify with object name </types> <types> <name>CustomLabel</name> <members></members> //Deployed as part of custom object file. Must dot qualify with object name </types> <types> <name>FolderShare</name> <members></members> </types> <types> <name>ListView</name> <members></members> //Deployed as part of custom object file. Must dot qualify with object name </types> <types> <name>MetadataWithContent</name> <members></members> </types> <types> <name>NamedFilter</name> <members></members> //Deployed as part of custom object file. Must dot qualify with object name </types> <types> <name>Package</name> <members></members> </types> <types> <name>Picklist</name> <members></members> //Deployed as part of custom object file. Must dot qualify with object name </types> <types> <name>Report</name> <members></members> </types> <types> <name>SearchLayouts</name> <members></members> //Deployed as part of custom object file. Must dot qualify with object name </types> <types> <name>SharingReason</name> <members></members> //Deployed as part of custom object file. Must dot qualify with object name </types> <types> <name>SharingRecalculation</name> <members></members> //Deployed as part of custom object file. Must dot qualify with object name </types> <types> <name>SharingRules</name> <members></members> </types> <types> <name>ValidationRule</name> <members></members> //Deployed as part of custom object file. Must dot qualify with object name </types> <types> <name>WebLink</name> //Migration guide says component name is Weblink but that is incorrenct <members></members> //Deployed as part of custom object file. Must dot qualify with object name </types> </package>
Activate the new or refreshed sandbox after the sandbox is refreshed.
2. After refresh.
- Update sandbox SSO certificate if you have a certification and send new URL details to identity team.
- Un schedule scheduled apex, reports and dashboards. This would prevent unwanted batch jobs to run on the sandbox and perform any mass operations. Now for testing, you can run the jobs manually if needed.
- Re-schedule Jobs, Reports or Dashboards
- Update URLs in Custom Settings like the below example to avoid any unwanted data post to production applications.
- endpoints__c
- Update URLs in Custom Labels
- Update URLs in VF pages if there is any hard coded urls. ( really, you should move these to custom settings or custom labels)
- Update URLs in formula fields .
- Update URLs in other static content.
- Disable Chatter notification for users.
- Provide new endpoints/credentials to integration team or other systems for testing in development environments. This is important to prevent unwanted data posts to production applications.
- Set Deliverability for email access — Setup / Deliverability / Access level = System email only.
- On your home page, Update message area to differentiate between production and sandboxes.
- If you have consultants in your sandbox which you have to create every time after the sandbox refresh,create them as read only users in production. This way after you refresh sandbox, you can easily update them in sandboxes to give them access. Otherwise you will have to recreate these users every time which is a waste of time.
For partial and development sandboxes.
- If your sandbox is other than full or partial copy, do the following
- Migrate Custom Setting Data so that you can have data for testing.
- If you have Custom Objects which has static data, please reload them using data loader.
Handling Emails.
- Update Global Email Deliver-ability.
- Update Email addresses for Users to prevent accidental emails.
- Update email addresses in Custom Settings
- Update email addresses in Custom Labels
- Update hard coded emails in Workflow Email Alerts
- If you have email address fields in Standard and Custom Objects, the below apex class would help to update them in one shot to null!!
- Please copy the below apex class to your sandbox.
- Execute batch commands for each object you wish to update emails.
Apex Class code for batch update of email addresses in Standard and Custom sObjects
global class UpdateEmails implements Database.Batchable<sObject> { global final String Query; global final string EmailFields; global UpdateEmails(String q, String e){Query=q;EmailFields=e;} global Database.QueryLocator start(Database.BatchableContext BC) { return Database.getQueryLocator(query); } global void execute(Database.BatchableContext BC, List<sObject> scope){ List<sObject> sObjectListForUpdate = new List<sObject>(); for(sObject s : scope){ sObject newSObject = (sObject)s; for (String emailFld : EmailFields.split(';')) { if (newSObject.get(emailFld) != null) { string updatedEmail = String.valueOf(newSObject.get(EmailFld)) + '.fake’; newSObject.put(EmailFld, updatedEmail); } } sObjectListForUPdate.add(newSObject); } database.update(sObjectListForUpdate, false); } global void finish(Database.BatchableContext BC){} }
To execute this batch code, you can use the following syntax on your IDE or dev console where you can execute under anonymous window.
Database.executeBatch (new UpdateEmails(’SELECT id,ContactEmail,SuppliedEmail FROM Case WHERE ContactEmail != null or SuppliedEmail !=null','ContactEmail;SuppliedEmail'), 2000);
Some No-No’s.
- Do not plan your sandbox refresh during Salesforce spring, summer and winter release dates and times.
- Do not do sandbox refreshes during your regular development times. This would create double work on the team.
Please feel free to post your comments on the below post. I also have a checklist which i can share with you as well. Feel free to email me at buyan@eigenx.com for any further questions.