Auto generate Package.xml using Ant – complete source code and Video

If we want to use Ant Migration tool to fetch Salesforce Organization metadata info or to deploy some changes to Salesforce, we need to create “package.xml” manually or take help of eclipse. wildcard character (*) doesnt work with some components like report, emailtemplate, dashboard and document. Package.xml must needs to have folder name and files inside folder name.  After getting package.xml from eclipse, what if some folder is added into Organization ? We need to update “package.xml” again with help of eclipse or do it manually. Therefore I wanted to automate generation of package.xml completely and came up with this article and below workaround.

I would be using Ant to generate “package.xml” with below steps

  1. get Ant Migration tool jar from here
  2. get AntContrib from here
  3. create basic template of “package.xml” with placeholders in place of Report, Dashboard, EmailTemplate and Document, sample here
  4. create Ant Macro to replace placeholders
  5. create second Ant Macro to fetch all folders available using Metadata API (using sf:listMetadata)
  6. create third Ant Macro to fetch files inside each folder using Metadata API (using sf:listMetadata)

We need to perform heavy file manipulation operations and iterate through each line, which is not possible by standard Ant  feature and therefore we need AntContrib library.

file – GeneratePackage_xml.properties
This file will have all configuration like username, Password

#   By      :   Jitendra Zaa
#   Date    :   12/31/2014
#   Address :   https://jitendrazaa.com
sfSandbox.serverurl = https://test.salesforce.com
sfPRO.serverurl = https://login.salesforce.com
 
sf.username = YOURUSERNAME
sf.password = YOURPASSWORD 
sf.xmlPath = tmp
sf.Name = TrainingOrg

package.xml.template = config/template-Package-Folders.xml

file – GeneratePackage_xml.xml
this file will have complete logic with definitions of custom Macro to retrieve folder names and files inside those folder. It is not necessary to create separate file, we can use build.xml itself however good to separate logic from main file.

<!--
    Author  :   Jitendra Zaa
    Date    :   12/31/2014
    Address :   https://jitendrazaa.com
-->
<project name="Generate Package.xml" default="testPackageXML" basedir="." xmlns:sf="antlib:com.salesforce">
    
    <!-- 
        Import AntContrib 
        Download it from - http://mvnrepository.com/artifact/ant-contrib/ant-contrib/1.0b3 
    --> 
      <taskdef resource="net/sf/antcontrib/antlib.xml">
        <classpath>
          <pathelement location="${basedir}/lib/ant-contrib-1.0b3.jar"/>
        </classpath>
      </taskdef>
       
    <property file="GeneratePackage_xml.properties"/>
    <property environment="env"/> 
  
    <macrodef name="Package_fetchFolderContents"> 
        <attribute name="folderName"/>
        <attribute name="componentType"/>
        <attribute name="orgName"/> 
        <attribute name="sfUserName"/>
        <attribute name="sfPassword"/>
        <attribute name="sfServerURL"/>        
        <sequential>             
            <sf:listMetadata
                    username="@{sfUserName}"
                    password="@{sfPassword}" 
                    serverurl="@{sfServerURL}"
                    metadataType="@{componentType}" 
                    folder="@{folderName}"
                    resultFilePath="tmp/@{orgName}-@{componentType}-@{folderName}.log"/> 
            <if>
                <!-- Execute only if file created -->
                <available file="tmp/@{orgName}-@{componentType}-@{folderName}.log"/>
                <then>
                    <loadfile srcfile="tmp/@{orgName}-@{componentType}-@{folderName}.log" property="@{orgName}.@{componentType}.@{folderName}" >
                        <filterchain> 
                            <ignoreblank/>
                            <linecontains negate="false">
                                <contains value="FileName:"/>
                            </linecontains>                             
                            <prefixlines prefix="&lt;members&gt;"/>
                            <suffixlines suffix="&lt;/members&gt;"/>
                            <tokenfilter> 
                                <replacestring from="FileName: reports/" to=""/>
                                <replacestring from="FileName: documents/" to=""/>
                                <replacestring from="FileName: email/" to=""/> 
                                <replacestring from="FileName: dashboards/" to=""/>  
                                <replacestring from=".report" to=""/> 
                                <replacestring from=".email" to=""/> 
                                <replacestring from=".dashboard" to=""/> 
                            </tokenfilter>
                        </filterchain> 
                    </loadfile>                     
                    <concat destfile="tmp/@{orgName}.Package.xml.Snnipet.@{componentType}" append="true"> 
                        ${@{orgName}.@{componentType}.@{folderName}}
                     </concat>
                    <!-- <echo> Extracted Report Names - ${@{orgName}.@{componentType}.@{folderName}} </echo> -->
               </then>
           </if>
        </sequential>    
    </macrodef>	
    <macrodef name="Package_computeFolderName">
        <attribute name="sfUserName"/>
        <attribute name="sfPassword"/>
        <attribute name="sfServerURL"/>   
        <attribute name="metadataType"/> 
        <attribute name="tmpFolderPath"/>   
        <attribute name="componentType"/>  
        <attribute name="orgName"/>           
        <sequential>                
            <sf:listMetadata
                    username="@{sfUserName}"
                    password="@{sfPassword}" 
                    serverurl="@{sfServerURL}"
                    metadataType="@{metadataType}" 
                    resultFilePath="@{tmpFolderPath}"/>
            <echo> Output file for sf:listMetadata - @{tmpFolderPath} </echo>          
            <loadfile srcfile="@{tmpFolderPath}" property="@{orgName}.@{metadataType}.onlyFolderName" >
                <filterchain> 
                    <ignoreblank/>
                    <linecontains negate="false">
                        <contains value="FileName"/>
                    </linecontains>  
                    <tokenfilter>
                        <replacestring from="FileName: reports/" to=""/>
                        <replacestring from="FileName: documents/" to=""/>
                        <replacestring from="FileName: email/" to=""/> 
                        <replacestring from="FileName: dashboards/" to=""/>  
                        <replacestring from=" " to=""/>
                    </tokenfilter>
                </filterchain> 
            </loadfile>                      
            <for param="line" list="${@{orgName}.@{metadataType}.onlyFolderName}" delimiter="${line.separator}">
              <sequential> 
                <echo> Folder - @{line} </echo>  
                <concat destfile="tmp/@{orgName}.Package.xml.Snnipet.@{componentType}" append="true">
                    &lt;members&gt; @{line} &lt;/members&gt; 
                </concat>                
                <Package_fetchFolderContents 
                    folderName="@{line}" 
                    componentType="@{componentType}" 
                    orgName="@{orgName}" 
                    sfusername="@{sfUserName}"
                    sfPassword="@{sfPassword}" 
                    sfServerURL="@{sfServerURL}" />                    
              </sequential>
            </for> 
        </sequential>
     </macrodef>     
    <macrodef name="GeneratePackage_xml">
        <attribute name="sfUserName"/>
        <attribute name="sfPassword"/>
        <attribute name="sfServerURL"/> 
        <!-- This attribute decides where to copy Package.xml -->
        <attribute name="packageXMLPath"/>  
        <attribute name="orgName"/>         
        <sequential>
             <!-- Delete All files first else it will append to existing old file -->
            <delete file="tmp/@{orgName}.Package.xml.Snnipet.Dashboard"/>
            <delete file="tmp/@{orgName}.Package.xml.Snnipet.Report"/>
            <delete file="tmp/@{orgName}.Package.xml.Snnipet.EmailTemplate"/>
            <delete file="tmp/@{orgName}.Package.xml.Snnipet.Document"/>
            
            <!-- Create Fresh Files -->
            <touch file="tmp/@{orgName}.Package.xml.Snnipet.Dashboard"/>
            <touch file="tmp/@{orgName}.Package.xml.Snnipet.Report"/>
            <touch file="tmp/@{orgName}.Package.xml.Snnipet.EmailTemplate"/>
            <touch file="tmp/@{orgName}.Package.xml.Snnipet.Document"/>
            
            <!-- Empty Content in each file -->
            <property name="blankVal" value=""/> 
            <concat destfile="tmp/@{orgName}.Package.xml.Snnipet.Dashboard" append="true"> ${blankVal} </concat>
            <concat destfile="tmp/@{orgName}.Package.xml.Snnipet.Report" append="true"> ${blankVal} </concat>
            <concat destfile="tmp/@{orgName}.Package.xml.Snnipet.EmailTemplate" append="true"> ${blankVal} </concat>
            <concat destfile="tmp/@{orgName}.Package.xml.Snnipet.Document" append="true"> ${blankVal} </concat>            
             
            <!-- 
                Imp Step :
                1. We must assign Password to property and then pass to other Macro else it will ignore
                   all occurrences of '$' in password
                2. Property Name must be unique so safer to add Org Name also
            -->
            <property name="sfPassword.prop.@{orgName}" value="@{sfPassword}"/>        
            <Package_computeFolderName 
                sfUserName="@{sfUserName}"
                sfPassword="$${sfPassword.prop.@{orgName}}" 
                sfServerURL="@{sfServerURL}"
                metadataType="DashboardFolder"
                componentType = "Dashboard"                
                tmpFolderPath = "tmp/@{orgName}.DashboardFolders.log" 
                orgName="@{orgName}" 
            />    
             
            <Package_computeFolderName 
                sfUserName="@{sfUserName}"
                sfPassword="$${sfPassword.prop.@{orgName}}" 
                sfServerURL="@{sfServerURL}"
                metadataType="ReportFolder"  
                componentType="Report"
                tmpFolderPath = "tmp/@{orgName}.ReportFolders.log" 
                orgName="@{orgName}" 
            />  
            
            <Package_computeFolderName 
                sfUserName="@{sfUserName}"
                sfPassword="$${sfPassword.prop.@{orgName}}" 
                sfServerURL="@{sfServerURL}"
                metadataType="EmailFolder" 
                componentType="EmailTemplate"
                tmpFolderPath = "tmp/@{orgName}.EmailFolders.log" 
                orgName="@{orgName}" 
            />
               
            <Package_computeFolderName 
                sfUserName="@{sfUserName}"
                sfPassword="$${sfPassword.prop.@{orgName}}" 
                sfServerURL="@{sfServerURL}"
                metadataType="DocumentFolder" 
                componentType = "Document" 
                tmpFolderPath = "tmp/@{orgName}.DocumentFolders.log" 
                orgName="@{orgName}" 
            /> 
            
            <!-- Load Generated File content which has Snippet of Package.xml in Property -->
            <loadfile property="@{orgName}.DashboardFolder.log.edit" srcFile="tmp/@{orgName}.Package.xml.Snnipet.Dashboard" failonerror="false"/>
            <loadfile property="@{orgName}.ReportFolder.log.edit" srcFile="tmp/@{orgName}.Package.xml.Snnipet.Report" failonerror="false"/> 
            <loadfile property="@{orgName}.EmailFolder.log.edit" srcFile="tmp/@{orgName}.Package.xml.Snnipet.EmailTemplate" failonerror="false"/>
            <loadfile property="@{orgName}.DocumentFolder.log.edit" srcFile="tmp/@{orgName}.Package.xml.Snnipet.Document" failonerror="false"/>
            
            <copy file="${package.xml.template}" tofile="@{packageXMLPath}" overwrite="true" failonerror="true"/> 
            <replace file="@{packageXMLPath}">
                <replacefilter token="_replaceReportMembersTag_" value="${@{orgName}.ReportFolder.log.edit}"/> 
                <replacefilter token="_replaceEmailTemplateMembersTag_" value="${@{orgName}.EmailFolder.log.edit}"/> 
                <replacefilter token="_replaceDashboardMembersTag_" value="${@{orgName}.DashboardFolder.log.edit}"/> 
                <replacefilter token="_replaceDocumentMembersTag_" value="${@{orgName}.DocumentFolder.log.edit}"/> 
            </replace>            
        </sequential> 
    </macrodef>
    
    <target name="testPackageXML"> 
        <GeneratePackage_xml
            sfUserName = "${sf.username}" 
            sfPassword = "$${sf.password}"
            sfServerURL = "${sfPRO.serverurl}"
            packageXMLPath = "${basedir}/${sf.xmlPath}/Package.xml" 
            orgName="${sf.Name}"  />            
    </target>
</project>

file – build.xml
this is main file needed by Ant which will be used to bootstrap

<!--
    Author  :   Jitendra Zaa
    Date    :   12/31/2014
    Address :   https://jitendrazaa.com
-->
<project name="Generate Package.xml subfile" default="testPackageXML" basedir="." xmlns:sf="antlib:com.salesforce">
    <import file="GeneratePackage_xml.xml" as="complete"/>
</project>

folder – config
copy “template-Package-Folders.xml” in this folder. we can have multiple formats of package.xml as per need.

folder – lib
store downloaded “ant-contrib-1.0b3.jar” in this folder

How to run this

  1. Open Command prompt
  2. Navigate to folder where “build.xml” is present
  3. execute command : “Ant” or “Ant testPackageXML”

This is just demo that how we can perform file manipulations in Ant and how to fetch folder and file names using Metadata API. Every project has specific needs so you may need to modify this code. Complete source code and jar file is available in my github account,


Posted

in

by

Comments

16 responses to “Auto generate Package.xml using Ant – complete source code and Video”

  1. Param Avatar
    Param

    Hi Jitendra, I am getting the below error on execution, any idea why…?

    $ ANT testPackageXML

    Buildfile: c:git_localmy_devAutoGenerate_Package.xml_Antbuild.xml

    testPackageXML:

    [delete] Deleting: c:git_localmy_devAutoGenerate_Package.xml_AnttmpDataloadOrg.Package.xml.Snnipet.Dashboard

    [delete] Deleting: c:git_localmy_devAutoGenerate_Package.xml_AnttmpDataloadOrg.Package.xml.Snnipet.Report

    [delete] Deleting: c:git_localmy_devAutoGenerate_Package.xml_AnttmpDataloadOrg.Package.xml.Snnipet.EmailTemplate

    [delete] Deleting: c:git_localmy_devAutoGenerate_Package.xml_AnttmpDataloadOrg.Package.xml.Snnipet.Document

    [touch] Creating c:git_localmy_devAutoGenerate_Package.xml_AnttmpDataloadOrg.Package.xml.Snnipet.Dashboard

    [touch] Creating c:git_localmy_devAutoGenerate_Package.xml_AnttmpDataloadOrg.Package.xml.Snnipet.Report

    [touch] Creating c:git_localmy_devAutoGenerate_Package.xml_AnttmpDataloadOrg.Package.xml.Snnipet.EmailTemplate

    [touch] Creating c:git_localmy_devAutoGenerate_Package.xml_AnttmpDataloadOrg.Package.xml.Snnipet.Document

    BUILD FAILED

    c:git_localmy_devAutoGenerate_Package.xml_AntGeneratePackage_xml.xml:232: The following error occurred while executing this line:

    c:git_localmy_devAutoGenerate_Package.xml_AntGeneratePackage_xml.xml:176: The following error occurred while executing this line:

    c:git_localmy_devAutoGenerate_Package.xml_AntGeneratePackage_xml.xml:91: Problem: failed to create task or type antlib:com.salesforce:listMetadata

    Cause: The name is undefined.

    Action: Check the spelling.

    Action: Check that any custom tasks/types have been declared.

    Action: Check that any / declarations have taken place.

    No types or tasks have been defined in this namespace yet

    This appears to be an antlib declaration.

    Action: Check that the implementing library exists in one of:

    -c:apache-ant-1.9.6lib

    -\myerhome1Userdirs$pasingh.antlib

    -a directory added on the command line with the -lib argument

    Total time: 0 seconds

    1. Deepak Roy Avatar
      Deepak Roy

      Hi Param, Were you able to resolve the issue?

  2. sneh Avatar
    sneh

    Hi Jitendra,

    I find your posts very helpful. I have a question here regarding the automation of deployment,

    I have developed an ant script which will copy latest modified files to a tempfolder and from there it will use a package.xml file which will have each and every component to deploy to destination org. can you guide me how can I accomodate this approach with my ant code? I can also get the file names which are in my tempfolder.

    Process currently following : Ant will look into eclipse local project folder and get latest modified files to a tempfolder.
    – Ant will go back to the source directory with the filenames copied into temp folder and get -meta.xml file if it exsits.
    – here I also want to create a package.xml on the fly using your approach.
    Please guide how can I combine? what would be the best way. Thank you so much for your help.

  3. Roshni Rahul Avatar
    Roshni Rahul

    Hi Jitendra,

    Thank you for the blog. It helped me to automate my migrations. I have another requirement which needs your guidance. After i push to github I am trying to create the ant script so that it will push only changed components from org 1 to org 2. Please give your thoughts on this.

    Thank you

  4. Karan Shekhar Avatar
    Karan Shekhar

    Hi Jitendra,

    You are amazing

  5. deepish adwani Avatar
    deepish adwani

    Hi Jitendra,
    Thank you for this amazing blog. I used it and successfully generated a Package.xml. It was really simple.
    Although my question is not related to this blog and slightly on higher level:
    Is it a best practice to keep all your meta data in versioning control ? (In my case, i used this tool to generate package.xml and keep all the data in git).
    I got couple of errors (mostly field missing, permission kind off) when i was trying to deploy this in another org.

    Any suggestions will be helpful. Looking forward for the reply. Thanks.

  6. Pranay Mistry (@pjigs) Avatar

    Hello @JitendraZaa. Do you have any script that will pull in only updated Metadata files from the entire ORG in the last 24 hours and update the package.xml or generate the package.xml

    For e.g.

    this

    *
    ApexClass

    gets updated to

    File1.cls
    ApexClass

  7. Vaishali Kaushik Avatar
    Vaishali Kaushik

    Hi Jitendra,

    I have few questions related to package.XML.

    I want to know if we can enable few settings in package.XML before deployment.

    Like : Enabling translation workbench ,

    Enabling multi-currency and more .

    How can i identify which configuration we can configure Using ANT deployment and which not.

    Please help me out with this issue.

  8. Vaishali Kaushik Avatar
    Vaishali Kaushik

    Hi Jitendra ,

    I have a question related to package.XML

    I want to know if we can enable few configuration settings using ANT .

    Like if i can enable multi-currency or can we enable Translation workbench using before deployment using package.XMl

    Please help me out with this issue.

    Thank you.

    1. Jitendra Avatar

      Yes, you can. Update Package.xml or configuration dynamically before deployment. This post might come handy – https://www.jitendrazaa.com/blog/salesforce/dynamically-remove-xml-content-from-metadata-before-salesforce-deployment-using-xmltask-video/

  9. Ankush Somani Avatar

    Thanks A lot Jitendra. This blog helped and saved lot of time. I was coming across java.head.runtime error. as one of Email folder has lot of temporary templates. Did some file editing and process one by one folder manually. Still it saved enough time. Thanks a ton.

  10. Anupam Avatar
    Anupam

    Hi

    This works fine but the content for the unfiled$public folder for report and dashboard is not fetched.
    I noticed in the report folder text file that the folder id is not present for this . Any idea how to resolve it.

    ************************************************************
    FileName: unfiled$public
    FullName/Id: unfiled$public/
    Manageable State: unmanaged
    Namespace Prefix:
    Created By (Name/Id): Anupam Tripathi/00590000000oQzwAAE
    Last Modified By (Name/Id): Anupam Tripathi/00590000000oQzwAAE
    ************************************************************
    ************************************************************
    FileName: reports/echosign_dev1__EchoSignReports
    FullName/Id: echosign_dev1__EchoSignReports/00l90000001yokzAAA
    Manageable State: installed
    Namespace Prefix: echosign_dev1
    Created By (Name/Id): Anupam Tripathi/00590000000oQzwAAE
    Last Modified By (Name/Id): Anupam Tripathi/00590000000oQzwAAE
    ************************************************************
    ************************************************************

    1. Anupam Avatar
      Anupam

      when using the generated package to retrieve the components i am getting below warning, as the macro was not able to retrieve the reports inside this folder and replace it with the placeholder.

      [sf:retrieve] package.xml – Entity of type ‘Report’ named ‘ FileName:unfiled$public ‘ cannot be found

  11. Anupam Avatar
    Anupam

    Hi

    This is not pulling report under folder “unfiled$public”. Any way to fix it?

  12. […] need to know name of all files thats part of any pull request. Example- in Salesforce you want to perform delta deployment with only components that are part of user […]

  13. […] are already many different tools build to automate the process of deployment […]

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.