apfell-jxa ls example

This section will walkthrough an example of how to write a browser script for a command and leverage support scripts.

Command Output

The first step is to understand the command output that you will be scripting. This is a lot easier to do if you can make the output structured in some fashion. Apfell agents tend to do JSON output, but this isn't a hard requirement.

For the ls command with the apfell-jxa agent, the output looks like:

{
      "NSFileExtensionHidden": false,
      "NSFileCreationDate": "2017-06-26T18:04:55.000Z",
      "NSFileReferenceCount": 6,
      "NSFileModificationDate": "2019-07-27T21:45:07.269Z",
      "type": "D",
      "files": [
            {
                  "name": "test.txt",
                  "type": "",
                  "size": 1475,
                  "permissions": "644",
                  "owner": "itsafeature(501)",
                  "group": "staff(20)",
                  "hidden": ""
            }
      ],
      "name": "/Users/itsafeature/Desktop/",
      "size": 192,
      "permissions": "700",
      "owner": "itsafeature(501)",
      "group": "staff(20)",
      "hidden": ""
}

So, there is information about the folder or file that ls was run against, and if it's a folder, there's an array of extra information in a files keyword.

Creating a Script

When creating a script, it's important to understand the flow of what gets passed to your script and when it gets called. Scripts are loaded in to the browser when you request certain pages and are applied to tasks every time. So, if you have a callback with three ls commands, the ls browser script will be applied to all of them. Refresh the page and it'll be applied to all of them again.

Since a script can be called multiple times for a single response, it's important to not issue permanent changes. For example, you wouldn't want the browser script for ls to issue an additional task to the callback to download a specific file because every time the page is refreshed, viewed by another operator, or a number of other scenarios, the agent would get tasked again and again. That sort of scripting will come later.

The next step is to start creating the function:

function(task, response){  // this will be the same for all scripts tied to a specific command
  var output = "";
  for(var i = 0; i < response.length; i++){
    try{
        var data = JSON.parse(response[i]['response']);
        output += JSON.stringify(data, null, 2);
    }catch(error){
        return "Failed to parse json with error: " + error.toString() + "\n" + JSON.stringify(response, null, 2);
    }
  }
  return output;
}

Let's start examining the above basic script:

  • function(task, response)

    • Since this is tied to a specific command, it'll always have this same definition, but of course the names of the variables are up to you. The first parameter will be a JSON dictionary of the task and the second parameter will be an array of JSON dictionaries of the responses.

  • The next thing is just looping through the responses. For a basic starting point and knowing that the output will be JSON, try to just parse the actual response value into JSON and convert it back to a string to display. If everything is parsed correctly, then our output from this should look the same as if there was no browser script applied at all. As a good rule of thumb, if there's an error, it's good to display the error and the actual output.

Displaying Output

The next step is to decide what to do with the output. The easiest and most common scenario will be to display the output in a table, but you're able to do whatever. Since this is likely to be a common task, it's good to create a support script to create tables in general, then simply leverage that script with our command's specific data.

There is already a support_script called create_table that takes in two arguments:

  1. An array of dictionaries that specify the name of a column and the size of the column in rem element sizes. By default ,the table spans the width of the screen, so sometimes the columns will be bigger than specified

  2. An array of dictionaries that specify the content and styling of the rows.

Judging from our sample output, an appropriate call to this support script might look like the following:

support_scripts['create_table'](
[
{"name": "name", "size": "10em"},
{"name": "size", "size": "2em"}, 
{"name": "permissions", "size": "2em"},
{"name": "owner","size": "3em"},
{"name": "group", "size": "2em"},
{"name": "hidden", "size": "1em"},
{"name": "type", "size": "1em"}
], rows);

Now to actually get the data for the rows.

var row_style = "";
// to the columns "hidden" and "type", style them to be "text-align:center"
var cell_style = {"hidden": "text-align:center",
                  "type":"text-align:center"};
// push information about the initial file
rows.push({"name": data['name'],
           "size": data['size'],
           "permissions": data['permissions'],
           "owner": data['owner'],
           "group": data['group'],
           "hidden": data['hidden'],
           "type": data['type'
           "row-style": row_style,
           "cell-style": cell_style
});
// check if ls of directory or file
if(!data.hasOwnProperty('files')){data['files'] = []};
// iterate through all the files and add them to the rows for the table
data['files'].forEach(function(r){
  var row_style = "";
// if the filename has .sh in it, make the row red with white text
  if(r['name'].includes(".sh")){row_style="background-color:red;color:white"}
  rows.push({"name": r['name'],
             "size": r['size'],
             "permissions": r['permissions'],
             "owner": r['owner'],
             "group": r['group'],
             "hidden": r['hidden'],
             "type": r['type'],
             "row-style": row_style,
             "cell-style": {"hidden": "text-align:center",
                            "type":"text-align:center"}
            });
});

Now that all of the data is parsed out and formatted for the create_table support script, it can be applied and tested.

Final Script and Output

Now putting it all together and registering it gives:

Now any apfell-jxa ls command will be automatically scripted with the code as seen below:

On an individual basis, the blue toggle switch can be flipped to show the original output again as well

Last updated