using System.Diagnostics; using System.IO.Compression; using System.Reflection; namespace BlueberryUpdater { internal class Program { static string appName = "Blueberry"; static string updaterName = appName + "Updater"; static string installPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles), appName); static void Main(string[] args) { if (Directory.Exists(installPath) && File.Exists(Path.Combine(installPath, appName + ".exe"))) Uninstall(); else Install(); } private static void Uninstall() { Console.WriteLine("Would you like to uninstall Blueberry? [y/N]"); var key = Console.ReadLine(); if (key == null ||key.ToLower() != "y") return; var appdata = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), appName); Console.WriteLine("Removing Blueberry..."); var dirs = Directory.GetDirectories(installPath); var files = Directory.GetFiles(installPath); var total = dirs.Length + files.Length; var i = 0; foreach (var dir in dirs) { i++; Directory.Delete(dir, true); DrawProgressBar((int)((double)i / total * 100), dir.Split('\\').Last()); } foreach (var file in files) { i++; if (file.Split('\\').Last() == updaterName + ".exe") continue; File.Delete(file); i++; DrawProgressBar((int)((double)i / total * 100), file.Split('\\').Last()); } Directory.Delete(appdata, true); DrawProgressBar(100, "Done"); Console.WriteLine(); Console.WriteLine("Uninstall Complete!"); Console.WriteLine("Press any key to exit..."); Console.ReadKey(); SelfDelete(); } private static void Install() { try { string resourceName = "BlueberryUpdater.AppPayload.zip"; // Format: Namespace.Filename Console.WriteLine($"Installing to {installPath}..."); // 1. Clean existing install if necessary if (Directory.Exists(installPath)) { Directory.Delete(installPath, true); } Directory.CreateDirectory(installPath); // 2. Extract Embedded Resource var assembly = Assembly.GetExecutingAssembly(); using (Stream stream = assembly.GetManifestResourceStream(resourceName)) { if (stream == null) { throw new Exception($"Resource '{resourceName}' not found. Check Embedded Resource settings."); } using (ZipArchive archive = new(stream)) { int totalEntries = archive.Entries.Count; int currentEntry = 0; foreach (ZipArchiveEntry entry in archive.Entries) { currentEntry++; // Calculate percentage int percent = (int)((double)currentEntry / totalEntries * 100); // Draw Progress Bar DrawProgressBar(percent, entry.Name); // Create the full path string destinationPath = Path.GetFullPath(Path.Combine(installPath, entry.FullName)); // Security check: prevent ZipSlip (writing outside target folder) if (!destinationPath.StartsWith(installPath, StringComparison.OrdinalIgnoreCase)) continue; // Handle folders vs files if (string.IsNullOrEmpty(entry.Name)) // It's a directory { Directory.CreateDirectory(destinationPath); } else // It's a file { // Ensure the directory exists (zipped files might not list their dir first) Directory.CreateDirectory(Path.GetDirectoryName(destinationPath)); entry.ExtractToFile(destinationPath, overwrite: true); } } } } MoveUpdater(); DrawProgressBar(100, "Done"); Console.WriteLine(); Console.WriteLine("Installation Complete!"); // Optional: Create Shortcut logic here } catch (Exception ex) { Console.ForegroundColor = ConsoleColor.Red; Console.WriteLine($"Error: {ex.Message}"); } Console.WriteLine("Press any key to exit..."); Console.ReadKey(); } public static void SelfDelete() { string exePath = Process.GetCurrentProcess().MainModule.FileName; string directoryToDelete = Path.GetDirectoryName(exePath); string args = $"/C timeout /t 2 /nobreak > Nul & del \"{exePath}\" & rmdir /q \"{directoryToDelete}\""; ProcessStartInfo psi = new ProcessStartInfo { FileName = "cmd.exe", Arguments = args, WindowStyle = ProcessWindowStyle.Hidden, CreateNoWindow = true, UseShellExecute = false }; Process.Start(psi); Environment.Exit(0); } public static void MoveUpdater() { string currentExe = Process.GetCurrentProcess().MainModule.FileName; var updaterPath = Path.Combine(installPath, updaterName + ".exe"); Directory.CreateDirectory(Path.GetDirectoryName(updaterPath)); File.Copy(currentExe, updaterPath, overwrite: true); } static void DrawProgressBar(int percent, string filename) { // Move cursor to start of line Console.CursorLeft = 0; // Limit filename length for clean display string shortName = filename.Length > 20 ? filename.Substring(0, 17) + "..." : filename.PadRight(20); Console.Write("["); int width = Console.WindowWidth - 1; // Width of the bar int progress = (int)((percent / 100.0) * width); // Draw filled part Console.Write(new string('#', progress)); // Draw empty part Console.Write(new string('-', width - progress)); Console.Write($"] {percent}% {shortName}"); } } }