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/

Wednesday, January 29, 2020

PowerShell Scripts and Cmdlets - Part 3

In my last post, I walked through creating a Cmdlet using .NET and working in Visual Studio 2010.  To review, a PowerShell cmdlet is implemented as a class within a class library.  The cmdlet class inherits from one of two classes, Cmdlet or PSCmdlet.  At this point (I'm writing this post 8 and a half years later than part 2) I should just move onto the implementation without sweating the details too much.  I did find a blog post here - https://weblogs.asp.net/shahar/cmdlet-vs-pscmslet-windows-powershell -  that talks about the difference.  Long story short I'm going to derive from Cmdlet for reasons stated in that article.

Side Note: I had to chuckle a little (and I'm still smiling as I write this) that I actually am reading and finding useful a blog post that I wrote that long ago.  And I still need to google and research quite a bit whenever I do anything with PowerShell since I do it so infrequently.  So back to the nitty-gritty as Natcho Libre would say...

I should mention that for this particular implementation I have now moved on from Visual Studio 2010 and am now using Visual Studio 2017 (It is the year 2020 now, after all).  The class library I'm creating is going to be using a REST client library that I've created to call the Salesforce REST API.

I did follow my advice from the last post and found a verb that has Microsoft's official stamp of approval (by virtue of showing up in their latest documentation here).  I decided to use the Publish verb, and for the noun I used SalesforceContentVersion to indicate both the platform and the REST endpoint (ContentVersion).  Given that information my class looks like this:


using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Management.Automation;

namespace SFRest.PowerShell
{
    [Cmdlet("Publish", "SalesforceContentVersion")]
    public class PublishSalesforceContentVersionCmdlet : Cmdlet
    {
    }
}


Notice three important things in the above code snippet:

1. I've included a using System.Management.Automation to include the PowerShell namespace and make it easier to reference the PowerShell types.
2. I've indicated my Cmdlet verb and noun in the Cmdlet attribute on the class
3. I've inherited my class from Cmdlet

At this point I can compile my class without error and am ready to move on to the implementation.  Note that as I go off to implement this thing, I'm going to rely on Microsoft's Writing a Windows PowerShell Cmdlet to actually get it working.

The first thing I'm starting with is the parameters that I need to pass in.  In my case I need to pass in a bunch of string parameters that will be used for authentication with Salesforce and then parameters related to the metadata and data for the file that will be uploaded.

I'm not going to get into the details but basically most of my parameters are defined in my class like this:

[Parameter(Mandatory = true)]
public String FilePath { get; set; }


Most of my parameters are mandatory, but there is one that is a switch parameter which is an optional parameter that acts like a boolean.  By default the value is false, but when you specify this parameter on the command line it becomes true.  Here is my switch parameter:

[Parameter(HelpMessage = "Specify for loading a file to a production org.")]
public SwitchParameter IsProduction { get; set; }


The next step after specifying parameters was to actually write the code that would upload the file.

For my cmdlet, I put all of the code in the ProcessRecord method, which is overridden from the Cmdlet.ProcessRecord method.  To do this, you just override the method in your cmdlet class and implement away:

protected override void ProcessRecord()
{
    // Implementation goes here
}

Once you get the implementation out of the way, you can proceed to test your cmdlet.  I'll cover that in Part 4, hopefully before another 8 years has passed.

Wednesday, August 3, 2011

PowerShell Scripts and Cmdlets - Part 2

In my last post I talked briefly about my introduction to the world of PowerShell and outlined my plan for learning it. When I'm learning a new programming language or working in a new environment, I find it works well to have a real problem I'm trying to solve and then use that as the impetus to learn the language. That's why I picked the Unix "touch" command for my first cmdlet. There is no analogous cmdlet in PowerShell and it was something I wish I had when I was writing my first couple of PowerShell scripts.

In this post I want to dig a little deeper into how I implemented my touch-item cmdlet. I decided to use VisualStudio 2010, since it's the most recent release of VS as of now. I think prior versions of VS will work just as well, and most of this discussion will still be relevant if you are using VS 2008, 2005, etc.

Step 1 - Prerequisites
When starting out, you will need (of course) a version of Visual Studio and PowerShell installed. PowerShell might already be installed, depending upon the version of Windows you're running. But if not, you will need to install it. I'm using PowerShell 2.0 for this example. To access the PowerShell types in System.Management.Automation, you need to add a reference to System.Management.Automation in your project file. There are multiple ways of doing this. I learned after the fact that you can just edit your .csproj file directly and add a line like this:

<Reference Include="System.Management.Automation" />

This is much easier than what I did. If I remember correctly, I installed the PowerShell SDK and then added the reference via the VS UI as you typically would. I had to browse for it, though. It didn't appear in the .NET tab in the Add Reference dialog. The path to the assembly is:

[program files]\Reference Assemblies\Microsoft\WindowsPowerShell\v1.0\System.Management.Automation.dll

Step 2 - Create the Class
When you create your Visual Studio project, you should create a class library. Once you have created your project and added the reference to System.Management.Automation, you can create a class file that will contain the code for your cmdlet. To avoid too much typing, I added a using System.Management.Automation statement to access the types without having to type the namespace every time.

Your class should inherit from one of the PowerShell cmdlet base classes - Cmdlet or PSCmdlet. The class must be public. And the class must be decorated with the Cmdlet attribute. The Cmdlet attribute specifies the verb-noun name of the cmdlet. The MS documentation states you should only use approved verbs. I broke that rule since "Touch" is not an approved verb (oh well). In all, your class declaration should appear as follows:

[Cmdlet("Set", "CoolnessLevel")]
class SetCoolnessLevelCommand : Cmdlet
{
... code goes here
}

You should now have a project that compiles. At this point, you are ready to start writing some actual code to do something interesting, which will be the topic of my next post.

If you can't wait until then, MSDN has some really good documentation on writing PowerShell cmdlets.



Monday, July 25, 2011

PowerShell Scripts and Cmdlets - Part 1

I have to admit I am a newcomer to PowerShell - definitely late to the party, and probably not fashionably late, either. But I decided to dig into it since I had to write a PS script as part of a project at work. And after that I decided I wouldn't just leave it at that, but use this as an opportunity to pick up another platform to add to my resume. So I wrote out a list of things I want to learn and am picking them off one by one.

1. Write a script
2. Write a cmdlet
3. Write a script using Exchange Management shell
4. Write a script that uses the SharePoint object model
5. Write a script that interacts with AD, WMI, other?

The first thing on my list is writing a PS script. I wrote a script to move files off of various servers onto a central server into a structured hierarchy of folders. The script employed some interesting bits, like an XML configuration file, command-line syntax help, working with files and directories and even displaying progress via the Write-Progress cmdlet. Nothing overly complicated, but it was a good way to get introduced to PS. I also wrote a script to create test data to test my script. In writing that script, I discovered that PS has no cmdlet akin to the Unix touch command. Segue into my next task.

For my cmdlet task, I decided to write a command that can update the last modified timestamp of a file or group of files. My idea is to have this follow the conventions of the Unix touch command. Except for the name. Powershell has it's own naming convention that uses a verb-noun pair for naming commands. To stick with this convention, I decided to go with "touch-item" as the name of my cmdlet.

I won't go into the minute details of how you write a cmdlet, but the basics are you create a class that inherits the Cmdlet class, add an attribute specific to Powershell that defines the name of the cmdlet (i.e. touch-item, in my case), and then override some methods to provide the cmdlet's processing. For my development environment, I decided to use VisualStudio 2010 and targeting the .NET Framework 3.5.


Saturday, November 20, 2010

Rodents

Mice. I don't like them. I have to admit they are kind of cute. But I don't want them in my house. I really don't like the thought that they are running around in my cupboards. And of course they aren't house broken. That's the worst part. They leave their mess.

They also cause me more work. I don't really need any more projects. But now I need to go and find out where they are getting in, and then plug the holes. I already have enough things to do, I'm not really looking for more. That being said, it will feel good to have it done. And I'm planning on getting it done tomorrow.

Thursday, April 22, 2010

Cars

Yesterday, we bought a 2006 Toyota Sienna to replace our Venture. The Sienna has the LE trim level with some extras like heated leather seats. I'm not sure if Homelink is standard on the LE, but this one has it. Last night I checked out Homlink's website (http://www.homelink.com) and found that you can get instructions for programming it. Helpful, because the Sienna didn't come with an owners manual. There are a few things I need to attend to with this new van:

1. Program the homelink to work with the garage door opener.
2. Transfer the rubber floor mats from the venture to the new van
3. See about getting a passenger sideview mirror housing. The one on there is cracked.
4. Get a 1/8" stereo cable for plugging in the iPod.

I think it may have a disc changer in it too, but I'm not sure how many CDs it can hold.

Friday, March 13, 2009

Alec's Platelet Counts

I made a spreadsheet and form using Google Docs to track Alec's Platelet counts. It was amazingly easy! (Please DON'T fill out this form. I just put it up here temporarily to see how to embed it. I'll be taking it off of here after I figure out where to put it.)