Asset Recovery in a Remote World
Introduction
Managing assets in the era of remote work can feel like juggling in the dark. Devices are no longer just down the hall—they’re across the country (or even the globe).
This article will help you streamline asset recovery using no-code automation platforms like Tines, along with tools like Slack, Jamf Pro, and FedEx APIs. Building on my JNUC 2024 session of the same name, we'll go over practical steps to get those devices back where they belong.
All example scripts and details can be found in the companion GitHub repo.

Common Challenges
"Who has what, where are they, why do they have two, and why haven't either checked into Jamf in weeks?"
In a remote-first world, managing assets presents unique challenges:
- Devices are spread out across as many locations as you have employees.
- Communication is no longer face-to-face, and messages can easily be missed.
- The days of simply walking over and reclaiming a device are gone.
The Power of Automation
Time is more than just money—it's sanity. Every hour spent manually tracking and recovering assets is an hour that could be spent on strategic initiatives. Automating asset recovery saves time, reduces human error, and ensures nothing slips through the cracks.
Platforms Aplenty
- Tines (Preferred): Tines is a no-code platform that is as easy to use as it is powerful. The barrier to entry is extremely low, and if you really need to use Python to get around an obstacle, you can do so with their
runscriptactions. - AWS + Lambdas: If you already have an AWS environment, this is a great choice. By using AWS Lambda, you can write code to create tailored solutions that integrate with other AWS services, providing a scalable, cost-effective approach.
- Okta Workflows (Least Flexible): While not as flexible or robust as Tines or AWS Lambda, power users can still squeeze a lot of functionality out of this solution. Great if you already use Okta and the above options aren't viable.
Integrations of Interest
- Mobile Device Management (Jamf Pro): Use your MDM to get asset data, identify which devices need to be returned, and securely wipe them once they make it back home.
- Enterprise Communication (Slack, Teams): Automate outreach and follow-ups to keep users informed and engaged. Slack's rich suite of API's and block kit builder come in clutch here.
- Shipping Logistics (FedEx, UPS): Automatically create return labels and track shipments in real time. While getting certified to generate shipping labels can be challenging, the rewards are nigh innumerable.
- Intermediary Data Store (Airtable, Google Sheets): Keep in-scope data in one place, giving you a clear view of asset statuses throughout the recovery process. Airtable is a great choice for tracking asset recovery, storing asset details, monitoring communication, and attractively presenting constituent data.
One Step at a Time
"Like a carpenter... That builds stairs." - Andy Bernard

Identification
Start by identifying the devices you need to reclaim. This step can vary significantly based on your organization's needs and how assets were obtained (IE, leased vs owned). Key factors include whether the devices are end-of-life, unused, or there are multiple assets assigned to a single user.
- Jamf Pro: Use Jamf Pro to identify the devices needing to be reclaimed. This will almost always be the starting point for asset recovery.
- Data Centralization: Store the identified data in a tool like Airtable or Google Sheets to provide a clear overview of which assets are in scope for recovery.
Remember, in almost every case, you should count on every asset eventually making its way back into inventory. If you don't have a CMDB (Configuration Management Database) or an asset database, please get one. This will make future identification and recovery far more efficient.
...
computer_group = jamf.get_computer_group(id=computer_group_id)
if 'Error' in computer_group:
print(f"Failed to retrieve computer group: {computer_group['Error']}")
return
all_computer_details = []
for computer in computer_group.get('computers', []):
inventory_details = jamf.get_computer_inventory_details(computer['id'])
if inventory_details['success']:
computer_data = {
'jamf_id': computer['id'],
'asset_serial': inventory_details['data']['serialNumber'],
'asset_name': inventory_details['data']['name'],
'asset_model': inventory_details['data']['model'],
'user_name': inventory_details['data'].get('userName'),
'user_email': inventory_details['data'].get('userEmail')
}
# Create Airtable record
airtable_record = airtable.create_record(computer_data) # Assuming you have a "create_record" method
if 'id' in airtable_record:
computer_data['airtable_record_id'] = airtable_record['id']
print(f"Airtable record created for computer ID {computer['id']}")
else:
print(f"Failed to create Airtable record for computer ID {computer['id']}")
all_computer_details.append(computer_data)
else:
print(f"Failed to retrieve inventory for computer ID {computer['id']}: {inventory_details['message']}")
...
Communication
Good communication is paramount—start early, nudge often, and escalate as necessary.
- Automated Messaging: Using Slack (or your messenger of choice), send initial DMs to in-scope device owners. These messages should not only inform but should include the necessary mechanisms to collect important context.
- Persistence is Key: Plan for automated follow-ups and escalate as needed, ensuring that the process is persistent but respectful. This can include moving from Slack messages to email follow-ups and should eventually involve managerial assistance as appropriate.
- Handling Different Platforms: While Slack is used as an example, the automations and strategies here are not dependent on any single communication platform—regardless if your organization uses Slack, Teams, or another tool.
- Interactive Messaging: Use interactive messages that allow users to easily respond, acknowledge instructions, or request help if needed. The goal is to remove as many obstacles as possible.
- Tracking Interactions: Use your data store (Airtable, Sheets, etc.) to log all interactions—track who has been contacted, who has responded, and what has been said. Maintaining these logs will help identify the need for repeat messaging and will make escalation easier.
Remember, effective communication is not just about the initial contact; it's also about ensuring users are guided through the process as much or as little as necessary. Provide clear instructions, remove roadblocks, and be persistent but respectful.
...
def send_direct_message(slack_token: str, email: str, serial_number: str):
"""
Send a direct message to a user's email via Slack with a given system serial number using Slack blocks.
Args:
slack_token (str): Authentication token for Slack.
email (str): User email to send message to.
serial_number (str): System serial number to include in the message.
"""
slack = SlackClient(token=slack_token)
# Slack blocks with the serial number embedded in the message
blocks = [
...
{
"type": "section",
"block_id": "asset_recovery_question",
"text": {
"type": "mrkdwn",
"text": "Hello, our records indicate asset {asset} hasn't checked into Jamf in a while. "
"Do you still have this system?"
}
},
...
]
# Find user ID by email
user_id = slack.find_user_by_email(email)
if user_id:
success = slack.send_message(channel_id=user_id, blocks=blocks)
if success:
print(f"Message successfully sent to {email}")
return True
else:
print(f"Failed to send message to {email}")
return False
else:
print(f"Could not find Slack user with email: {email}")
return False
...
Reclamation
Reclaiming assets efficiently is all about reducing friction while keeping a close eye on every step of the return process.
- Streamlined Return Labels: Use your shipping platform's API (e.g., FedEx) to automatically create return labels and send them directly to the user. Consider sending the label through multiple channels, such as Slack and email, to ensure the user receives it in a format they prefer.
- Real-Time Shipment Tracking: Monitor shipments as they progress. Use Airtable or your intermediary data store to log the status of each shipment, ensuring no step in the return process is overlooked. Keep all stakeholders informed by updating statuses in real time.
- Facilitating Returns: Assist users in finding the nearest drop-off location by providing automated suggestions based on their address. Include detailed information such as location address, hours of operation, and any other key details to ensure clarity and convenience.
The goal is to make the return process as easy as possible—similar to returning an online purchase. By minimizing obstacles and providing flexible return options, you improve the chances of a successful and timely reclamation.
...
def handle_slack_response(slack_response):
"""
Handle the response received from Slack and generate a FedEx return label if necessary.
Args:
slack_response (dict): The response payload from Slack.
"""
user_response = slack_response['actions'][0]['selected_option']['value']
if user_response == "send_asset_back":
print("User wants to send the system back. Generating FedEx return label...")
fedex_data = generate_fedex_return_label(slack_response['user']['id'])
if fedex_data:
update_slack_dm(slack_response, fedex_data)
def generate_fedex_return_label(user_id):
"""
Generate a FedEx return label for the user who wants to send back the system.
Args:
user_id (str): Slack user ID who needs the return label.
Returns:
dict: FedEx return information such as tracking number, return label URL, and location.
"""
api_key = 'YOUR_FEDEX_API_KEY'
fedex = FedExAPI(api_key=api_key, environment='production') # Use 'sandbox' for testing
# Dummy data for FedEx label creation (replace with actual FedEx API call)
shipment_details = {
'Recipient': {'Email': f'{user_id}@yourcompany.com'}, # Assuming email format
'Package': {'Weight': '5 lbs', 'Dimensions': '10x10x10'},
'Sender': {'Address': 'Company Address'}
}
response = fedex.create_shipment(shipment_details)
if response['success']:
return {
'tracking_number': response['tracking_number'],
'label_url': response['label_url'],
'fedex_location': "FedEx Office Store #123",
'location_address': "123 FedEx Lane, City, State, ZIP"
}
else:
print("Failed to create FedEx return label:", response['message'])
return None
...
Deletion
Asset recovery doesn’t end when the device is returned—you need to ensure proper asset hygiene and data security through several important steps:
- Device Erasures: Use the Jamf Pro API to securely wipe the returned device. Expose this functionality in a secure, accessible way to your analysts, ensuring they can initiate wipes efficiently without degrading overall security.
- Record Cleanup: Don't forget about your other consoles: Endpoint detection tools, vulnerability management systems, and any other security tools where the device had a corresponding record. Automating this cleanup process can reduce human error and ensure that stale records do not linger, which will complicate your future endeavors.
- CMDB Updates: Update your Configuration Management Database (CMDB) to reflect the asset's status. This includes marking the asset as wiped and securely stored or ready for re-deployment. Keeping your CMDB updated is crucial for both compliance and operational efficiency.
- Maintain the Audit Trail: Record and retain each step of the reclamation process. This can include logs from the Jamf Pro API, shipment tracking details, and confirmation of successful wipes. Ensuring a comprehensive audit trail will help with both internal reviews and compliance requirements.
...
# Step 2: Send the EraseDevice command to the computer
erase_response = jamf_client.erase_device(computer_id, passcode)
if not erase_response['success']:
print(f"Failed to send EraseDevice command: {erase_response['message']}")
return
# Retrieve the status UUID from the response
status_uuid = erase_response['data']['computer_command']['command']['command_uuid']
print(f"EraseDevice command sent successfully. Status UUID: {status_uuid}")
# Step 3: Loop and wait for the command to be 'Acknowledged'
print("Waiting for the EraseDevice command to be acknowledged...")
check_counter = 0
while check_counter < 10:
check_counter += 1
status_response = jamf_client.check_mdm_command_status(status_uuid)
if status_response['success']:
status = status_response['data']['computer_command']['status']
print(f"Current status: {status}")
if status == 'Acknowledged':
print(f"Command acknowledged for computer ID: {computer_id}")
break
else:
print(f"Failed to check command status: {status_response['message']}")
return
...
Wrapping Up
Asset recovery in a remote-first world can be challenging, but leveraging automation platforms and integrating tools you already have can make those painful of processes palatable.
Start small: automate one part of the workflow, like identifying devices or automating communication. Learn, iterate, and gradually expand automation to cover the full lifecycle, from identification to sanitization.
The goal is progress, not perfection. Each step reduces your workload, minimizes errors, and ensures valuable assets are recovered smoothly.
For scripts, examples, and more context, check out the JNUC 2024 Repository - And don't hesitate to reach out if you need help getting started!