Passing variables from PowerShell to UCS Director

Version 8
    Task NamePassing Variables from PowerShell to UCSD
    Description
    Prerequisites
    1. Tested on 5.3
    CategoryWorkflow
    ComponentsvSphere 5.x
    User Inputs

    Instructions for Regular Workflow Use:

    1. Download the attached .ZIP file below to your computer. *Remember the location of the saved file on your computer.
    2. Unzip the file on your computer. Should end up with a .WFD file.
    3. Log in to UCS Director as a user that has "system-admin" privileges.
    4. Navigate to "Policies-->Orchestration" and click on "Import".
    5. Click "Browse" and navigate to the location on your computer where the .WFD file resides. Choose the .WFD file and click "Open".
    6. Click "Upload" and then "OK" once the file upload is completed. Then click "Next".
    7. Click the "Select" button next to "Import Workflows". Click the "Check All" button to check all checkboxes and then the "Select" button.
    8. Click "Submit".
    9. A new folder should appear in "Policies-->Orchestration" that contains the imported workflow. You will now need to update the included tasks with information about the specific environment.

     

     

     

     

     

    UCS Director supports running PowerShell scripts straight from a workflow. Although this adds powerful new possibilities, it can be tricky to pass objects back from PowerShell to UCS Director.

     

    For example, let's take this trivial PowerShell script:

     

    write "192.168.100.1";

    return "Hello World!";


    Running that from UCS Director will give me the following XML output:


    <?xml version='1.0'?>

    <Objects>

    <Object Type='System.String'>192.168.100.1</Object>

    <Object Type='System.String'>Hello World!</Object>

    </Objects>


    As you can see, anything that is written to the terminal or returned will be passed back to UCS Director. Although some basic Regular Expression might help out here, I wanted to do something a bit smarter.

     

    Setting variables inside PowerShell

    The first step was to create a method for containing my variables. For example, if I want to pass an IP Address, a machine name and some metadata back, how can I guarantee the order of the XML? What if it changes in the future?

     

    The way I've dealt with this problem is to store everything in an associative array. This way I can pass key/value pairs back to UCS Director in a standard, easy to use way. For example:

     

    $ip_address = "192.168.100.1";

    $machine_name = "matday-test-01";

    # Create an array to store all the UCSD return values in:

    $ucsd = @{}

    $ucsd.ip_address = $ip_address;

    $ucsd.machine_name = $machine_name;

    $ucsd.metadata = "Authored by Matt Day";

    # Return the array back to UCSD:

    return $ucsd;


    The neat thing here is that I'm able to pass anything I like back to UCS Director in a safe fashion. Taking a look at the XML this will return, you get this:


    <?xml version='1.0'?>

    <Objects>

    <Object Type='System.Collections.Hashtable'>

    <Property Name='Key' Type='System.String'>metadata</Property>

    <Property Name='Value' Type='System.String'>Authored by Matt Day</Property>

    <Property Name='Key' Type='System.String'>ip_address</Property>

    <Property Name='Value' Type='System.String'>192.168.100.1</Property>

    <Property Name='Key' Type='System.String'>machine_name</Property>

    <Property Name='Value' Type='System.String'>matday-test-01</Property>

    </Object>

    </Objects>


    Parsing PowerShell variables

    Once this PowerShell script is complete, we need to parse the XML data. To do this, we can create a custom workflow task to capture this output.


    For this example, I'm going to create a custom task that captures IP Address, Machine Name and Metadata, although yours can include anything you like.


    The first step is to import the UCS Director and Java libraries.


    // Import UCS Director scripting library and Java standard library

    importPackage(com.cloupia.lib.util);

    importPackage(java.util);

     

    Then the above XML needs to be included as a variable.

     

    var xml = "<?xml version='1.0'?><Objects><Object Type='System.Collections.Hashtable'><Property Name='Key' Type='System.String'>metadata</Property><Property Name='Value' Type='System.String'>Authored by Matt Day</Property><Property Name='Key' Type='System.String'>ip_address</Property><Property Name='Value' Type='System.String'>192.168.100.1</Property><Property Name='Key' Type='System.String'>machine_name</Property><Property Name='Value' Type='System.String'>matday-test-01</Property></Object></Objects>";

     

    You might want to map this to an input for your custom task.

     

    I now need to use the built-in XMLUtil class to parse the above XML tree. It works hierarchically, so each layer of XML needs to be done sequentially.

     

    The first step is to grab the data inside the <Objects>...</Objects> layer:

     

    var objects_xml = XMLUtil.getValue("Objects", xml);

     

    This returns a list of all the matching sections. In this case, it's only going to return 1 item containing the <Object>...</Object> tags. Again, we need to parse this:

     

    // Get the first match (0) from the <Objects>...</Objects> section:

    object_list = XMLUtil.getTag("Object",objects_xml.get(0))


    Now we need to build a list of all the <Property>...</Property> tags from the single <Object>...</Object> tree:

     

    // Get the first match (0) from the <Object>...</Object> section:    

    property_list = XMLUtil.getTag("Property",object_list.get(0))

     

    As you saw above, the key/value pairs are on alternating lines. The easiest way to obtain this information is to iterate through the list, treating even numbers as keys and odd numbers as values.

     

    // Store results in a HashMap (a Java associative array)

    var variable_map = new HashMap();


    // Store previous keys in buffer:

    var key_buffer = "";


    // Loop through all values taking even as keys and odd as values:

    for (i = 0; i < property_list.size(); i++) {

    // Remove XML tags with a bit of Regular Expression

    property_list.set(i, property_list.get(i).replaceAll("<.*?>",""));

    // Keys (even numbers)

    if ((i % 2) == 0) {

    key_buffer = property_list.get(i);

    }

    // Values (odd numbers)

    else {

    variable_map.put(key_buffer, property_list.get(i));

    }

    }

     

    Now using the example above where the IP Address, Machine Name and Metadata are all being passed back, we can pull this out via a UCS Director script:

     

    output.ip_address = variable_map.get("ip_address");

    output.machine_name = variable_map.get("machine_name");

    output.metadata = variable_map.get("metadata");


    This could be extended to anything you like and further checked, mapped etc in to UCS Director's various types.

    Putting it all together

    I built a small Custom Workflow Task to show the concept.

     

    Inputs:

    Screen Shot 2015-05-12 at 21.41.03.png

    Outputs:

    Screen Shot 2015-05-12 at 21.42.21.png

    Script

    importPackage(com.cloupia.lib.util);

    importPackage(java.util);

     

    var xml = input.xml

     

    // Try and parse the <Objects>...</Objects> section

    var objects_xml = XMLUtil.getValue("Objects", xml);

    // Parse the objects list now (should also be a single section):

    object_list = XMLUtil.getTag("Object",objects_xml.get(0))

    // Parse the object_list to get properties:

    property_list = XMLUtil.getTag("Property",object_list.get(0))

     

    // Store results in a HashMap (a Java associative array)

    var variable_map = new HashMap();

    // Store previous keys in buffer:

    var key_buffer = "";


    // Loop through all values taking even as keys and odd as values:

    for (i = 0; i < property_list.size(); i++) {

         // Remove XML tags

         property_list.set(i, property_list.get(i).replaceAll("<.*?>",""));

         // Keys

         if ((i % 2) == 0) {

              key_buffer = property_list.get(i);

         }

         // Values

         else {

              variable_map.put(key_buffer, property_list.get(i));

         }

    }

     

    // Match desired output to HashMap fields:

    output.ip_address = variable_map.get("ip_address");

    output.machine_name = variable_map.get("machine_name");

    output.metadata = variable_map.get("metadata");


    Sample Output

    To prove the concept, here's a sample workflow:

    Workflow Summary

    Screen Shot 2015-05-12 at 21.45.26.png

    PowerShell Script

    Screen Shot 2015-05-12 at 21.47.00.png

    Custom Workflow Inputs

    Screen Shot 2015-05-12 at 21.47.48.png

    Results

    Screen Shot 2015-05-12 at 21.48.51.png

    Log File

    Screen Shot 2015-05-12 at 21.49.26.png


    Extending in the Future

    This could be extended to support any output from a PowerShell script. By adding a new input to the above workflow, "variable", you could specify exactly what you wanted output and run it multiple times against the same PowerShell output, allowing a general purpose variable collector.


    For example, we add a new input variable_name:

    Screen Shot 2015-05-13 at 23.11.50.png

    And reduce the outputs to a single variable result:

    Screen Shot 2015-05-13 at 23.13.01.png

     

    The following script could then be used to match any output:

    importPackage(com.cloupia.lib.util);

    importPackage(java.util);

     

    var xml = input.xml

     

    // Try and parse the <Objects>...</Objects> section

    var objects_xml = XMLUtil.getValue("Objects", xml);

     

    // Parse the objects list now (should also be a single section):

    object_list = XMLUtil.getTag("Object",objects_xml.get(0));

     

    // Parse the object_list to get properties:

    property_list = XMLUtil.getTag("Property",object_list.get(0));

     

    // PowerShell returns arrays weirdly to UCSD, alternating rows of keys/values

    // Like this:

    //   <Property Name="Key" Type="System.String">ip</Property>

    //   <Property Name="Value" Type="System.String">192.168.100.1</Property>

    //   <Property Name="Key" Type="System.String">server_name</Property>

    //   <Property Name="Value" Type="System.String">New Server</Property>

    //

     

    // Store output in a HashMap:

    var variable_map = new HashMap();

     

    // Store previous keys in buffer:

    var key_buffer = "";

     

    // Loop through all values taking even as keys and odd as values:

    for (i = 0; i < property_list.size(); i++) {

            // Remove XML tags (can't seem to coax the XML library to do this for me!)

            property_list.set(i, property_list.get(i).replaceAll("<.*?>",""));

            // Keys

            if ((i % 2) == 0) {

                    key_buffer = property_list.get(i);

            }

            // Values

            else {

                    variable_map.put(key_buffer, property_list.get(i));

            }

    }

     

    // Match desired output to HashMap fields:

    output.variable = variable_map.get(input.variable_name);


    Running the Workflow

    In this case the user input mapping is the same (mapped to a PowerShell command output):

    Screen Shot 2015-05-13 at 23.14.03.png

    However this time we map the variable name to the desired outcome (in this case an IP address):

    Screen Shot 2015-05-13 at 23.14.09.png

    When run the variable that we desire is collected:

    Screen Shot 2015-05-13 at 23.15.02.png

    You can see in the logs how it's matched:

    Screen Shot 2015-05-13 at 23.15.09.png

     

    Here are the examples from the attached workflow:

     

    Workflow:

     

    Screen Shot 2015-05-13 at 5.31.15 PM.png

     

    Custom Workflow Tasks:

     

    Screen Shot 2015-05-13 at 5.32.25 PM.png

     

    The workflow:

     

    Screen Shot 2015-05-13 at 5.31.31 PM.png