initial commit
This commit is contained in:
24
BlueberryUpdater/BlueberryUpdater.csproj
Normal file
24
BlueberryUpdater/BlueberryUpdater.csproj
Normal file
@@ -0,0 +1,24 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>net8.0-windows10.0.17763.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<ApplicationManifest>app.manifest</ApplicationManifest>
|
||||
<ApplicationIcon>bb.ico</ApplicationIcon>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Content Include="bb.ico">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<EmbeddedResource Include="AppPayload.zip">
|
||||
<Visible>false</Visible>
|
||||
</EmbeddedResource>
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
195
BlueberryUpdater/Program.cs
Normal file
195
BlueberryUpdater/Program.cs
Normal file
@@ -0,0 +1,195 @@
|
||||
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}");
|
||||
}
|
||||
}
|
||||
}
|
||||
79
BlueberryUpdater/app.manifest
Normal file
79
BlueberryUpdater/app.manifest
Normal file
@@ -0,0 +1,79 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<assembly manifestVersion="1.0" xmlns="urn:schemas-microsoft-com:asm.v1">
|
||||
<assemblyIdentity version="1.0.0.0" name="MyApplication.app"/>
|
||||
<trustInfo xmlns="urn:schemas-microsoft-com:asm.v2">
|
||||
<security>
|
||||
<requestedPrivileges xmlns="urn:schemas-microsoft-com:asm.v3">
|
||||
<!-- UAC Manifest Options
|
||||
If you want to change the Windows User Account Control level replace the
|
||||
requestedExecutionLevel node with one of the following.
|
||||
|
||||
<requestedExecutionLevel level="asInvoker" uiAccess="false" />
|
||||
<requestedExecutionLevel level="requireAdministrator" uiAccess="false" />
|
||||
<requestedExecutionLevel level="highestAvailable" uiAccess="false" />
|
||||
|
||||
Specifying requestedExecutionLevel element will disable file and registry virtualization.
|
||||
Remove this element if your application requires this virtualization for backwards
|
||||
compatibility.
|
||||
-->
|
||||
<requestedExecutionLevel level="requireAdministrator" uiAccess="false" />
|
||||
</requestedPrivileges>
|
||||
</security>
|
||||
</trustInfo>
|
||||
|
||||
<compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
|
||||
<application>
|
||||
<!-- A list of the Windows versions that this application has been tested on
|
||||
and is designed to work with. Uncomment the appropriate elements
|
||||
and Windows will automatically select the most compatible environment. -->
|
||||
|
||||
<!-- Windows Vista -->
|
||||
<!--<supportedOS Id="{e2011457-1546-43c5-a5fe-008deee3d3f0}" />-->
|
||||
|
||||
<!-- Windows 7 -->
|
||||
<!--<supportedOS Id="{35138b9a-5d96-4fbd-8e2d-a2440225f93a}" />-->
|
||||
|
||||
<!-- Windows 8 -->
|
||||
<!--<supportedOS Id="{4a2f28e3-53b9-4441-ba9c-d69d4a4a6e38}" />-->
|
||||
|
||||
<!-- Windows 8.1 -->
|
||||
<!--<supportedOS Id="{1f676c76-80e1-4239-95bb-83d0f6d0da78}" />-->
|
||||
|
||||
<!-- Windows 10 -->
|
||||
<!--<supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}" />-->
|
||||
|
||||
</application>
|
||||
</compatibility>
|
||||
|
||||
<!-- Indicates that the application is DPI-aware and will not be automatically scaled by Windows at higher
|
||||
DPIs. Windows Presentation Foundation (WPF) applications are automatically DPI-aware and do not need
|
||||
to opt in. Windows Forms applications targeting .NET Framework 4.6 that opt into this setting, should
|
||||
also set the 'EnableWindowsFormsHighDpiAutoResizing' setting to 'true' in their app.config.
|
||||
|
||||
Makes the application long-path aware. See https://docs.microsoft.com/windows/win32/fileio/maximum-file-path-limitation -->
|
||||
<!--
|
||||
<application xmlns="urn:schemas-microsoft-com:asm.v3">
|
||||
<windowsSettings>
|
||||
<dpiAware xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">true</dpiAware>
|
||||
<longPathAware xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">true</longPathAware>
|
||||
</windowsSettings>
|
||||
</application>
|
||||
-->
|
||||
|
||||
<!-- Enable themes for Windows common controls and dialogs (Windows XP and later) -->
|
||||
<!--
|
||||
<dependency>
|
||||
<dependentAssembly>
|
||||
<assemblyIdentity
|
||||
type="win32"
|
||||
name="Microsoft.Windows.Common-Controls"
|
||||
version="6.0.0.0"
|
||||
processorArchitecture="*"
|
||||
publicKeyToken="6595b64144ccf1df"
|
||||
language="*"
|
||||
/>
|
||||
</dependentAssembly>
|
||||
</dependency>
|
||||
-->
|
||||
|
||||
</assembly>
|
||||
BIN
BlueberryUpdater/bb.ico
Normal file
BIN
BlueberryUpdater/bb.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 39 KiB |
Reference in New Issue
Block a user