macOS屏幕锁定时launchDaemon无法运行脚本的解决求助
解决受管理macOS设备上LaunchDaemon锁屏后不运行的问题
先解决日志无输出问题(核心排查基础)
没有日志就无法定位问题,先给监控LaunchDaemon添加日志配置:
- 在plist中加入日志路径配置:
<key>StandardOutPath</key> <string>/var/log/admin_monitor.log</string> <key>StandardErrorPath</key> <string>/var/log/admin_monitor_error.log</string> - 给日志文件设置正确权限,避免写入失败:
sudo chown root:wheel /var/log/admin_monitor*.log - 重启LaunchDaemon后锁屏测试,查看日志内容:
sudo launchctl unload /Library/LaunchDaemons/com.yourcompany.adminmonitor.plist sudo launchctl load /Library/LaunchDaemons/com.yourcompany.adminmonitor.plist
检查LaunchDaemon配置合法性
受管理设备对LaunchDaemon配置要求严格,确保以下几点:
- plist必须放在
/Library/LaunchDaemons/目录,文件名采用反向域名格式(例如com.yourcompany.adminmonitor.plist) - plist权限必须为
root:wheel 644,执行命令修正:sudo chmod 644 /Library/LaunchDaemons/com.yourcompany.adminmonitor.plist sudo chown root:wheel /Library/LaunchDaemons/com.yourcompany.adminmonitor.plist - 配置正确的运行触发规则:如果是持续监控,用
StartInterval定时检查(例如每分钟一次),不要依赖用户会话相关触发条件:<key>RunAtLoad</key> <true/> <key>StartInterval</key> <integer>60</integer>
修正脚本的用户上下文依赖问题
锁屏后用户会话会切换,脚本中如果使用依赖当前登录用户的命令(如consoleuser、who)会失效,需直接从系统数据库读取用户信息:
- 用
dscl命令获取所有非系统账户(UniqueID ≥500),并检查管理员组:# 获取非系统用户列表 users=$(dscl . -list /Users UniqueID | awk '$2 >= 500 {print $1}') for user in $users; do # 跳过系统服务账户(以_开头) [[ "$user" =~ ^_ ]] && continue # 检查是否在admin组 if dscl . -read /Groups/admin GroupMembership | grep -q "$user"; then # 执行创建定时移除Daemon的逻辑 create_remove_daemon "$user" fi done
定时移除Daemon的正确配置
创建的移除权限Daemon要确保一次性运行、不依赖用户会话:
- 模板plist设置
KeepAlive为false,OnDemand为true,避免重复运行 - 用
StartInterval设置900秒(15分钟)延迟,不要在脚本中使用sleep(sleep会占用进程,可能被系统回收) - 同样给移除Daemon添加日志配置,方便排查执行情况
排查MDM限制
受管理设备可能存在MDM策略限制:
- 联系MDM管理员确认是否有“禁止后台进程锁屏时运行”的策略
- 确保脚本和LaunchDaemon路径在SIP允许范围内(
/Library/LaunchDaemons/和/Library/Scripts/均为合法路径)
示例完整配置
监控LaunchDaemon plist(/Library/LaunchDaemons/com.yourcompany.adminmonitor.plist)
<?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.yourcompany.adminmonitor</string> <key>ProgramArguments</key> <array> <string>/Library/Scripts/admin_monitor.sh</string> </array> <key>RunAtLoad</key> <true/> <key>StartInterval</key> <integer>60</integer> <key>StandardOutPath</key> <string>/var/log/admin_monitor.log</string> <key>StandardErrorPath</key> <string>/var/log/admin_monitor_error.log</string> <key>User</key> <string>root</string> <key>GroupName</key> <string>wheel</string> </dict> </plist>
监控脚本(/Library/Scripts/admin_monitor.sh)
#!/bin/bash LOG_FILE="/var/log/admin_monitor.log" REMOVE_TEMPLATE="/Library/Scripts/remove_admin_template.plist" log() { echo "$(date '+%Y-%m-%d %H:%M:%S') - $1" >> "$LOG_FILE" } create_remove_daemon() { local user="$1" local daemon_label="com.yourcompany.removeadmin.$user" local daemon_path="/Library/LaunchDaemons/$daemon_label.plist" [ -f "$daemon_path" ] && { log "Remove daemon for $user exists, skip"; return; } cp "$REMOVE_TEMPLATE" "$daemon_path" sed -i '' "s/{{USER}}/$user/g" "$daemon_path" sed -i '' "s/{{LABEL}}/$daemon_label/g" "$daemon_path" chown root:wheel "$daemon_path" chmod 644 "$daemon_path" launchctl load "$daemon_path" log "Created remove daemon for $user" } check_admin_users() { log "Starting admin check" users=$(dscl . -list /Users UniqueID | awk '$2 >= 500 {print $1}') for user in $users; do [[ "$user" =~ ^_ ]] && continue if dscl . -read /Groups/admin GroupMembership | grep -q "$user"; then log "Found admin user: $user" create_remove_daemon "$user" else local daemon_path="/Library/LaunchDaemons/com.yourcompany.removeadmin.$user.plist" [ -f "$daemon_path" ] && { launchctl unload "$daemon_path"; rm "$daemon_path"; log "Removed daemon for $user"; } fi done log "Admin check finished" } check_admin_users
移除权限Daemon模板(/Library/Scripts/remove_admin_template.plist)
<?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>{{LABEL}}</string> <key>ProgramArguments</key> <array> <string>/usr/sbin/dseditgroup</string> <string>-o</string> <string>edit</string> <string>-d</string> <string>{{USER}}</string> <string>-t</string> <string>user</string> <string>admin</string> </array> <key>RunAtLoad</key> <true/> <key>StartInterval</key> <integer>900</integer> <key>StandardOutPath</key> <string>/var/log/remove_admin_{{USER}}.log</string> <key>StandardErrorPath</key> <string>/var/log/remove_admin_{{USER}}_error.log</string> <key>User</key> <string>root</string> <key>GroupName</key> <string>wheel</string> <key>KeepAlive</key> <false/> <key>OnDemand</key> <true/> </dict> </plist>
内容的提问来源于stack exchange,提问作者meatballbeam
相关产品推荐
相关产品推荐

