← Back to articles Azure

Intune Mac PKG Update Error 0x87D30145 Fix

Intune Mac PKG Update Error 0x87D30145 Fix

The Error and Why It Happens

If you've pushed a PKG update through Intune on macOS and hit this in the device status:

"The app is running and could not be updated. The update will be tried the next time the device syncs. (0x87D30145)"

You already know the frustration. The device syncs again, same result. The app is still running, the update never lands, and you're staring at a wall of red in the Intune console.

Error code 0x87D30145 maps to APPS_INSTALL_ERROR_APP_CURRENTLY_RUNNING. The Intune Management Extension (IME) on macOS detects that the process associated with the application is active and refuses to overwrite it mid-run. This is not a bug — it's an intentional guard. The problem is that for always-on tools like GlobalProtect, Zscaler, or similar VPN/security agents, the process is always running. The update never gets a window unless you create one manually.

The IME on macOS performs a process name check before invoking the installer. If it finds a match, it backs off and queues a retry. Those retries don't help when the app restarts itself on exit, which is exactly what GlobalProtect does via its launchd daemon.

The Real Fix: Preinstall Script

The cleanest production solution is a preinstall script delivered via Intune Shell Scripts, or better — bundled directly as a preinstall script inside a custom PKG wrapper. The goal is simple: kill the running process (and optionally unload the launchd service) before the new PKG payload gets laid down.

You have two delivery paths here:

  • Option A: Deploy a separate Intune Shell Script that runs before the PKG, using assignment filters or sequencing logic
  • Option B: Wrap your PKG in a custom outer PKG that contains a preinstall script — this is the more reliable approach and keeps everything atomic

Option A: Intune Shell Script (Quick and Dirty)

If you don't want to repackage, you can push a Shell Script through Devices > macOS > Shell Scripts in the Intune portal. Assign it to the same group as your PKG, set it to run as root, and push it ahead of the app deployment. The timing isn't guaranteed to be atomic, but in practice it works because the process stays dead long enough for the PKG to install — unless the daemon restarts it instantly.

For GlobalProtect specifically, you need to unload the launchd daemon, not just kill the UI process:

#!/bin/bash
# Kill GlobalProtect processes and unload daemons before PKG update
# msendpoint.com

# Kill the UI agent
/usr/bin/killall "GlobalProtect" 2>/dev/null

# Unload the PanGPS daemon (runs as root)
/bin/launchctl unload /Library/LaunchDaemons/com.paloaltonetworks.gp.pangpsd.plist 2>/dev/null

# Unload the PanGPA agent
/bin/launchctl unload /Library/LaunchAgents/com.paloaltonetworks.gp.pangpa.plist 2>/dev/null

# Give it a moment
sleep 3

exit 0

Set Run script as signed-in user to No (runs as root), and Retry if script fails to your preference. Max frequency should be set to match your update cadence — don't leave it running weekly after the update is done.

Option B: Custom PKG Wrapper with Preinstall Script (Recommended)

This is what I'd do in production. You take the original GlobalProtect PKG, extract it or wrap it, and bundle a preinstall script that fires automatically before the payload installs. macOS PKG installers support pre and postinstall scripts natively — Intune's IME calls /usr/sbin/installer under the hood, which respects them fully.

The tool I use for this is munkipkg or a simple flat-package construction with pkgbuild and productbuild. Here's the manual approach:

First, create your preinstall script:

#!/bin/bash
# preinstall script for GlobalProtect wrapper PKG
# Placed at: ./scripts/preinstall

# Terminate GlobalProtect UI
/usr/bin/killall "GlobalProtect" 2>/dev/null

# Unload system daemon
if /bin/launchctl list | grep -q "com.paloaltonetworks.gp.pangpsd"; then
    /bin/launchctl unload /Library/LaunchDaemons/com.paloaltonetworks.gp.pangpsd.plist 2>/dev/null
fi

# Unload user agent
if /bin/launchctl list | grep -q "com.paloaltonetworks.gp.pangpa"; then
    /bin/launchctl unload /Library/LaunchAgents/com.paloaltonetworks.gp.pangpa.plist 2>/dev/null
fi

sleep 2
exit 0

Make it executable: chmod +x ./scripts/preinstall

Then build the component package and wrap it:

# Step 1: Build a component pkg from the extracted app payload
# (Skip if you're embedding the original PKG as a payload)
pkgbuild \
  --identifier "com.yourorg.globalprotect-wrapper" \
  --version "6.3.0" \
  --scripts ./scripts \
  --nopayload \
  PreinstallOnly.pkg

# Step 2: Create a distribution package combining preinstall + original PKG
productbuild \
  --distribution ./Distribution.xml \
  --package-path . \
  GlobalProtect-6.3.0-Wrapped.pkg

Your Distribution.xml references both PreinstallOnly.pkg and the original GlobalProtect PKG in sequence. The preinstall fires, daemons unload, then the real installer runs. Upload the wrapped PKG to Intune as a new macOS LOB app.

Detection — Make Sure the Old Version Doesn't Ghost You

One thing that bites people: after a failed 0x87D30145 cycle, the Intune console sometimes reports the old version as installed because the detection rule still matches. When you upload a new PKG version, update your detection rule to check for the new version string explicitly. For GlobalProtect, the receipt-based detection works well:

# Detection script for GlobalProtect version check
# Use as a custom script detection rule in Intune

#!/bin/bash
REQUIRED_VERSION="6.3.0"
INSTALLED_VERSION=$(/usr/bin/defaults read /Applications/GlobalProtect.app/Contents/Info.plist CFBundleShortVersionString 2>/dev/null)

if [ "$INSTALLED_VERSION" = "$REQUIRED_VERSION" ]; then
    echo "Installed: $INSTALLED_VERSION"
    exit 0
else
    exit 1
fi

Set the detection rule type to Script, check Use script output to detect compliance if you want to capture the echo, or just rely on exit codes. Exit 0 = detected, exit 1 = not detected, Intune reinstalls.

Why the "Next Sync" Retry Never Works for Always-On Agents

The Intune Management Extension checks in roughly every 8 hours on macOS, or on-demand via the Company Portal. When it retries the PKG install after 0x87D30145, it runs the same process check again. If the daemon reloaded itself (which GlobalProtect's launchd entries are configured to do with KeepAlive = true), the process is back. The IME backs off again. You're in a loop.

This is the core reason why just waiting for a resync never resolves the issue for VPN agents. The only escape is active intervention — either through a preinstall script, a separate shell script that runs first, or scheduling a maintenance window where end users are prompted to disconnect and the daemon is briefly unloaded via script before the install fires.

What About PPPC / TCC for the Script?

If you're running the shell script via Intune and the target processes have TCC protections (less common for VPN daemons, but worth noting), killall from a root context generally doesn't require PPPC approval. The launchd unload calls also operate at the system level without TCC involvement. You shouldn't need to push a custom PPPC profile just to kill GlobalProtect — but if you're dealing with a different app that has stronger sandboxing, keep that in mind.

Practical Advice From Production

A few things learned the hard way:

  • Always version-stamp your PKG identifier when uploading to Intune. Reusing the same app entry and uploading a new file sometimes causes the IME to skip the install if it thinks the app is already at the target version based on cached state.
  • Test your preinstall script manually on a test Mac first via sudo bash preinstall before wrapping it. Sounds obvious, but a typo in the plist path will silently succeed (because 2>/dev/null swallows errors) while doing nothing.
  • If you're using Palo Alto's official PKG, check whether a newer version ships a preinstall already — some vendors have started including these specifically because Intune deployments are common. GlobalProtect 6.x packages as of early 2026 still don't include one, but worth checking release notes.
  • For any security agent (VPN, EDR, DLP), build the kill-and-unload wrapper pattern once and reuse it. The 0x87D30145 problem isn't unique to GlobalProtect — you'll hit it with CrowdStrike Falcon, Zscaler ZIA, and any other always-on agent.

Summary

Error 0x87D30145 is straightforward once you understand what the IME is doing. It's not going to update a running process, and for always-on agents it will never get a natural window. The fix is equally straightforward: create that window yourself with a preinstall script. The wrapped PKG approach is the most reliable because it's atomic — the kill and the install happen in the same installer invocation, with no timing gap. If wrapping is too much overhead for a one-off update, the Intune Shell Script approach works fine. Just make sure you're cleaning up after yourself and not leaving a daemon-killing script scheduled to run indefinitely on your fleet.

Was this article helpful?

🎓 Ready to go deeper?

Practice real MD-102 exam questions, get AI feedback on your weak areas, and fast-track your Intune certification.

Start Free Practice → Book a Session
Souhaiel Morhag
Souhaiel Morhag
Microsoft Endpoint & Modern Workplace Engineer

Souhaiel Morhag is a Microsoft Intune and endpoint management specialist with hands-on experience deploying and securing enterprise environments across Microsoft 365. He founded MSEndpoint.com to share practical, real-world guides for IT admins navigating Microsoft technologies — and built the MSEndpoint Academy at app.msendpoint.com/academy, a dedicated learning platform for professionals preparing for the MD-102 (Microsoft 365 Endpoint Administrator) certification. Through in-depth articles and AI-powered practice exams, Souhaiel helps IT teams move faster and certify with confidence.

Related Articles

Popular on MSEndpoint