643 lines
42 KiB
C#
643 lines
42 KiB
C#
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<string>();
|
|
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);
|
|
}
|
|
}
|
|
}
|
|
}
|