README.md — Documentation complète du projet

Refonte action Write : modifier les XML existants au lieu de les regenerer
This commit is contained in:
2026-03-04 08:16:23 +01:00
parent c75c731ddc
commit 83df3cc4ef
5 changed files with 467 additions and 46 deletions

View File

@@ -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( param(
[Parameter(Mandatory)] [Parameter(Mandatory)]
@@ -143,47 +197,96 @@ elseif ($Action -eq "Write") {
Write-Log -Message "[$hostname] $baseUrl$apiBasePath (user: $($creds.Username))" -Level INFO Write-Log -Message "[$hostname] $baseUrl$apiBasePath (user: $($creds.Username))" -Level INFO
# Lire les colonnes dynamiques DeviceId_N / DeviceType_N # GET liste des devices Enocean existants sur l'automate
$xmlFiles = @() $jsonContent = Invoke-EnoceanGet `
$resourceIndex = 1 -BaseUrl $baseUrl `
-ApiBasePath $apiBasePath `
-ResourcePath "files/enocean/configuration/devices" `
-Username $creds.Username `
-Password $creds.Password
while ($true) { $deviceFiles = Get-DeviceListFromJson -JsonContent $jsonContent
$deviceIdCol = "DeviceId_$resourceIndex"
$deviceTypeCol = "DeviceType_$resourceIndex"
# Verifier si les colonnes existent if ($deviceFiles.Count -eq 0) {
$props = $automate.PSObject.Properties.Name Write-Log -Message "[$hostname] Aucun device Enocean existant sur l'automate - ignore" -Level WARN
if ($deviceIdCol -notin $props -or $deviceTypeCol -notin $props) { continue
break }
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 # Trier par ResourceNumber
$deviceType = $automate.$deviceTypeCol $existingDevices = @($existingDevices | Sort-Object { $_.ResourceNumber })
# Ignorer si vide # Associer par index sequentiel : device[0] -> DeviceId_1, device[1] -> DeviceId_2, etc.
if (-not $deviceId -or $deviceId -eq "" -or -not $deviceType -or $deviceType -eq "") { $xmlFiles = @()
$resourceIndex++ 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 continue
} }
$xmlContent = New-EnoceanDeviceXml ` $newDeviceId = $automate.$deviceIdCol
-ResourceNumber $resourceIndex `
-DeviceId $deviceId ` # Ignorer si DeviceId vide
-DeviceType $deviceType 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 += @{ $xmlFiles += @{
Name = "enoceandevice$resourceIndex.xml" Name = $device.Name
Content = $xmlContent Content = $result.ModifiedXml
} }
Update-Stats -Counter DevicesProcessed Update-Stats -Counter DevicesProcessed
Write-Log -Message "[$hostname] XML genere : device $resourceIndex (Id=$deviceId, Type=$deviceType)" -Level INFO Write-Log -Message "[$hostname] Device $($device.Name) : $($result.OldDeviceId) -> $newDeviceId" -Level INFO
$resourceIndex++
} }
if ($xmlFiles.Count -eq 0) { 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 continue
} }
@@ -191,7 +294,7 @@ elseif ($Action -eq "Write") {
$zipBytes = New-EnoceanZip -XmlFiles $xmlFiles $zipBytes = New-EnoceanZip -XmlFiles $xmlFiles
$zipFilename = Get-ZipFilename -ZipBytes $zipBytes $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 ` Send-EnoceanConfig `
-BaseUrl $baseUrl ` -BaseUrl $baseUrl `

269
README.md Normal file
View File

@@ -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 <Read|Write> -CsvInput <chemin> [-Username <login>] [-Password <mdp>]
```
| 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.

View File

@@ -75,9 +75,9 @@ function Invoke-EnoceanGet {
$response = Invoke-WebRequest -Uri $url -Method GET -Headers $headers -UseBasicParsing -TimeoutSec 30 $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[]]) { 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 return $response.Content
} }
@@ -105,28 +105,21 @@ function Send-EnoceanConfig {
[string]$Password [string]$Password
) )
$headers = Get-AuthHeader -Username $Username -Password $Password $authHeaders = Get-AuthHeader -Username $Username -Password $Password
$url = "$BaseUrl$ApiBasePath/files/bacnet/inputConfiguration" $url = "$BaseUrl$ApiBasePath/files/bacnet/inputConfiguration"
Write-Log -Message "POST $url (fichier: $ZipFilename, taille: $($ZipBytes.Length) octets)" -Level INFO 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") $boundary = [System.Guid]::NewGuid().ToString("N")
$headers["Content-Type"] = "multipart/form-data; boundary=$boundary"
$encoding = [System.Text.Encoding]::ASCII $encoding = [System.Text.Encoding]::ASCII
# Partie avant le fichier # Partie avant le fichier
$headerPart = @" $headerPart = "--$boundary`r`nContent-Disposition: form-data; name=`"File`"; filename=`"$ZipFilename`"`r`nContent-Type: application/octet-stream`r`n`r`n"
--$boundary
Content-Disposition: form-data; name="File"; filename="$ZipFilename"
Content-Type: application/octet-stream
"@
# Partie finale # Partie finale
$footerPart = "`r`n--$boundary--`r`n" $footerPart = "`r`n--$boundary--`r`n"
$headerBytes = $encoding.GetBytes($headerPart.Replace("`n", "`r`n")) $headerBytes = $encoding.GetBytes($headerPart)
$footerBytes = $encoding.GetBytes($footerPart) $footerBytes = $encoding.GetBytes($footerPart)
# Assembler le body complet en byte[] # Assembler le body complet en byte[]
@@ -137,10 +130,39 @@ Content-Type: application/octet-stream
$bodyBytes = $bodyStream.ToArray() $bodyBytes = $bodyStream.ToArray()
$bodyStream.Close() $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 try {
return $response $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 Export-ModuleMember -Function Initialize-ApiClient, Get-AuthHeader, Invoke-EnoceanGet, Send-EnoceanConfig

View File

@@ -71,4 +71,30 @@ function New-EnoceanDeviceXml {
return $xml 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 "<DeviceId>[^<]*</DeviceId>", "<DeviceId>$NewDeviceId</DeviceId>"
return @{
ModifiedXml = $modifiedXml
OldDeviceId = $oldDeviceId
DeviceType = $deviceType
}
}
Export-ModuleMember -Function Get-DeviceListFromJson, Parse-EnoceanDeviceXml, New-EnoceanDeviceXml, Update-EnoceanDeviceId

View File

@@ -20,7 +20,8 @@ function New-EnoceanZip {
$entry = $archive.CreateEntry($entryPath, [System.IO.Compression.CompressionLevel]::Optimal) $entry = $archive.CreateEntry($entryPath, [System.IO.Compression.CompressionLevel]::Optimal)
$entryStream = $entry.Open() $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.Write($xmlFile.Content)
$writer.Flush() $writer.Flush()
$writer.Close() $writer.Close()