Skip to content

Add GoToMeeting as a macOS and Windows FMA - #46264

Merged
allenhouchins merged 3 commits into
mainfrom
allenhouchins-gotomeeting-fma
May 27, 2026
Merged

Add GoToMeeting as a macOS and Windows FMA#46264
allenhouchins merged 3 commits into
mainfrom
allenhouchins-gotomeeting-fma

Conversation

@allenhouchins

@allenhouchins allenhouchins commented May 27, 2026

Copy link
Copy Markdown
Member

Introduce GoToMeeting to maintained apps: add homebrew and winget input manifests, darwin and windows output metadata (with installers, checksums, and install/uninstall script refs), and frontend icon asset.

Include Windows install/uninstall PowerShell scripts (MSI machine-wide install and uninstall via hard-coded UpgradeCode) and macOS DMG install/uninstall refs and scripts. Update ee/maintained-apps/outputs/apps.json to register GoToMeeting for darwin and windows.

Also adjust Windows validation logic to special-case GoToMeeting version checks: winget reports a package version (10.19.0.19950) that contains an extra ".0" segment compared to the installed MSI/registry version (10.19.19950), so the validator falls back to existence-only validation and logs the difference.

Summary by CodeRabbit

New Features

  • Added GoToMeeting application support on macOS and Windows platforms
  • Enabled automated installation and uninstallation capabilities for both operating systems
  • Integrated GoToMeeting icon in the application interface

Review Change Stack

Introduce GoToMeeting to maintained apps: add homebrew and winget input manifests, darwin and windows output metadata (with installers, checksums, and install/uninstall script refs), and frontend icon asset.

Include Windows install/uninstall PowerShell scripts (MSI machine-wide install and uninstall via hard-coded UpgradeCode) and macOS DMG install/uninstall refs and scripts. Update ee/maintained-apps/outputs/apps.json to register GoToMeeting for darwin and windows.

Also adjust Windows validation logic to special-case GoToMeeting version checks: winget reports a package version (10.19.0.19950) that contains an extra ".0" segment compared to the installed MSI/registry version (10.19.19950), so the validator falls back to existence-only validation and logs the difference.
@codecov

codecov Bot commented May 27, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 50.00000% with 1 line in your changes missing coverage. Please review.
✅ Project coverage is 66.83%. Comparing base (2d2c93a) to head (8bbb2ba).
⚠️ Report is 10 commits behind head on main.

Files with missing lines Patch % Lines
...ages/SoftwarePage/components/icons/GoToMeeting.tsx 50.00% 1 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main   #46264      +/-   ##
==========================================
- Coverage   66.84%   66.83%   -0.01%     
==========================================
  Files        2761     2762       +1     
  Lines      220853   220861       +8     
  Branches    10879    11010     +131     
==========================================
- Hits       147626   147621       -5     
- Misses      59860    59869       +9     
- Partials    13367    13371       +4     
Flag Coverage Δ
frontend 55.79% <50.00%> (-0.01%) ⬇️

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

☔ View full report in Codecov by Sentry.
📢 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.

Remove the ad-hoc GoToMeeting version-format special-case from Windows validation and instead rely on an explicit exists_query in the winget input. Update the winget input to use an exists_query (matching program DisplayName) and remove fuzzy_match_name/publisher constraints. Replace the simple MSI-related uninstall script with a robust PowerShell uninstall that locates the app's uninstall registry entry (HKLM/HKLM\WOW6432Node/HKCU), prefers QuietUninstallString, stops related processes, parses the uninstall command, adds silent switches when needed, and returns appropriate exit codes. Update the outputs to relax the publisher requirement for existence/patch checks and point to the new uninstall script ref.
fleet-release
fleet-release previously approved these changes May 27, 2026
@github-actions

Copy link
Copy Markdown
Contributor

Script Diff Results

ee/maintained-apps/outputs/gotomeeting/darwin.json

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

ee/maintained-apps/outputs/gotomeeting/windows.json

=== Install Script (no changes) ===
=== Uninstall // da9b7854 -> 9ac6e58a ===

--- /tmp/old.v7ZS93	2026-05-27 17:35:14.218392471 +0000
+++ /tmp/new.rkTV9L	2026-05-27 17:35:14.219392464 +0000
@@ -1,26 +1,116 @@
-# Fleet uninstalls app by finding all related product codes for the specified upgrade code.
-# GoToMeeting's winget manifest does not expose the UpgradeCode, so it is hard-coded
-# here from the MSI's UpgradeCode property.
-$inst = New-Object -ComObject "WindowsInstaller.Installer"
-$timeoutSeconds = 300  # 5 minute timeout per product
-
-foreach ($product_code in $inst.RelatedProducts('{BF62CF5F-6CB1-4010-8F05-F7EBB182D3EA}')) {
-    $process = Start-Process msiexec -ArgumentList @("/quiet", "/x", $product_code, "/norestart") -PassThru
-
-    # Wait for process with timeout
-    $completed = $process.WaitForExit($timeoutSeconds * 1000)
-
-    if (-not $completed) {
-        Stop-Process -Id $process.Id -Force -ErrorAction SilentlyContinue
-        Exit 1603  # ERROR_UNINSTALL_FAILURE
+# Best-effort uninstall for GoToMeeting.
+#
+# The winget installer is the GoToMeeting "Setup" bootstrapper (ARPSYSTEMCOMPONENT=1,
+# so it hides itself from Programs and Features). It installs the actual GoToMeeting
+# app, which self-registers a separate, visible uninstall entry (DisplayName like
+# "GoToMeeting <version>"). We therefore can't uninstall via the bootstrapper's
+# UpgradeCode; instead we locate the app's own registry entry and run its
+# uninstaller. We search HKLM, the 32-bit hive, and HKCU because the app may be
+# registered per-machine or per-user.
+
+$softwareNameLike = "GoToMeeting*"
+
+$paths = @(
+  'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall',
+  'HKLM:\SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall',
+  'HKCU:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall'
+)
+
+$exitCode = 0
+
+try {
+
+[array]$uninstallKeys = Get-ChildItem `
+    -Path $paths `
+    -ErrorAction SilentlyContinue |
+        ForEach-Object { Get-ItemProperty $_.PSPath -ErrorAction SilentlyContinue }
+
+$selected = $null
+foreach ($key in $uninstallKeys) {
+    if ($key.DisplayName -and $key.DisplayName -like $softwareNameLike) {
+        $selected = $key
+        break
     }
+}
+
+if (-not $selected) {
+    Write-Host "Uninstall entry not found for $softwareNameLike"
+    Exit 1
+}
+
+# Best-effort: stop running GoToMeeting processes so the uninstaller doesn't
+# fail on locked files.
+foreach ($proc in @("g2mstart", "g2mlauncher", "g2mcomm", "g2muicore", "GoToMeeting", "GoTo")) {
+    Stop-Process -Name $proc -Force -ErrorAction SilentlyContinue
+}
+
+# Prefer QuietUninstallString (it already includes the correct silent switches).
+$useQuiet = $false
+if ($selected.QuietUninstallString) {
+    $uninstallCommand = $selected.QuietUninstallString
+    $useQuiet = $true
+} elseif ($selected.UninstallString) {
+    $uninstallCommand = $selected.UninstallString
+} else {
+    Write-Host "Selected entry has no UninstallString: $($selected.DisplayName)"
+    Exit 1
+}
 
-    # If the uninstall failed, bail
-    if ($process.ExitCode -ne 0) {
-        Write-Output "Uninstall for $($product_code) exited $($process.ExitCode)"
-        Exit $process.ExitCode
+# Split the uninstall string into exe + args. Handle quoted paths, unquoted
+# paths that may contain spaces (capture through .exe), and a bare token.
+$exePath = ""
+$existingArgs = ""
+if ($uninstallCommand -match '^\s*"([^"]+)"\s*(.*)$') {
+    $exePath = $matches[1]
+    $existingArgs = $matches[2].Trim()
+} elseif ($uninstallCommand -match '(?i)^\s*(.+?\.exe)\s*(.*)$') {
+    $exePath = $matches[1]
+    $existingArgs = $matches[2].Trim()
+} elseif ($uninstallCommand -match '^\s*(\S+)\s*(.*)$') {
+    $exePath = $matches[1]
+    $existingArgs = $matches[2].Trim()
+} else {
+    Throw "Could not parse uninstall string: $uninstallCommand"
+}
+
+# If we fell back to UninstallString (no quiet variant), add a silent switch.
+if (-not $useQuiet) {
+    if ($exePath -match '(?i)msiexec') {
+        if ($existingArgs -notmatch '/quiet' -and $existingArgs -notmatch '/qn') {
+            $existingArgs = ("$existingArgs /quiet /norestart").Trim()
+        }
+    } elseif ($existingArgs -notmatch '/S\b' -and $existingArgs -notmatch '/silent' -and $existingArgs -notmatch '/quiet') {
+        # Custom uninstaller: GoTo's uninstaller honors /S for silent operation.
+        $existingArgs = ("$existingArgs /S").Trim()
     }
 }
 
-# All uninstalls succeeded; exit success
-Exit 0
+Write-Host "Selected entry DisplayName: $($selected.DisplayName)"
+Write-Host "Uninstall command: $exePath"
+Write-Host "Uninstall args: $existingArgs"
+
+$processOptions = @{
+    FilePath = $exePath
+    PassThru = $true
+    Wait = $true
+}
+
+if ($existingArgs -ne '') {
+    $processOptions.ArgumentList = $existingArgs
+}
+
+$process = Start-Process @processOptions
+$exitCode = $process.ExitCode
+Write-Host "Uninstall exit code: $exitCode"
+
+# Treat msiexec reboot-required success codes as success.
+if ($exitCode -eq 3010 -or $exitCode -eq 1641) {
+    Exit 0
+}
+
+Exit $exitCode
+
+} catch {
+    Write-Host "Error: $_"
+    Exit 1
+}

Refactor the GoToMeeting uninstall PowerShell: stop additional g2mupdate process, simplify parsing to extract the uninstaller executable, and build vendor-documented silent args (/uninstall /ForAllUsers /silent) instead of relying on registry /S which G2MUninstall.exe ignores. Start-Process invocation was simplified to pass explicit ArgumentList; error messages and parsing failures were clarified. Also update outputs/windows.json to reference the new uninstall script id (uninstall_script_ref -> "cef7f8fa"). These changes make uninstalls more reliable and avoid hangs from incorrect silent switches.
@github-actions

Copy link
Copy Markdown
Contributor

Script Diff Results

ee/maintained-apps/outputs/gotomeeting/darwin.json

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

ee/maintained-apps/outputs/gotomeeting/windows.json

=== Install Script (no changes) ===
=== Uninstall // 9ac6e58a -> cef7f8fa ===

--- /tmp/old.tec5xD	2026-05-27 17:50:50.023307213 +0000
+++ /tmp/new.eSOlxT	2026-05-27 17:50:50.023307213 +0000
@@ -3,10 +3,13 @@
 # The winget installer is the GoToMeeting "Setup" bootstrapper (ARPSYSTEMCOMPONENT=1,
 # so it hides itself from Programs and Features). It installs the actual GoToMeeting
 # app, which self-registers a separate, visible uninstall entry (DisplayName like
-# "GoToMeeting <version>"). We therefore can't uninstall via the bootstrapper's
-# UpgradeCode; instead we locate the app's own registry entry and run its
-# uninstaller. We search HKLM, the 32-bit hive, and HKCU because the app may be
-# registered per-machine or per-user.
+# "GoToMeeting <version>") whose uninstaller is G2MUninstall.exe.
+#
+# We locate that entry and run G2MUninstall.exe directly. NOTE: the registry
+# QuietUninstallString uses "/S", which G2MUninstall.exe does not recognize as a
+# silent switch (it hangs waiting on UI). The vendor's documented silent switch
+# is "/silent" (see silentinstallhq.com), so we build the arguments ourselves
+# rather than trusting the registry string.
 
 $softwareNameLike = "GoToMeeting*"
 
@@ -40,70 +43,48 @@
 
 # Best-effort: stop running GoToMeeting processes so the uninstaller doesn't
 # fail on locked files.
-foreach ($proc in @("g2mstart", "g2mlauncher", "g2mcomm", "g2muicore", "GoToMeeting", "GoTo")) {
+foreach ($proc in @("g2mstart", "g2mlauncher", "g2mcomm", "g2muicore", "g2mupdate", "GoToMeeting", "GoTo")) {
     Stop-Process -Name $proc -Force -ErrorAction SilentlyContinue
 }
 
-# Prefer QuietUninstallString (it already includes the correct silent switches).
-$useQuiet = $false
-if ($selected.QuietUninstallString) {
-    $uninstallCommand = $selected.QuietUninstallString
-    $useQuiet = $true
-} elseif ($selected.UninstallString) {
-    $uninstallCommand = $selected.UninstallString
+# Extract just the uninstaller exe path from whichever registry string is
+# available; we supply the silent switches ourselves.
+$uninstallCommand = if ($selected.QuietUninstallString) {
+    $selected.QuietUninstallString
 } else {
+    $selected.UninstallString
+}
+
+if (-not $uninstallCommand) {
     Write-Host "Selected entry has no UninstallString: $($selected.DisplayName)"
     Exit 1
 }
 
-# Split the uninstall string into exe + args. Handle quoted paths, unquoted
-# paths that may contain spaces (capture through .exe), and a bare token.
 $exePath = ""
-$existingArgs = ""
-if ($uninstallCommand -match '^\s*"([^"]+)"\s*(.*)$') {
-    $exePath = $matches[1]
-    $existingArgs = $matches[2].Trim()
-} elseif ($uninstallCommand -match '(?i)^\s*(.+?\.exe)\s*(.*)$') {
+if ($uninstallCommand -match '^\s*"([^"]+)"') {
+    # Quoted path
     $exePath = $matches[1]
-    $existingArgs = $matches[2].Trim()
-} elseif ($uninstallCommand -match '^\s*(\S+)\s*(.*)$') {
+} elseif ($uninstallCommand -match '(?i)^\s*(.+?\.exe)') {
+    # Unquoted path that may contain spaces (e.g. "C:\Program Files (x86)\...")
     $exePath = $matches[1]
-    $existingArgs = $matches[2].Trim()
 } else {
-    Throw "Could not parse uninstall string: $uninstallCommand"
+    Throw "Could not parse uninstaller path from: $uninstallCommand"
 }
 
-# If we fell back to UninstallString (no quiet variant), add a silent switch.
-if (-not $useQuiet) {
-    if ($exePath -match '(?i)msiexec') {
-        if ($existingArgs -notmatch '/quiet' -and $existingArgs -notmatch '/qn') {
-            $existingArgs = ("$existingArgs /quiet /norestart").Trim()
-        }
-    } elseif ($existingArgs -notmatch '/S\b' -and $existingArgs -notmatch '/silent' -and $existingArgs -notmatch '/quiet') {
-        # Custom uninstaller: GoTo's uninstaller honors /S for silent operation.
-        $existingArgs = ("$existingArgs /S").Trim()
-    }
-}
+# Vendor-documented silent uninstall switches. /ForAllUsers matches the
+# machine-wide install (G2MINSTALLFORALLUSERS=1); /silent is the correct silent
+# switch (NOT /S, which G2MUninstall.exe ignores).
+$uninstallArgs = "/uninstall /ForAllUsers /silent"
 
 Write-Host "Selected entry DisplayName: $($selected.DisplayName)"
 Write-Host "Uninstall command: $exePath"
-Write-Host "Uninstall args: $existingArgs"
-
-$processOptions = @{
-    FilePath = $exePath
-    PassThru = $true
-    Wait = $true
-}
-
-if ($existingArgs -ne '') {
-    $processOptions.ArgumentList = $existingArgs
-}
+Write-Host "Uninstall args: $uninstallArgs"
 
-$process = Start-Process @processOptions
+$process = Start-Process -FilePath $exePath -ArgumentList $uninstallArgs -PassThru -Wait
 $exitCode = $process.ExitCode
 Write-Host "Uninstall exit code: $exitCode"
 
-# Treat msiexec reboot-required success codes as success.
+# Treat msiexec-style reboot-required success codes as success.
 if ($exitCode -eq 3010 -or $exitCode -eq 1641) {
     Exit 0
 }

@allenhouchins
allenhouchins marked this pull request as ready for review May 27, 2026 18:19
@allenhouchins
allenhouchins requested a review from a team as a code owner May 27, 2026 18:19
Copilot AI review requested due to automatic review settings May 27, 2026 18:19
@fleet-release
fleet-release requested a review from eashaw May 27, 2026 18:19

@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
allenhouchins merged commit 23f5958 into main May 27, 2026
29 of 30 checks passed
@allenhouchins
allenhouchins deleted the allenhouchins-gotomeeting-fma branch May 27, 2026 18:20
@allenhouchins allenhouchins changed the title Add GoToMeeting maintained-app support Add GoToMeeting as a macOS and Windows FMA May 27, 2026
@coderabbitai

coderabbitai Bot commented May 27, 2026

Copy link
Copy Markdown
Contributor

Caution

Review failed

The pull request is closed.

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 33b92132-b056-4c48-afff-7c2b9cf7361d

📥 Commits

Reviewing files that changed from the base of the PR and between 6a48147 and 8bbb2ba.

⛔ Files ignored due to path filters (1)
  • website/assets/images/app-icon-gotomeeting-60x60@2x.png is excluded by !**/*.png
📒 Files selected for processing (9)
  • ee/maintained-apps/inputs/homebrew/gotomeeting.json
  • ee/maintained-apps/inputs/winget/gotomeeting.json
  • ee/maintained-apps/inputs/winget/scripts/gotomeeting_install.ps1
  • ee/maintained-apps/inputs/winget/scripts/gotomeeting_uninstall.ps1
  • ee/maintained-apps/outputs/apps.json
  • ee/maintained-apps/outputs/gotomeeting/darwin.json
  • ee/maintained-apps/outputs/gotomeeting/windows.json
  • frontend/pages/SoftwarePage/components/icons/GoToMeeting.tsx
  • frontend/pages/SoftwarePage/components/icons/index.ts

Walkthrough

This PR adds complete GoToMeeting managed software support for macOS and Windows. It defines platform-specific installer inputs (Homebrew for macOS, Winget for Windows), implements Windows MSI installation and registry-based uninstallation PowerShell scripts, generates platform output configurations with version metadata and detection queries, and integrates a GoToMeeting icon and registry entry into the frontend application interface.

Possibly related PRs

  • fleetdm/fleet#46250: Both PRs add new software support and extend SOFTWARE_NAME_TO_ICON_MAP in the frontend to register the new app icon.
  • fleetdm/fleet#46208: Both PRs add a new maintained app with input/output configurations and update SOFTWARE_NAME_TO_ICON_MAP to wire the frontend icon.
  • fleetdm/fleet#45858: Both PRs add new maintained-app entries and extend SOFTWARE_NAME_TO_ICON_MAP with the new app's icon key.
✨ 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-gotomeeting-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.

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.

2 participants