diff --git a/scripts/Bootstrap/Install-AutopilotMonitor.ps1 b/scripts/Bootstrap/Install-AutopilotMonitor.ps1 index 273a64b3..5a4b2279 100644 --- a/scripts/Bootstrap/Install-AutopilotMonitor.ps1 +++ b/scripts/Bootstrap/Install-AutopilotMonitor.ps1 @@ -37,16 +37,11 @@ param( # Configuration - Everything in ProgramData for easy cleanup $AgentBasePath = "$env:ProgramData\AutopilotMonitor" $AgentBinPath = "$AgentBasePath\Agent" -$AgentSpoolPath = "$AgentBasePath\Spool" -$AgentLogsPath = "$AgentBasePath\Logs" -$TaskName = "AutopilotMonitor-Agent" -$LogFile = "$AgentLogsPath\Bootstrap.log" +$LogFile = "$AgentBasePath\Bootstrap.log" # Ensure directories exist New-Item -Path $AgentBasePath -ItemType Directory -Force -ErrorAction SilentlyContinue | Out-Null New-Item -Path $AgentBinPath -ItemType Directory -Force -ErrorAction SilentlyContinue | Out-Null -New-Item -Path $AgentSpoolPath -ItemType Directory -Force -ErrorAction SilentlyContinue | Out-Null -New-Item -Path $AgentLogsPath -ItemType Directory -Force -ErrorAction SilentlyContinue | Out-Null function Write-Log { param([string]$Message) @@ -213,54 +208,18 @@ try { } } - # Register as Scheduled Task (survives reboots, easy to remove) - Write-Log "Registering Scheduled Task..." - - # Check if task already exists - $existingTask = Get-ScheduledTask -TaskName $TaskName -ErrorAction SilentlyContinue - - if ($null -ne $existingTask) { - Write-Log "Scheduled Task already exists - removing old task" - Unregister-ScheduledTask -TaskName $TaskName -Confirm:$false + # Let the agent install/deploy itself and manage its own Scheduled Task + Write-Log "Calling agent install mode (--install)..." + & $agentExePath --install + $installExitCode = $LASTEXITCODE + if ($installExitCode -ne 0) { + throw "Agent install failed with exit code $installExitCode" } - - # Create action - run agent executable - $action = New-ScheduledTaskAction -Execute $agentExePath -WorkingDirectory $AgentBinPath - - # Create trigger - run at computer startup - $trigger = New-ScheduledTaskTrigger -AtStartup - - # Create settings - run whether user is logged on or not - $settings = New-ScheduledTaskSettingsSet ` - -AllowStartIfOnBatteries ` - -DontStopIfGoingOnBatteries ` - -StartWhenAvailable ` - -RunOnlyIfNetworkAvailable ` - -ExecutionTimeLimit (New-TimeSpan -Hours 0) # No time limit - - # Create principal - run as SYSTEM - $principal = New-ScheduledTaskPrincipal -UserId "SYSTEM" -LogonType ServiceAccount -RunLevel Highest - - # Register the task - Register-ScheduledTask ` - -TaskName $TaskName ` - -Action $action ` - -Trigger $trigger ` - -Settings $settings ` - -Principal $principal ` - -Description "Autopilot Monitor Agent - Temporary enrollment monitoring" ` - -Force - - Write-Log "Scheduled Task registered successfully" - - # Start the task immediately (don't wait for next reboot) - Write-Log "Starting agent task..." - Start-ScheduledTask -TaskName $TaskName - Write-Log "Agent task started" + Write-Log "Agent install mode completed successfully" Write-Log "===== Bootstrap Completed Successfully =====" Write-Log "Agent Path: $AgentBasePath" - Write-Log "Scheduled Task: $TaskName" + Write-Log "Scheduled Task: managed by agent (--install)" Write-Log "Agent will monitor enrollment and auto-remove when complete" Write-Log "Bootstrap log: $LogFile" Write-Log "" diff --git a/src/Agent/AutopilotMonitor.Agent/Program.cs b/src/Agent/AutopilotMonitor.Agent/Program.cs index 24ab0048..f175a61d 100644 --- a/src/Agent/AutopilotMonitor.Agent/Program.cs +++ b/src/Agent/AutopilotMonitor.Agent/Program.cs @@ -15,6 +15,13 @@ namespace AutopilotMonitor.Agent { static void Main(string[] args) { + // Install mode: ensure folders, deploy payload, create/update Scheduled Task, and start it. + if (args.Contains("--install")) + { + RunInstallMode(args); + return; + } + // Check for mass rollout simulator mode if (args.Contains("--simulator-mass-rollout")) { @@ -28,6 +35,86 @@ namespace AutopilotMonitor.Agent RunAgent(args); } + static void RunInstallMode(string[] args) + { + var logDir = Environment.ExpandEnvironmentVariables(@"%ProgramData%\AutopilotMonitor\Logs"); + var logger = new AgentLogger(logDir, enableDebug: true); + var consoleMode = args.Contains("--console") || Environment.UserInteractive; + + try + { + logger.Info("======================= Agent install mode (--install) ======================="); + + EnsureAgentDirectories(logger); + + var sourceExePath = Assembly.GetExecutingAssembly().Location; + var sourceDir = Path.GetDirectoryName(sourceExePath) ?? string.Empty; + var targetAgentDir = Environment.ExpandEnvironmentVariables(@"%ProgramData%\AutopilotMonitor\Agent"); + var targetExePath = Path.Combine(targetAgentDir, "AutopilotMonitor.Agent.exe"); + + if (!string.Equals( + Path.GetFullPath(sourceDir).TrimEnd('\\'), + Path.GetFullPath(targetAgentDir).TrimEnd('\\'), + StringComparison.OrdinalIgnoreCase)) + { + logger.Info($"Install mode called from '{sourceDir}'. Deploying payload to '{targetAgentDir}'."); + CopyDirectory(sourceDir, targetAgentDir, logger); + } + else + { + logger.Info("Agent already running from target install directory; payload copy not required."); + } + + var taskName = "AutopilotMonitor-Agent"; + var taskCommand = $"\"{targetExePath}\""; + + logger.Info($"Registering Scheduled Task '{taskName}' for executable: {targetExePath}"); + + var createExitCode = RunProcess( + "schtasks.exe", + $"/Create /TN \"{taskName}\" /TR \"{taskCommand}\" /SC ONSTART /RU SYSTEM /RL HIGHEST /F", + logger); + + if (createExitCode != 0) + { + throw new InvalidOperationException($"Failed to create/update Scheduled Task '{taskName}' (exit code {createExitCode})"); + } + + logger.Info($"Scheduled Task '{taskName}' created/updated successfully"); + + var startExitCode = RunProcess( + "schtasks.exe", + $"/Run /TN \"{taskName}\"", + logger); + + if (startExitCode != 0) + { + throw new InvalidOperationException($"Failed to start Scheduled Task '{taskName}' (exit code {startExitCode})"); + } + + logger.Info($"Scheduled Task '{taskName}' started successfully"); + + if (consoleMode) + { + Console.WriteLine("Installation completed successfully."); + Console.WriteLine($"Task: {taskName}"); + Console.WriteLine($"Executable: {targetExePath}"); + Console.WriteLine($"Log: {logDir}"); + } + + Environment.Exit(0); + } + catch (Exception ex) + { + logger.Error("Registration mode failed", ex); + if (consoleMode) + { + Console.WriteLine($"ERROR: {ex.Message}"); + } + Environment.Exit(1); + } + } + static void RunAgent(string[] args) { var consoleMode = args.Contains("--console") || Environment.UserInteractive; @@ -416,6 +503,103 @@ namespace AutopilotMonitor.Agent return null; } + private static void EnsureAgentDirectories(AgentLogger logger) + { + var basePath = Environment.ExpandEnvironmentVariables(@"%ProgramData%\AutopilotMonitor"); + var paths = new[] + { + basePath, + Path.Combine(basePath, "Agent"), + Path.Combine(basePath, "Spool"), + Path.Combine(basePath, "Logs") + }; + + foreach (var path in paths) + { + if (!Directory.Exists(path)) + { + Directory.CreateDirectory(path); + logger.Info($"Created directory: {path}"); + } + else + { + logger.Debug($"Directory already exists: {path}"); + } + } + } + + private static int RunProcess(string fileName, string arguments, AgentLogger logger) + { + var psi = new ProcessStartInfo + { + FileName = fileName, + Arguments = arguments, + UseShellExecute = false, + RedirectStandardOutput = true, + RedirectStandardError = true, + CreateNoWindow = true + }; + + logger.Info($"Executing: {fileName} {arguments}"); + + using (var process = Process.Start(psi)) + { + if (process == null) + throw new InvalidOperationException($"Failed to start process: {fileName}"); + + var stdout = process.StandardOutput.ReadToEnd(); + var stderr = process.StandardError.ReadToEnd(); + process.WaitForExit(); + + if (!string.IsNullOrWhiteSpace(stdout)) + logger.Info($"Process output: {stdout.Trim()}"); + if (!string.IsNullOrWhiteSpace(stderr)) + logger.Warning($"Process error output: {stderr.Trim()}"); + + logger.Info($"Process exit code: {process.ExitCode}"); + return process.ExitCode; + } + } + + private static void CopyDirectory(string sourceDir, string targetDir, AgentLogger logger) + { + if (!Directory.Exists(sourceDir)) + throw new DirectoryNotFoundException($"Source directory not found: {sourceDir}"); + + Directory.CreateDirectory(targetDir); + + foreach (var dir in Directory.GetDirectories(sourceDir, "*", SearchOption.AllDirectories)) + { + var rel = dir.Substring(sourceDir.Length).TrimStart('\\'); + Directory.CreateDirectory(Path.Combine(targetDir, rel)); + } + + foreach (var file in Directory.GetFiles(sourceDir, "*", SearchOption.AllDirectories)) + { + var rel = file.Substring(sourceDir.Length).TrimStart('\\'); + var dest = Path.Combine(targetDir, rel); + Directory.CreateDirectory(Path.GetDirectoryName(dest) ?? targetDir); + + try + { + File.Copy(file, dest, true); + } + catch (Exception ex) + { + // If destination exists, keep going and let scheduled task use the latest available payload. + if (File.Exists(dest)) + { + logger.Warning($"Could not overwrite '{dest}': {ex.Message}. Keeping existing file."); + continue; + } + + throw; + } + } + + logger.Info($"Payload deployment completed: '{sourceDir}' -> '{targetDir}'"); + } + /// /// Checks if another AutopilotMonitor.Agent.exe process is already running. /// diff --git a/src/Agent/AutopilotMonitor.Agent/buildcounter.txt b/src/Agent/AutopilotMonitor.Agent/buildcounter.txt index ec635144..48082f72 100644 --- a/src/Agent/AutopilotMonitor.Agent/buildcounter.txt +++ b/src/Agent/AutopilotMonitor.Agent/buildcounter.txt @@ -1 +1 @@ -9 +12