Add Feature Software Inventory (#7597)

* Feature Software Added new Feature Software with Uninstall function * Fix else if Fix else if * Feature Software Show Uninstall Button to Admins only! * Software Feature Change rights for software commands to DeviceDetails. * Add Software Feature Add possibility to view / uninstall Windows Apps and Store Apps. You need ‘Details’ rights to view the software. You need admin rights in MeshCentral to uninstall the application. * Change InstalledApps from PS to RegistryQuery

esgheorghe committed May 16, 2026 at 18:43 UTC 51d62c1e5b1c1a25c42d02e73c10fc576f166f84
6 files changed +1503 -81
agents/meshcore.js
+459 -7
@@ -1283,6 +1283,191 @@ function handleServerCommand(data) {
1283 break;
1284 case 'msg': {
1285 switch (data.type) {
1286 + // FÜGE DIESEN NEUEN BLOCK HIER EIN
1287 + case 'software': {
1288 + // Hilfsfunktion: Antwortet als 'software'-Typ
1289 + var sendSoftwareResponse = function(responseData) {
1290 + mesh.SendCommand({
1291 + action: 'msg',
1292 + type: 'software',
1293 + value: (typeof responseData === 'string') ? responseData : JSON.stringify(responseData),
1294 + sessionid: data.sessionid
1295 + });
1296 + };
1297 +
1298 + if (data.value == 'installedapps') {
1299 + try {
1300 + if (require('win-info').installedApps) {
1301 + require('win-info').installedApps().then(sendSoftwareResponse).catch(function(e) { sendSoftwareResponse({ error: e.toString() }); });
1302 + } else { sendSoftwareResponse({ error: "Not supported" }); }
1303 + } catch (e) { sendSoftwareResponse({ error: e.toString() }); }
1304 + }
1305 + else if (data.value == 'installedstoreapps') {
1306 + try {
1307 + if (require('win-info').installedStoreApps) {
1308 + require('win-info').installedStoreApps().then(sendSoftwareResponse).catch(function(e) { sendSoftwareResponse({ error: e.toString() }); });
1309 + }
1310 + } catch (e) { sendSoftwareResponse({ error: e.toString() }); }
1311 + }
1312 + // --- UNINSTALL APP (Win32) ---
1313 + else if (typeof data.value === 'string' && data.value.indexOf('uninstallapp ') === 0) {
1314 + var base64Cmd = data.value.substring(13).trim(); // "uninstallapp " abschneiden
1315 + var uninstallCmd = '';
1316 +
1317 + try {
1318 + var b = Buffer.from(base64Cmd, 'base64');
1319 + var decoded = b.toString();
1320 + if (decoded && decoded.length > 0 && decoded.indexOf(':') >= 0) { uninstallCmd = decoded; } else { uninstallCmd = base64Cmd; }
1321 + } catch (e) { uninstallCmd = base64Cmd; }
1322 +
1323 + if (!uninstallCmd || uninstallCmd.trim() === '' || uninstallCmd.trim() === '\\') {
1324 + sendSoftwareResponse({ success: false, error: 'No valid uninstall command available' });
1325 + } else {
1326 + var logFile = 'C:\\ProgramData\\MeshAgent_Uninstall.log';
1327 + var child_process = require('child_process');
1328 + var cmdPath = process.env['windir'] + '\\system32\\cmd.exe';
1329 +
1330 + var writeLog = function(message, callback) {
1331 + try {
1332 + var timestamp = new Date().toISOString();
1333 + var logLine = timestamp + ' | ' + message;
1334 + logLine = logLine.replace(/"/g, '""').replace(/&/g, '^&').replace(/</g, '^<').replace(/>/g, '^>').replace(/\|/g, '^|');
1335 + var logChild = child_process.execFile(cmdPath, ['cmd', '/c', 'echo ' + logLine + ' >> "' + logFile + '"'], { timeout: 5000 });
1336 + logChild.on('exit', function() { if (callback) callback(); });
1337 + } catch (e) { if (callback) callback(); }
1338 + };
1339 +
1340 + try {
1341 + writeLog('UNINSTALL START - Command: ' + uninstallCmd.replace(/\\/g, '/'));
1342 + var originalCmd = uninstallCmd;
1343 +
1344 + if (uninstallCmd.toLowerCase().indexOf('msiexec') >= 0) {
1345 + uninstallCmd = uninstallCmd.replace(/\/q[nbrf]?/gi, '');
1346 + if (uninstallCmd.indexOf('/QN') < 0) { uninstallCmd = uninstallCmd + ' /QN /norestart'; }
1347 + } else {
1348 + if (uninstallCmd.toLowerCase().indexOf('/s') < 0) { uninstallCmd = uninstallCmd + ' /S /silent /SILENT /VERYSILENT /quiet /norestart'; }
1349 + }
1350 + uninstallCmd = uninstallCmd.replace(/\s+/g, ' ').trim();
1351 +
1352 + var child = child_process.execFile(cmdPath, ['cmd', '/c', uninstallCmd], { timeout: 300000 });
1353 + child.stdout.str = ''; child.stderr.str = '';
1354 + child._cmd = uninstallCmd;
1355 +
1356 + child.stdout.on('data', function(c) { this.str += c.toString(); });
1357 + child.stderr.on('data', function(c) { this.str += c.toString(); });
1358 +
1359 + child.on('exit', function(code) {
1360 + var success = (code === 0 || code === null || code === 3010);
1361 + var status = success ? 'SUCCESS' : 'FAILED';
1362 + writeLog('UNINSTALL ' + status + ' - ExitCode: ' + code);
1363 +
1364 + var result = { success: success, exitCode: code, command: child._cmd };
1365 + if (child.stdout.str) result.stdout = child.stdout.str.trim().substring(0, 500);
1366 + if (child.stderr.str) result.stderr = child.stderr.str.trim().substring(0, 500);
1367 + sendSoftwareResponse(result);
1368 + });
1369 + sendSoftwareResponse({ status: 'Silent uninstall started', command: uninstallCmd });
1370 + } catch (ex) {
1371 + writeLog('UNINSTALL ERROR - ' + ex.toString());
1372 + sendSoftwareResponse({ error: ex.toString() });
1373 + }
1374 + }
1375 + }
1376 + // --- UNINSTALL STORE APP (Verbesserte Version) ---
1377 + else if (typeof data.value === 'string' && data.value.indexOf('uninstallstoreapp ') === 0) {
1378 + // Namen extrahieren und Anführungszeichen entfernen, falls vorhanden
1379 + var rawName = data.value.substring(18).trim();
1380 + var packageName = rawName.replace(/"/g, "").replace(/'/g, "");
1381 +
1382 + var logFile = 'C:\\ProgramData\\MeshAgent_StoreUninstall.log';
1383 +
1384 + try {
1385 + var psPath = (process.env['SystemRoot'] || 'C:\\Windows') + '\\System32\\WindowsPowerShell\\v1.0\\powershell.exe';
1386 + var child_process = require('child_process');
1387 +
1388 + // Marker, um das Ende der Ausgabe zu erkennen
1389 + var completionMarker = '###DONE###' + Date.now();
1390 +
1391 + var child = child_process.execFile(psPath, ['powershell', '-NoProfile', '-NoLogo', '-ExecutionPolicy', 'Bypass', '-Command', '-'], {});
1392 + child.stdout.str = ''; child.stderr.str = '';
1393 + child._completed = false;
1394 +
1395 + child.stdout.on('data', function(c) {
1396 + this.str += c.toString();
1397 + // Wir warten auf den Marker
1398 + if (!child._completed && this.str.indexOf(completionMarker) >= 0) {
1399 + child._completed = true;
1400 + // Ergebnis extrahieren (alles vor dem Marker)
1401 + var output = this.str.split(completionMarker)[0].trim();
1402 + sendSoftwareResponse({ status: 'Finished', output: output });
1403 + }
1404 + });
1405 + child.stderr.on('data', function(c) { this.str += c.toString(); });
1406 +
1407 + // Das ausführliche PowerShell Skript (wie früher)
1408 + var script = [
1409 + "$LogFile = '" + logFile + "'",
1410 + "$TargetName = '" + packageName + "'",
1411 + "function Write-Log { param([string]$Msg); $Line = \"$(Get-Date -Format 'yyyy-MM-dd HH:mm:ss') - $Msg\"; Add-Content -Path $LogFile -Value $Line -ErrorAction SilentlyContinue; Write-Output $Msg }",
1412 + "",
1413 + "Write-Log \"STORE UNINSTALL START: $TargetName\"",
1414 + "Write-Log \"Running as: $([System.Security.Principal.WindowsIdentity]::GetCurrent().Name)\"",
1415 + "",
1416 + "$foundCount = 0",
1417 + "$pkgs = Get-AppxPackage -AllUsers -Name \"*$TargetName*\"",
1418 + "if ($pkgs) {",
1419 + " if ($pkgs -isnot [array]) { $pkgs = @($pkgs) }",
1420 + " Write-Log \"Found (AllUsers): $($pkgs.Count) packages\"",
1421 + " foreach ($p in $pkgs) {",
1422 + " Write-Log \"Removing: $($p.PackageFullName)\"",
1423 + " try {",
1424 + " Remove-AppxPackage -Package $p.PackageFullName -AllUsers -ErrorAction Stop",
1425 + " Write-Log \"SUCCESS: Removed with -AllUsers\"",
1426 + " $foundCount++",
1427 + " } catch {",
1428 + " Write-Log \"WARN: AllUsers failed, trying current user only. Error: $($_.Exception.Message)\"",
1429 + " try {",
1430 + " Remove-AppxPackage -Package $p.PackageFullName -ErrorAction Stop",
1431 + " Write-Log \"SUCCESS: Removed from CurrentUser\"",
1432 + " $foundCount++",
1433 + " } catch {",
1434 + " Write-Log \"ERROR: Failed to remove $($p.PackageFullName). Error: $($_.Exception.Message)\"",
1435 + " }",
1436 + " }",
1437 + " }",
1438 + "} else { Write-Log \"Found (AllUsers): 0 packages\" }",
1439 + "",
1440 + "$prov = Get-AppxProvisionedPackage -Online | Where-Object { $_.PackageName -like \"*$TargetName*\" }",
1441 + "if ($prov) {",
1442 + " if ($prov -isnot [array]) { $prov = @($prov) }",
1443 + " Write-Log \"Found provisioned: $($prov.Count) packages\"",
1444 + " foreach ($pr in $prov) {",
1445 + " Write-Log \"Deprovisioning: $($pr.DisplayName)\"",
1446 + " try {",
1447 + " Remove-AppxProvisionedPackage -Online -PackageName $pr.PackageName -ErrorAction Stop | Out-Null",
1448 + " Write-Log \"SUCCESS: Deprovisioned\"",
1449 + " } catch {",
1450 + " Write-Log \"ERROR: Failed to deprovision. Error: $($_.Exception.Message)\"",
1451 + " }",
1452 + " }",
1453 + "} else { Write-Log \"Found provisioned: 0 packages\" }",
1454 + "",
1455 + "'" + completionMarker + "'",
1456 + "exit"
1457 + ].join("\r\n");
1458 +
1459 + child.stdin.write(script + "\r\n");
1460 +
1461 + // Timeout Safety
1462 + setTimeout(function() { if (!child._completed) { child.kill(); sendSoftwareResponse({ error: 'Timeout' }); } }, 60000);
1463 +
1464 + sendSoftwareResponse({ status: 'Store app removal started', package: packageName });
1465 + } catch (ex) {
1466 + sendSoftwareResponse({ error: ex.toString() });
1467 + }
1468 + }
1469 + break;
1470 + }
1471 case 'console': { // Process a console command
1472 if ((typeof data.rights != 'number') || ((data.rights & 8) == 0) || ((data.rights & 16) == 0)) break; // Check console rights (Remote Control and Console)
1473 if (data.value && data.sessionid) {
@@ -5803,20 +5988,287 @@ function processConsoleCommand(cmd, args, rights, sessionid) {
5988 break;
5989 }
5990 case 'installedapps': {
5806 - if(process.platform == 'win32'){
5807 - require('win-info').installedApps().then(function (apps){ sendConsoleText(JSON.stringify(apps,null,1)); });
5991 + if (process.platform == 'win32') {
5992 + try {
5993 + // Wir nutzen 'sessionid' aus dem Funktionskopf von processConsoleCommand
5994 + require('win-info').installedApps().then(function (apps) {
5995 + sendConsoleText(JSON.stringify(apps, null, 1), sessionid);
5996 + }).catch(function(e) {
5997 + sendConsoleText("Error: " + e, sessionid);
5998 + });
5999 + } catch (ex) {
6000 + sendConsoleText("Module win-info error: " + ex, sessionid);
6001 + }
6002 }
6003 break;
6004 }
6005 case 'installedstoreapps': {
5812 - if(process.platform == 'win32'){
5813 - var apps = require('win-info').installedStoreApps();
5814 - if (apps[0]) {
5815 - sendConsoleText(JSON.stringify(apps,null,1));
5816 - };
6006 + if (process.platform == 'win32') {
6007 + try {
6008 + // Hier muss der Name exakt so geschrieben sein wie in module.exports der win-info.js
6009 + require('win-info').installedStoreApps().then(function (apps) {
6010 + if (apps && apps.length > 0) {
6011 + sendConsoleText(JSON.stringify(apps, null, 1), sessionid);
6012 + } else {
6013 + sendConsoleText("No Store Apps found or PowerShell error.", sessionid);
6014 + }
6015 + }).catch(function(e) {
6016 + sendConsoleText("Promise Error: " + e, sessionid);
6017 + });
6018 + } catch (ex) {
6019 + sendConsoleText("Module win-info error: " + ex, sessionid);
6020 + }
6021 }
6022 break;
6023 }
6024 + case 'uninstallapp':
6025 + if (args['_'].length < 1) {
6026 + response = "Usage: uninstallapp <base64-encoded-command>";
6027 + } else {
6028 + var base64Cmd = args['_'][0];
6029 + var uninstallCmd = '';
6030 +
6031 + // Base64 dekodieren
6032 + try {
6033 + var b = Buffer.from(base64Cmd, 'base64');
6034 + var decoded = b.toString();
6035 +
6036 + if (decoded && decoded.length > 0 && decoded.indexOf(':') >= 0) {
6037 + uninstallCmd = decoded;
6038 + } else {
6039 + uninstallCmd = base64Cmd;
6040 + }
6041 + } catch (e) {
6042 + uninstallCmd = base64Cmd;
6043 + }
6044 +
6045 + // Prüfen ob Befehl leer oder ungültig ist
6046 + if (!uninstallCmd || uninstallCmd.trim() === '' || uninstallCmd.trim() === '\\') {
6047 + response = JSON.stringify({ success: false, error: 'No valid uninstall command available' });
6048 + break;
6049 + }
6050 +
6051 + var logFile = 'C:\\ProgramData\\MeshAgent_Uninstall.log';
6052 + var child_process = require('child_process');
6053 + var cmdPath = process.env['windir'] + '\\system32\\cmd.exe';
6054 +
6055 + // Log-Funktion mit CMD echo
6056 + function writeLog(message, callback) {
6057 + try {
6058 + var timestamp = new Date().toISOString();
6059 + var logLine = timestamp + ' | ' + message;
6060 + // Sonderzeichen escapen
6061 + logLine = logLine.replace(/"/g, '""').replace(/&/g, '^&').replace(/</g, '^<').replace(/>/g, '^>').replace(/\|/g, '^|');
6062 +
6063 + var logChild = child_process.execFile(cmdPath, ['cmd', '/c', 'echo ' + logLine + ' >> "' + logFile + '"'], { timeout: 5000 });
6064 + logChild.on('exit', function() {
6065 + if (callback) callback();
6066 + });
6067 + } catch (e) {
6068 + if (callback) callback();
6069 + }
6070 + }
6071 +
6072 + try {
6073 + // Logging - Start
6074 + writeLog('UNINSTALL START - Command: ' + uninstallCmd.replace(/\\/g, '/'));
6075 +
6076 + var originalCmd = uninstallCmd;
6077 +
6078 + // Silent-Parameter hinzufügen
6079 + if (uninstallCmd.toLowerCase().indexOf('msiexec') >= 0) {
6080 + uninstallCmd = uninstallCmd.replace(/\/q[nbrf]?/gi, '');
6081 + if (uninstallCmd.indexOf('/QN') < 0) {
6082 + uninstallCmd = uninstallCmd + ' /QN /norestart';
6083 + }
6084 + } else {
6085 + if (uninstallCmd.toLowerCase().indexOf('/s') < 0) {
6086 + uninstallCmd = uninstallCmd + ' /S /silent /SILENT /VERYSILENT /quiet /norestart';
6087 + }
6088 + }
6089 +
6090 + uninstallCmd = uninstallCmd.replace(/\s+/g, ' ').trim();
6091 +
6092 + writeLog('UNINSTALL EXEC - Modified: ' + uninstallCmd.replace(/\\/g, '/'));
6093 +
6094 + var child = child_process.execFile(cmdPath, ['cmd', '/c', uninstallCmd], { timeout: 300000 });
6095 +
6096 + child.stdout.str = '';
6097 + child.stderr.str = '';
6098 + child._sessionid = sessionid;
6099 + child._cmd = uninstallCmd;
6100 + child._originalCmd = originalCmd;
6101 + child._writeLog = writeLog;
6102 +
6103 + child.stdout.on('data', function(c) { this.str += c.toString(); });
6104 + child.stderr.on('data', function(c) { this.str += c.toString(); });
6105 +
6106 + child.on('exit', function(code) {
6107 + var success = (code === 0 || code === null || code === 3010);
6108 + var status = success ? 'SUCCESS' : 'FAILED';
6109 +
6110 + child._writeLog('UNINSTALL ' + status + ' - ExitCode: ' + code);
6111 +
6112 + var result = {
6113 + success: success,
6114 + exitCode: code,
6115 + command: child._cmd
6116 + };
6117 +
6118 + if (child.stdout.str) result.stdout = child.stdout.str.trim().substring(0, 500);
6119 + if (child.stderr.str) result.stderr = child.stderr.str.trim().substring(0, 500);
6120 +
6121 + sendConsoleText(JSON.stringify(result), child._sessionid);
6122 + });
6123 +
6124 + response = JSON.stringify({ status: 'Silent uninstall started', command: uninstallCmd });
6125 + } catch (ex) {
6126 + writeLog('UNINSTALL ERROR - ' + ex.toString());
6127 + response = JSON.stringify({ error: ex.toString() });
6128 + }
6129 + }
6130 + break;
6131 +
6132 + case 'uninstallstoreapp':
6133 + if (args['_'].length < 1) {
6134 + response = "Usage: uninstallstoreapp <PackageName>";
6135 + } else {
6136 + var packageName = args['_'][0];
6137 + var logFile = 'C:\\ProgramData\\MeshAgent_StoreUninstall.log';
6138 +
6139 + try {
6140 + var psPath = (process.env['SystemRoot'] || 'C:\\Windows') + '\\System32\\WindowsPowerShell\\v1.0\\powershell.exe';
6141 + var child_process = require('child_process');
6142 + var safePackageName = packageName.replace(/'/g, "''");
6143 + var completionMarker = '###DONE###' + Date.now();
6144 +
6145 + var child = child_process.execFile(psPath, ['powershell', '-NoProfile', '-NoLogo', '-Command', '-'], {});
6146 +
6147 + child.stdout.str = '';
6148 + child.stderr.str = '';
6149 + child._sessionid = sessionid;
6150 + child._packageName = packageName;
6151 + child._marker = completionMarker;
6152 + child._completed = false;
6153 + child._logFile = logFile;
6154 +
6155 + child.stdout.on('data', function(c) {
6156 + this.str += c.toString();
6157 +
6158 + if (!child._completed && this.str.indexOf(child._marker) >= 0) {
6159 + child._completed = true;
6160 + var result = this.str.split(child._marker)[0].trim();
6161 +
6162 + if (result === 'NO_MATCH' || result === '') {
6163 + sendConsoleText(JSON.stringify({
6164 + success: false,
6165 + error: 'No matching packages found',
6166 + package: child._packageName
6167 + }), child._sessionid);
6168 + } else if (result.indexOf('REMOVED') >= 0 || result.indexOf('DEPROVISIONED') >= 0) {
6169 + var items = result.split('|').filter(function(x) { return x.length > 0; });
6170 + sendConsoleText(JSON.stringify({
6171 + success: true,
6172 + message: 'Store app removed',
6173 + results: items
6174 + }), child._sessionid);
6175 + } else if (result.indexOf('FAILED') >= 0) {
6176 + sendConsoleText(JSON.stringify({
6177 + success: false,
6178 + error: 'Removal failed',
6179 + details: result.split('|')
6180 + }), child._sessionid);
6181 + } else {
6182 + sendConsoleText(JSON.stringify({ output: result }), child._sessionid);
6183 + }
6184 + }
6185 + });
6186 +
6187 + child.stderr.on('data', function(c) { this.str += c.toString(); });
6188 +
6189 + // PowerShell-Skript mit Logging
6190 + var script = [
6191 + "$LogFile = '" + logFile + "'",
6192 + "function Write-Log { param([string]$Message); $LogLine = \"$(Get-Date -Format 'yyyy-MM-dd HH:mm:ss') - $Message\"; Add-Content -Path $LogFile -Value $LogLine -ErrorAction SilentlyContinue }",
6193 + "",
6194 + "Write-Log 'STORE UNINSTALL START: " + safePackageName + "'",
6195 + "Write-Log \"Running as: $([System.Security.Principal.WindowsIdentity]::GetCurrent().Name)\"",
6196 + "",
6197 + "$ErrorActionPreference = 'SilentlyContinue'",
6198 + "$results = @()",
6199 + "",
6200 + "# Packages suchen",
6201 + "$pkgs = Get-AppxPackage -AllUsers -Name '*" + safePackageName + "*'",
6202 + "Write-Log \"Found (AllUsers): $($pkgs.Count) packages\"",
6203 + "",
6204 + "if (-not $pkgs) {",
6205 + " $pkgs = Get-AppxPackage -Name '*" + safePackageName + "*'",
6206 + " Write-Log \"Found (CurrentUser): $($pkgs.Count) packages\"",
6207 + "}",
6208 + "",
6209 + "foreach ($p in $pkgs) {",
6210 + " Write-Log \"Removing: $($p.PackageFullName)\"",
6211 + " try {",
6212 + " Remove-AppxPackage -Package $p.PackageFullName -AllUsers -ErrorAction Stop",
6213 + " Write-Log 'SUCCESS: Removed with -AllUsers'",
6214 + " $results += 'REMOVED_ALLUSERS:' + $p.PackageFullName",
6215 + " } catch {",
6216 + " Write-Log \"FAILED -AllUsers: $($_.Exception.Message)\"",
6217 + " try {",
6218 + " Remove-AppxPackage -Package $p.PackageFullName -ErrorAction Stop",
6219 + " Write-Log 'SUCCESS: Removed'",
6220 + " $results += 'REMOVED:' + $p.PackageFullName",
6221 + " } catch {",
6222 + " Write-Log \"FAILED: $($_.Exception.Message)\"",
6223 + " $results += 'FAILED:' + $p.PackageFullName + ':' + $_.Exception.Message",
6224 + " }",
6225 + " }",
6226 + "}",
6227 + "",
6228 + "# Provisioned Packages entfernen",
6229 + "$prov = Get-AppxProvisionedPackage -Online 2>$null | Where-Object { $_.PackageName -like '*" + safePackageName + "*' }",
6230 + "Write-Log \"Found provisioned: $($prov.Count) packages\"",
6231 + "",
6232 + "foreach ($pr in $prov) {",
6233 + " Write-Log \"Deprovisioning: $($pr.DisplayName)\"",
6234 + " try {",
6235 + " Remove-AppxProvisionedPackage -Online -PackageName $pr.PackageName -ErrorAction Stop | Out-Null",
6236 + " Write-Log 'SUCCESS: Deprovisioned'",
6237 + " $results += 'DEPROVISIONED:' + $pr.DisplayName",
6238 + " } catch {",
6239 + " Write-Log \"FAILED: $($_.Exception.Message)\"",
6240 + " $results += 'DEPROV_FAILED:' + $pr.DisplayName",
6241 + " }",
6242 + "}",
6243 + "",
6244 + "if ($results.Count -eq 0) {",
6245 + " Write-Log 'NO_MATCH'",
6246 + " 'NO_MATCH'",
6247 + "} else {",
6248 + " $output = $results -join '|'",
6249 + " Write-Log \"RESULT: $output\"",
6250 + " $output",
6251 + "}",
6252 + "'" + completionMarker + "'",
6253 + "exit"
6254 + ].join("\r\n");
6255 +
6256 + child.stdin.write(script + "\r\n");
6257 +
6258 + setTimeout(function() {
6259 + if (!child._completed) {
6260 + sendConsoleText(JSON.stringify({ error: 'Timeout after 2 minutes' }), child._sessionid);
6261 + }
6262 + }, 120000);
6263 +
6264 + response = JSON.stringify({ status: 'Store app removal started', package: packageName });
6265 + } catch (ex) {
6266 + var logErr = new Date().toISOString() + ' - STORE UNINSTALL ERROR: ' + ex.toString() + '\r\n';
6267 + try { require('fs').appendFileSync(logFile, logErr); } catch (e) { }
6268 + response = JSON.stringify({ error: ex.toString() });
6269 + }
6270 + break;
6271 + }
6272 case 'qfe': {
6273 if(process.platform == 'win32'){
6274 var qfe = require('win-info').qfe();
agents/modules_meshcore/win-info.js
+124 -63
@@ -16,6 +16,9 @@ limitations under the License.
16
17 var promise = require('promise');
18
19 +// We use the environment variable directly or the standard Windows path
20 +var psPath = (process.env['SystemRoot'] ? process.env['SystemRoot'] : 'C:\\Windows') + '\\System32\\WindowsPowerShell\\v1.0\\powershell.exe';
21 +
22 function qfe()
23 {
24 try {
@@ -179,78 +182,136 @@ function pendingReboot()
182 return (ret);
183 }
184
182 -function installedApps()
183 -{
184 - var promise = require('promise');
185 - var ret = new promise(function (a, r) { this._resolve = a; this._reject = r; });
185 +function installedApps() {
186 + var registry = require('win-registry');
187 + var HKEY = registry.HKEY;
188 + var results = [];
189
187 - var code = "\
188 - var reg = require('win-registry');\
189 - var result = [];\
190 - var val, tmp;\
191 - var items = reg.QueryKey(reg.HKEY.LocalMachine, 'SOFTWARE\\\\Microsoft\\\\Windows\\\\CurrentVersion\\\\Uninstall');\
192 - for (var key in items.subkeys)\
193 - {\
194 - val = {};\
195 - try\
196 - {\
197 - val.name = reg.QueryKey(reg.HKEY.LocalMachine, 'SOFTWARE\\\\Microsoft\\\\Windows\\\\CurrentVersion\\\\Uninstall\\\\' + items.subkeys[key], 'DisplayName');\
198 - }\
199 - catch(e)\
200 - {\
201 - continue;\
202 - }\
203 - try\
204 - {\
205 - val.version = reg.QueryKey(reg.HKEY.LocalMachine, 'SOFTWARE\\\\Microsoft\\\\Windows\\\\CurrentVersion\\\\Uninstall\\\\' + items.subkeys[key], 'DisplayVersion');\
206 - if (val.version == '') { delete val.version; }\
207 - }\
208 - catch(e)\
209 - {\
210 - }\
211 - try\
212 - {\
213 - val.location = reg.QueryKey(reg.HKEY.LocalMachine, 'SOFTWARE\\\\Microsoft\\\\Windows\\\\CurrentVersion\\\\Uninstall\\\\' + items.subkeys[key], 'InstallLocation');\
214 - if (val.location == '') { delete val.location; }\
215 - }\
216 - catch(e)\
217 - {\
218 - }\
219 - try\
220 - {\
221 - val.installdate = reg.QueryKey(reg.HKEY.LocalMachine, 'SOFTWARE\\\\Microsoft\\\\Windows\\\\CurrentVersion\\\\Uninstall\\\\' + items.subkeys[key], 'InstallDate');\
222 - if (val.installdate == '') { delete val.installdate; }\
223 - }\
224 - catch(e)\
225 - {\
226 - }\
227 - result.push(val);\
228 - }\
229 - console.log(JSON.stringify(result,'', 1));process.exit();";
190 + var registryPaths = [
191 + 'SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall',
192 + 'SOFTWARE\\Wow6432Node\\Microsoft\\Windows\\CurrentVersion\\Uninstall'
193 + ];
194
231 - ret.child = require('child_process').execFile(process.execPath, [process.execPath.split('\\').pop().split('.exe')[0], '-exec "' + code + '"']);
232 - ret.child.promise = ret;
233 - ret.child.stdout.str = ''; ret.child.stdout.on('data', function (c) { this.str += c.toString(); });
234 - ret.child.on('exit', function (c) { this.promise._resolve(JSON.parse(this.stdout.str.trim())); });
235 - return (ret);
195 + try {
196 + for (var i in registryPaths) {
197 + var path = registryPaths[i];
198 +
199 + // Wir nutzen direkt die Registry-Komponente für die Unterschlüssel
200 + var keyInfo = registry.QueryKey(HKEY.LocalMachine, path);
201 +
202 + // Falls QueryKey fehlschlägt oder keine Subkeys hat, überspringen
203 + if (!keyInfo || !keyInfo.subkeys) continue;
204 +
205 + for (var j = 0; j < keyInfo.subkeys.length; j++) {
206 + var subPath = path + '\\' + keyInfo.subkeys[j];
207 +
208 + // Wir nutzen deine im Modul definierte regQuery Funktion!
209 + // Diese ist sicher, da sie try-catch bereits enthält.
210 + var name = regQuery(HKEY.LocalMachine, subPath, 'DisplayName');
211 +
212 + if (name && name != '') {
213 + results.push({
214 + name: name,
215 + version: regQuery(HKEY.LocalMachine, subPath, 'DisplayVersion') || '',
216 + publisher: regQuery(HKEY.LocalMachine, subPath, 'Publisher') || '',
217 + uninstall: regQuery(HKEY.LocalMachine, subPath, 'QuietUninstallString') ||
218 + regQuery(HKEY.LocalMachine, subPath, 'UninstallString') || ''
219 + });
220 + }
221 + }
222 + }
223 + } catch (e) {
224 + // Stille Fehlerbehandlung für maximale Stabilität im Agenten
225 + }
226 +
227 + // Das Promise-Mirror Objekt für MeshCentral Kompatibilität
228 + return {
229 + data: results,
230 + then: function(cb) { if (typeof cb === 'function') cb(this.data); return this; },
231 + catch: function(cb) { return this; },
232 + finally: function(cb) { if (typeof cb === 'function') cb(); return this; },
233 + on: function(ev, cb) { if (ev === 'exit' && typeof cb === 'function') cb(0); return this; }
234 + };
235 }
236
238 -function installedStoreApps(){
237 +function installedStoreApps() {
238 + var ret = new promise(function (a, r) { this._resolve = a; this._reject = r; });
239 +
240 + // Basierend auf deiner funktionierenden Version + Scope-Erkennung
241 + var psCommand = [
242 + "$ErrorActionPreference = 'SilentlyContinue'",
243 + "$allUsersApps = @(Get-AppxPackage -AllUsers)",
244 + "$allUsersPkgNames = @($allUsersApps | Select-Object -ExpandProperty PackageFullName)",
245 + "$userOnlyApps = @(Get-AppxPackage | Where-Object { $allUsersPkgNames -notcontains $_.PackageFullName })",
246 + "$provPkgs = @(Get-AppxProvisionedPackage -Online | Select-Object -ExpandProperty DisplayName)",
247 + "$results = @()",
248 + "foreach ($app in $allUsersApps) {",
249 + " if ($app.Name -and $app.Name -notlike 'Microsoft.Windows.*' -and $app.Name -notlike 'windows.*' -and $app.Name -notlike '*_neutral_*') {",
250 + " $scope = 'System'",
251 + " if ($provPkgs -contains $app.Name) { $scope = 'System+Prov' }",
252 + " $results += [PSCustomObject]@{ Name=$app.Name; Version=[string]$app.Version; PackageFullName=$app.PackageFullName; Publisher=$app.Publisher; Scope=$scope }",
253 + " }",
254 + "}",
255 + "foreach ($app in $userOnlyApps) {",
256 + " if ($app.Name -and $app.Name -notlike 'Microsoft.Windows.*' -and $app.Name -notlike 'windows.*' -and $app.Name -notlike '*_neutral_*') {",
257 + " $scope = 'User'",
258 + " if ($provPkgs -contains $app.Name) { $scope = 'User+Prov' }",
259 + " $results += [PSCustomObject]@{ Name=$app.Name; Version=[string]$app.Version; PackageFullName=$app.PackageFullName; Publisher=$app.Publisher; Scope=$scope }",
260 + " }",
261 + "}",
262 + "$results | Sort-Object Name -Unique | ConvertTo-Json -Compress"
263 + ].join("; ");
264 +
265 try {
240 - var tokens = require('win-wmi-fixed').query('ROOT\\CIMV2', 'SELECT * FROM Win32_InstalledStoreProgram');
241 - if (tokens[0]){
242 - for (var index = 0; index < tokens.length; index++) {
243 - for (var key in tokens[index]) {
244 - if (key.startsWith('__')) delete tokens[index][key];
266 + var psPath = (process.env['SystemRoot'] || 'C:\\Windows') + '\\System32\\WindowsPowerShell\\v1.0\\powershell.exe';
267 +
268 + ret.child = require('child_process').execFile(
269 + psPath,
270 + ['-NoProfile', '-NonInteractive', '-ExecutionPolicy', 'Bypass', '-Command', psCommand],
271 + { timeout: 60000, maxBuffer: 10 * 1024 * 1024 }
272 + );
273 +
274 + ret.child.promise = ret;
275 + ret.child.stdout.str = '';
276 + ret.child.stdout.on('data', function (chunk) { this.str += chunk.toString(); });
277 +
278 + ret.child.on('exit', function (code) {
279 + try {
280 + var output = this.stdout.str.trim();
281 +
282 + if (output === '' || output === 'null' || output === '[]') {
283 + this.promise._resolve([]);
284 + return;
285 }
286 +
287 + var data = JSON.parse(output);
288 + if (!Array.isArray(data)) { data = [data]; }
289 +
290 + var apps = data.map(function(app) {
291 + return {
292 + name: app.Name || '',
293 + version: app.Version || '',
294 + publisher: app.Publisher || '',
295 + packageFullName: app.PackageFullName || '',
296 + scope: app.Scope || '',
297 + uninstall: 'Remove-AppxPackage -Package "' + (app.PackageFullName || '') + '" -AllUsers'
298 + };
299 + });
300 +
301 + this.promise._resolve(apps);
302 + } catch (e) {
303 + this.promise._resolve([]);
304 }
247 - return (tokens);
248 - } else {
249 - return ([]);
250 - };
305 + });
306 +
307 + ret.child.on('error', function (err) {
308 + this.promise._resolve([]);
309 + });
310 } catch (ex) {
252 - return ([]);
311 + ret._resolve([]);
312 }
313 +
314 + return (ret);
315 }
316
317 function defender(){
meshuser.js
+42 -3
@@ -1055,9 +1055,48 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain, use
1055 // If a response is needed, set a callback function
1056 var func = null;
1057 if (command.responseid != null) { func = function (r) { try { ws.send(JSON.stringify({ action: 'msg', result: r ? 'OK' : 'Unable to route', tag: command.tag, responseid: command.responseid })); } catch (ex) { } } }
1058 -
1059 - // Route this command to a target node
1060 - routeCommandToNode(command, requiredRights, requiredNonRights, func, routingOptions);
1058 +
1059 + // BEGIN SOFTWARE
1060 + var isSoftwareCmd = (command.type === 'console') &&
1061 + (command.value === 'installedapps' ||
1062 + command.value === 'installedstoreapps' ||
1063 + (typeof command.value === 'string' &&
1064 + (command.value.indexOf('uninstallapp ') === 0 ||
1065 + command.value.indexOf('uninstallstoreapp ') === 0)));
1066 +
1067 + if (isSoftwareCmd) {
1068 + // Wir prüfen auf MESHRIGHT_DEVICEDETAILS (0x100000)
1069 + parent.GetNodeWithRights(domain, user, command.nodeid, function (node, rights, visible) {
1070 + var mesh = parent.meshes[node.meshid];
1071 + if ((node != null) && (mesh != null) && ((rights & MESHRIGHT_DEVICEDETAILS) != 0)) {
1072 + var agent = parent.wsagents[command.nodeid];
1073 + if (agent != null) {
1074 + // Wir bauen das Command um
1075 + // WICHTIG: type='software' und sessionid muss gesetzt sein
1076 + var newCommand = {
1077 + action: 'msg',
1078 + type: 'software', // Neuer Typ!
1079 + value: command.value,
1080 + sessionid: ws.sessionId, // Essenziell für den Rückweg
1081 + rights: rights,
1082 + username: user.name,
1083 + remoteaddr: req.clientIp
1084 + };
1085 +
1086 + try { agent.send(JSON.stringify(newCommand)); } catch (ex) { }
1087 + if (func) { func(true); }
1088 + } else {
1089 + if (func) { func(false); } // Agent offline
1090 + }
1091 + } else {
1092 + if (func) { func(false); } // Keine Rechte
1093 + }
1094 + });
1095 + } else {
1096 + // Standard Routing für Console etc.
1097 + routeCommandToNode(command, requiredRights, requiredNonRights, func, routingOptions);
1098 + }
1099 + // END SOFTWARE
1100 break;
1101 }
1102 case 'events':
translate/translate.json
+39
@@ -22450,6 +22450,45 @@
22450 "default3.handlebars->container->column_l->p17->p17title->3"
22451 ]
22452 },
22453 + {
22454 + "bs": "Softver",
22455 + "ca": "Programari",
22456 + "cs": "Software",
22457 + "da": "Software",
22458 + "de": "Software",
22459 + "en": "Software",
22460 + "es": "Software",
22461 + "fi": "Ohjelmisto",
22462 + "fr": "Logiciel",
22463 + "hi": "सॉफ्टवेयर",
22464 + "hu": "Szoftver",
22465 + "it": "Software",
22466 + "ja": "ソフトウェア",
22467 + "ko": "소프트웨어",
22468 + "nl": "Software",
22469 + "pl": "Oprogramowanie",
22470 + "pt": "Software",
22471 + "pt-br": "Software",
22472 + "ru": "Программное обеспечение",
22473 + "sv": "Programvara",
22474 + "tr": "Yazılım",
22475 + "uk": "Програмне забезпечення",
22476 + "zh-chs": "软件",
22477 + "zh-cht": "軟體",
22478 + "xloc": [
22479 + "default-mobile.handlebars->53->564",
22480 + "default.handlebars->118->1133",
22481 + "default.handlebars->118->1158",
22482 + "default.handlebars->118->2437",
22483 + "default.handlebars->container->topbar->1->1->MainSubMenuSpan->MainSubMenu->1->0->MainDevInfoSW",
22484 + "default.handlebars->contextMenu->cxdetails",
22485 + "default3.handlebars->118->1127",
22486 + "default3.handlebars->118->1152",
22487 + "default3.handlebars->118->2434",
22488 + "default3.handlebars->container->topbar->1->1->MainSubMenuSpan->MainSubMenu->1->0->MainDevInfoSW",
22489 + "default3.handlebars->contextMenu->cxdetails"
22490 + ]
22491 + },
22492 {
22493 "bs": "Uređaj",
22494 "ca": "Dispositiu",
views/default.handlebars
+419 -3
@@ -237,6 +237,7 @@
237 <td tabindex=0 id=MainDevFiles class="topbar_td style3x" onmouseup=go(13,event) onkeypress="if (event.key == 'Enter') go(13)">Files</td>
238 <td tabindex=0 id=MainDevEvents class="topbar_td style3x" onmouseup=go(16,event) onkeypress="if (event.key == 'Enter') go(16)">Events</td>
239 <td tabindex=0 id=MainDevInfo class="topbar_td style3x" onmouseup=go(17,event) onkeypress="if (event.key == 'Enter') go(17)">Details</td>
240 + <td tabindex=0 id=MainDevApps class="topbar_td style3x" onmouseup=go(18,event) onkeypress="if (event.key == 'Enter') go(18)">Software</td>
241 <td tabindex=0 id=MainDevAmt class="topbar_td style3x" onmouseup=go(14,event) onkeypress="if (event.key == 'Enter') go(14)">Intel&reg;AMT</td>
242 <td tabindex=0 id=MainDevConsole class="topbar_td style3x" onmouseup=go(15,event) onkeypress="if (event.key == 'Enter') go(15)">Console</td>
243 <td tabindex=0 id=MainDevPlugins class="topbar_td style3x" onmouseup=go(19,event) onkeypress="if (event.key == 'Enter') go(19)">Plugins</td>
@@ -1058,6 +1059,33 @@
1059 <div id=p17info2></div>
1060 </div>
1061 </div>
1062 + <div id=p18 style="display:none">
1063 + <div id="p18title">
1064 + <div id="p18BackButton" style="float:left">
1065 + <div class="backButton" tabindex=0 onclick=goBack() title="Back"
1066 + onkeypress="if (event.key == 'Enter') goBack()">
1067 + <div class="backButtonEx"></div>
1068 + </div>
1069 + </div>
1070 + <h1>Software - <span id=p18deviceName></span></h1>
1071 + </div>
1072 + <div id="p18toolbar" style="padding:10px;background-color:#f5f5f5;border-bottom:1px solid #ddd">
1073 + <input type="button" id="p18RefreshBtn" class="btn btn-primary btn-sm" value="Refresh" onclick="loadInstalledApps()" />
1074 + <input type="text" id="p18SearchInput" class="form-control-sm" placeholder="Search software..."
1075 + style="margin-left:10px;width:250px" onkeyup="filterInstalledApps()" />
1076 + <label style="margin-left:15px">
1077 + <input type="checkbox" id="p18ShowStore" onchange="toggleStoreApps()" /> Show Store Apps
1078 + </label>
1079 + <span id="p18Status" style="margin-left:20px;color:gray"></span>
1080 + </div>
1081 + <div id="p18info" style="overflow-y:auto;padding:10px">
1082 + <div id="p18html">
1083 + <div style="text-align:center;padding:50px;color:gray">
1084 + <i>Click "Refresh" to load installed software...</i>
1085 + </div>
1086 + </div>
1087 + </div>
1088 + </div>
1089 <div id=p19 style="display:none">
1090 <div id="p19title">
1091 <div id="p19BackButton" style="float:left"><div class="backButton" tabindex=0 onclick=goBack() title="Back" onkeypress="if (event.key == 'Enter') goBack()"><div class="backButtonEx"></div></div></div>
@@ -1625,6 +1653,13 @@
1653 var p12TermConsoleMsgTimer = null;
1654 var p13FilesConsoleMsgTimer = null;
1655
1656 + //
1657 + // INSTALLED SOFTWARE
1658 + //
1659 + var installedAppsData = null; // Gespeicherte App-Daten
1660 + var installedAppsLoading = false; // Ladestatus
1661 + var showStoreApps = false; // Store Apps anzeigen?
1662 +
1663 /*
1664 // Check browser dark mode preference
1665 var prefersDarkScheme = window.matchMedia('(prefers-color-scheme: dark)').matches;
@@ -1995,6 +2030,7 @@
2030 QV('p15title', !(hide & 8));
2031 QV('p16title', !(hide & 8));
2032 QV('p17title', !(hide & 8));
2033 + QV('p18title', !(hide & 8));
2034 QV('p20title', !(hide & 8));
2035 QV('p21title', !(hide & 8));
2036 QV('p30title', !(hide & 8));
@@ -2066,6 +2102,8 @@
2102 QS('p10info')['max-height'] = 'calc(100vh - ' + (20 + xh + xh2) + 'px)';
2103 QS('p17info')['height'] = 'calc(100vh - ' + (20 + xh + xh2) + 'px)';
2104 QS('p17info')['max-height'] = 'calc(100vh - ' + (20 + xh + xh2) + 'px)';
2105 + QS('p18info')['height'] = 'calc(100vh - ' + (120 + xh + xh2) + 'px)';
2106 + QS('p18info')['max-height'] = 'calc(100vh - ' + (120 + xh + xh2) + 'px)';
2107 QS('p20main')['height'] = 'calc(100vh - ' + (-50 + xh + xh2) + 'px)';
2108 QS('p20main')['max-height'] = 'calc(100vh - ' + (-50 + xh + xh2) + 'px)';
2109 QS('p21main')['height'] = 'calc(100vh - ' + (20 + xh + xh2) + 'px)';
@@ -2095,6 +2133,7 @@
2133 QV('p15BackButton', false);
2134 QV('p16BackButton', false);
2135 QV('p17BackButton', false);
2136 + QV('p18BackButton', false);
2137 }
2138 p1updateInfo();
2139 }
@@ -2747,6 +2786,7 @@
2786 if (node != null) { node.lastconnect = message.lastconnects[lcnodeid] }
2787 }
2788 mainUpdate(4);
2789 + break;
2790 }
2791 case 'msg': {
2792 // Check if this is a message from a node
@@ -2761,7 +2801,22 @@
2801 if (typeof message.memory.percentConsumed == 'number') { memory = message.memory.percentConsumed; }
2802 deviceDetailsStatsData.push([now, cpu, memory]);
2803 deviceDetailsStatsDraw(message);
2764 - } else if (message.type === 'console') { p15consoleReceive(nodes[index], message.value, message.source); } // This is a console message.
2804 + } else if (message.type === 'software') {
2805 + var softwareData = message.value;
2806 +
2807 + if (typeof softwareData === 'string' && softwareData.trim().charAt(0) === '[' && softwareData.indexOf('"name"') >= 0) {
2808 + if (typeof handleInstalledAppsResponse === 'function') {
2809 + handleInstalledAppsResponse(softwareData);
2810 + }
2811 + }
2812 +
2813 + else if (typeof softwareData === 'string' && (softwareData.indexOf('"success"') >= 0 || softwareData.indexOf('"status"') >= 0 || softwareData.indexOf('"error"') >= 0)) {
2814 + if (typeof handleUninstallResponse === 'function') {
2815 + handleUninstallResponse(softwareData);
2816 + }
2817 + }
2818 +
2819 + } else if (message.type === 'console') { p15consoleReceive(nodes[index], message.value, message.source); } // This is a console message.
2820 else if (message.type === 'notify') { // This is a notification message.
2821 var n = getstore('notifications', 0);
2822 if (((n & 8) == 0) && (message.amtMessage != null)) { break; } // Intel AMT desktop & terminal messages should be ignored.
@@ -3118,7 +3173,7 @@
3173 if (clipb != '') { clipb += ' '; }
3174 clipb += p;
3175 } else {
3121 - x += '<td><strike style=color:#BBB>' + p.substring(0, 4) + '&nbsp;' + p.substring(4); + '</strike>';
3176 + x += '<td><strike style=color:#BBB>' + p.substring(0, 4) + '&nbsp;' + p.substring(4) + '</strike>';
3177 }
3178 }
3179 } else {
@@ -8092,6 +8147,8 @@
8147 QV('MainDevTerminal', (((node.agent == null) && (node.intelamt != null)) || ((node.agent) && (node.agent.caps == null)) || ((node.agent) && ((node.agent.caps & 2) != 0)) || (node.intelamt && (node.intelamt.state == 2))) && (meshrights & 8) && terminalAccess);
8148 QV('MainDevFiles', (node.agent != null) && (node.agent.caps != null) && ((node.agent.caps & 4) != 0) && (meshrights & 8) && fileAccess);
8149 QV('MainDevInfo', (node.mtype < 3) && ((meshrights & 1048576) != 0));
8150 + //QV('MainDevApps', (rights & 1) != 0);
8151 + QV('MainDevApps',(node.mtype < 3) && ((meshrights & 1048576) != 0));
8152 QV('MainDevAmt', (node.intelamt != null) && ((node.intelamt.state == 2) || (node.conn & 2)) && (meshrights & 8) && amtAccess);
8153 QV('MainDevConsole', (consoleRights && ((node.agent != null) && (node.agent.caps != null) && ((node.agent.caps & 8) != 0))) && (meshrights & 8));
8154 QV('MainDevPlugins', false);
@@ -12837,6 +12894,357 @@
12894 setDialogMode(2, decodeURIComponent(drive) + ': ' + "BitLocker Information", 1, null, x, '');
12895 }
12896
12897 + // ============================================
12898 + // INSTALLED SOFTWARE (Panel 18)
12899 + // ============================================
12900 + //
12901 +
12902 + var installedAppsData = { desktop: [], store: [] }; // Getrennte Speicherung
12903 + var installedAppsLoading = false;
12904 + var installedAppsLoadingType = 0; // 0=idle, 1=desktop, 2=store
12905 + var showStoreApps = false;
12906 +
12907 + function loadInstalledApps() {
12908 + if (currentNode == null) return;
12909 + if (installedAppsLoading) return;
12910 +
12911 + installedAppsLoading = true;
12912 + installedAppsLoadingType = 1; // Desktop Apps laden
12913 + installedAppsData = { desktop: [], store: [] }; // Reset
12914 +
12915 + QH('p18Status', '<span style="color:blue">Loading desktop apps...</span>');
12916 + QH('p18html', '<div style="text-align:center;padding:50px;color:gray">Loading installed software...</div>');
12917 +
12918 + // Prüfen ob Agent online
12919 + if ((currentNode.conn & 1) == 0) {
12920 + QH('p18html', '<div style="text-align:center;padding:50px;color:#cc0000"><b>Device is offline.</b></div>');
12921 + QH('p18Status', '<span style="color:red">Offline</span>');
12922 + installedAppsLoading = false;
12923 + installedAppsLoadingType = 0;
12924 + return;
12925 + }
12926 +
12927 + // Desktop Apps laden
12928 + meshserver.send({
12929 + action: 'msg',
12930 + type: 'software',
12931 + nodeid: currentNode._id,
12932 + value: 'installedapps'
12933 + });
12934 + }
12935 +
12936 + function handleInstalledAppsResponse(data) {
12937 + try {
12938 + var apps = JSON.parse(data);
12939 + if (!Array.isArray(apps)) { apps = []; }
12940 +
12941 + if (installedAppsLoadingType === 1) {
12942 + // Desktop Apps erhalten
12943 + installedAppsData.desktop = apps;
12944 + installedAppsLoading = false;
12945 + installedAppsLoadingType = 0;
12946 +
12947 + // Wenn Store Apps Checkbox aktiv, diese auch laden
12948 + if (showStoreApps && currentNode != null) {
12949 + loadStoreApps();
12950 + } else {
12951 + displayInstalledApps();
12952 + }
12953 + } else if (installedAppsLoadingType === 2) {
12954 + // Store Apps erhalten
12955 + installedAppsData.store = apps;
12956 + installedAppsLoading = false;
12957 + installedAppsLoadingType = 0;
12958 + displayInstalledApps();
12959 + } else {
12960 + // Unbekannter Typ - als Desktop behandeln
12961 + installedAppsData.desktop = apps;
12962 + installedAppsLoading = false;
12963 + installedAppsLoadingType = 0;
12964 + displayInstalledApps();
12965 + }
12966 + } catch (e) {
12967 + H('p18Status', '<span style="color:red">Error parsing data: ' + e.message + '</span>');
12968 + installedAppsLoading = false;
12969 + installedAppsLoadingType = 0;
12970 + }
12971 + }
12972 +
12973 + function loadStoreApps() {
12974 + if (currentNode == null) return;
12975 + if (installedAppsLoading) return;
12976 +
12977 + installedAppsLoading = true;
12978 + installedAppsLoadingType = 2; // Store Apps laden
12979 +
12980 + QH('p18Status', '<span style="color:blue">Loading store apps...</span>');
12981 +
12982 + meshserver.send({
12983 + action: 'msg',
12984 + type: 'software',
12985 + nodeid: currentNode._id,
12986 + value: 'installedstoreapps'
12987 + });
12988 + }
12989 +
12990 + function toggleStoreApps() {
12991 + showStoreApps = Q('p18ShowStore').checked;
12992 +
12993 + if (showStoreApps && installedAppsData.store.length === 0 && currentNode != null) {
12994 + // Store Apps müssen noch geladen werden
12995 + loadStoreApps();
12996 + } else {
12997 + // Einfach neu anzeigen
12998 + displayInstalledApps();
12999 + }
13000 + }
13001 +
13002 + function displayInstalledApps() {
13003 + var searchTerm = '';
13004 + if (Q('p18SearchInput')) { searchTerm = Q('p18SearchInput').value.toLowerCase(); }
13005 +
13006 + var desktopApps = installedAppsData.desktop || [];
13007 + var storeApps = installedAppsData.store || [];
13008 +
13009 + // Filtern nach Suchbegriff
13010 + if (searchTerm) {
13011 + desktopApps = desktopApps.filter(function(app) {
13012 + return (app.name && app.name.toLowerCase().indexOf(searchTerm) >= 0) ||
13013 + (app.publisher && app.publisher.toLowerCase().indexOf(searchTerm) >= 0);
13014 + });
13015 + storeApps = storeApps.filter(function(app) {
13016 + return (app.name && app.name.toLowerCase().indexOf(searchTerm) >= 0) ||
13017 + (app.publisher && app.publisher.toLowerCase().indexOf(searchTerm) >= 0);
13018 + });
13019 + }
13020 +
13021 + // Sortieren nach Name
13022 + desktopApps.sort(function(a, b) {
13023 + return (a.name || '').toLowerCase().localeCompare((b.name || '').toLowerCase());
13024 + });
13025 + storeApps.sort(function(a, b) {
13026 + return (a.name || '').toLowerCase().localeCompare((b.name || '').toLowerCase());
13027 + });
13028 +
13029 + var x = '';
13030 +
13031 + // ===== DESKTOP APPS TABELLE (immer anzeigen) =====
13032 + x += '<div style="margin-bottom:20px">';
13033 + x += '<h4 style="border-bottom:2px solid #007bff;padding-bottom:5px;margin-top:0">';
13034 + x += '<span style="color:#007bff">&#128187;</span> Desktop Applications (' + desktopApps.length + ')';
13035 + x += '</h4>';
13036 + x += '<table style="width:100%;border-collapse:collapse;font-size:13px">';
13037 + x += '<thead><tr style="background-color:#e9ecef">';
13038 + x += '<th style="text-align:left;padding:8px;border:1px solid #ddd">Name</th>';
13039 + x += '<th style="text-align:left;padding:8px;border:1px solid #ddd;width:120px">Version</th>';
13040 + x += '<th style="text-align:left;padding:8px;border:1px solid #ddd;width:200px">Publisher</th>';
13041 + x += '<th style="text-align:center;padding:8px;border:1px solid #ddd;width:180px">Actions</th>';
13042 + x += '</tr></thead><tbody>';
13043 +
13044 + if (desktopApps.length === 0) {
13045 + x += '<tr><td colspan="4" style="text-align:center;color:gray;padding:20px">';
13046 + x += searchTerm ? 'No applications matching "' + EscapeHtml(searchTerm) + '"' : 'No desktop applications found';
13047 + x += '</td></tr>';
13048 + } else {
13049 + for (var i = 0; i < desktopApps.length; i++) {
13050 + var app = desktopApps[i];
13051 + var uninstallCmd = app.uninstall ? encodeURIComponent(app.uninstall) : '';
13052 + var hasUninstall = (uninstallCmd !== '');
13053 + var rowColor = (i % 2 == 0) ? '#ffffff' : '#f8f9fa';
13054 +
13055 + x += '<tr style="background-color:' + rowColor + '">';
13056 + x += '<td style="padding:6px 8px;border:1px solid #eee" title="' + EscapeHtml(app.name || '') + '">' + EscapeHtml(app.name || '-') + '</td>';
13057 + x += '<td style="padding:6px 8px;border:1px solid #eee">' + EscapeHtml(app.version || '-') + '</td>';
13058 + x += '<td style="padding:6px 8px;border:1px solid #eee">' + EscapeHtml(app.publisher || '-') + '</td>';
13059 + x += '<td style="text-align:center;padding:6px 8px;border:1px solid #eee">';
13060 +
13061 + var userRights = GetNodeRights(currentNode);
13062 +
13063 + if (hasUninstall && (userRights == 0xFFFFFFFF)) {
13064 + x += '<input type="button" value="Uninstall" style="padding:2px 8px;cursor:pointer" onclick="uninstallDesktopApp(\'' + encodeURIComponent(app.uninstall) + '\')" />';
13065 + } else {
13066 + x += '<span style="color:#999">-</span>';
13067 + }
13068 +
13069 +
13070 + x += '</td>';
13071 + x += '</tr>';
13072 + }
13073 + }
13074 +
13075 + x += '</tbody></table>';
13076 + x += '</div>';
13077 +
13078 + // ===== STORE APPS TABELLE (nur wenn Checkbox aktiv) =====
13079 + if (showStoreApps) {
13080 + x += '<div style="margin-bottom:20px">';
13081 + x += '<h4 style="border-bottom:2px solid #28a745;padding-bottom:5px">';
13082 + x += '<span style="color:#28a745">&#127979;</span> Microsoft Store Apps (' + storeApps.length + ')';
13083 + x += '</h4>';
13084 + x += '<table style="width:100%;border-collapse:collapse;font-size:13px">';
13085 + x += '<thead><tr style="background-color:#d4edda">';
13086 + x += '<th style="text-align:left;padding:8px;border:1px solid #c3e6cb">Name</th>';
13087 + x += '<th style="text-align:left;padding:8px;border:1px solid #c3e6cb;width:120px">Version</th>';
13088 + x += '<th style="text-align:left;padding:8px;border:1px solid #c3e6cb;width:250px">Package</th>';
13089 + x += '<th style="text-align:center;padding:8px;border:1px solid #c3e6cb;width:100px">Scope</th>';
13090 + x += '<th style="text-align:center;padding:8px;border:1px solid #c3e6cb;width:80px">Actions</th>';
13091 + x += '</tr></thead><tbody>';
13092 +
13093 + if (storeApps.length === 0) {
13094 + x += '<tr><td colspan="5" style="text-align:center;color:gray;padding:20px">';
13095 + x += searchTerm ? 'No store apps matching "' + EscapeHtml(searchTerm) + '"' : 'No store apps found';
13096 + x += '</td></tr>';
13097 + } else {
13098 + for (var i = 0; i < storeApps.length; i++) {
13099 + var app = storeApps[i];
13100 + var uninstallCmd = app.uninstall ? encodeURIComponent(app.uninstall) : '';
13101 + var hasUninstall = (uninstallCmd !== '');
13102 + var rowColor = (i % 2 == 0) ? '#ffffff' : '#f0fff0';
13103 +
13104 + // Scope-Badge erstellen
13105 + var scopeBadge = '';
13106 + if (app.scope) {
13107 + if (app.scope.indexOf('User') >= 0) {
13108 + scopeBadge = '<span style="background:#ffc107;color:#000;padding:2px 6px;border-radius:3px;font-size:10px" title="Installed for current user only">User</span>';
13109 + } else if (app.scope.indexOf('System') >= 0) {
13110 + scopeBadge = '<span style="background:#17a2b8;color:#fff;padding:2px 6px;border-radius:3px;font-size:10px" title="Installed for all users">System</span>';
13111 + }
13112 + if (app.scope.indexOf('Prov') >= 0) {
13113 + scopeBadge += ' <span style="background:#6c757d;color:#fff;padding:2px 6px;border-radius:3px;font-size:10px" title="Provisioned for new users">Prov</span>';
13114 + }
13115 + } else {
13116 + scopeBadge = '<span style="color:#999">-</span>';
13117 + }
13118 +
13119 + x += '<tr style="background-color:' + rowColor + '">';
13120 + x += '<td style="padding:6px 8px;border:1px solid #c3e6cb">' + EscapeHtml(app.name || '-') + '</td>';
13121 + x += '<td style="padding:6px 8px;border:1px solid #c3e6cb">' + EscapeHtml(app.version || '-') + '</td>';
13122 + x += '<td style="padding:6px 8px;border:1px solid #c3e6cb" title="' + EscapeHtml(app.packageFullName || '') + '">' + EscapeHtml(app.publisher || '-') + '</td>';
13123 + x += '<td style="text-align:center;padding:6px 8px;border:1px solid #c3e6cb">' + scopeBadge + '</td>';
13124 + x += '<td style="text-align:center;padding:6px 8px;border:1px solid #c3e6cb">';
13125 +
13126 + var userRights = GetNodeRights(currentNode);
13127 +
13128 + if (hasUninstall && (userRights == 0xFFFFFFFF)) {
13129 + x += '<input type="button" value="Remove" style="padding:2px 8px;cursor:pointer" onclick="uninstallStoreApp(\'' + encodeURIComponent(app.name) + '\')" />';
13130 + } else {
13131 + x += '<span style="color:#999">-</span>';
13132 + }
13133 +
13134 + x += '</td>';
13135 + x += '</tr>';
13136 + }
13137 + }
13138 +
13139 + x += '</tbody></table>';
13140 + x += '</div>';
13141 + }
13142 +
13143 + QH('p18html', x);
13144 +
13145 + // Status aktualisieren
13146 + var totalDesktop = (installedAppsData.desktop || []).length;
13147 + var totalStore = (installedAppsData.store || []).length;
13148 + var shownDesktop = desktopApps.length;
13149 + var shownStore = showStoreApps ? storeApps.length : 0;
13150 +
13151 + var statusText = 'Desktop: ' + shownDesktop + '/' + totalDesktop;
13152 + if (showStoreApps) {
13153 + statusText += ' | Store: ' + shownStore + '/' + totalStore;
13154 + }
13155 + QH('p18Status', statusText);
13156 + }
13157 +
13158 + function filterInstalledApps() {
13159 + if (filterInstalledApps.timer) clearTimeout(filterInstalledApps.timer);
13160 + filterInstalledApps.timer = setTimeout(function() {
13161 + displayInstalledApps();
13162 + }, 300);
13163 + }
13164 +
13165 + function uninstallDesktopApp(encodedCmd) {
13166 + if (xxdialogMode) return;
13167 + if (currentNode == null) return;
13168 +
13169 + var cmd = decodeURIComponent(encodedCmd);
13170 +
13171 + // Prüfen ob Befehl leer ist
13172 + if (!cmd || cmd.trim() === '') {
13173 + QH('p18Status', '<span style="color:red">Error: No uninstall command available</span>');
13174 + return;
13175 + }
13176 +
13177 + var dialogContent = '<div style="max-width:500px">';
13178 + dialogContent += '<p><b>Uninstall this software?</b></p>';
13179 + dialogContent += '<div style="background-color:#f5f5f5;padding:10px;border-radius:5px;word-break:break-all;font-family:monospace;font-size:11px;max-height:80px;overflow-y:auto">' + EscapeHtml(cmd) + '</div>';
13180 + dialogContent += '<p style="margin-top:10px"><i>Silent uninstall will be performed.</i></p>';
13181 + dialogContent += '</div>';
13182 +
13183 + setDialogMode(2, "Uninstall Software", 3, function() {
13184 + QH('p18Status', '<span style="color:blue">Uninstalling...</span>');
13185 +
13186 + // Base64 kodieren um Sonderzeichen-Probleme zu vermeiden
13187 + var base64Cmd = btoa(unescape(encodeURIComponent(cmd)));
13188 +
13189 + meshserver.send({
13190 + action: 'msg',
13191 + type: 'software',
13192 + nodeid: currentNode._id,
13193 + value: 'uninstallapp ' + base64Cmd
13194 + });
13195 +
13196 + setTimeout(function() {
13197 + QH('p18Status', 'Uninstall started. Click Refresh to update.');
13198 + }, 2000);
13199 + }, dialogContent);
13200 + }
13201 +
13202 +
13203 +
13204 + function uninstallStoreApp(encodedCmd) {
13205 + if (xxdialogMode) return;
13206 + if (currentNode == null) return;
13207 +
13208 + var cmd = decodeURIComponent(encodedCmd);
13209 +
13210 + var dialogContent = '<div style="max-width:500px">';
13211 + dialogContent += '<p><b>Remove this Store app?</b></p>';
13212 + dialogContent += '<div style="background-color:#f5f5f5;padding:10px;border-radius:5px;word-break:break-all;font-family:monospace;font-size:11px">' + EscapeHtml(cmd) + '</div>';
13213 + dialogContent += '</div>';
13214 +
13215 + setDialogMode(2, "Remove Store App", 3, function() {
13216 + QH('p18Status', '<span style="color:blue">Removing...</span>');
13217 +
13218 + var escapedCmd = cmd.replace(/"/g, '\\"');
13219 +
13220 + meshserver.send({
13221 + action: 'msg',
13222 + type: 'software',
13223 + nodeid: currentNode._id,
13224 + value: 'uninstallstoreapp "' + escapedCmd + '"'
13225 + });
13226 +
13227 + setTimeout(function() {
13228 + QH('p18Status', 'Remove command sent. Click Refresh to update.');
13229 + }, 2000);
13230 + }, dialogContent);
13231 + }
13232 +
13233 + function handleUninstallResponse(data) {
13234 + try {
13235 + var result = JSON.parse(data);
13236 + if (result.success) {
13237 + QH('p18Status', '<span style="color:green">✓ Success!</span> Click Refresh to update.');
13238 + } else if (result.error) {
13239 + QH('p18Status', '<span style="color:red">✗ Error: ' + EscapeHtml(result.error) + '</span>');
13240 + }
13241 + } catch(e) {}
13242 + }
13243 +
13244 + // ============================================
13245 + // END INSTALLED SOFTWARE
13246 + // ============================================
13247 +
13248 //
13249 // CONSOLE
13250 //
@@ -19136,7 +19544,7 @@
19544 QV('ServerSubMenuSpan', x == 6 || x == 115 || x == 40 || x == 41 || x == 42 || x == 43);
19545 QV('UsersSubMenuSpan', x == 4 || x == 50 || x == 52);
19546 QV('EventsSubMenuSpan', (x == 3) || (x == 60));
19139 - var panels = { 3: 'EventsLive', 4: 'UsersGeneral', 10: 'MainDev', 11: 'MainDevDesktop', 12: 'MainDevTerminal', 13: 'MainDevFiles', 14: 'MainDevAmt', 15: 'MainDevConsole', 16: 'MainDevEvents', 17: 'MainDevInfo', 19: 'MainDevPlugins', 20: 'MeshGeneral', 21: 'MeshSummary', 30: 'UserGeneral', 31: 'UserEvents', 6: 'ServerGeneral', 40: 'ServerStats', 41: 'ServerTrace', 42: 'ServerPlugins', 50: 'UsersGroups', 52: 'UsersRecordings', 60: 'EventsReport', 115: 'ServerConsole' };
19547 + var panels = { 3: 'EventsLive', 4: 'UsersGeneral', 10: 'MainDev', 11: 'MainDevDesktop', 12: 'MainDevTerminal', 13: 'MainDevFiles', 14: 'MainDevAmt', 15: 'MainDevConsole', 16: 'MainDevEvents', 17: 'MainDevInfo', 18: 'MainDevApps', 19: 'MainDevPlugins', 20: 'MeshGeneral', 21: 'MeshSummary', 30: 'UserGeneral', 31: 'UserEvents', 6: 'ServerGeneral', 40: 'ServerStats', 41: 'ServerTrace', 42: 'ServerPlugins', 50: 'UsersGroups', 52: 'UsersRecordings', 60: 'EventsReport', 115: 'ServerConsole' };
19548 for (var i in panels) {
19549 QC(panels[i]).remove('style3x');
19550 QC(panels[i]).remove('style3sel');
@@ -19182,6 +19590,14 @@
19590
19591 // Some panels must not have scroll bars
19592 QS('column_l')['overflow'] = ([11,12].indexOf(x)>= 0)?'hidden':null;
19593 +
19594 + // Software Panel - Apps laden wenn Panel 18 aktiv wird
19595 + if (x == 18) {
19596 + if (currentNode == null) { go(1); return; }
19597 + QH('p18deviceName', EscapeHtml(currentNode.name));
19598 + // Optional: Apps automatisch laden
19599 + loadInstalledApps();
19600 + }
19601 }
19602
19603 //
views/default3.handlebars
+420 -5
@@ -304,6 +304,8 @@
304 onkeypress="if (event.key == 'Enter') go(16)">Events</td>
305 <td tabindex=0 id=MainDevInfo class="topbar_td style3x" onmouseup=go(17,event)
306 onkeypress="if (event.key == 'Enter') go(17)">Details</td>
307 + <td tabindex=0 id=MainDevApps class="topbar_td style3x" onmouseup=go(18,event)
308 + onkeypress="if (event.key == 'Enter') go(18)">Software</td>
309 <td tabindex=0 id=MainDevAmt class="topbar_td style3x" onmouseup=go(14,event)
310 onkeypress="if (event.key == 'Enter') go(14)">Intel&reg;AMT</td>
311 <td tabindex=0 id=MainDevConsole class="topbar_td style3x" onmouseup=go(15,event)
@@ -1445,6 +1447,33 @@
1447 <div id=p17info2></div>
1448 </div>
1449 </div>
1450 + <div id=p18 style="display:none">
1451 + <div id="p18title">
1452 + <div id="p18BackButton" style="float:left">
1453 + <div class="backButton" tabindex=0 onclick=goBack() title="Back"
1454 + onkeypress="if (event.key == 'Enter') goBack()">
1455 + <div class="backButtonEx"></div>
1456 + </div>
1457 + </div>
1458 + <h1>Software - <span id=p18deviceName></span></h1>
1459 + </div>
1460 + <div id="p18toolbar" style="padding:10px;background-color:#f5f5f5;border-bottom:1px solid #ddd">
1461 + <input type="button" id="p18RefreshBtn" class="btn btn-primary btn-sm" value="Refresh" onclick="loadInstalledApps()" />
1462 + <input type="text" id="p18SearchInput" class="form-control-sm" placeholder="Search software..."
1463 + style="margin-left:10px;width:250px" onkeyup="filterInstalledApps()" />
1464 + <label style="margin-left:15px">
1465 + <input type="checkbox" id="p18ShowStore" onchange="toggleStoreApps()" /> Show Store Apps
1466 + </label>
1467 + <span id="p18Status" style="margin-left:20px;color:gray"></span>
1468 + </div>
1469 + <div id="p18info" style="overflow-y:auto;padding:10px">
1470 + <div id="p18html">
1471 + <div style="text-align:center;padding:50px;color:gray">
1472 + <i>Click "Refresh" to load installed software...</i>
1473 + </div>
1474 + </div>
1475 + </div>
1476 + </div>
1477 <div id=p19 style="display:none">
1478 <div id="p19title" class="d-flex align-items-center">
1479 <div id="p19BackButton" class="pe-2">
@@ -2141,6 +2170,13 @@
2170 var p12TermConsoleMsgTimer = null;
2171 var p13FilesConsoleMsgTimer = null;
2172
2173 + //
2174 + // INSTALLED SOFTWARE
2175 + //
2176 + var installedAppsData = null; // Gespeicherte App-Daten
2177 + var installedAppsLoading = false; // Ladestatus
2178 + var showStoreApps = false; // Store Apps anzeigen?
2179 +
2180 /*
2181 // Check browser dark mode preference
2182 var prefersDarkScheme = window.matchMedia('(prefers-color-scheme: dark)').matches;
@@ -2514,6 +2550,7 @@
2550 QC('p15title').add('d-none');
2551 QC('p16title').add('d-none');
2552 QC('p17title').add('d-none');
2553 + QC('p18title').add('d-none');
2554 QC('p20title').add('d-none');
2555 QC('p21title').add('d-none');
2556 QC('p30title').add('d-none');
@@ -2587,6 +2624,8 @@
2624 QS('p10info')['max-height'] = 'calc(100vh - ' + (20 + xh + xh2) + 'px)';
2625 QS('p17info')['height'] = 'calc(100vh - ' + (20 + xh + xh2) + 'px)';
2626 QS('p17info')['max-height'] = 'calc(100vh - ' + (20 + xh + xh2) + 'px)';
2627 + QS('p18info')['height'] = 'calc(100vh - ' + (120 + xh + xh2) + 'px)';
2628 + QS('p18info')['max-height'] = 'calc(100vh - ' + (120 + xh + xh2) + 'px)';
2629 QS('p20main')['height'] = 'calc(100vh - ' + (-50 + xh + xh2) + 'px)';
2630 QS('p20main')['max-height'] = 'calc(100vh - ' + (-50 + xh + xh2) + 'px)';
2631 QS('p21main')['height'] = 'calc(100vh - ' + (20 + xh + xh2) + 'px)';
@@ -2616,6 +2655,7 @@
2655 QV('p15BackButton', false);
2656 QV('p16BackButton', false);
2657 QV('p17BackButton', false);
2658 + QV('p18BackButton', false);
2659 }
2660 p1updateInfo();
2661 }
@@ -3419,6 +3459,7 @@
3459 if (node != null) { node.lastconnect = message.lastconnects[lcnodeid] }
3460 }
3461 mainUpdate(4);
3462 + break;
3463 }
3464 case 'msg': {
3465 // Check if this is a message from a node
@@ -3433,7 +3474,22 @@
3474 if (typeof message.memory.percentConsumed == 'number') { memory = message.memory.percentConsumed; }
3475 deviceDetailsStatsData.push([now, cpu, memory]);
3476 deviceDetailsStatsDraw(message);
3436 - } else if (message.type === 'console') { p15consoleReceive(nodes[index], message.value, message.source); } // This is a console message.
3477 + } else if (message.type === 'software') {
3478 + var softwareData = message.value;
3479 +
3480 + if (typeof softwareData === 'string' && softwareData.trim().charAt(0) === '[' && softwareData.indexOf('"name"') >= 0) {
3481 + if (typeof handleInstalledAppsResponse === 'function') {
3482 + handleInstalledAppsResponse(softwareData);
3483 + }
3484 + }
3485 +
3486 + else if (typeof softwareData === 'string' && (softwareData.indexOf('"success"') >= 0 || softwareData.indexOf('"status"') >= 0 || softwareData.indexOf('"error"') >= 0)) {
3487 + if (typeof handleUninstallResponse === 'function') {
3488 + handleUninstallResponse(softwareData);
3489 + }
3490 + }
3491 +
3492 + } else if (message.type === 'console') { p15consoleReceive(nodes[index], message.value, message.source); } // This is a console message.
3493 else if (message.type === 'notify') { // This is a notification message.
3494 var n = getstore('notifications', 0);
3495 if (((n & 8) == 0) && (message.amtMessage != null)) { break; } // Intel AMT desktop & terminal messages should be ignored.
@@ -8987,6 +9043,8 @@
9043 QV('MainDevTerminal', (((node.agent == null) && (node.intelamt != null)) || ((node.agent) && (node.agent.caps == null)) || ((node.agent) && ((node.agent.caps & 2) != 0)) || (node.intelamt && (node.intelamt.state == 2))) && (meshrights & 8) && terminalAccess);
9044 QV('MainDevFiles', (node.agent != null) && (node.agent.caps != null) && ((node.agent.caps & 4) != 0) && (meshrights & 8) && fileAccess);
9045 QV('MainDevInfo', (node.mtype < 3) && ((meshrights & 1048576) != 0));
9046 + //QV('MainDevApps', (rights & 1) != 0);
9047 + QV('MainDevApps',(node.mtype < 3) && ((meshrights & 1048576) != 0));
9048 QV('MainDevAmt', (node.intelamt != null) && ((node.intelamt.state == 2) || (node.conn & 2)) && (meshrights & 8) && amtAccess);
9049 QV('MainDevConsole', (consoleRights && ((node.agent != null) && (node.agent.caps != null) && ((node.agent.caps & 8) != 0))) && (meshrights & 8));
9050 QV('MainDevPlugins', false);
@@ -13884,6 +13942,357 @@
13942 showModal('xxAddAgentModal', 'idx_dlgOkButton');
13943 }
13944
13945 + // ============================================
13946 + // INSTALLED SOFTWARE (Panel 18)
13947 + // ============================================
13948 + //
13949 +
13950 + var installedAppsData = { desktop: [], store: [] }; // Getrennte Speicherung
13951 + var installedAppsLoading = false;
13952 + var installedAppsLoadingType = 0; // 0=idle, 1=desktop, 2=store
13953 + var showStoreApps = false;
13954 +
13955 + function loadInstalledApps() {
13956 + if (currentNode == null) return;
13957 + if (installedAppsLoading) return;
13958 +
13959 + installedAppsLoading = true;
13960 + installedAppsLoadingType = 1; // Desktop Apps laden
13961 + installedAppsData = { desktop: [], store: [] }; // Reset
13962 +
13963 + QH('p18Status', '<span style="color:blue">Loading desktop apps...</span>');
13964 + QH('p18html', '<div style="text-align:center;padding:50px;color:gray">Loading installed software...</div>');
13965 +
13966 + // Prüfen ob Agent online
13967 + if ((currentNode.conn & 1) == 0) {
13968 + QH('p18html', '<div style="text-align:center;padding:50px;color:#cc0000"><b>Device is offline.</b></div>');
13969 + QH('p18Status', '<span style="color:red">Offline</span>');
13970 + installedAppsLoading = false;
13971 + installedAppsLoadingType = 0;
13972 + return;
13973 + }
13974 +
13975 + // Desktop Apps laden
13976 + meshserver.send({
13977 + action: 'msg',
13978 + type: 'software',
13979 + nodeid: currentNode._id,
13980 + value: 'installedapps'
13981 + });
13982 + }
13983 +
13984 + function handleInstalledAppsResponse(data) {
13985 + try {
13986 + var apps = JSON.parse(data);
13987 + if (!Array.isArray(apps)) { apps = []; }
13988 +
13989 + if (installedAppsLoadingType === 1) {
13990 + // Desktop Apps erhalten
13991 + installedAppsData.desktop = apps;
13992 + installedAppsLoading = false;
13993 + installedAppsLoadingType = 0;
13994 +
13995 + // Wenn Store Apps Checkbox aktiv, diese auch laden
13996 + if (showStoreApps && currentNode != null) {
13997 + loadStoreApps();
13998 + } else {
13999 + displayInstalledApps();
14000 + }
14001 + } else if (installedAppsLoadingType === 2) {
14002 + // Store Apps erhalten
14003 + installedAppsData.store = apps;
14004 + installedAppsLoading = false;
14005 + installedAppsLoadingType = 0;
14006 + displayInstalledApps();
14007 + } else {
14008 + // Unbekannter Typ - als Desktop behandeln
14009 + installedAppsData.desktop = apps;
14010 + installedAppsLoading = false;
14011 + installedAppsLoadingType = 0;
14012 + displayInstalledApps();
14013 + }
14014 + } catch (e) {
14015 + H('p18Status', '<span style="color:red">Error parsing data: ' + e.message + '</span>');
14016 + installedAppsLoading = false;
14017 + installedAppsLoadingType = 0;
14018 + }
14019 + }
14020 +
14021 + function loadStoreApps() {
14022 + if (currentNode == null) return;
14023 + if (installedAppsLoading) return;
14024 +
14025 + installedAppsLoading = true;
14026 + installedAppsLoadingType = 2; // Store Apps laden
14027 +
14028 + QH('p18Status', '<span style="color:blue">Loading store apps...</span>');
14029 +
14030 + meshserver.send({
14031 + action: 'msg',
14032 + type: 'console',
14033 + nodeid: currentNode._id,
14034 + value: 'installedstoreapps'
14035 + });
14036 + }
14037 +
14038 + function toggleStoreApps() {
14039 + showStoreApps = Q('p18ShowStore').checked;
14040 +
14041 + if (showStoreApps && installedAppsData.store.length === 0 && currentNode != null) {
14042 + // Store Apps müssen noch geladen werden
14043 + loadStoreApps();
14044 + } else {
14045 + // Einfach neu anzeigen
14046 + displayInstalledApps();
14047 + }
14048 + }
14049 +
14050 + function displayInstalledApps() {
14051 + var searchTerm = '';
14052 + if (Q('p18SearchInput')) { searchTerm = Q('p18SearchInput').value.toLowerCase(); }
14053 +
14054 + var desktopApps = installedAppsData.desktop || [];
14055 + var storeApps = installedAppsData.store || [];
14056 +
14057 + // Filtern nach Suchbegriff
14058 + if (searchTerm) {
14059 + desktopApps = desktopApps.filter(function(app) {
14060 + return (app.name && app.name.toLowerCase().indexOf(searchTerm) >= 0) ||
14061 + (app.publisher && app.publisher.toLowerCase().indexOf(searchTerm) >= 0);
14062 + });
14063 + storeApps = storeApps.filter(function(app) {
14064 + return (app.name && app.name.toLowerCase().indexOf(searchTerm) >= 0) ||
14065 + (app.publisher && app.publisher.toLowerCase().indexOf(searchTerm) >= 0);
14066 + });
14067 + }
14068 +
14069 + // Sortieren nach Name
14070 + desktopApps.sort(function(a, b) {
14071 + return (a.name || '').toLowerCase().localeCompare((b.name || '').toLowerCase());
14072 + });
14073 + storeApps.sort(function(a, b) {
14074 + return (a.name || '').toLowerCase().localeCompare((b.name || '').toLowerCase());
14075 + });
14076 +
14077 + var x = '';
14078 +
14079 + // ===== DESKTOP APPS TABELLE (immer anzeigen) =====
14080 + x += '<div style="margin-bottom:20px">';
14081 + x += '<h4 style="border-bottom:2px solid #007bff;padding-bottom:5px;margin-top:0">';
14082 + x += '<span style="color:#007bff">&#128187;</span> Desktop Applications (' + desktopApps.length + ')';
14083 + x += '</h4>';
14084 + x += '<table style="width:100%;border-collapse:collapse;font-size:13px">';
14085 + x += '<thead><tr style="background-color:#e9ecef">';
14086 + x += '<th style="text-align:left;padding:8px;border:1px solid #ddd">Name</th>';
14087 + x += '<th style="text-align:left;padding:8px;border:1px solid #ddd;width:120px">Version</th>';
14088 + x += '<th style="text-align:left;padding:8px;border:1px solid #ddd;width:200px">Publisher</th>';
14089 + x += '<th style="text-align:center;padding:8px;border:1px solid #ddd;width:180px">Actions</th>';
14090 + x += '</tr></thead><tbody>';
14091 +
14092 + if (desktopApps.length === 0) {
14093 + x += '<tr><td colspan="4" style="text-align:center;color:gray;padding:20px">';
14094 + x += searchTerm ? 'No applications matching "' + EscapeHtml(searchTerm) + '"' : 'No desktop applications found';
14095 + x += '</td></tr>';
14096 + } else {
14097 + for (var i = 0; i < desktopApps.length; i++) {
14098 + var app = desktopApps[i];
14099 + var uninstallCmd = app.uninstall ? encodeURIComponent(app.uninstall) : '';
14100 + var hasUninstall = (uninstallCmd !== '');
14101 + var rowColor = (i % 2 == 0) ? '#ffffff' : '#f8f9fa';
14102 +
14103 + x += '<tr style="background-color:' + rowColor + '">';
14104 + x += '<td style="padding:6px 8px;border:1px solid #eee" title="' + EscapeHtml(app.name || '') + '">' + EscapeHtml(app.name || '-') + '</td>';
14105 + x += '<td style="padding:6px 8px;border:1px solid #eee">' + EscapeHtml(app.version || '-') + '</td>';
14106 + x += '<td style="padding:6px 8px;border:1px solid #eee">' + EscapeHtml(app.publisher || '-') + '</td>';
14107 + x += '<td style="text-align:center;padding:6px 8px;border:1px solid #eee">';
14108 +
14109 + var userRights = GetNodeRights(currentNode);
14110 +
14111 + if (hasUninstall && (userRights == 0xFFFFFFFF)) {
14112 + x += '<input type="button" value="Uninstall" style="padding:2px 8px;cursor:pointer" onclick="uninstallDesktopApp(\'' + encodeURIComponent(app.uninstall) + '\')" />';
14113 + } else {
14114 + x += '<span style="color:#999">-</span>';
14115 + }
14116 +
14117 + x += '</td>';
14118 + x += '</tr>';
14119 + }
14120 + }
14121 +
14122 + x += '</tbody></table>';
14123 + x += '</div>';
14124 +
14125 + // ===== STORE APPS TABELLE (nur wenn Checkbox aktiv) =====
14126 + if (showStoreApps) {
14127 + x += '<div style="margin-bottom:20px">';
14128 + x += '<h4 style="border-bottom:2px solid #28a745;padding-bottom:5px">';
14129 + x += '<span style="color:#28a745">&#127979;</span> Microsoft Store Apps (' + storeApps.length + ')';
14130 + x += '</h4>';
14131 + x += '<table style="width:100%;border-collapse:collapse;font-size:13px">';
14132 + x += '<thead><tr style="background-color:#d4edda">';
14133 + x += '<th style="text-align:left;padding:8px;border:1px solid #c3e6cb">Name</th>';
14134 + x += '<th style="text-align:left;padding:8px;border:1px solid #c3e6cb;width:120px">Version</th>';
14135 + x += '<th style="text-align:left;padding:8px;border:1px solid #c3e6cb;width:250px">Package</th>';
14136 + x += '<th style="text-align:center;padding:8px;border:1px solid #c3e6cb;width:100px">Scope</th>';
14137 + x += '<th style="text-align:center;padding:8px;border:1px solid #c3e6cb;width:80px">Actions</th>';
14138 + x += '</tr></thead><tbody>';
14139 +
14140 + if (storeApps.length === 0) {
14141 + x += '<tr><td colspan="5" style="text-align:center;color:gray;padding:20px">';
14142 + x += searchTerm ? 'No store apps matching "' + EscapeHtml(searchTerm) + '"' : 'No store apps found';
14143 + x += '</td></tr>';
14144 + } else {
14145 + for (var i = 0; i < storeApps.length; i++) {
14146 + var app = storeApps[i];
14147 + var uninstallCmd = app.uninstall ? encodeURIComponent(app.uninstall) : '';
14148 + var hasUninstall = (uninstallCmd !== '');
14149 + var rowColor = (i % 2 == 0) ? '#ffffff' : '#f0fff0';
14150 +
14151 + // Scope-Badge erstellen
14152 + var scopeBadge = '';
14153 + if (app.scope) {
14154 + if (app.scope.indexOf('User') >= 0) {
14155 + scopeBadge = '<span style="background:#ffc107;color:#000;padding:2px 6px;border-radius:3px;font-size:10px" title="Installed for current user only">User</span>';
14156 + } else if (app.scope.indexOf('System') >= 0) {
14157 + scopeBadge = '<span style="background:#17a2b8;color:#fff;padding:2px 6px;border-radius:3px;font-size:10px" title="Installed for all users">System</span>';
14158 + }
14159 + if (app.scope.indexOf('Prov') >= 0) {
14160 + scopeBadge += ' <span style="background:#6c757d;color:#fff;padding:2px 6px;border-radius:3px;font-size:10px" title="Provisioned for new users">Prov</span>';
14161 + }
14162 + } else {
14163 + scopeBadge = '<span style="color:#999">-</span>';
14164 + }
14165 +
14166 + x += '<tr style="background-color:' + rowColor + '">';
14167 + x += '<td style="padding:6px 8px;border:1px solid #c3e6cb">' + EscapeHtml(app.name || '-') + '</td>';
14168 + x += '<td style="padding:6px 8px;border:1px solid #c3e6cb">' + EscapeHtml(app.version || '-') + '</td>';
14169 + x += '<td style="padding:6px 8px;border:1px solid #c3e6cb" title="' + EscapeHtml(app.packageFullName || '') + '">' + EscapeHtml(app.publisher || '-') + '</td>';
14170 + x += '<td style="text-align:center;padding:6px 8px;border:1px solid #c3e6cb">' + scopeBadge + '</td>';
14171 + x += '<td style="text-align:center;padding:6px 8px;border:1px solid #c3e6cb">';
14172 +
14173 + var userRights = GetNodeRights(currentNode);
14174 +
14175 + if (hasUninstall && (userRights == 0xFFFFFFFF)) {
14176 + x += '<input type="button" value="Remove" style="padding:2px 8px;cursor:pointer" onclick="uninstallStoreApp(\'' + encodeURIComponent(app.name) + '\')" />';
14177 + } else {
14178 + x += '<span style="color:#999">-</span>';
14179 + }
14180 +
14181 + x += '</td>';
14182 + x += '</tr>';
14183 + }
14184 + }
14185 +
14186 + x += '</tbody></table>';
14187 + x += '</div>';
14188 + }
14189 +
14190 +
14191 + QH('p18html', x);
14192 +
14193 + // Status aktualisieren
14194 + var totalDesktop = (installedAppsData.desktop || []).length;
14195 + var totalStore = (installedAppsData.store || []).length;
14196 + var shownDesktop = desktopApps.length;
14197 + var shownStore = showStoreApps ? storeApps.length : 0;
14198 +
14199 + var statusText = 'Desktop: ' + shownDesktop + '/' + totalDesktop;
14200 + if (showStoreApps) {
14201 + statusText += ' | Store: ' + shownStore + '/' + totalStore;
14202 + }
14203 + QH('p18Status', statusText);
14204 + }
14205 +
14206 + function filterInstalledApps() {
14207 + if (filterInstalledApps.timer) clearTimeout(filterInstalledApps.timer);
14208 + filterInstalledApps.timer = setTimeout(function() {
14209 + displayInstalledApps();
14210 + }, 300);
14211 + }
14212 +
14213 + function uninstallDesktopApp(encodedCmd) {
14214 + if (xxdialogMode) return;
14215 + if (currentNode == null) return;
14216 +
14217 + var cmd = decodeURIComponent(encodedCmd);
14218 +
14219 + // Prüfen ob Befehl leer ist
14220 + if (!cmd || cmd.trim() === '') {
14221 + QH('p18Status', '<span style="color:red">Error: No uninstall command available</span>');
14222 + return;
14223 + }
14224 +
14225 + var dialogContent = '<div style="max-width:500px">';
14226 + dialogContent += '<p><b>Uninstall this software?</b></p>';
14227 + dialogContent += '<div style="background-color:#f5f5f5;padding:10px;border-radius:5px;word-break:break-all;font-family:monospace;font-size:11px;max-height:80px;overflow-y:auto">' + EscapeHtml(cmd) + '</div>';
14228 + dialogContent += '<p style="margin-top:10px"><i>Silent uninstall will be performed.</i></p>';
14229 + dialogContent += '</div>';
14230 +
14231 + setDialogMode(2, "Uninstall Software", 3, function() {
14232 + QH('p18Status', '<span style="color:blue">Uninstalling...</span>');
14233 +
14234 + // Base64 kodieren um Sonderzeichen-Probleme zu vermeiden
14235 + var base64Cmd = btoa(unescape(encodeURIComponent(cmd)));
14236 +
14237 + meshserver.send({
14238 + action: 'msg',
14239 + type: 'software',
14240 + nodeid: currentNode._id,
14241 + value: 'uninstallapp ' + base64Cmd
14242 + });
14243 +
14244 + setTimeout(function() {
14245 + QH('p18Status', 'Uninstall started. Click Refresh to update.');
14246 + }, 2000);
14247 + }, dialogContent);
14248 + }
14249 +
14250 +
14251 + function uninstallStoreApp(encodedCmd) {
14252 + if (xxdialogMode) return;
14253 + if (currentNode == null) return;
14254 +
14255 + var cmd = decodeURIComponent(encodedCmd);
14256 +
14257 + var dialogContent = '<div style="max-width:500px">';
14258 + dialogContent += '<p><b>Remove this Store app?</b></p>';
14259 + dialogContent += '<div style="background-color:#f5f5f5;padding:10px;border-radius:5px;word-break:break-all;font-family:monospace;font-size:11px">' + EscapeHtml(cmd) + '</div>';
14260 + dialogContent += '</div>';
14261 +
14262 + setDialogMode(2, "Remove Store App", 3, function() {
14263 + QH('p18Status', '<span style="color:blue">Removing...</span>');
14264 +
14265 + var escapedCmd = cmd.replace(/"/g, '\\"');
14266 +
14267 + meshserver.send({
14268 + action: 'msg',
14269 + type: 'software',
14270 + nodeid: currentNode._id,
14271 + value: 'uninstallstoreapp "' + escapedCmd + '"'
14272 + });
14273 +
14274 + setTimeout(function() {
14275 + QH('p18Status', 'Remove command sent. Click Refresh to update.');
14276 + }, 2000);
14277 + }, dialogContent);
14278 + }
14279 +
14280 +
14281 + function handleUninstallResponse(data) {
14282 + try {
14283 + var result = JSON.parse(data);
14284 + if (result.success) {
14285 + QH('p18Status', '<span style="color:green">✓ Success!</span> Click Refresh to update.');
14286 + } else if (result.error) {
14287 + QH('p18Status', '<span style="color:red">✗ Error: ' + EscapeHtml(result.error) + '</span>');
14288 + }
14289 + } catch(e) {}
14290 + }
14291 +
14292 + // ============================================
14293 + // END INSTALLED SOFTWARE
14294 + // ============================================
14295 +
14296 //
14297 // CONSOLE
14298 //
@@ -17386,10 +17795,8 @@
17795 // Delete accounts, ask for confirmation
17796 var x = "Confirm delete selected account(s)?" + '<br /><br />';
17797 x += '<label><input id=d3check type=checkbox class="form-check-input me-2" onchange=p3usersGroupActionFunctionDelCheck() />' + "Confirm" + '</label>';
17389 - setModalContent('xxAddAgent', "Delete Accounts", x);
17390 - showModal('xxAddAgentModal', 'idx_dlgOkButton', p3groupActionFunctionDelExec);
17798 + //setDialogMode(2, "Delete Accounts", 3, p3groupActionFunctionDelExec, x);
17799 QE('idx_dlgOkButton', false);
17392 - return false;
17800 } else if (op == 4) {
17801 // Validate emails
17802 for (var i in userids) {
@@ -20846,7 +21253,7 @@
21253 QV('ServerSubMenuSpan', x == 6 || x == 115 || x == 40 || x == 41 || x == 42 || x == 43);
21254 QV('UsersSubMenuSpan', x == 4 || x == 50 || x == 52);
21255 QV('EventsSubMenuSpan', (x == 3) || (x == 60));
20849 - var panels = { 3: 'EventsLive', 4: 'UsersGeneral', 10: 'MainDev', 11: 'MainDevDesktop', 12: 'MainDevTerminal', 13: 'MainDevFiles', 14: 'MainDevAmt', 15: 'MainDevConsole', 16: 'MainDevEvents', 17: 'MainDevInfo', 19: 'MainDevPlugins', 20: 'MeshGeneral', 21: 'MeshSummary', 30: 'UserGeneral', 31: 'UserEvents', 6: 'ServerGeneral', 40: 'ServerStats', 41: 'ServerTrace', 42: 'ServerPlugins', 50: 'UsersGroups', 52: 'UsersRecordings', 60: 'EventsReport', 115: 'ServerConsole' };
21256 + var panels = { 3: 'EventsLive', 4: 'UsersGeneral', 10: 'MainDev', 11: 'MainDevDesktop', 12: 'MainDevTerminal', 13: 'MainDevFiles', 14: 'MainDevAmt', 15: 'MainDevConsole', 16: 'MainDevEvents', 17: 'MainDevInfo', 18: 'MainDevApps', 19: 'MainDevPlugins', 20: 'MeshGeneral', 21: 'MeshSummary', 30: 'UserGeneral', 31: 'UserEvents', 6: 'ServerGeneral', 40: 'ServerStats', 41: 'ServerTrace', 42: 'ServerPlugins', 50: 'UsersGroups', 52: 'UsersRecordings', 60: 'EventsReport', 115: 'ServerConsole' };
21257 for (var i in panels) {
21258 QC(panels[i]).remove('style3x');
21259 QC(panels[i]).remove('style3sel');
@@ -20892,6 +21299,14 @@
21299
21300 // Some panels must not have scroll bars
21301 QS('column_l')['overflow'] = ([11, 12].indexOf(x) >= 0) ? 'hidden' : null;
21302 +
21303 + // Software Panel - Apps laden wenn Panel 18 aktiv wird
21304 + if (x == 18) {
21305 + if (currentNode == null) { go(1); return; }
21306 + QH('p18deviceName', EscapeHtml(currentNode.name));
21307 + // Optional: Apps automatisch laden
21308 + loadInstalledApps();
21309 + }
21310 }
21311
21312 //