Automating MSP Client Onboarding with LogicMonitor

Automating MSP Client Onboarding with LogicMonitor

Automating client onboarding can eliminate the tedious task of cloning dashboards, creating group directory structure, setting up reports, and configuring access roles. All of these tasks are prone to human error and to put it mildly, not really fun to do. In this blog, we’ll walk through a PowerShell script to automate some of these tasks. Specifically, the creation of the Client Group Structure with multiple NOC locations, dashboard groups, the cloning of standard dashboards, and the creation of a read-only user role for new clients when they access the LogicMonitor portal.    

I do use a function named  Send-Request written by a colleague of mine, Jonathan Arnold, which is available on the official Monitoring Recipies Github. This function simplifies the API request to a one-line Send-Request $resourcePath $httpVerb $queryParams $data

The code snippets in this blog will be defining these variables along with code logic to accomplish specific tasks.  

Creating the Resource Directory Structure

The first problem that needs to be addressed is the different NOC locations. MSPs will normally have a standard folder structure used across all clients for formality. However, clients will have multiple NOCs that need to be accounted for. I used an array and a do loop to solve this.  

<# Client Info #>
$Client = 'Woeber Capital'
<# NOC Locations #>
$SitesArrray = @("Atlanta","DR","Florida","Home Routers","LA","NYC")

In my group creation portion of the script, I first build the Root Client Folder using our LogicMonitor REST API Developer’s Guide.  

#Main Client Group 
$data = '{"name":"' + $Client + '","parentId":' + $clientDir + '}'
$resultsGrp = Send-Request $resourcePath $httpVerb $queryParams $data

*$clientDir is the ID of the main Client group and is optional

Then use a foreach loop to create the groups based on the NOC location and under each of these groups create a standard group structure of “System | Network | DR” groups under the NOC.  The $resultsGrrp.id is the Device Group ID created in the previous step.  

<# Static Groups creation Loop through Locations #>
foreach ($Site in $SitesArrray){
    #Main Location Group 
    $data = '{"name":"' + $Site + '","parentId":' + $resultsGrrp.id + '}'
    $resultsLocation = Send-Request $resourcePath $httpVerb $queryParams $data
    Write-host $data

    #System
    $data = '{"name":"System","parentId":'+ $resultsLocation.id + '}'
    $resultsSubdir = Send-Request $resourcePath $httpVerb $queryParams $data
    
    #Network
    $data = '{"name":"Network","parentId":'+ $resultsLocation.id + '}'
    $resultsSubdir = Send-Request $resourcePath $httpVerb $queryParams $data
    
    #DR
    $data = '{"name":"DR","parentId":'+ $resultsLocation.id + '}'
    $resultsSubdir = Send-Request $resourcePath $httpVerb $queryParams $data
}

Creating the Dashboard Structure and Cloning Default Dashboards

Next, create the Dashboard group with a preconfigured ##defaultResourceGroup## token 

that points back to the group structure we just created. This way any dashboards cloned to this Dashboard group will be automatically configured to point to the client group structures previously created.  

# Add Dashboad Group with Widget Token
$resourcePath = "/dashboard/groups"
$queryParams = ""
$data = '{"name":"' + $Client + '","parentId":' + $dbGroup + ',"widgetTokens":[{"name":"defaultResourceGroup","value":"CLIENTS/' + $Client + '"}]}'
Write-Host $data
$resultsDashboards = Send-Request $resourcePath $httpVerb $queryParams $data

Cloning Dashboards has a few tricky parts. The first is the widget sizes are not saved in the dashboard itself, but in the widgets. It’s possible to get around this by getting the widget configuration from the original dashboard and patch the cloned dashboard with the correct widget size information. Details of how this can be done can be found in our Dashboard Developer API Guide. The below code accomplishes this.  

    # Loop through the widgets to build WidgeConf JSON
    $i = 0
    DO
    {
        $widId = $orgDashboard.items[$i].id
        $widgetCof = $orgWidSize.widgetsConfig.$widId
        $WidConf = $WidConf + '"' + $clonedDashboard.items[$i].id + '" :' + '{"col":' + $WidgetCof.col + ',"sizex":' + $WidgetCof.sizex + ',"row":' + $WidgetCof.row + ',"sizey":' + $WidgetCof.sizey + "}"
        if ($i -lt ($clonedDashboard.total -1)){$widConf = $WidConf + ","}     
        $i++

    } While ($i -lt $clonedDashboard.total)

    write $data

    # Patch Dashboard with WidgeConfig JSON
    $httpVerb = "PATCH"
    $resourcePath = "/dashboard/dashboards/" + $cDashboard.id 
    $queryParams = ''
    $data = '{"widgetsConfig":{' + $widConf + '}}' #| ConvertTo-Json -depth 3
    $resizeDashboard = Send-Request $resourcePath $httpVerb $queryParams $data

To specify which dashboards to clone and to accommodate complex directory structures it is possible to use arrays. In the below example you can see arrays representing three dashboard groups. One for the root client level and two for subgroups, Microsoft and VMware. The array contains the original dashboard IDs to be cloned. This is the same technique used in the NOC grouping structure previously. The simplest way to find the Dashboard ID is in the URL, an example “/santaba/uiv3/dashboard/index.jsp#dashboard=7”, the Dashboard ID is 7.     

#Root level Dashboarrds
$DBToCloneArrray =  @(7,54,62,42,73)

# Microsoft Dashbaords
$msDashboards = @(295,354,122)

# VMWare Dashboards
$vmDashboards = @(294,165,456)

The full dashboard section is the most complicated part of the onboarding script.  Below is the full code for this section and it uses the above root level dashboard array.  

<# Clone Root Level Dashboards #> 
foreach ($DB in $DBToCloneArrray){
    #Get Dashboard Name
    $httpVerb = “GET”
    $resourcePath = “/dashboard/dashboards/" + $DB
    $queryParams = '?fields=name'
    $data = $null
    $orgDbName = Send-Request $resourcePath $httpVerb $queryParams $data
    write $resourcePath
    write "testing = " + $orgDbName

    # Clone the dashboard
    $httpVerb = "POST"
    $resourcePath = "/dashboard/dashboards/" + $DB + "/clone"
    $queryParams = ''
    $data = '{"name":"' + $orgDbName.name + '","sharable":true, "groupId":' + $resultsDashboards.id + '}'
    $cDashboard = Send-Request $resourcePath $httpVerb $queryParams $data
    write $data

    # Original dashboard Widget Size, Sort by name to ensure array in same order as Cloned Dashboard
    $httpVerb = “GET”
    $resourcePath = “/dashboard/dashboards/" + $DB
    $queryParams = ‘?fields=widgetsConfig’
    $data = $null
    $orgWidSize = Send-Request $resourcePath $httpVerb $queryParams $data
    $orgWidSize | ConvertTo-Json -Depth 6


    # Original Dashboard Widget List, Sort by name to ensure array in same order as original Dashboard
    write "Orriginal Widget List"
    $httpVerb = “GET”
    $resourcePath = “/dashboard/dashboards/" + $DB + "/widgets”
    $queryParams = '?fields=id,name&sort=+name'
    $data = $null
    $orgDashboard = Send-Request $resourcePath $httpVerb $queryParams $data
    $orgDashboard | ConvertTo-Json

    # Cloned Dashboard Widget List
    write "Cloned Widget List"
    $httpVerb = “GET”
    $resourcePath = “/dashboard/dashboards/" + $cDashboard.id + "/widgets”
    $queryParams = ‘?fields=id,name&sort=+name'
    $data = $null
    $clonedDashboard = Send-Request $resourcePath $httpVerb $queryParams $data
    $clonedDashboard | ConvertTo-Json

    # Build the WidgeConfig JSON
    $WidConf = ''

    # Loop through the widgets to build WidgeConf JSON
    $i = 0
    DO
    {
        $widId = $orgDashboard.items[$i].id
        $widgetCof = $orgWidSize.widgetsConfig.$widId
        $WidConf = $WidConf + '"' + $clonedDashboard.items[$i].id + '" :' + '{"col":' + $WidgetCof.col + ',"sizex":' + $WidgetCof.sizex + ',"row":' + $WidgetCof.row + ',"sizey":' + $WidgetCof.sizey + "}"
        if ($i -lt ($clonedDashboard.total -1)){$widConf = $WidConf + ","}     
        $i++

    } While ($i -lt $clonedDashboard.total)

    write $data
    # Patch Dashboard with WidgeConfig JSON

    $httpVerb = "PATCH"
    $resourcePath = "/dashboard/dashboards/" + $cDashboard.id 
    $queryParams = ''
    $data = '{"widgetsConfig":{' + $widConf + '}}' #| ConvertTo-Json -depth 3
    $resizeDashboard = Send-Request $resourcePath $httpVerb $queryParams $data

Creating the Client User Access Roles

Now that everything is created and we have all the needed information, we can create the user security role that gives read-only access for the new clients. This is simply creating a new role with “operation”:”read”  for the client groups created with the script. Details for creating User Access Roles are in the roles section of our API developer Guide.  

# Add the Company Role  
$resourcePath = "/setting/roles"
$queryParams = ""
$data = '{"name":"' + $Client + '","privileges":[{"objectType":"dashboard_group","objectId":' + $resultsDashboards.id + ',"objectName":"' + $Client + '","operation":"read"}, {"objectType":"host_group","objectId":' + $results.id + ',"objectName":"' + $Client + '","operation":"read"}, {"objectType":"report_group","objectId":' + $resultsReport.id + ',"objectName":"' + $Client + '","operation" : "read"},{"objectType":"help","objectId" : "feedback","objectName":"help","operation":"read"},{"objectType":"help","objectId" : "document","objectName":"help","operation":"read"},{"objectType":"help","objectId":"training","objectName":"help","operation":"read"}]}'
Write-Host $data
$resultsRole = Send-Request $resourcePath $httpVerb $queryParams $data

We’ve discussed the different sections on creating an MSP onboarding script with examples on how to code them. The tools outlined should make it possible to develop onboarding scripts flexible enough for custom MSP onboarding needs and automate a rather tedious process. For more details and Professional Service options please reach out to your Custom Success Manager.  

Jeff Woeber

Jeff Woeber is a Solutions Engineer on the Professional Service team at LogicMonitor where he has consulted and deployed some of LogicMonitor’s largest environments. Jeff loves all things automation and has a real passion for solving the impossible problems.

Subscribe to our LogicBlog to stay updated on the latest developments from LogicMonitor and get notified about blog posts from our world-class team of IT experts and engineers, as well as our leadership team with in-depth knowledge and decades of collective experience in delivering a product IT professionals love.