using System; using System.Collections.Generic; using System.Diagnostics; using System.Drawing; using System.Drawing.Drawing2D; using System.Drawing.Text; using System.IO; using System.Threading.Tasks; using System.Windows.Forms; using Microsoft.Win32; namespace DomainJoinTool { public class MainForm : Form { // ── Cereda/Zelo Colors ──────────────────────────────────────────────── static readonly Color CTeal = ColorTranslator.FromHtml("#0D9488"); static readonly Color CTealD = ColorTranslator.FromHtml("#0F766E"); static readonly Color CGreen = ColorTranslator.FromHtml("#059669"); static readonly Color CRed = ColorTranslator.FromHtml("#DC2626"); static readonly Color CAmber = ColorTranslator.FromHtml("#D97706"); static readonly Color CGray = ColorTranslator.FromHtml("#6B7280"); static readonly Color CBG = ColorTranslator.FromHtml("#F8FAFC"); static readonly Color CBorder= ColorTranslator.FromHtml("#E2E8F0"); static readonly Color CText = ColorTranslator.FromHtml("#0F172A"); static readonly Color CText2 = ColorTranslator.FromHtml("#64748B"); // ── State ───────────────────────────────────────────────────────────── bool _entra, _domain; string _dn = ""; int _step; Timer _animTimer; float _phase, _spin; // ── Controls ────────────────────────────────────────────────────────── StatusBadge _badge; Panel _body1, _body2; ModernInput _txUser, _txPass; RoundButton _btnJoin, _btnLeave, _btnMig; Label _lnkBack, _lnkTab1, _lnkTab2; ComboBox _cbSrc, _cbDst; ProgressBar _pb; Label _lbPb; RichTextBox _log; Label _lblStat; Label _lblMigStatus; ProgressBar _pbMig; Label _lblMigCount; RichTextBox _logMig; const string DOM = "winkel.local"; const string LOG = @"C:\ProgramData\DomainJoinTool\join.log"; const string FLG = @"C:\ProgramData\DomainJoinTool\pending-migration.txt"; const string EXE = @"C:\ProgramData\DomainJoinTool\DomainJoinTool.exe"; public MainForm() { Text = "Cereda Systems · Domain Join Tool"; ClientSize = new Size(620, 680); FormBorderStyle = FormBorderStyle.FixedSingle; MaximizeBox = false; StartPosition = FormStartPosition.CenterScreen; BackColor = CBG; Font = new Font("Segoe UI", 9f); Icon = MakeIcon(); Build(); _animTimer = new Timer { Interval = 16 }; _animTimer.Tick += (s, e) => { _phase = (_phase + 0.025f) % 1f; _spin = (_spin + 9f) % 360f; _badge?.Tick(_phase, _spin); }; _animTimer.Start(); DetectStatus(); LoadProfiles(); } void Build() { // ── Header ──────────────────────────────────────────────────────── var hdr = new Panel { Dock = DockStyle.Top, Height = 64, BackColor = CTeal }; hdr.Paint += (s, e) => { var g = e.Graphics; g.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.AntiAlias; g.TextRenderingHint = TextRenderingHint.ClearTypeGridFit; // Subtle darker stripe at bottom using (var br = new SolidBrush(Color.FromArgb(30, 0, 0, 0))) g.FillRectangle(br, 0, hdr.Height - 3, hdr.Width, 3); // Logo circle using (var br = new SolidBrush(Color.FromArgb(40, 255, 255, 255))) g.FillEllipse(br, 14, 10, 36, 36); using (var f = new Font("Segoe UI", 11f, FontStyle.Bold)) using (var br = new SolidBrush(Color.White)) { var sf = new System.Drawing.StringFormat { Alignment = System.Drawing.StringAlignment.Center, LineAlignment = System.Drawing.StringAlignment.Center }; g.DrawString("IT", f, br, new RectangleF(14, 10, 36, 36), sf); } // Text using (var br = new SolidBrush(Color.FromArgb(200, 255, 255, 255))) g.DrawString("CEREDA SYSTEMS", new Font("Segoe UI", 7.5f, FontStyle.Bold), br, 60, 12); g.DrawString("Domain Join Tool", new Font("Segoe UI", 13f, FontStyle.Bold), Brushes.White, 58, 28); // Version badge using (var br = new SolidBrush(Color.FromArgb(40, 255, 255, 255))) g.FillRectangle(br, hdr.Width - 68, 20, 56, 20); using (var br = new SolidBrush(Color.FromArgb(200, 255, 255, 255))) g.DrawString("v2.5.0", new Font("Segoe UI", 8f), br, new RectangleF(hdr.Width - 68, 20, 56, 20), new System.Drawing.StringFormat { Alignment = System.Drawing.StringAlignment.Center, LineAlignment = System.Drawing.StringAlignment.Center }); }; Controls.Add(hdr); // ── Tab bar ─────────────────────────────────────────────────────── var tabBar = new Panel { Dock = DockStyle.Top, Height = 38, BackColor = Color.White }; tabBar.Paint += (s, e) => { e.Graphics.DrawLine(new Pen(CBorder), 0, 37, 600, 37); // Active indicator if (_step == 0) e.Graphics.FillRectangle(new SolidBrush(CTeal), 16, 35, 120, 3); else e.Graphics.FillRectangle(new SolidBrush(CTeal), 148, 35, 140, 3); }; _lnkTab1 = TabLabel("1 · Domain Join", new Point(16, 8), true); _lnkTab2 = TabLabel("2 · Profil Migration", new Point(148, 8), false); _lnkTab1.Click += (s, e) => GoTo(0, tabBar); _lnkTab2.Click += (s, e) => GoTo(1, tabBar); tabBar.Controls.Add(_lnkTab1); tabBar.Controls.Add(_lnkTab2); Controls.Add(tabBar); // ── Bodies (explicit position to avoid Dock=Fill overlap bug) ──── // hdr=64 + tabBar=38 = 102px _body1 = new Panel { Location = new Point(0, 102), Size = new Size(620, 578), BackColor = CBG }; _body2 = new Panel { Location = new Point(0, 102), Size = new Size(620, 578), BackColor = CBG, Visible = false }; BuildBody1(); BuildBody2(); Controls.Add(_body1); Controls.Add(_body2); } Label TabLabel(string t, Point loc, bool active) { var l = new Label { Text = t, Font = new Font("Segoe UI", 9f, active ? FontStyle.Bold : FontStyle.Regular), ForeColor = active ? CTeal : CText2, Location = loc, AutoSize = true, Cursor = Cursors.Hand }; l.MouseEnter += (s, e) => l.ForeColor = CTeal; l.MouseLeave += (s, e) => l.ForeColor = (_step == (l == _lnkTab1 ? 0 : 1)) ? CTeal : CText2; return l; } void GoTo(int step, Panel tabBar = null) { _step = step; _body1.Visible = step == 0; _body2.Visible = step == 1; _lnkTab1.Font = new Font("Segoe UI", 9f, step == 0 ? FontStyle.Bold : FontStyle.Regular); _lnkTab2.Font = new Font("Segoe UI", 9f, step == 1 ? FontStyle.Bold : FontStyle.Regular); _lnkTab1.ForeColor = step == 0 ? CTeal : CText2; _lnkTab2.ForeColor = step == 1 ? CTeal : CText2; (tabBar ?? _body1.Parent?.Parent as Panel)?.Invalidate(); // Find and invalidate the tabBar foreach (Control c in Controls) if (c is Panel p && p.Height == 38) { p.Invalidate(); break; } } // ── Body 1: Domain Join ─────────────────────────────────────────────── void BuildBody1() { int m = 16, y = 14, w = 588; // ── Status Card ─────────────────────────────────────────────────── var statCard = MkCard(m, y, w, 86); y += 98; _lblStat = new Label { Text = "Status", Font = new Font("Segoe UI", 9f, FontStyle.Bold), ForeColor = CTealD, Location = new Point(20, 10), AutoSize = true }; statCard.Controls.Add(_lblStat); statCard.Controls.Add(new Panel { Location = new Point(20, 30), Size = new Size(w - 26, 1), BackColor = CBorder }); _badge = new StatusBadge { Location = new Point(20, 40) }; statCard.Controls.Add(_badge); // Right: Entra → AD statCard.Controls.Add(new Label { Text = "Entra ID", Font = new Font("Segoe UI", 8.5f, FontStyle.Bold), ForeColor = CText2, Location = new Point(280, 38), AutoSize = true }); statCard.Controls.Add(new Label { Text = "→", Font = new Font("Segoe UI", 14f), ForeColor = Color.FromArgb(180, 180, 200), Location = new Point(358, 32), AutoSize = true }); statCard.Controls.Add(new Label { Text = DOM, Font = new Font("Segoe UI", 8.5f, FontStyle.Bold), ForeColor = CGreen, Location = new Point(382, 38), AutoSize = true }); statCard.Controls.Add(new Label { Text = Environment.MachineName, Font = new Font("Cascadia Mono", 7.5f), ForeColor = CText2, Location = new Point(280, 57), AutoSize = true }); _body1.Controls.Add(statCard); // ── Credentials Card ────────────────────────────────────────────── var credCard = MkCard(m, y, w, 168); y += 180; credCard.Controls.Add(new Label { Text = "Domain-Zugangsdaten", Font = new Font("Segoe UI", 10f, FontStyle.Bold), ForeColor = CTealD, Location = new Point(20, 12), AutoSize = true }); credCard.Controls.Add(new Label { Text = "Ziel: " + DOM, Font = new Font("Cascadia Mono", 8f, FontStyle.Bold), ForeColor = CGreen, Location = new Point(220, 15), AutoSize = true }); credCard.Controls.Add(new Panel { Location = new Point(20, 36), Size = new Size(w - 26, 1), BackColor = CBorder }); _txUser = Inp(new Point(20, 44), new Size(w - 40, 52), "Benutzername"); _txUser.Val = "Administrator"; _txPass = Inp(new Point(20, 104), new Size(w - 40, 52), "Passwort", true); credCard.Controls.Add(_txUser); credCard.Controls.Add(_txPass); _body1.Controls.Add(credCard); // ── Action Buttons ──────────────────────────────────────────────── _btnJoin = new RoundButton("▶ Entra leaven + Domain joinen", CTeal, 10) { Location = new Point(m, y), Size = new Size(420, 44) }; _btnLeave = new RoundButton("■ Nur Entra verlassen", CRed, 10) { Location = new Point(m + 428, y), Size = new Size(160, 44) }; _btnJoin.Click += DoJoin; _btnLeave.Click += DoLeave; _body1.Controls.Add(_btnJoin); _body1.Controls.Add(_btnLeave); y += 52; // ── Progress ───────────────────────────────────────────────────── _pb = new ProgressBar { Location = new Point(m, y), Size = new Size(w, 4), Style = ProgressBarStyle.Marquee, MarqueeAnimationSpeed = 25, Visible = false }; y += 8; _lbPb = Lbl("Bitte warten…", new Point(m, y)); _lbPb.Visible = false; y += 22; _body1.Controls.Add(_pb); _body1.Controls.Add(_lbPb); // ── Log ─────────────────────────────────────────────────────────── _body1.Controls.Add(Lbl("Log · " + LOG, new Point(m, y), new Font("Cascadia Mono", 7.5f), CText2)); y += 16; _log = new RichTextBox { Location = new Point(m, y), Size = new Size(w, 100), BackColor = Color.FromArgb(13, 17, 23), ForeColor = Color.FromArgb(100, 210, 130), Font = new Font("Cascadia Mono", 8f), ReadOnly = true, BorderStyle = BorderStyle.None }; _body1.Controls.Add(_log); } // ── Body 2: Migration ───────────────────────────────────────────────── void BuildBody2() { int m = 16, y = 14, w = 588; var infoCard = MkCard(m, y, w, 140); y += 152; infoCard.Controls.Add(new Label { Text = "Was wird migriert", Font = new Font("Segoe UI", 10f, FontStyle.Bold), ForeColor = CTealD, Location = new Point(20, 12), AutoSize = true }); infoCard.Controls.Add(new Panel { Location = new Point(20, 36), Size = new Size(w - 26, 1), BackColor = CBorder }); int iy = 46; foreach (var (t, ok) in new[] { ("Dokumente, Desktop, Downloads, Bilder, Videos, Musik", true), ("AppData\\Roaming — Outlook, Edge, Chrome, App-Einstellungen", true), ("Hintergrundbild, Taskbar-Pins, Startmenü-Layout", true), ("Temp-Cache, NTUSER.DAT — nicht übertragen", false), }) { infoCard.Controls.Add(Lbl((ok ? "✓ " : "✗ ") + t, new Point(20, iy), null, ok ? CGreen : CAmber)); iy += 24; } _body2.Controls.Add(infoCard); var selCard = MkCard(m, y, w, 108); y += 120; selCard.Controls.Add(new Label { Text = "Profile auswählen", Font = new Font("Segoe UI", 10f, FontStyle.Bold), ForeColor = CTealD, Location = new Point(20, 12), AutoSize = true }); selCard.Controls.Add(new Panel { Location = new Point(20, 36), Size = new Size(w - 26, 1), BackColor = CBorder }); selCard.Controls.Add(Lbl("Von (altes Entra-Profil):", new Point(20, 46))); selCard.Controls.Add(Lbl("Nach (neues Domain-Profil):", new Point(20, 76))); _cbSrc = Cmb(new Point(210, 43), new Size(w - 230, 24)); _cbDst = Cmb(new Point(210, 73), new Size(w - 230, 24)); selCard.Controls.Add(_cbSrc); selCard.Controls.Add(_cbDst); _body2.Controls.Add(selCard); _btnMig = new RoundButton("▶ Profil jetzt migrieren", CGreen, 10) { Location = new Point(m, y), Size = new Size(440, 44) }; y += 52; _btnMig.Click += DoMig; _lnkBack = new Label { Text = "← Zurück zu Domain Join", Font = new Font("Segoe UI", 9f), ForeColor = CText2, Location = new Point(m + 448, y - 40), AutoSize = true, Cursor = Cursors.Hand }; _lnkBack.Click += (s, e) => GoTo(0); _lnkBack.MouseEnter += (s, e) => _lnkBack.ForeColor = CTeal; _lnkBack.MouseLeave += (s, e) => _lnkBack.ForeColor = CText2; _body2.Controls.Add(_btnMig); _body2.Controls.Add(_lnkBack); // ── Status + Progress + Log (Tab 2) ────────────────────────────── _lblMigStatus = new Label { Text = "", Font = new Font("Segoe UI", 9f, FontStyle.Bold), ForeColor = CTealD, Location = new Point(m, y), AutoSize = true }; _lblMigCount = new Label { Text = "", Font = new Font("Cascadia Mono", 8f), ForeColor = CText2, Location = new Point(m + 300, y), AutoSize = true }; y += 22; _pbMig = new ProgressBar { Location = new Point(m, y), Size = new Size(w, 6), Style = ProgressBarStyle.Marquee, MarqueeAnimationSpeed = 20, Visible = false }; y += 14; _logMig = new RichTextBox { Location = new Point(m, y), Size = new Size(w, 106), BackColor = Color.FromArgb(13, 17, 23), ForeColor = Color.FromArgb(100, 210, 130), Font = new Font("Cascadia Mono", 8f), ReadOnly = true, BorderStyle = BorderStyle.None, Visible = false }; _body2.Controls.Add(_lblMigStatus); _body2.Controls.Add(_lblMigCount); _body2.Controls.Add(_pbMig); _body2.Controls.Add(_logMig); } // ── State ───────────────────────────────────────────────────────────── void DetectStatus() { Task.Run(new Action(() => { try { var r = Run("dsregcmd", "/status", 10000); _entra = r.Contains("AzureAdJoined : YES"); _domain = r.Contains("DomainJoined : YES"); foreach (var ln in r.Split('\n')) if (ln.Trim().StartsWith("DomainName :")) { _dn = ln.Split(':')[1].Trim(); break; } } catch { } Invoke(new Action(() => { RefreshStat(); CheckAutoMig(); })); })); } void RefreshStat() { if (_domain) { _badge.Mode = StatusBadge.BMode.Joined; _lblStat.Text = "Domain joined: " + _dn; _lblStat.ForeColor = CGreen; _btnJoin.Text = "↺ Domain wechseln"; _btnLeave.Text = "■ Verlassen"; } else if (_entra) { _badge.Mode = StatusBadge.BMode.Entra; _lblStat.Text = "Entra ID joined — bereit für Domain-Join"; _lblStat.ForeColor = CTealD; } else { _badge.Mode = StatusBadge.BMode.None; _lblStat.Text = "Workgroup — kein Domain-Join"; _lblStat.ForeColor = CAmber; _btnLeave.Enabled = false; } } void CheckAutoMig() { if (!File.Exists(FLG) || !_domain) return; try { var old = File.ReadAllText(FLG).Trim(); LoadProfiles(); for (int i = 0; i < _cbSrc.Items.Count; i++) if (_cbSrc.Items[i].ToString().Equals(old, StringComparison.OrdinalIgnoreCase)) { _cbSrc.SelectedIndex = i; break; } var cur = Environment.UserName; for (int i = 0; i < _cbDst.Items.Count; i++) if (_cbDst.Items[i].ToString().Equals(cur, StringComparison.OrdinalIgnoreCase)) { _cbDst.SelectedIndex = i; break; } GoTo(1); if (MessageBox.Show("Domain-Join erfolgreich!\n\nProfil migrieren?\n Von: C:\\Users\\" + old + "\n Nach: C:\\Users\\" + cur, "Profil Migration", MessageBoxButtons.YesNo, MessageBoxIcon.Question) == DialogResult.Yes) DoMig(null, EventArgs.Empty); else File.Delete(FLG); } catch { } } void LoadProfiles() { if (_cbSrc == null) return; var list = new List(); if (Directory.Exists(@"C:\Users")) foreach (var d in Directory.GetDirectories(@"C:\Users")) { var n = Path.GetFileName(d); if (n != "Public" && n != "Default" && n != "Default User" && n != "All Users") list.Add(n); } _cbSrc.Items.Clear(); _cbDst.Items.Clear(); foreach (var p in list) { _cbSrc.Items.Add(p); _cbDst.Items.Add(p); } if (_cbSrc.Items.Count > 0) _cbSrc.SelectedIndex = 0; if (_cbDst.Items.Count > 1) _cbDst.SelectedIndex = 1; } // ── Actions ─────────────────────────────────────────────────────────── async void DoJoin(object s, EventArgs e) { if (string.IsNullOrWhiteSpace(_txUser.Val) || string.IsNullOrWhiteSpace(_txPass.Val)) { MessageBox.Show("Bitte Benutzername und Passwort eingeben.", "Eingabe fehlt", MessageBoxButtons.OK, MessageBoxIcon.Warning); return; } if (MessageBox.Show("Computer wird der Domain '" + DOM + "' beigetreten.\n\nFortfahren?", "Bestätigung", MessageBoxButtons.YesNo, MessageBoxIcon.Question) != DialogResult.Yes) return; Busy(true, "Domain-Wechsel läuft…"); _badge.Mode = StatusBadge.BMode.Working; var user = _txUser.Val.Trim(); var pass = _txPass.Val; await Task.Run(new Action(async () => { if (_entra) { Prog("Entra ID verlassen…"); Log(Run("dsregcmd", "/leave", 30000)); await Task.Delay(2000); } Prog("Domain joinen…"); if (!user.Contains("\\") && !user.Contains("@")) user = DOM + "\\" + user; var res = PS("$pw=ConvertTo-SecureString '" + Esc(pass) + "' -AsPlainText -Force; $cr=New-Object System.Management.Automation.PSCredential('" + Esc(user) + "',$pw); Add-Computer -DomainName '" + DOM + "' -Credential $cr -Force 2>&1"); Log(res); var ok = !res.ToLower().Contains("fehler") && !res.ToLower().Contains("error") && !res.ToLower().Contains("failed") && !res.ToLower().Contains("denied"); if (ok) SetupAutoMig(); Invoke(new Action(() => { Busy(false, ""); if (ok) { if (MessageBox.Show("Erfolgreich!\n\nJetzt neu starten?", "✓ Fertig", MessageBoxButtons.YesNo, MessageBoxIcon.Information) == DialogResult.Yes) Run("shutdown", "/r /t 5 /c \"Domain Join\""); } else MessageBox.Show("Fehlgeschlagen — Log prüfen.", "Fehler", MessageBoxButtons.OK, MessageBoxIcon.Error); DetectStatus(); })); })); } async void DoLeave(object s, EventArgs e) { if (MessageBox.Show("Computer verlässt " + (_domain ? "Domain '" + _dn + "'" : "Entra ID") + ".\n\nFortfahren?", "Bestätigung", MessageBoxButtons.YesNo, MessageBoxIcon.Question) != DialogResult.Yes) return; Busy(true, "Verlasse…"); _badge.Mode = StatusBadge.BMode.Working; await Task.Run(new Action(() => { Log(Run("dsregcmd", "/leave", 30000)); Invoke(new Action(() => { Busy(false, ""); MessageBox.Show("Fertig. Bitte neu starten.", "Erledigt", MessageBoxButtons.OK, MessageBoxIcon.Information); DetectStatus(); })); })); } void SetupAutoMig() { try { Directory.CreateDirectory(Path.GetDirectoryName(FLG)); File.WriteAllText(FLG, Environment.UserName); File.Copy(System.Reflection.Assembly.GetExecutingAssembly().Location, EXE, true); using (var k = Registry.LocalMachine.OpenSubKey(@"SOFTWARE\Microsoft\Windows\CurrentVersion\RunOnce", true)) k.SetValue("DomainJoinMigration", "\"" + EXE + "\""); Log("[AUTO] Migration startet nach nächstem Login automatisch"); } catch (Exception ex) { Log("[WARN] " + ex.Message); } } async void DoMig(object s, EventArgs e) { if (_cbSrc.SelectedItem == null || _cbDst.SelectedItem == null) { MessageBox.Show("Bitte Quell- und Zielprofil auswählen.", "Profil fehlt", MessageBoxButtons.OK, MessageBoxIcon.Warning); return; } var src = _cbSrc.SelectedItem.ToString(); var dst = _cbDst.SelectedItem.ToString(); if (src == dst) { MessageBox.Show("Quelle und Ziel sind identisch.", "Fehler", MessageBoxButtons.OK, MessageBoxIcon.Warning); return; } var srcPath = @"C:\Users\" + src; var dstPath = @"C:\Users\" + dst; if (!Directory.Exists(srcPath)) { MessageBox.Show("Quellprofil nicht gefunden:\n" + srcPath, "Fehler", MessageBoxButtons.OK, MessageBoxIcon.Error); return; } if (MessageBox.Show("Profil kopieren?\n\nVon: " + srcPath + "\nNach: " + dstPath + "\n\nDas kann mehrere Minuten dauern.", "Bestätigung", MessageBoxButtons.YesNo, MessageBoxIcon.Question) != DialogResult.Yes) return; _btnMig.Enabled = false; _logMig.Visible = true; _logMig.Clear(); _pbMig.Visible = true; _lblMigCount.Text = ""; MigLog("[START] " + src + " → " + dst); MigStatus("⏳ Migration läuft…"); Log("[START] Profil-Migration: " + src + " → " + dst); await Task.Run(new Action(() => { try { if (!Directory.Exists(dstPath)) { Directory.CreateDirectory(dstPath); MigLog("[INFO] Zielordner angelegt: " + dstPath); } var args = "\"" + srcPath + "\" \"" + dstPath + "\"" + " /E /XJ /XC /XN /XO" + " /XA:O" + " /XD Temp INetCache WebCache \"AppData\\Local\\Temp\" OneDrive \"Cereda Systems GmbH\" \"OneDrive - Cereda Systems GmbH\"" + " /XF NTUSER.DAT ntuser.dat ntuser.ini \"*.regtrans-ms\" \"*.blf\"" + " /R:0 /W:0 /NP"; MigLog("[CMD] robocopy " + args); var psi = new System.Diagnostics.ProcessStartInfo( @"C:\Windows\System32\robocopy.exe", args) { RedirectStandardOutput = true, RedirectStandardError = true, UseShellExecute = false, CreateNoWindow = true }; int exitCode = -1; int copiedFiles = 0, skippedFiles = 0; using (var proc = System.Diagnostics.Process.Start(psi)) { string line; while ((line = proc.StandardOutput.ReadLine()) != null) { MigLog(line); // Count copied files ("Neue Datei" or "New File") if (line.Contains("Neue Datei") || line.Contains("New File")) { copiedFiles++; MigCount("Kopiert: " + copiedFiles + " Dateien" + (skippedFiles > 0 ? " • Übersprungen: " + skippedFiles : "")); } else if (line.Contains("FEHLER") || line.Contains("ERROR")) { skippedFiles++; MigCount("Kopiert: " + copiedFiles + " Dateien • Übersprungen: " + skippedFiles); } } proc.WaitForExit(1800000); exitCode = proc.ExitCode; } Invoke(new Action(() => _pbMig.Visible = false)); // robocopy: 0-7 = OK (0=nichts zu tun, 1=kopiert, 2=extra, etc.), 8+ = Fehler if (exitCode >= 8) { MigLog("[FEHLER] ExitCode=" + exitCode); MigStatus("✗ Fehler (ExitCode " + exitCode + ")"); } else { MigLog("[OK] Abgeschlossen. ExitCode=" + exitCode); MigStatus("✓ Migration abgeschlossen — " + copiedFiles + " Dateien kopiert"); } try { if (File.Exists(FLG)) File.Delete(FLG); } catch { } Invoke(new Action(() => { _btnMig.Enabled = true; MessageBox.Show("Migration abgeschlossen!\n\nDateien sind in:\n" + dstPath, "✓ Fertig", MessageBoxButtons.OK, MessageBoxIcon.Information); })); } catch (Exception ex) { MigLog("[EXCEPTION] " + ex.Message); MigStatus("✗ Fehler aufgetreten"); Log("[EXCEPTION] Migration: " + ex.Message); Invoke(new Action(() => { _btnMig.Enabled = true; MessageBox.Show("Fehler bei Migration:\n\n" + ex.Message, "Fehler", MessageBoxButtons.OK, MessageBoxIcon.Error); })); } })); } // ── Helpers ─────────────────────────────────────────────────────────── void Busy(bool b, string m) { if (InvokeRequired) { Invoke(new Action(() => Busy(b, m))); return; } _btnJoin.Enabled = _btnLeave.Enabled = !b; _pb.Visible = _lbPb.Visible = b; if (!string.IsNullOrEmpty(m)) _lbPb.Text = m; } void Prog(string m) { if (InvokeRequired) { Invoke(new Action(() => Prog(m))); return; } _lbPb.Text = m; Log(m); } void Log(string t) { if (InvokeRequired) { Invoke(new Action(() => Log(t))); return; } if (string.IsNullOrWhiteSpace(t)) return; var ln = "[" + DateTime.Now.ToString("HH:mm:ss") + "] " + t.Trim(); _log?.AppendText(ln + "\n"); _log?.ScrollToCaret(); try { Directory.CreateDirectory(Path.GetDirectoryName(LOG)); File.AppendAllText(LOG, DateTime.Now.ToString("yyyy-MM-dd") + " " + ln + Environment.NewLine); } catch { } } void MigLog(string t) { if (InvokeRequired) { Invoke(new Action(() => MigLog(t))); return; } if (string.IsNullOrWhiteSpace(t)) return; _logMig?.AppendText(t.TrimEnd() + "\n"); _logMig?.ScrollToCaret(); } void MigStatus(string t) { if (InvokeRequired) { Invoke(new Action(() => MigStatus(t))); return; } if (_lblMigStatus != null) { _lblMigStatus.Text = t; _lblMigStatus.ForeColor = t.StartsWith("✓") ? CGreen : t.StartsWith("✗") ? CRed : CTealD; } } void MigCount(string t) { if (InvokeRequired) { Invoke(new Action(() => MigCount(t))); return; } if (_lblMigCount != null) _lblMigCount.Text = t; } // Same pattern as ZeloServerVerwaltung Panel MkCard(int x, int y, int w, int h) { var p = new Panel { Location = new Point(x, y), Size = new Size(w, h), BackColor = Color.White }; var accent = new Panel { Location = Point.Empty, Size = new Size(5, h), BackColor = CTeal }; p.Controls.Add(accent); p.Paint += (s, e) => { var g = e.Graphics; g.SmoothingMode = SmoothingMode.AntiAlias; // Border using (var path = RR(1, 1, w - 2, h - 2, 10)) using (var pen = new Pen(Color.FromArgb(215, 225, 235), 1f)) g.DrawPath(pen, path); // Subtle bottom shadow line using (var pen = new Pen(Color.FromArgb(50, 0, 100, 150), 1.5f)) g.DrawLine(pen, 10, h - 1, w - 4, h - 1); }; using (var path = RR(0, 0, w, h, 10)) p.Region = new Region(path); return p; } Label Lbl(string t, Point loc, Font f = null, Color? c = null) => new Label { Text = t, Font = f ?? new Font("Segoe UI", 9f), ForeColor = c ?? CText2, Location = loc, AutoSize = true }; ModernInput Inp(Point loc, Size sz, string placeholder = "", bool pw = false) { return new ModernInput(placeholder, pw) { Location = loc, Size = sz }; } ComboBox Cmb(Point loc, Size sz) => new ComboBox { Location = loc, Size = sz, DropDownStyle = ComboBoxStyle.DropDownList, FlatStyle = FlatStyle.Flat, Font = new Font("Segoe UI", 9f) }; static GraphicsPath RR(int x, int y, int w, int h, int r) { var p = new GraphicsPath(); p.AddArc(x, y, r * 2, r * 2, 180, 90); p.AddArc(x + w - r * 2, y, r * 2, r * 2, 270, 90); p.AddArc(x + w - r * 2, y + h - r * 2, r * 2, r * 2, 0, 90); p.AddArc(x, y + h - r * 2, r * 2, r * 2, 90, 90); p.CloseFigure(); return p; } static Icon MakeIcon() { var bmp = new Bitmap(32, 32); using (var g = Graphics.FromImage(bmp)) { g.SmoothingMode = SmoothingMode.AntiAlias; g.TextRenderingHint = TextRenderingHint.ClearTypeGridFit; // Teal rounded square using (var path = RR(0, 0, 32, 32, 6)) using (var br = new SolidBrush(ColorTranslator.FromHtml("#0D9488"))) g.FillPath(br, path); // White "C" letter var sf = new StringFormat { Alignment = StringAlignment.Center, LineAlignment = StringAlignment.Center }; g.DrawString("C", new Font("Segoe UI", 16f, FontStyle.Bold), Brushes.White, new RectangleF(0, 0, 32, 32), sf); } IntPtr hIcon = bmp.GetHicon(); return Icon.FromHandle(hIcon); } string Run(string exe, string args, int ms = 60000) { var psi = new ProcessStartInfo(exe, args) { RedirectStandardOutput = true, RedirectStandardError = true, UseShellExecute = false, CreateNoWindow = true }; using (var p = Process.Start(psi)) { var o = p.StandardOutput.ReadToEnd() + p.StandardError.ReadToEnd(); p.WaitForExit(ms); return o; } } string PS(string sc) => Run("powershell.exe", "-NoProfile -NonInteractive -ExecutionPolicy Bypass -Command \"" + sc.Replace("\"", "\\\"") + "\"", 120000); static string Esc(string s) => s.Replace("'", "''"); protected override void OnFormClosed(FormClosedEventArgs e) { _animTimer?.Stop(); _animTimer?.Dispose(); base.OnFormClosed(e); } // ── ModernInput — Floating-Label + Fokus-Effekt + Validierung ───────── class ModernInput : Panel { readonly Label _lbl; readonly TextBox _txt; readonly Label _icon; bool _focused, _ready; static readonly Color Teal = ColorTranslator.FromHtml("#0D9488"); static readonly Color Gray = ColorTranslator.FromHtml("#94A3B8"); static readonly Color Border = ColorTranslator.FromHtml("#E2E8F0"); static readonly Color Green = ColorTranslator.FromHtml("#059669"); static readonly Color Red = ColorTranslator.FromHtml("#DC2626"); public string Val { get => _txt.Text; set { _txt.Text = value; Update_(); } } public ModernInput(string placeholder, bool pw = false) { DoubleBuffered = true; SetStyle(ControlStyles.OptimizedDoubleBuffer | ControlStyles.AllPaintingInWmPaint | ControlStyles.UserPaint | ControlStyles.ResizeRedraw, true); BackColor = Color.Transparent; Height = 52; _lbl = new Label { Text = placeholder, Font = new Font("Segoe UI", 9.5f), ForeColor = Gray, Location = new Point(13, 15), AutoSize = true }; _txt = new TextBox { BorderStyle = BorderStyle.None, Location = new Point(13, 28), Font = new Font("Segoe UI", 10.5f), BackColor = Color.White }; if (pw) _txt.PasswordChar = '●'; // Validation icon _icon = new Label { Font = new Font("Segoe UI", 12f), Text = "", AutoSize = true, ForeColor = Gray }; _txt.GotFocus += (s, e) => { _focused = true; Update_(); Invalidate(); }; _txt.LostFocus += (s, e) => { _focused = false; Update_(); Invalidate(); }; _txt.TextChanged += (s, e) => { Update_(); Invalidate(); }; Controls.Add(_lbl); Controls.Add(_txt); Controls.Add(_icon); _ready = true; } void Update_() { bool fl = _focused || !string.IsNullOrEmpty(_txt.Text); _lbl.Font = new Font("Segoe UI", fl ? 7.5f : 9.5f); _lbl.Location = new Point(13, fl ? 6 : 15); _lbl.ForeColor = _focused ? Teal : Gray; bool filled = !string.IsNullOrWhiteSpace(_txt.Text); _icon.Text = filled ? "✓" : ""; _icon.ForeColor = filled ? Green : Gray; if (_icon.Width > 0) _icon.Location = new Point(Width - _icon.Width - 12, (Height - _icon.Height) / 2); } protected override void OnResize(EventArgs e) { base.OnResize(e); if (!_ready || _txt == null) return; _txt.Width = Width - 32; Update_(); } protected override void OnPaint(PaintEventArgs e) { var g = e.Graphics; g.SmoothingMode = SmoothingMode.AntiAlias; // White background using (var br = new SolidBrush(Color.White)) g.FillRectangle(br, 0, 0, Width, Height); // Border var bc = _focused ? Teal : Border; using (var pen = new Pen(bc, _focused ? 2f : 1f)) g.DrawRectangle(pen, 0, 0, Width - 1, Height - 1); // Bottom accent line on focus if (_focused) using (var pen = new Pen(Teal, 2.5f)) g.DrawLine(pen, 2, Height - 1, Width - 3, Height - 1); } } // ── RoundButton (same as ZeloServerVerwaltung) ──────────────────────── class RoundButton : Control { bool _hov, _press; readonly Color _base, _hov2, _press2; readonly int _r; public RoundButton(string text, Color baseColor, int radius = 8) { _base = baseColor; _hov2 = ControlPaint.Dark(baseColor, 0.10f); _press2 = ControlPaint.Dark(baseColor, 0.20f); _r = radius; Text = text; Font = new Font("Segoe UI", 9f, FontStyle.Bold); Cursor = Cursors.Hand; SetStyle(ControlStyles.AllPaintingInWmPaint | ControlStyles.UserPaint | ControlStyles.OptimizedDoubleBuffer, true); } protected override void OnPaint(PaintEventArgs e) { var g = e.Graphics; g.SmoothingMode = SmoothingMode.AntiAlias; g.Clear(Parent?.BackColor ?? Color.White); var fill = _press ? _press2 : (_hov && Enabled) ? _hov2 : _base; using (var path = RR(0, 0, Width, Height, _r)) using (var br = new SolidBrush(fill)) g.FillPath(br, path); if (!Enabled) using (var path = RR(0, 0, Width, Height, _r)) using (var br = new SolidBrush(Color.FromArgb(110, 255, 255, 255))) g.FillPath(br, path); TextRenderer.DrawText(e.Graphics, Text, Font, new Rectangle(0, 0, Width, Height), Color.White, TextFormatFlags.HorizontalCenter | TextFormatFlags.VerticalCenter); } protected override void OnMouseEnter(EventArgs e) { _hov = true; Invalidate(); base.OnMouseEnter(e); } protected override void OnMouseLeave(EventArgs e) { _hov = false; _press = false; Invalidate(); base.OnMouseLeave(e); } protected override void OnMouseDown(MouseEventArgs e) { if (e.Button == MouseButtons.Left) { _press = true; Invalidate(); } base.OnMouseDown(e); } protected override void OnMouseUp(MouseEventArgs e) { _press = false; Invalidate(); base.OnMouseUp(e); } protected override void OnEnabledChanged(EventArgs e) { Cursor = Enabled ? Cursors.Hand : Cursors.Default; Invalidate(); base.OnEnabledChanged(e); } } // ── StatusBadge (adapted from ZeloServerVerwaltung) ─────────────────── class StatusBadge : Control { public enum BMode { Entra, Joined, None, Working } BMode _mode; float _phase, _spin; public BMode Mode { get => _mode; set { _mode = value; Invalidate(); } } public StatusBadge() { SetStyle(ControlStyles.AllPaintingInWmPaint | ControlStyles.UserPaint | ControlStyles.OptimizedDoubleBuffer, true); Size = new Size(230, 28); BackColor = Color.White; } public void Tick(float ph, float sp) { _phase = ph; _spin = sp; if (_mode != BMode.None) Invalidate(); } protected override void OnPaint(PaintEventArgs e) { var g = e.Graphics; g.SmoothingMode = SmoothingMode.AntiAlias; g.TextRenderingHint = TextRenderingHint.ClearTypeGridFit; g.Clear(Color.White); Color bg; string txt; switch (_mode) { case BMode.Joined: bg = ColorTranslator.FromHtml("#059669"); txt = "DOMAIN JOINED"; break; case BMode.Entra: bg = ColorTranslator.FromHtml("#0D9488"); txt = "ENTRA ID JOINED"; break; case BMode.Working: bg = ColorTranslator.FromHtml("#D97706"); txt = "BITTE WARTEN..."; break; default: bg = ColorTranslator.FromHtml("#6B7280"); txt = "WORKGROUP"; break; } int r = Height / 2; using (var path = new GraphicsPath()) { path.AddArc(0, 0, r * 2, r * 2, 90, 180); path.AddArc(Width - r * 2, 0, r * 2, r * 2, 270, 180); path.CloseFigure(); using (var br = new SolidBrush(bg)) g.FillPath(br, path); } // Dot int dx = r, dy = r, dr = 5; if (_mode == BMode.Working) { using (var p2 = new Pen(Color.FromArgb(60, Color.White), 2f)) g.DrawEllipse(p2, dx - dr, dy - dr, dr * 2, dr * 2); using (var p2 = new Pen(Color.White, 2f) { StartCap = LineCap.Round, EndCap = LineCap.Round }) g.DrawArc(p2, dx - dr, dy - dr, dr * 2, dr * 2, _spin, 100f); } else { double pv = Math.Sin(_phase * Math.PI * 2) * 0.5 + 0.5; int gr = dr + (int)(pv * 2); using (var b = new SolidBrush(Color.FromArgb(60, Color.White))) g.FillEllipse(b, dx - gr, dy - gr, gr * 2, gr * 2); using (var b = new SolidBrush(Color.White)) g.FillEllipse(b, dx - dr, dy - dr, dr * 2, dr * 2); } // Text var rect = new RectangleF(r * 2 + 6, 0, Width - r * 2 - 6 - r, Height); var sf = new StringFormat { Alignment = StringAlignment.Near, LineAlignment = StringAlignment.Center }; using (var f = new Font("Segoe UI", 8.5f, FontStyle.Bold)) using (var br = new SolidBrush(Color.White)) g.DrawString(txt, f, br, rect, sf); } } } }