DEV Community

Cover image for The Updater Installed Outlook 83 Times, the Running Build Is Old
Mustafa ERBAY
Mustafa ERBAY

Posted on Originally published at mustafaerbay.com.tr

The Updater Installed Outlook 83 Times, the Running Build Is Old

The Outlook open on my Mac right now is running a release behind the build its
receipt claims to have installed. Its four siblings — Word, Excel, PowerPoint,
OneNote — were updated in the same minute and are on the new build. The only
difference is that Outlook is open.

Chasing that difference meant reading the machine's whole install history, and
the place it led me is this: that ledger is not an inventory, it is a log of
attempts. It records what someone tried to install, not what is installed. A
few days ago, when
brew counted my packages,
three commands returning three different numbers was a curiosity. Here the gap
changes the answer to "which version am I running".

Where the ledger lives, and what it keeps

The file is /Library/Receipts/InstallHistory.plist. Any user can read it; no
root required. Each record carries five fields: date, display name, display
version, package identifiers, and the name of the process that performed the
install. In 84 records a sixth field shows up, contentType: config-data —
signature and rule files, which we will come back to.

Opening it with Python's plistlib gave 806 records, the oldest from 11
December 2024 and the newest from 23 October 2026. That is 680 days, 22.3
months, an average of 36 records a month. This machine's install history is
exactly as old as the machine: the oldest receipt carries the same date, so
there is no migrated history underneath.

macOS shows the same list in System Information, under Software, Installations.
It holds 801 rows there and 806 in the file. Two measurement traps showed up in
the first minute:

The timestamps are UTC. The first record reads "macOS 15.1, 17:35:17";
System Information shows the same row as "20:35". The gap is exactly three
hours — my time zone. Anyone reading the file directly and asking "what got
installed in the middle of the night" is looking at a table shifted by three
hours. Every clock time below says which source it came from.

Five records have no name. In 5 of the 806 records the display name is
empty, and the package identifiers are com.apple.cdm.pkg.GarageBand_MASReceipt
and its siblings: App Store receipts for the five Apple apps that shipped with
the machine. A row with no name has nothing to show in the list, and that is
the five-row gap.

How many of the 806 rows are mine

Splitting the records by package identity produced this table:

Rows What they are
166 the first record for that identity
586 a record re-installing an identity already in the ledger
54 a record with no package identifier at all

So most of the ledger is not a new thing being installed; it is an existing
thing being refreshed. The processes that performed the installs make it
clearer: 542 records by installer, 116 by softwareupdated, 86 by
appstoreagent, 33 by XProtectUpdateService, 18 by bootinstalld, 10 by
Installer and 1 by CoreServicesUIAgent. Lower-case installer is the
command line installer, which the background updaters invoke. Capitalised
Installer is the graphical package window — my own double-click. The ledger
holds 10 of those.

The first day is its own story: on 11 December 2024, between 17:35 and 17:40 as
the file writes it (20:35 to 20:40 local), fifty records in five minutes.
Thirty-eight of them are sound libraries named MAContent10_AssetPack_...,
GarageBand's instrument packs. The first page of my install history is content
I did not pick but that came with the machine. I had already measured
what stays on disk after deleting apps;
what is being counted here is not space, it is rows.

Apple's share: a signature every six days

113 of the records have a display name starting with XProtect: 71
XProtectPlistConfigData, 33 XProtectCloudKitUpdate, 9 XProtectPayloads.
These are not applications but Apple's malware signatures and remediation
rules — and most of the records carrying that contentType field.

Apple documents the flow: signatures are updated automatically "independent
from system updates", and by default macOS checks for them daily. The same page
says notarisation updates are "distributed using CloudKit sync" and are "much
more frequent". Apple does not say that the ledger's XProtectCloudKitUpdate
is that flow; the name and the transport line up, so that is how I read it.
What is daily is the check, not the install: 113 rows across 680 days works out
to one every six days.

There are also 21 rows named macOS ..., one of them "macOS Rapid Security
Response 26.3.1 (a)". The operating system's own updates land in the same
ledger, and none carries a package identifier. Of the 54 records with no
identifier, 33 are XProtect's CloudKit flow and 21 are the OS: every
identifier-less row belongs to Apple. Write an audit script keyed on package
identifiers and those 54 rows slip out of your hands without a word.

System Information renders the list with four columns: Software Name, Version,
Source, Install Date. The Source column has three possible values — Apple, Mac
App Store, 3rd Party — and here 241 of the 801 rows say Apple and 560 say 3rd
Party. Mac App Store never appears, despite the 86 records signed by
appstoreagent. I do not know what rule the classification follows, only that
it has never once used that label on this machine. The 241 does close the
arithmetic, though: 192 records carrying an Apple identifier plus the 54 with
no identifier makes 246, minus the five nameless rows the list drops, exactly
241.

Six packages, 441 rows

The six busiest identities: Outlook 84, Word 76, Excel 75, PowerPoint 74,
OneNote 71, OneDrive 61. That is 441 records, 54.7% of an 806-row ledger. Add
Teams with 20 and Microsoft AutoUpdate's own 14 and the count carrying a
Microsoft identifier reaches 475. Apple identifiers account for 192; of the
remaining 139, eighty-five are third-party and fifty-four are the
identifier-less rows. (These sets overlap — do not add the percentages up.)

The count has two fine points. First, records with the display name "Microsoft
Outlook" number 83, while records carrying Outlook's package identifier number

  1. The difference is one row: the "Microsoft 365 and Office for Mac" install of 20 January 2025, a single record holding 13 package identifiers. Second, that one row is one of the ledger's capital-Installer entries — so Office came onto this machine by my hand, and the 83 installs after it were the updater's. That 83 is the number in the title.

Third parties work the same way: WhatsApp left 22 records, AdGuard 7. On the
Apple side, Rosetta's automatic update accounts for 19, Xcode 15, the Xcode
command line tools 12. All 86 appstoreagent records carry receipts, so the
idea that App Store apps are invisible here is wrong.

It would have been easy to decide the updater had lost its mind. The numbers
say otherwise. Microsoft publishes its own release history, and between 1
January and 22 October 2026 there are 42 separate release dates: four a month,
six in some months. A weekly rhythm. Outlook's first record is from 20 January
2025; 84 installs across the 21 months since works out to 4.0 a month. The
ledger counts correctly; the cadence really is that fast.

The receipts hold one more oddity. Under pkgutil --pkg-info, five packages —
Word, Excel, PowerPoint, Outlook, OneNote — have a location field that is not
/Applications but
Library/Caches/com.microsoft.autoupdate.helper/Clones.noindex. The sixth busy
package, OneDrive, has a plain Applications. That split is not arbitrary:
Microsoft's documentation says smaller updater packages are supported
"currently, only [by] core Office apps (Word, Excel, PowerPoint, Outlook, and
OneNote)" — exactly those five. The sixth package naming that same cache folder
is Teams.

Microsoft's documentation describes three updater types: full, file delta and
binary delta. Delta packages "rely on the installed apps remaining unchanged";
if a security product modifies an app, it may not launch after the update. The
documentation does not mention clones or that cache path; connecting the two is
my own inference. What can be measured is this: the receipt does not point at
the place where the app lives. The parent of that cache,
/Library/Caches/com.microsoft.autoupdate.helper, has permissions
drwxr--r--, and with no execute bit I cannot see the clone underneath it at
all as myself.

The ledger says "installed", the app says otherwise

What Outlook's receipt says:

$ pkgutil --pkg-info com.microsoft.package.Microsoft_Outlook.app
package-id: com.microsoft.package.Microsoft_Outlook.app
version: 16.114.26102031
volume: /
location: Library/Caches/com.microsoft.autoupdate.helper/Clones.noindex
install-time: 1792757416
Enter fullscreen mode Exit fullscreen mode

That install-time is 23 October 2026, 15:10 local. The last Outlook row in
the ledger carries the same instant (12:10 UTC in the file).

What the app on disk says:

$ /usr/libexec/PlistBuddy -c 'Print :CFBundleVersion' \
    "/Applications/Microsoft Outlook.app/Contents/Info.plist"
16.114.26101123
Enter fullscreen mode Exit fullscreen mode

Microsoft's release history says which build is which: 26102031 is 16.114.1,
released 22 October 2026; 26101123 is 16.114, released on the 14th. The
receipt claims it installed the 22 October build; the app in /Applications is
the 14 October build. The bundle's modification time (stat, local) is 15
October 05:15 — the exact time of the previous Outlook row in the ledger. That
install touched the folder; the two on 23 October did not.

The control group was already sitting on the same machine. On the night of 23
October, between 01:45 and 01:47 local, the same updater installed all four
siblings:

App Receipt version Bundle version Bundle mtime State
Word 16.114.26102031 16.114.26102031 23 Oct 01:47 closed
Excel 16.114.26102031 16.114.26102031 23 Oct 01:45 closed
PowerPoint 16.114.26102031 16.114.26102031 23 Oct 01:46 closed
OneNote 16.114.26102031 16.114.26102031 23 Oct 01:46 closed
Outlook 16.114.26102031 16.114.26101123 15 Oct 05:15 open

Same minute, same updater, one variable: being open. The process list agrees
(ps, local time): Outlook has been open since 03:11 on 23 October and has not
quit since.

The competing explanations had to be eliminated one by one, because Microsoft's
own documentation raises one of them as a warning:

  • A security product may have modified the app — the known fragility of delta packages. codesign -v "/Applications/Microsoft Outlook.app" returns 0 silently, so the signature on disk is valid.
  • The update may have been deferred — the updater's own preferences do hold a deferral list, but it names Teams, not Outlook.
  • The updater may only be downloading — the setting reads HowToCheck = AutomaticDownload, so it downloads and installs.
  • The updater itself may be waiting to update — Microsoft's note calls this out specifically; the installed version is 4.85, and the siblings did update, so that path is clear.

That leaves the open application. Microsoft's administrator tool msupdate
treats it as a case of its own: -t | --terminate <secs>, the "number of
seconds to wait before closing opened applications in order for updates to
complete". That is a command line option rather than documented behaviour of
the background updater — but it does show the vendor counting an open app as an
obstacle to updating.

One detail narrowed the claim. kern.boottime says the machine restarted at
03:02 on 23 October, and Outlook opened nine minutes later. So after the 01:47
install the machine shut down completely — every application closed — and the
swap still did not happen. A pending clone is not applied retroactively when
the app later quits; the swap happens inside an installer run while the app is
closed. "Quitting it will fix this" is therefore incomplete: quitting alone is
not enough, the updater's next round has to come along.

One last question: which package installed the app on disk?

$ pkgutil --file-info /Applications/Xcode.app
volume: /
path: /Applications/Xcode.app

pkgid: com.apple.pkg.Xcode
pkg-version: 27.1.0.0.1790740605
install-time: 1792279896
uid: 0  gid: 0  mode: 40755

$ pkgutil --file-info "/Applications/Microsoft Outlook.app"
volume: /
path: /Applications/Microsoft Outlook.app
Enter fullscreen mode Exit fullscreen mode

Xcode earns a pkgid block; Outlook does not. With 84 records and a valid
receipt in hand, the receipt database does not know who owns the app under
/Applications.

A fourth ledger: the updater's own record

With all three of the operating system's records misleading me, the correct
answer turned out to live in the vendor's own file:

$ defaults read com.microsoft.autoupdate2 AppVersions
{
    "/Applications/Microsoft Excel.app"   = "16.114.26102031";
    "/Applications/Microsoft OneNote.app" = "16.114.26102031";
    "/Applications/Microsoft Outlook.app" = "16.114.26101123";
    ...
}
Enter fullscreen mode Exit fullscreen mode

The updater knows the running build exactly: the 14 October build for Outlook,
the 22 October one for its siblings. The LastUpdate field in the same file
reads "2026-10-26 07:41:22 +0000" — it looked this morning, 10:41 local, and
the swap still has not happened. The wait is open-ended.

The practical upshot: do not ask macOS's install ledger which version is
running. Ask the application's CFBundleVersion; for Microsoft apps the
updater's own AppVersions record gives the same answer in one command.

Diagram

How the ledgers divide the work

On this machine, "what is installed" has four separate records: 806 attempts in
the install ledger, 213 distinct package identities inside that file, 187
receipts under pkgutil --pkgs, and the vendor's own version record. 179 of
the 213 identities are in the receipt database; 34 have no receipt, mostly data
updates like XProtect that install no files. In the other direction, 8 receipts
belong to packages with no row in the ledger at all: four
com.apple.pkg.CoreTypes... entries, MobileDevice,
MobileDeviceDevelopment, XcodeSystemResources and
com.apple.files.data-template. The install ledger is not complete either.

That has a price. Answer "is Outlook patched?" from the install ledger and I
would say this machine has been patched since 23 October — while the build
running is the 14th's. On a one-person setup that is a curiosity; for someone
tracking a fleet with inventory scripts it is a wrong security picture, and
wrong in the bad direction: the ledger makes you look more current than you
are.

If you go looking on your own machine

  • Fix the time zone. InstallHistory.plist timestamps are UTC; System Information converts to local time, plistlib does not.
  • Count the rows in two places. The GUI skips nameless records; here the gap was five rows.
  • Count without trusting package identity. 54 records have no identifier at all, and every one belongs to Apple.
  • Read the installed version from the app. Not the receipt's version field; the bundle's CFBundleVersion is what runs.
  • Account for open apps. An application you have not quit in weeks may be running the old build while the ledger says "updated" — and quitting it later does not by itself trigger the swap.
  • Do not treat receipts as an inventory. pkgutil --file-info returns no package name at all for some applications.

The warning in man pkgutil applies here too: the files and directories where
receipts are stored are subject to change, so always use pkgutil to query
them. Reaching into the file directly — as this post does for the install
ledger — leaves the responsibility for the measurement with you.

The window I left open

Outlook is still open. Quitting it would let the next installer round level the
two build numbers, and this post's evidence would be gone.

What stuck with me is not Outlook. I went to the install ledger looking for a
list of my own decisions and came back with a release calendar: more than half
of the 806 rows are the weekly cadence of six packages, 113 are Apple's
signatures arriving every six days, 586 are an existing thing being refreshed.
My own double-clicks account for 10 rows. What I call the machine's "history"
is a record of other people's schedules rather than of my intent — and there is
nothing wrong with that; updates flowing without asking me is the right design
from a security standpoint.

The one thing that changes is where I look. I had counted
how many bytes of Intel code the apps carry
and examined my download records
through the quarantine ledger;
in both cases the record was accurate and still did not answer the question I
had. The same holds here: to learn what was installed, read the ledger; to
learn what is running, read the bundle.

Official Sources

Top comments (0)