291 lines
10 KiB
C#
291 lines
10 KiB
C#
using System.Diagnostics;
|
|
using System.ServiceProcess;
|
|
using System.Security.Principal;
|
|
|
|
namespace PrintServiceCLI;
|
|
|
|
/// <summary>
|
|
/// Manages Windows Service installation and verification
|
|
/// </summary>
|
|
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
|
|
|
|
/// <summary>
|
|
/// Check if service is installed and running, with auto-install option
|
|
/// </summary>
|
|
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;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Prompt user and install service if they agree
|
|
/// </summary>
|
|
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();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Install and start the service with UAC elevation
|
|
/// </summary>
|
|
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;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Restart process with admin elevation
|
|
/// </summary>
|
|
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;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Copy service files from source to destination
|
|
/// </summary>
|
|
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);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Check if running with administrator privileges
|
|
/// </summary>
|
|
private static bool IsAdministrator()
|
|
{
|
|
using var identity = WindowsIdentity.GetCurrent();
|
|
var principal = new WindowsPrincipal(identity);
|
|
return principal.IsInRole(WindowsBuiltInRole.Administrator);
|
|
}
|
|
}
|