xetup/config-editor.hta
Filip Zubik c42943cfa8 PS scripts, web platform, Forgejo CI, xetup.exe launcher
Initial deployment suite for X9.cz MSP Windows 10/11 deployment:
- PowerShell scripts 00-11: admin account, bloatware removal, software (winget+Atera),
  system registry tweaks, default profile, personalization, scheduled tasks,
  BackInfo desktop info, Windows activation, PC identity/rename, network, Dell Update
- Web platform: xetup.x9.cz (nginx), spec/annotation page, /dl shortlink, GitHub mirror
- Forgejo Actions CI: auto-build xetup.exe on push, publish to releases/latest
- Go xetup.exe: embeds all scripts/assets, per-feature checkboxes, load/save config
2026-04-16 14:49:41 +02:00

632 lines
20 KiB
HTML

<html>
<head>
<title>X9 - Deployment Config Editor</title>
<HTA:APPLICATION
ID="ConfigEditor"
APPLICATIONNAME="X9 Config Editor"
SCROLL="no"
SINGLEINSTANCE="yes"
WINDOWSTATE="normal"
INNERBORDER="no"
SELECTION="no"
CONTEXTMENU="no"
/>
<meta http-equiv="x-ua-compatible" content="ie=11">
<style>
* { box-sizing: border-box; margin: 0; padding: 0; }
body {
font-family: Segoe UI, Arial, sans-serif;
font-size: 13px;
background: #1a2a33;
color: #d0dde3;
height: 100vh;
overflow: hidden;
display: flex;
flex-direction: column;
}
#header {
background: #223B47;
padding: 12px 18px;
border-bottom: 2px solid #2e5568;
flex-shrink: 0;
}
#header h1 {
font-size: 16px;
font-weight: 600;
color: #e8f4f8;
letter-spacing: 0.5px;
}
#header .config-path {
font-size: 11px;
color: #7aabbd;
margin-top: 3px;
word-break: break-all;
}
#tabs {
display: flex;
background: #1a2a33;
border-bottom: 1px solid #2e5568;
flex-shrink: 0;
}
.tab {
padding: 8px 18px;
cursor: pointer;
color: #7aabbd;
border-bottom: 2px solid transparent;
font-size: 12px;
font-weight: 500;
}
.tab:hover { color: #b0d4e0; }
.tab.active { color: #e8f4f8; border-bottom: 2px solid #4a9aba; background: #1e3340; }
#content {
flex: 1;
overflow-y: auto;
padding: 0;
}
.tab-panel { display: none; padding: 16px 18px; }
.tab-panel.active { display: block; }
table { width: 100%; border-collapse: collapse; }
th {
background: #223B47;
color: #9ac8d8;
font-size: 11px;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.5px;
padding: 7px 10px;
text-align: left;
border-bottom: 1px solid #2e5568;
}
td {
padding: 6px 10px;
border-bottom: 1px solid #1e3340;
vertical-align: middle;
}
tr:hover td { background: #1e3340; }
.step-num {
color: #4a9aba;
font-size: 11px;
font-weight: 600;
width: 36px;
}
.step-name { color: #d0dde3; }
.info-btn {
background: #2e5568;
border: none;
color: #7aabbd;
width: 20px;
height: 20px;
border-radius: 50%;
cursor: pointer;
font-size: 11px;
font-weight: 700;
line-height: 20px;
text-align: center;
position: relative;
}
.info-btn:hover { background: #3a6f8a; color: #e8f4f8; }
input[type="checkbox"] {
width: 16px;
height: 16px;
cursor: pointer;
accent-color: #4a9aba;
}
input[type="text"], select {
background: #1a2a33;
border: 1px solid #2e5568;
color: #d0dde3;
padding: 5px 8px;
border-radius: 3px;
font-size: 12px;
width: 100%;
}
input[type="text"]:focus, select:focus {
outline: none;
border-color: #4a9aba;
}
.label-cell {
width: 160px;
color: #9ac8d8;
font-size: 12px;
font-weight: 500;
padding-top: 10px;
vertical-align: top;
}
.section-title {
color: #4a9aba;
font-size: 12px;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.5px;
margin: 16px 0 8px 0;
padding-bottom: 4px;
border-bottom: 1px solid #2e5568;
}
.section-title:first-child { margin-top: 0; }
.btn {
background: #223B47;
border: 1px solid #2e5568;
color: #d0dde3;
padding: 5px 12px;
border-radius: 3px;
cursor: pointer;
font-size: 12px;
}
.btn:hover { background: #2e5568; color: #e8f4f8; }
.btn-danger {
background: #3d1f1f;
border-color: #6b2c2c;
color: #d08080;
}
.btn-danger:hover { background: #5a2020; color: #f0a0a0; }
.btn-add {
background: #1a3a2a;
border-color: #2e6a4a;
color: #80c8a0;
margin-top: 8px;
}
.btn-add:hover { background: #235a38; color: #a0e0b8; }
#footer {
background: #223B47;
border-top: 2px solid #2e5568;
padding: 10px 18px;
display: flex;
align-items: center;
gap: 14px;
flex-shrink: 0;
}
#btn-save {
background: #1a5c3a;
border: 1px solid #2a8a58;
color: #80e0b0;
padding: 7px 22px;
border-radius: 3px;
cursor: pointer;
font-size: 13px;
font-weight: 600;
}
#btn-save:hover { background: #206e46; color: #a0f0c8; }
#status {
font-size: 12px;
color: #7aabbd;
flex: 1;
}
#tooltip {
position: fixed;
background: #0f1e26;
border: 1px solid #3a7a9a;
color: #c0dde8;
padding: 8px 12px;
border-radius: 4px;
font-size: 12px;
max-width: 340px;
line-height: 1.5;
z-index: 9999;
display: none;
pointer-events: none;
box-shadow: 0 4px 12px rgba(0,0,0,0.5);
}
.settings-grid { display: grid; grid-template-columns: 160px 1fr; gap: 8px 12px; align-items: center; }
.settings-grid .span2 { grid-column: 1 / -1; }
</style>
</head>
<body>
<div id="header">
<h1>X9 - Deployment Config</h1>
<div class="config-path" id="config-path-display">Loading...</div>
</div>
<div id="tabs">
<div class="tab active" onclick="showTab('steps')">Steps</div>
<div class="tab" onclick="showTab('software')">Software</div>
<div class="tab" onclick="showTab('settings')">Settings</div>
</div>
<div id="content">
<!-- STEPS TAB -->
<div class="tab-panel active" id="tab-steps">
<table id="steps-table">
<thead>
<tr>
<th style="width:40px;">On</th>
<th style="width:36px;">Step</th>
<th>Name</th>
<th style="width:32px;"></th>
</tr>
</thead>
<tbody id="steps-tbody">
</tbody>
</table>
</div>
<!-- SOFTWARE TAB -->
<div class="tab-panel" id="tab-software">
<table id="software-table">
<thead>
<tr>
<th>Package Name</th>
<th>Winget ID</th>
<th style="width:70px;"></th>
</tr>
</thead>
<tbody id="software-tbody">
</tbody>
</table>
<button class="btn btn-add" onclick="addSoftwareRow()">+ Add package</button>
</div>
<!-- SETTINGS TAB -->
<div class="tab-panel" id="tab-settings">
<div class="section-title">Deployment</div>
<div class="settings-grid">
<div class="label-cell">Timezone</div>
<div><input type="text" id="s-timezone" onchange="updateSetting('deployment','timezone',this.value)"></div>
<div class="label-cell">Locale</div>
<div><input type="text" id="s-locale" onchange="updateSetting('deployment','locale',this.value)"></div>
</div>
<div class="section-title">Admin Account</div>
<div class="settings-grid">
<div class="label-cell">Username</div>
<div><input type="text" id="s-admin-user" onchange="updateSetting('adminAccount','username',this.value)"></div>
<div class="label-cell">Password</div>
<div><input type="text" id="s-admin-pass" onchange="updateSetting('adminAccount','password',this.value)"></div>
<div class="label-cell">Description</div>
<div><input type="text" id="s-admin-desc" onchange="updateSetting('adminAccount','description',this.value)"></div>
</div>
<div class="section-title">PDF Default</div>
<div class="settings-grid">
<div class="label-cell">Force Adobe Reader</div>
<div><input type="checkbox" id="s-force-adobe" onchange="updateSettingBool('pdfDefault','forceAdobeReader',this.checked)"></div>
<div class="label-cell">Scheduled Task</div>
<div><input type="checkbox" id="s-pdf-task" onchange="updateSettingBool('pdfDefault','scheduledTaskEnabled',this.checked)"></div>
</div>
<div class="section-title">Desktop Info</div>
<div class="settings-grid">
<div class="label-cell">Enabled</div>
<div><input type="checkbox" id="s-di-enabled" onchange="updateSettingBool('desktopInfo','enabled',this.checked)"></div>
<div class="label-cell">Position</div>
<div>
<select id="s-di-position" onchange="updateSetting('desktopInfo','position',this.value)">
<option value="bottomRight">Bottom Right</option>
<option value="bottomLeft">Bottom Left</option>
<option value="topRight">Top Right</option>
<option value="topLeft">Top Left</option>
<option value="center">Center</option>
</select>
</div>
<div class="label-cell">Font Size</div>
<div><input type="text" id="s-di-fontsize" onchange="updateSettingInt('desktopInfo','fontSize',this.value)"></div>
<div class="label-cell">Font Color</div>
<div><input type="text" id="s-di-fontcolor" onchange="updateSetting('desktopInfo','fontColor',this.value)" placeholder="#FFFFFF"></div>
</div>
<div class="section-title">Activation</div>
<div class="settings-grid">
<div class="label-cell">Product Key</div>
<div><input type="text" id="s-act-key" onchange="updateSetting('activation','productKey',this.value)" placeholder="XXXXX-XXXXX-XXXXX-XXXXX-XXXXX"></div>
<div class="label-cell">KMS Server</div>
<div><input type="text" id="s-act-kms" onchange="updateSetting('activation','kmsServer',this.value)" placeholder="kms.example.com"></div>
</div>
</div>
</div>
<div id="footer">
<button id="btn-save" onclick="saveConfig()">Save config.json</button>
<div id="status">Ready.</div>
</div>
<div id="tooltip"></div>
<script language="JScript">
var STEP_DEFS = [
{ key: "adminAccount", num: "00", label: "Admin account", info: "Creates local admin account for MSP remote access. Account name and generated password are saved to Deploy.log." },
{ key: "bloatware", num: "01", label: "Bloatware removal", info: "Removes 44 pre-installed UWP apps (Teams, Xbox, Cortana, Solitaire, Office Hub, Skype...) and unused Windows Capabilities and Features. Calculator is kept." },
{ key: "software", num: "02", label: "Software install", info: "Installs packages from the list below via winget. Also sets Adobe Reader as default PDF viewer via HKCR." },
{ key: "systemRegistry", num: "03", label: "System registry", info: "HKLM-wide tweaks: disables Widgets, Teams auto-install, Edge first run, OneDrive, Outlook auto-install, GameDVR, Recall. Sets timezone. Hides taskbar search box via policy (Win11)." },
{ key: "defaultProfile", num: "04", label: "Default profile", info: "Modifies C:\\Users\\Default\\NTUSER.DAT so ALL future users inherit: left-aligned taskbar, hidden search/Copilot/TaskView/Widgets buttons, file extensions visible, Explorer opens to This PC, Num Lock on." },
{ key: "personalization", num: "05", label: "Personalization", info: "Dark system theme, light app theme, accent color #223B47, transparency off, accent on title bars. Applied to Default profile and current user." },
{ key: "scheduledTasks", num: "06", label: "Scheduled tasks", info: "Registers 4 tasks: ShowAllTrayIcons (logon - clears systray cache + restarts Explorer), UnlockStartLayout (once), PDF-DefaultApp (logon as SYSTEM - restores HKCR association), DesktopInfo (logon - renders wallpaper)." },
{ key: "desktopInfo", num: "07", label: "Desktop info", info: "Custom desktop wallpaper showing: computer name, IP, OS version, username, deployment date. Rendered as BMP on every logon via scheduled task. Replaces BackInfo.exe." },
{ key: "activation", num: "08", label: "Windows activation", info: "Checks and applies Windows activation." }
];
var configPath = "";
var config = null;
var fso = null;
function init() {
try {
fso = new ActiveXObject("Scripting.FileSystemObject");
// Derive config path from HTA location
var htaPath = location.pathname.replace(/\//g, "\\");
// Remove leading backslash from pathname
if (htaPath.charAt(0) === "\\") {
htaPath = htaPath.substring(1);
}
var dir = fso.GetParentFolderName(htaPath);
configPath = dir + "\\config\\config.json";
document.getElementById("config-path-display").innerText = configPath;
loadConfig();
buildStepsTable();
resizeWindow();
} catch(e) {
setStatus("Init error: " + e.message, true);
}
}
function resizeWindow() {
window.resizeTo(800, 720);
window.moveTo(
(screen.width - 800) / 2,
(screen.height - 720) / 2
);
}
function loadConfig() {
try {
if (!fso.FileExists(configPath)) {
setStatus("config.json not found: " + configPath, true);
config = {};
return;
}
var f = fso.OpenTextFile(configPath, 1, false, -1);
var raw = f.ReadAll();
f.Close();
config = JSON.parse(raw);
setStatus("Loaded: " + configPath);
populateSettings();
buildSoftwareTable();
} catch(e) {
setStatus("Load error: " + e.message, true);
config = {};
}
}
function buildStepsTable() {
var tbody = document.getElementById("steps-tbody");
var html = "";
for (var i = 0; i < STEP_DEFS.length; i++) {
var s = STEP_DEFS[i];
var checked = "";
// Check config.steps if loaded, default true
if (config && config.steps && config.steps[s.key] === false) {
checked = "";
} else {
checked = "checked";
}
html += "<tr>";
html += "<td><input type='checkbox' " + checked + " onclick=\"toggleStep('" + s.key + "', this.checked)\"></td>";
html += "<td class='step-num'>" + s.num + "</td>";
html += "<td class='step-name'>" + s.label + "</td>";
html += "<td><button class='info-btn' onmouseover=\"showTooltip(event, '" + escapeQ(s.info) + "')\" onmouseout='hideTooltip()'>i</button></td>";
html += "</tr>";
}
tbody.innerHTML = html;
}
function buildSoftwareTable() {
var tbody = document.getElementById("software-tbody");
var html = "";
if (config && config.software && config.software.install) {
var list = config.software.install;
for (var i = 0; i < list.length; i++) {
html += makeSoftwareRow(i, list[i].name, list[i].wingetId);
}
}
tbody.innerHTML = html;
}
function makeSoftwareRow(idx, name, wingetId) {
return "<tr id='sw-row-" + idx + "'>" +
"<td><input type='text' value='" + escapeQ(name) + "' onchange=\"updateSoftwareName(" + idx + ", this.value)\"></td>" +
"<td><input type='text' value='" + escapeQ(wingetId) + "' onchange=\"updateSoftwareId(" + idx + ", this.value)\"></td>" +
"<td><button class='btn btn-danger' onclick='removeSoftwareRow(" + idx + ")'>Remove</button></td>" +
"</tr>";
}
function addSoftwareRow() {
if (!config.software) { config.software = {}; }
if (!config.software.install) { config.software.install = []; }
config.software.install.push({ name: "", wingetId: "" });
buildSoftwareTable();
setStatus("Package added. Fill in name and Winget ID, then save.");
}
function removeSoftwareRow(idx) {
if (!config || !config.software || !config.software.install) { return; }
config.software.install.splice(idx, 1);
buildSoftwareTable();
setStatus("Package removed. Click Save to persist.");
}
function updateSoftwareName(idx, val) {
if (config && config.software && config.software.install && config.software.install[idx] !== undefined) {
config.software.install[idx].name = val;
}
}
function updateSoftwareId(idx, val) {
if (config && config.software && config.software.install && config.software.install[idx] !== undefined) {
config.software.install[idx].wingetId = val;
}
}
function populateSettings() {
if (!config) { return; }
if (config.deployment) {
setVal("s-timezone", config.deployment.timezone || "");
setVal("s-locale", config.deployment.locale || "");
}
if (config.adminAccount) {
setVal("s-admin-user", config.adminAccount.username || "");
setVal("s-admin-pass", config.adminAccount.password || "");
setVal("s-admin-desc", config.adminAccount.description || "");
}
if (config.pdfDefault) {
setChk("s-force-adobe", config.pdfDefault.forceAdobeReader !== false);
setChk("s-pdf-task", config.pdfDefault.scheduledTaskEnabled !== false);
}
if (config.desktopInfo) {
setChk("s-di-enabled", config.desktopInfo.enabled !== false);
setSelVal("s-di-position", config.desktopInfo.position || "bottomRight");
setVal("s-di-fontsize", config.desktopInfo.fontSize !== undefined ? String(config.desktopInfo.fontSize) : "12");
setVal("s-di-fontcolor", config.desktopInfo.fontColor || "#FFFFFF");
}
if (config.activation) {
setVal("s-act-key", config.activation.productKey || "");
setVal("s-act-kms", config.activation.kmsServer || "");
}
}
function setVal(id, val) {
var el = document.getElementById(id);
if (el) { el.value = val; }
}
function setChk(id, val) {
var el = document.getElementById(id);
if (el) { el.checked = !!val; }
}
function setSelVal(id, val) {
var el = document.getElementById(id);
if (!el) { return; }
for (var i = 0; i < el.options.length; i++) {
if (el.options[i].value === val) {
el.selectedIndex = i;
break;
}
}
}
function toggleStep(key, enabled) {
if (!config) { return; }
if (!config.steps) { config.steps = {}; }
config.steps[key] = enabled;
}
function updateSetting(section, key, val) {
if (!config) { return; }
if (!config[section]) { config[section] = {}; }
config[section][key] = val;
}
function updateSettingBool(section, key, val) {
if (!config) { return; }
if (!config[section]) { config[section] = {}; }
config[section][key] = !!val;
}
function updateSettingInt(section, key, val) {
if (!config) { return; }
if (!config[section]) { config[section] = {}; }
var n = parseInt(val, 10);
config[section][key] = isNaN(n) ? val : n;
}
function saveConfig() {
if (!config) { setStatus("No config loaded.", true); return; }
try {
// Sync steps checkboxes before save (in case table was rebuilt)
syncStepsFromTable();
var json = JSON.stringify(config, null, 2);
var f = fso.CreateTextFile(configPath, true, true);
f.Write(json);
f.Close();
setStatus("Saved: " + configPath + " [" + now() + "]");
} catch(e) {
setStatus("Save error: " + e.message, true);
}
}
function syncStepsFromTable() {
if (!config.steps) { config.steps = {}; }
var rows = document.getElementById("steps-tbody").getElementsByTagName("tr");
for (var i = 0; i < rows.length; i++) {
var chk = rows[i].getElementsByTagName("input")[0];
if (chk && STEP_DEFS[i]) {
config.steps[STEP_DEFS[i].key] = chk.checked;
}
}
}
function showTab(name) {
var panels = document.getElementsByClassName("tab-panel");
for (var i = 0; i < panels.length; i++) {
panels[i].className = "tab-panel";
}
var tabs = document.getElementsByClassName("tab");
for (var i = 0; i < tabs.length; i++) {
tabs[i].className = "tab";
}
document.getElementById("tab-" + name).className = "tab-panel active";
var allTabs = document.getElementById("tabs").getElementsByClassName("tab");
var nameMap = { steps: 0, software: 1, settings: 2 };
if (nameMap[name] !== undefined) {
allTabs[nameMap[name]].className = "tab active";
}
}
function showTooltip(evt, text) {
var t = document.getElementById("tooltip");
t.innerText = text;
t.style.display = "block";
positionTooltip(evt);
}
function positionTooltip(evt) {
var t = document.getElementById("tooltip");
var x = evt.clientX + 12;
var y = evt.clientY + 12;
if (x + 350 > document.body.clientWidth) { x = evt.clientX - 350; }
if (y + 80 > document.body.clientHeight) { y = evt.clientY - 80; }
t.style.left = x + "px";
t.style.top = y + "px";
}
function hideTooltip() {
document.getElementById("tooltip").style.display = "none";
}
function setStatus(msg, isError) {
var el = document.getElementById("status");
el.innerText = msg;
el.style.color = isError ? "#d08080" : "#7aabbd";
}
function now() {
var d = new Date();
return d.getHours() + ":" + pad(d.getMinutes()) + ":" + pad(d.getSeconds());
}
function pad(n) { return n < 10 ? "0" + n : String(n); }
function escapeQ(s) {
return s.replace(/\\/g, "\\\\").replace(/'/g, "\\'").replace(/"/g, "&quot;");
}
window.onload = function() { init(); };
</script>
</body>
</html>