Can’t we just use the Migration Tools and a destructiveChange.xml?
Unfortunately not! While it may seem obvious just to create a destructiveChanges.xml that lists all of the content in the org and just run the deploy (undeploy really), this will not work if you have a more complex code base. The migration tools are not good at resolving dependencies and relationships. If you have references between pages, components, static resources, custom fields, rollup fields and reports etc you may find that the undeploy fails as it does not resolve the dependencies correctly and hence cannot remove all the content listed.
To remove the content from the org we need to first find all the content that is in the org, remove any dependencies and then undeploy all of the content.
Retrieve of the Metadata Content
We can retrieve the content we want by specifying the items we want in a package.xml file but if we don’t know what content is in the org, how can we create the package.xml in the first place? Although many of the types we can specify in the package.xml can be starred (<members>*</members>) which will return all of those types in the org, many types like reports or custom fields on standard objects do not support the starred syntax. This poses a problem because how can we empty an org if we don’t know all of the items that need to be removed. However, there are some features we can use to get the types and content available in an org.
The Salesforce Migration Tools have a task called sf:describeMetadata that will list all of the types that are available in an org. To make this task easier to work with we created an ant macrodef for it:
file attribute, that contains entries like the following for every type available in the org:
This task will create a file, specified by thesf:listMetadata. The output of this target is a file that contains the metadata for all instances of the the type specified. The following is an example of the output of sf:listMetadata for the type objects:
There is an additional task in the Salesforce Migration Tools that will allow you to find out all the instances of a specified type in an org called
Similar to the sf:describeMetadata task, we can create a macrodef that makes the sf:listMetadata task easier to use as it defaults some the attributes required and alters the output into a format that is easier for us to work with:
With these 2 metadata macros we can identify the entire contents of the org by listing the metadata for all types in the org. However there is a complication we must deal with; some content like reports are contained in folders and the list metadata task cannot list all the content for all folders, it can only list the content for a specified folder using the folder attribute. If we want to retrieve all of the content then we need to be able to identify all of the folders but currently the Salesforce Migration tools do not support this so we need another way to achieve it. The Force.com Command Line Interface (CLI) from Dave Carroll is a great utility that can help us query the reports using SOQL. This utility is excellent and I may do blog post in future covering its use in detail but for the moment we will just use it to query report content.
Download Force.com CLI and save it in the lib directory of your project. Then add the following 2 tasks to your build.xml:
These tasks will allow SOQL queries to be run and the result stored in a property specified in the outputproperty attribute. With the soql task it is possible to retrieve all the content from an org dynamically. The following four tasks are wrapper tasks that use JavaScript to call the other tasks in order to retrieve all of the content from an org. The target retrieve.all.metadata at the end simply uses these tasks to retieve the metadata listing for all content in the org into the temp directory:
The various metadata listing files can be used to create a package.xml file that can be used to retrieve the entire contents of the org. The following target will create the package.xml and retrieve all of the content into the temp directory.
retrieve.all.content target and then browse in the temp directory now you will see metadata files for all of the content in your org.
If you run this newBreak Dependencies in the Code / Metadata
Now that we have all of the content we can break all of the dependencies in the metadata so that we can undeploy all of the content. This will leave a clean developer org that can be reused for future development without the need to create a new developer org.
To break the dependencies we need to blank the code for triggers, classes, pages, components and formulas and deploy them to the org. We can clear the code with regular expressions and simple XSLT operations.
The regular expression for clearing triggers is very simple, we simply need to remove all content between the first curly brace { and the closing curly brace }. The following reset.triggers target adds a space to the start of every line in the trigger files, removes any comments, removes all line breaks and then removes any content after the first opening curly brace and replaces it with a closing brace, effectively clearing all the trigger content.
@RestResource notation that may be at the top of the file.
A very similar target can be used to clear classes, however this target needs to strip any
The Visualforce pages and components can also be cleared using regular expressions but in these case we just replace the entire content of the file with empty <apex:page/> or <apex:component/> elements. The following tasks are used for this purpose:
The formulas on custom fields can be cleared using an XSLT operation. The formulas are not cleared really but we set them to return default primitive values so that there are no dependencies on other org content. The following xslt file, saved as reset.formulas.xslt in the xslt directory, will reset the formulas of all custom fields in your org.
This XSLT can be applied to the object files to reset the formulas using the following target:
Now we have all the targets we need to reset the content we can create a target to deploy all of the cleared content, which will break all the dependencies.
package.xml and a destructiveChanges.xml listing the content that needs to be removed. Creating an empty package.xml involves a very simple XSLT operation on the package.xml retrieved in the temp directory (save this file as empty.package.xml in the xslt directory):
Once the deploy is complete, we can use an undeploy to remove all of the custom content which will leave the org clean of all content and ready for reuse. In order to undeploy the content we need an emptydestructive.changes.xslt in the xslt directory:
We can create the destructiveChanges.xml with an XSLT operation also but this is a bit more complicated as we need to omit the Salesforce standard content that cannot be removed from an org. The following XSLT file omits the standard content from the package.xml should be saved asdestructiveChanges.xml that will remove all the custom content in the org. The follow target removes all of the content:
The above two XSLT files are used to create the package.xml and theundeploy.content target is run the org is clean of all custom content is ready for reuse. Although the steps to get this point may have seemed long winded and complicated, you now have a procedure that can be reused on any number of orgs so they can be recycled.
Once thisAlthough this blog post may seem long and somewhat complicated, the targets and tasks created will be applicable to all developer orgs and can be used a utility to recycle orgs. As previously mentioned recycling orgs saves time and eliminates the need to configure orgs when starting new development work.