Default language.

Important This documentation space contains information about the SaaS version of BMC Helix Discovery. If you are using the on-premises version of BMC Helix Discovery, see BMC Helix Discovery 25.2 (On-Premises).

REST API example code


This page contains examples of code calling the BMC Helix Discovery REST API. As well as helping with the specific scenarios in the examples, the code can be read to familiarize yourself with some general principles for working with the API.

Each example contains full, working code including error handling. Most of the examples embed the appliance name and permanent token near the top of the listing for convenience - you will need to change these before running the code. The final example shows how to request an expiring token for a BMC Helix Discovery user and then use it immediately.

A variety of endpoints are used for the examples, and full details of how to use each endpoint can be found in the endpoints list

Examples are provided in the following languages:

Node (JavaScript)

The examples were tested using version 6 of Node.js. The Request library is used to simplify the HTTP calls, so you'll need to ensure you have that installed.

If your BMC Helix Discovery appliance uses a self-signed certificate, it must be added on the client machine as a trusted root certificate. This might not be convenient for API testing, so our examples try to mitigate this by disabling SSL validation:

agentOptions: {
                 rejectUnauthorized: false
              }

This approach is NOT recommended in production scripts.

Powershell

The examples were tested with version 5 of Powershell.


The examples

Search the data

Run a search query and navigate the paginated results.

View example

Based on a real issue, this example executes a search query to find all discovered Windows Hosts that do not have one of a certain list of patches installed.

An initial call to /data/search is made, which returns the first page of results along with some metadata. In the metadata is a "next" link, which is used to request the next page of results. This pattern is repeated until the last page of results, which does not contain a "next" link.

As each results page is consumed, the contents are converted to csv and written to stdout.


const BASE_PATH = 'https://appliance/api/v1.1';  // UPDATE ME
const TOKEN = 'your_token';  // UPDATE ME

const QUERY = `SEARCH Host AS all_hosts
               WHERE os_class = "Windows"
               SEARCH Patch
               WHERE name in ['KB4012598', 'KB4012212', 'KB4012215', 'KB4012213', 'KB4012216', 'KB4012214',
                              'KB4012217', 'KB4012216', 'KB4012606', 'KB4013198', 'KB4013429', 'KB4012598', 'KB4011981']
               TRAVERSE InstalledSoftware:HostedSoftware:Host:Host AS patched_hosts
               SEARCH IN (all_hosts - patched_hosts)
               SHOW name, os, os_version, join(#InferredElement:Inference:Associate:DiscoveryAccess.endpoint, ',') as "IP Address"`
;


// Set up some defaults for every call to the API
var request = require('request').defaults({auth: {'Bearer': TOKEN},
                                           json: true,
                                           agentOptions: {
                                               rejectUnauthorized: false
                                           }});


// Helper functions
function fail(message) {
    console.error(`ERROR: ${message}`);
    process.exit(1);
}

function get(options, callback) {
    request.get(options, function (error, response, body) {
       if (error) {
            fail(error);
        }

       if (response.statusCode === 200) {
            callback(body);
        }
       else {
           let msg = `Request to ${options.uri} failed with status ${response.statusCode}`;
           if (body && body.message) {
                msg = `${body.message} (${msg})`;
            }
           else if (response.statusMessage) {
                msg = `${response.statusMessage} (${msg})`;
            }
            fail(msg);
        }
    });
}

function print_csv(results_page) {
   // https://stackoverflow.com/a/29976603
   let csv_lines = results_page.map(function(results_row) {

      return JSON.stringify(results_row);
    })
    .join('\n')
    .replace(/(^\[)|(\]$)/mg, '');

    console.log(csv_lines);
}

// Application code

function success_callback(write_headings, body) {
   // We searched for one node kind so expecting one result set back
   const results_page = body[0];

   // Output headings just once
   if (results_page.headings.length && write_headings) {
        console.log('"' + results_page.headings.join('","') + '"');
    }

   // Write out this page
   print_csv(results_page.results);

   // Are there more pages?
   if (results_page.next) {
        get({uri: results_page.next}, success_callback.bind(this, false));
    }
}

// Run the query, getting back first page of results.
// Our callback will keep asking for the next page.
get( {uri: `${BASE_PATH}/data/search`,
      qs: {query: QUERY}}, success_callback.bind(this, true));

$BASE_PATH = "https://appliance/api/v1.1" # UPDATE ME
$TOKEN = "your token" # UPDATE ME

$QUERY = @"
        SEARCH Host AS all_hosts
        WHERE os_class = "Windows"
        SEARCH Patch
        WHERE name in ['KB4012598', 'KB4012212', 'KB4012215', 'KB4012213',
                       'KB4012216', 'KB4012214', 'KB4012217', 'KB4012216',
                       'KB4012606', 'KB4013198', 'KB4013429', 'KB4012598',
                       'KB4011981']
        TRAVERSE InstalledSoftware:HostedSoftware:Host:Host AS patched_hosts
        SEARCH IN (all_hosts - patched_hosts)
        SHOW name, os, os_version,
             join(#InferredElement:Inference:Associate:DiscoveryAccess.endpoint,
                  ',') as "IP Address"
"@



# Tell powershell not to default to insecure protocols!
[System.Net.ServicePointManager]::SecurityProtocol = [System.Net.SecurityProtocolType]::Tls12

################################################################################
################################################################################
###                                                                          ###
###                                WARNING                                   ###
### The following line disables server certificate validation to avoid       ###
### installing CA certificate for the server. It should only be present for  ###
### testing. You should never disable certificate validation in production.  ###
###                                                                          ###
################################################################################
################################################################################
[System.Net.ServicePointManager]::ServerCertificateValidationCallback = { $true }

function restful_get {
   Param([string]$url,
         [string]$token,
         [hashtable]$params)

   $headers = @{"Accept" = "application/json";
                "Authorization" = "Bearer $token"}

   try {
       return Invoke-RestMethod -Uri $url -Headers $headers -Body $params `
                                 -ErrorVariable rest_error
    } catch {
       $err = $_
       try {
           $temp = $rest_error.Message | ConvertFrom-Json
           if ($temp.message) {
               $err = $temp.message
            }
        } catch {}
       Write-Error $err
        exit
    }
}

function search_data {
   Param([string] $base_path,
         [string] $token,
         [string] $query)

   # We are going to output search results as CSV, and objects are easier
   # to convert to CSV than lists, so we are using "format=object" to get
   # a list of objects rather than a list of lists
   $result = restful_get -url "$base_path/data/search" -token $token `
                          -params @{"query" = $query; "format" = "object"}

   # We searched for one node kind so expecting one result set back
   $all_results = $result[0].results

   while ($result[0].next) {
       $result = restful_get -url $result[0].next -token $token
       $all_results += $result[0].results
    }

   return $all_results
}

$result = search_data -base_path $BASE_PATH -token $TOKEN `
                      -query $QUERY

echo $result | Select-Object "name", "os", "os_version", "IP Address" | ConvertTo-Csv -NoTypeInformation

Create an IP address scan

Create a discovery run, wait until the run is complete, and then retrieve results.

View example

An initial POST call to /discovery/runs is made, which returns the uuid of the newly created snapshot discovery run. We then poll for the status of the new run at /discovery/runs/{uuid}, until the 'finished' field of the returned status is true.

At this point the run has completed and we can then call /discovery/runs/{uuid}/inferred to retrieve the counts of devices inferred in this run. These are written to stdout.


const BASE_PATH = 'https://appliance/api/v1.1';  // UPDATE ME
const TOKEN = 'your_token';  // UPDATE ME

const IP_SCAN = {
    scan_kind: 'IP',
    ranges: ['192.168.0.*'], // UPDATE ME
   label: 'Test network'
};


// Set up some defaults for every call to the API
var request = require('request').defaults({auth: {'Bearer': TOKEN},
                                           json: true,
                                           agentOptions: {
                                               rejectUnauthorized: false
                                           }});


// Helper functions
function fail(message) {
    console.error(`ERROR: ${message}`);
    process.exit(1);
}

function rest(options, callback) {
    request(options, function (error, response, body) {
       if (error) {
            fail(error);
        }

       if (response.statusCode === 200) {
            callback(body);
        }
       else {
           let msg = `Request to ${options.uri} failed with status ${response.statusCode}`;
           if (body && body.message) {
                msg = `${body.message} (${msg})`;
            }
           else if (response.statusMessage) {
                msg = `${response.statusMessage} (${msg})`;
            }
            fail(msg);
        }
    });
}

function get(options, callback) {
    options.method = 'GET';
    rest(options, callback);
}

function post(options, callback) {
    options.method = 'POST';
    rest(options, callback);
}

// Application code

function get_inferred(run_id) {
    get({uri: `${BASE_PATH}/discovery/runs/${run_id}/inferred`}, function (body) {
        console.log('Inferred devices:');
       for (var device in body) {
            console.log(device, body[device].count);
        }
    });
}

function check_status(run_id) {
   // Wait for 3 seconds
   setTimeout(function () {
        console.log('Checking discovery run status...')

       // Retrieve discovery run status
       get({uri: `${BASE_PATH}/discovery/runs/${run_id}`},
           function (body) {
               if (body.finished) {
                   // Retrieve inferred devices if finished
                   get_inferred(run_id);
                }
               else {
                   // Keep checking status otherwise
                   setImmediate(check_status, run_id);
                }
            });
    }, 3000);
}

// Start the discovery, receive run id.
// check_status will be called until the run is finished.
post({uri: `${BASE_PATH}/discovery/runs`, body: IP_SCAN},
   function (body) {
        check_status(body.uuid);
    });

$BASE_PATH = "https://appliance/api/v1.1" # UPDATE ME
$TOKEN = "your token" # UPDATE ME

$IP_SCAN = @{"scan_kind" = "IP";
            "ranges"    = @("192.168.0.*"); # UPDATE ME
            "label"     = "Test network"}


# Tell powershell not to default to insecure protocols!
[System.Net.ServicePointManager]::SecurityProtocol = [System.Net.SecurityProtocolType]::Tls12

################################################################################
################################################################################
###                                                                          ###
###                                WARNING                                   ###
### The following line disables server certificate validation to avoid       ###
### installing CA certificate for the server. It should only be present for  ###
### testing. You should never disable certificate validation in production.  ###
###                                                                          ###
################################################################################
################################################################################
[System.Net.ServicePointManager]::ServerCertificateValidationCallback = { $true }


function restful_get {
   Param([string]$url,
         [string]$token,
         [hashtable]$params)

   $headers = @{"Accept" = "application/json";
                "Authorization" = "Bearer $token"}

   try {
       return Invoke-RestMethod -Uri $url -Headers $headers -Body $params `
                                 -ErrorVariable rest_error
    } catch {
       $err = $_
       try {
           $temp = $rest_error.Message | ConvertFrom-Json
           if ($temp.message) {
               $err = $temp.message
            }
        } catch {}
       Write-Error $err
        exit
    }
}

function restful_post {
   Param([string]$url,
         [string]$token,
         [hashtable]$params)

   $headers = @{"Accept" = "application/json";
                "Authorization" = "Bearer $token"}

   $json = $params | ConvertTo-Json

   try {
       return Invoke-RestMethod -Method Post -Uri $url -Headers $headers `
                                 -Body $json -ErrorVariable rest_error
    } catch {
       $err = $_
       try {
           $temp = $rest_error.Message | ConvertFrom-Json
           if ($temp.message) {
               $err = $temp.message
            }
        } catch {}
       Write-Error $err
        exit
    }
}

function scan {
   Param([string]$base_path,
         [string]$token,
         [hashtable]$params)

   # Start the run
   $new_run = restful_post -url "$base_path/discovery/runs" `
                            -token $token -params $params
   $run_id = $new_run.uuid

   # Poll run status every 3 seconds until the run is finished
   do {
       Start-Sleep -s 3
       Write-Host "Checking discovery run status..."
       $result = restful_get -url "$base_path/discovery/runs/$run_id" `
                              -token $token
    } while (!$result.finished)

   # See what the scan found
   return restful_get -url "$base_path/discovery/runs/$run_id/inferred" `
                       -token $token
}

$result = scan -base_path $BASE_PATH -token $TOKEN -params $IP_SCAN

echo "Inferred devices:"
foreach($device in $result.PsObject.Properties) {
   echo "    $($device.Name) $($device.Value.count)"
}

Create a cloud credential

Use cloud metadata to understand how to create a cloud credential.

View example

This example creates a new credential to allow BMC Helix Discovery to scan Amazon Web Services. This is achieved with a single call to the /vault/credentials endpoint. However, the more interesting part is finding which parameters are needed in that call.

The documentation for /vault/credentials lists a large number of fields, explaining that the set of required fields in a request depends on the type(s) of the credential being created. However, the documentation also says that credentials used to access cloud providers may require additional fields not listed. This is because TKU knowledge updates may add support for discovering new cloud providers, or extend support for existing providers. The /discovery/cloud_metadata endpoint is provided to be used as a reference when developing applications that manage cloud credentials or start cloud runs. This endpoint returns a list of cloud providers, along with parameters needed for scanning each provider and managing credentials for each provider. The endpoint is intended to be used interactively while writing your client application, not embedded in your application itself.

In this case we are interested in creating a credential for Amazon Web Services, so we call /discovery/cloud_metadata and examine the 'cred_params' field for the 'aws' provider. The actual results you see may vary by BMC Helix Discovery and TKU version, but our version shows ten parameters:

{
   "cred_params": [
      {
       "allowed_values": [
         "..."
        ],
       "description": "Amazon Web Services Access Key ID. This is required unless a CyberArk query is used.",
       "is_list": false,
       "mandatory": false,
       "name": "aws.access_key_id",
       "type": "str"
      },
      {
       "allowed_values": [],
       "description": "Amazon Web Services Access Key Secret. This is required unless a CyberArk query is used.",
       "is_list": false,
       "mandatory": false,
       "name": "aws.access_key_secret",
       "type": "str"
      },
      {
       "allowed_values": [],
       "description": "CyberArk query to retrieve AWS Access Key",
       "is_list": false,
       "mandatory": false,
       "name": "aws.access_key.cyberark",
       "type": "str"
      },


...

Although none of the parameters are marked as mandatory, looking at the parameter descriptions we see that aws.access_key_id and aws.access_key_secret are actually required unless CyberArk is being used. Assuming we don't want to use CyberArk in this example, we must provide these two parameters to successfully create a credential for Amazon Web Services. This is in addition to the standard types and label fields which are used for all credential types.

Therefore, our application should issue a POST request to /vault/credentials with the following JSON body:

{
   "types": ["aws"],
   "label": "Test AWS credential",
   "aws.access_key_id": "your key id",
   "aws.access_key_secret": "your key secret"
}

The endpoint will reply with the ID of the newly created credential, which we write to stdout.


const BASE_PATH = 'https://appliance/api/v1.1';  // UPDATE ME
const TOKEN = 'your_token';  // UPDATE ME

const AWS_CREDENTIAL = {
    types: ['aws'],
    label: 'Test AWS credential', // UPDATE ME
   'aws.access_key_id': 'your key id',  // UPDATE ME
   'aws.access_key_secret': 'your key secret'  // UPDATE ME
};


// Set up some defaults for every call to the API
var request = require('request').defaults({auth: {'Bearer': TOKEN},
                                           json: true,
                                           agentOptions: {
                                               rejectUnauthorized: false
                                           }});


// Helper functions
function fail(message) {
    console.error(`ERROR: ${message}`);
    process.exit(1);
}

function post(options, callback) {
    request.post(options, function (error, response, body) {
       if (error) {
            fail(error);
        }

       if (response.statusCode === 200) {
            callback(body);
        }
       else {
           let msg = `Request to ${options.uri} failed with status ${response.statusCode}`;
           if (body && body.message) {
                msg = `${body.message} (${msg})`;
            }
           else if (response.statusMessage) {
                msg = `${response.statusMessage} (${msg})`;
            }
            fail(msg);
        }
    });
}

// Application code

// Create the credential, print out credential id.
post({uri: `${BASE_PATH}/vault/credentials`, body: AWS_CREDENTIAL},
   function (body) {
        console.log("Created credential id:", body.uuid);
    });

$BASE_PATH = "https://appliance/api/v1.1" # UPDATE ME
$TOKEN = "your token" # UPDATE ME

$AWS_CREDENTIAL = @{"types" = @("aws");
                   "label" = "Test AWS credential"; # UPDATE ME
                   "aws.access_key_id" = "your key id"; # UPDATE ME
                   "aws.access_key_secret" = "your key secret"} # UPDATE ME


# Tell powershell not to default to insecure protocols!
[System.Net.ServicePointManager]::SecurityProtocol = [System.Net.SecurityProtocolType]::Tls12

################################################################################
################################################################################
###                                                                          ###
###                                WARNING                                   ###
### The following line disables server certificate validation to avoid       ###
### installing CA certificate for the server. It should only be present for  ###
### testing. You should never disable certificate validation in production.  ###
###                                                                          ###
################################################################################
################################################################################
[System.Net.ServicePointManager]::ServerCertificateValidationCallback = { $true }

function restful_post {
   Param([string]$url,
         [string]$token,
         [hashtable]$params)

   $headers = @{"Accept" = "application/json";
                "Authorization" = "Bearer $token"}

   $json = $params | ConvertTo-Json

   try {
       return Invoke-RestMethod -Method Post -Uri $url -Headers $headers `
                                 -Body $json -ErrorVariable rest_error
    } catch {
       $err = $_
       try {
           $temp = $rest_error.Message | ConvertFrom-Json
           if ($temp.message) {
               $err = $temp.message
            }
        } catch {}
       Write-Error $err
        exit
    }
}

$result = restful_post -url "$BASE_PATH/vault/credentials" `
                       -token $TOKEN -params $AWS_CREDENTIAL

echo "Created credential id: $($result.uuid)"

Create a cloud scan

Use cloud metadata to understand how to discover a cloud provider.

View example

This example creates a snapshot discovery run against Amazon Web Services and prints out counts of devices inferred from this run. The code is similar to the IP address scan example. A POST call to /discovery/runs is made, which returns the uuid of the newly created snapshot discovery run. We now poll for the status of the new run at /discovery/runs/{uuid}, until the 'finished' field of the returned status is true. At this point the run has completed and we can then call /discovery/runs/{uuid}/inferred to retrieve the counts of devices inferred in this run. These are written to stdout.

However, the interesting part is deciding which parameters to pass in the initial /discovery/runs call. Looking at the endpoint's documentation, as we are scanning a cloud provider the scan_kind field must be set to "Cloud". The documentation says the scan_params field now also needs to be supplied. The "provider" field must first be set to "aws", but in a similar way to the cloud credential example (see that for more details), we now use the /discovery/cloud_metadata endpoint as a reference to see which other fields are needed. Examining the "scan_params" field returned from the metadata endpoint we see (your output may vary):

{    
   "scan_params": [
      {
       "allowed_values": {
         "fddaa835070ad676013e89485e421dd3": "Test AWS credential"
        },
       "description": "Credential for the scan",
       "is_list": false,
       "mandatory": true,
       "name": "credential",
       "type": "str"
      },
      {
       "allowed_values": {
         "ap-northeast-1": "Asia Pacific (Tokyo)",
         "ap-northeast-2": "Asia Pacific (Seoul)",
         "ap-south-1": "Asia Pacific (Mumbai)",
         "ap-southeast-1": "Asia Pacific (Singapore)",
         "ap-southeast-2": "Asia Pacific (Sydney)",
         "ca-central-1": "Canada (Central)",
         "eu-central-1": "EU (Frankfurt)",
         "eu-west-1": "EU (Ireland)",
         "eu-west-2": "EU (London)",
         "sa-east-1": "South America (São Paulo)",
         "us-east-1": "US East (N. Virginia)",
         "us-east-2": "US East (Ohio)",
         "us-west-1": "US West (N. California)",
         "us-west-2": "US West (Oregon)"
        },
       "description": "List of regions to scan",
       "is_list": true,
       "mandatory": true,
       "name": "region",
       "type": "str"
      }
    ]
   
    ...
 

We see that there are two additional parameters needed to control the AWS scan, both of which are mandatory. The first field is "credential", which is the AWS cloud credential that BMC Helix Discovery should use during the scan when accessing the AWS APIs. The "allowed_values" lists all suitable credentials in the system. In this case we see the single credential that we created in the earlier example. The second field is "region", which tells BMC Helix Discovery which region(s) to scan. As this field has an "is_list" value set to true, we must supply one or more values in a list, picking from the allowed values. In both cases, the field value used should be the unique left hand side of the allowed values, not the descriptive string on the right.

Therefore, our application should issue a POST request to /discovery/runs with the following JSON body:

{
   "scan_kind": "Cloud",
   "scan_params": {
       "provider": "aws",
       "credential": "your aws credential id",
       "region": ["your aws region, e.g us-west-1"]
    },
   "label": "AWS scan"
}


const BASE_PATH = 'https://appliance/api/v1.1';  // UPDATE ME
const TOKEN = 'your_token';  // UPDATE ME

const AWS_SCAN = {
    scan_kind: 'Cloud',
    scan_params: {
        provider: 'aws',
        credential: 'your aws credential id',  // UPDATE ME
       region: ['your aws region, e.g us-west-1']  // UPDATE ME
   },
    label: 'AWS scan'
};

// Set up some defaults for every call to the API
var request = require('request').defaults({auth: {'Bearer': TOKEN},
                                           json: true,
                                           agentOptions: {
                                               rejectUnauthorized: false
                                           }});


// Helper functions
function fail(message) {
    console.error(`ERROR: ${message}`);
    process.exit(1);
}

function rest(options, callback) {
    request(options, function (error, response, body) {
       if (error) {
            fail(error);
        }

       if (response.statusCode === 200) {
            callback(body);
        }
       else {
           let msg = `Request to ${options.uri} failed with status ${response.statusCode}`;
           if (body && body.message) {
                msg = `${body.message} (${msg})`;
            }
           else if (response.statusMessage) {
                msg = `${response.statusMessage} (${msg})`;
            }
            fail(msg);
        }
    });
}

function get(options, callback) {
    options.method = 'GET';
    rest(options, callback);
}

function post(options, callback) {
    options.method = 'POST';
    rest(options, callback);
}

// Application code

function get_inferred(run_id) {
    get({uri: `${BASE_PATH}/discovery/runs/${run_id}/inferred`}, function (body) {
        console.log('Inferred devices:');
       for (var device in body) {
            console.log(device, body[device].count);
        }
    });
}

function check_status(run_id) {
   // Wait for 3 seconds
   setTimeout(function () {
        console.log('Checking discovery run status...')

       // Retrieve discovery run status
       get({uri: `${BASE_PATH}/discovery/runs/${run_id}`},
           function (body) {
               if (body.finished) {
                   // Retrieve inferred devices if finished
                   get_inferred(run_id);
                }
               else {
                   // Keep checking status otherwise
                   setImmediate(check_status, run_id);
                }
            });
    }, 3000);
}

// Start the discovery, receive run id.
// check_status will be called until the run is finished.
post({uri: `${BASE_PATH}/discovery/runs`, body: AWS_SCAN},
   function (body) {
        check_status(body.uuid);
    });

$BASE_PATH = "https://appliance/api/v1.1" # UPDATE ME
$TOKEN = "your token" # UPDATE ME

$AWS_SCAN = @{"scan_kind" = "Cloud";
             "scan_params" = @{"provider" = "aws";
                               "credential" = "your aws credential id"; # UPDATE ME
                               "region" = @("your aws region, e.g us-west-1") # UPDATE ME
                               };
             "label" = "AWS scan"}


# Tell powershell not to default to insecure protocols!
[System.Net.ServicePointManager]::SecurityProtocol = [System.Net.SecurityProtocolType]::Tls12

################################################################################
################################################################################
###                                                                          ###
###                                WARNING                                   ###
### The following line disables server certificate validation to avoid       ###
### installing CA certificate for the server. It should only be present for  ###
### testing. You should never disable certificate validation in production.  ###
###                                                                          ###
################################################################################
################################################################################
[System.Net.ServicePointManager]::ServerCertificateValidationCallback = { $true }


function restful_get {
   Param([string]$url,
         [string]$token,
         [hashtable]$params)

   $headers = @{"Accept" = "application/json";
                "Authorization" = "Bearer $token"}

   try {
       return Invoke-RestMethod -Uri $url -Headers $headers -Body $params `
                                 -ErrorVariable rest_error
    } catch {
       $err = $_
       try {
           $temp = $rest_error.Message | ConvertFrom-Json
           if ($temp.message) {
               $err = $temp.message
            }
        } catch {}
       Write-Error $err
        exit
    }
}

function restful_post {
   Param([string]$url,
         [string]$token,
         [hashtable]$params)

   $headers = @{"Accept" = "application/json";
                "Authorization" = "Bearer $token"}

   $json = $params | ConvertTo-Json

   try {
       return Invoke-RestMethod -Method Post -Uri $url -Headers $headers `
                                 -Body $json -ErrorVariable rest_error
    } catch {
       $err = $_
       try {
           $temp = $rest_error.Message | ConvertFrom-Json
           if ($temp.message) {
               $err = $temp.message
            }
        } catch {}
       Write-Error $err
        exit
    }
}

function scan {
   Param([string]$base_path,
         [string]$token,
         [hashtable]$params)

   # Start the run
   $new_run = restful_post -url "$base_path/discovery/runs" `
                            -token $token -params $params
   $run_id = $new_run.uuid

   # Poll run status every 3 seconds until the run is finished
   do {
       Start-Sleep -s 3
       Write-Host "Checking discovery run status..."
       $result = restful_get -url "$base_path/discovery/runs/$run_id" `
                              -token $token
    } while (!$result.finished)

   # See what the scan found
   return restful_get -url "$base_path/discovery/runs/$run_id/inferred" `
                       -token $token
}

$result = scan -base_path $BASE_PATH -token $TOKEN -params $AWS_SCAN
echo "Inferred devices:"
foreach($device in $result.PsObject.Properties) {
   echo "    $($device.Name) $($device.Value.count)"
}

Generate and use an expiring token

Use the username and password of a BMC Helix Discovery user to obtain and use an expiring API token.

View example

Previous examples have embedded a permanent API token in their scripts. In this example we pass the username and password of a valid BMC Helix Discovery user to the REST API and receive back an expiring token. From this point on we can use the token in subsequent API requests, in the same way as in other examples. This approach is suitable for interactive scripts that are to be run on behalf of different users.

First, the username and password are read from command line arguments:

> node example.js your_username your_password
> powershell example.ps1 your_username your_password

The, to obtain the expiring token we issue a POST call to the /api/token endpoint with the following parameters (note these are NOT JSON):

 "grant_type": "password"
"username": "your_username"
"password": "your_password"

If the supplied credentials are valid and the BMC Helix Discovery user is permitted to access the REST API, the response will contain a token within a JSON object:

{
   "access_token": "<your_token>",
   "expires_in": 3600,
   "token_type": "Bearer"
}

The example then shows that the token works by trying to set the status of the discovery process, issuing a PATCH call to the /discovery endpoint. Note that some users may not have permission to access this endpoint.

Expiring tokens are valid for one hour.


const APPLIANCE = "https://appliance";  // UPDATE ME


const BASE_PATH = `${APPLIANCE}/api/v1.1`;

// Set up some defaults for every call to the API
var request = require('request').defaults({json: true,
                                           agentOptions: {
                                               rejectUnauthorized: false
                                           }});


// Helper functions
function fail(message) {
    console.error(`ERROR: ${message}`);
    process.exit(1);
}

function rest(options, callback) {
    request(options, function (error, response, body) {
       if (error) {
            fail(error);
        }

       if (response.statusCode === 200) {
            callback(body);
        }
       else {
           let msg = `Request to ${options.uri} failed with status ${response.statusCode}`;
           if (body && body.message) {
                msg = `${body.message} (${msg})`;
            }
           else if (response.statusMessage) {
                msg = `${response.statusMessage} (${msg})`;
            }
            fail(msg);
        }
    });
}

function patch(options, callback) {
    options.method = 'PATCH';
    rest(options, callback);
}

function post(options, callback) {
    options.method = 'POST';
    rest(options, callback);
}


function parse_command_line() {
   let args = process.argv.slice(2);

   if (args.length < 2) {
        fail('Username and password required');
    }
   let username = args[0];
   let password = args[1];

   return {
       'username': username,
       'password': password
    }
}

// Application code

function start_discovery() {
    patch({uri: `${BASE_PATH}/discovery`, body: {status: 'running'}},
       function () {});
}


var options = parse_command_line();


const GRANT = {
    grant_type: 'password',
    username: options.username,
    password: options.password
};


// Obtain the token. Note that unlike all other endpoints,
// this one gets parameters as x-www-form-urlencoded
post({uri: `${APPLIANCE}/api/token`, form: GRANT},
   function (body) {
       // Token obtained, now we can add authentication to request object
       request = request.defaults({ auth: {'Bearer': body.access_token} });

       // Start discovery
       start_discovery();
    });

# This script accepts two mandatory command line arguments: USERNAME
# and PASSWORD

param([string]$USERNAME,
     [string]$PASSWORD)


$APPLIANCE = "https://appliance" # UPDATE ME


# Tell powershell not to default to insecure protocols!
[System.Net.ServicePointManager]::SecurityProtocol = [System.Net.SecurityProtocolType]::Tls12

################################################################################
################################################################################
###                                                                          ###
###                                WARNING                                   ###
### The following line disables server certificate validation to avoid       ###
### installing CA certificate for the server. It should only be present for  ###
### testing. You should never disable certificate validation in production.  ###
###                                                                          ###
################################################################################
################################################################################
[System.Net.ServicePointManager]::ServerCertificateValidationCallback = { $true }

function get_token {
   Param([string]$appliance,
         [string]$username,
         [string]$password)

   # Make the call to authenticate and get a temporary token for subsequent calls

   $body = @{"grant_type" = "password";
             "username" = $username;
             "password" = $password}

   try {
       $result = Invoke-RestMethod -Method Post -Uri "$appliance/api/token" `
                                    -Body $body -ErrorVariable rest_error
    } catch {
       $err = $_
       try {
           $temp = $rest_error.Message | ConvertFrom-Json
           if ($temp.message) {
               $err = $temp.message
            }
        } catch {}
       Write-Error $err
        exit
    }

   return $result.access_token
}

function restful_patch {
   Param([string]$url,
         [string]$token,
         [hashtable]$params)

   $headers = @{"Accept" = "application/json";
                "Authorization" = "Bearer $token"}

   $json = $params | ConvertTo-Json

   try {
       return Invoke-RestMethod -Method Patch -Uri $url -Headers $headers `
                                 -Body $json -ErrorVariable rest_error
    } catch {
       $err = $_
       try {
           $temp = $rest_error.Message | ConvertFrom-Json
           if ($temp.message) {
               $err = $temp.message
            }
        } catch {}
       Write-Error $err
        exit
    }
}



if (!$USERNAME -or !$PASSWORD) {
   Write-Error "Please supply a username and a password"
    exit
}

$token = get_token -appliance $APPLIANCE `
                          -username $USERNAME -password $PASSWORD

restful_patch -url "$APPLIANCE/api/v1.1/discovery" -token $token `
              -params @{"status" = "running"}

 

Tip: For faster searching, add an asterisk to the end of your partial query. Example: cert*