최초 커밋: Windows 제품 키 백업 프로그램 (WinForms, .NET Framework 4.8)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019WT2pzDE45iP4QMAAo7cyzmain
commit
29f799ca08
|
|
@ -0,0 +1,11 @@
|
|||
# 빌드 결과물
|
||||
bin/
|
||||
obj/
|
||||
|
||||
# Visual Studio
|
||||
.vs/
|
||||
*.user
|
||||
*.suo
|
||||
|
||||
# 프로그램이 저장한 키 정보 파일 (개인 정보 — 커밋 금지)
|
||||
WindowsKeyInfo_*.txt
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
using System.Reflection;
|
||||
using System.Runtime.Versioning;
|
||||
[assembly: TargetFramework(".NETFramework,Version=v4.8", FrameworkDisplayName = ".NET Framework 4.8")]
|
||||
[assembly: AssemblyTitle("Windows 제품 키 백업")]
|
||||
[assembly: AssemblyProduct("WindowsKeyInfo")]
|
||||
[assembly: AssemblyVersion("1.0.0.0")]
|
||||
[assembly: AssemblyFileVersion("1.0.0.0")]
|
||||
|
|
@ -0,0 +1,623 @@
|
|||
using System;
|
||||
using System.Drawing;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using System.Windows.Forms;
|
||||
using Microsoft.Win32;
|
||||
|
||||
namespace WindowsKeyInfo
|
||||
{
|
||||
internal static class Program
|
||||
{
|
||||
[STAThread]
|
||||
private static void Main()
|
||||
{
|
||||
Application.EnableVisualStyles();
|
||||
Application.SetCompatibleTextRenderingDefault(false);
|
||||
Application.Run(new MainForm());
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>레지스트리에서 윈도우 키 정보를 읽어오는 클래스 (원본 VBScript 로직 이식)</summary>
|
||||
internal static class KeyReader
|
||||
{
|
||||
private const string CurrentVersionPath = @"SOFTWARE\Microsoft\Windows NT\CurrentVersion";
|
||||
private const string ComputerNamePath = @"SYSTEM\CurrentControlSet\Control\ComputerName\ComputerName";
|
||||
private const string NotFound = "없음";
|
||||
|
||||
public sealed class KeyInfo
|
||||
{
|
||||
public string ComputerName;
|
||||
public string ProductName;
|
||||
public string ProductId;
|
||||
public string InstalledKey;
|
||||
public string BackupKey;
|
||||
}
|
||||
|
||||
public static KeyInfo Read()
|
||||
{
|
||||
var info = new KeyInfo();
|
||||
|
||||
// 64비트 윈도우에서는 반드시 64비트 레지스트리 뷰로 읽어야 DigitalProductId가 정상적으로 보임
|
||||
RegistryView view = Environment.Is64BitOperatingSystem ? RegistryView.Registry64 : RegistryView.Default;
|
||||
|
||||
using (RegistryKey hklm = RegistryKey.OpenBaseKey(RegistryHive.LocalMachine, view))
|
||||
{
|
||||
using (RegistryKey cv = hklm.OpenSubKey(CurrentVersionPath))
|
||||
{
|
||||
info.ProductName = GetString(cv, "ProductName");
|
||||
info.ProductId = GetString(cv, "ProductId");
|
||||
|
||||
byte[] digitalId = cv == null ? null : cv.GetValue("DigitalProductId") as byte[];
|
||||
info.InstalledKey = ConvertToKey(digitalId);
|
||||
}
|
||||
|
||||
using (RegistryKey spp = hklm.OpenSubKey(CurrentVersionPath + @"\SoftwareProtectionPlatform"))
|
||||
{
|
||||
info.BackupKey = GetString(spp, "BackupProductKeyDefault");
|
||||
}
|
||||
|
||||
using (RegistryKey cn = hklm.OpenSubKey(ComputerNamePath))
|
||||
{
|
||||
string name = GetString(cn, "ComputerName");
|
||||
info.ComputerName = name == NotFound ? Environment.MachineName : name;
|
||||
}
|
||||
}
|
||||
|
||||
return info;
|
||||
}
|
||||
|
||||
private static string GetString(RegistryKey key, string valueName)
|
||||
{
|
||||
if (key == null) return NotFound;
|
||||
object value = key.GetValue(valueName);
|
||||
string text = value == null ? null : value.ToString();
|
||||
return string.IsNullOrWhiteSpace(text) ? NotFound : text;
|
||||
}
|
||||
|
||||
/// <summary>DigitalProductId(바이너리) → 25자리 제품 키 변환</summary>
|
||||
public static string ConvertToKey(byte[] digitalId)
|
||||
{
|
||||
const int keyOffset = 52;
|
||||
const string maps = "BCDFGHJKMPQRTVWXY2346789";
|
||||
|
||||
if (digitalId == null || digitalId.Length < keyOffset + 15) return NotFound;
|
||||
|
||||
byte[] key = (byte[])digitalId.Clone(); // 원본 배열 보존
|
||||
|
||||
// Windows 8 이상 형식인지 확인
|
||||
int isWin8 = (key[66] / 6) & 1;
|
||||
key[66] = (byte)((key[66] & 0xF7) | ((isWin8 & 2) * 4));
|
||||
|
||||
string keyOutput = string.Empty;
|
||||
int last = 0;
|
||||
|
||||
for (int i = 24; i >= 0; i--)
|
||||
{
|
||||
int current = 0;
|
||||
for (int j = 14; j >= 0; j--)
|
||||
{
|
||||
current = current * 256;
|
||||
current = key[j + keyOffset] + current;
|
||||
key[j + keyOffset] = (byte)(current / 24);
|
||||
current = current % 24;
|
||||
}
|
||||
keyOutput = maps[current] + keyOutput;
|
||||
last = current;
|
||||
}
|
||||
|
||||
// Windows 8 이상 키는 'N' 문자 위치를 보정
|
||||
if (isWin8 == 1)
|
||||
{
|
||||
string keypart1 = keyOutput.Substring(1, last);
|
||||
string keypart2 = keyOutput.Substring(last + 1);
|
||||
keyOutput = keypart1 + "N" + keypart2;
|
||||
}
|
||||
|
||||
return keyOutput.Substring(0, 5) + "-" +
|
||||
keyOutput.Substring(5, 5) + "-" +
|
||||
keyOutput.Substring(10, 5) + "-" +
|
||||
keyOutput.Substring(15, 5) + "-" +
|
||||
keyOutput.Substring(20, 5);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>메인 창 (WinForms)</summary>
|
||||
internal sealed class MainForm : Form
|
||||
{
|
||||
private readonly KeyReader.KeyInfo _info;
|
||||
private readonly TextBox _txtProductName;
|
||||
private readonly TextBox _txtProductId;
|
||||
private readonly TextBox _txtInstalledKey;
|
||||
private readonly TextBox _txtBackupKey;
|
||||
|
||||
public MainForm()
|
||||
{
|
||||
Text = "Windows 제품 키 백업";
|
||||
FormBorderStyle = FormBorderStyle.FixedDialog;
|
||||
MaximizeBox = false;
|
||||
StartPosition = FormStartPosition.CenterScreen;
|
||||
AutoScaleMode = AutoScaleMode.Dpi;
|
||||
Font = new Font("맑은 고딕", 9.5f);
|
||||
ClientSize = new Size(560, 344);
|
||||
|
||||
var table = new TableLayoutPanel
|
||||
{
|
||||
Dock = DockStyle.Top,
|
||||
Height = 180,
|
||||
Padding = new Padding(14, 14, 14, 0),
|
||||
ColumnCount = 2,
|
||||
RowCount = 4
|
||||
};
|
||||
table.ColumnStyles.Add(new ColumnStyle(SizeType.Absolute, 130));
|
||||
table.ColumnStyles.Add(new ColumnStyle(SizeType.Percent, 100));
|
||||
for (int r = 0; r < 4; r++) table.RowStyles.Add(new RowStyle(SizeType.Percent, 25));
|
||||
|
||||
_txtProductName = AddRow(table, 0, "제품 이름", false);
|
||||
_txtProductId = AddRow(table, 1, "제품 ID", false);
|
||||
_txtInstalledKey = AddRow(table, 2, "설치된 키", true);
|
||||
_txtBackupKey = AddRow(table, 3, "백업 키", true);
|
||||
|
||||
var buttons = new FlowLayoutPanel
|
||||
{
|
||||
Dock = DockStyle.Bottom,
|
||||
FlowDirection = FlowDirection.RightToLeft,
|
||||
Height = 56,
|
||||
Padding = new Padding(10, 10, 10, 10)
|
||||
};
|
||||
|
||||
var btnClose = MakeButton("닫기");
|
||||
btnClose.Click += (s, e) => Close();
|
||||
var btnSave = MakeButton("파일로 저장");
|
||||
btnSave.Click += (s, e) => SaveToFile();
|
||||
var btnCopy = MakeButton("복사");
|
||||
btnCopy.Click += (s, e) => CopyToClipboard();
|
||||
|
||||
buttons.Controls.Add(btnClose);
|
||||
buttons.Controls.Add(btnSave);
|
||||
buttons.Controls.Add(btnCopy);
|
||||
|
||||
// 화면 하단 바로가기 버튼 (기본 브라우저로 열기)
|
||||
var links = new FlowLayoutPanel
|
||||
{
|
||||
Dock = DockStyle.Bottom,
|
||||
FlowDirection = FlowDirection.LeftToRight,
|
||||
Height = 50,
|
||||
Padding = new Padding(14, 4, 10, 10)
|
||||
};
|
||||
|
||||
var lblLinks = new Label
|
||||
{
|
||||
Text = "바로가기:",
|
||||
AutoSize = true,
|
||||
Margin = new Padding(0, 9, 6, 0)
|
||||
};
|
||||
var btnGetCidMe = MakeLinkButton("GETCID.ME", "https://getcid.me/");
|
||||
var btnGetCidInfo = MakeLinkButton("GETCID.INFO", "https://getcid.info/");
|
||||
var btnGetCidUs = MakeLinkButton("GETCID.US", "https://getcid.us/");
|
||||
|
||||
links.Controls.Add(lblLinks);
|
||||
links.Controls.Add(btnGetCidMe);
|
||||
links.Controls.Add(btnGetCidInfo);
|
||||
links.Controls.Add(btnGetCidUs);
|
||||
|
||||
// 전화 인증 (slmgr 명령어 방식 — slui 4 창이 막혀 있어도 동작)
|
||||
var phone = new FlowLayoutPanel
|
||||
{
|
||||
Dock = DockStyle.Bottom,
|
||||
FlowDirection = FlowDirection.LeftToRight,
|
||||
Height = 44,
|
||||
Padding = new Padding(14, 4, 10, 0)
|
||||
};
|
||||
var lblPhone = new Label
|
||||
{
|
||||
Text = "전화 인증:",
|
||||
AutoSize = true,
|
||||
Margin = new Padding(0, 9, 6, 0)
|
||||
};
|
||||
var btnShowIid = MakeActionButton("① 설치 ID 보기");
|
||||
btnShowIid.Click += (s, e) => ShowInstallationId();
|
||||
var btnEnterCid = MakeActionButton("② 확인 ID 입력");
|
||||
btnEnterCid.Click += (s, e) => EnterConfirmationId();
|
||||
|
||||
phone.Controls.Add(lblPhone);
|
||||
phone.Controls.Add(btnShowIid);
|
||||
phone.Controls.Add(btnEnterCid);
|
||||
|
||||
var tip = new ToolTip();
|
||||
tip.SetToolTip(btnShowIid, "설치 ID(Installation ID)를 확인합니다 (slmgr /dti)");
|
||||
tip.SetToolTip(btnEnterCid, "받은 확인 ID(Confirmation ID)를 입력해 인증합니다 (slmgr /atp, 관리자 권한 필요)");
|
||||
tip.SetToolTip(btnGetCidMe, "https://getcid.me/");
|
||||
tip.SetToolTip(btnGetCidInfo, "https://getcid.info/");
|
||||
tip.SetToolTip(btnGetCidUs, "https://getcid.us/");
|
||||
|
||||
// Dock=Bottom 은 나중에 추가한 것이 더 아래에 배치됨
|
||||
Controls.Add(table);
|
||||
Controls.Add(buttons);
|
||||
Controls.Add(phone);
|
||||
Controls.Add(links);
|
||||
CancelButton = btnClose;
|
||||
|
||||
try
|
||||
{
|
||||
_info = KeyReader.Read();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
MessageBox.Show("레지스트리에서 정보를 읽지 못했습니다.\n\n" + ex.Message,
|
||||
Text, MessageBoxButtons.OK, MessageBoxIcon.Error);
|
||||
_info = new KeyReader.KeyInfo
|
||||
{
|
||||
ComputerName = Environment.MachineName,
|
||||
ProductName = "없음",
|
||||
ProductId = "없음",
|
||||
InstalledKey = "없음",
|
||||
BackupKey = "없음"
|
||||
};
|
||||
}
|
||||
|
||||
_txtProductName.Text = _info.ProductName;
|
||||
_txtProductId.Text = _info.ProductId;
|
||||
_txtInstalledKey.Text = _info.InstalledKey;
|
||||
_txtBackupKey.Text = _info.BackupKey;
|
||||
}
|
||||
|
||||
private static TextBox AddRow(TableLayoutPanel table, int row, string caption, bool monospace)
|
||||
{
|
||||
var label = new Label
|
||||
{
|
||||
Text = caption,
|
||||
Dock = DockStyle.Fill,
|
||||
TextAlign = ContentAlignment.MiddleLeft
|
||||
};
|
||||
var box = new TextBox
|
||||
{
|
||||
ReadOnly = true,
|
||||
Dock = DockStyle.Fill,
|
||||
Anchor = AnchorStyles.Left | AnchorStyles.Right,
|
||||
BackColor = SystemColors.Window
|
||||
};
|
||||
if (monospace) box.Font = new Font("Consolas", 11f, FontStyle.Bold);
|
||||
|
||||
table.Controls.Add(label, 0, row);
|
||||
table.Controls.Add(box, 1, row);
|
||||
return box;
|
||||
}
|
||||
|
||||
private static Button MakeButton(string text)
|
||||
{
|
||||
return new Button
|
||||
{
|
||||
Text = text,
|
||||
Width = 110,
|
||||
Height = 32,
|
||||
Margin = new Padding(6, 0, 0, 0)
|
||||
};
|
||||
}
|
||||
|
||||
private Button MakeLinkButton(string text, string url)
|
||||
{
|
||||
var btn = new Button
|
||||
{
|
||||
Text = text,
|
||||
Width = 130,
|
||||
Height = 32,
|
||||
Margin = new Padding(0, 0, 8, 0),
|
||||
Cursor = Cursors.Hand
|
||||
};
|
||||
btn.Click += (s, e) => OpenUrl(url);
|
||||
return btn;
|
||||
}
|
||||
|
||||
private void OpenUrl(string url)
|
||||
{
|
||||
try
|
||||
{
|
||||
System.Diagnostics.Process.Start(new System.Diagnostics.ProcessStartInfo(url) { UseShellExecute = true });
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
MessageBox.Show("브라우저를 열지 못했습니다.\n\n" + url + "\n\n" + ex.Message, Text,
|
||||
MessageBoxButtons.OK, MessageBoxIcon.Error);
|
||||
}
|
||||
}
|
||||
|
||||
private static Button MakeActionButton(string text)
|
||||
{
|
||||
return new Button
|
||||
{
|
||||
Text = text,
|
||||
Width = 140,
|
||||
Height = 32,
|
||||
Margin = new Padding(0, 0, 8, 0),
|
||||
Cursor = Cursors.Hand
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>System32 경로 (32비트 프로세스면 Sysnative 사용)</summary>
|
||||
private static string SystemDir()
|
||||
{
|
||||
string windir = Environment.GetFolderPath(Environment.SpecialFolder.Windows);
|
||||
string sysnative = Path.Combine(windir, "Sysnative");
|
||||
if (!Environment.Is64BitProcess && Directory.Exists(sysnative)) return sysnative;
|
||||
return Path.Combine(windir, "System32");
|
||||
}
|
||||
|
||||
/// <summary>① 설치 ID 보기: cscript slmgr.vbs /dti 실행 후 결과 표시</summary>
|
||||
private void ShowInstallationId()
|
||||
{
|
||||
string sys = SystemDir();
|
||||
string cscript = Path.Combine(sys, "cscript.exe");
|
||||
string slmgr = Path.Combine(sys, "slmgr.vbs");
|
||||
string output;
|
||||
|
||||
Cursor = Cursors.WaitCursor;
|
||||
try
|
||||
{
|
||||
var psi = new System.Diagnostics.ProcessStartInfo(cscript, "//nologo \"" + slmgr + "\" /dti")
|
||||
{
|
||||
UseShellExecute = false,
|
||||
RedirectStandardOutput = true,
|
||||
RedirectStandardError = true,
|
||||
CreateNoWindow = true,
|
||||
StandardOutputEncoding = Encoding.GetEncoding(
|
||||
System.Globalization.CultureInfo.CurrentCulture.TextInfo.OEMCodePage)
|
||||
};
|
||||
using (var p = System.Diagnostics.Process.Start(psi))
|
||||
{
|
||||
output = p.StandardOutput.ReadToEnd() + p.StandardError.ReadToEnd();
|
||||
p.WaitForExit(60000);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Cursor = Cursors.Default;
|
||||
MessageBox.Show("설치 ID를 가져오지 못했습니다.\n\n" + ex.Message, Text,
|
||||
MessageBoxButtons.OK, MessageBoxIcon.Error);
|
||||
return;
|
||||
}
|
||||
Cursor = Cursors.Default;
|
||||
|
||||
// 출력에서 가장 긴 숫자열(설치 ID, 보통 63자리)을 추출
|
||||
string iid = null;
|
||||
foreach (System.Text.RegularExpressions.Match m in
|
||||
System.Text.RegularExpressions.Regex.Matches(output, @"\d{40,}"))
|
||||
{
|
||||
if (iid == null || m.Value.Length > iid.Length) iid = m.Value;
|
||||
}
|
||||
|
||||
if (iid == null)
|
||||
{
|
||||
MessageBox.Show("설치 ID를 찾지 못했습니다. 윈도우가 보낸 메시지:\n\n" + output.Trim(), Text,
|
||||
MessageBoxButtons.OK, MessageBoxIcon.Warning);
|
||||
return;
|
||||
}
|
||||
|
||||
using (var dlg = new InstallationIdDialog(iid))
|
||||
{
|
||||
dlg.ShowDialog(this);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>② 확인 ID 입력: 관리자 권한으로 slmgr.vbs /atp 확인ID 실행</summary>
|
||||
private void EnterConfirmationId()
|
||||
{
|
||||
string cid;
|
||||
using (var dlg = new ConfirmationIdDialog())
|
||||
{
|
||||
if (dlg.ShowDialog(this) != DialogResult.OK) return;
|
||||
cid = dlg.ConfirmationId;
|
||||
}
|
||||
|
||||
string sys = SystemDir();
|
||||
string wscript = Path.Combine(sys, "wscript.exe");
|
||||
string slmgr = Path.Combine(sys, "slmgr.vbs");
|
||||
|
||||
try
|
||||
{
|
||||
// wscript로 실행하면 slmgr가 결과(성공/오류)를 자체 메시지 창으로 보여 줌
|
||||
var psi = new System.Diagnostics.ProcessStartInfo(wscript, "\"" + slmgr + "\" /atp " + cid)
|
||||
{
|
||||
UseShellExecute = true,
|
||||
Verb = "runas" // 관리자 권한 요청 (UAC 창)
|
||||
};
|
||||
System.Diagnostics.Process.Start(psi);
|
||||
}
|
||||
catch (System.ComponentModel.Win32Exception ex) when (ex.NativeErrorCode == 1223)
|
||||
{
|
||||
MessageBox.Show("관리자 권한 요청이 취소되었습니다.\n확인 ID 입력에는 관리자 권한이 필요합니다.", Text,
|
||||
MessageBoxButtons.OK, MessageBoxIcon.Information);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
MessageBox.Show("확인 ID 입력을 실행하지 못했습니다.\n\n" + ex.Message, Text,
|
||||
MessageBoxButtons.OK, MessageBoxIcon.Error);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>원본 스크립트와 같은 형식의 텍스트</summary>
|
||||
private string BuildText()
|
||||
{
|
||||
var sb = new StringBuilder();
|
||||
sb.AppendLine("Product Name: " + _info.ProductName);
|
||||
sb.AppendLine("Product ID: " + _info.ProductId);
|
||||
sb.AppendLine("Orginal Installed Key: " + _info.InstalledKey);
|
||||
sb.AppendLine("Backup Installed Key: " + _info.BackupKey);
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
private void CopyToClipboard()
|
||||
{
|
||||
try
|
||||
{
|
||||
Clipboard.SetText(BuildText());
|
||||
MessageBox.Show("키 정보를 클립보드에 복사했습니다.", Text,
|
||||
MessageBoxButtons.OK, MessageBoxIcon.Information);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
MessageBox.Show("클립보드 복사에 실패했습니다.\n\n" + ex.Message, Text,
|
||||
MessageBoxButtons.OK, MessageBoxIcon.Error);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>exe가 있는 폴더에 WindowsKeyInfo_컴퓨터이름_사용자이름.txt 로 저장</summary>
|
||||
private void SaveToFile()
|
||||
{
|
||||
string computer = (_info.ComputerName ?? Environment.MachineName).Replace(" ", "");
|
||||
string user = Environment.UserName;
|
||||
string fileName = "WindowsKeyInfo_" + computer + "_" + user + ".txt";
|
||||
string folder = AppDomain.CurrentDomain.BaseDirectory;
|
||||
string fullPath = Path.Combine(folder, fileName);
|
||||
|
||||
try
|
||||
{
|
||||
File.WriteAllText(fullPath, BuildText(), new UTF8Encoding(true));
|
||||
MessageBox.Show("저장했습니다.\n\n" + fullPath, Text,
|
||||
MessageBoxButtons.OK, MessageBoxIcon.Information);
|
||||
}
|
||||
catch (UnauthorizedAccessException)
|
||||
{
|
||||
MessageBox.Show("이 폴더에 쓸 권한이 없습니다.\n\n" + folder +
|
||||
"\n\nexe를 바탕화면이나 문서 폴더 등 쓰기 가능한 곳으로 옮긴 뒤 다시 시도해 주세요.",
|
||||
Text, MessageBoxButtons.OK, MessageBoxIcon.Warning);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
MessageBox.Show("파일 저장에 실패했습니다.\n\n" + ex.Message, Text,
|
||||
MessageBoxButtons.OK, MessageBoxIcon.Error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>설치 ID 표시 창 (7자리씩 9묶음)</summary>
|
||||
internal sealed class InstallationIdDialog : Form
|
||||
{
|
||||
public InstallationIdDialog(string iid)
|
||||
{
|
||||
Text = "설치 ID (Installation ID)";
|
||||
FormBorderStyle = FormBorderStyle.FixedDialog;
|
||||
MaximizeBox = false;
|
||||
MinimizeBox = false;
|
||||
ShowInTaskbar = false;
|
||||
StartPosition = FormStartPosition.CenterParent;
|
||||
AutoScaleMode = AutoScaleMode.Dpi;
|
||||
Font = new Font("맑은 고딕", 9.5f);
|
||||
ClientSize = new Size(620, 190);
|
||||
|
||||
var sbGrouped = new StringBuilder();
|
||||
for (int i = 0; i < iid.Length; i += 7)
|
||||
{
|
||||
if (i > 0) sbGrouped.Append(' ');
|
||||
sbGrouped.Append(iid.Substring(i, Math.Min(7, iid.Length - i)));
|
||||
}
|
||||
string grouped = sbGrouped.ToString();
|
||||
|
||||
var lbl = new Label
|
||||
{
|
||||
Text = "아래 설치 ID를 전화 자동 응답에 입력하거나 확인 ID 발급처에 전달하세요.\n" +
|
||||
"받은 확인 ID는 메인 창의 [② 확인 ID 입력]에 넣으면 됩니다.",
|
||||
Location = new Point(14, 12),
|
||||
Size = new Size(592, 42)
|
||||
};
|
||||
var box = new TextBox
|
||||
{
|
||||
Text = grouped,
|
||||
ReadOnly = true,
|
||||
Multiline = true,
|
||||
Location = new Point(14, 60),
|
||||
Size = new Size(592, 64),
|
||||
Font = new Font("Consolas", 12f, FontStyle.Bold),
|
||||
BackColor = SystemColors.Window
|
||||
};
|
||||
|
||||
var btnCopy = new Button { Text = "복사 (숫자만)", Location = new Point(366, 140), Size = new Size(120, 32) };
|
||||
btnCopy.Click += (s, e) =>
|
||||
{
|
||||
try
|
||||
{
|
||||
Clipboard.SetText(iid);
|
||||
MessageBox.Show("설치 ID를 클립보드에 복사했습니다.", Text, MessageBoxButtons.OK, MessageBoxIcon.Information);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
MessageBox.Show("복사에 실패했습니다.\n\n" + ex.Message, Text, MessageBoxButtons.OK, MessageBoxIcon.Error);
|
||||
}
|
||||
};
|
||||
var btnClose = new Button { Text = "닫기", Location = new Point(494, 140), Size = new Size(112, 32), DialogResult = DialogResult.OK };
|
||||
|
||||
Controls.Add(lbl);
|
||||
Controls.Add(box);
|
||||
Controls.Add(btnCopy);
|
||||
Controls.Add(btnClose);
|
||||
AcceptButton = btnClose;
|
||||
CancelButton = btnClose;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>확인 ID 입력 창 (48자리 숫자, 하이픈/공백 허용)</summary>
|
||||
internal sealed class ConfirmationIdDialog : Form
|
||||
{
|
||||
private readonly TextBox _box;
|
||||
public string ConfirmationId { get; private set; }
|
||||
|
||||
public ConfirmationIdDialog()
|
||||
{
|
||||
Text = "확인 ID (Confirmation ID) 입력";
|
||||
FormBorderStyle = FormBorderStyle.FixedDialog;
|
||||
MaximizeBox = false;
|
||||
MinimizeBox = false;
|
||||
ShowInTaskbar = false;
|
||||
StartPosition = FormStartPosition.CenterParent;
|
||||
AutoScaleMode = AutoScaleMode.Dpi;
|
||||
Font = new Font("맑은 고딕", 9.5f);
|
||||
ClientSize = new Size(620, 170);
|
||||
|
||||
var lbl = new Label
|
||||
{
|
||||
Text = "받은 확인 ID 48자리를 입력하세요. 하이픈(-)이나 공백은 넣어도 자동으로 제거됩니다.\n" +
|
||||
"[인증]을 누르면 관리자 권한 확인 창이 뜨고, 결과는 윈도우 메시지 창으로 표시됩니다.",
|
||||
Location = new Point(14, 12),
|
||||
Size = new Size(592, 42)
|
||||
};
|
||||
_box = new TextBox
|
||||
{
|
||||
Location = new Point(14, 62),
|
||||
Size = new Size(592, 30),
|
||||
Font = new Font("Consolas", 12f, FontStyle.Bold)
|
||||
};
|
||||
|
||||
var btnOk = new Button { Text = "인증", Location = new Point(366, 120), Size = new Size(120, 32) };
|
||||
btnOk.Click += (s, e) => TryAccept();
|
||||
var btnCancel = new Button { Text = "취소", Location = new Point(494, 120), Size = new Size(112, 32), DialogResult = DialogResult.Cancel };
|
||||
|
||||
Controls.Add(lbl);
|
||||
Controls.Add(_box);
|
||||
Controls.Add(btnOk);
|
||||
Controls.Add(btnCancel);
|
||||
AcceptButton = btnOk;
|
||||
CancelButton = btnCancel;
|
||||
}
|
||||
|
||||
private void TryAccept()
|
||||
{
|
||||
var digits = new StringBuilder();
|
||||
foreach (char c in _box.Text)
|
||||
{
|
||||
if (c >= '0' && c <= '9') digits.Append(c);
|
||||
}
|
||||
|
||||
if (digits.Length != 48)
|
||||
{
|
||||
MessageBox.Show("확인 ID는 숫자 48자리여야 합니다.\n현재 입력된 숫자: " + digits.Length + "자리", Text,
|
||||
MessageBoxButtons.OK, MessageBoxIcon.Warning);
|
||||
_box.Focus();
|
||||
return;
|
||||
}
|
||||
|
||||
ConfirmationId = digits.ToString();
|
||||
DialogResult = DialogResult.OK;
|
||||
Close();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,28 @@
|
|||
# WindowsKeyInfo
|
||||
|
||||
Windows 제품 키 정보를 확인하고 백업하는 WinForms 프로그램입니다.
|
||||
|
||||
## 주요 기능
|
||||
|
||||
- 레지스트리에서 제품 이름, 제품 ID, 설치된 키, 백업 키 확인
|
||||
- 키 정보 클립보드 복사
|
||||
- 키 정보를 `WindowsKeyInfo_컴퓨터이름_사용자이름.txt` 파일로 저장 (exe와 같은 폴더)
|
||||
- 전화 인증 지원
|
||||
- ① 설치 ID 보기 (`slmgr /dti`)
|
||||
- ② 확인 ID 입력 (`slmgr /atp`, 관리자 권한 필요)
|
||||
- 확인 ID 발급 사이트 바로가기 버튼
|
||||
|
||||
## 요구 사항
|
||||
|
||||
- Windows
|
||||
- .NET Framework 4.8
|
||||
|
||||
## 빌드
|
||||
|
||||
```
|
||||
dotnet build -c Release
|
||||
```
|
||||
|
||||
## 주의
|
||||
|
||||
저장된 `WindowsKeyInfo_*.txt` 파일에는 제품 키가 들어 있으므로 외부에 공유하지 마세요.
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>WinExe</OutputType>
|
||||
<TargetFramework>net48</TargetFramework>
|
||||
<LangVersion>7.3</LangVersion>
|
||||
<AssemblyName>WindowsKeyInfo</AssemblyName>
|
||||
<RootNamespace>WindowsKeyInfo</RootNamespace>
|
||||
<PlatformTarget>AnyCPU</PlatformTarget>
|
||||
<Prefer32Bit>false</Prefer32Bit>
|
||||
<Nullable>disable</Nullable>
|
||||
<GenerateAssemblyInfo>true</GenerateAssemblyInfo>
|
||||
<AssemblyTitle>Windows 제품 키 백업</AssemblyTitle>
|
||||
<Product>WindowsKeyInfo</Product>
|
||||
<Version>1.0.0</Version>
|
||||
<DebugType>none</DebugType>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Reference Include="System.Windows.Forms" />
|
||||
<Reference Include="System.Drawing" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
Loading…
Reference in New Issue