From 83df3cc4ef5830cfb4d1e1f58f56b53a01f94493 Mon Sep 17 00:00:00 2001 From: Charles Date: Wed, 4 Mar 2026 08:16:23 +0100 Subject: [PATCH] =?UTF-8?q?README.md=20=E2=80=94=20Documentation=20compl?= =?UTF-8?q?=C3=A8te=20du=20projet=20Refonte=20action=20Write=20:=20modifie?= =?UTF-8?q?r=20les=20XML=20existants=20au=20lieu=20de=20les=20regenerer?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- EnoceanCLI.ps1 | 159 +++++++++++++++++++----- README.md | 269 ++++++++++++++++++++++++++++++++++++++++ modules/ApiClient.psm1 | 54 +++++--- modules/XmlParser.psm1 | 28 ++++- modules/ZipBuilder.psm1 | 3 +- 5 files changed, 467 insertions(+), 46 deletions(-) create mode 100644 README.md diff --git a/EnoceanCLI.ps1 b/EnoceanCLI.ps1 index 67120ed..8d91e6d 100644 --- a/EnoceanCLI.ps1 +++ b/EnoceanCLI.ps1 @@ -1,5 +1,59 @@ -# EnoceanCLI.ps1 - CLI Enocean pour automates Distech Controls Eclypse -# Lecture/ecriture des configurations Enocean via REST API +<# +.SYNOPSIS + CLI Enocean pour automates Distech Controls Eclypse. + Lecture et ecriture des configurations Enocean via REST API. + +.DESCRIPTION + EnoceanCLI permet de gerer les configurations EnOcean sur des automates + Distech Controls Eclypse Gen 1 (ECY-xxx) en utilisant leur API REST. + + Action READ : Lit la configuration EnOcean de chaque automate et genere + un fichier CSV avec les DeviceId et DeviceType de chaque device. + + Action WRITE : Modifie les DeviceId sur chaque automate a partir du CSV. + Seul le DeviceId est modifie, toute la configuration existante + (Points, MaxReceiveTime, Description...) est preservee. + +.PARAMETER Action + Action a effectuer : + Read - Lire la configuration et generer un CSV de sortie + Write - Ecrire les DeviceId depuis le CSV vers les automates + +.PARAMETER CsvInput + Chemin vers le fichier CSV d'entree (separateur point-virgule). + Le CSV doit contenir au minimum : Hostname, Current Ip, HttpPort, HttpsPort. + Pour l'action Write, il doit aussi contenir les colonnes DeviceId_1, DeviceId_2, etc. + +.PARAMETER Username + Nom d'utilisateur pour l'authentification API (defaut: admin). + Peut etre surcharge par la colonne Username du CSV. + +.PARAMETER Password + Mot de passe pour l'authentification API (defaut: vide). + Peut etre surcharge par la colonne Password du CSV. + +.EXAMPLE + .\EnoceanCLI.ps1 -Action Read -CsvInput ".\automates.csv" + + Lit la configuration EnOcean de tous les automates listes dans le CSV. + Genere un fichier enocean_YYYY-MM-DD_HHhMM.csv dans le repertoire courant. + +.EXAMPLE + .\EnoceanCLI.ps1 -Action Write -CsvInput ".\enocean_2026-03-03_23h07.csv" -Password "MonMotDePasse" + + Ecrit les DeviceId du CSV vers les automates. Les configurations existantes + (Points, MaxReceiveTime, etc.) sont preservees. + +.EXAMPLE + Get-Help .\EnoceanCLI.ps1 -Detailed + + Affiche cette aide detaillee. + +.NOTES + Prerequis : PowerShell 5.1+ (inclus dans Windows 10/11) + API : Distech Controls Eclypse REST API v1 + Securite : TLS 1.2, certificats auto-signes acceptes +#> param( [Parameter(Mandatory)] @@ -143,47 +197,96 @@ elseif ($Action -eq "Write") { Write-Log -Message "[$hostname] $baseUrl$apiBasePath (user: $($creds.Username))" -Level INFO - # Lire les colonnes dynamiques DeviceId_N / DeviceType_N - $xmlFiles = @() - $resourceIndex = 1 + # GET liste des devices Enocean existants sur l'automate + $jsonContent = Invoke-EnoceanGet ` + -BaseUrl $baseUrl ` + -ApiBasePath $apiBasePath ` + -ResourcePath "files/enocean/configuration/devices" ` + -Username $creds.Username ` + -Password $creds.Password - while ($true) { - $deviceIdCol = "DeviceId_$resourceIndex" - $deviceTypeCol = "DeviceType_$resourceIndex" + $deviceFiles = Get-DeviceListFromJson -JsonContent $jsonContent - # Verifier si les colonnes existent - $props = $automate.PSObject.Properties.Name - if ($deviceIdCol -notin $props -or $deviceTypeCol -notin $props) { - break + if ($deviceFiles.Count -eq 0) { + Write-Log -Message "[$hostname] Aucun device Enocean existant sur l'automate - ignore" -Level WARN + continue + } + + Write-Log -Message "[$hostname] $($deviceFiles.Count) device(s) existant(s) sur l'automate" -Level INFO + + # GET chaque XML existant et parser pour obtenir ResourceNumber + $existingDevices = @() + foreach ($deviceFile in $deviceFiles) { + try { + $xmlContent = Invoke-EnoceanGet ` + -BaseUrl $baseUrl ` + -ApiBasePath $apiBasePath ` + -ResourcePath "files/enocean/configuration/devices/$($deviceFile.Name)?encode=bin" ` + -Username $creds.Username ` + -Password $creds.Password + + $parsed = Parse-EnoceanDeviceXml -XmlContent $xmlContent + $existingDevices += @{ + Name = $deviceFile.Name + XmlContent = $xmlContent + ResourceNumber = $parsed.ResourceNumber + DeviceType = $parsed.DeviceType + DeviceId = $parsed.DeviceId + } } + catch { + Write-Log -Message "[$hostname] Erreur lecture $($deviceFile.Name) : $($_.Exception.Message)" -Level ERROR + } + } - $deviceId = $automate.$deviceIdCol - $deviceType = $automate.$deviceTypeCol + # Trier par ResourceNumber + $existingDevices = @($existingDevices | Sort-Object { $_.ResourceNumber }) - # Ignorer si vide - if (-not $deviceId -or $deviceId -eq "" -or -not $deviceType -or $deviceType -eq "") { - $resourceIndex++ + # Associer par index sequentiel : device[0] -> DeviceId_1, device[1] -> DeviceId_2, etc. + $xmlFiles = @() + for ($i = 0; $i -lt $existingDevices.Count; $i++) { + $seqIndex = $i + 1 + $deviceIdCol = "DeviceId_$seqIndex" + $deviceTypeCol = "DeviceType_$seqIndex" + $device = $existingDevices[$i] + + # Verifier si la colonne DeviceId_N existe dans le CSV + $props = $automate.PSObject.Properties.Name + if ($deviceIdCol -notin $props) { + Write-Log -Message "[$hostname] Colonne $deviceIdCol absente du CSV - device $($device.Name) non modifie" -Level WARN continue } - $xmlContent = New-EnoceanDeviceXml ` - -ResourceNumber $resourceIndex ` - -DeviceId $deviceId ` - -DeviceType $deviceType + $newDeviceId = $automate.$deviceIdCol + + # Ignorer si DeviceId vide + if (-not $newDeviceId -or $newDeviceId -eq "") { + $seqIndex++ + continue + } + + # Verifier DeviceType CSV vs XML si la colonne existe + if ($deviceTypeCol -in $props) { + $csvDeviceType = $automate.$deviceTypeCol + if ($csvDeviceType -and $csvDeviceType -ne "" -and $csvDeviceType -ne $device.DeviceType) { + Write-Log -Message "[$hostname] Device $($device.Name) : DeviceType CSV ($csvDeviceType) differe du XML ($($device.DeviceType))" -Level WARN + } + } + + # Modifier uniquement le DeviceId dans le XML existant + $result = Update-EnoceanDeviceId -XmlContent $device.XmlContent -NewDeviceId $newDeviceId $xmlFiles += @{ - Name = "enoceandevice$resourceIndex.xml" - Content = $xmlContent + Name = $device.Name + Content = $result.ModifiedXml } Update-Stats -Counter DevicesProcessed - Write-Log -Message "[$hostname] XML genere : device $resourceIndex (Id=$deviceId, Type=$deviceType)" -Level INFO - - $resourceIndex++ + Write-Log -Message "[$hostname] Device $($device.Name) : $($result.OldDeviceId) -> $newDeviceId" -Level INFO } if ($xmlFiles.Count -eq 0) { - Write-Log -Message "[$hostname] Aucun device a ecrire - ignore" -Level WARN + Write-Log -Message "[$hostname] Aucun device a modifier - ignore" -Level WARN continue } @@ -191,7 +294,7 @@ elseif ($Action -eq "Write") { $zipBytes = New-EnoceanZip -XmlFiles $xmlFiles $zipFilename = Get-ZipFilename -ZipBytes $zipBytes - Write-Log -Message "[$hostname] Envoi de $($xmlFiles.Count) device(s) ($zipFilename)..." -Level INFO + Write-Log -Message "[$hostname] Envoi de $($xmlFiles.Count) device(s) modifie(s) ($zipFilename)..." -Level INFO Send-EnoceanConfig ` -BaseUrl $baseUrl ` diff --git a/README.md b/README.md new file mode 100644 index 0000000..2643bfb --- /dev/null +++ b/README.md @@ -0,0 +1,269 @@ +# EnoceanCLI + +Outil en ligne de commande pour lire et écrire les configurations EnOcean sur les automates **Distech Controls Eclypse Gen 1** via leur API REST. + +## Ce que fait cet outil + +- **Read** : Se connecte à chaque automate listé dans un CSV, récupère la configuration EnOcean (DeviceId, DeviceType de chaque capteur), et génère un fichier CSV de sortie. +- **Write** : Prend un CSV contenant les DeviceId souhaités et les applique sur chaque automate. **Seul le DeviceId est modifié** — toute la configuration existante sur l'automate (Points, MaxReceiveTime, Description, etc.) est préservée. + +--- + +## Prérequis + +- **Windows 10 ou 11** (PowerShell 5.1 est inclus par défaut, rien à installer) +- Un accès réseau aux automates Eclypse (HTTP ou HTTPS) +- Les identifiants de connexion aux automates (par défaut : `admin` / mot de passe vide) + +### Vérifier que PowerShell est disponible + +Ouvrir un terminal (touche `Windows` + `R`, taper `powershell`, puis `Entrée`) et taper : + +```powershell +$PSVersionTable.PSVersion +``` + +Le numéro `Major` doit être **5 ou plus**. + +--- + +## Installation + +Aucune installation requise. Télécharger ou cloner le dossier du projet : + +``` +gfxEnocean/ +├── EnoceanCLI.ps1 <- Script principal +├── modules/ <- Modules (ne pas modifier) +│ ├── Logger.psm1 +│ ├── CsvHandler.psm1 +│ ├── ApiClient.psm1 +│ ├── XmlParser.psm1 +│ └── ZipBuilder.psm1 +└── README.md +``` + +--- + +## Utilisation + +### Ouvrir PowerShell dans le bon dossier + +1. Ouvrir l'explorateur de fichiers et naviguer dans le dossier `gfxEnocean` +2. Cliquer dans la barre d'adresse, taper `powershell`, puis appuyer sur `Entrée` + +Ou bien dans un terminal PowerShell : + +```powershell +cd "C:\chemin\vers\gfxEnocean" +``` + +### Politique d'exécution + +Si c'est la première fois, PowerShell peut bloquer l'exécution des scripts. Autoriser pour la session en cours : + +```powershell +Set-ExecutionPolicy -Scope Process -ExecutionPolicy Bypass +``` + +Cette commande est sans risque : elle n'autorise les scripts que pour la fenêtre PowerShell en cours. + +--- + +### Action Read — Lire la configuration des automates + +```powershell +.\EnoceanCLI.ps1 -Action Read -CsvInput ".\mon_fichier.csv" +``` + +Avec un mot de passe : + +```powershell +.\EnoceanCLI.ps1 -Action Read -CsvInput ".\mon_fichier.csv" -Password "MonMotDePasse" +``` + +**Résultat** : Un fichier `enocean_YYYY-MM-DD_HHhMM.csv` est créé dans le dossier courant avec les DeviceId et DeviceType de chaque capteur EnOcean. + +--- + +### Action Write — Écrire les DeviceId sur les automates + +```powershell +.\EnoceanCLI.ps1 -Action Write -CsvInput ".\enocean_2026-03-03_23h07.csv" -Password "MonMotDePasse" +``` + +**Ce qui se passe** : +1. Le script se connecte à chaque automate +2. Il récupère la configuration EnOcean existante (XML complet avec Points, etc.) +3. Il remplace **uniquement le DeviceId** par la valeur du CSV +4. Il renvoie la configuration modifiée à l'automate + +**Ce qui est préservé** : Points, MaxReceiveTime, Description, Name, ResourceNumber, DeviceType — tout sauf le DeviceId. + +--- + +### Aide intégrée + +```powershell +Get-Help .\EnoceanCLI.ps1 -Detailed +``` + +--- + +## Format du CSV d'entrée + +Le fichier CSV utilise le **point-virgule** (`;`) comme séparateur. + +### Colonnes obligatoires + +| Colonne | Description | +|---------|-------------| +| `Hostname` | Nom de l'automate | +| `Current Ip` | Adresse IP de l'automate | +| `HttpPort` | Port HTTP (`80` ou `-1` si désactivé) | +| `HttpsPort` | Port HTTPS (`443` ou `-1` si désactivé) | + +### Colonnes optionnelles + +| Colonne | Description | +|---------|-------------| +| `RestServiceURL` | Chemin API (défaut : `/api/rest/v1/`) | +| `Username` | Login spécifique à cet automate | +| `Password` | Mot de passe spécifique à cet automate | + +### Colonnes pour l'action Write + +| Colonne | Description | +|---------|-------------| +| `DeviceId_1` | DeviceId à écrire sur le 1er capteur EnOcean | +| `DeviceType_1` | DeviceType du 1er capteur (pour vérification) | +| `DeviceId_2` | DeviceId à écrire sur le 2e capteur | +| `DeviceType_2` | DeviceType du 2e capteur | +| ... | Autant de paires que de capteurs | + +### Exemple de CSV + +```csv +"Hostname";"Current Ip";"HttpPort";"HttpsPort";"RestServiceURL";"DeviceId_1";"DeviceType_1";"DeviceId_2";"DeviceType_2" +"MON-AUTOMATE-01";"192.168.1.11";"-1";"443";"/api/rest/v1/";"99864513";"A50401";"65313272";"A5100C" +"MON-AUTOMATE-02";"192.168.1.12";"80";"-1";"/api/rest/v1/";"45678912";"A53001";"";"" +``` + +**Note** : Les colonnes DeviceId/DeviceType vides sont ignorées — le capteur correspondant n'est pas modifié. + +--- + +## Workflow typique + +### 1. Préparer le CSV d'entrée + +Partir d'un export existant (XNU Export, inventaire réseau...) contenant au minimum les colonnes `Hostname`, `Current Ip`, `HttpPort`, `HttpsPort`. + +### 2. Lire la configuration actuelle + +```powershell +.\EnoceanCLI.ps1 -Action Read -CsvInput ".\automates.csv" -Password "MonMotDePasse" +``` + +Cela génère un CSV avec les DeviceId actuels de chaque automate. + +### 3. Modifier les DeviceId dans le CSV + +Ouvrir le CSV généré dans Excel ou un éditeur de texte. Modifier les colonnes `DeviceId_1`, `DeviceId_2`, etc. avec les nouvelles valeurs. + +**Attention** : Ne pas modifier les colonnes `DeviceType_N`. Elles servent uniquement de référence. Si le DeviceType du CSV diffère de celui de l'automate, un avertissement sera affiché dans les logs. + +### 4. Écrire la nouvelle configuration + +```powershell +.\EnoceanCLI.ps1 -Action Write -CsvInput ".\enocean_modifie.csv" -Password "MonMotDePasse" +``` + +### 5. Vérifier + +Relancer un Read pour confirmer que les DeviceId ont bien changé : + +```powershell +.\EnoceanCLI.ps1 -Action Read -CsvInput ".\automates.csv" -Password "MonMotDePasse" +``` + +--- + +## Logs + +Chaque exécution génère un fichier log dans le dossier courant : + +``` +enocean_2026-03-04_14h30.log +``` + +### Niveaux de log + +| Niveau | Couleur console | Signification | +|--------|----------------|---------------| +| `INFO` | Cyan | Opération normale | +| `SUCCESS` | Vert | Opération réussie | +| `WARN` | Jaune | Avertissement (DeviceType différent, colonne manquante...) | +| `ERROR` | Rouge | Erreur (connexion échouée, automate injoignable...) | + +### Exemple de log (action Write) + +``` +[2026-03-04 00:17:43] [INFO] === EnoceanCLI demarre - Action: Write === +[2026-03-04 00:17:43] [INFO] [MON-AUTOMATE-01] https://10.60.105.42/api/rest/v1 (user: admin) +[2026-03-04 00:17:43] [INFO] [MON-AUTOMATE-01] 3 device(s) existant(s) sur l'automate +[2026-03-04 00:17:43] [INFO] [MON-AUTOMATE-01] Device enoceandevice1.xml : 12345678 -> 99864513 +[2026-03-04 00:17:43] [INFO] [MON-AUTOMATE-01] Device enoceandevice2.xml : 87654321 -> 65313272 +[2026-03-04 00:17:44] [WARN] [MON-AUTOMATE-01] Device enoceandevice3.xml : DeviceType CSV (D50001) differe du XML (A50401) +[2026-03-04 00:17:44] [INFO] [MON-AUTOMATE-01] Device enoceandevice3.xml : 11112222 -> 32602064 +[2026-03-04 00:17:44] [SUCCESS] [MON-AUTOMATE-01] Configuration envoyee avec succes +[2026-03-04 00:17:44] [INFO] ========== RESUME ========== +[2026-03-04 00:17:44] [INFO] Automates traites : 1 +[2026-03-04 00:17:44] [INFO] Automates en erreur : 0 +[2026-03-04 00:17:44] [INFO] Devices traites : 3 +[2026-03-04 00:17:44] [INFO] Duree totale : 00:00:01.23 +[2026-03-04 00:17:44] [INFO] ============================ +``` + +--- + +## Résumé des paramètres + +``` +.\EnoceanCLI.ps1 -Action -CsvInput [-Username ] [-Password ] +``` + +| Paramètre | Obligatoire | Défaut | Description | +|-----------|:-----------:|--------|-------------| +| `-Action` | Oui | — | `Read` ou `Write` | +| `-CsvInput` | Oui | — | Chemin du fichier CSV d'entrée | +| `-Username` | Non | `admin` | Login API (surchargeable par le CSV) | +| `-Password` | Non | *(vide)* | Mot de passe API (surchargeable par le CSV) | + +--- + +## Dépannage + +### "Impossible d'exécuter le script car l'exécution de scripts est désactivée" + +```powershell +Set-ExecutionPolicy -Scope Process -ExecutionPolicy Bypass +``` + +### "Aucun port HTTP/HTTPS valide" + +Vérifier que les colonnes `HttpPort` et `HttpsPort` du CSV contiennent des valeurs valides. Un port à `-1` signifie "désactivé". Au moins un des deux doit être actif. + +### "Aucun device Enocean existant sur l'automate" + +L'automate n'a pas de capteurs EnOcean configurés. L'action Write ne peut pas modifier des devices qui n'existent pas encore — ils doivent d'abord être créés via le logiciel EC-gfxProgram. + +### Timeout ou erreur de connexion + +- Vérifier que l'automate est joignable (`ping 10.60.x.x`) +- Vérifier les identifiants (Username / Password) +- Vérifier que le port est correct (HTTP 80 ou HTTPS 443) + +### Le WARN "DeviceType CSV diffère du XML" + +C'est un avertissement informatif. Le DeviceId sera quand même appliqué. Ce message signifie que le type de capteur indiqué dans le CSV ne correspond pas à celui configuré sur l'automate. Vérifier que le bon DeviceId est associé au bon capteur. diff --git a/modules/ApiClient.psm1 b/modules/ApiClient.psm1 index 705ac84..08bb897 100644 --- a/modules/ApiClient.psm1 +++ b/modules/ApiClient.psm1 @@ -75,9 +75,9 @@ function Invoke-EnoceanGet { $response = Invoke-WebRequest -Uri $url -Method GET -Headers $headers -UseBasicParsing -TimeoutSec 30 - # Si la reponse est un byte[] (encode=bin), decoder en string UTF-8 + # Si la reponse est un byte[] (encode=bin), decoder en string UTF-8 sans BOM if ($response.Content -is [byte[]]) { - return [System.Text.Encoding]::UTF8.GetString($response.Content) + return [System.Text.Encoding]::UTF8.GetString($response.Content).TrimStart([char]0xFEFF) } return $response.Content } @@ -105,28 +105,21 @@ function Send-EnoceanConfig { [string]$Password ) - $headers = Get-AuthHeader -Username $Username -Password $Password + $authHeaders = Get-AuthHeader -Username $Username -Password $Password $url = "$BaseUrl$ApiBasePath/files/bacnet/inputConfiguration" Write-Log -Message "POST $url (fichier: $ZipFilename, taille: $($ZipBytes.Length) octets)" -Level INFO - # Construction multipart manuelle (compatible PS 5.1, pas de -Form) + # Construction multipart manuelle $boundary = [System.Guid]::NewGuid().ToString("N") - $headers["Content-Type"] = "multipart/form-data; boundary=$boundary" - $encoding = [System.Text.Encoding]::ASCII # Partie avant le fichier - $headerPart = @" ---$boundary -Content-Disposition: form-data; name="File"; filename="$ZipFilename" -Content-Type: application/octet-stream - -"@ + $headerPart = "--$boundary`r`nContent-Disposition: form-data; name=`"File`"; filename=`"$ZipFilename`"`r`nContent-Type: application/octet-stream`r`n`r`n" # Partie finale $footerPart = "`r`n--$boundary--`r`n" - $headerBytes = $encoding.GetBytes($headerPart.Replace("`n", "`r`n")) + $headerBytes = $encoding.GetBytes($headerPart) $footerBytes = $encoding.GetBytes($footerPart) # Assembler le body complet en byte[] @@ -137,10 +130,39 @@ Content-Type: application/octet-stream $bodyBytes = $bodyStream.ToArray() $bodyStream.Close() - $response = Invoke-WebRequest -Uri $url -Method POST -Headers $headers -Body $bodyBytes -UseBasicParsing -TimeoutSec 60 + # Utiliser HttpWebRequest directement pour envoyer les bytes bruts sans troncature + $request = [System.Net.HttpWebRequest]::Create($url) + $request.Method = "POST" + $request.ContentType = "multipart/form-data; boundary=$boundary" + $request.ContentLength = $bodyBytes.Length + $request.Timeout = 60000 + $request.UserAgent = $authHeaders["User-Agent"] + $request.Accept = $authHeaders["Accept"] + $request.Headers.Add("Authorization", $authHeaders["Authorization"]) - Write-Log -Message "POST reponse : $($response.StatusCode)" -Level SUCCESS - return $response + try { + $reqStream = $request.GetRequestStream() + $reqStream.Write($bodyBytes, 0, $bodyBytes.Length) + $reqStream.Close() + + $response = $request.GetResponse() + $statusCode = [int]$response.StatusCode + $response.Close() + + Write-Log -Message "POST reponse : $statusCode" -Level SUCCESS + } + catch [System.Net.WebException] { + $responseBody = "" + if ($_.Exception.Response) { + $stream = $_.Exception.Response.GetResponseStream() + $reader = New-Object System.IO.StreamReader($stream) + $responseBody = $reader.ReadToEnd() + $reader.Close() + $stream.Close() + } + Write-Log -Message "POST erreur $($_.Exception.Message) - Reponse serveur: $responseBody" -Level ERROR + throw + } } Export-ModuleMember -Function Initialize-ApiClient, Get-AuthHeader, Invoke-EnoceanGet, Send-EnoceanConfig diff --git a/modules/XmlParser.psm1 b/modules/XmlParser.psm1 index 0f2894f..6841279 100644 --- a/modules/XmlParser.psm1 +++ b/modules/XmlParser.psm1 @@ -71,4 +71,30 @@ function New-EnoceanDeviceXml { return $xml } -Export-ModuleMember -Function Get-DeviceListFromJson, Parse-EnoceanDeviceXml, New-EnoceanDeviceXml +function Update-EnoceanDeviceId { + [CmdletBinding()] + param( + [Parameter(Mandatory)] + [string]$XmlContent, + + [Parameter(Mandatory)] + [string]$NewDeviceId + ) + + # Parser pour extraire les valeurs actuelles + [xml]$xml = $XmlContent + $config = $xml.EnOceanDevice.Configuration + $oldDeviceId = $config.DeviceId + $deviceType = $config.DeviceType + + # Remplacement par regex sur le XML brut (preserve l'encodage et le formatage original) + $modifiedXml = $XmlContent -replace "[^<]*", "$NewDeviceId" + + return @{ + ModifiedXml = $modifiedXml + OldDeviceId = $oldDeviceId + DeviceType = $deviceType + } +} + +Export-ModuleMember -Function Get-DeviceListFromJson, Parse-EnoceanDeviceXml, New-EnoceanDeviceXml, Update-EnoceanDeviceId diff --git a/modules/ZipBuilder.psm1 b/modules/ZipBuilder.psm1 index 88349d3..48275e9 100644 --- a/modules/ZipBuilder.psm1 +++ b/modules/ZipBuilder.psm1 @@ -20,7 +20,8 @@ function New-EnoceanZip { $entry = $archive.CreateEntry($entryPath, [System.IO.Compression.CompressionLevel]::Optimal) $entryStream = $entry.Open() - $writer = New-Object System.IO.StreamWriter($entryStream, [System.Text.Encoding]::UTF8) + $utf8NoBom = New-Object System.Text.UTF8Encoding($false) + $writer = New-Object System.IO.StreamWriter($entryStream, $utf8NoBom) $writer.Write($xmlFile.Content) $writer.Flush() $writer.Close()