using System.Diagnostics; using System.ServiceProcess; using System.Security.Principal; namespace PrintServiceCLI; /// /// Manages Windows Service installation and verification /// public static class ServiceManager { private const string ServiceName = "LAAPC Print Service"; private const string ServiceInstallPath = @"C:\ProgramData\LAAPC"; private const string ServiceExeName = "PrintService.exe"; private static bool? _serviceAvailable = null; // Cache result /// /// Check if service is installed and running, with auto-install option /// public static bool EnsureServiceRunning(bool silent = false) { // Return cached result if already checked if (_serviceAvailable.HasValue) return _serviceAvailable.Value; try { // Check if service exists and is running using var service = new ServiceController(ServiceName); service.Refresh(); if (service.Status == ServiceControllerStatus.Running) { _serviceAvailable = true; return true; } // Service exists but not running - try to start it if (service.Status == ServiceControllerStatus.Stopped) { if (!silent) Console.WriteLine("Service is stopped. Starting..."); service.Start(); service.WaitForStatus(ServiceControllerStatus.Running, TimeSpan.FromSeconds(10)); _serviceAvailable = true; return true; } _serviceAvailable = false; return false; } catch (InvalidOperationException) { // Service doesn't exist - offer to install if (silent) { _serviceAvailable = false; return false; } return PromptAndInstall(); } catch (Exception ex) { if (!silent) Console.Error.WriteLine($"Error checking service: {ex.Message}"); _serviceAvailable = false; return false; } } /// /// Prompt user and install service if they agree /// private static bool PromptAndInstall() { Console.WriteLine(); Console.WriteLine("===================================================="); Console.WriteLine("LAAPC Print Service is not installed on this PC."); Console.WriteLine("===================================================="); Console.WriteLine(); Console.WriteLine("The service must be installed locally to process print jobs."); Console.WriteLine("This is a one-time setup that requires administrator access."); Console.WriteLine(); Console.Write("Install the service now? [Y/N]: "); var response = Console.ReadLine()?.Trim().ToUpper(); if (response != "Y" && response != "YES") { Console.WriteLine("Installation cancelled. Service is required for printing."); _serviceAvailable = false; return false; } return InstallService(); } /// /// Install and start the service with UAC elevation /// private static bool InstallService() { try { Console.WriteLine(); Console.WriteLine("Installing service..."); // Check if running as admin if (!IsAdministrator()) { Console.WriteLine("Requesting administrator privileges..."); return InstallWithElevation(); } // Copy service files to local machine var cliPath = AppContext.BaseDirectory; var serviceSourcePath = Path.Combine(Path.GetDirectoryName(cliPath)!, ServiceExeName); if (!File.Exists(serviceSourcePath)) { // Try relative path serviceSourcePath = Path.Combine(cliPath, "..", "PrintService", ServiceExeName); serviceSourcePath = Path.GetFullPath(serviceSourcePath); } if (!File.Exists(serviceSourcePath)) { Console.Error.WriteLine($"ERROR: Could not find {ServiceExeName}"); Console.Error.WriteLine($"Expected location: {serviceSourcePath}"); _serviceAvailable = false; return false; } // Create installation directory Directory.CreateDirectory(ServiceInstallPath); // Copy service executable and dependencies var serviceDestPath = Path.Combine(ServiceInstallPath, ServiceExeName); CopyServiceFiles(Path.GetDirectoryName(serviceSourcePath)!, ServiceInstallPath); Console.WriteLine($"Files copied to: {ServiceInstallPath}"); // Install Windows Service using sc.exe var scInstall = Process.Start(new ProcessStartInfo { FileName = "sc.exe", Arguments = $"create \"{ServiceName}\" binPath=\"{serviceDestPath}\" start=auto", UseShellExecute = false, RedirectStandardOutput = true, RedirectStandardError = true, CreateNoWindow = true }); scInstall?.WaitForExit(); if (scInstall?.ExitCode != 0) { Console.Error.WriteLine("ERROR: Failed to install service"); _serviceAvailable = false; return false; } Console.WriteLine("Service installed successfully."); // Start the service Console.WriteLine("Starting service..."); var scStart = Process.Start(new ProcessStartInfo { FileName = "sc.exe", Arguments = $"start \"{ServiceName}\"", UseShellExecute = false, RedirectStandardOutput = true, RedirectStandardError = true, CreateNoWindow = true }); scStart?.WaitForExit(); if (scStart?.ExitCode != 0) { Console.Error.WriteLine("WARNING: Service installed but failed to start"); Console.Error.WriteLine("Try running: sc start \"LAAPC Print Service\""); _serviceAvailable = false; return false; } // Wait for service to be ready Thread.Sleep(2000); Console.WriteLine("Service started successfully!"); Console.WriteLine(); _serviceAvailable = true; return true; } catch (Exception ex) { Console.Error.WriteLine($"ERROR: Installation failed: {ex.Message}"); _serviceAvailable = false; return false; } } /// /// Restart process with admin elevation /// private static bool InstallWithElevation() { try { var startInfo = new ProcessStartInfo { FileName = Process.GetCurrentProcess().MainModule?.FileName ?? "PrintServiceCLI.exe", Arguments = "--install-service", UseShellExecute = true, Verb = "runas" // Trigger UAC prompt }; var process = Process.Start(startInfo); process?.WaitForExit(); if (process?.ExitCode == 0) { Console.WriteLine("Service installed successfully."); _serviceAvailable = true; return true; } Console.Error.WriteLine("Installation was cancelled or failed."); _serviceAvailable = false; return false; } catch (Exception ex) { Console.Error.WriteLine($"ERROR: Could not elevate privileges: {ex.Message}"); _serviceAvailable = false; return false; } } /// /// Copy service files from source to destination /// private static void CopyServiceFiles(string sourceDir, string destDir) { // Copy main executable var sourceExe = Path.Combine(sourceDir, ServiceExeName); var destExe = Path.Combine(destDir, ServiceExeName); File.Copy(sourceExe, destExe, overwrite: true); // Copy all DLLs and config files foreach (var file in Directory.GetFiles(sourceDir)) { var fileName = Path.GetFileName(file); var ext = Path.GetExtension(file).ToLower(); if (ext == ".dll" || ext == ".json" || ext == ".config") { var destFile = Path.Combine(destDir, fileName); File.Copy(file, destFile, overwrite: true); } } // Create necessary folders var capturesPath = Path.Combine(destDir, "Captures"); var queuePath = Path.Combine(destDir, "Queue"); var archivePath = Path.Combine(destDir, "Archive"); var errorPath = Path.Combine(destDir, "Error"); var outputPath = Path.Combine(destDir, "Output"); Directory.CreateDirectory(capturesPath); Directory.CreateDirectory(queuePath); Directory.CreateDirectory(archivePath); Directory.CreateDirectory(errorPath); Directory.CreateDirectory(outputPath); } /// /// Check if running with administrator privileges /// private static bool IsAdministrator() { using var identity = WindowsIdentity.GetCurrent(); var principal = new WindowsPrincipal(identity); return principal.IsInRole(WindowsBuiltInRole.Administrator); } }