<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[Blockly + Javascripte aus einer script.json extrahieren]]></title><description><![CDATA[<p dir="auto">Hallo,<br />
da ich im Moment meine Scripte aufräume und auch ein paar geändert habe, ist mir im Eifer des Gefechts leider ein Fehler passiert und ich habe ein Script (Blockly)was ich noch brauchte überschrieben und gespeichert.<br />
Ist ja kein Problem ich mach ja mit Backitup immer schön Backups, auch von Javascript (dachte ich). Leider mußte ich feststellen das in der Backupdatei nur eine große Datei ist und zwar die script.json. Mit der konnte ich erstmal garnichts anfangen.<br />
Na dann frag ich mal ChatGPT und mit desen Hilfe haben wir folgendes gebaut.</p>
<p dir="auto">Das muss man tun. Ich werde hier die Pfade,Ordner und Dateinamen nennen so wie ich sie nutze. Sollte ihr andere nutzen dann müßt ihr das in den Dateien</p>
<p dir="auto">1-Export-ioBroker.ps1<br />
2-Extract-Blockly.ps1</p>
<p dir="auto">ändern. Steht jeweils ganz oben darin.</p>
<p dir="auto">Auf Laufwerk C habe ich einen Ordner erstellt = iobroker_Script_recover<br />
Dann geht ihr in diesen Ordner und ertellt eine Textdatei und da fügt ihr folgendes ein.</p>
<pre><code>$jsonFile = "C:\iobroker_Script_recover\script.json"
$root = "C:\iobroker_Script_recover\Export"

New-Item -ItemType Directory -Force -Path $root | Out-Null

$report = @()

[int]$countJS = 0
[int]$countBlockly = 0
[int]$countDisabled = 0

$json = Get-Content $jsonFile -Raw | ConvertFrom-Json

foreach ($property in $json.PSObject.Properties) {

    $id = $property.Name
    $obj = $property.Value

    if (-not $obj.common) { continue }
    if (-not $obj.common.source) { continue }

    $enabled = $obj.common.enabled
    $engineType = $obj.common.engineType

    $relative = $id -replace '^script\.js\.common\.', ''
    $parts = $relative.Split('.')

    $scriptName = $parts[-1]

    if ($parts.Count -gt 1) {
        $folder = Join-Path $root ($parts[0..($parts.Count-2)] -join '\')
    }
    else {
        $folder = $root
    }

    if (-not $enabled) {
        $folder = Join-Path $folder "_DEAKTIVIERT"
        $countDisabled++
    }

    New-Item -ItemType Directory -Force -Path $folder | Out-Null

    $source = $obj.common.source

    $xmlMatch = [regex]::Match(
        $source,
        '&lt;xml[\s\S]*?&lt;/xml&gt;'
    )

    if ($xmlMatch.Success) {

        $xmlFile = Join-Path $folder ($scriptName + ".xml")

        $xmlMatch.Value |
            Set-Content $xmlFile -Encoding UTF8

        $source =
            $source.Replace(
                $xmlMatch.Value,
                ""
            ).Trim()

        $countBlockly++
    }
    else {
        $countJS++
    }

    $jsFile = Join-Path $folder ($scriptName + ".js")

    $source |
        Set-Content $jsFile -Encoding UTF8

    $report += [PSCustomObject]@{
        Name       = $scriptName
        Aktiv      = $enabled
        EngineType = $engineType
        Datei      = $jsFile
    }

    Write-Host "Exportiert: $scriptName"
}

$csvFile = Join-Path $root "Scriptliste.csv"

$report |
    Export-Csv `
        -Path $csvFile `
        -NoTypeInformation `
        -Encoding UTF8

$statFile = Join-Path $root "Statistik.txt"

@"
Gesamt exportiert : $($report.Count)
JavaScript        : $countJS
Blockly           : $countBlockly
Deaktiviert       : $countDisabled
"@ | Set-Content $statFile -Encoding UTF8

Write-Host ""
Write-Host "Export abgeschlossen."
Write-Host "Ordner: $root"
</code></pre>
<p dir="auto">Das nennt ihr =   1-Export-ioBroker.ps1<br />
Das ganze dann so speichern unter, in den selben Ordner.<br />
<img src="/assets/uploads/files/1782772415222-einstellungen.jpg" alt="einstellungen.jpg" class=" img-fluid img-markdown" width="924" height="141" /></p>
<p dir="auto">Jetzt die zweite Datei. Wieder ein Textdokument erstellen und dort das einfügen.</p>
<pre><code>Add-Type -AssemblyName System.Web

$root = "C:\iobroker_Script_recover\Export"

$count = 0

Get-ChildItem $root -Recurse -Filter *.js | ForEach-Object {

    $file = $_.FullName
    $content = Get-Content $file -Raw

    if ($content -match '//([A-Za-z0-9+/=%]+)\s*$') {

        try {

            $encoded = $Matches[1]

            $bytes = [Convert]::FromBase64String($encoded)

            $urlText = [Text.Encoding]::UTF8.GetString($bytes)

            $xml = [System.Web.HttpUtility]::UrlDecode($urlText)

            if ($xml.StartsWith("&lt;xml")) {

                $xmlFile =
                    [System.IO.Path]::ChangeExtension(
                        $file,
                        ".xml"
                    )

                $xml |
                    Set-Content $xmlFile -Encoding UTF8

                Write-Host "Blockly:" $xmlFile

                $count++
            }
        }
        catch {
        }
    }
}

Write-Host ""
Write-Host "Blockly-Dateien gefunden:" $count
</code></pre>
<p dir="auto">Das nennt ihr =   2-Extract-Blockly.ps1<br />
Das ganze dann so speichern unter, in den selben Ordner.<br />
<img src="/assets/uploads/files/1782772869959-einstellungen2.jpg" alt="einstellungen2.jpg" class=" img-fluid img-markdown" width="927" height="135" /></p>
<p dir="auto">Zum testen könnt ihr nun mal aus einer Javascript Backupdatei ( sieht ungefair so aus  javascripts_2026_06_29-03_37_23_backupiobroker.tar.gz)  die script.json entpacken.</p>
<p dir="auto">Die script.json kommt jetzt auch in den auf C erstellten Ordner "iobroker_Script_recover"</p>
<p dir="auto">Nun Powershell öffnen und dort die Befehle nacheinander eingeben und mit Enter bestättigen.</p>
<p dir="auto">cd C:\iobroker_Script_recover<br />
.\1-Export-ioBroker.ps1<br />
.\2-Extract-Blockly.ps1</p>
<p dir="auto">Jetzt sollte in dem Ordner ein weiterer Odner "Export" erstellt worden sein.<br />
<img src="/assets/uploads/files/1782773429631-1.jpg" alt="1.jpg" class=" img-fluid img-markdown" width="619" height="121" /></p>
<p dir="auto">In Export<br />
<img src="/assets/uploads/files/1782773711262-2.jpg" alt="2.jpg" class=" img-fluid img-markdown" width="363" height="432" /></p>
<p dir="auto">auch xml für Blocklys<br />
<img src="/assets/uploads/files/1782901272830-xml.jpg" alt="xml.jpg" class=" img-fluid img-markdown" width="196" height="183" /></p>
<p dir="auto">In diesem Ordner sind alle <em><strong>Javascripte + Blocklys</strong></em> in der korrekte Ordnerstruktur vorhanden. Sollten Scripte deaktiviert sein, sind diese in einem extra Ordner Names "DEAKTIVIERT"</p>
<p dir="auto">Bei mir hat das funtioniert und ich konnte mein Blockly einfach wieder exportieren und es läuft.</p>
<p dir="auto">########################################<br />
EDIT: 21.07.2026 - Hier die EXE direkt für Windows<br />
ioBroker_Script_Extractor.exe (gepackte Version) = <a href="/assets/uploads/files/1786298709452-iobroker_script_extractor.zip">ioBroker_Script_Extractor.zip</a><br />
########################################</p>
<p dir="auto">Gruß Michael</p>
]]></description><link>https://forum.iobroker.net/topic/84890/blockly-javascripte-aus-einer-script.json-extrahieren</link><generator>RSS for Node</generator><lastBuildDate>Sat, 10 Oct 2026 04:30:45 GMT</lastBuildDate><atom:link href="https://forum.iobroker.net/topic/84890.rss" rel="self" type="application/rss+xml"/><pubDate>Mon, 29 Jun 2026 23:03:49 GMT</pubDate><ttl>60</ttl><item><title><![CDATA[Reply to Blockly + Javascripte aus einer script.json extrahieren on Mon, 10 Aug 2026 18:06:36 GMT]]></title><description><![CDATA[<p dir="auto">Nice, muss ich mir gleich nach der Arbeit anschauen. <a class="plugin-mentions-user plugin-mentions-a" href="/user/shadowhunter23" aria-label="Profile: shadowhunter23">@<bdi>shadowhunter23</bdi></a>   .....</p>
]]></description><link>https://forum.iobroker.net/post/1351106</link><guid isPermaLink="true">https://forum.iobroker.net/post/1351106</guid><dc:creator><![CDATA[Michael Schmitt]]></dc:creator><pubDate>Mon, 10 Aug 2026 18:06:36 GMT</pubDate></item><item><title><![CDATA[Reply to Blockly + Javascripte aus einer script.json extrahieren on Thu, 20 Aug 2026 09:54:28 GMT]]></title><description><![CDATA[<blockquote>
<p dir="auto"><a class="plugin-mentions-user plugin-mentions-a" href="/user/michael-schmitt" aria-label="Profile: Michael-Schmitt">@<bdi>Michael-Schmitt</bdi></a> <a href="/post/1351036">sagte</a>:</p>
</blockquote>
<blockquote>
<p dir="auto">EDIT: es ist jeder Willkommen die Idee weiter zu spinnen und  zu verbessern.</p>
</blockquote>
<p dir="auto">Ebenso ein Spaß Projekt:<br />
<a href="https://forum.iobroker.net/topic/85201/iobroker-backup-analyzer-ein-backup-offline-auswerten">https://forum.iobroker.net/topic/85201/iobroker-backup-analyzer-ein-backup-offline-auswerten</a></p>
]]></description><link>https://forum.iobroker.net/post/1351103</link><guid isPermaLink="true">https://forum.iobroker.net/post/1351103</guid><dc:creator><![CDATA[Shadowhunter23]]></dc:creator><pubDate>Thu, 20 Aug 2026 09:54:28 GMT</pubDate></item><item><title><![CDATA[Reply to Blockly + Javascripte aus einer script.json extrahieren on Mon, 10 Aug 2026 15:53:25 GMT]]></title><description><![CDATA[<p dir="auto">Sry hab ich falsch geschrieben, du must die Backup Datei nehmen die mit Javascript beginnt</p>
]]></description><link>https://forum.iobroker.net/post/1351093</link><guid isPermaLink="true">https://forum.iobroker.net/post/1351093</guid><dc:creator><![CDATA[Michael Schmitt]]></dc:creator><pubDate>Mon, 10 Aug 2026 15:53:25 GMT</pubDate></item><item><title><![CDATA[Reply to Blockly + Javascripte aus einer script.json extrahieren on Mon, 10 Aug 2026 15:32:05 GMT]]></title><description><![CDATA[<p dir="auto"><a class="plugin-mentions-user plugin-mentions-a" href="/user/michael-schmitt" aria-label="Profile: michael-schmitt">@<bdi>michael-schmitt</bdi></a><br />
habe die exe mal gestartet, funktioniert bei mir.<br />
Gut gemacht <img src="https://forum.iobroker.net/assets/plugins/nodebb-plugin-emoji/emoji/android/1f44d.png?v=e94e0d8a63f" class="not-responsive emoji emoji-android emoji--+1" style="height:23px;width:auto;vertical-align:middle" title=":+1:" alt="👍" /></p>
]]></description><link>https://forum.iobroker.net/post/1351088</link><guid isPermaLink="true">https://forum.iobroker.net/post/1351088</guid><dc:creator><![CDATA[Nashra]]></dc:creator><pubDate>Mon, 10 Aug 2026 15:32:05 GMT</pubDate></item><item><title><![CDATA[Reply to Blockly + Javascripte aus einer script.json extrahieren on Mon, 10 Aug 2026 15:47:59 GMT]]></title><description><![CDATA[<blockquote>
<p dir="auto"><a class="plugin-mentions-user plugin-mentions-a" href="/user/michael-schmitt" aria-label="Profile: Michael-Schmitt">@<bdi>Michael-Schmitt</bdi></a> <a href="/post/1351032">sagte</a>:</p>
<p dir="auto">du hast aber die zb "iobroker_2026_08_09-03_37_10_backupiobroker.tar.gz" auf deinem PC. wenn die Dateien angelegt werden sollte das Programm auch laufen. Ist kein Symbol in der Taskleiste ?</p>
</blockquote>
<p dir="auto">Ja, Backup-File ist auf dem PC im gleichen Directory. Beim Starten des .exe mosert erst Windows (11) wg. unbekannter App; nach dem Start öffnet sich das Fenster zur Dateiauswahl. Nach der Auswahl wird die o.g. Datei 'temp_backup.tar' angelegt und das Programm beendet sich. Die ganze Zeit erscheint kein Programmsymbol in der Taskleiste.</p>
<p dir="auto">Nachtrag: Wenn ich das ps1 anlege und es in PowerShell öffne, kommt die Fehlermeldung</p>
<ul>
<li>CategoryInfo: Sicherheitsfehler: (:) [], PSSecurityException</li>
<li>FullyQualifiedErrorId : UnauthorizedAccess</li>
</ul>
<p dir="auto">Noch ein Nachtrag: In PS musste ich die Ausführungsrichtlinie ändern (auf 'Bypass' oder 'Unrestricted'). Dann läuft das Skript los.<br />
Allerdings findet er in meinem Backup-File keine Datei script.json (die habe ich nach dem Entpacken auch nicht gefunden). Meine Skripte liegen im Backup wohl alle in der Datei "opjects.jsonl" <img src="https://forum.iobroker.net/assets/plugins/nodebb-plugin-emoji/emoji/android/1f615.png?v=e94e0d8a63f" class="not-responsive emoji emoji-android emoji--confused" style="height:23px;width:auto;vertical-align:middle" title=":confused:" alt="😕" /></p>
]]></description><link>https://forum.iobroker.net/post/1351086</link><guid isPermaLink="true">https://forum.iobroker.net/post/1351086</guid><dc:creator><![CDATA[vowill]]></dc:creator><pubDate>Mon, 10 Aug 2026 15:47:59 GMT</pubDate></item><item><title><![CDATA[Reply to Blockly + Javascripte aus einer script.json extrahieren on Sun, 09 Aug 2026 20:48:49 GMT]]></title><description><![CDATA[<p dir="auto">Teilweise Gemini und ChatGPT</p>
<p dir="auto">EDIT: es ist jeder Willkommen die Idee weiter zu spinnen und  zu verbessern. Ich mach das nur aus Spaß an der Freude und aus der Not das ein oder andere mal ein Script zerschossen zu haben. Also Leute mit bezahlter Ki zaubern da sicher was besseres ;)</p>
<p dir="auto">Für  mich reichts</p>
]]></description><link>https://forum.iobroker.net/post/1351036</link><guid isPermaLink="true">https://forum.iobroker.net/post/1351036</guid><dc:creator><![CDATA[Michael Schmitt]]></dc:creator><pubDate>Sun, 09 Aug 2026 20:48:49 GMT</pubDate></item><item><title><![CDATA[Reply to Blockly + Javascripte aus einer script.json extrahieren on Sun, 09 Aug 2026 20:26:21 GMT]]></title><description><![CDATA[<p dir="auto"><a class="plugin-mentions-user plugin-mentions-a" href="/user/michael-schmitt" aria-label="Profile: Michael-Schmitt">@<bdi>Michael-Schmitt</bdi></a><br />
Mit welcher KI baust du das Tool?</p>
]]></description><link>https://forum.iobroker.net/post/1351035</link><guid isPermaLink="true">https://forum.iobroker.net/post/1351035</guid><dc:creator><![CDATA[Shadowhunter23]]></dc:creator><pubDate>Sun, 09 Aug 2026 20:26:21 GMT</pubDate></item><item><title><![CDATA[Reply to Blockly + Javascripte aus einer script.json extrahieren on Mon, 10 Aug 2026 16:24:00 GMT]]></title><description><![CDATA[<p dir="auto">du hast aber die zb "javascript_2026_08_09-03_37_10_backupiobroker.tar.gz" auf deinem PC. wenn die Dateien angelegt werden sollte das Programm auch laufen. Ist kein Symbol in der Taskleiste ?</p>
<p dir="auto"><img src="/assets/uploads/files/1786312882538-1.jpg" alt="1.jpg" class=" img-fluid img-markdown" width="244" height="106" /></p>
]]></description><link>https://forum.iobroker.net/post/1351032</link><guid isPermaLink="true">https://forum.iobroker.net/post/1351032</guid><dc:creator><![CDATA[Michael Schmitt]]></dc:creator><pubDate>Mon, 10 Aug 2026 16:24:00 GMT</pubDate></item><item><title><![CDATA[Reply to Blockly + Javascripte aus einer script.json extrahieren on Sun, 09 Aug 2026 20:01:43 GMT]]></title><description><![CDATA[<p dir="auto"><a class="plugin-mentions-user plugin-mentions-a" href="/user/michael-schmitt" aria-label="Profile: michael-schmitt">@<bdi>michael-schmitt</bdi></a> Danke für deine schnelle Rückmeldung. Vermutlich läuft bei mir das Programm nicht richtig: Es legt nur die schon genannte Datei 'temp_backup.tar' an, ohne dass sich ein Fenster öffnet. Vielleicht braucht das exe noch irgendwelche Zugriffsrechte ...</p>
]]></description><link>https://forum.iobroker.net/post/1351031</link><guid isPermaLink="true">https://forum.iobroker.net/post/1351031</guid><dc:creator><![CDATA[vowill]]></dc:creator><pubDate>Sun, 09 Aug 2026 20:01:43 GMT</pubDate></item><item><title><![CDATA[Reply to Blockly + Javascripte aus einer script.json extrahieren on Sun, 09 Aug 2026 18:41:21 GMT]]></title><description><![CDATA[<p dir="auto"><a class="plugin-mentions-user plugin-mentions-a" href="/user/vowill" aria-label="Profile: vowill">@<bdi>vowill</bdi></a> werden dir den in der GUI die Scripte angezeigt ?<br />
<img src="/assets/uploads/files/1786298994783-444.jpg" alt="444.jpg" class=" img-fluid img-markdown" width="939" height="820" /></p>
<p dir="auto">du kannst unten auf</p>
<ul>
<li>in Zwischenablage  - kopiert den code der unten steht in die Zwischenablage</li>
<li>Alle exportieren - hiermit werden alle gefundenen Scripte exportiert</li>
<li>Ausgewählte exportieren - die Scripte die blau unterlegt sind werden exportiert</li>
</ul>
]]></description><link>https://forum.iobroker.net/post/1351020</link><guid isPermaLink="true">https://forum.iobroker.net/post/1351020</guid><dc:creator><![CDATA[Michael Schmitt]]></dc:creator><pubDate>Sun, 09 Aug 2026 18:41:21 GMT</pubDate></item><item><title><![CDATA[Reply to Blockly + Javascripte aus einer script.json extrahieren on Sun, 09 Aug 2026 18:04:33 GMT]]></title><description><![CDATA[<blockquote>
<p dir="auto"><a class="plugin-mentions-user plugin-mentions-a" href="/user/michael-schmitt" aria-label="Profile: Michael-Schmitt">@<bdi>Michael-Schmitt</bdi></a> <a href="/post/1348678">sagte</a>:</p>
<p dir="auto">Hier die EXE direkt für Windows</p>
</blockquote>
<p dir="auto">Hm. Dieses Program legt bei mir dann einen Ordner "temp_extract" an, in dem das File "temp_backup.tar" abgelegt wird. Aber wo finde ich nun die Skripte?</p>
]]></description><link>https://forum.iobroker.net/post/1351019</link><guid isPermaLink="true">https://forum.iobroker.net/post/1351019</guid><dc:creator><![CDATA[vowill]]></dc:creator><pubDate>Sun, 09 Aug 2026 18:04:33 GMT</pubDate></item><item><title><![CDATA[Reply to Blockly + Javascripte aus einer script.json extrahieren on Tue, 11 Aug 2026 08:20:09 GMT]]></title><description><![CDATA[<p dir="auto">Hier die neueste Version</p>
<p dir="auto">ioBroker_Script_Extractor.ps1</p>
<pre><code>Add-Type -AssemblyName System.Windows.Forms
Add-Type -AssemblyName System.Drawing
Add-Type -AssemblyName System.Web
Add-Type -AssemblyName System.IO.Compression
Add-Type -AssemblyName System.IO.Compression.FileSystem

$so = "C:\iobroker_Script_recover"
$jp = ""
$tmp = Join-Path $so "temp_extract"

try {
    # 1. Dateifenster beim Start öffnen
    $fd = New-Object System.Windows.Forms.OpenFileDialog
    $fd.InitialDirectory = if (Test-Path $so) { $so } else { [Environment]::GetFolderPath("Desktop") }
    $fd.Filter = "ioBroker Dateien (*.json;*.tar.gz)|*.json;*.tar.gz"
    $fd.Title = "Bitte waehle deine script.json ODER eine ioBroker .tar.gz Datei aus"

    if ($fd.ShowDialog() -ne [System.Windows.Forms.DialogResult]::OK) { exit }
    $gewaehlteDatei = $fd.FileName

    # Wenn eine .tar.gz ausgewählt wurde, entpacken im Speicher
    if ($gewaehlteDatei.ToLower().EndsWith(".tar.gz")) {
        if (Test-Path $tmp) { Remove-Item $tmp -Recurse -Force -EA SilentlyContinue }
        New-Item -ItemType Directory -Force -Path $tmp | Out-Null
        
        try {
            $inFile = New-Object System.IO.FileInfo($gewaehlteDatei)
            $gz = New-Object System.IO.Compression.GZipStream($inFile.OpenRead(), [System.IO.Compression.CompressionMode]::Decompress)
            
            $tarPath = Join-Path $tmp "temp_backup.tar"
            $tarOut = [System.IO.File]::Create($tarPath)
            $gz.CopyTo($tarOut)
            $tarOut.Close()
            $gz.Close()
            
            $ts = [System.IO.File]::OpenRead($tarPath)
            $buf = New-Object byte[] 512
            
            while ($ts.Read($buf, 0, 512) -eq 512) {
                $name = [System.Text.Encoding]::ASCII.GetString($buf, 0, 100).Trim(([char]0))
                $szStr = [System.Text.Encoding]::ASCII.GetString($buf, 124, 12).Trim()
                $sz = 0
                if ($szStr) { $sz = [Convert]::ToInt64($szStr, 8) }
                
                if ($name.ToLower().EndsWith("script.json")) {
                    $outStream = [System.IO.File]::Create((Join-Path $tmp "script.json"))
                    $rem = $sz
                    $dBuf = New-Object byte[] 512
                    while ($rem -gt 0) {
                        $read = 512
                        if ($rem -lt 512) { $read = $rem }
                        [void]$ts.Read($dBuf, 0, 512)
                        $outStream.Write($dBuf, 0, $read)
                        $rem -= 512
                    }
                    $outStream.Close()
                    $jp = Join-Path $tmp "script.json"
                    break
                } else { 
                    if ($sz -gt 0) { 
                        $blocks = [Math]::Ceiling($sz / 512)
                        [void]$ts.Seek(($blocks * 512), [System.IO.SeekOrigin]::Current) 
                    } 
                }
            }
            $ts.Close()
        } catch {}
    } else {
        $jp = $gewaehlteDatei
    }

    if (-not $jp -or -not (Test-Path $jp)) { exit }

    $raw = Get-Content $jp -Raw -Encoding UTF8
    if ([string]::IsNullOrWhiteSpace($raw)) { exit }
    $json = ConvertFrom-Json $raw
    $skriptListe = [System.Collections.Generic.List[Object]]::new()

    function Get-SichererBlocklyXml ([string]$sourceCode) {
        if ([string]::IsNullOrWhiteSpace($sourceCode)) { return "" }
        if ($sourceCode -match '//([A-Za-z0-9+/=%]{50,})\s*$') {
            try {
                $xml = [System.Web.HttpUtility]::UrlDecode([Text.Encoding]::UTF8.GetString([Convert]::FromBase64String($Matches[1])))
                if ($xml -match '&lt;xml') { return ($xml -replace '&gt;&lt;', "&gt;`r`n&lt;").Trim() }
            } catch {}
        }
        if ($sourceCode -match '(?s)(&lt;xml.*?&lt;/xml&gt;)') { return ($Matches[1] -replace '&gt;&lt;', "&gt;`r`n&lt;").Trim() }
        return ""
    }

    function Add-SkriptToList ([string]$id, $obj) {
        if (-not $obj) { return }
        if ($obj.type -match "channel|folder") { return }
        $parts = ($id -replace '^script\.js\.(common\.|global\.)?', '').Split('.')
        $p = "Hauptordner"
        if ($parts.Count -gt 1) { $p = $parts[0..($parts.Count-2)] -join '/' }
        $src = ""
        if ($obj.common -and $obj.common.source) { $src = $obj.common.source }
        $en = $true
        if ($obj.common -and $obj.common.enabled -ne $null) { $en = $obj.common.enabled }
        $xml = Get-SichererBlocklyXml -sourceCode $src
        $typText = "JavaScript"
        if ($xml) { $typText = "Blockly" }
        $statText = "Deaktiviert"
        if ($en) { $statText = "Aktiv" }
        $skriptListe.Add([PSCustomObject]@{
            Name      = $parts[-1]
            Pfad      = $p
            Internal  = $id
            JsSource  = $src
            XmlSource = $xml
            Typ       = $typText
            Status    = $statText
        })
    }

    if ($json.PSObject.Properties.Name -contains "objects") {
        foreach ($p in $json.objects.PSObject.Properties) { if ($p.Name -like "script.js.*") { Add-SkriptToList -id $p.Name -obj $p.Value } }
    } else { 
        foreach ($p in $json.PSObject.Properties) { if ($p.Name -like "script.js.*") { Add-SkriptToList -id $p.Name -obj $p.Value } } 
    }
    if ($skriptListe.Count -eq 0) { exit }

    function Update-Grid {
        $filterText = $txtSearch.Text.Trim()
        $ausTyp = $cmbType.SelectedItem.ToString()
        $suchenInCode = $rbSearchDp.Checked
        
        $filtered = $skriptListe | Where-Object { 
            if ([string]::IsNullOrWhiteSpace($filterText)) {
                $true
            } else {
                if ($suchenInCode) {
                    # Durchsucht JavaScript-Quellcode und Blockly-XML nach dem Datenpunkt/Begriff
                    $_.JsSource.ToLower().Contains($filterText.ToLower()) -or 
                    ($_.XmlSource -and $_.XmlSource.ToLower().Contains($filterText.ToLower()))
                } else {
                    # Standard-Suche nach Name oder Pfad
                    $_.Name.ToLower().Contains($filterText.ToLower()) -or 
                    $_.Pfad.ToLower().Contains($filterText.ToLower())
                }
            }
        }
        
        if ($chkHide.Checked) { 
            $filtered = $filtered | Where-Object { $_.Status -eq "Aktiv" } 
        }
        if ($ausTyp -eq "Nur Blockly") { 
            $filtered = $filtered | Where-Object { $_.Typ -eq "Blockly" } 
        }
        elseif ($ausTyp -eq "Nur JavaScript") { 
            $filtered = $filtered | Where-Object { $_.Typ -eq "JavaScript" } 
        }
        
        $dt = New-Object System.Data.DataTable
        [void]$dt.Columns.AddRange(@("Name","ioBroker-Pfad","Typ","Status","InternalID"))
        foreach ($item in $filtered) { [void]$dt.Rows.Add($item.Name, $item.Pfad, $item.Typ, $item.Status, $item.Internal) }
        $dgv.DataSource = $dt
        if ($dgv.Columns["InternalID"]) { $dgv.Columns["InternalID"].Visible = $false }
        
        $modusText = if ($suchenInCode) { "Treffer für Datenpunkt/Code" } else { "Gefundene Skripte" }
        $lblCount.Text = "$modusText (aktueller Filter): " + $filtered.Count
        $lblPrev.Text = "Code-Vorschau (0 Skripte ausgewählt):"
    }

    function Start-ScriptExport ([string]$modus) {
        $ausIDs = @()
        if ($modus -eq "all") { 
            foreach ($row in $dgv.Rows) { 
                if ($row.Cells["InternalID"].Value) { $ausIDs += [string]$row.Cells["InternalID"].Value }
            } 
        } else { 
            if ($dgv.SelectedRows.Count -eq 0) { return }
            foreach ($row in $dgv.SelectedRows) { 
                if ($row.Cells["InternalID"].Value) { $ausIDs += [string]$row.Cells["InternalID"].Value }
            } 
        }
        
        $folderDialog = New-Object System.Windows.Forms.FolderBrowserDialog
        if ($folderDialog.ShowDialog() -ne [System.Windows.Forms.DialogResult]::OK) { return }
        
        $root = Join-Path $folderDialog.SelectedPath ("ioBroker_Backup_" + (Get-Date -Format "yyyy-MM-dd_HH-mm"))
        New-Item -ItemType Directory -Force -Path $root | Out-Null
        
        $utf8NoBom = New-Object System.Text.UTF8Encoding $false

        foreach ($targetID in $ausIDs) {
            $scriptData = $skriptListe | Where-Object { $_.Internal -eq $targetID }
            if (-not $scriptData) { continue }
            $folder = $root
            if ($scriptData.Pfad -ne "Hauptordner") { $folder = Join-Path $root ($scriptData.Pfad -replace '/', '\') }
            if ($scriptData.Status -eq "Deaktiviert") { $folder = Join-Path $folder "_DEAKTIVIERT" }
            New-Item -ItemType Directory -Force -Path $folder | Out-Null
            
            if ($scriptData.Typ -eq "Blockly" -and $scriptData.XmlSource) {
                [System.IO.File]::WriteAllText((Join-Path $folder ($scriptData.Name + ".xml")), $scriptData.XmlSource, $utf8NoBom)
                $cleanJs = $scriptData.JsSource
                if ($cleanJs -match '//([A-Za-z0-9+/=%]{50,})\s*$') { $cleanJs = $cleanJs.Replace($Matches[0], "").Trim() }
                if ($cleanJs -match '(?s)&lt;xml.*?&gt;&lt;/xml&gt;') { $cleanJs = $cleanJs.Replace($Matches[0], "").Trim() }
                [System.IO.File]::WriteAllText((Join-Path $folder ($scriptData.Name + ".js")), $cleanJs, $utf8NoBom)
            } else { 
                [System.IO.File]::WriteAllText((Join-Path $folder ($scriptData.Name + ".js")), $scriptData.JsSource, $utf8NoBom)
            }
        }
        Invoke-Item $root
        [System.Windows.Forms.MessageBox]::Show("Export von $($ausIDs.Count) Skripte(n) erfolgreich abgeschlossen!", "Fertig", [System.Windows.Forms.MessageBoxButtons]::OK, [System.Windows.Forms.MessageBoxIcon]::Information)
    }

    # GUI Definition
    $f = New-Object System.Windows.Forms.Form
    $f.Text = "ioBroker Script Extractor &amp; Search"
    $f.Size = New-Object System.Drawing.Size(960, 830)
    $f.StartPosition = "CenterScreen"

    $pnlTop = New-Object System.Windows.Forms.Panel
    $pnlTop.Dock = "Top"
    $pnlTop.Height = 100
    $f.Controls.Add($pnlTop)

    # Suchmodus Radio-Buttons
    $grpMode = New-Object System.Windows.Forms.GroupBox -Property @{Text="Suchmodus"; Location=New-Object System.Drawing.Point(10,5); Size=New-Object System.Drawing.Size(465,40)}
    $rbSearchName = New-Object System.Windows.Forms.RadioButton -Property @{Text="Skripte (Name/Pfad)"; Location=New-Object System.Drawing.Point(10,15); Size=New-Object System.Drawing.Size(150,20); Checked=$true}
    $rbSearchDp = New-Object System.Windows.Forms.RadioButton -Property @{Text="In Skripten nach Datenpunkt / Code suchen"; Location=New-Object System.Drawing.Point(170,15); Size=New-Object System.Drawing.Size(280,20)}
    $grpMode.Controls.AddRange(@($rbSearchName, $rbSearchDp))

    $lblSrc = New-Object System.Windows.Forms.Label -Property @{Text="Suchen:"; Location=New-Object System.Drawing.Point(10,53); Size=New-Object System.Drawing.Size(50,20)}
    $txtSearch = New-Object System.Windows.Forms.TextBox -Property @{Location=New-Object System.Drawing.Point(65,50); Size=New-Object System.Drawing.Size(410,20)}
    $chkHide = New-Object System.Windows.Forms.CheckBox -Property @{Text="Deaktivierte ausblenden"; Location=New-Object System.Drawing.Point(490,48); Size=New-Object System.Drawing.Size(170,24)}

    $cmbType = New-Object System.Windows.Forms.ComboBox
    $cmbType.Location = New-Object System.Drawing.Point(670, 48)
    $cmbType.Size = New-Object System.Drawing.Size(150, 24)
    $cmbType.DropDownStyle = [System.Windows.Forms.ComboBoxStyle]::DropDownList
    [void]$cmbType.Items.AddRange(@("Alle Typen", "Nur Blockly", "Nur JavaScript"))
    $cmbType.SelectedIndex = 0

    $lblCount = New-Object System.Windows.Forms.Label
    $lblCount.Text = "Gefundene Skripte: 0"
    $lblCount.Location = New-Object System.Drawing.Point(10, 78)
    $lblCount.Size = New-Object System.Drawing.Size(500, 20)
    $lblCount.Font = New-Object System.Drawing.Font("Arial", 9, [System.Drawing.FontStyle]::Bold)

    $pnlTop.Controls.AddRange(@($grpMode, $lblSrc, $txtSearch, $chkHide, $cmbType, $lblCount))

    $pnlBottom = New-Object System.Windows.Forms.Panel
    $pnlBottom.Dock = "Bottom"
    $pnlBottom.Height = 55
    $f.Controls.Add($pnlBottom)

    $btnClip = New-Object System.Windows.Forms.Button -Property @{Text="In Zwischenablage"; Location=New-Object System.Drawing.Point(10,10); Size=New-Object System.Drawing.Size(160,35)}
    $btnAll = New-Object System.Windows.Forms.Button -Property @{Text="Alle exportieren"; Location=New-Object System.Drawing.Point(180,10); Size=New-Object System.Drawing.Size(150,35)}
    $btnOk = New-Object System.Windows.Forms.Button -Property @{Text="Ausgewaehlte exportieren"; Location=New-Object System.Drawing.Point(500,10); Size=New-Object System.Drawing.Size(160,35)}
    $btnCan = New-Object System.Windows.Forms.Button -Property @{Text="Schliessen"; Location=New-Object System.Drawing.Point(670,10); Size=New-Object System.Drawing.Size(150,35)}
    $pnlBottom.Controls.AddRange(@($btnClip, $btnAll, $btnOk, $btnCan))

    $dgv = New-Object System.Windows.Forms.DataGridView -Property @{Location=New-Object System.Drawing.Point(10, 105); Size=New-Object System.Drawing.Size(925, 235); Anchor="Top, Left, Right"; AutoSizeColumnsMode="Fill"; SelectionMode="FullRowSelect"; MultiSelect=$true; AllowUserToAddRows=$false; ReadOnly=$true; RowHeadersVisible=$false}
    $f.Controls.Add($dgv)

    $lblPrev = New-Object System.Windows.Forms.Label
    $lblPrev.Text = "Code-Vorschau:"
    $lblPrev.Location = New-Object System.Drawing.Point(10, 345)
    $lblPrev.Size = New-Object System.Drawing.Size(500, 20)
    $lblPrev.Anchor = "Top, Left"
    $f.Controls.Add($lblPrev)

    $txtPrev = New-Object System.Windows.Forms.RichTextBox
    $txtPrev.Location = New-Object System.Drawing.Point(10, 370)
    $txtPrev.Size = New-Object System.Drawing.Size(925, 310)
    $txtPrev.Anchor = "Top, Bottom, Left, Right"
    $txtPrev.Font = New-Object System.Drawing.Font("Consolas", 10)
    $txtPrev.ReadOnly = $true
    $txtPrev.WordWrap = $false
    $f.Controls.Add($txtPrev)

    $txtSearch.Add_TextChanged({ Update-Grid })
    $chkHide.Add_CheckedChanged({ Update-Grid })
    $cmbType.Add_SelectedIndexChanged({ Update-Grid })
    $rbSearchName.Add_CheckedChanged({ Update-Grid })
    $rbSearchDp.Add_CheckedChanged({ Update-Grid })

    $dgv.Add_SelectionChanged({
        $anz = $dgv.SelectedRows.Count
        if ($anz -gt 0) {
            $selectedRow = $dgv.SelectedRows | Select-Object -First 1
            if ($selectedRow.Cells["InternalID"] -and $selectedRow.Cells["InternalID"].Value) {
                $selID = [string]$selectedRow.Cells["InternalID"].Value
                $m = $skriptListe | Where-Object { $_.Internal -eq $selID }
                if ($m) { 
                    $codeText = if ($m.Typ -eq "Blockly" -and $m.XmlSource) { $m.XmlSource } else { $m.JsSource }
                    
                    # Vorschau zurücksetzen
                    $txtPrev.Text = $codeText
                    $txtPrev.SelectAll()
                    $txtPrev.SelectionBackColor = [System.Drawing.Color]::White
                    $txtPrev.SelectionColor = [System.Drawing.Color]::Black
                    $txtPrev.DeselectAll()
                    
                    # Bei Datenpunktsuche Treffer gelb markieren &amp; anspringen
                    $searchTerm = $txtSearch.Text.Trim()
                    if ($rbSearchDp.Checked -and -not [string]::IsNullOrWhiteSpace($searchTerm)) {
                        $idx = $codeText.IndexOf($searchTerm, [System.StringComparison]::OrdinalIgnoreCase)
                        if ($idx -ge 0) {
                            $txtPrev.Select($idx, $searchTerm.Length)
                            # Korrigierte Eigenschaft SelectionBackColor für WinForms RichTextBox
                            $txtPrev.SelectionBackColor = [System.Drawing.Color]::Yellow
                            $txtPrev.SelectionColor = [System.Drawing.Color]::Black
                            $txtPrev.ScrollToCaret()
                        }
                    }
                }
            }
            $lblPrev.Text = if ($anz -eq 1) { "Code-Vorschau (1 Skript ausgewählt):" } else { "Code-Vorschau ($anz Skripte ausgewählt):" }
        } else { 
            $txtPrev.Text = ""
            $lblPrev.Text = "Code-Vorschau (0 Skripte ausgewählt):" 
        }
    })

    $btnClip.Add_Click({
        if (-not [string]::IsNullOrEmpty($txtPrev.Text)) {
            [System.Windows.Forms.Clipboard]::SetText($txtPrev.Text)
            $global:oldText = $btnClip.Text
            $btnClip.Text = "Kopiert!"
            $btnClip.Enabled = $false
            $global:clipTimer = New-Object System.Windows.Forms.Timer -Property @{ Interval = 1500 }
            $global:clipTimer.Add_Tick({ 
                $btnClip.Text = $global:oldText
                $btnClip.Enabled = $true
                $global:clipTimer.Stop()
                $global:clipTimer.Dispose() 
            })
            $global:clipTimer.Start()
        }
    })

    $btnAll.Add_Click({ Start-ScriptExport -modus "all" })
    $btnOk.Add_Click({ Start-ScriptExport -modus "selected" })
    $btnCan.Add_Click({ $f.Close() })

    Update-Grid
    [void]$f.ShowDialog()

} finally {
    if (Test-Path $tmp) { Remove-Item $tmp -Recurse -Force -EA SilentlyContinue }
}
</code></pre>
<p dir="auto">damit kann man nun auch nach Datenpunkten in den Scripten suchen und diese werden dann aufgelistet.</p>
<p dir="auto"><img src="/assets/uploads/files/1786295639549-333.jpg" alt="333.jpg" class=" img-fluid img-markdown" width="931" height="802" /></p>
<p dir="auto"><a href="/assets/uploads/files/1786436401714-iobroker_script_extractor.zip">ioBroker_Script_Extractor.zip</a></p>
]]></description><link>https://forum.iobroker.net/post/1351008</link><guid isPermaLink="true">https://forum.iobroker.net/post/1351008</guid><dc:creator><![CDATA[Michael Schmitt]]></dc:creator><pubDate>Tue, 11 Aug 2026 08:20:09 GMT</pubDate></item><item><title><![CDATA[Reply to Blockly + Javascripte aus einer script.json extrahieren on Wed, 22 Jul 2026 22:41:45 GMT]]></title><description><![CDATA[<p dir="auto">Hi, gern geschehen. Freu mich wenn es nützlich ist ;)</p>
]]></description><link>https://forum.iobroker.net/post/1348932</link><guid isPermaLink="true">https://forum.iobroker.net/post/1348932</guid><dc:creator><![CDATA[Michael Schmitt]]></dc:creator><pubDate>Wed, 22 Jul 2026 22:41:45 GMT</pubDate></item><item><title><![CDATA[Reply to Blockly + Javascripte aus einer script.json extrahieren on Tue, 21 Jul 2026 22:10:12 GMT]]></title><description><![CDATA[<blockquote>
<p dir="auto"><a class="plugin-mentions-user plugin-mentions-a" href="/user/michael-schmitt" aria-label="Profile: Michael-Schmitt">@<bdi>Michael-Schmitt</bdi></a> <a href="/post/1348781">sagte</a>:</p>
<p dir="auto">Das Tool sollte dafür reichen</p>
</blockquote>
<p dir="auto">Hi Michael,</p>
<p dir="auto">dein Tool für Windows eignet sich hervorragend zum Ansehen und Herauskopieren von Skripts bzw. -teilen aus Backups. Ich hab's ausprobiert. Danke für deine Ideen!</p>
<p dir="auto">Gruß, Fuzzy</p>
]]></description><link>https://forum.iobroker.net/post/1348855</link><guid isPermaLink="true">https://forum.iobroker.net/post/1348855</guid><dc:creator><![CDATA[fuzzy1955]]></dc:creator><pubDate>Tue, 21 Jul 2026 22:10:12 GMT</pubDate></item><item><title><![CDATA[Reply to Blockly + Javascripte aus einer script.json extrahieren on Tue, 21 Jul 2026 21:57:16 GMT]]></title><description><![CDATA[<p dir="auto">Ich habe aktuell einen Adapter für das Thema im Latest.</p>
<p dir="auto">Denke, dass er bald im stable ankommen wird.<br />
<a href="https://forum.iobroker.net/topic/84506/test-adapter-script-restore-v0.1.11">https://forum.iobroker.net/topic/84506/test-adapter-script-restore-v0.1.11</a></p>
<p dir="auto">Aber auch ein Cooler Ansatz für Windows User.</p>
<p dir="auto">EDIT</p>
<p dir="auto">Oh, wurde oben ja schon drauf hingewiesen...</p>
<blockquote>
<p dir="auto"><a class="plugin-mentions-user plugin-mentions-a" href="/user/michael-schmitt" aria-label="Profile: Michael-Schmitt">@<bdi>Michael-Schmitt</bdi></a> <a href="/post/1348781">sagte</a>:</p>
<p dir="auto">Ja gibt es, aber das wird man ja nicht sooo oft brauchen (hoffe ich) und dann möchte ich mir nicht extra einen Adapter installieren. Das Tool sollte dafür reichen. (für meine Zwecke)</p>
</blockquote>
<p dir="auto">Ich hab mir schon oft Scripte zerschossen, weil ich vergessen hab eine Kopie zu erstellen 😂</p>
]]></description><link>https://forum.iobroker.net/post/1348853</link><guid isPermaLink="true">https://forum.iobroker.net/post/1348853</guid><dc:creator><![CDATA[David G.]]></dc:creator><pubDate>Tue, 21 Jul 2026 21:57:16 GMT</pubDate></item><item><title><![CDATA[Reply to Blockly + Javascripte aus einer script.json extrahieren on Tue, 21 Jul 2026 11:22:17 GMT]]></title><description><![CDATA[<blockquote>
<p dir="auto"><a class="plugin-mentions-user plugin-mentions-a" href="/user/dodi666" aria-label="Profile: dodi666">@<bdi>dodi666</bdi></a> <a href="/post/1348779">sagte</a>:</p>
<p dir="auto">Sehr cool, probiere ich heute Abend mal aus. Aber gibt es dafür nicht auch einen Adapter?</p>
</blockquote>
<p dir="auto">Ja gibt es, aber das wird man ja nicht sooo oft brauchen (hoffe ich) und dann möchte ich mir nicht extra einen Adapter installieren. Das Tool sollte dafür reichen. (für meine Zwecke)</p>
]]></description><link>https://forum.iobroker.net/post/1348781</link><guid isPermaLink="true">https://forum.iobroker.net/post/1348781</guid><dc:creator><![CDATA[Michael Schmitt]]></dc:creator><pubDate>Tue, 21 Jul 2026 11:22:17 GMT</pubDate></item><item><title><![CDATA[Reply to Blockly + Javascripte aus einer script.json extrahieren on Tue, 21 Jul 2026 11:20:02 GMT]]></title><description><![CDATA[<p dir="auto">Sehr cool, probiere ich heute Abend mal aus. Aber gibt es dafür nicht auch einen Adapter?<br />
<a href="https://forum.iobroker.net/topic/84506/test-adapter-script-restore-v0.1.11">test-adapter-script-restore</a></p>
]]></description><link>https://forum.iobroker.net/post/1348779</link><guid isPermaLink="true">https://forum.iobroker.net/post/1348779</guid><dc:creator><![CDATA[dodi666]]></dc:creator><pubDate>Tue, 21 Jul 2026 11:20:02 GMT</pubDate></item><item><title><![CDATA[Reply to Blockly + Javascripte aus einer script.json extrahieren on Mon, 10 Aug 2026 08:58:31 GMT]]></title><description><![CDATA[<p dir="auto">Dann stell ich das mal hier rein. Falls nicht erwünscht dann löschen.</p>
]]></description><link>https://forum.iobroker.net/post/1348734</link><guid isPermaLink="true">https://forum.iobroker.net/post/1348734</guid><dc:creator><![CDATA[Michael Schmitt]]></dc:creator><pubDate>Mon, 10 Aug 2026 08:58:31 GMT</pubDate></item><item><title><![CDATA[Reply to Blockly + Javascripte aus einer script.json extrahieren on Tue, 11 Aug 2026 08:05:12 GMT]]></title><description><![CDATA[<p dir="auto">.............................</p>
]]></description><link>https://forum.iobroker.net/post/1348708</link><guid isPermaLink="true">https://forum.iobroker.net/post/1348708</guid><dc:creator><![CDATA[Michael Schmitt]]></dc:creator><pubDate>Tue, 11 Aug 2026 08:05:12 GMT</pubDate></item><item><title><![CDATA[Reply to Blockly + Javascripte aus einer script.json extrahieren on Mon, 20 Jul 2026 12:22:09 GMT]]></title><description><![CDATA[<p dir="auto">Hi,<br />
hab noch ein wenig mit rum gespielt. Nun kann direkt die zB <strong>javascripts_2026_07_20-03_37_23_backupiobroker.tar.gz</strong> ohne entpacken oder eine entpackte script.json genutzt werden.</p>
<p dir="auto">Zudem kann die Suche verfeinert werden=<br />
<strong>Alle Typen - Nur Blockly - Nur Javascript</strong><br />
<img src="/assets/uploads/files/1784543133145-111.jpg" alt="111.jpg" class=" img-fluid img-markdown" width="920" height="196" /></p>
<ul>
<li>Ordner in C:\iobroker_Script_recover erstellen<br />
in diesen Ordner</li>
<li>Datei erstellen: ioBroker_Script_Extractor.ps1 mit diesem Inhalt.</li>
</ul>
<pre><code>Add-Type -AssemblyName System.Windows.Forms
Add-Type -AssemblyName System.Drawing
Add-Type -AssemblyName System.Web
Add-Type -AssemblyName System.IO.Compression
Add-Type -AssemblyName System.IO.Compression.FileSystem

$so = "C:\iobroker_Script_recover"
$jp = ""

# 1. Komfortabler Auswahldialog für den Benutzer
$fdSelect = New-Object System.Windows.Forms.OpenFileDialog
$fdSelect.InitialDirectory = $so
$fdSelect.Filter = "ioBroker Backups (*.json;*.tar.gz)|*.json;*.tar.gz|Alle Dateien (*.*)|*.*"
$fdSelect.Title = "Bitte wähle deine script.json ODER eine ioBroker .tar.gz Backup-Datei aus"

if ($fdSelect.ShowDialog() -ne [System.Windows.Forms.DialogResult]::OK) { exit }
$gewaehlteDatei = $fdSelect.FileName

# Erkennung der .tar.gz Endung und Entpacken im Arbeitsspeicher
if ($gewaehlteDatei.ToLower().EndsWith(".tar.gz")) {
    Write-Host "Backup-Archiv ausgewählt: $(Split-Path $gewaehlteDatei -Leaf)" -ForegroundColor Yellow
    Write-Host "Entpacke script.json mit Windows-Systemkern..." -ForegroundColor Cyan
    
    $tmp = Join-Path $so "temp_extract"
    if (Test-Path $tmp) { Remove-Item $tmp -Recurse -Force -EA SilentlyContinue }
    New-Item -ItemType Directory -Force -Path $tmp | Out-Null
    
    try {
        $inFile = New-Object System.IO.FileInfo($gewaehlteDatei)
        $gzStream = New-Object System.IO.Compression.GZipStream($inFile.OpenRead(), [System.IO.Compression.CompressionMode]::Decompress)
        
        $tarPath = Join-Path $tmp "temp_backup.tar"
        $tarOutStream = [System.IO.File]::Create($tarPath)
        $gzStream.CopyTo($tarOutStream)
        $tarOutStream.Close(); $gzStream.Close()
        
        $tarFileStream = [System.IO.File]::OpenRead($tarPath)
        $buffer = New-Object byte[] 512
        
        while ($tarFileStream.Read($buffer, 0, 512) -eq 512) {
            $name = [System.Text.Encoding]::ASCII.GetString($buffer, 0, 100).Trim(([char]0))
            $sizeString = [System.Text.Encoding]::ASCII.GetString($buffer, 124, 12).Trim()
            $size = if ($sizeString) { [Convert]::ToInt64($sizeString, 8) } else { 0 }
            
            if ($name.ToLower().EndsWith("script.json")) {
                $outJp = Join-Path $tmp "script.json"
                $outStream = [System.IO.File]::Create($outJp)
                
                $remaining = $size
                $dataBuffer = New-Object byte[] 512
                while ($remaining -gt 0) {
                    $bytesToRead = if ($remaining -gt 512) { 512 } else { $remaining }
                    [void]$tarFileStream.Read($dataBuffer, 0, 512)
                    $outStream.Write($dataBuffer, 0, $bytesToRead)
                    $remaining -= 512
                }
                $outStream.Close()
                $jp = $outJp
                Write-Host "script.json erfolgreich aus dem Archiv extrahiert!" -ForegroundColor Green
                break
            } else {
                if ($size -gt 0) {
                    $blocks = [Math]::Ceiling($size / 512)
                    [void]$tarFileStream.Seek(($blocks * 512), [System.IO.SeekOrigin]::Current)
                }
            }
        }
        $tarFileStream.Close()
    } catch {
        Write-Host "Fehler beim .NET-Entpacken: $_" -ForegroundColor Red
    }
} else {
    $jp = $gewaehlteDatei
}

if (-not $jp -or -not (Test-Path $jp)) {
    [System.Windows.Forms.MessageBox]::Show("Es konnte keine gültige script.json geladen werden!", "Fehler", [System.Windows.Forms.MessageBoxButtons]::OK, [System.Windows.Forms.MessageBoxIcon]::Error)
    exit
}

$raw = Get-Content $jp -Raw; if ([string]::IsNullOrWhiteSpace($raw)) { exit }
$json = ConvertFrom-Json $raw; $skriptListe = [System.Collections.Generic.List[Object]]::new()

# Zuweisungsschutz für die korrekte Erkennung von Blocklys
function Get-SichererBlocklyXml ([string]$sourceCode) {
    if ([string]::IsNullOrWhiteSpace($sourceCode)) { return "" }
    
    if ($sourceCode -match '//([A-Za-z0-9+/=%]{50,})\s*$') {
        try {
            $gesichertesMatch = $Matches[1]
            $xml = [System.Web.HttpUtility]::UrlDecode([Text.Encoding]::UTF8.GetString([Convert]::FromBase64String($gesichertesMatch)))
            if ($xml -match '&lt;xml') { return ($xml -replace '&gt;&lt;', "&gt;`r`n&lt;").Trim() }
        } catch {}
    }
    
    if ($sourceCode -match '(?s)(&lt;xml.*?&lt;/xml&gt;)') { 
        $gesichertesXmlMatch = $Matches[1]
        return ($gesichertesXmlMatch -replace '&gt;&lt;', "&gt;`r`n&lt;").Trim() 
    }
    return ""
}

function Add-SkriptToList ([string]$id, $obj) {
    if (-not $obj -or ($obj.type -match "channel|folder")) { return }
    $saubereID = $id -replace '^script\.js\.(common\.|global\.)?', ''
    $parts = $saubereID.Split('.'); $p = "Hauptordner"
    if ($parts.Count -gt 1) { $p = $parts[0..($parts.Count-2)] -join '/' }
    $src = if ($obj.common -and $obj.common.source) { $obj.common.source } else { "" }
    $en = if ($obj.common -and $obj.common.enabled -ne $null) { $obj.common.enabled } else { $true }
    $xml = Get-SichererBlocklyXml -sourceCode $src
    $skriptListe.Add([PSCustomObject]@{
        Name = $parts[-1]; Pfad = $p; Internal = $id; JsSource = $src; XmlSource = $xml;
        Typ = if ($xml) { "Blockly" } else { "JavaScript" }; Status = if ($en) { "Aktiv" } else { "Deaktiviert" }
    })
}

if ($json.PSObject.Properties.Name -contains "objects") {
    foreach ($p in $json.objects.PSObject.Properties) { if ($p.Name -like "script.js.*") { Add-SkriptToList -id $p.Name -obj $p.Value } }
} else {
    foreach ($p in $json.PSObject.Properties) { if ($p.Name -like "script.js.*") { Add-SkriptToList -id $p.Name -obj $p.Value } }
}
if ($skriptListe.Count -eq 0) { exit }
# 1. FUNKTION GANZ OBEN DEFINIEREN
function Update-Grid {
    $filterText = $txtSearch.Text.Trim()
    $ausgewaehlterTyp = $cmbType.SelectedItem.ToString()
    
    $filtered = $skriptListe | Where-Object { 
        [string]::IsNullOrWhiteSpace($filterText) -or 
        $_.Name.ToLower().Contains($filterText.ToLower()) -or 
        $_.Pfad.ToLower().Contains($filterText.ToLower())
    }
    
    if ($chkHide.Checked) { $filtered = $filtered | Where-Object { $_.Status -eq "Aktiv" } }
    
    if ($ausgewaehlterTyp -eq "Nur Blockly") { $filtered = $filtered | Where-Object { $_.Typ -eq "Blockly" } }
    elseif ($ausgewaehlterTyp -eq "Nur JavaScript") { $filtered = $filtered | Where-Object { $_.Typ -eq "JavaScript" } }
    
    $dt = New-Object System.Data.DataTable
    [void]$dt.Columns.AddRange(@("Name","ioBroker-Pfad","Typ","Status","InternalID"))
    foreach ($item in $filtered) { [void]$dt.Rows.Add($item.Name, $item.Pfad, $item.Typ, $item.Status, $item.Internal) }
    
    $dgv.DataSource = $dt
    if ($dgv.Columns["InternalID"]) { $dgv.Columns["InternalID"].Visible = $false }
    $lblCount.Text = "Gefundene Skripte (aktueller Filter): " + $filtered.Count
    $lblPrev.Text = "Code-Vorschau (0 Skripte ausgewählt):"
}

# NEU: Die Export-Logik als eigene Funktion, damit das Fenster offen bleiben kann!
function Start-ScriptExport ([string]$modus) {
    $ausIDs = @()
    if ($modus -eq "all") {
        foreach ($row in $dgv.Rows) { $ausIDs += [string]$row.Cells["InternalID"].Value }
    } else {
        if ($dgv.SelectedRows.Count -eq 0) {
            [System.Windows.Forms.MessageBox]::Show("Bitte wähle zuerst mindestens ein Skript aus der Liste aus!", "Hinweis", [System.Windows.Forms.MessageBoxButtons]::OK, [System.Windows.Forms.MessageBoxIcon]::Information)
            return
        }
        foreach ($row in $dgv.SelectedRows) { $ausIDs += [string]$row.Cells["InternalID"].Value }
    }
    
    $folderDialog = New-Object System.Windows.Forms.FolderBrowserDialog
    if ($folderDialog.ShowDialog() -ne [System.Windows.Forms.DialogResult]::OK) { return }
    
    $root = Join-Path $folderDialog.SelectedPath ("ioBroker_Backup_" + (Get-Date -Format "yyyy-MM-dd_HH-mm"))
    New-Item -ItemType Directory -Force -Path $root | Out-Null
    
    foreach ($targetID in $ausIDs) {
        $scriptData = $skriptListe | Where-Object { $_.Internal -eq $targetID }; if (-not $scriptData) { continue }
        $folder = if ($scriptData.Pfad -ne "Hauptordner") { Join-Path $root ($scriptData.Pfad -replace '/', '\') } else { $root }
        if ($scriptData.Status -eq "Deaktiviert") { $folder = Join-Path $folder "_DEAKTIVIERT" }
        New-Item -ItemType Directory -Force -Path $folder | Out-Null
        if ($scriptData.Typ -eq "Blockly" -and $scriptData.XmlSource) {
            $scriptData.XmlSource | Set-Content (Join-Path $folder ($scriptData.Name + ".xml")) -Encoding UTF8
            $cleanJs = $scriptData.JsSource
            if ($cleanJs -match '//([A-Za-z0-9+/=%]{50,})\s*$') { $cleanJs = $cleanJs.Replace($Matches, "").Trim() }
            if ($cleanJs -match '(?s)&lt;xml.*?&gt;&lt;/xml&gt;') { $cleanJs = $cleanJs.Replace($Matches, "").Trim() }
            $cleanJs | Set-Content (Join-Path $folder ($scriptData.Name + ".js")) -Encoding UTF8
        } else { $scriptData.JsSource | Set-Content (Join-Path $folder ($scriptData.Name + ".js")) -Encoding UTF8 }
    }
    
    # Erfolgsmeldung anzeigen und Ordner öffnen, ohne das Tool zu schließen!
    Invoke-Item $root
    [System.Windows.Forms.MessageBox]::Show("Export von $($ausIDs.Count) Skripten erfolgreich abgeschlossen!", "Fertig", [System.Windows.Forms.MessageBoxButtons]::OK, [System.Windows.Forms.MessageBoxIcon]::Information)
}

# 2. FENSTER ERSTELLEN
$f = New-Object System.Windows.Forms.Form
$f.Text = "ioBroker Script Extractor"
$f.Size = New-Object System.Drawing.Size(950, 800)
$f.StartPosition = "CenterScreen"

$pnlTop = New-Object System.Windows.Forms.Panel
$pnlTop.Dock = "Top"
$pnlTop.Height = 75
$f.Controls.Add($pnlTop)

$lblSrc = New-Object System.Windows.Forms.Label -Property @{Text="Suchen:"; Location=New-Object System.Drawing.Point(10,18); Size=New-Object System.Drawing.Size(50,20)}
$txtSearch = New-Object System.Windows.Forms.TextBox -Property @{Location=New-Object System.Drawing.Point(65,15); Size=New-Object System.Drawing.Size(410,20)}
$chkHide = New-Object System.Windows.Forms.CheckBox -Property @{Text="Deaktivierte ausblenden"; Location=New-Object System.Drawing.Point(490,13); Size=New-Object System.Drawing.Size(170,24)}

$cmbType = New-Object System.Windows.Forms.ComboBox
$cmbType.Location = New-Object System.Drawing.Point(670, 13)
$cmbType.Size = New-Object System.Drawing.Size(150, 24)
$cmbType.DropDownStyle = [System.Windows.Forms.ComboBoxStyle]::DropDownList
[void]$cmbType.Items.AddRange(@("Alle Typen", "Nur Blockly", "Nur JavaScript"))
$cmbType.SelectedIndex = 0

$lblCount = New-Object System.Windows.Forms.Label
$lblCount.Text = "Gefundene Skripte: 0"
$lblCount.Location = New-Object System.Drawing.Point(10, 48)
$lblCount.Size = New-Object System.Drawing.Size(500, 20)
$lblCount.Font = New-Object System.Drawing.Font("Arial", 9, [System.Drawing.FontStyle]::Bold)

$pnlTop.Controls.AddRange(@($lblSrc, $txtSearch, $chkHide, $cmbType, $lblCount))

$pnlBottom = New-Object System.Windows.Forms.Panel
$pnlBottom.Dock = "Bottom"
$pnlBottom.Height = 55
$f.Controls.Add($pnlBottom)

$btnClip = New-Object System.Windows.Forms.Button -Property @{Text="In Zwischenablage"; Location=New-Object System.Drawing.Point(10,10); Size=New-Object System.Drawing.Size(160,35)}
$btnAll = New-Object System.Windows.Forms.Button -Property @{Text="Alle exportieren"; Location=New-Object System.Drawing.Point(180,10); Size=New-Object System.Drawing.Size(150,35)}
$btnOk = New-Object System.Windows.Forms.Button -Property @{Text="Ausgewaehlte exportieren"; Location=New-Object System.Drawing.Point(500,10); Size=New-Object System.Drawing.Size(160,35)}
$btnCan = New-Object System.Windows.Forms.Button -Property @{Text="Schliessen"; Location=New-Object System.Drawing.Point(670,10); Size=New-Object System.Drawing.Size(150,35)}
$pnlBottom.Controls.AddRange(@($btnClip, $btnAll, $btnOk, $btnCan))

$dgv = New-Object System.Windows.Forms.DataGridView
$dgv.Location = New-Object System.Drawing.Point(10, 80)
$dgv.Size = New-Object System.Drawing.Size(915, 260)
$dgv.Anchor = "Top, Left, Right"
$dgv.AutoSizeColumnsMode = "Fill"
$dgv.SelectionMode = "FullRowSelect"
$dgv.MultiSelect = $true
$dgv.AllowUserToAddRows = $false
$dgv.ReadOnly = $true
$dgv.RowHeadersVisible = $false
$f.Controls.Add($dgv)

$lblPrev = New-Object System.Windows.Forms.Label
$lblPrev.Text = "Code-Vorschau:"
$lblPrev.Location = New-Object System.Drawing.Point(10, 355)
$lblPrev.Size = New-Object System.Drawing.Size(500, 20)
$lblPrev.Anchor = "Top, Left"
$f.Controls.Add($lblPrev)

$txtPrev = New-Object System.Windows.Forms.RichTextBox
$txtPrev.Location = New-Object System.Drawing.Point(10, 380)
$txtPrev.Size = New-Object System.Drawing.Size(915, 310)
$txtPrev.Anchor = "Top, Bottom, Left, Right"
$txtPrev.Font = New-Object System.Drawing.Font("Consolas", 10)
$txtPrev.ReadOnly = $true
$txtPrev.WordWrap = $false
$f.Controls.Add($txtPrev)

# 3. EVENTS VERDRAHTEN
$txtSearch.Add_TextChanged({ Update-Grid })
$chkHide.Add_CheckedChanged({ Update-Grid })
$cmbType.Add_SelectedIndexChanged({ Update-Grid })

$dgv.Add_SelectionChanged({
    $anz = $dgv.SelectedRows.Count
    if ($anz -gt 0) {
        $selectedRow = $dgv.SelectedRows | Select-Object -First 1; $selID = [string]$selectedRow.Cells["InternalID"].Value
        if (-not [string]::IsNullOrEmpty($selID)) {
            $m = $skriptListe | Where-Object { $_.Internal -eq $selID }
            if ($m) { $txtPrev.Text = if ($m.Typ -eq "Blockly" -and $m.XmlSource) { $m.XmlSource } else { $m.JsSource } }
        }
        $lblPrev.Text = if ($anz -eq 1) { "Code-Vorschau (1 Skript ausgewählt):" } else { "Code-Vorschau ($anz Skripte ausgewählt):" }
    } else { $txtPrev.Text = ""; $lblPrev.Text = "Code-Vorschau (0 Skripte ausgewählt):" }
})

$btnClip.Add_Click({
    if (-not [string]::IsNullOrEmpty($txtPrev.Text)) {
        [System.Windows.Forms.Clipboard]::SetText($txtPrev.Text)
        $global:oldText = $btnClip.Text; $btnClip.Text = "Kopiert!"; $btnClip.Enabled = $false
        $global:clipTimer = New-Object System.Windows.Forms.Timer -Property @{ Interval = 1500 }
        $global:clipTimer.Add_Tick({ $btnClip.Text = $global:oldText; $btnClip.Enabled = $true; $global:clipTimer.Stop(); $global:clipTimer.Dispose() }); $global:clipTimer.Start()
    }
})

# KORREKTUR: Knöpfe rufen jetzt die Funktion auf und halten das Fenster offen!
$btnAll.Add_Click({ Start-ScriptExport -modus "all" })
$btnOk.Add_Click({ Start-ScriptExport -modus "selected" })
$btnCan.Add_Click({ $f.Close() })

# Grid initial befüllen &amp; Fenster starten
Update-Grid
[void]$f.ShowDialog()

# Nach dem manuellen Schließen des Fensters den Temp-Ordner aufräumen
if (Test-Path (Join-Path $so "temp_extract")) { Remove-Item (Join-Path $so "temp_extract") -Recurse -Force -EA SilentlyContinue }
</code></pre>
<p dir="auto">Powershell öffnen</p>
<ul>
<li>cd C:\iobroker_Script_recover</li>
<li>.\ioBroker_Script_Extractor.ps1</li>
<li>javascripts_XXXXXXXX_backupiobroker.tar.gz oder script.json aus dem Backup von Backitup auswählen.</li>
</ul>
<p dir="auto"><img src="/assets/uploads/files/1784550105251-screenshot-2026-07-20-142038.jpg" alt="Screenshot 2026-07-20 142038.jpg" class=" img-fluid img-markdown" width="1048" height="340" /></p>
<p dir="auto">Gruß</p>
]]></description><link>https://forum.iobroker.net/post/1348697</link><guid isPermaLink="true">https://forum.iobroker.net/post/1348697</guid><dc:creator><![CDATA[Michael Schmitt]]></dc:creator><pubDate>Mon, 20 Jul 2026 12:22:09 GMT</pubDate></item><item><title><![CDATA[Reply to Blockly + Javascripte aus einer script.json extrahieren on Sun, 19 Jul 2026 21:06:58 GMT]]></title><description><![CDATA[<p dir="auto">Hi,<br />
ja hab ich schon gesehen/gelesen. Super gemacht und eine super Idee. Ich für mich möchte aber deswegen keinen Adapter installieren und mach sowas außerhalb. So oft wird man das ja eh nicht brauchen (hoffentlich)</p>
<p dir="auto">Gruß</p>
]]></description><link>https://forum.iobroker.net/post/1348682</link><guid isPermaLink="true">https://forum.iobroker.net/post/1348682</guid><dc:creator><![CDATA[Michael Schmitt]]></dc:creator><pubDate>Sun, 19 Jul 2026 21:06:58 GMT</pubDate></item><item><title><![CDATA[Reply to Blockly + Javascripte aus einer script.json extrahieren on Sun, 19 Jul 2026 21:02:38 GMT]]></title><description><![CDATA[<blockquote>
<p dir="auto"><a class="plugin-mentions-user plugin-mentions-a" href="/user/michael-schmitt" aria-label="Profile: Michael-Schmitt">@<bdi>Michael-Schmitt</bdi></a> <a href="/post/1348678">sagte</a>:</p>
<p dir="auto">hab jetzt noch ein wenig daran gebaut</p>
</blockquote>
<p dir="auto">Eigentlich eine gute Idee und auch schön umgesetzt! Aber schau dir doch mal den relativ neuen Adapter <strong>script-restore</strong> von <a class="plugin-mentions-user plugin-mentions-a" href="/user/david-g." aria-label="Profile: david-g.">@<bdi>david-g.</bdi></a>  an. Der hat eine GUI und erspart einem jegliches Gefummel und entpacken. Z.Zt. nur im Beta, kommt aber schon bald ins Stable</p>
]]></description><link>https://forum.iobroker.net/post/1348681</link><guid isPermaLink="true">https://forum.iobroker.net/post/1348681</guid><dc:creator><![CDATA[fastfoot]]></dc:creator><pubDate>Sun, 19 Jul 2026 21:02:38 GMT</pubDate></item><item><title><![CDATA[Reply to Blockly + Javascripte aus einer script.json extrahieren on Mon, 10 Aug 2026 08:58:50 GMT]]></title><description><![CDATA[<p dir="auto">Hallo,</p>
<p dir="auto">hab jetzt noch ein wenig daran gebaut (Ki war der Macher) und nun schaut es so aus.<br />
<strong>Ist nur mit Javascript  und Blockly getestet.</strong></p>
<p dir="auto">Jetzt mit</p>
<ul>
<li>Suchfunktion</li>
<li>einzelne Scripte wiederherstellen</li>
<li>direkt in Zwischenablage kopieren</li>
<li>deaktivierte Scripte ausblenden</li>
<li>Namens oder Ordnersuche</li>
</ul>
<p dir="auto"><img src="/assets/uploads/files/1784492194032-1.jpg" alt="1.jpg" class=" img-fluid img-markdown" width="828" height="724" /></p>
<ul>
<li>Ordner in C:\iobroker_Script_recover erstellen<br />
in diesen Ordner</li>
<li>Datei erstellen:  <strong>ioBroker_Script_Extractor.ps1</strong> mit diesem Inhalt.</li>
</ul>
<pre><code>Add-Type -AssemblyName System.Windows.Forms
Add-Type -AssemblyName System.Drawing
Add-Type -AssemblyName System.Web

$fd = New-Object System.Windows.Forms.OpenFileDialog
$fd.InitialDirectory = "C:\"
$fd.Filter = "script.json|*.json"
$fd.Title = "Wähle script.json"

if ($fd.ShowDialog() -ne [System.Windows.Forms.DialogResult]::OK) { exit }

$raw = Get-Content $fd.FileName -Raw
if ([string]::IsNullOrWhiteSpace($raw)) { exit }
$json = ConvertFrom-Json $raw
$skriptListe = [System.Collections.Generic.List[Object]]::new()

function Get-SichererBlocklyXml ([string]$sourceCode) {
    if ([string]::IsNullOrWhiteSpace($sourceCode)) { return "" }
    if ($sourceCode -match '//([A-Za-z0-9+/=%]{50,})\s*$') {
        try {
            $xml = [System.Web.HttpUtility]::UrlDecode([Text.Encoding]::UTF8.GetString([Convert]::FromBase64String($Matches[1])))
            if ($xml -match '&lt;xml') { return ($xml -replace '&gt;&lt;', "&gt;`r`n&lt;").Trim() }
        } catch {}
    }
    if ($sourceCode -match '(?s)(&lt;xml.*?&lt;/xml&gt;)') { return ($Matches[1] -replace '&gt;&lt;', "&gt;`r`n&lt;").Trim() }
    return ""
}

function Add-SkriptToList ([string]$id, $obj) {
    if (-not $obj -or ($obj.type -match "channel|folder")) { return }
    $saubereID = $id -replace '^script\.js\.(common\.|global\.)?', ''
    $parts = $saubereID.Split('.')
    $p = "Hauptordner"
    if ($parts.Count -gt 1) { $p = $parts[0..($parts.Count-2)] -join '/' }
    $src = ""
    if ($obj.common -and $obj.common.source) { $src = $obj.common.source }
    elseif ($obj.source) { $src = $obj.source }
    $en = $true
    if ($obj.common -and $obj.common.enabled -ne $null) { $en = $obj.common.enabled }
    $xml = Get-SichererBlocklyXml -sourceCode $src
    $skriptListe.Add([PSCustomObject]@{
        Name = $parts[-1]; Pfad = $p; Internal = $id; JsSource = $src; XmlSource = $xml;
        Typ = if ($xml) { "Blockly" } else { "JavaScript" }; Status = if ($en) { "Aktiv" } else { "Deaktiviert" }
    })
}

if ($json.PSObject.Properties.Name -contains "objects") {
    foreach ($p in $json.objects.PSObject.Properties) { if ($p.Name -like "script.js.*") { Add-SkriptToList -id $p.Name -obj $p.Value } }
} else {
    foreach ($p in $json.PSObject.Properties) { if ($p.Name -like "script.js.*") { Add-SkriptToList -id $p.Name -obj $p.Value } }
}
if ($skriptListe.Count -eq 0) { exit }

$f = New-Object System.Windows.Forms.Form
$f.Text = "ioBroker Script Extractor"
$f.Size = New-Object System.Drawing.Size(850, 750)
$f.StartPosition = "CenterScreen"

$txtSearch = New-Object System.Windows.Forms.TextBox
$txtSearch.Location = New-Object System.Drawing.Point(65, 12)
$txtSearch.Size = New-Object System.Drawing.Size(450, 20)
$f.Controls.Add($txtSearch)

$chkHide = New-Object System.Windows.Forms.CheckBox
$chkHide.Text = "Deaktivierte ausblenden"
$chkHide.Location = New-Object System.Drawing.Point(530, 10)
$chkHide.Size = New-Object System.Drawing.Size(180, 24)
$f.Controls.Add($chkHide)

$lblCount = New-Object System.Windows.Forms.Label
$lblCount.Text = "Gefundene Skripte: 0"
$lblCount.Location = New-Object System.Drawing.Point(10, 45)
$lblCount.Size = New-Object System.Drawing.Size(400, 20)
$lblCount.Font = New-Object System.Drawing.Font("Arial", 9, [System.Drawing.FontStyle]::Bold)
$f.Controls.Add($lblCount)

$dgv = New-Object System.Windows.Forms.DataGridView
$dgv.Location = New-Object System.Drawing.Point(10, 70)
$dgv.Size = New-Object System.Drawing.Size(810, 240)
$dgv.AutoSizeColumnsMode = "Fill"
$dgv.SelectionMode = "FullRowSelect"
$dgv.MultiSelect = $true
$dgv.AllowUserToAddRows = $false
$dgv.ReadOnly = $true
$dgv.RowHeadersVisible = $false
$f.Controls.Add($dgv)

$lblPrev = New-Object System.Windows.Forms.Label
$lblPrev.Text = "Vorschau:"
$lblPrev.Location = New-Object System.Drawing.Point(10, 325)
$lblPrev.Size = New-Object System.Drawing.Size(500, 20)
$f.Controls.Add($lblPrev)

$txtPrev = New-Object System.Windows.Forms.RichTextBox
$txtPrev.Location = New-Object System.Drawing.Point(10, 345)
$txtPrev.Size = New-Object System.Drawing.Size(810, 260)
$txtPrev.Font = New-Object System.Drawing.Font("Consolas", 10)
$txtPrev.ReadOnly = $true
$txtPrev.WordWrap = $false
$f.Controls.Add($txtPrev)

$btnClip = New-Object System.Windows.Forms.Button
$btnClip.Text = "📋 In Zwischenablage"
$btnClip.Location = New-Object System.Drawing.Point(10, 620)
$btnClip.Size = New-Object System.Drawing.Size(160, 35)
$f.Controls.Add($btnClip)

$btnAll = New-Object System.Windows.Forms.Button
$btnAll.Text = "📦 Alle exportieren"
$btnAll.Location = New-Object System.Drawing.Point(180, 620)
$btnAll.Size = New-Object System.Drawing.Size(150, 35)
$btnAll.DialogResult = 7
$f.Controls.Add($btnAll)

$btnOk = New-Object System.Windows.Forms.Button
$btnOk.Text = "Ausgewählte exportieren"
$btnOk.Location = New-Object System.Drawing.Point(500, 620)
$btnOk.Size = New-Object System.Drawing.Size(160, 35)
$btnOk.DialogResult = [System.Windows.Forms.DialogResult]::OK
$f.Controls.Add($btnOk)

$btnCan = New-Object System.Windows.Forms.Button
$btnCan.Text = "Abbrechen"
$btnCan.Location = New-Object System.Drawing.Point(670, 620)
$btnCan.Size = New-Object System.Drawing.Size(150, 35)
$btnCan.DialogResult = [System.Windows.Forms.DialogResult]::Cancel
$f.Controls.Add($btnCan)

$lblSrc = New-Object System.Windows.Forms.Label -Property @{Text="Suchen:"; Location=New-Object System.Drawing.Point(10,15); Size=New-Object System.Drawing.Size(50,20)}
$f.Controls.Add($lblSrc)

# KORREKTUR: Wildcards (*...) wieder korrekt für den Pfad-Vergleich aktiviert
function Update-Grid {
    $filtered = $skriptListe | Where-Object { [string]::IsNullOrWhiteSpace($txtSearch.Text) -or $_.Name -like "*$($txtSearch.Text)*" -or $_.Pfad -like "*$($txtSearch.Text)*" } | Where-Object { -not $chkHide.Checked -or $_.Status -eq "Aktiv" }
    $dt = New-Object System.Data.DataTable; [void]$dt.Columns.AddRange(@("Name","ioBroker-Pfad","Typ","Status","InternalID"))
    foreach ($item in $filtered) { [void]$dt.Rows.Add($item.Name, $item.Pfad, $item.Typ, $item.Status, $item.Internal) }
    $dgv.DataSource = $dt; if ($dgv.Columns["InternalID"]) { $dgv.Columns["InternalID"].Visible = $false }
    $lblCount.Text = "Gefundene Skripte (aktueller Filter): " + $filtered.Count
    $lblPrev.Text = "Vorschau (0 Skripte ausgewählt):"
}
$txtSearch.Add_TextChanged({ Update-Grid }); $chkHide.Add_CheckedChanged({ Update-Grid })

$dgv.Add_SelectionChanged({
    $anz = $dgv.SelectedRows.Count
    if ($anz -gt 0) {
        $selID = [string]$dgv.SelectedRows[0].Cells["InternalID"].Value
        if (-not [string]::IsNullOrEmpty($selID)) {
            $m = $skriptListe | Where-Object { $_.Internal -eq $selID }
            if ($m) { $txtPrev.Text = if ($m.Typ -eq "Blockly" -and $m.XmlSource) { $m.XmlSource } else { $m.JsSource } }
        }
        $lblPrev.Text = if ($anz -eq 1) { "Code-Vorschau (1 Skript ausgewählt):" } else { "Code-Vorschau ($anz Skripte ausgewählt):" }
    } else { $txtPrev.Text = ""; $lblPrev.Text = "Vorschau (0 Skripte ausgewählt):" }
})

$btnClip.Add_Click({
    if (-not [string]::IsNullOrEmpty($txtPrev.Text)) {
        [System.Windows.Forms.Clipboard]::SetText($txtPrev.Text)
        $global:oldText = $btnClip.Text; $btnClip.Text = "✅ Kopiert!"; $btnClip.Enabled = $false
        $global:clipTimer = New-Object System.Windows.Forms.Timer -Property @{ Interval = 1500 }
        $global:clipTimer.Add_Tick({ $btnClip.Text = $global:oldText; $btnClip.Enabled = $true; $global:clipTimer.Stop(); $global:clipTimer.Dispose() }); $global:clipTimer.Start()
    }
})

Update-Grid
$res = $f.ShowDialog(); $ausIDs = @()
if ($res -eq 7) { foreach ($r in $dgv.Rows) { $ausIDs += [string]$r.Cells["InternalID"].Value } }
elseif ($res -eq [System.Windows.Forms.DialogResult]::OK -and $dgv.SelectedRows.Count -gt 0) { foreach ($r in $dgv.SelectedRows) { $ausIDs += [string]$r.Cells["InternalID"].Value } }
else { $f.Dispose(); exit }; $f.Dispose()

$folderDialog = New-Object System.Windows.Forms.FolderBrowserDialog
if ($folderDialog.ShowDialog() -ne [System.Windows.Forms.DialogResult]::OK) { exit }
$root = Join-Path $folderDialog.SelectedPath ("ioBroker_Backup_" + (Get-Date -Format "yyyy-MM-dd_HH-mm"))
New-Item -ItemType Directory -Force -Path $root | Out-Null

foreach ($targetID in $ausIDs) {
    $scriptData = $skriptListe | Where-Object { $_.Internal -eq $targetID }
    if (-not $scriptData) { continue }
    $folder = if ($scriptData.Pfad -ne "Hauptordner") { Join-Path $root ($scriptData.Pfad -replace '/', '\') } else { $root }
    if ($scriptData.Status -eq "Deaktiviert") { $folder = Join-Path $folder "_DEAKTIVIERT" }
    New-Item -ItemType Directory -Force -Path $folder | Out-Null
    if ($scriptData.Typ -eq "Blockly" -and $scriptData.XmlSource) {
        $scriptData.XmlSource | Set-Content (Join-Path $folder ($scriptData.Name + ".xml")) -Encoding UTF8
        $cleanJs = $scriptData.JsSource
        if ($cleanJs -match '//([A-Za-z0-9+/=%]{50,})\s*$') { $cleanJs = $cleanJs.Replace($Matches[0], "").Trim() }
        if ($cleanJs -match '(?s)&lt;xml.*?&gt;&lt;/xml&gt;') { $cleanJs = $cleanJs.Replace($Matches[0], "").Trim() }
        $cleanJs | Set-Content (Join-Path $folder ($scriptData.Name + ".js")) -Encoding UTF8
    } else { $scriptData.JsSource | Set-Content (Join-Path $folder ($scriptData.Name + ".js")) -Encoding UTF8 }
}
Invoke-Item $root

</code></pre>
<ul>
<li>Powershell öffnen</li>
<li>cd C:\iobroker_Script_recover</li>
<li>.\ioBroker_Script_Extractor.ps1</li>
<li><strong>script.json</strong> aus dem Javascript Backup auswählen (muß man davor natürlich entpacken)</li>
</ul>
<p dir="auto"><img src="/assets/uploads/files/1784493170953-3.jpg" alt="3.jpg" class=" img-fluid img-markdown" width="832" height="381" /></p>
<p dir="auto">hier zB mit deaktivierten scripten ausblenden<br />
<img src="/assets/uploads/files/1784493218235-2.jpg" alt="2.jpg" class=" img-fluid img-markdown" width="819" height="724" /></p>
<p dir="auto">ich hoffe es hilft jemandem ;)</p>
]]></description><link>https://forum.iobroker.net/post/1348678</link><guid isPermaLink="true">https://forum.iobroker.net/post/1348678</guid><dc:creator><![CDATA[Michael Schmitt]]></dc:creator><pubDate>Mon, 10 Aug 2026 08:58:50 GMT</pubDate></item></channel></rss>