Wednesday, May 16, 2018

Fiddling with the SFDX CLI API calls

What makes the sfdx CLI tick? Sometimes learning how something works can be as much fun as actually using it.

The goal here is to capture the raw API calls the sfdx CLI is sending to the Salesforce APIs. In addition to a better understanding of what it is doing you can use it to debug the CLI itself.

This post was inspired by a, ahem, very similar post by Christian Carter - SFDX With Charles Proxy. The primary difference is that I'm using Fiddler on Windows rather than Charles Proxy on macOS.

Using the direct sfdx logging support is another option to monitor what is going on. Or even browsing the source directly under %LOCALAPPDATA%\sfdx (seems they are going to some lengths to hide the source now). While something like fiddler is more complicated (some might even say "fiddly") to configure, it is harder to hide anything from it (intentionally or otherwise).

Configure to intercept HTTPS traffic

After installation the first this is to configure Fiddler and Windows to allow interception and decryption of HTTPS traffic.

  1. Tools > Options
  2. HTTPS tab
  3. Check Decrypt HTTPS traffic
  4. Click 'Yes' to reconfigure Windows' Trusted CA Certificate You might want to read up on what a Root Certificate is before doing so.
  5. (Optional) change the drop down from "... from all processes" to "... from non-browsers only"
  6. (Optional) Toggle the "Skip decryption for the following hosts" to "Perform decryption for the following hosts". Then add *

Now the Fiddler is ready to intercept the traffic you need to configure sfdx to send it to the correct location. The default proxy port of 8888 is configured under Options > Connections > Fiddler listens on port:...

set http_proxy=http://localhost:8888
set https_proxy=https://localhost:8888

If you just stop there and try and call sfdx force:org:list you will find the CONNECTED STATUS comes back as "ECONNRESET". It would appear that node.js doesn't like our self signed root certificate. You can tell node to mind its own business with:


Again, you only want to do this for a single session and not configure that across all processes. It potentially opens you up to all sorts of man in the middle security attacks.

Now what?

Now my friend, now we can see each and every callout to the Salesforce APIs and the corresponding responses.

Lets look at what happens with the command sfdx force:org:list.

This reveals up to four API calls per valid Org. The exact calls will depend on the org types and if you have recently successfully authenticated to them. Generally, you get:

  1. A failed GET /services/data/v42.0 with an invalid token
  2. A POST /services/oauth2/token to refresh the access token
  3. A successful GET /services/data/v42.0
  4. A GET /services/data/v42.0/query?q=... with a SOQL query over ScratchOrgInfo. Presumably this only works with Scratch Orgs at this time.

So every org you register with SFDX needs 3 or 4 API calls with a force:org:list. There is certainly something to be said for dropping unused orgs.

Anyway... Happy Fiddling!

Wednesday, May 9, 2018

Salesforce Log Categories and Events by Level - Revisited

Way back in June 2014 I posted a table to logging events by level and category - Salesforce Log Categories and Events by Level.

I was never really happy with the table layout trying to squeeze that much data in. Also, new logging levels keep getting added and several have been shuffled around recently with respect to the level they occur at.

Here is a hopefully simpler revised attempt using lists. It is compiled directly from the Debug Log Levels detail page, so it should be eaiser to keep up to date.

Similar data can be found in the Debug Log Levels documentation. I found at the time of publishing that I had several in my list that didn't appear on that page, such as CALLOUT_REQUEST_PREPARE, USER_DEBUG_WARN, and DUPLICATE_DETECTION_MATCH_INVOCATION_DETAILS.

The levels are cumulative. So everything that appears at the ERROR level will appear at all the lower levels as well. Everything at the WARN level will appear at INFO, DEBUG, FINE, ... and so on.

Tuesday, April 10, 2018

The unofficial way to install Apps and Packages in Your Trailhead Playground

There is a Trailhead module called Trailhead Playground Management that includes a unit on Install[ing] Apps and Packages in Your Trailhead Playground. This is an important step in many other modules and day to day Salesforce work. You need to be able to install an App/Package into a target org so you can use its features.

The unit goes into the steps in some detail and focuses on being accessible to those just starting out with Salesforce.

Above is a quiz question from the Trailhead Playground Management module. It's technically correct as per the instructions in that module, but I don't think it is the best approach and from what I've seen is a common source of confusion.

I'd like to present an alternative approach. It might not be as accessible, but it does bypass a number of steps that can lead to further complications. So, without any more fanfare I present the Deployment Fish approved way of installing packages and apps into a Salesforce Org.

The URL Hack Manipulation Maneuver

The steps are as follows:

  1. Obtain the package installation URL.
  2. Copy everything from that URL except for the domain
  3. Log into the org where you want to install the package
  4. Paste the content copied from Step 2 after the domain.
  5. Follow the remaining prompts.

Let us try this as a worked example using the Install the DreamHouse app package from the Trailhead unit

  1. Obtain the package installation URL:
    Just right click on the package link and Copy Link Address. The approach will vary based on the browser, but there should be a fairly simple way to extract the link.
  2. Copy everything from that URL except for the domain:
    The ID with the 04t keyprefix is the important part here. That identifies what package/app you are installing.
  3. Log into the org/playground where you want to install the package
  4. Paste the content copied from Step 2 after the domain.
    URL before:
    URL after:
  5. Follow the remaining prompts.

AppExchange packages

A similar technique can be used with the AppExchange. The only catch here is they make it a bit harder to get the package version id. Lets use the Salesforce Adoption Dashboards as an example app.

  1. From the app listing, press "Get It Now".
  2. You may need to login to the AppExchange. It doesn't matter what account you log in with at this step.
  3. On the "Where do you want to install this package?" select either the "Install in Production" or "Install in Sandbox" buttons. It shouldn't matter.
  4. Move past the Confirm Installation Details page and press "Confirm and Install"
  5. You will end up at a login page. DO NOT LOGIN HERE!
    Look closely at the URL. You will see the package version ID.
    In my case it was
  6. Append that ID to /packaging/installPackage.apexp?p0= on your actual target org as you did in step 4 of the URL Manipulation Maneuver.

These steps may look complicated at first, but in reality it is a small cut-and-paste job to find the package version Id. Once you have the Id the rest of the process becomes much simpler. You could even look at automating the process using the SalesforceDX CLI.

The real benefit is for those that routinely work between multiple orgs. It provides more certainty that you are installing in the org you intended to. Plus you don't even need to figure out your Trailhead playgrounds authentication details.

Sunday, April 1, 2018

Breeding your own deployment fish

Sometimes it just isn't practical to head out into the ocean to catch your own deployment fish. Or the metadata gods don't favor your change set with a fresh catch.

What are you to do if the deployment fish aren't biting?

JavaScript to the rescue!

A few moments of playing on the /changemgmt/monitorDeploymentsDetails.apexp page reveals that JSON data about the current deployment status flows through SfdcApp.MonitorDeployment.InProgressComponent.refreshInProgressSection. We can call the same function ourselves and manipulate the totalComponentsCount and succeededComponentsCount data as required:

SfdcApp.MonitorDeployment.InProgressComponent.refreshInProgressSection(Sfdc.JSON.parse('{"hasErrors":false,"hasFatalError":false,"refreshInternalInMillis":3000,"hasCodeCoverageError":false,"totalTestsCount":20,"totalComponentsCount":4,"succeededComponentsCount":21,"isComponentSaveFailing":false,"isDeployComplete":true,"failedComponentsCount":0,"failedTestsCount":0,"isDeployCanceled":false,"completedDate":"4/1/2018 1:33 AM","hasTestRunStarted":false,"isCheckOnly":true,"isTestRunFailing":false,"isAbortRequested":false,"hasPayloadError":false,"isTestRunRequired":false,"stateDetail":"","succeededTestsCount":20,"deployStatus":"Succeeded"}'), !0);

Better yet, we can wrap it in our own function to call as required.

function deploymentFish(a, b) {
  var c = Sfdc.JSON.parse(document.getElementById(chartDataHiddenElementId).value);
  c.succeededComponentsCount = a;
  c.totalComponentsCount = b;
  document.getElementById(chartDataHiddenElementId).value = Sfdc.JSON.stringify(c);

Or, if you prefer, in bookmarklet form: (Installation link for Deployment Fish)

var d = prompt('Deployment fish size?', '17/14');
var a = parseInt(d.split('/')[0]);
var b = parseInt(d.split('/')[1]);
var c = Sfdc.JSON.parse(document.getElementById(chartDataHiddenElementId).value);
c.succeededComponentsCount = a;
c.totalComponentsCount = b;
document.getElementById(chartDataHiddenElementId).value = Sfdc.JSON.stringify(c);

Monday, March 12, 2018

The Trailhead Electric Imp project

For some time now I've been meaning to complete the Electric Imp Trailhead project. Unfortunately there were a couple of things holding me back from completing it.

Firstly, I needed the physical impExplorer Developer Kit hardware.

The board itself is a pretty reasonable $25 USD. And then I got to the shipping options to New Zealand in the cart and it looked something like this: (circa 2017)

Ouch! A bit hard to justify $116.49 shipping on a $25 purchase. To be fair, I checked it again today while writing this post and they now have some much more reasonable options via USPS starting at $16.33 USD.

Anyway, as luck would have it I found some time to sit down in the Dreamforce '17 Developer Forest and start working through the project on site with the provided hardware. For some reason that I can't recall I opted to work through this module with an existing Developer edition org (lack of time to delete and then create a new trail org?). It was also my downfall, as the org had a namespace defined and the IoT Contexts didn't seem to support namespaces. There were GACKs all over the place. Lesson learned, next time I'll be tackling it against a clean org.

Fast forward to today, some months after Dreamforce and I've got some spare time to sit down and tackle this again. An no, I haven't forgotten the second thing that was holding me back. I'm just getting to that now.

I needed a fridge to use for the project. One that also got a good WiFi connection. I don't know about you, but my actual fridge is a bit of a WiFi dead zone. It's probably either the proximity of the microwave or the Faraday cage I wrapped it in to protect my ripe avocados from EMP attacks. This was an easy enough problem to solve. Like anyone else with a 3D printer, the solution was only several hours of plastic extrusion away. It turns out there was a purpose made fridge ready to go on Thingiverse- Model Fridge for Salesforce IoT Electric Imp Developer Board.

With my new mini fridge, electric imp board, and three AA-batteries-I-borrowed-from-my-sons-remote-control-car-but-will-replace-before-he-wakes-up I embarked on completing the project.

There appears to be two general ways to approach this trailhead project. You can either follow the instructions carefully and double check everything as you go (my chosen path), or you can just jump through pressing as fast as you can because each step ends with the statement:

We won’t check any of your setup. Click Verify Step to go to the next step in the project.

Boo! This is actually a real pain, as there isn't anything to indicate if you've taken a misstep at any point along the way.

That said, it is all fairly straight forward if you are proficient at following instructions plus cutting and pasting.

The IoT orchestrations were new to me and hence the most challenging part of the project. For instance, I initially found it confusing that the conditions you define on a State are the exit criteria that transition to the other states. I guess this makes sense as a finite-state machine would only be concerned with the transitions it can make from the current state. E.g. If I'm in a door open state, I'm only interested in transitioning back to the default state when the door is closed again.

As the Electric Imp is communicating to Salesforce via Platform Events it provided an easy mechanism to send in mock readings via Apex. This helped with testing when your child wanted the AA batteries back.

Smart_Fridge_Reading__e mockReading = new Smart_Fridge_Reading__e();
mockReading.deviceId__c = '23733t1ed87bf1ee';
mockReading.door__c = 'Closed';
mockReading.humidity__c = 10.441;
mockReading.temperature__c = 8.6859;;

Things I'd change? I'd probably take the light sensor LUX reading and relay that directly back to Salesforce rather than having a configured level to indicate that the door is open. I'm assuming it has the current form to show how values can be passed back from the Device to the Agent for additional processing. I'm also interested in the accelerator and air pressure sensor which are part of the imp001 hardware but aren't utilized in this badge. Maybe another state if the fridge door is closed too hard?

If you go away from the project for several days you might get the following error message from the Agent code:

[Agent] ERROR: [ { "message": "Session expired or invalid", "errorCode": "INVALID_SESSION_ID" } ]

In my case that was caused by the agent code persisting the access_token that it gets via the initial OAuth process. There isn't an automated mechanism to drop the expired session details or refresh it. Instead you need to use the Agent URL to complete the OAuth process again.

See Expired OAuth details being restored by getStoredCredentials()

Other related Trailhead modules: