Fortschritt, .NET-Prüfung, Updates, Fenster merken, Box-Funktionen
- Große Kopien laufen im Hintergrund mit Fortschritt und Abbrechen; abgebrochene Kopien werden aufgeräumt, das Original bleibt unverändert - Sichern/Wiederherstellen mit Fortschritt, keine halben ZIP-Dateien - Installer auf .NET Framework 4.8: erkennt fehlende .NET 9 Desktop Runtime und installiert sie auf Wunsch (Microsoft-Signatur wird geprüft) - Update-Hinweis über Gitea-Releases, Update mit automatischem Neustart - Fenstergröße und -position werden gemerkt - Desktop-Boxen: umbenennen, löschen, verschieben, herausziehen - Fenster und Boxen kehren auf sichtbare Bildschirme zurück - README ergänzt Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
+10
-3
@@ -11,7 +11,8 @@ public partial class App : Application
|
||||
base.OnStartup(e);
|
||||
bool uninstall = e.Args.Any(a => a.Equals("--uninstall", StringComparison.OrdinalIgnoreCase));
|
||||
bool silent = e.Args.Any(a => a.Equals("/S", StringComparison.OrdinalIgnoreCase));
|
||||
var dirArg = e.Args.FirstOrDefault(a => a.StartsWith("/D=", StringComparison.OrdinalIgnoreCase))?[3..];
|
||||
bool launch = e.Args.Any(a => a.Equals("/launch", StringComparison.OrdinalIgnoreCase));
|
||||
var dirArg = e.Args.FirstOrDefault(a => a.StartsWith("/D=", StringComparison.OrdinalIgnoreCase))?.Substring(3);
|
||||
|
||||
// Stille Installation/Deinstallation für Skripte: DeskCore-Setup.exe /S [/D=Ordner] bzw. --uninstall /S
|
||||
if (silent)
|
||||
@@ -20,8 +21,14 @@ public partial class App : Application
|
||||
try
|
||||
{
|
||||
if (uninstall) Installer.Uninstall(removeData: false, progress);
|
||||
else Installer.Install(dirArg ?? Installer.InstalledDir ?? Installer.DefaultDir,
|
||||
desktopLink: true, autostart: Installer.AutostartEnabled, progress);
|
||||
else
|
||||
{
|
||||
var dir = dirArg ?? Installer.InstalledDir ?? Installer.DefaultDir;
|
||||
Installer.Install(dir, desktopLink: Installer.InstalledDir is null || Installer.DesktopLinkExists,
|
||||
autostart: Installer.AutostartEnabled, progress, installRuntime: true);
|
||||
// nach einem Update aus DeskCore heraus: gleich wieder starten
|
||||
if (launch) System.Diagnostics.Process.Start(System.IO.Path.Combine(dir, Installer.ExeName));
|
||||
}
|
||||
Shutdown(0);
|
||||
}
|
||||
catch (Exception ex)
|
||||
|
||||
@@ -1,10 +1,17 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<OutputType>WinExe</OutputType>
|
||||
<TargetFramework>net9.0-windows</TargetFramework>
|
||||
<!-- .NET Framework 4.8 ist in jedem Windows 10/11 enthalten → der Installer läuft immer
|
||||
und kann die für DeskCore nötige .NET-9-Laufzeit selbst nachinstallieren -->
|
||||
<TargetFramework>net48</TargetFramework>
|
||||
<UseWPF>true</UseWPF>
|
||||
<UseWindowsForms>true</UseWindowsForms>
|
||||
<LangVersion>latest</LangVersion>
|
||||
<Nullable>enable</Nullable>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<PlatformTarget>AnyCPU</PlatformTarget>
|
||||
<Prefer32Bit>false</Prefer32Bit>
|
||||
<GenerateDocumentationFile>false</GenerateDocumentationFile>
|
||||
<AssemblyName>DeskCore-Setup</AssemblyName>
|
||||
<RootNamespace>DeskCoreSetup</RootNamespace>
|
||||
<ApplicationIcon>..\app.ico</ApplicationIcon>
|
||||
@@ -15,9 +22,17 @@
|
||||
<Authors>M_Viper</Authors>
|
||||
<Copyright>Copyright (c) M_Viper. Alle Rechte vorbehalten.</Copyright>
|
||||
<NeutralLanguage>de-DE</NeutralLanguage>
|
||||
<!-- fertig veröffentlichte DeskCore.exe, die eingepackt wird (build.ps1 legt sie hier ab) -->
|
||||
<!-- fertig veröffentlichte DeskCore.exe, die eingepackt wird (build.ps1 legt sie hier ab) -->
|
||||
<AppPayload Condition="'$(AppPayload)' == ''">..\artifacts\app\DeskCore.exe</AppPayload>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<!-- WinForms nur für den Ordner-Dialog; nicht global, sonst Namenskonflikte mit WPF -->
|
||||
<Using Remove="System.Windows.Forms" />
|
||||
<Using Remove="System.Drawing" />
|
||||
<Reference Include="Microsoft.CSharp" />
|
||||
<Reference Include="Microsoft.VisualBasic" />
|
||||
<Reference Include="System.Net.Http" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<!-- gleiches Design wie die App -->
|
||||
<Page Include="..\Themes\Colors.Light.xaml" Link="Themes\Colors.Light.xaml" />
|
||||
|
||||
+26
-6
@@ -23,6 +23,9 @@ public static class Installer
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Pfad dieses Setups (auch als Deinstallationsprogramm).</summary>
|
||||
public static string SelfPath => System.Reflection.Assembly.GetExecutingAssembly().Location;
|
||||
|
||||
public static string DefaultDir =>
|
||||
Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "Programs", AppName);
|
||||
|
||||
@@ -66,8 +69,19 @@ public static class Installer
|
||||
|
||||
// ---------- Installieren ----------
|
||||
|
||||
public static void Install(string dir, bool desktopLink, bool autostart, IProgress<(double, string)> progress)
|
||||
public static void Install(string dir, bool desktopLink, bool autostart, IProgress<(double, string)> progress,
|
||||
bool installRuntime = false)
|
||||
{
|
||||
if (!Runtime.Is64BitWindows)
|
||||
throw new InvalidOperationException("DeskCore benötigt ein 64-Bit-Windows (Windows 10 oder 11).");
|
||||
if (installRuntime && !Runtime.IsInstalled)
|
||||
{
|
||||
// 0–60 % für die Laufzeit, Rest für DeskCore
|
||||
Runtime.Install((v, text) => progress.Report((v * 0.6, text)));
|
||||
var inner = progress;
|
||||
progress = new Scaled(inner, 0.6, 0.4);
|
||||
}
|
||||
|
||||
progress.Report((0.05, "DeskCore wird beendet …"));
|
||||
CloseRunningApp();
|
||||
RemoveLegacyInstall();
|
||||
@@ -79,7 +93,7 @@ public static class Installer
|
||||
WriteResource("payload.LICENSE.txt", Path.Combine(dir, "LICENSE.txt"));
|
||||
|
||||
// Setup selbst als Deinstallationsprogramm ablegen
|
||||
var self = Environment.ProcessPath!;
|
||||
var self = SelfPath;
|
||||
var uninstaller = Path.Combine(dir, UninstallerName);
|
||||
if (!string.Equals(Path.GetFullPath(self), Path.GetFullPath(uninstaller), StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
@@ -149,7 +163,7 @@ public static class Installer
|
||||
{
|
||||
if (p.MainModule?.FileName is { } f && Storage_IsUnder(f, dir))
|
||||
{
|
||||
p.Kill(entireProcessTree: true);
|
||||
p.Kill();
|
||||
p.WaitForExit(10000);
|
||||
}
|
||||
}
|
||||
@@ -160,7 +174,7 @@ public static class Installer
|
||||
DeleteIfPointsTo(Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.DesktopDirectory), LegacyName + ".lnk"), exe);
|
||||
using (var run = Registry.CurrentUser.OpenSubKey(RunKey, writable: true))
|
||||
{
|
||||
if (run?.GetValue(LegacyName) is string cmd && cmd.Contains(exe, StringComparison.OrdinalIgnoreCase))
|
||||
if (run?.GetValue(LegacyName) is string cmd && cmd.IndexOf(exe, StringComparison.OrdinalIgnoreCase) >= 0)
|
||||
run.DeleteValue(LegacyName, throwOnMissingValue: false);
|
||||
}
|
||||
|
||||
@@ -221,7 +235,7 @@ public static class Installer
|
||||
|
||||
// Das laufende Deinstallationsprogramm kann sich nicht selbst löschen → kurz nach dem Beenden per cmd.
|
||||
// rmdir ohne /s entfernt den Ordner nur, wenn er danach leer ist.
|
||||
var self = Environment.ProcessPath!;
|
||||
var self = SelfPath;
|
||||
if (Storage_IsUnder(self, dir))
|
||||
{
|
||||
Process.Start(new ProcessStartInfo("cmd.exe",
|
||||
@@ -240,6 +254,12 @@ public static class Installer
|
||||
|
||||
// ---------- Helfer ----------
|
||||
|
||||
/// <summary>Rechnet Fortschritt 0–1 in einen Teilbereich um.</summary>
|
||||
private sealed class Scaled(IProgress<(double, string)> inner, double offset, double factor) : IProgress<(double, string)>
|
||||
{
|
||||
public void Report((double, string) value) => inner.Report((offset + value.Item1 * factor, value.Item2));
|
||||
}
|
||||
|
||||
/// <summary>Laufendes DeskCore beenden (das X versteckt es nur im Infobereich).</summary>
|
||||
private static void CloseRunningApp()
|
||||
{
|
||||
@@ -247,7 +267,7 @@ public static class Installer
|
||||
{
|
||||
try
|
||||
{
|
||||
p.Kill(entireProcessTree: true);
|
||||
p.Kill();
|
||||
p.WaitForExit(10000);
|
||||
}
|
||||
catch { }
|
||||
|
||||
@@ -79,6 +79,14 @@
|
||||
Text="DeskCore sortiert deine Desktop-Icons in eigene Kategorien – mit Suche, Desktop-Boxen, automatischem Einsortieren und Rückgängig-Funktion."/>
|
||||
<TextBlock Style="{StaticResource Body}" Margin="0,14,0,0"
|
||||
Text="Der Assistent installiert DeskCore nur für dein Benutzerkonto. Administratorrechte sind nicht nötig."/>
|
||||
<Border x:Name="RuntimeHint" Margin="0,22,0,0" Padding="14,10" CornerRadius="6" Visibility="Collapsed"
|
||||
Background="{DynamicResource ControlFillBrush}" BorderBrush="{DynamicResource ControlStrokeBrush}" BorderThickness="1">
|
||||
<DockPanel>
|
||||
<TextBlock DockPanel.Dock="Left" Text="" FontFamily="{DynamicResource IconFont}" FontSize="16"
|
||||
Foreground="{DynamicResource AccentBrush}" Margin="0,2,12,0" VerticalAlignment="Top"/>
|
||||
<TextBlock x:Name="RuntimeHintText" TextWrapping="Wrap" LineHeight="20"/>
|
||||
</DockPanel>
|
||||
</Border>
|
||||
<Border x:Name="UpdateHint" Margin="0,22,0,0" Padding="14,10" CornerRadius="6" Visibility="Collapsed"
|
||||
Background="{DynamicResource AccentSubtleBrush}" BorderBrush="{DynamicResource AccentSubtleStrokeBrush}" BorderThickness="1">
|
||||
<DockPanel>
|
||||
@@ -113,6 +121,8 @@
|
||||
<CheckBox x:Name="DesktopLinkBox" Content="Verknüpfung auf dem Desktop" Margin="0,24,0,0" IsChecked="True"/>
|
||||
<CheckBox x:Name="StartMenuBox" Content="Eintrag im Startmenü" Margin="0,12,0,0" IsChecked="True" IsEnabled="False"/>
|
||||
<CheckBox x:Name="AutostartBox" Content="Mit Windows starten (unauffällig im Infobereich)" Margin="0,12,0,0"/>
|
||||
<CheckBox x:Name="RuntimeBox" Margin="0,12,0,0" IsChecked="True" Visibility="Collapsed"
|
||||
Content="Microsoft .NET 9 Desktop Runtime automatisch installieren (benötigt, ca. 55 MB)"/>
|
||||
</StackPanel>
|
||||
|
||||
<!-- 3: Fortschritt / Fertig -->
|
||||
@@ -130,6 +140,14 @@
|
||||
</Border>
|
||||
<TextBlock x:Name="DoneText" VerticalAlignment="Center" Margin="12,0,0,0" FontWeight="SemiBold"/>
|
||||
</StackPanel>
|
||||
<Border x:Name="RuntimeMissing" Margin="0,18,0,0" Padding="14,10" CornerRadius="6" Visibility="Collapsed"
|
||||
Background="{DynamicResource AccentSubtleBrush}" BorderBrush="{DynamicResource AccentSubtleStrokeBrush}" BorderThickness="1">
|
||||
<StackPanel>
|
||||
<TextBlock TextWrapping="Wrap" LineHeight="20"
|
||||
Text="Damit DeskCore startet, wird noch die kostenlose „Microsoft .NET 9 Desktop Runtime“ benötigt."/>
|
||||
<Button Content="Download-Seite von Microsoft öffnen" HorizontalAlignment="Left" Margin="0,10,0,0" Click="OpenRuntimePage_Click"/>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
<CheckBox x:Name="LaunchBox" Content="DeskCore jetzt starten" IsChecked="True" Margin="0,22,0,0"/>
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
|
||||
@@ -26,6 +26,16 @@ public partial class MainWindow : Window
|
||||
DesktopLinkBox.IsChecked = Installer.InstalledDir is null || Installer.DesktopLinkExists;
|
||||
AutostartBox.IsChecked = Installer.AutostartEnabled;
|
||||
|
||||
if (!Runtime.IsInstalled)
|
||||
{
|
||||
RuntimeHint.Visibility = Visibility.Visible;
|
||||
RuntimeBox.Visibility = Visibility.Visible;
|
||||
RuntimeHintText.Text = Runtime.Is64BitWindows
|
||||
? "DeskCore benötigt die kostenlose „Microsoft .NET 9 Desktop Runtime“. Sie fehlt auf diesem PC und wird bei der " +
|
||||
"Installation automatisch von Microsoft geladen (ca. 55 MB). Windows fragt dabei einmal nach Administratorrechten."
|
||||
: "DeskCore benötigt ein 64-Bit-Windows (Windows 10 oder 11) und kann auf diesem PC leider nicht installiert werden.";
|
||||
}
|
||||
|
||||
if (Installer.InstalledDir is null && Installer.LegacyInstallDir is not null)
|
||||
{
|
||||
UpdateHint.Visibility = Visibility.Visible;
|
||||
@@ -102,7 +112,7 @@ public partial class MainWindow : Window
|
||||
case 1 when AcceptLicense.IsChecked == true: ShowPage(2); break;
|
||||
case 2:
|
||||
var dir = DirBox.Text.Trim();
|
||||
if (!Path.IsPathFullyQualified(dir) || dir.IndexOfAny(Path.GetInvalidPathChars()) >= 0)
|
||||
if (!IsFullPath(dir) || dir.IndexOfAny(Path.GetInvalidPathChars()) >= 0)
|
||||
{
|
||||
MessageBox.Show(this, "Bitte einen gültigen Ordner angeben, z. B. C:\\Programme\\DeskCore.", "DeskCore Setup");
|
||||
return;
|
||||
@@ -119,9 +129,14 @@ public partial class MainWindow : Window
|
||||
|
||||
private void ChangeDir_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
var dlg = new Microsoft.Win32.OpenFolderDialog { Title = "Installationsordner wählen" };
|
||||
if (dlg.ShowDialog(this) != true) return;
|
||||
var chosen = dlg.FolderName;
|
||||
using var dlg = new System.Windows.Forms.FolderBrowserDialog
|
||||
{
|
||||
Description = "Installationsordner wählen",
|
||||
SelectedPath = Directory.Exists(DirBox.Text) ? DirBox.Text : Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData),
|
||||
ShowNewFolderButton = true,
|
||||
};
|
||||
if (dlg.ShowDialog() != System.Windows.Forms.DialogResult.OK) return;
|
||||
var chosen = dlg.SelectedPath;
|
||||
// wie bei üblichen Installern: Unterordner "DeskCore" anhängen
|
||||
DirBox.Text = Path.GetFileName(chosen.TrimEnd('\\')).Equals("DeskCore", StringComparison.OrdinalIgnoreCase)
|
||||
? chosen : Path.Combine(chosen, "DeskCore");
|
||||
@@ -136,15 +151,24 @@ public partial class MainWindow : Window
|
||||
SetButtons(false);
|
||||
var progress = new Progress<(double Value, string Text)>(p => SetProgress(p.Value, p.Text));
|
||||
bool desktop = DesktopLinkBox.IsChecked == true, autostart = AutostartBox.IsChecked == true;
|
||||
bool runtime = RuntimeBox.Visibility == Visibility.Visible && RuntimeBox.IsChecked == true;
|
||||
try
|
||||
{
|
||||
await Task.Run(() => Installer.Install(dir, desktop, autostart, progress));
|
||||
await Task.Run(() => Installer.Install(dir, desktop, autostart, progress, runtime));
|
||||
_installedExe = Path.Combine(dir, Installer.ExeName);
|
||||
_done = true;
|
||||
ProgressTitle.Text = "Installation abgeschlossen";
|
||||
ProgressText.Text = $"DeskCore wurde in {dir} installiert. Du findest es im Startmenü" + (desktop ? " und auf dem Desktop." : ".");
|
||||
DoneText.Text = "Alles bereit!";
|
||||
DonePanel.Visibility = Visibility.Visible;
|
||||
if (!Runtime.IsInstalled)
|
||||
{
|
||||
// Laufzeit fehlt weiterhin (abgewählt) → Hinweis statt Startversuch
|
||||
DoneText.Text = "DeskCore ist installiert";
|
||||
RuntimeMissing.Visibility = Visibility.Visible;
|
||||
LaunchBox.IsChecked = false;
|
||||
LaunchBox.IsEnabled = false;
|
||||
}
|
||||
ShowPage(3);
|
||||
}
|
||||
catch (Exception ex)
|
||||
@@ -230,6 +254,12 @@ public partial class MainWindow : Window
|
||||
new DoubleAnimation(target, TimeSpan.FromMilliseconds(250)) { EasingFunction = new CubicEase() });
|
||||
}
|
||||
|
||||
private static bool IsFullPath(string p) =>
|
||||
p.Length >= 3 && char.IsLetter(p[0]) && p[1] == ':' && (p[2] == '\\' || p[2] == '/');
|
||||
|
||||
private void OpenRuntimePage_Click(object sender, RoutedEventArgs e) =>
|
||||
Process.Start(new ProcessStartInfo(Runtime.DownloadPage) { UseShellExecute = true });
|
||||
|
||||
private void Minimize_Click(object sender, RoutedEventArgs e) => WindowState = WindowState.Minimized;
|
||||
|
||||
private void Cancel_Click(object sender, RoutedEventArgs e)
|
||||
|
||||
@@ -0,0 +1,169 @@
|
||||
using System.ComponentModel;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Net;
|
||||
using System.Net.Http;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Security.Cryptography.X509Certificates;
|
||||
using Microsoft.Win32;
|
||||
|
||||
namespace DeskCoreSetup;
|
||||
|
||||
/// <summary>Prüft und installiert die von DeskCore benötigte "Microsoft .NET 9 Desktop Runtime".</summary>
|
||||
public static class Runtime
|
||||
{
|
||||
public const string DisplayName = "Microsoft .NET 9 Desktop Runtime";
|
||||
public const int Major = 9;
|
||||
public const string DownloadPage = "https://dotnet.microsoft.com/de-de/download/dotnet/9.0";
|
||||
// offizieller Microsoft-Kurzlink auf die jeweils aktuelle 9.0-Version
|
||||
private const string InstallerUrl = "https://aka.ms/dotnet/9.0/windowsdesktop-runtime-win-x64.exe";
|
||||
|
||||
public static bool Is64BitWindows => Environment.Is64BitOperatingSystem;
|
||||
|
||||
public static bool IsInstalled
|
||||
{
|
||||
get
|
||||
{
|
||||
try
|
||||
{
|
||||
var dir = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles),
|
||||
"dotnet", "shared", "Microsoft.WindowsDesktop.App");
|
||||
if (Directory.Exists(dir) && Directory.GetDirectories(dir).Any(d => Path.GetFileName(d).StartsWith($"{Major}.")))
|
||||
return true;
|
||||
}
|
||||
catch { }
|
||||
try
|
||||
{
|
||||
using var hklm = RegistryKey.OpenBaseKey(RegistryHive.LocalMachine, RegistryView.Registry32);
|
||||
using var k = hklm.OpenSubKey(@"SOFTWARE\dotnet\Setup\InstalledVersions\x64\sharedfx\Microsoft.WindowsDesktop.App");
|
||||
if (k?.GetValueNames().Any(n => n.StartsWith($"{Major}.")) == true) return true;
|
||||
}
|
||||
catch { }
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Lädt die Laufzeit von Microsoft und installiert sie (Windows fragt einmal nach Administratorrechten).</summary>
|
||||
public static void Install(Action<double, string> progress)
|
||||
{
|
||||
ServicePointManager.SecurityProtocol |= SecurityProtocolType.Tls12;
|
||||
var file = Path.Combine(Path.GetTempPath(), $"windowsdesktop-runtime-{Major}-win-x64.exe");
|
||||
|
||||
using (var http = new HttpClient { Timeout = TimeSpan.FromMinutes(20) })
|
||||
{
|
||||
http.DefaultRequestHeaders.UserAgent.ParseAdd("DeskCore-Setup");
|
||||
using var resp = http.GetAsync(InstallerUrl, HttpCompletionOption.ResponseHeadersRead).GetAwaiter().GetResult();
|
||||
resp.EnsureSuccessStatusCode();
|
||||
long total = resp.Content.Headers.ContentLength ?? 0;
|
||||
using var input = resp.Content.ReadAsStreamAsync().GetAwaiter().GetResult();
|
||||
using var output = File.Create(file);
|
||||
var buffer = new byte[81920];
|
||||
long done = 0;
|
||||
int read;
|
||||
while ((read = input.Read(buffer, 0, buffer.Length)) > 0)
|
||||
{
|
||||
output.Write(buffer, 0, read);
|
||||
done += read;
|
||||
progress(total > 0 ? (double)done / total : 0,
|
||||
total > 0 ? $"{DisplayName} wird heruntergeladen … {done / 1048576} von {total / 1048576} MB"
|
||||
: $"{DisplayName} wird heruntergeladen … {done / 1048576} MB");
|
||||
}
|
||||
}
|
||||
|
||||
// Sicherheit: nur ausführen, wenn die Datei gültig von Microsoft signiert ist
|
||||
if (!IsSignedByMicrosoft(file))
|
||||
{
|
||||
try { File.Delete(file); } catch { }
|
||||
throw new InvalidOperationException("Die heruntergeladene Datei ist nicht gültig von Microsoft signiert und wurde nicht ausgeführt.");
|
||||
}
|
||||
|
||||
progress(1, $"{DisplayName} wird installiert – bitte die Windows-Abfrage bestätigen …");
|
||||
Process p;
|
||||
try
|
||||
{
|
||||
p = Process.Start(new ProcessStartInfo(file, "/install /quiet /norestart") { UseShellExecute = true, Verb = "runas" })!;
|
||||
}
|
||||
catch (Win32Exception ex) when (ex.NativeErrorCode == 1223)
|
||||
{
|
||||
throw new InvalidOperationException($"Die Installation von {DisplayName} wurde abgebrochen.");
|
||||
}
|
||||
p.WaitForExit();
|
||||
try { File.Delete(file); } catch { }
|
||||
// 0 = ok, 3010/1641 = ok, Neustart empfohlen
|
||||
if (p.ExitCode is not (0 or 3010 or 1641) || !IsInstalled)
|
||||
throw new InvalidOperationException($"{DisplayName} konnte nicht installiert werden (Code {p.ExitCode}).");
|
||||
}
|
||||
|
||||
// ---------- Signaturprüfung (WinVerifyTrust) ----------
|
||||
|
||||
private static bool IsSignedByMicrosoft(string file)
|
||||
{
|
||||
try
|
||||
{
|
||||
var subject = X509Certificate.CreateFromSignedFile(file).Subject;
|
||||
if (subject.IndexOf("O=Microsoft Corporation", StringComparison.OrdinalIgnoreCase) < 0) return false;
|
||||
}
|
||||
catch { return false; }
|
||||
return VerifyTrust(file);
|
||||
}
|
||||
|
||||
private static bool VerifyTrust(string file)
|
||||
{
|
||||
var fileInfo = new WINTRUST_FILE_INFO
|
||||
{
|
||||
cbStruct = (uint)Marshal.SizeOf<WINTRUST_FILE_INFO>(),
|
||||
pcwszFilePath = file,
|
||||
};
|
||||
var pFile = Marshal.AllocHGlobal(Marshal.SizeOf<WINTRUST_FILE_INFO>());
|
||||
try
|
||||
{
|
||||
Marshal.StructureToPtr(fileInfo, pFile, false);
|
||||
var data = new WINTRUST_DATA
|
||||
{
|
||||
cbStruct = (uint)Marshal.SizeOf<WINTRUST_DATA>(),
|
||||
dwUIChoice = 2, // WTD_UI_NONE
|
||||
fdwRevocationChecks = 0, // WTD_REVOKE_NONE
|
||||
dwUnionChoice = 1, // WTD_CHOICE_FILE
|
||||
pFile = pFile,
|
||||
dwStateAction = 0,
|
||||
dwProvFlags = 0x40, // WTD_SAFER_FLAG
|
||||
};
|
||||
var action = new Guid("00AAC56B-CD44-11d0-8CC2-00C04FC295EE"); // WINTRUST_ACTION_GENERIC_VERIFY_V2
|
||||
return WinVerifyTrust(IntPtr.Zero, ref action, ref data) == 0;
|
||||
}
|
||||
finally
|
||||
{
|
||||
Marshal.FreeHGlobal(pFile);
|
||||
}
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
|
||||
private struct WINTRUST_FILE_INFO
|
||||
{
|
||||
public uint cbStruct;
|
||||
public string pcwszFilePath;
|
||||
public IntPtr hFile;
|
||||
public IntPtr pgKnownSubject;
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
private struct WINTRUST_DATA
|
||||
{
|
||||
public uint cbStruct;
|
||||
public IntPtr pPolicyCallbackData;
|
||||
public IntPtr pSIPClientData;
|
||||
public uint dwUIChoice;
|
||||
public uint fdwRevocationChecks;
|
||||
public uint dwUnionChoice;
|
||||
public IntPtr pFile;
|
||||
public uint dwStateAction;
|
||||
public IntPtr hWVTStateData;
|
||||
public IntPtr pwszURLReference;
|
||||
public uint dwProvFlags;
|
||||
public uint dwUIContext;
|
||||
public IntPtr pSignatureSettings;
|
||||
}
|
||||
|
||||
[DllImport("wintrust.dll", ExactSpelling = true, SetLastError = false, CharSet = CharSet.Unicode)]
|
||||
private static extern int WinVerifyTrust(IntPtr hwnd, ref Guid pgActionID, ref WINTRUST_DATA pWVTData);
|
||||
}
|
||||
Reference in New Issue
Block a user