Archiving inactive Device entries in a BYOD environment

Android_Admin
Conversationalist

Archiving inactive Device entries in a BYOD environment

Interested if others are doing this cleanup and if so how they are doing it. 

We would like to programatically clean up Systems Manager of devices which have been inactive for over 30 days. As we are a predominantly a BYOD environment (80k+ devices), users are free to remove management (they lose all enterprise apps and data, certs etc. if they do so) and there is also a churn of devices as people replace them.

We don't want to keep redundant devices in the system long term and want to keep tight on licensing.
For compliance and support reasons we would want to extract the metadata of the devices we archive and store it in another system for a period of time.

Meraki inbuilt policies only allow you to tag a device 'inactive' after a maximum of 7 days. This isn't sufficient as that catches people on holiday so we can't rely on Meraki identifying them correctly. 

There are no APIs I'm aware of that would allow me to selectively pull the data of devices that haven't checked in for 30+ days - We'd have to pull all the device data and then filter it which isn't efficient. 

 

Any thoughts on how best to achieve this or even sharing how you clean up appreciated. 

6 Replies 6
BrechtSchamp
Kind of a big deal

I'd base my script around:

GET List the devices enrolled in an SM network with various specified fields and filters
{{baseUrl}}/networks/{{networkId}}/sm/devices

HTTP REQUEST

GET /networks/[network_id]/sm/devices

PARAMETERS

parameters Description of the parameter
fieldsAdditional fields that will be displayed for each device. Multiple fields can be passed in as comma separated values. The default fields are: id, name, tags, ssid, wifiMac, osName, systemModel, uuid, and serialNumber. The additional fields are: ip, systemType, availableDeviceCapacity, kioskAppName, biosVersion, lastConnected, missingAppsCount, userSuppliedAddress, location, lastUser, publicIp, phoneNumber, diskInfoJson, deviceCapacity, isManaged, hadMdm, isSupervised, meid, imei, iccid, simCarrierNetwork, cellularDataUsed, isHotspotEnabled, createdAt, batteryEstCharge, quarantined, avName, avRunning, asName, fwName, isRooted, loginRequired, screenLockEnabled, screenLockDelay, autoLoginDisabled, hasMdm, hasDesktopAgent, diskEncryptionEnabled, hardwareEncryptionCaps, and passCodeLock.
wifiMacsFilter devices by wifi mac(s). Multiple wifi macs can be passed in as comma separated values.
serialsFilter devices by serial(s). Multiple serials can be passed in as comma separated values.
idsFilter devices by id(s). Multiple ids can be passed in as comma separated values.
scopeSpecify a scope (one of all, none, withAny, withAll, withoutAny, or withoutAll) and a set of tags as comma separated values.
batchTokenOn networks with more than 1000 devices, the device list will be limited to 1000 devices per query. If there are more devices to be seen, a batch token will be returned as a part of the device list. To see the remainder of the devices, pass in the batchToken as a parameter in the next request. Requests made with the batchToken do not require additional parameters as the batchToken includes the parameters passed in with the original request. Additional parameters passed in with the batchToken will be ignored.

SAMPLE REQUEST

curl -L -H 'X-Cisco-Meraki-API-KEY: <key>' -X GET -H 'Content-Type: application/json' 'https://api.meraki.com/api/v0/networks/[networkId]/sm/devices?fields=ip&scope=withAll,recently-added'

SAMPLE RESPONSE

Successful HTTP Status: 200

{
  "devices": [
    {
      "id": "123456",
      "name": "Device name",
      "tags": [
        "recently-added"
      ],
      "ssid": "Wifi name",
      "wifiMac": "00:aa:00:00:a0:a0"
      "osName": "iOS 9.3.2",
      "systemModel": "iPod",
      "uuid": "123aaa123aaa",
      "serialNumber": "AABBCCDD",
      "ip": "11.1.1.111"
    }
  ],
  "batchToken": <batchToken>
}
HEADERS

X-Cisco-Meraki-API-Key
{{X-Cisco-Meraki-API-Key}}

 

https://documenter.getpostman.com/view/897512/meraki-dashboard-api/2To9xm?version=latest#996d807e-52...

 

This call could get the information for your archive, and with the "lastConnected" field you should be able to isolate the "inactive" users. A consecutive call unenrolling those should do the trick.

 

Would that be an option?

Android_Admin
Conversationalist

Thanks for the speedy response. This is pretty much what we were considering along with pulling lastConnected but it pulls all the devices and not just the ones which have hit the 30 days. With a large volume of devices, this isn't ideal for us. It may be the best we can do for now but having a change to the Meraki policy options so we can set to say 'Device must check in every 30 days' would allow us to pull a subset of devices based on their compliance with this policy. That would be helpful and the numbers fairly small. 

At the moment we are just considering tagging devices meeting the criteria for our separate clean-up process to pick them up and archive them. We want tag after 30 days and archive after 60 days. Recycling of licences is quite important to us for some applications. 

BrechtSchamp
Kind of a big deal


@Android_Admin wrote:

Thanks for the speedy response. This is pretty much what we were considering along with pulling lastConnected but it pulls all the devices and not just the ones which have hit the 30 days. With a large volume of devices, this isn't ideal for us. It may be the best we can do for now but having a change to the Meraki policy options so we can set to say 'Device must check in every 30 days' would allow us to pull a subset of devices based on their compliance with this policy. That would be helpful and the numbers fairly small. 

At the moment we are just considering tagging devices meeting the criteria for our separate clean-up process to pick them up and archive them. We want tag after 30 days and archive after 60 days. Recycling of licences is quite important to us for some applications. 


Sounds like a good procedure tbh. When is life ever ideal ;).

PhilipDAth
Kind of a big deal
Kind of a big deal

I like @BrechtSchamp 's answer.  You can do the query on the lastConnected field.

 

Another option you could consider is creating an additional Systems Manages network called something like "Archive".  You could initially move clients that look like they are inactive to this network.  You could even create a policy to do something different to notify the users their device is on the verge of being deleted and to contact support if they still need it.

And then finally delete them from here once they have been present for a period of time.

Android_Admin
Conversationalist

Thanks for the response. I'll have a think about whether moving devices to a different Network is beneficial. It might be if we can free the licences earlier. We have to archive to an external system before deleting. It might make it easier to do from a dedicated 'Archive' Network but I'd have to look at what we need for compliance. 

wperry1
Here to help

I use the attached script to identify and tag devices that haven't checked in for 30+ days via the API. You could add a step to export the data to a CSV or other data source.

 

### SET THESE VARIABLES ###
    # Set the number of days old before the device is tagged.
    $cutOffDays = 30
    
    # You will need to set your Organization ID Here. It is probably a 5-digit number
    $orgId = ""

    # You can find this by logging in to Meraki with your account. 
    $tennantFQDN = "xxx.meraki.com"

    # You will need to get an API key from Meraki and put it here
    $apiKey = ""

    # Sets verbose output of REST requests
    $verbose = $true

    # Time (MS) to wait after each REST request (If you get errors about too many/too fast API requests, increase this number until you don't)
    $throttleMS = 100
### ###

# Setting some headers to be used with later requests
$contentType = "application/json"
$headers = @{ "X-Cisco-Meraki-API-KEY" = $apiKey }

# Since Meraki is using Unix Timestamps, This will be used to conver them to DATETIME values later
$start = Get-Date "1970-01-01T00:00:00Z"

# Set the cutoff Date 
$cutOffDate = (Get-Date).AddDays(-1 * $cutOffDays)

# Set the tag to be used for devices over the threshold
$tag = "Over$cutOffDays`Days" 

# This is needed for SSL REST Requests to work properly
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12

function Get-SMNetworks{
    # Build parameters to request the list of networks in your organization
    $params = @{ContentType = $contentType
                Headers     = $headers
                Method      = "Get"
                Uri         = "https://$tennantFQDN/api/v0/organizations/$orgId/networks"
                Body        = $null
                Verbose     = $verbose
                }
    
    # Execute the REST request to return your organizations networks
    $networks = Invoke-RestMethod @params
    Start-Sleep -Milliseconds $throttleMS
    if($networks){
        # If networks were found, filter the list to SME networks and return the result
        $networks = $networks | WHERE { $_.type -like "systems manager" }
        return $networks
    }
}

function Get-SMDevices{
    param([string]$NetworkID,
            [string]$Fields)
    
    #Only proceed if a network ID was provided
    if($NetworkID){
        $firstPass = $true
        $batchToken = $null

        # This request can only return 1000 results. Results are paged so you can retrieve all records through multiple results
        while($firstPass -or $response.batchToken -ne $null){
            $firstPass = $false

            # Build the URI request and append either the requested fields or the batch token for page 2+ of results
            $uri = "https://$tennantFQDN/api/v0/networks/$NetworkID/sm/devices"
            if($batchToken){ $uri += "?batchToken=$batchToken" }
            elseif($fields){ $uri += "?fields=$fields" }

            # Execute the request
            $response = Invoke-RestMethod -ContentType $contentType -Headers $headers -Method Get -Uri $uri -Verbose
            Start-Sleep -Milliseconds $throttleMS

            # Update the Batch Token. Allows the next page of results to be retrieved
            $batchToken = $response.batchToken

            # Add the NetworkID to all devices returned (Used later as updates must be perfomred per network)
            $response.devices | Add-Member -MemberType NoteProperty -Name "NetworkID" -Value $NetworkID

            #Add the devices to the device collection
            $devices += $response.devices
        }
        return $devices
    }
}

# Load all your organization's SME Networks
$networks = Get-SMNetworks

# Load all devices from your SME networks
$allDevices = $networks | % { Get-SMDevices -NetworkID $_.id -Fields "lastConnected" }

# Get all devices that have and have not checked in in $cutOffDays days
$addTag = $allDevices | SELECT id,serialNumber,tags,@{LABEL="LastConnect";Expression={$start.AddSeconds($_.lastConnected)}},NetworkID | WHERE { $_.LastConnect -lt $cutOffDate -and $_.tags -notcontains $tag }
$remTag = $allDevices | SELECT id,serialNumber,tags,@{LABEL="LastConnect";Expression={$start.AddSeconds($_.lastConnected)}},NetworkID | WHERE { $_.LastConnect -ge $cutOffDate -and $_.tags -contains $tag }

# These hashtables will be used to build the JSON requests and parameters for the REST Request
$body = @{ "ids" = ""; "updateAction" = ""; "tags" = $tag }
$params = @{ ContentType = $contentType; Headers = $headers; Method = "Put"; Uri = $null; Body = ""; Verbose = $verbose; }

### Tag Devices that have not checked in in $cutOffDays days ###
foreach($network in $networks){
        
    # This url will be used to send the tagging requests
    $netId = $network.id
    $params.uri = "https://$tennantFQDN/api/v0/networks/$netId/sm/devices/tags"

    # Get the devices for this network that need tags added
    $addTagNet = $addTag | WHERE NetworkID -eq $netId

    # Only execute the add tag operation if there are devices needing the tag
    if($addTagNet){
        # Set the updateAction to add as wer are adding tags
        $body.updateAction = "add"
        # Add the device IDs to the body
        $body.ids = $addTagNet.id -join ", "
        # Convert the body to JSON
        $json = ConvertTo-Json $body
        # Add the json body to the request parameters
        $params.Body = $json
        # Execute the request to tag devices
        Invoke-RestMethod @params
        Start-Sleep -Milliseconds $throttleMS
    }

    # Get the devices for this network that need tags removed
    $remTagNet = $remTag | WHERE NetworkID -eq $netId

    # Only execute the add tag operation if there are devices needing the tag
    if($remTagNet){
        pause
        # Set the updateAction to delete as wer are removing tags
        $body.updateAction = "delete"
        # Add the device IDs to the body
        $body.ids = $remTagNet.id -join ", "
        # Convert the body to JSON
        $json = ConvertTo-Json $body
        # Add the json body to the request parameters
        $params.Body = $json
        # Execute the request to tag devices
        Invoke-RestMethod @params
        Start-Sleep -Milliseconds $throttleMS
    }
}
### ###
Get notified when there are additional replies to this discussion.
Welcome to the Meraki Community!
To start contributing, simply sign in with your Cisco account. If you don't yet have a Cisco account, you can sign up.
Labels