Skip to content

Add Microsoft Office as a Fleet-maintained app for Windows#43938

Merged
lukeheath merged 11 commits into
mainfrom
allenhouchins-microsoft-office-fma
Jun 8, 2026
Merged

Add Microsoft Office as a Fleet-maintained app for Windows#43938
lukeheath merged 11 commits into
mainfrom
allenhouchins-microsoft-office-fma

Conversation

@allenhouchins

@allenhouchins allenhouchins commented Apr 22, 2026

Copy link
Copy Markdown
Member

This pull request adds full support for managing Microsoft Office (Click-to-Run) as a maintained app on Windows. It introduces new input and output manifests, robust install and uninstall scripts, and logic updates to correctly identify and handle Office installations, including edge cases with legacy version folders and Click-to-Run's auto-update behavior.

Support for Microsoft Office on Windows:

  • Added a new maintained app entry for Microsoft Office in apps.json and created a detailed output manifest (windows.json) specifying install/uninstall scripts, version detection queries, and metadata. [1] [2]
  • Introduced robust PowerShell install (microsoft_office_install.ps1) and uninstall (microsoft_office_uninstall.ps1) scripts tailored for Click-to-Run, including logic to handle registry detection, process cleanup, and silent operation. [1] [2]
  • Added the input manifest for Microsoft Office, including all relevant metadata and script references.

Detection and versioning improvements:

  • Updated the Windows app validation logic to correctly handle Microsoft Office's unique Click-to-Run versioning and naming, ensuring only genuine Office products are exempt from strict version checks.
  • Enhanced the Winget ingester to ignore legacy "year" folders (e.g., "2010", "2013") in version detection, preventing them from outranking real semver-style versions, and added corresponding unit tests. [1] [2] [3]

Summary by CodeRabbit

  • New Features
    • Added Microsoft Office (Click-to-Run) as a managed Windows app with installer/uninstaller automation, app metadata, and a dedicated app icon.
  • Bug Fixes / Improvements
    • Improved Office detection to tolerate Click-to-Run name/version variations.
    • Winget ingestion now ignores legacy year-only folders so real version entries sort correctly.
  • Tests
    • Added a test ensuring year-only directories are excluded from version ordering.

Exclude single-segment year folders in winget version detection to avoid legacy folders (e.g. "2010") outranking semver versions; add unit test to cover this behavior. Add Microsoft Office winget input (microsoft-office.json) and PowerShell install/uninstall scripts. Update apps.json to include a Microsoft Office entry and add ee/maintained-apps/outputs/microsoft-office/windows.json with a shipped version, installer URL, script refs, and embedded script contents so the app can be installed/uninstalled via the tooling.
fleet-release
fleet-release previously approved these changes Apr 22, 2026
@codecov

codecov Bot commented Apr 22, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 75.00000% with 1 line in your changes missing coverage. Please review.
✅ Project coverage is 67.15%. Comparing base (591a86f) to head (459692e).
⚠️ Report is 153 commits behind head on main.

Files with missing lines Patch % Lines
.../SoftwarePage/components/icons/MicrosoftOffice.tsx 50.00% 1 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main   #43938      +/-   ##
==========================================
+ Coverage   66.96%   67.15%   +0.19%     
==========================================
  Files        2851     2908      +57     
  Lines      225409   226115     +706     
  Branches    11547    11671     +124     
==========================================
+ Hits       150934   151857     +923     
+ Misses      60782    60534     -248     
- Partials    13693    13724      +31     
Flag Coverage Δ
backend 68.81% <100.00%> (+0.15%) ⬆️
frontend 57.64% <50.00%> (+0.79%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@allenhouchins allenhouchins marked this pull request as ready for review June 3, 2026 21:13
@allenhouchins allenhouchins requested review from a team as code owners June 3, 2026 21:13
Copilot AI review requested due to automatic review settings June 3, 2026 21:13
@fleet-release fleet-release requested a review from eashaw June 3, 2026 21:13

@claude claude Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Claude Code Review

This repository is configured for manual code reviews. Comment @claude review to trigger a review and subscribe this PR to future pushes, or @claude review once for a one-time review.

Tip: disable this comment in your organization's Code Review settings.

@allenhouchins

Copy link
Copy Markdown
Member Author

Go test failure seems unrelated to this PR.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds Microsoft Office (Click-to-Run) as a Fleet-maintained app for Windows, including manifests/scripts for install & uninstall, plus ingestion/validation updates to handle Click-to-Run versioning and winget manifest quirks.

Changes:

  • Added Microsoft Office maintained app metadata and generated output manifest (Windows).
  • Added PowerShell install/uninstall scripts for Click-to-Run.
  • Updated winget ingester version-dir filtering and Windows validator logic; added a frontend icon + icon map entry.

Reviewed changes

Copilot reviewed 10 out of 11 changed files in this pull request and generated 7 comments.

Show a summary per file
File Description
frontend/pages/SoftwarePage/components/icons/MicrosoftOffice.tsx Adds Microsoft Office icon asset component.
frontend/pages/SoftwarePage/components/icons/index.ts Registers the Microsoft Office icon in the software icon map.
ee/maintained-apps/outputs/microsoft-office/windows.json Adds generated Windows FMA manifest for Microsoft Office (queries, URL, scripts, hash).
ee/maintained-apps/outputs/apps.json Adds Microsoft Office to the maintained apps catalog list.
ee/maintained-apps/inputs/winget/scripts/microsoft_office_uninstall.ps1 Adds Click-to-Run uninstall script.
ee/maintained-apps/inputs/winget/scripts/microsoft_office_install.ps1 Adds Click-to-Run install script.
ee/maintained-apps/inputs/winget/microsoft-office.json Adds winget input manifest for generating the Office output manifest.
ee/maintained-apps/ingesters/winget/ingester.go Updates version-folder filtering logic (skips legacy year dirs).
ee/maintained-apps/ingesters/winget/ingester_test.go Adds unit test for excluding year-only folders.
cmd/maintained-apps/validate/windows.go Exempts Office Click-to-Run from strict version checks during validation.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread ee/maintained-apps/ingesters/winget/ingester.go
Comment thread ee/maintained-apps/outputs/microsoft-office/windows.json Outdated
Comment thread ee/maintained-apps/outputs/microsoft-office/windows.json Outdated
Comment thread ee/maintained-apps/inputs/winget/microsoft-office.json
Comment thread ee/maintained-apps/outputs/apps.json Outdated
Comment thread cmd/maintained-apps/validate/windows.go
@coderabbitai

coderabbitai Bot commented Jun 3, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 8fb931c6-1aee-4d6b-a493-d0699646725b

📥 Commits

Reviewing files that changed from the base of the PR and between 4f18ee2 and 459692e.

📒 Files selected for processing (1)
  • cmd/maintained-apps/validate/windows.go
🚧 Files skipped from review as they are similar to previous changes (1)
  • cmd/maintained-apps/validate/windows.go

Walkthrough

This PR adds Microsoft Office Click-to-Run support: winget input JSON and PowerShell install/uninstall scripts, outputs including a 16.0.19929.20090 version entry with script refs and SQL queries, a frontend icon and icon-map entry, a Windows validation exemption treating detected "Microsoft 365"/"Microsoft Office" Click-to-Run installs as present despite version mismatches, and a winget ingester fix + test to ignore single-segment year-only directories when enumerating version manifests.

Possibly related PRs

  • fleetdm/fleet#42397: Adds an app-specific early-return special-case in cmd/maintained-apps/validate/windows.go for Click-to-Run/installed variants (similar pattern).
  • fleetdm/fleet#46217: Also modifies cmd/maintained-apps/validate/windows.go to use osquery publisher data when making Windows maintained-apps decisions.
  • fleetdm/fleet#46264: Introduces app-specific exceptions in Windows maintained-apps validation to treat winget-reported version mismatches as installed (parallel handling for a different app).
🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Description check ⚠️ Warning The PR description is comprehensive and detailed, covering the main changes across all affected files, the objectives, and technical improvements. However, it does not include a related issue number or completion of the required checklist items from the template. Add the related issue number (e.g., 'Resolves #XXXXX') and complete the checklist items from the template to confirm testing, validation, and compatibility requirements have been met.
Docstring Coverage ⚠️ Warning Docstring coverage is 25.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately and concisely describes the main objective of the PR: adding Microsoft Office as a Fleet-maintained app for Windows, which is the primary focus across all changes.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch allenhouchins-microsoft-office-fma

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 5

🧹 Nitpick comments (2)
ee/maintained-apps/ingesters/winget/ingester_test.go (1)

40-51: ⚡ Quick win

Add a guard test for non-year single-segment versions.

This case validates year-folder exclusion, but it doesn’t protect against accidentally excluding legitimate single-segment versions (e.g., 1). Adding that test will lock the intended behavior.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@ee/maintained-apps/ingesters/winget/ingester_test.go` around lines 40 - 51,
Add a guard unit test in ingester_test.go that uses wingetVersionManifestDirs to
ensure single-segment numeric folder names that are not years (e.g., "1") are
not excluded; build an input slice (using dir("1"), dir("2010"), maybe a
multi-segment like dir("16.0.1")) call wingetVersionManifestDirs(in) and assert
that the result still contains the "1" entry while "2010" is excluded and
multi-segment versions remain present, so the test protects the intended
behavior of wingetVersionManifestDirs.
ee/maintained-apps/inputs/winget/scripts/microsoft_office_uninstall.ps1 (1)

90-100: 💤 Low value

Consider handling the timeout case explicitly.

If the polling timeout is reached at line 94 (OfficeClickToRun processes still running after 30 minutes), the script continues to line 100 and exits with the original uninstaller exit code. This could mask scenarios where the uninstaller reported success but processes remain running.

Consider logging a warning or adjusting the exit code if the timeout is reached, so operators can detect incomplete cleanup.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@ee/maintained-apps/inputs/winget/scripts/microsoft_office_uninstall.ps1`
around lines 90 - 100, The loop polling OfficeClickToRun (using Get-Process
-Name 'OfficeClickToRun') currently just proceeds to Exit $exitCode even if
$elapsed reaches $timeout; update the logic to detect the timeout case after the
while loop and explicitly handle it by emitting a warning (e.g.
Write-Warning/Write-Host) that OfficeClickToRun processes remained after
$timeout seconds and set a distinct non-zero exit code (or modify $exitCode) so
callers can detect incomplete cleanup; reference the variables $timeout,
$elapsed, the loop using OfficeClickToRun, and the final Exit $exitCode when
adding this check.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@ee/maintained-apps/ingesters/winget/ingester.go`:
- Around line 127-129: The guard that currently drops any folder name without a
dot (the condition using strings.Contains(name, ".") in ingester.go) is too
broad; change it to only skip names that are legacy year-only folders (e.g.,
four-digit years). Replace the unconditional no-dot skip with a check that
detects a 4-digit numeric year (for example via regexp `^\d{4}$` or parse and
range-check an int) and continue only when that match is true, leaving other
single-segment versions like "1" or "2" to be processed.

In `@ee/maintained-apps/inputs/winget/scripts/microsoft_office_uninstall.ps1`:
- Around line 42-48: The termination list in $officeProcesses currently includes
'TEAMS' and 'ONEDRIVE' which are independent apps and should not be
unconditionally killed; either remove those entries from $officeProcesses or
change the Stop-Process logic to first Get-Process -Name $proc -ErrorAction
SilentlyContinue and inspect the process executable path (e.g., the Path
property) to ensure it belongs to the Office Click-to-Run/Office install
location (look for "Office", "ClickToRun" or your known install folder) before
calling Stop-Process -Force; update the code around $officeProcesses and the
Stop-Process loop to perform this conditional check.

In `@ee/maintained-apps/outputs/microsoft-office/windows.json`:
- Line 7: The patched SQL ("patched" field) is too broad because name LIKE
'Microsoft 365 %' can match unrelated products (e.g., Teams, OneDrive) and skew
version_compare results; update the EXISTS filter used in the patched expression
to target only the specific Office product(s) you intend to check (for example
by using the exact product names or a product identifier/sku column rather than
a broad LIKE), keep publisher = 'Microsoft Corporation', and retain the
version_compare(version, '16.0.19929.20090') < 0 logic so the patched value
still means “no matching Office product has a version less than the target.”
Ensure the same narrowed filter is applied wherever the name pattern is used so
the patched boolean remains correct.
- Line 12: The hardcoded "sha256" entry in windows.json is unreliable and must
be replaced with digital-signature-based verification: remove the sha256 field
and instead implement validation that downloaded setup.exe is from official
Microsoft domains and signed by "Microsoft Corporation" (Authenticode signature
check). Update the consumer code that reads this JSON (e.g., the installer
download/verify routine) to enforce a domain whitelist for the CDN host and to
perform Authenticode signature validation against the signer name "Microsoft
Corporation" (and fail if the signature is missing/invalid), and add a JSON flag
like "require_signature": true and "trusted_signer": "Microsoft Corporation" to
document the requirement.

In `@frontend/pages/SoftwarePage/components/icons/MicrosoftOffice.tsx`:
- Around line 6-10: The SVG in the MicrosoftOffice component lacks a viewBox
which causes the embedded 32×32 <image> to be clipped when consumers resize via
props; fix by adding a viewBox="0 0 32 32" attribute to the <svg> element in
MicrosoftOffice.tsx (the root <svg> in the MicrosoftOffice component) so the
icon scales correctly when width/height props change.

---

Nitpick comments:
In `@ee/maintained-apps/ingesters/winget/ingester_test.go`:
- Around line 40-51: Add a guard unit test in ingester_test.go that uses
wingetVersionManifestDirs to ensure single-segment numeric folder names that are
not years (e.g., "1") are not excluded; build an input slice (using dir("1"),
dir("2010"), maybe a multi-segment like dir("16.0.1")) call
wingetVersionManifestDirs(in) and assert that the result still contains the "1"
entry while "2010" is excluded and multi-segment versions remain present, so the
test protects the intended behavior of wingetVersionManifestDirs.

In `@ee/maintained-apps/inputs/winget/scripts/microsoft_office_uninstall.ps1`:
- Around line 90-100: The loop polling OfficeClickToRun (using Get-Process -Name
'OfficeClickToRun') currently just proceeds to Exit $exitCode even if $elapsed
reaches $timeout; update the logic to detect the timeout case after the while
loop and explicitly handle it by emitting a warning (e.g.
Write-Warning/Write-Host) that OfficeClickToRun processes remained after
$timeout seconds and set a distinct non-zero exit code (or modify $exitCode) so
callers can detect incomplete cleanup; reference the variables $timeout,
$elapsed, the loop using OfficeClickToRun, and the final Exit $exitCode when
adding this check.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 809ea6a3-ddd5-4525-8b9f-f01c91078e1c

📥 Commits

Reviewing files that changed from the base of the PR and between b9e58d5 and d1c64e7.

⛔ Files ignored due to path filters (1)
  • website/assets/images/app-icon-microsoft-office-60x60@2x.png is excluded by !**/*.png
📒 Files selected for processing (10)
  • cmd/maintained-apps/validate/windows.go
  • ee/maintained-apps/ingesters/winget/ingester.go
  • ee/maintained-apps/ingesters/winget/ingester_test.go
  • ee/maintained-apps/inputs/winget/microsoft-office.json
  • ee/maintained-apps/inputs/winget/scripts/microsoft_office_install.ps1
  • ee/maintained-apps/inputs/winget/scripts/microsoft_office_uninstall.ps1
  • ee/maintained-apps/outputs/apps.json
  • ee/maintained-apps/outputs/microsoft-office/windows.json
  • frontend/pages/SoftwarePage/components/icons/MicrosoftOffice.tsx
  • frontend/pages/SoftwarePage/components/icons/index.ts

Comment thread ee/maintained-apps/ingesters/winget/ingester.go
Comment thread ee/maintained-apps/outputs/microsoft-office/windows.json Outdated
Comment thread ee/maintained-apps/outputs/microsoft-office/windows.json Outdated
Comment thread frontend/pages/SoftwarePage/components/icons/MicrosoftOffice.tsx
Stop fetching the Office deployment configuration from aka.ms at install time and instead write a pinned configuration XML to disk (fleet-office-config.xml in $env:TEMP). The installer script now generates a deterministic config (silent Display Level=None, AcceptEULA=TRUE, RemoveMSI, conditional Visio/Project installs) and writes it as UTF-8 without BOM, then passes the local file to setup.exe via /configure. Also updated the package metadata (windows.json) to reference the new install script hash.
@github-actions

github-actions Bot commented Jun 3, 2026

Copy link
Copy Markdown
Contributor

Script Diff Results

ee/maintained-apps/outputs/microsoft-office/windows.json

=== Install // 533c456c -> 7e3fc6bb ===

--- /tmp/old.rAsNwg	2026-06-03 21:48:42.640963341 +0000
+++ /tmp/new.wfl9Ui	2026-06-03 21:48:42.640963341 +0000
@@ -1,11 +1,56 @@
 # Microsoft Office (Click-to-Run) bootstrap installer.
-# setup.exe is a small (~7 MB) Office Deployment Tool bootstrap; it downloads
-# the configuration XML from the URL passed with /configure, then downloads
-# and installs the full Office product set defined in that configuration.
+# setup.exe is a small (~7 MB) Office Deployment Tool bootstrap. Rather than
+# fetching the configuration XML from a Microsoft-hosted URL at install time,
+# we write a pinned configuration to disk and point setup.exe at it with
+# /configure. This makes the install behavior fully determined by this script
+# and immune to changes in the remote config (e.g. Display Level flipping to
+# Full, or the product set changing) and to that URL being unreachable.
 # http://fleetdm.com/learn-more-about/exe-install-scripts
 
 $exeFilePath = "${env:INSTALLER_PATH}"
-$configUrl = "https://aka.ms/fhlwingetconfig"
+
+# Office Deployment Tool configuration. Equivalent to the config Microsoft
+# serves at https://aka.ms/fhlwingetconfig, pinned here for deterministic,
+# offline-safe installs.
+#   Display Level="None"  -> fully silent, no setup UI is shown.
+#   AcceptEULA="TRUE"     -> license terms are accepted automatically, so the
+#                            user is not prompted on first launch of an Office
+#                            app (required for a hands-off fleet deployment).
+#   RemoveMSI             -> removes any pre-existing MSI (volume/perpetual)
+#                            Office before installing Click-to-Run.
+# Visio/Project install only when a matching MSI product is already present
+# (MSICondition), so a clean machine receives Microsoft 365 Apps only.
+$configXml = @'
+<Configuration>
+  <Add>
+    <Product ID="O365ProPlusRetail">
+      <Language ID="MatchOS"/>
+      <Language ID="MatchPreviousMSI"/>
+      <ExcludeApp ID="Groove"/>
+      <ExcludeApp ID="Lync"/>
+    </Product>
+    <Product ID="VisioProRetail" MSICondition="VisPro,VisProR">
+      <Language ID="MatchOS"/>
+      <Language ID="MatchPreviousMSI"/>
+      <ExcludeApp ID="Groove"/>
+      <ExcludeApp ID="Lync"/>
+    </Product>
+    <Product ID="ProjectProRetail" MSICondition="PrjPro,PrjProR">
+      <Language ID="MatchOS"/>
+      <Language ID="MatchPreviousMSI"/>
+      <ExcludeApp ID="Groove"/>
+      <ExcludeApp ID="Lync"/>
+    </Product>
+  </Add>
+  <RemoveMSI/>
+  <Display Level="None" AcceptEULA="TRUE"/>
+</Configuration>
+'@
+
+# Write the configuration next to the installer. Use .NET WriteAllText so the
+# file is UTF-8 without a BOM, which the Office Deployment Tool parses cleanly.
+$configPath = Join-Path $env:TEMP "fleet-office-config.xml"
+[System.IO.File]::WriteAllText($configPath, $configXml, (New-Object System.Text.UTF8Encoding $false))
 
 $exitCode = 0
 
@@ -15,13 +60,13 @@
     # 15-60+ minutes depending on network speed and selected products.
     $processOptions = @{
         FilePath     = "$exeFilePath"
-        ArgumentList = "/configure `"$configUrl`""
+        ArgumentList = "/configure `"$configPath`""
         PassThru     = $true
         Wait         = $true
         NoNewWindow  = $true
     }
 
-    Write-Host "Starting Microsoft Office setup: $exeFilePath /configure $configUrl"
+    Write-Host "Starting Microsoft Office setup: $exeFilePath /configure $configPath"
     $process = Start-Process @processOptions
     $exitCode = $process.ExitCode
     Write-Host "setup.exe exit code: $exitCode"

=== Uninstall Script (no changes) ===

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
ee/maintained-apps/outputs/microsoft-office/windows.json (1)

6-7: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Align the exists/patched predicates with the Office name handling used elsewhere.

These queries only inventory Microsoft 365 %, but both the registry matchers in ee/maintained-apps/inputs/winget/scripts/microsoft_office_install.ps1 and the validator special-case in cmd/maintained-apps/validate/windows.go:138-151 treat Microsoft Office* as a valid Click-to-Run product too. If ARP reports a genuine Office install under a Microsoft Office ... name, Fleet will install it successfully and still report the maintained app absent/unpatched.

Proposed fix
-        "exists": "SELECT 1 FROM programs WHERE name LIKE 'Microsoft 365 %' AND publisher = 'Microsoft Corporation';",
-        "patched": "SELECT 1 WHERE NOT EXISTS (SELECT 1 FROM programs WHERE name LIKE 'Microsoft 365 %' AND publisher = 'Microsoft Corporation' AND version_compare(version, '16.0.19929.20090') < 0);"
+        "exists": "SELECT 1 FROM programs WHERE publisher = 'Microsoft Corporation' AND (name LIKE 'Microsoft 365 %' OR name LIKE 'Microsoft Office %');",
+        "patched": "SELECT 1 WHERE NOT EXISTS (SELECT 1 FROM programs WHERE publisher = 'Microsoft Corporation' AND (name LIKE 'Microsoft 365 %' OR name LIKE 'Microsoft Office %') AND version_compare(version, '16.0.19929.20090') < 0);"
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@ee/maintained-apps/outputs/microsoft-office/windows.json` around lines 6 - 7,
The SQL predicates under the "exists" and "patched" keys currently only match
names like 'Microsoft 365 %' but must also accept 'Microsoft Office %' to align
with the registry matcher and validator behavior (see
microsoft_office_install.ps1 and the Windows validator special-case). Update the
"exists" SQL to include OR name LIKE 'Microsoft Office %' and update the
"patched" SQL to apply the same inclusion so ARP-reported installs named
"Microsoft Office ..." are counted and version-compared the same way as
"Microsoft 365 ...".
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@ee/maintained-apps/inputs/winget/scripts/microsoft_office_install.ps1`:
- Around line 50-53: The script currently calls
[System.IO.File]::WriteAllText($configPath, $configXml, (New-Object
System.Text.UTF8Encoding $false)) before entering the existing try/catch, so
failures creating $configPath produce raw PowerShell errors; move the config
file creation into the same try block that handles installation so any exception
from WriteAllText is caught and logged the same way (use the existing catch to
call the script's logging and exit 1 path), ensuring $configPath and $configXml
are defined before the move and no other code paths assume the file exists
before the try.

---

Outside diff comments:
In `@ee/maintained-apps/outputs/microsoft-office/windows.json`:
- Around line 6-7: The SQL predicates under the "exists" and "patched" keys
currently only match names like 'Microsoft 365 %' but must also accept
'Microsoft Office %' to align with the registry matcher and validator behavior
(see microsoft_office_install.ps1 and the Windows validator special-case).
Update the "exists" SQL to include OR name LIKE 'Microsoft Office %' and update
the "patched" SQL to apply the same inclusion so ARP-reported installs named
"Microsoft Office ..." are counted and version-compared the same way as
"Microsoft 365 ...".
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 22bbe377-b438-4655-8c3d-e21c6e43c87a

📥 Commits

Reviewing files that changed from the base of the PR and between d1c64e7 and b9d4b98.

📒 Files selected for processing (2)
  • ee/maintained-apps/inputs/winget/scripts/microsoft_office_install.ps1
  • ee/maintained-apps/outputs/microsoft-office/windows.json

@github-actions

github-actions Bot commented Jun 4, 2026

Copy link
Copy Markdown
Contributor

Script Diff Results

ee/maintained-apps/outputs/microsoft-office/windows.json

=== Install // 533c456c -> 7e3fc6bb ===

--- /tmp/old.nZq5NZ	2026-06-04 14:51:12.013628246 +0000
+++ /tmp/new.xA6nSp	2026-06-04 14:51:12.013628246 +0000
@@ -1,11 +1,56 @@
 # Microsoft Office (Click-to-Run) bootstrap installer.
-# setup.exe is a small (~7 MB) Office Deployment Tool bootstrap; it downloads
-# the configuration XML from the URL passed with /configure, then downloads
-# and installs the full Office product set defined in that configuration.
+# setup.exe is a small (~7 MB) Office Deployment Tool bootstrap. Rather than
+# fetching the configuration XML from a Microsoft-hosted URL at install time,
+# we write a pinned configuration to disk and point setup.exe at it with
+# /configure. This makes the install behavior fully determined by this script
+# and immune to changes in the remote config (e.g. Display Level flipping to
+# Full, or the product set changing) and to that URL being unreachable.
 # http://fleetdm.com/learn-more-about/exe-install-scripts
 
 $exeFilePath = "${env:INSTALLER_PATH}"
-$configUrl = "https://aka.ms/fhlwingetconfig"
+
+# Office Deployment Tool configuration. Equivalent to the config Microsoft
+# serves at https://aka.ms/fhlwingetconfig, pinned here for deterministic,
+# offline-safe installs.
+#   Display Level="None"  -> fully silent, no setup UI is shown.
+#   AcceptEULA="TRUE"     -> license terms are accepted automatically, so the
+#                            user is not prompted on first launch of an Office
+#                            app (required for a hands-off fleet deployment).
+#   RemoveMSI             -> removes any pre-existing MSI (volume/perpetual)
+#                            Office before installing Click-to-Run.
+# Visio/Project install only when a matching MSI product is already present
+# (MSICondition), so a clean machine receives Microsoft 365 Apps only.
+$configXml = @'
+<Configuration>
+  <Add>
+    <Product ID="O365ProPlusRetail">
+      <Language ID="MatchOS"/>
+      <Language ID="MatchPreviousMSI"/>
+      <ExcludeApp ID="Groove"/>
+      <ExcludeApp ID="Lync"/>
+    </Product>
+    <Product ID="VisioProRetail" MSICondition="VisPro,VisProR">
+      <Language ID="MatchOS"/>
+      <Language ID="MatchPreviousMSI"/>
+      <ExcludeApp ID="Groove"/>
+      <ExcludeApp ID="Lync"/>
+    </Product>
+    <Product ID="ProjectProRetail" MSICondition="PrjPro,PrjProR">
+      <Language ID="MatchOS"/>
+      <Language ID="MatchPreviousMSI"/>
+      <ExcludeApp ID="Groove"/>
+      <ExcludeApp ID="Lync"/>
+    </Product>
+  </Add>
+  <RemoveMSI/>
+  <Display Level="None" AcceptEULA="TRUE"/>
+</Configuration>
+'@
+
+# Write the configuration next to the installer. Use .NET WriteAllText so the
+# file is UTF-8 without a BOM, which the Office Deployment Tool parses cleanly.
+$configPath = Join-Path $env:TEMP "fleet-office-config.xml"
+[System.IO.File]::WriteAllText($configPath, $configXml, (New-Object System.Text.UTF8Encoding $false))
 
 $exitCode = 0
 
@@ -15,13 +60,13 @@
     # 15-60+ minutes depending on network speed and selected products.
     $processOptions = @{
         FilePath     = "$exeFilePath"
-        ArgumentList = "/configure `"$configUrl`""
+        ArgumentList = "/configure `"$configPath`""
         PassThru     = $true
         Wait         = $true
         NoNewWindow  = $true
     }
 
-    Write-Host "Starting Microsoft Office setup: $exeFilePath /configure $configUrl"
+    Write-Host "Starting Microsoft Office setup: $exeFilePath /configure $configPath"
     $process = Start-Process @processOptions
     $exitCode = $process.ExitCode
     Write-Host "setup.exe exit code: $exitCode"

=== Uninstall Script (no changes) ===

Tighten detection and uninstall behavior for Microsoft Office (Click-to-Run). Change unique_identifier to "Microsoft 365 Apps" and update exists/patched osquery queries to match either "Microsoft 365 Apps %" or "Microsoft Office %" (avoids matching unrelated Office-branded packages or Store apps like "Microsoft 365 Copilot"). Update inputs to add an explicit exists_query and ignore_hash, and update outputs: set unique_identifier, adjust patched/exists queries, set uninstall script ref and mark sha256 as no_check. Modify the PowerShell uninstall script to limit which processes are force-stopped (exclude standalone apps like Teams and OneDrive) and add clarifying comments. Also refine validation logic in windows.go so only genuine Office suite names are exempt from strict version checks.
@github-actions

github-actions Bot commented Jun 8, 2026

Copy link
Copy Markdown
Contributor

Script Diff Results

ee/maintained-apps/outputs/microsoft-office/windows.json

=== Install Script (no changes) ===
=== Uninstall // 86950a4e -> 6e7a8864 ===

--- /tmp/old.oUlxWp	2026-06-08 15:35:40.827814199 +0000
+++ /tmp/new.2ST3pw	2026-06-08 15:35:40.828814194 +0000
@@ -38,10 +38,14 @@
 Write-Host "Found: $($uninstall.DisplayName) ($($uninstall.DisplayVersion))"
 Write-Host "Original UninstallString: $($uninstall.UninstallString)"
 
-# Close Office apps that could block the uninstaller.
+# Close Office suite apps that hold file locks and can block the uninstaller.
+# Deliberately limited to Click-to-Run suite components: standalone apps that
+# are commonly managed separately (new Teams, OneDrive) are NOT killed here, as
+# they aren't part of Microsoft 365 Apps and don't block OfficeClickToRun.exe —
+# force-stopping them would needlessly interrupt sync/calls.
 $officeProcesses = @(
     'WINWORD', 'EXCEL', 'POWERPNT', 'OUTLOOK', 'ONENOTE', 'MSACCESS',
-    'MSPUB', 'VISIO', 'WINPROJ', 'LYNC', 'TEAMS', 'ONEDRIVE'
+    'MSPUB', 'VISIO', 'WINPROJ', 'LYNC'
 )
 foreach ($proc in $officeProcesses) {
     Stop-Process -Name $proc -Force -ErrorAction SilentlyContinue

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@cmd/maintained-apps/validate/windows.go`:
- Around line 149-151: The current exemption for appName == "Microsoft Office"
is too broad because it only checks result.Name prefixes; update the condition
in the validation function to also verify the publisher field (e.g.,
result.Publisher or result.PublisherName) matches Microsoft’s publisher (use the
same canonical string used elsewhere in manifests, e.g., "Microsoft Corporation"
or the exact publisher constant) before allowing the HasPrefix checks for
"Microsoft 365 Apps" or "Microsoft Office" to bypass validation; keep the
existing name checks but add the publisher guard so only Microsoft-published
entries are exempt.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 70c4f5ab-875e-4373-b75c-8488f3d4392a

📥 Commits

Reviewing files that changed from the base of the PR and between 2d866a4 and 4f18ee2.

📒 Files selected for processing (5)
  • cmd/maintained-apps/validate/windows.go
  • ee/maintained-apps/inputs/winget/microsoft-office.json
  • ee/maintained-apps/inputs/winget/scripts/microsoft_office_uninstall.ps1
  • ee/maintained-apps/outputs/apps.json
  • ee/maintained-apps/outputs/microsoft-office/windows.json
✅ Files skipped from review due to trivial changes (1)
  • ee/maintained-apps/outputs/apps.json
🚧 Files skipped from review as they are similar to previous changes (1)
  • ee/maintained-apps/inputs/winget/microsoft-office.json

Comment thread cmd/maintained-apps/validate/windows.go
@github-actions

github-actions Bot commented Jun 8, 2026

Copy link
Copy Markdown
Contributor

Script Diff Results

ee/maintained-apps/outputs/microsoft-office/windows.json

=== Install Script (no changes) ===
=== Uninstall // 86950a4e -> 6e7a8864 ===

--- /tmp/old.gWNWWh	2026-06-08 17:26:34.779886615 +0000
+++ /tmp/new.jHMTSG	2026-06-08 17:26:34.780886597 +0000
@@ -38,10 +38,14 @@
 Write-Host "Found: $($uninstall.DisplayName) ($($uninstall.DisplayVersion))"
 Write-Host "Original UninstallString: $($uninstall.UninstallString)"
 
-# Close Office apps that could block the uninstaller.
+# Close Office suite apps that hold file locks and can block the uninstaller.
+# Deliberately limited to Click-to-Run suite components: standalone apps that
+# are commonly managed separately (new Teams, OneDrive) are NOT killed here, as
+# they aren't part of Microsoft 365 Apps and don't block OfficeClickToRun.exe —
+# force-stopping them would needlessly interrupt sync/calls.
 $officeProcesses = @(
     'WINWORD', 'EXCEL', 'POWERPNT', 'OUTLOOK', 'ONENOTE', 'MSACCESS',
-    'MSPUB', 'VISIO', 'WINPROJ', 'LYNC', 'TEAMS', 'ONEDRIVE'
+    'MSPUB', 'VISIO', 'WINPROJ', 'LYNC'
 )
 foreach ($proc in $officeProcesses) {
     Stop-Process -Name $proc -Force -ErrorAction SilentlyContinue

@lukeheath lukeheath merged commit 055ef89 into main Jun 8, 2026
58 of 61 checks passed
@lukeheath lukeheath deleted the allenhouchins-microsoft-office-fma branch June 8, 2026 19:33
@lukeheath

Copy link
Copy Markdown
Member

@allenhouchins Sorry didn't mean to click merge.

@allenhouchins

Copy link
Copy Markdown
Member Author

@lukeheath no worries! Thank you!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants