Version von installierten Modulen herausfinden

Es ist toll, dass ich im Modulstore einen Hinweis bekomme, wenn es bei installierten Modulen eine neue Version inkl. Changelog gibt. Aber ich frage mich dann jedes Mal, auf welcher Version bin ich den gerade und welche Einträge vom Changelog sind denn nun für mich relevant?

Ich finde aber einfach nicht, wo ich die Version eines installierten Moduls sehen kann? Oder gibt es eine Versionsinformation schlicht weg nicht? Was ich mir aber irgendwie auch nicht vorstellen kann und was wirklich schade wäre. Eigentlich hätte ich es jetzt unter Kern Instanzen/Module erwartet. Hier gibt es auch eine Spalte Version, aber da steht nur was zur Symcon Version.

Grüße André

Hier mal ein Script das ChatGPT in 5 Sekunden ausgespukt hat!

<?php

// Basisverzeichnis des Module Store
$moduleDir = IPS_GetKernelDir() . "modules/.store";

// Prüfen ob Verzeichnis existiert
if (!is_dir($moduleDir)) {
    echo "Module-Verzeichnis nicht gefunden: $moduleDir";
    return;
}

$modules = scandir($moduleDir);

$result = [];

foreach ($modules as $module) {
    // Standardordner überspringen
    if ($module === "." || $module === "..") {
        continue;
    }

    $fullPath = $moduleDir . DIRECTORY_SEPARATOR . $module;

    if (!is_dir($fullPath)) {
        continue;
    }

    // Prüfen ob library.json existiert
    $libraryFile = $fullPath . DIRECTORY_SEPARATOR . "library.json";
    if (!file_exists($libraryFile)) {
        continue;
    }

    // JSON lesen
    $json = json_decode(file_get_contents($libraryFile), true);

    $name = isset($json["name"]) ? $json["name"] : $module;
    $version = isset($json["version"]) ? $json["version"] : "Keine Version gefunden";
    $id = isset($json["id"]) ? $json["id"] : "Unbekannt";

    $result[] = [
        "Name"    => $name,
        "Version" => $version,
        "ID"      => $id,
        "Pfad"    => $fullPath
    ];
}

// Ausgabe formatieren
echo "Installierte Module:\n\n";

foreach ($result as $r) {
    echo "Name:     " . $r["Name"] . "\n";
    echo "Version:  " . $r["Version"] . "\n";
    echo "ID:       " . $r["ID"] . "\n";
    echo "Pfad:     " . $r["Pfad"] . "\n";
    echo "--------------------------------------------\n";
}

Und hier eine leicht optimierte Version von mir :slight_smile:

<?php

// Basisverzeichnis des Module Store
$moduleDir = IPS_GetKernelDir() . "modules/.store";

// Prüfen ob Verzeichnis existiert
if (!is_dir($moduleDir)) {
    echo "Module-Verzeichnis nicht gefunden: $moduleDir";
    return;
}

$modules = scandir($moduleDir);

$result = [];

foreach ($modules as $module) {
    // Standardordner überspringen
    if ($module === "." || $module === "..") {
        continue;
    }

    $fullPath = $moduleDir . DIRECTORY_SEPARATOR . $module;

    if (!is_dir($fullPath)) {
        continue;
    }

    // Prüfen ob library.json existiert
    $libraryFile = $fullPath . DIRECTORY_SEPARATOR . "library.json";
    if (!file_exists($libraryFile)) {
        continue;
    }

    // JSON lesen
    $json = json_decode(file_get_contents($libraryFile), true);

    $name = isset($json["name"]) ? $json["name"] : $module;
    $version = isset($json["version"]) ? $json["version"] : "Keine Version gefunden";
    $id = isset($json["id"]) ? $json["id"] : "Unbekannt";

    $result[] = [
        "Name"    => $name,
        "Version" => $version,
        "Id"      => $id,
        "Path"    => $module
    ];
}

// Ausgabe formatieren
echo "Installierte Module:\n\n";

// Spaltenbreiten automatisch bestimmen
$lenName    = max(array_map(fn($x) => strlen($x["Name"]),    $result));
$lenVersion = max(array_map(fn($x) => strlen($x["Version"]), $result));
$lenId      = max(array_map(fn($x) => strlen($x["Id"]),      $result));
$lenPath    = max(array_map(fn($x) => strlen($x["Path"]),    $result));

// Tabellenkopf
printf(
    "%-{$lenName}s   %-{$lenVersion}s %-{$lenId}s   %-{$lenPath}s\n",
    "Name", "Version", "Library-ID", "Bundle-ID"
);
echo str_repeat("-", $lenName + $lenVersion + $lenId + $lenPath) . "\n";

// Tabellenzeilen
foreach ($result as $r) {
    printf(
        "%-{$lenName}s   %-{$lenVersion}s   %-{$lenId}s   %-{$lenPath}s\n",
        $r["Name"],
        $r["Version"],
        $r["Id"],
        $r['Path'],
    );
}

?>

1 „Gefällt mir“

Klasse Script.
Aber toll wäre wenn das irgendwo im Store stehen würde.
So like: neue Version 1.2, installiert: 1.1.

3 „Gefällt mir“

+1

PS: Unabhängig davon, baue ich jetzt die Versionsnummer in den Konfigurationsdialog meiner Module ein :wink:

Steht da nicht die Installiertee Version?

Leider nein, das wäre schön. Die Version in deinem Screenshot ist scheinbar immer die neueste verfügbare Version. Ich habe hier ein paar Module, für die es ein Update gibt und da steht an der Stelle immer die letzte Version aus dem Changelog.

Das mit dem Skript ist ein schöner Workaround, danke @pitti .

Ich finde auch, dass diese Information im Modulstore für jeden ersichtlich sein sollte. Schließlich wird mir ja im Modulstore ja auch angezeigt, dass eine neuere Version verfügbar ist.

Kannst du bitte mal auch deinen Promt dazu reinstellen? Ich weiß immer nicht wie ich denen so sinnvolle Ausgaben entlocken kann.

Vielen Dank

Peter

Hi Peter,

ich bin da ganz einfach gestrickt und halte nicht viel von „denken in Prompt“, ich schreib einfach was ich haben will :slight_smile:

Erstelle ein Symcon Script, welches das Module Store Directory durchsucht und alle installieren Module mit ihrer Versionsnummer (library.json „version“) auflistet!

habe dann nochmal das hier hinterhergeschickt

Funktioniert, aber die Ausgabe mehr zeilenorientiert, also als Tabelle

Das wars, den rest habe ich dann noch selber geradegezogen, aber grundsätzlich funktionierte es ohne mein eingreifen!

Gruß Heiko

Vielen Dank für die schnelle Antwort!

Manchmal kann das Leben doch so einfach sein… :grinning_face:

Peter

1 „Gefällt mir“

@Dr.Niels gibt es in Symcon wirklich nicht die Möglichkeit, die ohne Skript die installierte Version eines Moduls zu sehen?

Und wäre es viel Aufwand das im Modulstore noch irgendwo kenntlich zu machen?

Das sieht richtig gut aus :+1:

Mir fehlten noch die im ModuleControl installierten Module. Daher habe ich deinen Code mal etwas erweitern lassen :slight_smile:

<?php

// Listet alle installierten Libraries und markiert die Quelle anhand des Store-Verzeichnisses (.store)

$storeGuids = getStoreLibraryGuidSet(); // GUID-Set: "GUID" => true (nur Store)

$rows = [];
foreach (IPS_GetLibraryList() as $guid) {
    $lib = IPS_GetLibrary($guid);
    $id = (string)($lib['GUID'] ?? $guid);

    // Heuristik: GUID in .store gefunden => Store, sonst ModuleControl
    $rows[] = [
        'Name'    => $lib['Name'] ?? '(ohne Name)',
        'Version' => (string)($lib['Version'] ?? '(ohne Version)'),
        'Id'      => $id,
        'Quelle'  => isset($storeGuids[strtolower($id)]) ? 'Store' : 'ModuleControl',
    ];
}

// Sortierung nach Name (wie im UI üblich)
usort($rows, fn($a, $b) => strcmp($a['Name'], $b['Name']));

echo "Installierte Libraries (Store + ModuleControl):\n\n";
printTable(
    ['Name' => 'Name', 'Version' => 'Version', 'Id' => 'Library-GUID', 'Quelle' => 'Quelle'],
    $rows
);

function getStoreLibraryGuidSet(): array
{
    // Alle Store-Bundles liegen unter: <KernelDir>/modules/.store/<bundle>/library.json
    $storeDir = IPS_GetKernelDir() . 'modules/.store';
    if (!is_dir($storeDir)) {
        return [];
    }

    $set = [];
    foreach (glob($storeDir . '/*/library.json') ?: [] as $file) {
        $json = json_decode((string)file_get_contents($file), true);
        $id = $json['id'] ?? null;

        if (is_string($id) && $id !== '') {
            $set[strtolower($id)] = true;
        }
    }
    return $set;
}

function printTable(array $headers, array $rows): void
{
    // Generische Text-Tabelle: Spaltenbreiten aus Header + Inhalt ableiten
    $keys = array_keys($headers);

    $width = array_fill_keys($keys, 0);
    foreach ($headers as $k => $label) {
        $width[$k] = strlen($label);
    }
    foreach ($rows as $row) {
        foreach ($keys as $k) {
            $width[$k] = max($width[$k], strlen((string)($row[$k] ?? '')));
        }
    }

    $fmtParts = [];
    foreach ($keys as $k) {
        $fmtParts[] = "%-{$width[$k]}s";
    }
    $fmt = implode('  ', $fmtParts) . "\n";

    printf($fmt, ...array_values($headers));

    $sep = [];
    foreach ($keys as $k) {
        $sep[] = str_repeat('-', $width[$k]);
    }
    echo implode('  ', $sep) . "\n";

    foreach ($rows as $row) {
        $values = [];
        foreach ($keys as $k) {
            $values[] = (string)($row[$k] ?? '');
        }
        printf($fmt, ...$values);
    }
}

Die Ausgabe sieht dann so aus:

Installierte Libraries (Store + ModuleControl):

Name                            Version  Library-GUID                            Quelle       
------------------------------  -------  --------------------------------------  -------------
Active List                     1.2      {658E494A-2ACE-B8FE-3943-40B6638217F3}  Store        
Alexa                           1.5      {8712139D-34F2-4C61-AB21-DB4DA3DB55A1}  Store        
Almanac                         5.7      {5C868298-0E0E-460C-B2EE-171513972D42}  Store        
Ambientika                      1.0      {E9850CE3-9FE3-15AB-0FAC-C9D0F7A7AE72}  ModuleControl
4 „Gefällt mir“

Cool, es lebe die Community :+1:

2 „Gefällt mir“

Danke, sehr interessant, Gesamt 59 :open_mouth: da muss ich wohl mal aufräumen :grinning_face:

1 „Gefällt mir“

Könnte msn auch irgendwie herausfinden, ob die Module bzw. Teile daraus überhaupt genutzt werden?

Manchmal sind die Namen auch nicht so sprechend bzw. wird durch das Script nur der Bibliotheksname und nicht der Modulname ausgegeben.

grafik

Das habe ich mich (und die KI) auch gefragt. Zusammen haben wir herausgefunden, dass es geht :slight_smile:

<?php

declare(strict_types=1);

// Installierte Libraries + Quelle (Store/ModuleControl) + Instanz-IDs (pro Library)

$storeGuids         = getStoreLibraryGuidSet();              // library-guid(lower) => true
$instancesByLibrary = buildInstanceIdsByLibraryReport();   // library-guid(lower) => [instanceId, ...]

$rows = [];
foreach (IPS_GetLibraryList() as $guid) {
    $lib     = IPS_GetLibrary($guid);
    $libGuid = (string)($lib['GUID'] ?? $guid);
    $libKey  = strtolower($libGuid);

    $rows[] = [
        'Name'      => $lib['Name'] ?? '(ohne Name)',
        'Version'   => (string)($lib['Version'] ?? '(ohne Version)'),
        'Id'        => $libGuid,
        'Quelle'    => isset($storeGuids[$libKey]) ? 'Store' : 'ModuleControl',
        'Instanzen' => formatInstanceSummary($instancesByLibrary[$libKey] ?? [], 3),
    ];
}

usort($rows, static fn($a, $b) => strcmp($a['Name'], $b['Name']));

echo "Installierte Libraries (Store + ModuleControl):\n\n";
printTable(
    [
        'Name'      => 'Name',
        'Version'   => 'Version',
        'Id'        => 'Library-GUID',
        'Quelle'    => 'Quelle',
        'Instanzen' => 'Instanzen (Anz: IDs)',
    ],
    $rows
);

/**
 * Report-fokussiert:
 * LibraryGUID -> [InstanceIDs...]
 *
 * Optional:
 * - $includeBuiltIn: Built-In Library mit aufnehmen
 * - $onlyWithInstances: Libraries ohne Instanzen weglassen
 */
function buildInstanceIdsByLibraryReport(bool $includeBuiltIn = true, bool $onlyWithInstances = false): array
{
    $builtInLibraryGuid = '{0945206A-47AA-4FDD-9093-99051E410E82}'; // Built-In

    $map = [];

    foreach (IPS_GetLibraryList() as $libraryGuid) {
        $libraryGuid = (string)$libraryGuid;

        if (!$includeBuiltIn && strcasecmp($libraryGuid, $builtInLibraryGuid) === 0) {
            continue;
        }

        $instanceIds = [];

        foreach (IPS_GetLibraryModules($libraryGuid) as $moduleGuid) {
            $moduleGuid = (string)$moduleGuid;

            $ids = IPS_GetInstanceListByModuleID($moduleGuid);
            if ($ids === []) {
                continue;
            }

            foreach ($ids as $id) {
                $instanceIds[] = (int)$id;
            }
        }

        $instanceIds = array_values(array_unique($instanceIds));
        sort($instanceIds, SORT_NUMERIC);

        if ($onlyWithInstances && $instanceIds === []) {
            continue;
        }

        // Key normalisieren (für konsistente Keys)
        $map[strtolower($libraryGuid)] = $instanceIds;
    }

    ksort($map);
    return $map;
}

/** Ausgabeformat: "Anzahl: id1, id2, id3, …" */
function formatInstanceSummary(array $ids, int $limit = 3): string
{
    $count = count($ids);
    if ($count === 0) {
        return '0';
    }

    $preview = array_slice($ids, 0, max(0, $limit));
    $suffix  = $count > count($preview) ? ', …' : '';

    return $count . ': ' . implode(', ', array_map('strval', $preview)) . $suffix;
}

/** Store-Erkennung über modules/.store/<bundle>/library.json */
function getStoreLibraryGuidSet(): array
{
    $storeDir = IPS_GetKernelDir() . 'modules/.store';
    if (!is_dir($storeDir)) {
        return [];
    }

    $set = [];
    foreach (glob($storeDir . '/*/library.json') ? : [] as $file) {
        $json = json_decode((string)file_get_contents($file), true);
        $id   = $json['id'] ?? null;

        if (is_string($id) && $id !== '') {
            $set[strtolower($id)] = true;
        }
    }
    return $set;
}

function printTable(array $headers, array $rows): void
{
    $keys = array_keys($headers);

    $strWidth = static function (string $s): int {
        // sichtbare Breite (Terminal/Monospace-Logik), nicht Bytes
        return mb_strwidth($s, 'UTF-8');
    };

    $padDisplay = static function (string $s, int $width): string {
        $w = mb_strwidth($s, 'UTF-8');
        if ($w >= $width) {
            return $s;
        }
        return $s . str_repeat(' ', $width - $w);
    };

    $width = array_fill_keys($keys, 0);
    foreach ($headers as $k => $label) {
        $width[$k] = $strWidth((string)$label);
    }
    foreach ($rows as $row) {
        foreach ($keys as $k) {
            $width[$k] = max($width[$k], $strWidth((string)($row[$k] ?? '')));
        }
    }

    // Header
    $lineParts = [];
    foreach ($keys as $k) {
        $lineParts[] = $padDisplay((string)$headers[$k], $width[$k]);
    }
    echo implode('  ', $lineParts) . "\n";

    // Separator
    $sepParts = [];
    foreach ($keys as $k) {
        $sepParts[] = str_repeat('-', $width[$k]);
    }
    echo implode('  ', $sepParts) . "\n";

    // Rows
    foreach ($rows as $row) {
        $values = [];
        foreach ($keys as $k) {
            $values[] = $padDisplay((string)($row[$k] ?? ''), $width[$k]);
        }
        echo implode('  ', $values) . "\n";
    }
}

Die Ausgabe ist jetzt:

Installierte Libraries (Store + ModuleControl):

Name                            Version  Library-GUID                            Quelle         Instanzen (Anz: IDs)          
------------------------------  -------  --------------------------------------  -------------  ------------------------------
Active List                     1.2      {658E494A-2ACE-B8FE-3943-40B6638217F3}  Store          3: 38739, 53363, 54400        
Alexa                           1.5      {8712139D-34F2-4C61-AB21-DB4DA3DB55A1}  Store          1: 10110                      
Almanac                         5.7      {5C868298-0E0E-460C-B2EE-171513972D42}  Store          1: 18241                      
Ambientika                      1.0      {E9850CE3-9FE3-15AB-0FAC-C9D0F7A7AE72}  ModuleControl  10: 10426, 10547, 12275, …  
Astyc84Misc                     2.0      {C9166CCE-244A-4024-B819-DBD9450A1419}  ModuleControl  0                             

EDIT:
Die Ausrichtung der Spalten war bei Umlauten noch nicht perfekt. Das haben wir noch gerade korrigiert.

3 „Gefällt mir“