Agent: the bootstrap provenance also reads the log names from before 2026-08-18

The first device on 2.0.1486 reported no bootstrap log at all. Installers up to v2.3 write
bootstrap_agent.log, and the MSI's own downloader (before the loader of 2026-09-08)
msi-bootstrap.log; the reader knew only the names from the rename of 2026-08-18 on, so the
old directly assigned installers that agent_started is meant to count read as "no bootstrap".

- The installer log is the more recently written of bootstrap-script.log and
  bootstrap_agent.log; the MSI channel also covers msi-bootstrap.log, which has no version.
- Tests: the old installer name, both installer logs (the newer one counts), the old MSI
  downloader.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Oliver Kieselbach
2026-10-13 20:04:06 +02:00
parent fd44518e14
commit 1892da30e4
2 changed files with 63 additions and 15 deletions
@@ -12,7 +12,8 @@ namespace AutopilotMonitor.Agent.V2.Core.Tests.Monitoring.Runtime
/// The bootstrap provenance in <c>agent_started</c>. Fixture lines are the scripts' real
/// formats: installer <c>[ts] Bootstrap script version: vX.Y</c>
/// (Install-AutopilotMonitor.ps1), loader <c>[ts] === Autopilot Monitor bootstrap loader vX.Y
/// started (user=..., pid=...) ===</c> (Start-AutopilotMonitor.ps1).
/// started (user=..., pid=...) ===</c> (Start-AutopilotMonitor.ps1), the MSI's downloader before
/// the loader <c>[ts] MSI bootstrap runner started (user=..., pid=...).</c>.
/// </summary>
public sealed class BootstrapProvenanceReaderTests : IDisposable
{
@@ -79,6 +80,39 @@ namespace AutopilotMonitor.Agent.V2.Core.Tests.Monitoring.Runtime
Assert.Null(p.ScriptVersion);
}
[Fact]
public void An_installer_from_before_the_log_rename_is_read_under_its_old_name()
{
Write(BootstrapProvenanceReader.LegacyInstallerLogName, InstallerRun("2.3"));
var p = BootstrapProvenanceReader.Read(_dir);
Assert.True(p.ScriptLogPresent);
Assert.Equal("2.3", p.ScriptVersion);
}
[Fact]
public void With_both_installer_logs_the_most_recently_written_one_counts()
{
Write(BootstrapProvenanceReader.InstallerLogName, InstallerRun("2.6"));
Write(BootstrapProvenanceReader.LegacyInstallerLogName, InstallerRun("2.3"));
File.SetLastWriteTimeUtc(Path.Combine(_dir, BootstrapProvenanceReader.InstallerLogName), DateTime.UtcNow.AddDays(-3));
File.SetLastWriteTimeUtc(Path.Combine(_dir, BootstrapProvenanceReader.LegacyInstallerLogName), DateTime.UtcNow.AddMinutes(-1));
Assert.Equal("2.3", BootstrapProvenanceReader.Read(_dir).ScriptVersion);
}
[Fact]
public void The_msi_downloader_from_before_the_loader_counts_as_msi_without_version()
{
Write(BootstrapProvenanceReader.LegacyMsiLoaderLogName, "[2026-08-01 09:00:00] MSI bootstrap runner started (user=SECRETUSER, pid=4242).");
var p = BootstrapProvenanceReader.Read(_dir);
Assert.Equal("msi", p.LoaderLog);
Assert.Null(p.LoaderVersion);
}
[Fact]
public void The_msi_loader_log_is_told_apart_from_the_intune_loader()
{
@@ -13,10 +13,13 @@ namespace AutopilotMonitor.Agent.V2.Core.Monitoring.Runtime
/// passing a version to <c>--install</c> would only ever cover installers released after that
/// change -- the old installers still assigned in Intune are exactly the ones worth counting.
/// <list type="bullet">
/// <item><c>bootstrap-script.log</c>: <c>Bootstrap script version: vX.Y</c>, written by every
/// <item><c>bootstrap-script.log</c> (<c>bootstrap_agent.log</c> up to installer v2.3, before
/// the log rename of 2026-08-18): <c>Bootstrap script version: vX.Y</c>, written by every
/// installer run since 2026-04-09. A log without that line means an older installer.</item>
/// <item><c>bootstrap-loader.log</c> (Intune) / <c>bootstrap-msi.log</c> (MSI):
/// <c>bootstrap loader vX.Y started</c>, written by every loader since v1.0.</item>
/// <item><c>bootstrap-loader.log</c> (Intune) / <c>bootstrap-msi.log</c> (MSI,
/// <c>msi-bootstrap.log</c> before 2026-08-18): <c>bootstrap loader vX.Y started</c>, written by
/// every loader since v1.0. The MSI's own downloader, which the loader replaced on 2026-09-08,
/// writes no version.</item>
/// <item><c>oobe_bootstrap.log</c>: the OOBE go-script (generated by the backend, no version).</item>
/// </list>
/// Only the version number is extracted; the loader line also names the account, which is
@@ -29,6 +32,10 @@ namespace AutopilotMonitor.Agent.V2.Core.Monitoring.Runtime
internal const string MsiLoaderLogName = "bootstrap-msi.log";
internal const string OobeLogName = "oobe_bootstrap.log";
// Names before the log rename of 2026-08-18; copies of those scripts are still assigned.
internal const string LegacyInstallerLogName = "bootstrap_agent.log";
internal const string LegacyMsiLoaderLogName = "msi-bootstrap.log";
// The logs grow across runs; the last run's lines sit at the end. One run writes a few KB.
private const int TailBytes = 64 * 1024;
@@ -42,22 +49,19 @@ namespace AutopilotMonitor.Agent.V2.Core.Monitoring.Runtime
{
try
{
var installerLog = ReadTail(Path.Combine(logDirectory, InstallerLogName));
var installerLog = ReadTail(MostRecentlyWritten(logDirectory, InstallerLogName, LegacyInstallerLogName));
// Intune loader and MSI loader write separate files; when a device has both, the
// more recently written one belongs to the bootstrap that ran last.
var loader = new[] { (Name: LoaderLogName, Channel: "loader"), (Name: MsiLoaderLogName, Channel: "msi") }
.Select(l => (l.Channel, Path: Path.Combine(logDirectory, l.Name)))
.Where(l => File.Exists(l.Path))
.OrderByDescending(l => File.GetLastWriteTimeUtc(l.Path))
.Select(l => (l.Channel, Version: LastVersion(ReadTail(l.Path), LoaderStartLine)))
.FirstOrDefault();
// Intune loader and MSI loader write separate files; every name but the Intune
// loader's is the MSI's.
var loaderPath = MostRecentlyWritten(logDirectory, LoaderLogName, MsiLoaderLogName, LegacyMsiLoaderLogName);
var loaderChannel = loaderPath == null ? null
: string.Equals(Path.GetFileName(loaderPath), LoaderLogName, StringComparison.OrdinalIgnoreCase) ? "loader" : "msi";
return new BootstrapProvenance(
scriptLogPresent: installerLog != null,
scriptVersion: LastVersion(installerLog, InstallerVersionLine),
loaderLog: loader.Channel,
loaderVersion: loader.Version,
loaderLog: loaderChannel,
loaderVersion: LastVersion(ReadTail(loaderPath), LoaderStartLine),
oobeLogPresent: File.Exists(Path.Combine(logDirectory, OobeLogName)));
}
catch
@@ -66,6 +70,16 @@ namespace AutopilotMonitor.Agent.V2.Core.Monitoring.Runtime
}
}
/// <summary>
/// The most recently written of the named logs that exist, or null. On a device with
/// several, it belongs to the bootstrap that ran last.
/// </summary>
private static string MostRecentlyWritten(string directory, params string[] names) =>
names.Select(name => Path.Combine(directory, name))
.Where(path => File.Exists(path))
.OrderByDescending(path => File.GetLastWriteTimeUtc(path))
.FirstOrDefault();
internal static string LastVersion(string text, Regex line)
{
if (string.IsNullOrEmpty(text)) return null;