February 24, 2019

Cohesity Unofficial PowerShell Function Library

In my previous post: Accessing the Cohesity REST API Using PowerShell, I showed you the hard way to access the API, by using the Invoke-RestMethod PowerShell cmdlet after manually manipulating the header, body and URL to build a REST API call. If you like, you can certainly reuse such code to build scripts, although even the most basic script will end up looking like pretty complicated code. In my opinion, it's easier to wrap such code into functions that can be called with minimal arguments, making your scripts faster to write and easier to read.

So, I built a group of functions and stored them in a script called cohesity-api.ps1. You can download a copy of the library by running the following command in PowerShell:


Invoke-WebRequest -Uri https://raw.githubusercontent.com/bseltz-cohesity/scripts/master/powershell/cohesity-api/cohesity-api.ps1 -OutFile cohesity-api.ps1


To use the functions you can simply source, or dot, the script.


. ./cohesity-api.ps1


This makes the functions available in the current PowerShell session (or in the current script). Let's review some of the basic functions it provides...

Authentication


The first function authenticates you to a Cohesity cluster.


apiauth -vip mycluster -username admin -domain local
Connected!


Notice that I haven't provided a password nor have I been prompted for one. this is because apiauth stores your password (encrypted) for future use, so that scripts can run unattended. The first time you attempt to connect to a cluster, you will be prompted for your password and it will be stored.

If your Cohesity (or Active Directory) password changes, the stored password will need to be updated. This can be done using the -updatepassword switch:


apiauth -vip mycluster -username admin -domain local -updatepassword
Enter password for admin at mycluster: *****
Connected!


Also note that -domain defaults to 'local', and positional arguments work fine, so to connect as local admin, you can simply type:


apiauth mycluster admin
Connected!



Making API Calls


Once you've been authenticated, you can make API calls. You can simply provide the HTTP verb (get, post, put, delete) and the tail of the URL of the API call. for example, to get a list of protection jobs, you can type:


$jobs = api get protectionJobs

$jobs | Format-Table

   id name                 environment    policyId                            viewBoxId parentSourceId sourceIds
   -- ----                 -----------    --------                            --------- -------------- ---------            
    7 VM Backup            kVMware        770535285385794:1544976774290:5             5              1 {29, 30}             
   12 Infrastructure       kVMware        770535285385794:1544976774290:5             5              1 {121, 36, 39, 33...} 
   35 Oracle               kOracle        770535285385794:1544976774290:377           5             64 {61}                 
  841 Generic NAS          kGenericNas    770535285385794:1544976774290:25            5             85 {86}                 
 6222 File-Based Backup    kPhysicalFiles 770535285385794:1544976774290:5             5             60 {98}                 
 8028 SQL Backup           kSQL           770535285385794:1544976774290:377           5             46 {31}                 
12886 Scripts              kView          770535285385794:1544976774290:25            5            102 {116}                
12889 _DELETED_Utils       kView          770535285385794:1544976774290:25            5            102 {117}                
18471 _DELETED_NAS2 Backup kGenericNas    770535285385794:1544976774290:25            5             85 {639, 641, 643, 64...
27443 GarrisonToVE1        kVMware        770535285385794:1544976774290:27442         5              1 {1709, 1708}         
29663 SQL Physical         kSQL           770535285385794:1544976774290:25            5             46 {1696}    
           

The URL of this API call is actually https://mycluster/irisservices/api/v1/public/protectionJobs. I provided protectionJobs to the api function and it tacked on https://mycluster/irisservices/api/v1/public/ in front of it.

Public vs. Internal API


Cohesity API calls are categorized as either public or internal. All are accessible to the user, but public calls are designed to be easier to use and perhaps less subject to change from version to version. However, there are operations that you just can't do without resorting to some internal APIs. The Internal calls are a bit more complex, but that shouldn't stop us from using them when we have to.

To make an internal API call using our api function, simply prepend a / infront of the URL tail, like so:


api get /backupjobssummary

backupJobSummary
----------------
@{jobDescription=; numSuccessfulJobRuns=117; avgRunTimeUsecs=55172566; minRunTimeUsecs=26007890; maxRunTimeUsecs=65136469...
@{jobDescription=; numSuccessfulJobRuns=73; avgRunTimeUsecs=178826855; minRunTimeUsecs=91026530; maxRunTimeUsecs=17669251...
@{jobDescription=; numSuccessfulJobRuns=349; numFailedJobRuns=2; numCancelledJobRuns=1; avgRunTimeUsecs=35299180; minRunT...
@{jobDescription=; numSuccessfulJobRuns=78; numFailedJobRuns=3; numCancelledJobRuns=1; avgRunTimeUsecs=4321777; minRunTim...
@{jobDescription=; numSuccessfulJobRuns=51; numFailedJobRuns=1; avgRunTimeUsecs=7610093; minRunTimeUsecs=5000368; maxRunT...
@{jobDescription=; numSuccessfulJobRuns=216; avgRunTimeUsecs=23021394; minRunTimeUsecs=7001213; maxRunTimeUsecs=506184963...
@{jobDescription=; numSuccessfulJobRuns=37; avgRunTimeUsecs=0; minRunTimeUsecs=0; numObjectsBackedUp=37; totalBytesReadFr...
@{jobDescription=; numSuccessfulJobRuns=6; avgRunTimeUsecs=0; minRunTimeUsecs=0; numObjectsBackedUp=6; totalBytesReadFrom...
@{jobDescription=}
@{jobDescription=; numSuccessfulJobRuns=9; avgRunTimeUsecs=37345742; minRunTimeUsecs=27006816; maxRunTimeUsecs=86040358; ...
@{jobDescription=; numSuccessfulJobRuns=3; avgRunTimeUsecs=31007165; minRunTimeUsecs=29007773; maxRunTimeUsecs=32007717; ...


Drilling Down on the Data


Let's have a look at some of the data we got back. Let's return to our public protectionJobs call.


$jobs = api get protectionJobs


Having a look at $jobs shows us the list of jobs we got back.


$jobs | ft

   id name                 environment    policyId                            viewBoxId parentSourceId sourceIds
   -- ----                 -----------    --------                            --------- -------------- ---------            
    7 VM Backup            kVMware        770535285385794:1544976774290:5             5              1 {29, 30}             
   12 Infrastructure       kVMware        770535285385794:1544976774290:5             5              1 {121, 36, 39, 33...} 
   35 Oracle               kOracle        770535285385794:1544976774290:377           5             64 {61}                 
  841 Generic NAS          kGenericNas    770535285385794:1544976774290:25            5             85 {86}                 
 6222 File-Based Backup    kPhysicalFiles 770535285385794:1544976774290:5             5             60 {98}                 
 8028 SQL Backup           kSQL           770535285385794:1544976774290:377           5             46 {31}                 
12886 Scripts              kView          770535285385794:1544976774290:25            5            102 {116}                
12889 _DELETED_Utils       kView          770535285385794:1544976774290:25            5            102 {117}                
18471 _DELETED_NAS2 Backup kGenericNas    770535285385794:1544976774290:25            5             85 {639, 641, 643, 64...
27443 GarrisonToVE1        kVMware        770535285385794:1544976774290:27442         5              1 {1709, 1708}         
29663 SQL Physical         kSQL           770535285385794:1544976774290:25            5             46 {1696}          
     

We can have a closer look at the first one by providing a list index:


$jobs[0]  

id                               : 7
name                             : VM Backup
environment                      : kVMware
policyId                         : 770535285385794:1544976774290:5
viewBoxId                        : 5
parentSourceId                   : 1
sourceIds                        : {29, 30}
excludeSourceIds                 : {19, 87, 24, 26...}
startTime                        : @{hour=23; minute=30}
timezone                         : America/New_York
incrementalProtectionSlaTimeMins : 60
fullProtectionSlaTimeMins        : 120
priority                         : kMedium
alertingPolicy                   : {kFailure}
indexingPolicy                   : @{disableIndexing=False; allowPrefixes=System.Object[]; denyPrefixes=System.Object[]}
qosType                          : kBackupHDD
environmentParameters            : @{vmwareParameters=}
uid                              : @{id=7; clusterId=770535285385794; clusterIncarnationId=1544976774290}
policyAppliedTimeMsecs           : 1548971018230
modificationTimeUsecs            : 1548971018230150
modifiedByUser                   : admin
creationTimeUsecs                : 1544977491464965


and drill down into nested attributes:


$jobs[0].startTime

hour minute
---- ------
  23     30


We can get properties from each job in the list:


$jobs | Select-Object -Property name, startTime 

name                 startTime
----                 ---------
VM Backup            @{hour=23; minute=30}
Infrastructure       @{hour=23; minute=40}
Oracle               @{hour=0; minute=0}
Generic NAS          @{hour=0; minute=10}
File-Based Backup    @{hour=0; minute=40}
SQL Backup           @{hour=0; minute=20}
Scripts              @{hour=0; minute=30}
_DELETED_Utils       @{hour=23; minute=50}
_DELETED_NAS2 Backup @{hour=21; minute=0}
GarrisonToVE1        @{hour=23; minute=10}
SQL Physical         @{hour=18; minute=0}


Or, we can enumerate over the list:


foreach ($job in $jobs){
    "$($job.name)`t($($job.environment))"
}

VM Backup (kVMware)
Infrastructure (kVMware)
Oracle Adapter (kOracle)
File-Based Backup (kPhysicalFiles)
SQL Backup (kSQL)
Scripts (kView)
GarrisonToVE1 (kVMware)
SQL Physical (kSQL)
Generic NAS (kGenericNas)



Query Strings


The last topic I want to cover in this article, before we move on to other operations like posts and puts, is query strings in get URLs. You can pass along parameters with your get calls like this:


api get protectionJobs?environments=kVMware


and we can use the ampersand to include multiple parameters:


api get protectionJobs?environments=kVMware&isActive=true 

isActive=true : The term 'isActive=true' is not recognized as the name of a cmdlet, function, script file, or operable program.
Check the spelling of the name, or if a path was included, verify that the path is correct and try again.
At line:1 char:45
+ api get protectionJobs?environments=kVMware&isActive=true
+                                             ~~~~~~~~~~~~~
+ CategoryInfo          : ObjectNotFound: (isActive=true:String) [], CommandNotFoundException
+ FullyQualifiedErrorId : CommandNotFoundException


Whoops! PowerShell interpreted the & as the call operator. We can escape the & with a back tick like this:


api get protectionJobs?environments=kVMware`&isActive=true


and that works fine, but when there are several & and other troublesome characters in the URL, it's easier to simply quote the entire URL:


api get 'protectionJobs?environments=kVMware&isActive=true'


We can even use spaces:



api get 'protectionJobs?names=VM Backup'



or using double quotes, we can use variable expansion in our URL:


api get "protectionJobs?environments=$environment&isActive=true"



What's Next


You may have noticed that everything we've done so far uses the get verb. We've yet to cover posts, puts and deletes, but we'll save those for the next few articles.