Severity: Warning
Files:
setup/taskschd/ServyFailureEmail.ps1 (line 289)
setup/taskschd/ServyFailureNotification.ps1 (line 234)
Code:
# ServyFailureEmail.ps1, line 207-212 — INITIAL read uses ParseExact correctly
$lastProcessed = [DateTime]::ParseExact(
(Get-Content $timestampFile -ErrorAction Stop),
'o',
[System.Globalization.CultureInfo]::InvariantCulture,
[System.Globalization.DateTimeStyles]::RoundtripKind
)
# ServyFailureEmail.ps1, line 289 — IN-LOOP concurrent re-read uses bare Parse
$fileTimestamp = [DateTime]::Parse($currentFileContent)
# Same pattern in ServyFailureNotification.ps1 line 170 vs line 234
Explanation:
Both scheduled-task scripts write the watermark with ToString("o") (round-trip ISO 8601, e.g. 2026-04-26T17:32:00.1234567+00:00). On the first read of the file ($lastProcessed), they correctly use ParseExact with 'o' and CultureInfo.InvariantCulture.
But the concurrent re-read inside the foreach loop (the safety check that prevents one script instance from rolling back another's watermark) uses bare [DateTime]::Parse($currentFileContent):
[DateTime]::Parse defaults to the current thread's culture, not InvariantCulture.
- On most non-en-US Windows machines (fr-FR, de-DE, sv-SE, ja-JP, et al.),
[DateTime]::Parse does interpret ISO 8601 strings produced by ToString("o") correctly most of the time, but it silently re-interprets the kind: a serialized timestamp marked +00:00 (UTC) is sometimes returned as Unspecified or local-converted, depending on the locale's DateTimeFormatInfo.
- Result: the comparison
$newestTimestamp -le $fileTimestamp (line 290 / 235) compares two DateTime instances of potentially different Kind. .NET's DateTime comparison ignores Kind, so a UTC timestamp can be compared against a local-converted timestamp and produce a wrong-direction result, causing the watermark to either:
- never advance (if the parsed file timestamp ends up "in the future" relative to the new event), or
- advance backwards (allowing duplicate notifications/emails).
The catch block on line 294 / 239 swallows parse failures and just overwrites the file, so the only observable failure mode is silent miscompare — not an exception.
Suggested fix:
Use the same ParseExact invocation everywhere the file is read:
$fileTimestamp = [DateTime]::ParseExact(
$currentFileContent,
'o',
[System.Globalization.CultureInfo]::InvariantCulture,
[System.Globalization.DateTimeStyles]::RoundtripKind
)
Same change in both scripts. This guarantees Kind consistency across reads/writes regardless of which thread culture the Scheduled Task ends up running under.
Severity: Warning
Files:
setup/taskschd/ServyFailureEmail.ps1(line 289)setup/taskschd/ServyFailureNotification.ps1(line 234)Code:
Explanation:
Both scheduled-task scripts write the watermark with
ToString("o")(round-trip ISO 8601, e.g.2026-04-26T17:32:00.1234567+00:00). On the first read of the file ($lastProcessed), they correctly useParseExactwith'o'andCultureInfo.InvariantCulture.But the concurrent re-read inside the foreach loop (the safety check that prevents one script instance from rolling back another's watermark) uses bare
[DateTime]::Parse($currentFileContent):[DateTime]::Parsedefaults to the current thread's culture, notInvariantCulture.[DateTime]::Parsedoes interpret ISO 8601 strings produced byToString("o")correctly most of the time, but it silently re-interprets the kind: a serialized timestamp marked+00:00(UTC) is sometimes returned asUnspecifiedor local-converted, depending on the locale'sDateTimeFormatInfo.$newestTimestamp -le $fileTimestamp(line 290 / 235) compares twoDateTimeinstances of potentially differentKind. .NET'sDateTimecomparison ignores Kind, so a UTC timestamp can be compared against a local-converted timestamp and produce a wrong-direction result, causing the watermark to either:The
catchblock on line 294 / 239 swallows parse failures and just overwrites the file, so the only observable failure mode is silent miscompare — not an exception.Suggested fix:
Use the same
ParseExactinvocation everywhere the file is read:Same change in both scripts. This guarantees
Kindconsistency across reads/writes regardless of which thread culture the Scheduled Task ends up running under.