Tuesday, January 11, 2022

PowerShell and XML

 A very poetic title, I must say.  Today's problem is looking for buttons in Salesforce.  I want to find all buttons for all objects.  In addition, I want to know if any of them are Javascript buttons.  I could manually go through each object in Setup and look at all of the buttons, but that doesn't sound like too much fun (actually that really sounds quite awful).

Finding anything code-, or configuration-, or metadata-related in Salesforce can be somewhat of a challenge.  The Developer Console has a limited ability to find things in code via it's 'Search in Files' tool under the Edit menu.  However, in my experience, this tool doesn't always find what I'm looking for.  I've had better luck using Visual Studio Code to search in files after retrieving a metadata package from the org.  The down-side to that is if you are not setup to use Visual Studio Code with Salesforce, there is a learning curve and legwork to be done.  (Another blog post for another day)  But, if you are setup in Visual Studio Code, and have the metadata from your org downloaded, then it's much easier to find what you're looking for using its global search.

So, how can we find buttons using the VS Code global search?  Well, the buttons are defined in the object XML and can be found by searching for 'webLinks' (the name of the element that represents buttons, links and actions for the object).  A quick search of 'webLinks' in the VS Code search turns up many hits.


We can very quickly see how many buttons there are, but this is not very useful unless you want to click on each one, open it in the file viewer, and review the contents of the webLinks element.

Sounds like too much work to me.  What I'm really looking for is something that will search through all of the object files for webLinks, maybe filter based on some other attributes of the webLinks elements, and then output useful information about the webLinks in a nice format.

I'm just going to jump to the next thought in my thought process, which was - since the metadata from Salesforce is XML, then it can be searched using XPath or XQuery.  Well, my actual thought was "I think there is a way to query XML, so I'm going to search Google to see what it is," which lead me to XPath and XQuery.  And, of course, the next question was "how do I run XPath or XQuery?"

Well... based on my previous blog posts, you can probably guess where I'm heading - PowerShell.  Let's go with the idea that this can all be done using PowerShell, and break the problem down.  (Side note: there is an XPath extension for Visual Studio Code.  After a cursory glance at the readme, I decided to stick with PowerShell for now. https://marketplace.visualstudio.com/items?itemName=deltaxml.xslt-xpath)

1. We need to go through all of the object files (they're XML, despite having the extention .object)

2. We need to read each file in as XML

3. We need to use XPath/XQuery to find the <webLinks> elements in each file

4. We need to output useful attributes from each <webLinks> element into a format the is friendly for non-technical people to review.

Now we can see each basic element of the problem, which all appear to be pretty manageable.  Time to unpack (hate that term) each one!


Looping Through the Object Files

First things first, we need to go through all of the object files.  The basic idea I have is, for a given directory, go through that folder and all subfolders, find all of the files of type *.object (or really could be whatever extension), and open each one in turn as XML.

Going through a folder (and subfolders) and finding files of a certain extension can be done using the Get-ChildItem command [1].   

Get-ChildItem -Path .\ -Include *.object -Recurse -ErrorAction SilentlyContinue

Read Each File in as XML

We can use the Get-Content cmdlet to read the file contents and use the [xml] type accelerator to bind the content to an XMLDocument object. [2,3]  This allows us to easily access the elements and attributes of the XML document.

[xml]$objectFile = Get-Content $_


Now we can take the files that are found with the Get-ChildItem command and pipe them into a ForEach-Object loop that uses the Get-Content command as follows:

Get-ChildItem -Path .\ -Include *.object -Recurse -ErrorAction SilentlyContinue |
ForEach-Object {

    Write-Host "Processing" $_.BaseName "..."

    [xml]$objectFile = Get-Content $_
    
    # do more processing with the XML document
    
}


Use XPath to Find <webLinks> Elements

Now that we have the file open, we can use XPath to look for the <webLinks> elements.  The XPath syntax for finding all webLinks elements is:

//webLinks

The Select-Xml command lets us use XPath to find nodes of a given name.  To find all <webLinks> nodes, the syntax is:

Select-Xml -Xml <XMLDocument> -XPath "//webLinks"

Now, there is a bit of a wrinkle in working with Salesforce .object files.  They use a custom namespace.  After some trial and error, I determined that you must use the -Namespace parameter to supply the custom namespace in a hash table in order to have the XPath actually match any nodes.  To do this, you need to create the namespace hash table as follows:

$sfdcNamespace = @{sfdc="http://soap.sforce.com/2006/04/metadata"}

Then, use the namespace in the Select-Xml command as follows:

Select-Xml -Xml <XMLDocument> -XPath "//sfdc:webLinks" -Namespace $sfdcNamespace

Notice that in the XPath expression, you must qualify the node name with the label used when creating the namespace hashtable (in this case, the label I used is 'sfdc').  This label is arbitrary, but you cannot use 'xmlns' for your label.

Output the Data

Now for the most visually appealing part, we get to output the data in a nice pretty format.

Footnotes:

1. https://devblogs.microsoft.com/scripting/use-windows-powershell-to-search-for-files/

2. https://devblogs.microsoft.com/scripting/powertip-use-powershell-to-easily-read-an-xml-document/

3. https://devblogs.microsoft.com/scripting/exploring-xml-document-by-using-the-xml-type-accelerator/