Files
Victor Lyuboslavsky e4025a8193 Always display Windows ESP error when software install fails (#47522)
<!-- Add the related story/sub-task/bug number, like Resolves #123, or
remove if NA -->
**Related issue:** Resolves #45948

# Checklist for submitter

If some of the following don't apply, delete the relevant line.

- [x] Changes file added for user-visible changes in `changes/`,
`orbit/changes/` or `ee/fleetd-chrome/changes`.
See [Changes
files](https://github.com/fleetdm/fleet/blob/main/docs/Contributing/guides/committing-changes.md#changes-files)
for more information.

- [x] Timeouts are implemented and retries are limited to avoid infinite
loops

## Testing

- [x] Added/updated automated tests
- [x] QA'd all new/changed functionality manually



<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

## Summary by CodeRabbit

* **New Features**
* Updated Windows ESP failure copy with clearer “Reset your device to
try again…” wording.
* When not all apps are required, added a **“Reset PC and Continue
Anyway”** soft-block option and continuable error text that lists failed
app names with truncation (“N more”).
* **Bug Fixes**
* Improved SyncML generation by escaping XML-sensitive characters in
embedded text.
* **Tests**
* Added/expanded unit and property-based tests covering continuable
error formatting, soft-block behavior, and SyncML XML escaping.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-06-18 06:35:57 +01:00

59 lines
2.8 KiB
Go

package microsoft_mdm
import (
"fmt"
"slices"
"strings"
)
// ESPTimeoutSeconds is the default timeout for the Enrollment Status Page (3 hours).
const ESPTimeoutSeconds = 3 * 60 * 60
// ESPSoftwareFailureErrorText is the message shown on the Windows ESP failure screen when a critical software install fails.
const ESPSoftwareFailureErrorText = "Critical software failed to install. Reset your device to try again. If failures keep happening, please contact your IT admin."
// ESPTimeoutErrorText is the message shown on the Windows ESP failure screen when the 3-hour ESP timeout expires before setup completes.
const ESPTimeoutErrorText = "Setup is taking longer than expected. Please try again. If this keeps happening, please contact your IT admin."
// espContinuableErrorSuffix follows the failed-software list when the end user is allowed to continue past the failure.
const espContinuableErrorSuffix = "Reset your device to try again, or proceed and install missing software via self-service. " +
"If unavailable, contact your IT admin."
// espMaxFailedNamesShown caps how many software names the ESP failure message lists before summarizing the rest as
// "N more". CustomErrorText renders in a small ESP area and the DMClient CSP documents no maximum length.
const espMaxFailedNamesShown = 3
// ESPSoftwareFailureContinuableErrorText returns the message shown on the Windows ESP failure screen when one or more
// software installs failed but "Cancel setup if software fails" (require_all_software_windows) is off, so the end user
// is allowed to continue to the desktop.
func ESPSoftwareFailureContinuableErrorText(failedNames []string) string {
list := joinFailedNames(failedNames)
if list == "" {
// Defensive: a failure was detected but no names were collected (e.g. rows with empty names).
return "Some software failed to install. " + espContinuableErrorSuffix
}
return fmt.Sprintf("%s failed to install. %s", list, espContinuableErrorSuffix)
}
// joinFailedNames renders names as a human-readable list: "A", "A and B", "A, B, and C". Empty names are dropped. When
// more than espMaxFailedNamesShown names remain, only the first that many are listed and the rest is summarized, e.g.
// "A, B, C, and 2 more". Returns "" when there are no non-empty names.
func joinFailedNames(names []string) string {
items := slices.DeleteFunc(slices.Clone(names), func(s string) bool { return s == "" })
if len(items) == 0 {
return ""
}
if extra := len(items) - espMaxFailedNamesShown; extra > 0 {
items = append(items[:espMaxFailedNamesShown:espMaxFailedNamesShown], fmt.Sprintf("%d more", extra))
}
switch len(items) {
case 1:
return items[0]
case 2:
return items[0] + " and " + items[1]
default:
return strings.Join(items[:len(items)-1], ", ") + ", and " + items[len(items)-1]
}
}