Skip to main content
KBY Technologies Logo
The Ops Playbook

Ending Application-In-Use Update Failures on Managed Macs

Stop repetitive helpdesk tickets by automating locked-app detection and force quit before Mac software updates run via Jamf Pro or Intune.

Ending Application-In-Use Update Failures on Managed Macs
Isla Morgan 19 July 202610 min read Tier L2 35 min setupApplication Lifecycle

Operational brief

Operational outcome

Stop repetitive helpdesk tickets by automating locked-app detection and force quit before Mac software updates run via Jamf Pro or Intune.

Target Tier

L2 support

Implement within an approved test scope before wider deployment.

Time to set aside

About 35 minutes

Includes configuration, validation and deployment.

Share
Tutorial navigation
Innovation note
Verify your deployment with small rollout groups and enforce least-privilege administrative access when implementing these automations.

Every fleet running Jamf Pro or Microsoft Intune has the same recurring ticket: a managed application update is pushed, the installer exits with a non-zero code, and the helpdesk queue fills with “update failed” tickets for Zoom, Chrome, Slack, or any app that locks its own binary while running. The root cause is almost always the same: the application was open when the installer tried to replace it.

The Old Way: a technician receives the failure ticket, remotely connects via Apple Remote Desktop or Jamf Remote, asks the user to save their work, force quits the application manually, and re-runs the policy by hand. This is repeated hundreds of times per patch cycle across a large fleet, consumes technician hours that scale linearly with headcount, and frustrates users who lose unsaved work when a technician force-quits without warning.

The New Way: a pre-flight guard script runs before the installer payload, detects the locking process, gives the user a bounded, self-service deferral window with a countdown notification, force quits safely once the window expires, and logs every decision as a structured evidence trail. The policy only proceeds to install once the process is confirmed dead. No technician touches the device, and every outcome is reportable as a compliance signal in both Jamf Pro and Intune.

#Prerequisites and Permissions

  • Jamf Pro 10.x or later with Script and Policy management rights (minimum role: Jamf Pro custom privilege set with Create/Read/Update Scripts and Create/Read/Update Policies, not full administrator).
  • Devices enrolled via Apple Business Manager with a Jamf Pro or Intune MDM profile that grants a Privacy Preferences Policy Control (PPPC) payload allowing the management agent to execute scripts with root privileges.
  • jamfHelper binary present at its default path (installed automatically with the Jamf Pro management framework).
  • For Intune-managed Macs: Shell script policies enabled under Devices > Scripts, and Custom Compliance policies enabled under Devices > Compliance policies (requires the Intune Company Portal 5.2404.0 or later on the endpoint).
  • Test scope: a single smart group or Azure AD dynamic group of no more than 25 pilot devices before fleet-wide scoping.
  • Blast radius: limited to the targeted application’s process and its own installer payload. The script never touches unrelated processes or files.
Managed Mac application update workflow
Validate the update guard with a small managed-Mac pilot group before wider deployment.

#Implementation Steps

#Step 1: Build the pre-flight guard script

Action: Create a bash script that checks for the target process, offers a bounded deferral, then force quits before handing control back to the policy.

1#!/bin/bash
2
3# app-update-guard.sh
4# Prevents failed "Application in Use" installs by detecting locking processes,
5# giving the user a bounded deferral window, then force-quitting before the
6# installer payload runs.
7
8APP_PROCESS="zoom.us"
9APP_DISPLAY_NAME="Zoom"
10MAX_DEFERRALS=3
11DEFER_PLIST="/Library/Application Support/OpsPlaybook/zoom_defer_count.plist"
12LOG_FILE="/var/log/mac-ops/app-update-guard.log"
13JAMF_HELPER="/Library/Application Support/JAMF/bin/jamfHelper.app/Contents/MacOS/jamfHelper"
14
15mkdir -p "/var/log/mac-ops"
16mkdir -p "/Library/Application Support/OpsPlaybook"
17
18log() {
19  echo "$(date '+%Y-%m-%d %H:%M:%S') $1" >> "$LOG_FILE"
20}
21
22get_defer_count() {
23  if [ -f "$DEFER_PLIST" ]; then
24    /usr/libexec/PlistBuddy -c "Print :DeferCount" "$DEFER_PLIST" 2>/dev/null || echo 0
25  else
26    echo 0
27  fi
28}
29
30set_defer_count() {
31  /usr/libexec/PlistBuddy -c "Delete :DeferCount" "$DEFER_PLIST" 2>/dev/null
32  /usr/libexec/PlistBuddy -c "Add :DeferCount integer $1" "$DEFER_PLIST" 2>/dev/null || 
33  /usr/libexec/PlistBuddy -c "Add :DeferCount integer $1" "$DEFER_PLIST"
34}
35
36PID_COUNT=$(pgrep -x "$APP_PROCESS" | wc -l | tr -d ' ')
37
38if [ "$PID_COUNT" -eq 0 ]; then
39  log "No blocking process found for $APP_DISPLAY_NAME. Proceeding with install."
40  exit 0
41fi
42
43DEFER_COUNT=$(get_defer_count)
44
45if [ "$DEFER_COUNT" -lt "$MAX_DEFERRALS" ]; then
46  NEW_COUNT=$((DEFER_COUNT + 1))
47  set_defer_count "$NEW_COUNT"
48  log "Deferral $NEW_COUNT of $MAX_DEFERRALS offered to user for $APP_DISPLAY_NAME."
49
50  "$JAMF_HELPER" -windowType utility -title "Update Ready: $APP_DISPLAY_NAME" \
51    -heading "Please save your work" \
52    -description "$APP_DISPLAY_NAME needs to close to finish an update. It will close automatically in 5 minutes. Save your work now." \
53    -button1 "OK" -defaultButton 1 -timeout 300 &
54
55  sleep 300
56fi
57
58RUNNING_AFTER_WAIT=$(pgrep -x "$APP_PROCESS" | wc -l | tr -d ' ')
59
60if [ "$RUNNING_AFTER_WAIT" -gt 0 ]; then
61  log "Force quitting $APP_DISPLAY_NAME after deferral window."
62  pkill -x "$APP_PROCESS"
63  sleep 5
64fi
65
66STILL_RUNNING=$(pgrep -x "$APP_PROCESS" | wc -l | tr -d ' ')
67
68if [ "$STILL_RUNNING" -gt 0 ]; then
69  log "ERROR: $APP_DISPLAY_NAME still running after force quit. Aborting install."
70  exit 1
71fi
72
73log "$APP_DISPLAY_NAME successfully cleared. Handing off to installer payload."
74exit 0

Expected result: exit code 0 when the process is absent or successfully cleared; exit code 1 when the process cannot be terminated.

Evidence: a timestamped entry in /var/log/mac-ops/app-update-guard.log for every run.

12025-03-11 09:14:02 Deferral 1 of 3 offered to user for Zoom.
22025-03-11 09:19:03 Zoom successfully cleared. Handing off to installer payload.

#Step 2: Upload the script to Jamf Pro

Action: Add the script under Settings > Computer Management > Scripts, priority set to Before.

Expected result: the script appears as an available payload in the policy builder’s Scripts tab.

Evidence: script ID visible in the Jamf Pro script list and referenced in the policy log.

#Step 3: Chain the guard script ahead of the installer payload

Action: Build a policy with two payloads in order: (1) the guard script, priority Before, (2) the Installomator-based install command or PKG payload, priority After. Scope to the pilot smart group first.

Expected result: Jamf Pro halts the policy and records a failure if the guard script exits 1, so the installer payload never runs against a locked binary.

Evidence: Policy Logs show “Script completed successfully” or “Script failed” against the guard script step, independent of the installer step status.

#Step 4: Configure the smart group trigger

Action: Create a Jamf Pro smart group using the criterion Application Title is Zoom.app and Application Version is not the current target version, and scope the policy to run on recurring check-in.

Expected result: only devices with an out-of-date, installed copy of the app are targeted, avoiding unnecessary script executions on devices without the app.

Evidence: smart group membership count matches the expected out-of-date population reported in Jamf Pro inventory.

macOS application update automation and verification
Use policy and compliance evidence to confirm the application closed before installation began.

#Step 5: Mirror the logic for Intune-managed Macs

Action: Deploy the same guard script as a Shell Script policy under Devices > Scripts, run as root, with a script frequency of “Not configured” so it only runs once per assignment cycle immediately before the app deployment’s install command triggers.

Expected result: the script executes on Intune check-in and produces the same log file and exit codes as the Jamf Pro version.

Evidence: script run status “Success” visible in the Intune admin centre under the script’s device status report.

#Step 6: Publish a compliance signal for reporting

Action: Add a companion shell script that reports the current deferral count as a custom compliance signal.

1#!/bin/bash
2# Intune custom compliance signal for app-update-guard state.
3DEFER_PLIST="/Library/Application Support/OpsPlaybook/zoom_defer_count.plist"
4
5if [ -f "$DEFER_PLIST" ]; then
6  DEFER_COUNT=$(/usr/libexec/PlistBuddy -c "Print :DeferCount" "$DEFER_PLIST" 2>/dev/null)
7else
8  DEFER_COUNT=0
9fi
10
11printf '{"AppUpdateGuardDeferrals": %s}\n' "$DEFER_COUNT"
12exit 0

Action: upload the matching JSON schema against a Custom Compliance policy in Intune.

1{
2  "Rules": [
3    {
4      "SettingName": "AppUpdateGuardDeferrals",
5      "Operator": "LessEquals",
6      "DataType": "Int64",
7      "Operand": "3",
8      "MoreInfoUrl": "https://learn.microsoft.com/en-us/mem/intune/protect/compliance-custom-script",
9      "RemediationStrings": [
10        {
11          "Language": "en_US",
12          "Title": "Application update deferral limit reached",
13          "Description": "Quit the pending application to allow the managed update to complete."
14        }
15      ]
16    }
17  ]
18}

Expected result: devices exceeding the deferral limit are flagged non-compliant, feeding conditional access policies if configured.

Evidence: compliance state “Not compliant” with reason “AppUpdateGuardDeferrals” visible in the Intune device compliance report.

#Step 7: Wire a failure webhook for escalation

Action: Configure a Jamf Pro webhook subscription on the ComputerPolicyFinished event, filtered to the guard policy ID, posting to an internal automation endpoint that opens a ticket only when the exit code is non-zero.

1{
2  "webhook": {
3    "id": 42,
4    "name": "AppUpdateGuard-PolicyFailed",
5    "url": "https://ops-automation.kby.example.com/webhooks/jamf/policy-failed",
6    "contentType": "application/json",
7    "event": "JSSEvent.PolicyCompleted"
8  },
9  "event": {
10    "policyId": 187,
11    "policyName": "Zoom - Update - Production",
12    "deviceId": 5521,
13    "deviceName": "MAC-FIN-0231",
14    "username": "j.smith",
15    "status": "Failed",
16    "exitCode": 1,
17    "lastScriptLog": "ERROR: Zoom still running after force quit. Aborting install.",
18    "deferralCount": 3
19  }
20}

Expected result: only genuine escalations (process could not be cleared after the full deferral cycle) reach the ticketing system, not every deferred update.

Evidence: ticket created with the device name, username, and last log line pre-populated from the webhook payload, requiring no manual triage step.

#Step 8: Expand the pattern across the app catalogue

Action: Parameterise the script (APP_PROCESS and APP_DISPLAY_NAME as Jamf Pro script parameters 4 and 5, or Intune script arguments) and reuse the same policy structure for every application in the update catalogue that is known to lock its binary during installs, such as Chrome, Slack, and Microsoft Teams.

Expected result: a single reusable script and policy template services the entire third-party app catalogue instead of one-off scripts per title.

Evidence: Jamf Pro policy history shows the same script ID referenced across multiple policies with different parameter values.

#Verification and Expected Evidence

  1. Confirm the guard script log file exists on a pilot device after a scoped policy run: /var/log/mac-ops/app-update-guard.log contains a completed entry with exit code 0.
  2. Confirm the installer payload only executed after the guard step by checking the Jamf Pro Policy Log timestamp ordering for the two payload steps.
  3. Confirm the deferral dialog appeared on a live test device by capturing a screenshot or screen recording during the pilot run.
  4. Confirm the Intune script run report shows “Success” and the compliance report shows the correct deferral count for the same pilot device.
  5. Confirm the webhook fired exactly once for a deliberately forced failure (simulate by relaunching the app immediately after force quit) and that a test ticket was created with correct field mapping.

#Rollback

If the guard script causes unexpected side effects, such as blocking installs indefinitely due to a process name mismatch, disable the policy scope immediately in Jamf Pro (set scope to no devices) or disable the Intune assignment. Remove the deferral plist from affected devices with a single cleanup script pushed as an emergency policy:

1#!/bin/bash
2rm -f "/Library/Application Support/OpsPlaybook/zoom_defer_count.plist"
3exit 0

Because the guard script never modifies application binaries, installer receipts, or system settings, rollback carries no data-loss risk. The only stateful artefact is the deferral counter plist, which is safe to delete at any time.

#Failure and Escalation Conditions

  • Wake a human technician when the webhook fires more than five times in one hour across the fleet, indicating a systemic issue such as a process name change after an app update.
  • Escalate to L2 if a single device reports three consecutive failed guard runs (visible in the compliance report as a sustained deferral count of 3 with no reduction over 48 hours).
  • Escalate to L3 if the guard script itself fails to execute (Jamf Pro reports “Script error” rather than a controlled exit code), which typically indicates a PPPC profile scope gap or missing root execution rights.
  • Monitoring signal: a Jamf Pro Advanced Computer Search saved as “Guard Script Failures – Last 7 Days” filtered on the last script exit code, reviewed daily by the automation owner.

#Measuring Ticket Deflection

Baseline the current volume of “application in use” or “update failed” tickets tagged against the target application over the prior 90 days. After deployment, track three figures monthly: the count of guard script executions that exited 0 without any technician involvement, the count of genuine escalations that reached the ticketing system, and the ticket volume for the same application category. A successful rollout typically shows escalations at under 5 percent of total guard executions, with the corresponding ticket category dropping by 80 percent or more within the first full patch cycle, since only genuinely stuck devices ever generate a ticket.

Evidence trail

Sources and verification

Vendor documentation and external technical references used to verify this playbook.

  1. 01Apple Platform Deployment guidesupport.apple.com
  2. 02Apple Device Management documentationdeveloper.apple.com
  3. 03Microsoft Intune custom compliance policy documentationlearn.microsoft.com
  4. 04Jamf Pro Policies documentationlearn.jamf.com

Advanced suggested reading

Ready to go deeper?

These articles come from KBY’s senior engineering editorial. Expect denser architecture, fewer introductory explanations and deeper production trade-offs.

Leaving Ops Playbook
Reader Interaction

Comments

Add a thoughtful note on Ending Application-In-Use Update Failures on Managed Macs. Comments are checked for spam and held for moderation before appearing.

Loading comments...

Loading anti-spam check...