Weiter zum Inhalt
  • Home
  • Aktuell
  • Tags
  • 0 Ungelesen 0
  • Kategorien
  • Unreplied
  • Beliebt
  • GitHub
  • Docu
  • Hilfe
Skins
  • Hell
  • Brite
  • Cerulean
  • Cosmo
  • Flatly
  • Journal
  • Litera
  • Lumen
  • Lux
  • Materia
  • Minty
  • Morph
  • Pulse
  • Sandstone
  • Simplex
  • Sketchy
  • Spacelab
  • United
  • Yeti
  • Zephyr
  • Dunkel
  • Cyborg
  • Darkly
  • Quartz
  • Slate
  • Solar
  • Superhero
  • Vapor

  • Standard: (Kein Skin)
  • Kein Skin
Einklappen
ioBroker Logo

Community Forum

donate donate
  1. ioBroker Community Home
  2. Deutsch
  3. Skripten / Logik
  4. Blockly + Javascripte aus einer script.json extrahieren

NEWS

  • Der neue Monatsrückblick für Mai und Juni 2026 ist online!
    BluefoxB
    Bluefox
    8
    1
    961

  • wichtiges UPDATE für controller 7.2.2 im stable
    HomoranH
    Homoran
    10
    1
    3.5k

  • Neues YouTube-Video: Visualisierung im Devices-Adapter
    BluefoxB
    Bluefox
    16
    1
    6.0k

Blockly + Javascripte aus einer script.json extrahieren

Geplant Angeheftet Gesperrt Verschoben Skripten / Logik
24 Beiträge 8 Kommentatoren 1.1k Aufrufe 8 Beobachtet
  • Älteste zuerst
  • Neuste zuerst
  • Meiste Stimmen
Antworten
  • In einem neuen Thema antworten
Anmelden zum Antworten
Dieses Thema wurde gelöscht. Nur Nutzer mit entsprechenden Rechten können es sehen.
  • Michael SchmittM Online
    Michael SchmittM Online
    Michael Schmitt
    schrieb am zuletzt editiert von
    #12

    Hi, gern geschehen. Freu mich wenn es nützlich ist ;)

    1 Antwort Letzte Antwort
    0
    • Michael SchmittM Online
      Michael SchmittM Online
      Michael Schmitt
      schrieb am zuletzt editiert von Michael Schmitt
      #13

      Hier die neueste Version

      ioBroker_Script_Extractor.ps1

      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 '<xml') { return ($xml -replace '><', ">`r`n<").Trim() }
                  } catch {}
              }
              if ($sourceCode -match '(?s)(<xml.*?</xml>)') { return ($Matches[1] -replace '><', ">`r`n<").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)<xml.*?></xml>') { $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 & 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 & 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 }
      }
      

      damit kann man nun auch nach Datenpunkten in den Scripten suchen und diese werden dann aufgelistet.

      333.jpg

      1 Antwort Letzte Antwort
      0
      • Michael SchmittM Michael Schmitt

        Hallo,

        hab jetzt noch ein wenig daran gebaut (Ki war der Macher) und nun schaut es so aus.
        Ist nur mit Javascript und Blockly getestet.

        Jetzt mit

        • Suchfunktion
        • einzelne Scripte wiederherstellen
        • direkt in Zwischenablage kopieren
        • deaktivierte Scripte ausblenden
        • Namens oder Ordnersuche

        1.jpg

        • Ordner in C:\iobroker_Script_recover erstellen
          in diesen Ordner
        • Datei erstellen: ioBroker_Script_Extractor.ps1 mit diesem Inhalt.
        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 '<xml') { return ($xml -replace '><', ">`r`n<").Trim() }
                } catch {}
            }
            if ($sourceCode -match '(?s)(<xml.*?</xml>)') { return ($Matches[1] -replace '><', ">`r`n<").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)<xml.*?></xml>') { $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
        
        
        • Powershell öffnen
        • cd C:\iobroker_Script_recover
        • .\ioBroker_Script_Extractor.ps1
        • script.json aus dem Javascript Backup auswählen (muß man davor natürlich entpacken)

        3.jpg

        hier zB mit deaktivierten scripten ausblenden
        2.jpg

        ich hoffe es hilft jemandem ;)

        vowillV Online
        vowillV Online
        vowill
        schrieb am zuletzt editiert von
        #14

        @Michael-Schmitt sagte:

        Hier die EXE direkt für Windows

        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?

        ioBroker im lxc auf NUC / Aktive Instanzen: 38

        1 Antwort Letzte Antwort
        0
        • Michael SchmittM Online
          Michael SchmittM Online
          Michael Schmitt
          schrieb am zuletzt editiert von Michael Schmitt
          #15

          @vowill werden dir den in der GUI die Scripte angezeigt ?
          444.jpg

          du kannst unten auf

          • in Zwischenablage - kopiert den code der unten steht in die Zwischenablage
          • Alle exportieren - hiermit werden alle gefundenen Scripte exportiert
          • Ausgewählte exportieren - die Scripte die blau unterlegt sind werden exportiert
          1 Antwort Letzte Antwort
          0
          • vowillV Online
            vowillV Online
            vowill
            schrieb zuletzt editiert von
            #16

            @michael-schmitt 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 ...

            ioBroker im lxc auf NUC / Aktive Instanzen: 38

            1 Antwort Letzte Antwort
            0
            • Michael SchmittM Online
              Michael SchmittM Online
              Michael Schmitt
              schrieb zuletzt editiert von Michael Schmitt
              #17

              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 ?

              1.jpg

              S vowillV 2 Antworten Letzte Antwort
              0
              • Michael SchmittM Michael Schmitt

                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 ?

                1.jpg

                S Abwesend
                S Abwesend
                Shadowhunter23
                schrieb zuletzt editiert von
                #18

                @Michael-Schmitt
                Mit welcher KI baust du das Tool?

                Proxmox 9 HA-Cluster mit 3x HP prodesk 400 G6 i5
                Bei einem downvote bitte melden, es könnte sein, dass dieser ungewollt ist!

                1 Antwort Letzte Antwort
                0
                • Michael SchmittM Online
                  Michael SchmittM Online
                  Michael Schmitt
                  schrieb zuletzt editiert von Michael Schmitt
                  #19

                  Teilweise Gemini und ChatGPT

                  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 ;)

                  Für mich reichts

                  S 1 Antwort Letzte Antwort
                  0
                  • Michael SchmittM Michael Schmitt

                    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 ?

                    1.jpg

                    vowillV Online
                    vowillV Online
                    vowill
                    schrieb zuletzt editiert von vowill
                    #20

                    @Michael-Schmitt sagte:

                    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 ?

                    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.

                    Nachtrag: Wenn ich das ps1 anlege und es in PowerShell öffne, kommt die Fehlermeldung

                    • CategoryInfo: Sicherheitsfehler: (:) [], PSSecurityException
                    • FullyQualifiedErrorId : UnauthorizedAccess

                    Noch ein Nachtrag: In PS musste ich die Ausführungsrichtlinie ändern (auf 'Bypass' oder 'Unrestricted'). Dann läuft das Skript los.
                    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" 😕

                    ioBroker im lxc auf NUC / Aktive Instanzen: 38

                    1 Antwort Letzte Antwort
                    0
                    • NashraN Offline
                      NashraN Offline
                      Nashra
                      Most Active Forum Testing
                      schrieb zuletzt editiert von
                      #21

                      @michael-schmitt
                      habe die exe mal gestartet, funktioniert bei mir.
                      Gut gemacht 👍

                      Gruß Ralf
                      Mir egal, wer Dein Vater ist! Wenn ich hier angel, wird nicht übers Wasser gelaufen!!

                      Benutzt das Voting rechts unten im Beitrag wenn er euch geholfen hat.

                      1 Antwort Letzte Antwort
                      0
                      • Michael SchmittM Online
                        Michael SchmittM Online
                        Michael Schmitt
                        schrieb zuletzt editiert von
                        #22

                        Sry hab ich falsch geschrieben, du must die Backup Datei nehmen die mit Javascript beginnt

                        1 Antwort Letzte Antwort
                        0
                        • Michael SchmittM Michael Schmitt

                          Teilweise Gemini und ChatGPT

                          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 ;)

                          Für mich reichts

                          S Abwesend
                          S Abwesend
                          Shadowhunter23
                          schrieb zuletzt editiert von Shadowhunter23
                          #23

                          @Michael-Schmitt sagte:

                          EDIT: es ist jeder Willkommen die Idee weiter zu spinnen und zu verbessern.

                          Ebenso ein Spaß Projekt:

                          280d670e-5d04-40a4-9ace-896a4425b27f-image.jpeg
                          574e21fa-6749-4488-b534-e33b5d5b60a8-image.jpeg
                          cdcf0bef-e0f3-424e-86e9-09ffcb70ae23-image.jpeg
                          bb900bdb-d1f0-4ebd-ac05-228d2368868f-image.jpeg
                          42bfc7cb-1e40-41b4-8665-3ed088b00e00-image.jpeg

                          Die exe Datei:
                          https://drive.google.com/file/d/1Jug1z98lpoxkRgWG7Jp-BQdsnhLoAw-F/view?usp=sharing

                          Und das Zip mit einzelnen Dateien:
                          https://drive.google.com/file/d/1EsFg0gmovrbbDVi_8BtJi7c6_r3tVMph/view?usp=sharing

                          Was es kann:

                          **Übersicht** — Backup-Datum, Objekt- und State-Zahlen, alle Adapter-Instanzen mit Version,
                          Aktivierungsstatus und Anzahl zugehöriger Objekte.
                          
                          **Skripte** — alle Skripte mit Typ und Status. Suche wahlweise nach Name/Pfad oder **im
                          Code**, wobei auch das dekodierte Blockly-XML durchsucht wird. Genau damit beantwortet sich
                          die Ausgangsfrage: Man gibt eine Datenpunkt-ID ein und bekommt die Skripte, die sie
                          tatsächlich verwenden — auch die Blockly-Skripte, wo die ID im generierten JavaScript
                          teilweise anders aussieht. Export legt die ioBroker-Ordnerstruktur an, Blockly wird als
                          `.xml` **und** als bereinigtes `.js` geschrieben, deaktivierte Skripte landen in einem
                          eigenen Unterordner.
                          
                          **VIS-Datenpunkte** — alle in der Visualisierung verwendeten Datenpunkte, getrennt nach
                          VIS 1 und VIS 2, mit View, Widget-ID, Widget-Typ und dem Feld, in dem der Datenpunkt steckt
                          (`oid` = angezeigter Wert, `visibility-oid` = Sichtbarkeit usw.). Aliasse werden mit ihrem
                          Ziel aufgelöst. Ein eigener Filter zeigt **tote Widgets**: Datenpunkte, die im Backup gar
                          nicht mehr existieren, auf die eine View aber weiterhin zeigt.
                          
                          **Verwaiste Datenpunkte** — drei Analysen:
                          
                          - *A: Objekt-Leichen* — Objekte, deren Adapter-Instanz im Backup fehlt.
                          - *B: unbenutzte User-Datenpunkte* — geprüft werden Vorkommen in Skripten, Vorkommen in
                           VIS-Views, Alias-Ziel und aktives Logging. Zusätzlich wird die letzte Wertänderung aus
                           der `states.jsonl` angezeigt.
                          - *C: States* — fünf Sichten auf die States-Datenbank: Werte ohne Objekt, Objekte ohne
                           Wert, älteste Datenpunkte, Störungen (Qualitätscode) und nicht quittierte Befehle.
                          
                          **Vergleich zweier Backups** — die Antwort auf „gestern lief es noch". Zeigt geänderte
                          Adapter-Versionen (Downgrades werden hervorgehoben), neu angelegte und gelöschte Objekte
                          nach Namensraum, geänderte VIS-Views und vor allem **inhaltlich geänderte Skripte mit
                          zeilengenauem Vergleich** — bei Blockly wird das XML verglichen, nicht das daraus erzeugte
                          JavaScript. Zusätzlich wird geprüft, ob beide Backups überhaupt aus derselben Installation
                          stammen (dazu unten mehr).
                          
                          ---
                          
                          ## Was ich beim Bauen über das Backup-Format gelernt habe
                          
                          Das ist vermutlich der für die Allgemeinheit interessantere Teil.
                          
                          **1. Es gibt keine `backup.json` mehr.** Aktuelles Backitup schreibt `objects.jsonl` und
                          `states.jsonl` im JSON-Lines-Format — eine Zeile pro Datensatz, kein umschließendes
                          JSON-Objekt. Die Objekt-ID steht dabei im Feld `_id` innerhalb der Zeile, nicht als
                          Schlüssel. Nebeneffekt: Man kann die Datei zeilenweise lesen, der Speicherbedarf ist von
                          der Dateigröße unabhängig. Ein Voll-Backup mit rund 25 MB `objects.jsonl` ist bei mir in
                          gut einer Sekunde ausgewertet.
                          
                          **2. Blockly-XML braucht zwingend eine URL-Dekodierung.** Das XML hängt als Base64-Kommentar
                          am Ende des `source`-Strings. Base64 dekodieren allein reicht nicht — das Ergebnis ist
                          zusätzlich URL-kodiert. Erst danach beginnt es mit `<xml`. Ich habe das gegen einen echten
                          Export aus dem Admin geprüft: nach Normalisierung zeichengenau identisch.
                          
                          **3. `states.jsonl` ist die einzige Stelle, die etwas über die tatsächliche Nutzung sagt.**
                          Alles andere im Backup sagt nur, dass ein Datenpunkt *existiert*. In der `states.jsonl`
                          steht dagegen pro Wert der Zeitpunkt der letzten echten Wertänderung (`lc`), wer geschrieben
                          hat (`from`), der Qualitätscode und das Ack-Flag. Der Wert selbst (`val`) ist unbrauchbar
                          und sollte gar nicht erst geladen werden — bei mir steckt in einem Datenpunkt ein komplettes
                          Kamera-JPEG als String.
                          
                          Das hat den größten praktischen Effekt gehabt: Meine Verwaisten-Analyse meldete 18
                          Kandidaten. Mit den Zeitstempeln bleibt davon genau **einer** als wirklich toter Datenpunkt
                          übrig — die anderen 17 hatten sich am Backup-Tag noch geändert. Es waren Datenpunkte, deren
                          IDs ein Skript zur Laufzeit zusammensetzt, weshalb keine Textsuche der Welt sie findet. Wer
                          also anhand von „kommt in keinem Skript vor" aufräumt, löscht munter lebende Datenpunkte.
                          
                          **4. ioBroker-IDs sind case-sensitiv — und das ist praxisrelevant.** In meinem Bestand gibt
                          es drei ID-Paare, die sich ausschließlich in der Groß-/Kleinschreibung unterscheiden; für
                          ioBroker sind das verschiedene Objekte, in Wahrheit sind es Tippfehler-Dubletten.
                          
                          Deutlicher noch bei den Werte-Leichen: Der alexa2-Adapter hat irgendwann von
                          `echo-devices` auf `Echo-Devices` umgestellt. In meiner States-Datenbank liegen dadurch bis
                          heute 110 Werte unter der alten Schreibweise — Objekte gibt es dazu null, Werte aber
                          weiterhin, mit Zeitstempeln von 2018. Insgesamt sind bei mir **994 Werte ohne zugehöriges
                          Objekt** in der Datenbank, überwiegend von Adaptern, die ich längst nicht mehr betreibe.
                          Im Admin sieht man davon nichts, weil dort der Objektbaum angezeigt wird — und ein Objekt
                          existiert ja gerade nicht.
                          
                          **5. Jedes Voll-Backup trägt eine eindeutige Installations-ID.** In `system.meta.uuid`
                          steht eine UUID, die bei der Erstinstallation vergeben wird — meine stammt von 2018 und ist
                          seither unverändert. Damit lässt sich hart prüfen, ob zwei Backups überhaupt zusammengehören.
                          Der Hostname taugt dafür nur bedingt: Backitup ersetzt ihn im Backup durch
                          `$$__hostname__$$`; der echte Name steckt dann nur noch im `from`-Feld des Host-Objekts.
                          
                          **6. VIS-Views liegen als ganz normales JSON im Voll-Backup**
                          (`files/vis*/main/vis-views.json`). Die Datenpunkte stecken in den `data`-Objekten der
                          Widgets, und zwar in jedem Feld, dessen Name `oid` enthält, plus in Bindings der Form
                          `{id;format}` in beliebigen Textfeldern. Wichtig dabei: Ein Binding kann statt des Werts ein
                          Zustandsattribut lesen — `{irgendeine.id.ts;date(hh:mm)}` greift auf den Zeitstempel von
                          `irgendeine.id` zu. Wer das `.ts` naiv als Teil der ID nimmt, meldet einen nicht
                          existierenden Datenpunkt.
                          
                          ---
                          
                          ## Kennzahlen aus der Praxis
                          
                          Damit man eine Vorstellung von der Größenordnung bekommt — mein System:
                          16.689 Objekte, 14.603 States, 64 Adapter-Instanzen, 193 Skripte (159 Blockly, 34
                          JavaScript). Ein Voll-Backup mit 17,7 MB gepackt ist in gut einer Sekunde vollständig
                          eingelesen und ausgewertet.
                          
                          ---
                          
                          ## Grenzen, ganz offen
                          
                          - Die Verwaisten-Analysen sind **Prüflisten, keine Löschlisten**. Nutzung durch externe
                           Systeme (MQTT-Clients, Grafana, Apps) ist in einem Backup grundsätzlich nicht erkennbar,
                           und zur Laufzeit zusammengesetzte IDs nur eingeschränkt.
                          - Redis-, InfluxDB- und Zigbee-Teilbackups werden nicht ausgewertet.
                          - Adapter-Konfigurationen (`native`) werden bewusst nirgends angezeigt — da stehen
                           Zugangsdaten drin.
                          - Getestet ist es bisher gegen meine eigenen Backups. Andere Backitup-Versionen und
                           ältere Formate sind zwar berücksichtigt, aber nicht mit echten Daten geprüft.
                          
                          ---
                          
                          ## Technisches
                          
                          C# / .NET 8, WinForms, keine Fremdbibliotheken. Läuft als portable Anwendung ohne
                          installiertes .NET.
                          
                          

                          Drei Hinweise, bevor jemand loslegt

                          Löscht nichts allein aufgrund einer Liste. Das Werkzeug liest nur und kann an eurem
                          System nichts kaputtmachen — gefährlich wird erst, was man mit den Ergebnissen anstellt.
                          Eine Analyse, die sagt „kommt in keinem Skript und keiner View vor", beweist nicht, dass
                          ein Datenpunkt unbenutzt ist. Bei mir waren 17 von 18 solcher Kandidaten quicklebendig,
                          weil ein Skript ihre IDs zur Laufzeit zusammensetzt. Im laufenden System gegenprüfen, und
                          im Zweifel den Datenpunkt erst mal ein paar Wochen beobachten statt löschen.

                          Ein ioBroker-Backup enthält Zugangsdaten. Im Klartext, in den native-Abschnitten der
                          Adapter-Instanzen: MQTT-Passwörter, API-Schlüssel, Cloud-Logins, dazu der Schlüssel, mit dem
                          ioBroker gespeicherte Passwörter verschlüsselt. Schickt euer Backup also nicht zum
                          „schau doch mal drüber" durch die Gegend und ladet es nirgends hoch. Aus demselben Grund
                          lohnt vor einem Screenshot ein Blick darauf, was gerade in der Tabelle steht.

                          Es ist ein privat gebautes Werkzeug, keine geprüfte Software. Ich gebe es ohne
                          Gewährleistung weiter und übernehme keine Haftung für Schäden, die aus seiner Nutzung
                          entstehen. Fehler meldet ihr am besten hier im Thread — mit der Backitup-Version und der
                          Stelle, an der es hakt.

                          Version aktuell: 1.2.4

                          Proxmox 9 HA-Cluster mit 3x HP prodesk 400 G6 i5
                          Bei einem downvote bitte melden, es könnte sein, dass dieser ungewollt ist!

                          1 Antwort Letzte Antwort
                          0
                          • Michael SchmittM Online
                            Michael SchmittM Online
                            Michael Schmitt
                            schrieb zuletzt editiert von
                            #24

                            Nice, muss ich mir gleich nach der Arbeit anschauen. @shadowhunter23 .....

                            1 Antwort Letzte Antwort
                            0

                            Hey! Du scheinst an dieser Unterhaltung interessiert zu sein, hast aber noch kein Konto.

                            Hast du es satt, bei jedem Besuch durch die gleichen Beiträge zu scrollen? Wenn du dich für ein Konto anmeldest, kommst du immer genau dorthin zurück, wo du zuvor warst, und kannst dich über neue Antworten benachrichtigen lassen (entweder per E-Mail oder Push-Benachrichtigung). Du kannst auch Lesezeichen speichern und Beiträge positiv bewerten, um anderen Community-Mitgliedern deine Wertschätzung zu zeigen.

                            Mit deinem Input könnte dieser Beitrag noch besser werden 💗

                            Registrieren Anmelden
                            Antworten
                            • In einem neuen Thema antworten
                            Anmelden zum Antworten
                            • Älteste zuerst
                            • Neuste zuerst
                            • Meiste Stimmen


                            Support us

                            ioBroker
                            Community Adapters
                            Donate

                            469

                            Online

                            33.0k

                            Benutzer

                            83.6k

                            Themen

                            1.3m

                            Beiträge
                            Community
                            Impressum | Datenschutz-Bestimmungen | Nutzungsbedingungen | Einwilligungseinstellungen
                            ioBroker Community 2014-2026
                            logo
                            • Anmelden

                            • Du hast noch kein Konto? Registrieren

                            • Anmelden oder registrieren, um zu suchen
                            • Erster Beitrag
                              Letzter Beitrag
                            0
                            • Home
                            • Aktuell
                            • Tags
                            • Ungelesen 0
                            • Kategorien
                            • Unreplied
                            • Beliebt
                            • GitHub
                            • Docu
                            • Hilfe