View Last Opened Time of Apps and Files on macOS
This comprehensive guide demonstrates how to track when applications and files were last accessed on macOS devices, providing essential insights for Mac fleet management and usage monitoring.
Overview
Monitoring app and file access patterns is crucial for:
- Performance optimization: Identifying resource-intensive applications
- License management: Tracking software usage for compliance
- Security auditing: Monitoring file access for security compliance
- Storage optimization: Finding unused applications and files
- User behavior analysis: Understanding work patterns and productivity
Basic Last Opened Time Script
Simple App/File Check
Create a basic script to check when an app or file was last opened:
#!/bin/bash
# Basic script to get last opened time of an app or file
# Usage: ./last_opened.sh "/path/to/app/or/file"
if [ $# -eq 0 ]; then
echo "Usage: $0 <file_path>"
echo "Example: $0 '/Applications/Safari.app'"
exit 1
fi
file_path="$1"
# Check if file exists
if [ ! -e "$file_path" ]; then
echo "Error: File or application not found: $file_path"
exit 1
fi
# Get last opened time using mdls
datetime=$(mdls "$file_path" -name kMDItemLastUsedDate | awk '{print $3,$4}')
if [ "$datetime" = "(null)" ]; then
echo "'$file_path' has never been opened or last opened time is not available"
exit 0
fi
echo "'$file_path' was last opened on (UTC):"
echo "$datetime"
echo "'$file_path' was last opened on (Local Time):"
echo "$(date -jf "%Y-%m-%d %H:%M:%S %z" "$datetime +0000" +"%Y-%m-%d %H:%M:%S")"
Enhanced Script with Error Handling
#!/bin/bash
# Enhanced script with comprehensive error handling and formatting
# Usage: ./enhanced_last_opened.sh "/path/to/app/or/file"
check_last_opened() {
local file_path="$1"
local file_name=$(basename "$file_path")
# Validate input
if [ -z "$file_path" ]; then
echo "Error: No file path provided"
return 1
fi
# Check if file exists
if [ ! -e "$file_path" ]; then
echo "Error: File not found: $file_path"
return 1
fi
# Get metadata
local datetime=$(mdls "$file_path" -name kMDItemLastUsedDate 2>/dev/null | awk '{print $3,$4}')
local creation_date=$(mdls "$file_path" -name kMDItemContentCreationDate 2>/dev/null | awk '{print $3,$4}')
local modification_date=$(mdls "$file_path" -name kMDItemContentModificationDate 2>/dev/null | awk '{print $3,$4}')
echo "=== File Information ==="
echo "File: $file_name"
echo "Path: $file_path"
echo ""
# Last opened time
if [ "$datetime" = "(null)" ] || [ -z "$datetime" ]; then
echo "Last Opened: Never opened or information not available"
else
echo "Last Opened (UTC): $datetime"
local local_time=$(date -jf "%Y-%m-%d %H:%M:%S %z" "$datetime +0000" +"%Y-%m-%d %H:%M:%S" 2>/dev/null)
if [ $? -eq 0 ]; then
echo "Last Opened (Local): $local_time"
else
echo "Last Opened (Local): Unable to convert time"
fi
fi
# Creation date
if [ "$creation_date" != "(null)" ] && [ -n "$creation_date" ]; then
echo "Created (UTC): $creation_date"
fi
# Modification date
if [ "$modification_date" != "(null)" ] && [ -n "$modification_date" ]; then
echo "Modified (UTC): $modification_date"
fi
echo "========================"
echo ""
}
# Main execution
if [ $# -eq 0 ]; then
echo "Usage: $0 <file_path>"
echo "Example: $0 '/Applications/Safari.app'"
exit 1
fi
check_last_opened "$1"
Advanced Application Monitoring
Bulk Application Scanner
#!/bin/bash
# Bulk application scanner with detailed reporting
# Usage: ./bulk_app_scanner.sh
scan_applications() {
local output_file="app_usage_report_$(date +%Y%m%d_%H%M%S).txt"
echo "MacFleet Application Usage Report" > "$output_file"
echo "Generated: $(date)" >> "$output_file"
echo "Device: $(scutil --get ComputerName)" >> "$output_file"
echo "User: $(whoami)" >> "$output_file"
echo "=======================================" >> "$output_file"
echo "" >> "$output_file"
# Scan Applications folder
echo "Scanning /Applications..."
find /Applications -maxdepth 2 -name "*.app" -type d | while read app_path; do
app_name=$(basename "$app_path" .app)
# Get last opened time
datetime=$(mdls "$app_path" -name kMDItemLastUsedDate 2>/dev/null | awk '{print $3,$4}')
if [ "$datetime" = "(null)" ] || [ -z "$datetime" ]; then
last_opened="Never opened"
else
last_opened=$(date -jf "%Y-%m-%d %H:%M:%S %z" "$datetime +0000" +"%Y-%m-%d %H:%M:%S" 2>/dev/null)
if [ $? -ne 0 ]; then
last_opened="$datetime (UTC)"
fi
fi
# Get app version
app_version=$(mdls "$app_path" -name kMDItemVersion 2>/dev/null | awk -F'"' '{print $2}')
if [ "$app_version" = "(null)" ] || [ -z "$app_version" ]; then
app_version="Unknown"
fi
# Get app size
app_size=$(du -sh "$app_path" 2>/dev/null | cut -f1)
if [ -z "$app_size" ]; then
app_size="Unknown"
fi
echo "App: $app_name" >> "$output_file"
echo " Version: $app_version" >> "$output_file"
echo " Size: $app_size" >> "$output_file"
echo " Last Opened: $last_opened" >> "$output_file"
echo " Path: $app_path" >> "$output_file"
echo "" >> "$output_file"
# Progress indicator
echo "Processed: $app_name"
done
echo "Report saved to: $output_file"
}
# Execute scan
scan_applications
Recently Used Applications Filter
#!/bin/bash
# Filter applications by recent usage
# Usage: ./recent_apps.sh [days]
filter_recent_apps() {
local days_threshold=${1:-7} # Default to 7 days
local cutoff_date=$(date -v -${days_threshold}d +%Y-%m-%d)
echo "Applications used in the last $days_threshold days:"
echo "Cutoff date: $cutoff_date"
echo "=================================="
find /Applications -maxdepth 2 -name "*.app" -type d | while read app_path; do
app_name=$(basename "$app_path" .app)
# Get last opened time
datetime=$(mdls "$app_path" -name kMDItemLastUsedDate 2>/dev/null | awk '{print $3,$4}')
if [ "$datetime" != "(null)" ] && [ -n "$datetime" ]; then
# Convert to comparable format
app_date=$(echo "$datetime" | cut -d' ' -f1)
# Compare dates
if [[ "$app_date" > "$cutoff_date" ]]; then
local_time=$(date -jf "%Y-%m-%d %H:%M:%S %z" "$datetime +0000" +"%Y-%m-%d %H:%M:%S" 2>/dev/null)
if [ $? -eq 0 ]; then
echo "$app_name - Last used: $local_time"
else
echo "$app_name - Last used: $datetime (UTC)"
fi
fi
fi
done | sort
}
# Main execution
if [ $# -gt 1 ]; then
echo "Usage: $0 [days]"
echo "Example: $0 30 # Show apps used in last 30 days"
exit 1
fi
filter_recent_apps "$1"
Enterprise File Monitoring
Document Access Tracker
#!/bin/bash
# Track document access patterns across common file types
# Usage: ./document_tracker.sh "/path/to/documents"
track_document_access() {
local base_path=${1:-"$HOME/Documents"}
local output_file="document_access_$(date +%Y%m%d_%H%M%S).json"
echo "Tracking document access in: $base_path"
# Create JSON report
cat > "$output_file" << EOF
{
"report_info": {
"generated": "$(date -u +%Y-%m-%dT%H:%M:%SZ)",
"device": "$(scutil --get ComputerName)",
"user": "$(whoami)",
"scan_path": "$base_path",
"macfleet_version": "1.0"
},
"documents": [
EOF
local first_entry=true
# Common document types
local file_types=("*.pdf" "*.doc" "*.docx" "*.xls" "*.xlsx" "*.ppt" "*.pptx" "*.txt" "*.rtf" "*.pages" "*.numbers" "*.keynote")
for pattern in "${file_types[@]}"; do
find "$base_path" -name "$pattern" -type f 2>/dev/null | while read file_path; do
# Get file metadata
local filename=$(basename "$file_path")
local extension="${filename##*.}"
local size=$(stat -f%z "$file_path" 2>/dev/null || echo "0")
# Get timestamps
local last_used=$(mdls "$file_path" -name kMDItemLastUsedDate 2>/dev/null | awk '{print $3,$4}')
local created=$(mdls "$file_path" -name kMDItemContentCreationDate 2>/dev/null | awk '{print $3,$4}')
local modified=$(mdls "$file_path" -name kMDItemContentModificationDate 2>/dev/null | awk '{print $3,$4}')
# Format for JSON
if [ "$last_used" = "(null)" ] || [ -z "$last_used" ]; then
last_used="null"
else
last_used="\"$last_used\""
fi
if [ "$created" = "(null)" ] || [ -z "$created" ]; then
created="null"
else
created="\"$created\""
fi
if [ "$modified" = "(null)" ] || [ -z "$modified" ]; then
modified="null"
else
modified="\"$modified\""
fi
# Add comma if not first entry
if [ "$first_entry" = false ]; then
echo "," >> "$output_file"
fi
first_entry=false
# Add document entry
cat >> "$output_file" << EOF
{
"filename": "$filename",
"extension": "$extension",
"size_bytes": $size,
"path": "$file_path",
"last_used": $last_used,
"created": $created,
"modified": $modified
}EOF
done
done
# Close JSON
cat >> "$output_file" << EOF
]
}
EOF
echo "Document access report saved to: $output_file"
}
# Execute tracking
track_document_access "$1"
Multi-User File Access Monitor
#!/bin/bash
# Monitor file access across multiple user accounts
# Usage: ./multi_user_monitor.sh
monitor_multi_user_access() {
local report_file="multi_user_access_$(date +%Y%m%d_%H%M%S).txt"
echo "MacFleet Multi-User File Access Report" > "$report_file"
echo "Generated: $(date)" >> "$report_file"
echo "Device: $(scutil --get ComputerName)" >> "$report_file"
echo "=========================================" >> "$report_file"
echo "" >> "$report_file"
# Get all user home directories
local users=($(dscl . -list /Users | grep -v "^_" | grep -v "daemon" | grep -v "nobody" | grep -v "root"))
for user in "${users[@]}"; do
local user_home=$(dscl . -read /Users/$user NFSHomeDirectory | awk '{print $2}')
if [ -d "$user_home" ]; then
echo "User: $user ($user_home)" >> "$report_file"
echo "--------------------------------" >> "$report_file"
# Check Documents folder
if [ -d "$user_home/Documents" ]; then
echo "Recently accessed documents:" >> "$report_file"
find "$user_home/Documents" -type f -name "*.pdf" -o -name "*.doc*" -o -name "*.xls*" -o -name "*.ppt*" 2>/dev/null | head -10 | while read file_path; do
local filename=$(basename "$file_path")
local last_used=$(mdls "$file_path" -name kMDItemLastUsedDate 2>/dev/null | awk '{print $3,$4}')
if [ "$last_used" != "(null)" ] && [ -n "$last_used" ]; then
echo " - $filename (Last used: $last_used)" >> "$report_file"
fi
done
fi
# Check Downloads folder
if [ -d "$user_home/Downloads" ]; then
echo "Recent downloads:" >> "$report_file"
find "$user_home/Downloads" -type f -mtime -7 2>/dev/null | head -5 | while read file_path; do
local filename=$(basename "$file_path")
local mod_time=$(stat -f "%Sm" -t "%Y-%m-%d %H:%M:%S" "$file_path" 2>/dev/null)
echo " - $filename (Downloaded: $mod_time)" >> "$report_file"
done
fi
echo "" >> "$report_file"
fi
done
echo "Multi-user access report saved to: $report_file"
}
# Execute monitoring
monitor_multi_user_access
Remote Fleet Management
Centralized Usage Collector
#!/bin/bash
# Collect usage data for remote fleet management
# Usage: ./fleet_collector.sh
collect_fleet_data() {
local device_id=$(system_profiler SPHardwareDataType | grep "Hardware UUID" | awk '{print $3}')
local computer_name=$(scutil --get ComputerName)
local timestamp=$(date -u +%Y-%m-%dT%H:%M:%SZ)
# Create comprehensive report
local report_file="fleet_usage_${computer_name}_$(date +%Y%m%d_%H%M%S).json"
cat > "$report_file" << EOF
{
"device_info": {
"device_id": "$device_id",
"computer_name": "$computer_name",
"timestamp": "$timestamp",
"os_version": "$(sw_vers -productVersion)",
"uptime": "$(uptime | awk '{print $3,$4}' | sed 's/,//')"
},
"application_usage": [
EOF
# Collect top applications
local first_app=true
find /Applications -maxdepth 2 -name "*.app" -type d | head -20 | while read app_path; do
local app_name=$(basename "$app_path" .app)
local last_used=$(mdls "$app_path" -name kMDItemLastUsedDate 2>/dev/null | awk '{print $3,$4}')
local version=$(mdls "$app_path" -name kMDItemVersion 2>/dev/null | awk -F'"' '{print $2}')
if [ "$first_app" = false ]; then
echo "," >> "$report_file"
fi
first_app=false
# Format values for JSON
if [ "$last_used" = "(null)" ] || [ -z "$last_used" ]; then
last_used="null"
else
last_used="\"$last_used\""
fi
if [ "$version" = "(null)" ] || [ -z "$version" ]; then
version="null"
else
version="\"$version\""
fi
cat >> "$report_file" << EOF
{
"name": "$app_name",
"version": $version,
"last_used": $last_used,
"path": "$app_path"
}EOF
done
cat >> "$report_file" << EOF
],
"system_stats": {
"total_applications": $(find /Applications -maxdepth 2 -name "*.app" -type d | wc -l | tr -d ' '),
"disk_usage": "$(df -h / | tail -1 | awk '{print $5}')",
"memory_usage": "$(vm_stat | grep "Pages active" | awk '{print $3}' | sed 's/\.//')"
}
}
EOF
echo "Fleet data collected in: $report_file"
# Optional: Send to central server
# curl -X POST -H "Content-Type: application/json" -d @"$report_file" "https://your-fleet-server.com/api/usage"
}
# Execute collection
collect_fleet_data
Automated Usage Monitoring
#!/bin/bash
# Automated usage monitoring with alerts
# Usage: ./usage_monitor.sh
setup_monitoring() {
local log_file="/var/log/macfleet_usage.log"
local config_file="/etc/macfleet/usage_config.conf"
# Create configuration if it doesn't exist
if [ ! -f "$config_file" ]; then
sudo mkdir -p /etc/macfleet
sudo cat > "$config_file" << EOF
# MacFleet Usage Monitoring Configuration
MONITOR_INTERVAL=3600 # Check every hour
ALERT_THRESHOLD=30 # Alert if app unused for 30 days
REPORT_INTERVAL=86400 # Generate report daily
CLEANUP_THRESHOLD=90 # Flag apps unused for 90 days
EOF
fi
# Source configuration
source "$config_file"
# Create monitoring script
cat > "/tmp/macfleet_monitor.sh" << EOF
#!/bin/bash
# MacFleet Usage Monitor
log_usage() {
local timestamp=\$(date -u +%Y-%m-%dT%H:%M:%SZ)
local computer_name=\$(scutil --get ComputerName)
echo "[\$timestamp] Starting usage check on \$computer_name" >> "$log_file"
# Check for unused applications
find /Applications -maxdepth 2 -name "*.app" -type d | while read app_path; do
local app_name=\$(basename "\$app_path" .app)
local last_used=\$(mdls "\$app_path" -name kMDItemLastUsedDate 2>/dev/null | awk '{print \$3,\$4}')
if [ "\$last_used" != "(null)" ] && [ -n "\$last_used" ]; then
local days_since=\$(( (\$(date +%s) - \$(date -jf "%Y-%m-%d %H:%M:%S %z" "\$last_used +0000" +%s)) / 86400 ))
if [ \$days_since -gt $ALERT_THRESHOLD ]; then
echo "[\$timestamp] ALERT: \$app_name unused for \$days_since days" >> "$log_file"
fi
if [ \$days_since -gt $CLEANUP_THRESHOLD ]; then
echo "[\$timestamp] CLEANUP: \$app_name flagged for removal (\$days_since days)" >> "$log_file"
fi
fi
done
echo "[\$timestamp] Usage check completed" >> "$log_file"
}
# Run monitoring
log_usage
EOF
chmod +x "/tmp/macfleet_monitor.sh"
# Set up launchd for automated monitoring
cat > "/tmp/com.macfleet.usage.monitor.plist" << EOF
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Label</key>
<string>com.macfleet.usage.monitor</string>
<key>ProgramArguments</key>
<array>
<string>/tmp/macfleet_monitor.sh</string>
</array>
<key>StartInterval</key>
<integer>$MONITOR_INTERVAL</integer>
<key>RunAtLoad</key>
<true/>
</dict>
</plist>
EOF
echo "Monitoring setup complete. Use 'sudo launchctl load /tmp/com.macfleet.usage.monitor.plist' to start."
}
# Execute setup
setup_monitoring
Best Practices for Mac Fleet Management
1. Performance Optimization
- Monitor resource-intensive applications
- Track applications that haven't been used recently
- Identify opportunities for app cleanup
- Optimize system performance through usage insights
2. Security and Compliance
- Track file access for security audits
- Monitor document handling compliance
- Identify unauthorized file access patterns
- Maintain access logs for compliance requirements
3. License Management
- Monitor software usage for license optimization
- Track application deployment effectiveness
- Identify unused licenses for cost savings
- Ensure compliance with software licensing agreements
4. User Experience
- Understand user behavior patterns
- Optimize application deployment strategies
- Improve user productivity through usage insights
- Customize device configurations based on usage
5. Maintenance and Cleanup
- Identify unused applications for removal
- Clean up temporary files and caches
- Optimize storage usage
- Schedule regular maintenance based on usage patterns
Troubleshooting Common Issues
1. Permission Errors
# Fix permission issues
sudo chown -R $(whoami) /Users/$(whoami)/Documents
sudo chmod -R 755 /Users/$(whoami)/Documents
2. Metadata Not Available
# Check if Spotlight indexing is enabled
mdutil -s /
# Re-index if necessary
sudo mdutil -i on /
sudo mdutil -E /
3. Script Execution Issues
# Make script executable
chmod +x script_name.sh
# Check bash version
bash --version
# Use full path for commands
/usr/bin/mdls instead of mdls
4. Date Format Issues
# Handle different date formats
date -jf "%Y-%m-%d %H:%M:%S %z" "2024-01-01 12:00:00 +0000" +"%Y-%m-%d %H:%M:%S"
Conclusion
Monitoring application and file usage is essential for effective Mac fleet management. These scripts provide comprehensive insights into user behavior, system performance, and security compliance. Regular monitoring helps optimize resources, improve user experience, and maintain security standards across your Mac fleet.
Remember to test scripts in a controlled environment before deploying them across your fleet, and always follow your organization's security and privacy policies when monitoring user activity.