
- Post History
- Subscribe to RSS Feed
- Mark as New
- Mark as Read
- Bookmark
- Subscribe
- Printer Friendly Page
- Report Inappropriate Content
07-31-2018 09:51 AM - edited 12-29-2023 04:14 PM
NOTE: MY POSTINGS REFLECT MY OWN VIEWS AND DO NOT NECESSARILY REPRESENT THE VIEWS OF MY EMPLOYER, ACCENTURE.
DIFFICULTY LEVEL: ADVANCED
Assumes having taken the class SSNF and has good advanced level of knowledge and/or familiarity with Scripting in ServiceNow. Assumes good knowledge and/or familiarity of Orchestration, Workflows, and Scripting in ServiceNow.
Originally Orchestration contained a baseline Run PowerShell Activity. Unfortunately this was deprecated in Helsinki Patch 1. With this article I show how you can bring back the Run PowerShell Script functionality using Orchestration's Custom Activity Builder.
I will also introduce a couple of best practices I use when creating Custom Activities:
- Pre- and Post-Processing boiler plate scripts
- Custom Activity Script Includes
- Custom Activity Logging
- JSON parameter passing to a Custom Activity
The original baseline Run PowerShell Activity (deprecated) allowed the developer to select a MID Server PowerShell script via a reference lookup field, and pass any parameters via JSON. We won't be able to achieve that exact same functionality, but it will be close enough.
Pre-Requisites
- This lab will work fine on your Personal Developer Instance (PDI).
- You will need to activate the Orchestration plugins to be able to create Custom Activities.
Mini-Lab: Activating Orchestration - Some Notes - You will need to install your own local MID Server if you do not have one already.
ServiceNow Discovery 101: Setting Up a Local MID Server
TIPS
We will build this example Globally scoped. If you want to put this into the actual baseline PowerShell folder then you will need to change the application scope to PowerShell, create the new Custom Activity, and clear out the Category. The new activity will then appear in the PowerShell folder, and you can still use it in your Globally scoped workflows.
You might want to create an update set to capture your work. You can click on the Publish button on the new Custom Activity and the Test Workflow to save your work in the update set.
Lab 1.1: Script Include Library
It is a good idea to work through the following article first: Mini-Lab: Creating a Custom PowerShell Command Activity. As we will be using the Script Include created there as a basis for creating our new PowerShell Run Script activity.
- Navigate to System Definition > Script Includes .
- Search for and edit the ML_WorkflowActivityUtils script created in the previous article.
- Modify the script to be the following:
var ML_WorkflowActivityUtils = Class.create();
ML_WorkflowActivityUtils.prototype = {
initialize: function(location) {
this.location = location;
},
// method to handle the post process results of a Custom Activity
postProcessing: function(executionResult, activityOutput) {
if (gs.nil(executionResult.error)) {
// initialize our result variable
activityOutput.result = "success";
try {
var output = executionResult.output;
// if our output returned information then capture it
if (output != null) {
activityOutput.output = output.substring(output.indexOf(''));
gs.info('---> [{1}] activityOutput.output: {0}',
[activityOutput.output, this.location + '.postProcessing']);
}
} catch (err) {
gs.error('---> [{1}] ERROR: {0}', [err, this.location + '.postProcessing']);
}
} else {
// we had something bad happen, handle it!
activityOutput.result = "failure";
activityOutput.errorMessage = executionResult.errorMessages;
gs.info('---> [{1}] activityOutput.errorMessage: {0}',
[activityOutput.errorMessage, this.location + '.postProcessing']);
}
},
// parse the incoming JSON containing the parameters and push it into the
// powershell activity parameters list
processJSONParameters: function (activityInput, executionParam) {
if (activityInput.Parameters !== null) {
// got this idea from the uncallable Script Include: ExchangeUtil
// convert the incoming JSON string to an Object array
var params = JSON.parse(activityInput.Parameters);
// loop through the array and pull out the key/value pairs
for (var key in params) {
var parameter = {};
parameter.name = key;
parameter.type = "plain";
parameter.value = params[key];
// this is how you actually place the variables into the execution call
executionParam.powershellVariables.push(parameter);
}
}
},
// verify a valid script file was passed in. If good then use it.
processScriptFile: function (activityInput, executionParam) {
if (activityInput.Scriptname !== null) {
gs.info('---> [{1}-{2}] activityInput.Scriptname: {0}',
[activityInput.Scriptname,
new GlideDateTime().getNumericValue(),
this.location + '.processScriptFile']);
// the MID Script File field is a reference field and will only take a sys_id
// so retrieve the sys_id of the given Script.
var scriptFileRecords = new GlideRecord('ecc_agent_script_file');
if (scriptFileRecords.get('name', activityInput.Scriptname)) {
gs.info('---> [{1}-{2}] sys_id: {0}',
[scriptFileRecords.getValue('sys_id'),
new GlideDateTime().getNumericValue(),
this.location + '.processScriptFile']);
executionParam.midScriptFile = scriptFileRecords.getValue('sys_id');
} else {
gs.info('---> [{1}-{2}] bad Scriptname: {0} does not exist.',
[activityInput.Scriptname,
new GlideDateTime().getNumericValue(),
this.location + '.processScriptFile']);
}
} else {
gs.info('---> [{1}-{2}] bad scriptfile: {0}',
[activityInput.Scriptfile,
new GlideDateTime().getNumericValue(),
this.location + '.processScriptFile']);
}
},
type: 'ML_WorkflowActivityUtils'
};
4. Click on the Update button to save your work.
NOTES
The processJSONParameters function parses the JSON Parameters input variable into individual parameters. These will be placed into a parameter array that will then be fed to the MID Service PowerShell Script. BTW, this is a best practice when dealing with lots of input fields. Rather than trying to define input fields for every value I use this method. My general rule-of-thumb is whenever it gets to be more than four then I look really hard at using JSON for input parms.
The processScriptFile function handles the fact that the MID Script File field on the Execution Command form is a reference field. Of course this means that it requires a sys_id. That has to be retrieved from the table where MID Scripts live: ecc_agent_script_file. This has the added benefit of allowing us to make sure that the file actually exists. Notice that we handle possible missing info by doing the minimum: logging it. A missing or bad MID Script name will cause the activity to blow up and the error may be available in the Outputs section for handling. See the following article if it does not: Mini-Lab: Orchestration: Grabbing the Workflow Context Logs.
GlideSystem calls do work from within a custom activity. You can't get at workflow.scratchpad or workflow.inputs or current or current.varaibles from within an activity, period. You will need to push those in through an input variable(s). Same for variables inside of a custom activity: they are not available in the workflow unless you place them in output variables.
To view the available Custom Activity fields in the Script editor click on the ">" button to the upper-right of the Script and you will see the variables tree. This also will allow you to see what is available for other settings within the Execution step. Click on the executionParam branch of the tree ("+"). You will see all of the variable parameters of the Execution Command form. If you expand the tree further for the powershellVariables > psVariables you will see where to find the name, value, and type variables used in the parameter object in the script above.
Notice the location variable at the top of the script? This is a good practice. You use this in your error and info messages. This helps to alleviate the issue of "mystery messages" appearing in the System Log. This can be an especially nasty issue when dealing with Workflows because Activity scripts are generally unsearchable.
Lab 1.2: Custom PowerShell Script
-
Navigate to Orchestration > Workflow Editor, and open the Workflow Editor.
We will be using all three of these functions in our new Custom Activity. The two new "process" functions will be utilized in the pre-processing step.
- In the Workflow Editor navigate to the Custom tab, and click the plus ("+") button to create a new Activity.
- Choose Powershell from the pop-up menu.
- Fill out the form with the following:
a. Name: Run PowerShell Script
b. Short Description: Run a given PowerShell MID Script
c. Accessible from: All application scopes
d. Category: PowerShell
e. Description: Activity to run a custom PowerShell Script with JSON parameters
4. Click on the Continue button.
5. On the Inputs form Add four fields:
a. Name: Hostname, Type: String
b. Name: Scriptname, Type: String
c. Name: Parameters, Type: String
d. Name: Credentials, Type: String
6. Click on the Go to Pre-Process button.
7. Fill in the Pre-Processing form with the following:
a. Input Process Script:
var location = 'WFCA: Run PowerShell Script Activity';
var utils = new ML_WorkflowActivityUtils(location);
utils.processJSONParameters(activityInput, executionParam);
utils.processScriptFile(activityInput, executionParam);
NOTE
You can see that we are filling in the MID Script File field from the input variable, and the powershellVariables as well. The Pre-Processing script gives the developer the ability to manipulate the incoming variables and adjust them for the Execution Command form.
8. Click on the Continue button.
9. Fill in the Execution Command form with the following:
a. Target Host: drag the Hostname input to this field
b. Script Type: MID Server script file
c. MID Server script file: leave blank (this is handled by our Script Include)
d. PowerShell variables: leave blank (this is handled by our Script Include)
e. Credential tag: drag the Credential input to this field
10. Click the Continue button.
REMEMBER
The MID Server script file and PowerShell variables fields are filled in behind-the-scenes by the Pre-processing script.
You get the names of the actual fields from this form simply by right-clicking on the field label. The only one you can't do this with is the PowerShell variables field which will always be powershellVariables an array.
You can't just drop the string value of the name into the MID Server script file field. Because it is a reference field it ignores it. You have to use the Pre-processing script to do the actual assignment.
11. Add three Output fields (from this point forward the creation of the new activity will be the same as in my previous article).
a. Name: result, Type: String
b. Name: output, Type: String
c. Name: errorMessage, Type: String
12. Click on the Go To Post-Processing button.
13. Fill in the Post-Processing form with the following:
a. Output process script:
var location = 'WFCA: Run PowerShell Script Activity';
new ML_WorkflowActivityUtils(location).postProcessing(executionResult, activityOutput);
NOTE
The error object is null then we have a success. We will be using the result variable to store success/failure. All activityOutput variables are available from the Databus. The else will handle things like bad commands, no script named, and so on. The try/catch construct is a safety net in case something bad happens.
14. Click the Continue button.
15. Add two condition fields:
Name: Success
Condition: activityOutput.result == 'success'
Else: false
Order: 100
Name: Failure
Condition: leave blank
Else: true
Order: 200
16. Now click on the Save button to save your work.
Lab 1.3: Testing
To test we need a PowerShell script to run, and a workflow that uses our new Custom Activity. The PowerShell script will be a simple script that echoes back any parameters sent through the Custom Activity. The moment you save the script down ServiceNow transmits it to your MID Server.
1. In your instance navigate to MID Server > Script Files.
2. Click the New button.
3. Fill out the form with the following:
a. Name: echoback.ps1
b. Parent: PowerShell (if you have two choices for PowerShell, it will be the second one)
c. Active: checked
d. Description: Echo back all passed parameters
e. Directory: un-checked
f. Script:
Write-Host $parm1
Write-Host $parm2
Write-Host $parm3
Write-Host $parm4
4. Click the Submit button to save your work.
Now we need to build our test workflow!
5. From the Workflow Editor navigate to Custom > Global > PowerShell. You will now see your new Run PowerShell Script Activity present.
6. In the Workflow Editor navigate to the Workflows tab, and click the plus ("+") button to create a new workflow.
7. Fill in the New Workflow form with the following:
a. Name: Run PowerShell Script Workflow
b. Table: Global
c. Description: Testing the Run PowerShell Script Custom Activity
d. If condition matches: -- None –-
8. Click the Submit button to start building your workflow.
9. Drag your custom PowerShell Command Activity onto the new workflow and fill in the properties form with the following:
a. Name: Run PowerShell Script Test
b. Hostname: 127.0.0.1
c. Scriptname: echoback.ps1
d. Parameters:
{"parm1":"test1","parm2":"test2","parm3":"test3","parm4":"test4"}
10. Click the Submit button to save the activity to the workflow.
11. To hang onto our results for the Custom Activity we will need to add a Run Script Activity after the PowerShell Command; in the Success branch. The after-activity results from your Custom Activity are available for coding in the Data tab, and might have a different number.
NOTE
Don't forget! The Data object number will likely be different than mine. It is best to look it up in the Data tab. It can be different for every Custom Activity you add to the form. I have 3, but you might have 6. Just saying.
12. Navigate to Core tab > Utilities and drag out a Run Script activity onto the Success output line of your custom activity.
13. Fill in the properties form with the following:
a. Name: Handle Results
b. Script:
var location = 'WF:' + context.name + '.' + activity.name;
var psScriptTest = data.get(3);
try {
gs.info('---> [{1}:{2}] previous_activity.result: {0}',
[psScriptTest.result, new GlideDateTime().getNumericValue(), location]);
gs.info('---> [{1}:{2}] previous_activity.output: {0}',
[psScriptTest.output, new GlideDateTime().getNumericValue(), location]);
gs.info('---> [{1}:{2}] previous_activity.errorMessage: {0}',
[psScriptTest.errorMessage, new GlideDateTime().getNumericValue(), location]);
var results = {output : '', error : ''};
results.output = psScriptTest.output;
results.error = psScriptTest.errorMessage;
workflow.info('---> [{1}:{2}] {0}',
[results.output, new GlideDateTime().getNumericValue(), location]);
workflow.scratchpad.output = results;
}
catch(err) {
gs.error('---> [{1}:{2}]\n{0}',
[err, new GlideDateTime().getNumericValue(),location]);
}
14. Click the Submit button to save your Activity to the workflow.
15. Add another Run Script Activity into the Failure output of the Run PowerShell Script Test activity.
Best Practice: You should always handle potential errors!
16. Fill out the form with the following:
a. Name: Handle Failure
b. Script:
var location = 'WF:' + context.name + '.' + activity.name;
var psScriptTest = data.get(3);
try {
gs.info('---> [{1}:{2}] previous_activity.result: {0}',
[psScriptTest.result, new GlideDateTime().getNumericValue(), location]);
gs.info('---> [{1}:{2}] previous_activity.output: {0}',
[psScriptTest.output, new GlideDateTime().getNumericValue(), location]);
gs.error('---> [{1}:{2}] previous_activity.errorMessage: {0}',
[psScriptTest.errorMessage, new GlideDateTime().getNumericValue(), location]);
// package up the results for pushing onto the workflow (future use)
var results = {output : '', error : ''};
results.output = psScriptTest.output;
results.error = psScriptTest.errorMessage;
workflow.info('---> [{1}:{2}] {0}',
[results.output, new GlideDateTime().getNumericValue(),location]);
workflow.scratchpad.output = results; // pass the results out onto the data stream
}
catch(err) {
gs.error('---> [{1}:{2}]\n{0}',
[err, new GlideDateTime().getNumericValue(),location]);
}
17. Wire up the workflow to match the following diagram:
18. Run the workflow.
19. You should have a successful run that looks like this:
20. From your instance navigate to System Logs > System Log > All. The System Logs list view will be displayed.
21. Filter for Messages starting with "--->", and order by Message descending (because of the microsecond tags). You should see entries like the following:
And there you have it! You have created and/or enhanced your Custom Activity Script Include to include pre-processing capabilities. You have also created a Generic Custom PowerShell activity that accepts a PowerShell Script name and parameters and applies them to the script accordingly. You also have a variety of best practices now at your fingertips when creating Custom Activities.
Quite a haul!
Enjoy!
Steven Bell.
If you find this article helps you, don't forget to log in and mark it as "Helpful"!
Originally published on: 07-31-2018 11:51 AM
I updated the code, fixed broken links, and brought the article into alignment with my new formatting standard.
- 3,756 Views
- Mark as Read
- Mark as New
- Bookmark
- Permalink
- Report Inappropriate Content
After I create the new PS Activity, I do not see the "pre processing" or "pre processing" steps as shown in the 2nd screen shot. Is this article still current, or were those removed in versions after Kingston?
- Mark as Read
- Mark as New
- Bookmark
- Permalink
- Report Inappropriate Content
Craig,
This article is still valid. You are running version London or later. The pre-processing and post-processing steps are now hidden under Inputs and Outputs steps.
Tuan