|
<macrodef name="destroy" description="Destroys all metadata in an organization - Revision 16"> |
|
|
|
<attribute name="username" /> |
|
<attribute name="password" /> |
|
<attribute name="serverurl" default="https://login.salesforce.com" /> |
|
<attribute name="tempDir" default="temp/destroy" description="Directory to write metadata." /> |
|
<attribute name="apiVersion" default="40.0" /> |
|
|
|
<sequential> |
|
|
|
<!-- prompt user to confirm --> |
|
<input message="THIS TASK IRREVERSIBLY DESTROYS ALL METADATA. ARE YOU SURE?" validargs="@{serverurl}/?un=@{username}" /> |
|
|
|
<!-- http api helper --> |
|
<macrodef name="soapcall"> |
|
<text name="request" /> |
|
<attribute name="endpoint" /> |
|
<attribute name="tempfile" default="" /> |
|
<attribute name="soapaction" default="""" /> |
|
<sequential> |
|
<local name="request" /> |
|
<property name="request" value="@{request}" /> |
|
<script language="javascript">with (new JavaImporter(java.net, java.io)) { |
|
var line, result = '', connection = new URL('@{endpoint}').openConnection(); |
|
connection.setDoOutput(true); |
|
connection.setRequestMethod('POST'); |
|
connection.setRequestProperty('Content-Type', 'text/xml'); |
|
connection.setRequestProperty('SOAPAction', '@{soapaction}'); |
|
var writer = new OutputStreamWriter(connection.getOutputStream()); |
|
writer.write(project.getProperty('request')); writer.flush(); //request |
|
var reader = new BufferedReader(new InputStreamReader(connection.getInputStream())); |
|
while ((line = reader.readLine()) != null) result += line + '\n'; reader.close(); //response |
|
var echo = project.createTask('echo'); |
|
if ('@{tempfile}') echo.setFile(new File('@{tempfile}')); |
|
echo.setMessage(result); |
|
echo.perform(); |
|
}</script> |
|
</sequential> |
|
</macrodef> |
|
|
|
<!-- http soap login --> |
|
<local name="loginResponse.tmp" /> |
|
<tempfile property="loginResponse.tmp" prefix="loginResponse" suffix=".tmp" createfile="true" deleteonexit="true" /> |
|
<soapcall tempfile="${loginResponse.tmp}" endpoint="@{serverurl}/services/Soap/u/@{apiVersion}" soapaction="login"><![CDATA[ |
|
<Envelope xmlns="http://schemas.xmlsoap.org/soap/envelope/"> |
|
<Body> |
|
<login xmlns="urn:partner.soap.sforce.com"> |
|
<username>@{username}</username> |
|
<password>@{password}</password> |
|
</login> |
|
</Body> |
|
</Envelope> |
|
]]></soapcall> |
|
|
|
<!-- parse endpoint --> |
|
<local name="loginUrl" /> |
|
<loadfile property="loginUrl" srcFile="${loginResponse.tmp}"> |
|
<filterchain><tokenfilter><filetokenizer/><replaceregex flags="gs" pattern=".*(https://[^/]+).*" replace="\1" /></tokenfilter></filterchain> |
|
</loadfile> |
|
|
|
<!-- parse session --> |
|
<local name="sessionId" /> |
|
<loadfile property="sessionId" srcFile="${loginResponse.tmp}"> |
|
<filterchain><tokenfilter><filetokenizer/><replaceregex flags="gs" pattern=".*<sessionId>([^<]+)</sessionId>.*" replace="\1" /></tokenfilter></filterchain> |
|
</loadfile> |
|
|
|
<!-- clear role and permission set dependencies with execanon --> |
|
<soapcall endpoint="${loginUrl}/services/Soap/T/@{apiVersion}"><![CDATA[ |
|
<Envelope xmlns="http://schemas.xmlsoap.org/soap/envelope/"> |
|
<Header> |
|
<SessionHeader xmlns="urn:tooling.soap.sforce.com"> |
|
<sessionId>${sessionId}</sessionId> |
|
</SessionHeader> |
|
</Header> |
|
<Body> |
|
<executeAnonymous xmlns="urn:tooling.soap.sforce.com"> |
|
<String> |
|
delete [SELECT Id FROM PermissionSetAssignment WHERE PermissionSet.ProfileId = null]; |
|
User[] users = [SELECT Id FROM User WHERE UserRoleId != null]; |
|
for (User user : users) user.UserRoleId = null; |
|
update users; |
|
</String> |
|
</executeAnonymous> |
|
</Body> |
|
</Envelope> |
|
]]></soapcall> |
|
|
|
<!-- reset working directory --> |
|
<delete dir="@{tempDir}" /> |
|
<mkdir dir="@{tempDir}" /> |
|
|
|
<!-- determines org shape --> |
|
<local name="describeMetadataResult.tmp" /> |
|
<tempfile property="describeMetadataResult.tmp" prefix="describeMetadataResult" suffix=".tmp" createfile="true" deleteonexit="true" /> |
|
<sf:describeMetadata serverurl="${loginUrl}" sessionid="${sessionId}" resultFilePath="${describeMetadataResult.tmp}" /> |
|
|
|
<!-- clean metadata descriptions --> |
|
<local name="metadataTypes.tmp" /> |
|
<tempfile property="metadataTypes.tmp" prefix="metadataTypes" suffix=".tmp" createfile="true" deleteonexit="true" /> |
|
<concat destFile="${metadataTypes.tmp}"> |
|
<fileset file="${describeMetadataResult.tmp}" /> |
|
<filterchain> |
|
<linecontainsregexp><regexp pattern="ChildObjects|XMLName" /></linecontainsregexp> |
|
<tokenfilter><replacestring from="," to="${line.separator}"/></tokenfilter> |
|
<tokenfilter><replacestring from="ChildObjects: " to=""/></tokenfilter> |
|
<tokenfilter><replacestring from="XMLName: " to=""/></tokenfilter> |
|
<tokenfilter><replacestring from="*" to=""/></tokenfilter> |
|
<tokenfilter><ignoreblank/></tokenfilter> |
|
<sortfilter/> |
|
</filterchain> |
|
</concat> |
|
|
|
<!-- lists by type with regex filter --> |
|
<macrodef name="listMetadataForDestroy"> |
|
<attribute name="negate" /> |
|
<attribute name="pattern" /> |
|
<attribute name="metadataType" /> |
|
<sequential> |
|
<echo>Preparing destructiveChangesPost.xml - @{metadataType}</echo> |
|
<local name="listMetadataResult.tmp" /> |
|
<tempfile property="listMetadataResult.tmp" prefix="@{metadataType}" suffix=".tmp" createfile="true" deleteonexit="true" /> |
|
<sf:listMetadata serverurl="${loginUrl}" sessionid="${sessionId}" metadataType="@{metadataType}" resultFilePath="${listMetadataResult.tmp}" /> |
|
<concat destFile="@{tempDir}/destructiveChangesPost.xml" append="true"> |
|
<fileset file="${listMetadataResult.tmp}" /> |
|
<header filtering="false"><![CDATA[${line.separator}<types>${line.separator} <name>@{metadataType}</name>${line.separator}]]></header> |
|
<filterchain> |
|
<linecontains><contains value="FullName/Id" /></linecontains> |
|
<replaceregex pattern="FullName/Id: (.+)/.*" replace="<members>\1</members>" /> |
|
<linecontainsregexp negate="@{negate}"><regexp pattern="@{pattern}" /></linecontainsregexp> |
|
</filterchain> |
|
<footer filtering="false"><![CDATA[</types>]]></footer> |
|
</concat> |
|
</sequential> |
|
</macrodef> |
|
|
|
<!-- open destructive changes definition --> |
|
<echo file="@{tempDir}/destructiveChangesPost.xml"><![CDATA[<Package>]]></echo> |
|
|
|
<!-- iterates over (most) metadata types --> |
|
<loadfile property="" srcFile="${metadataTypes.tmp}"> |
|
<filterchain> |
|
<!-- AppMenu - AppSwitcher.appmenu - Error: The AppMenu called 'AppSwitcher' is standard and cannot be deleted --> |
|
<!-- AssignmentRules - Case.assignmentRules - Error: The AssignmentRules called 'Case' is standard and cannot be deleted --> |
|
<!-- AutoResponseRules - Lead.autoResponseRules - Error: The AutoResponseRules called 'Lead' is standard and cannot be deleted --> |
|
<!-- Certificate - SelfSignedCert.crt - Error: We can't delete this certificate because your Identity Provider is using it --> |
|
<!-- CleanDataService - DataCloudCompanyMatch - Error: You can't delete default data integration rule --> |
|
<!-- Community - Zone.community - Error: invalid parameter value --> |
|
<!-- CustomSite - BigAss.site - Error: insufficient access rights on cross-reference id --> |
|
<!-- CustomObjectTranslation - MyMeta__mdt-en_US - Error: The CustomObjectTranslation called 'MyMeta__mdt-en_US' is standard and cannot be deleted --> |
|
<!-- EscalationRules - Case.escalationRules: - Error: The EscalationRules called 'Case' is standard and cannot be deleted --> |
|
<!-- Flow - TaskNotify.flowDefinition - Error: insufficient access rights on cross-reference id --> |
|
<!-- InstalledPackage - Error: cannot modify managed object: state=installed --> |
|
<!-- MatchingRules - Account.matchingRule: - Error: Matching Rules have to be deleted individually --> |
|
<!-- RecordType - Metric.Completion - Error: Cannot delete record type through API --> |
|
<!-- SharingRules - Account.sharingRules - Error: The SharingRules called 'Account' is standard and cannot be deleted --> |
|
<!-- Workflow - Account.workflow - Error: Cannot delete a workflow object; Workflow Rules and Actions must be deleted individually --> |
|
<linecontainsregexp negate="true"> |
|
<regexp pattern="AppMenu|AssignmentRules|AutoResponseRules|Certificate|CleanDataService|Community|CustomSite|CustomObjectTranslation|EscalationRules|Flow|InstalledPackage|MatchingRules|RecordType|SharingRules|Workflow" /> |
|
</linecontainsregexp> |
|
<!-- Layout - Remove only: Account-Account %28Marketing%29 and WorkFeedback-Feedback Layout - Summer %2715 etc --> |
|
<!-- Profile - Remove only: Custom: Marketing Profile and Custom: Support Profile and Custom: Sales Profile etc --> |
|
<!-- ListView - Leaves behind: Activity.All, Asset.All, Campaign.All, Contract.All, Product2.All, User.All etc --> |
|
<!-- ApexPage - Leaves behind: SiteHome Visualforce Page which is required in orgs containing Force.com Sites --> |
|
<!-- CustomField - Leaves behind: BigObject Customer_Interaction__b.Score_This_Game__c etc --> |
|
<!-- MatchingRule - Leaves behind: Lead.Standard_Lead_Match_Rule_v1_0 and Account.Standard_Account_Match_Rule_v1_0 etc --> |
|
<!-- CustomObject - Remove only: Big Objects, Custom Objects, Platform Events, External Objects etc --> |
|
<!-- BusinessProcess - Leaves behind: Case.master etc --> |
|
<!-- CustomApplication - Leaves behind: standard__AppLauncher etc --> |
|
<scriptfilter language="javascript"> |
|
var negate = false, pattern = '.*', metadataType = self.getToken(); |
|
if ('Layout' == metadataType) (negate = false) | (pattern = '%27|%28|%29'); |
|
if ('Profile' == metadataType) (negate = false) | (pattern = 'Custom%3A'); |
|
if ('ListView' == metadataType) (negate = true) | (pattern = '\\.All</members>'); |
|
if ('ApexPage' == metadataType) (negate = true) | (pattern = '>SiteHome<'); |
|
if ('CustomField' == metadataType) (negate = true) | (pattern = '__b\\.'); |
|
if ('MatchingRule' == metadataType) (negate = true) | (pattern = 'Standard_'); |
|
if ('CustomObject' == metadataType) (negate = false) | (pattern = '__b|__c|__e|__x|__mdt'); |
|
if ('BusinessProcess' == metadataType) (negate = true) | (pattern = 'master'); |
|
if ('CustomApplication' == metadataType) (negate = true) | (pattern = 'standard__'); |
|
var macro = project.createTask('listMetadataForDestroy'); |
|
macro.setDynamicAttribute('negate', negate); |
|
macro.setDynamicAttribute('pattern', pattern); |
|
macro.setDynamicAttribute('metadatatype', metadataType); |
|
macro.execute(); //dynamic attributes are lowercase insistent |
|
</scriptfilter> |
|
</filterchain> |
|
</loadfile> |
|
|
|
<!-- close destructive changes definition --> |
|
<echo append="true" file="@{tempDir}/destructiveChangesPost.xml"><![CDATA[</Package>]]></echo> |
|
|
|
<!-- retrieves by type and regex replaces --> |
|
<macrodef name="bulkRetrieveForDestroy"> |
|
<attribute name="metadataType" /> |
|
<attribute name="directoryName" /> |
|
<attribute name="pattern" /> |
|
<attribute name="expression" /> |
|
<sequential> |
|
<echo>Preparing package.xml - @{metadataType}</echo> |
|
<mkdir dir="@{tempDir}/@{directoryName}" /> |
|
<sf:bulkRetrieve serverurl="${loginUrl}" sessionid="${sessionId}" retrieveTarget="@{tempDir}" metadataType="@{metadataType}" batchSize="10000" /> |
|
<replaceregexp flags="gs"> |
|
<fileset dir="@{tempDir}/@{directoryName}" /> |
|
<regexp pattern="@{pattern}" /> |
|
<substitution expression="@{expression}" /> |
|
</replaceregexp> |
|
</sequential> |
|
</macrodef> |
|
|
|
<!-- fix layout custom links - Error: This WebLink is referenced elsewhere in salesforce.com --> |
|
<bulkRetrieveForDestroy |
|
metadataType="Layout" |
|
directoryName="layouts" |
|
pattern="<layoutItems>\s+<customLink>[^<]+</customLink>\s+</layoutItems>" |
|
expression="<!--\0-->" |
|
/> |
|
|
|
<!-- fix layout custom buttons - Error: This WebLink is referenced elsewhere in salesforce.com - Order-Order Layout --> |
|
<replaceregexp flags="gs"> |
|
<fileset dir="@{tempDir}/layouts" /> |
|
<regexp pattern="<customButtons>[^<]+</customButtons>" /> |
|
<substitution expression="<!--\0-->" /> |
|
</replaceregexp> |
|
|
|
<!-- fix profile default apps - Error: Unable to delete custom app. Profiles are using this custom app as default --> |
|
<bulkRetrieveForDestroy |
|
metadataType="Profile" |
|
directoryName="profiles" |
|
pattern="<userPermissions>.*</userPermissions>" |
|
expression="<applicationVisibilities><application>standard__AppLauncher</application><default>true</default><visible>true</visible></applicationVisibilities>" |
|
/> |
|
|
|
<!-- fix role parents - Error: Your attempt to delete the role could not be completed because at least one role reports to that role --> |
|
<bulkRetrieveForDestroy |
|
metadataType="Role" |
|
directoryName="roles" |
|
pattern="<parentRole>[^<]+</parentRole>" |
|
expression="<!--\0-->" |
|
/> |
|
|
|
<!-- fix object listviews - Error: cannot delete last filter --> |
|
<bulkRetrieveForDestroy |
|
metadataType="ListView" |
|
directoryName="objects" |
|
pattern="<listViews>.*</listViews>" |
|
expression="<listViews><fullName>All</fullName><filterScope>Everything</filterScope><label>All</label></listViews>" |
|
/> |
|
|
|
<!-- fix site dependencies - Error: This static resource is referenced elsewhere in salesforce.com. Remove the usage and try again --> |
|
<bulkRetrieveForDestroy |
|
metadataType="CustomSite" |
|
directoryName="sites" |
|
pattern="<CustomSite xmlns="http://soap.sforce.com/2006/04/metadata">.*<active>([^<]+)</active>.*<allowStandardPortalPages>([^<]+)</allowStandardPortalPages>.*<clickjackProtectionLevel>([^<]+)</clickjackProtectionLevel>.*<indexPage>([^<]+)</indexPage>.*<masterLabel>([^<]+)</masterLabel>.*<requireHttps>([^<]+)</requireHttps>.*<siteType>([^<]+)</siteType>.*<subdomain>([^<]+)</subdomain>.*</CustomSite>" |
|
expression="<CustomSite xmlns="http://soap.sforce.com/2006/04/metadata">${line.separator}<active>\1</active>${line.separator}<allowStandardPortalPages>\2</allowStandardPortalPages>${line.separator}<clickjackProtectionLevel>\3</clickjackProtectionLevel>${line.separator}<indexPage>SiteHome</indexPage>${line.separator}<masterLabel>\5</masterLabel>${line.separator}<requireHttps>\6</requireHttps>${line.separator}<siteType>\7</siteType>${line.separator}<subdomain>\8</subdomain>${line.separator}<urlPathPrefix>\5</urlPathPrefix>${line.separator}</CustomSite>" |
|
/> |
|
|
|
<!-- fix site index pages - Error: Required field is missing: indexPage --> |
|
<local name="NumberOfSites.tmp" /> |
|
<condition property="NumberOfSites.tmp" else=""><resourcecount when="ne" count="0"><fileset dir="@{tempDir}/sites" /></resourcecount></condition> |
|
<resourcecount property="NumberOfSites.tmp"><fileset dir="@{tempDir}/sites" /></resourcecount> |
|
<script language="javascript">with (new JavaImporter(java.io)) { |
|
if ('${NumberOfSites.tmp}') { |
|
var page = project.createTask('echo'); |
|
page.setFile(new File('@{tempDir}/pages/SiteHome.page')); |
|
page.setMessage('<apex:page/>'); |
|
page.perform(); |
|
var meta = project.createTask('echo'); |
|
meta.setFile(new File('@{tempDir}/pages/SiteHome.page-meta.xml')); |
|
meta.setMessage('<ApexPage><label>SiteHome</label></ApexPage>'); |
|
meta.perform(); |
|
} |
|
}</script> |
|
|
|
<!-- fix support setting queue dependencies - Error: cannot delete queue that is in use --> |
|
<mkdir dir="@{tempDir}/settings" /> |
|
<echoxml file="@{tempDir}/settings/Case.settings" namespacePolicy="all"> |
|
<CaseSettings> |
|
<defaultCaseOwner>@{username}</defaultCaseOwner> |
|
<defaultCaseOwnerType>User</defaultCaseOwnerType> |
|
</CaseSettings> |
|
</echoxml> |
|
|
|
<!-- fix big objects - Error: Custom BigObjects do not support layouts --> |
|
<delete><fileset dir="@{tempDir}" includes="**/*__b*" /></delete> |
|
|
|
<!-- MANUAL COMPONENTS --> |
|
<!-- CustomSite URL Rewriter is not available through Metadata API --> |
|
<!-- Inbound Email Services are not available through Metadata API --> |
|
<!-- Lead Settings behaviour is not available through Metadata API --> |
|
|
|
<!-- strips namespaced components --> |
|
<macrodef name="unspecifyForDestroy"> |
|
<attribute name="namespacePrefix" /> |
|
<sequential> |
|
<echo>Stripping namespaced components: @{namespacePrefix}</echo> |
|
<replaceregexp |
|
flags="gm" |
|
file="@{tempDir}/destructiveChangesPost.xml" |
|
match="<members>[^<]*@{namespacePrefix}__[^<]+</members>" |
|
replace="<!--\0-->" |
|
/> |
|
<delete><fileset dir="@{tempDir}" includes="**/@{namespacePrefix}__*" /></delete> |
|
</sequential> |
|
</macrodef> |
|
|
|
<!-- iterates over all namespace prefixes --> |
|
<echo>Listing installed packages...</echo> |
|
<local name="InstalledPackage.tmp" /> |
|
<tempfile property="InstalledPackage.tmp" prefix="InstalledPackage" suffix=".tmp" createfile="true" deleteonexit="true" /> |
|
<sf:listMetadata serverurl="${loginUrl}" sessionid="${sessionId}" metadataType="InstalledPackage" resultFilePath="${InstalledPackage.tmp}" /> |
|
<loadfile property="" srcFile="${InstalledPackage.tmp}"> |
|
<filterchain> |
|
<linecontains><contains value="FullName/Id" /></linecontains> |
|
<replaceregex pattern="FullName/Id: (.+)/.*" replace="\1" /> |
|
<sortfilter /> |
|
<uniqfilter /> |
|
<scriptfilter language="javascript"> |
|
var macro = project.createTask('unspecifyForDestroy'); |
|
macro.setDynamicAttribute('namespaceprefix', self.getToken()); |
|
macro.execute(); //dynamic attributes are lowercase insistent |
|
</scriptfilter> |
|
</filterchain> |
|
</loadfile> |
|
|
|
<!-- create package definition for fixes --> |
|
<echoxml file="@{tempDir}/package.xml" namespacePolicy="all"> |
|
<Package> |
|
<version>40.0</version> |
|
<types> |
|
<name>ApexPage</name> |
|
<members>*</members> |
|
</types> |
|
<types> |
|
<name>CustomSite</name> |
|
<members>*</members> |
|
</types> |
|
<types> |
|
<name>Layout</name> |
|
<members>*</members> |
|
</types> |
|
<types> |
|
<name>ListView</name> |
|
<members>*</members> |
|
</types> |
|
<types> |
|
<name>Profile</name> |
|
<members>*</members> |
|
</types> |
|
<types> |
|
<name>Role</name> |
|
<members>*</members> |
|
</types> |
|
<types> |
|
<name>Settings</name> |
|
<members>*</members> |
|
</types> |
|
</Package> |
|
</echoxml> |
|
|
|
<!-- destroy! --> |
|
<sf:deploy |
|
serverurl="${loginUrl}" |
|
sessionid="${sessionId}" |
|
deployRoot="@{tempDir}" |
|
ignoreWarnings="true" |
|
singlePackage="true" |
|
purgeOnDelete="true" |
|
/> |
|
|
|
</sequential> |
|
|
|
</macrodef> |