> For the complete documentation index, see [llms.txt](https://docs.realmjoin.com/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.realmjoin.com/de/dev-reference/report-functions/publish-rjrbfilestostoragecontainer.md).

# Publish-RjRbFilesToStorageContainer

## Übersicht

`Publish-RjRbFilesToStorageContainer` ist der Standard-Helfer für das Bereitstellen von Berichtsdateien (CSV, XLSX, ZIP, …) aus RealmJoin-Reporting-Runbooks über Azure Blob Storage. Er lädt eine oder mehrere lokale Dateien in einen Ziel-Container hoch und gibt für jeden Blob einen zeitlich begrenzten SAS-Downloadlink zurück, der sich für die Einbindung in Berichts-E-Mails, Teams-Nachrichten oder Runbook-Ausgaben eignet.

Wichtige Eigenschaften:

* **Kein `Az.Storage` Abhängigkeit** — Blob-Operationen werden direkt über die Azure Storage REST-API ausgeführt (Containererstellung, Upload, SAS Token-Erzeugung). Dadurch entfällt der bekannte Assembly-Konflikt zwischen `Az.Storage` und `ExchangeOnlineManagement` der in gemischten Reporting-Runbooks auftritt.
* **Selbstverbindend** — falls kein `Az` Kontext aktiv ist, ruft die Funktion transparent `Connect-RjRbAzAccount`. Ein optionales `-SubscriptionId` wechselt vor jeder Speicheroperation den Kontext.
* **Container automatisch erstellt** — wenn der Ziel-Container noch nicht existiert, wird er im laufenden Betrieb erstellt; ein vorhandener Container (HTTP 409) wird als Erfolg gewertet.
* **Uploads basierend auf HttpClient** — verwendet `System.Net.Http.HttpClient` direkt, weil Azure Automations `Invoke-RestMethod` Interceptor erforderliche benutzerdefinierte Header (`x-ms-blob-type`) mit Binärdaten entfernt.
* **Schreibgeschützte SAS-Links** — jede zurückgegebene URL ist mit dem Storage Account-Schlüssel signiert, auf einen einzelnen Blob beschränkt, nur über HTTPS erreichbar und gültig für `LinkExpiryDays` Tage (Standard 6).

Die zentralen Speichereinstellungen (Ressourcengruppe, Kontoname, Ablaufzeit in Tagen, Blob-Namenspräfix), die von einem typischen Runbook verwendet werden, liegen im RealmJoin-Anpassungs-JSON und sind dokumentiert in [Runbook-Report-Einstellungen — Storage Account-Bereitstellung](/de/automatisierung/runbooks/runbook-report-settings.md#storage-account-delivery). Dieses Dokument konzentriert sich auf den Aufruf der Funktion aus einem Runbook.

## Voraussetzungen

### Azure Storage Account

Ein vorhandenes Azure Storage Account (empfohlen: General Purpose v2) ist erforderlich. Der Ziel-Container muss vorher nicht existieren — er wird bei der ersten Verwendung automatisch erstellt.

### Azure RBAC am Storage Account

Die verwaltete Identität des Automation Accounts (oder der vom Runbook verwendete Service Principal) benötigt die folgenden Berechtigungen für das Storage Account oder dessen Ressourcengruppe:

| Aktion                                              | Erforderlich für                                                                           |
| --------------------------------------------------- | ------------------------------------------------------------------------------------------ |
| `Microsoft.Storage/storageAccounts/read`            | Lesen des Storage Account                                                                  |
| `Microsoft.Storage/storageAccounts/listKeys/action` | Abrufen des Kontoschlüssels, der für SharedKey-Signatur und SAS-Generierung verwendet wird |

Die integrierte Rolle **Storage Account Contributor** deckt beides ab. **Storage Blob Data Contributor** allein ist *nicht* ausreichend, da die Funktion Anfragen mit dem Kontoschlüssel signiert, anstatt AAD-gestützte Blob-Operationen zu verwenden.

### Modulkonnektivität

Die Funktion benötigt das `Az.Accounts` Modul in der Runbook-Umgebung (`Get-AzContext`, `Set-AzContext`, `Connect-AzAccount`, `Invoke-AzRestMethod`). Deklarieren Sie es explizit im konsumierenden Runbook:

```powershell
#Requires -Modules @{ModuleName = "RealmJoin.RunbookHelper"; ModuleVersion = "0.8.6" }
#Requires -Modules @{ModuleName = "Az.Accounts"; ModuleVersion = "5.3.4" }
```

Wenn `Az.Accounts` zur Laufzeit nicht verfügbar ist, schlägt die Funktion mit einer klaren Fehlermeldung schnell fehl — sie prüft im Vorfeld auf `Get-AzContext` und wirft *"Publish-RjRbFilesToStorageContainer requires the 'Az.Accounts' module. Add #Requires -Modules @{ModuleName = 'Az.Accounts'; ModuleVersion = '5.3.4'} to the calling runbook."* bevor irgendein Azure-Aufruf ausgeführt wird.

> **Warum ist `Az.Accounts` als `RequiredModules` Eintrag in `RealmJoin.RunbookHelper.psd1`?**
>
> `Az.Accounts` absichtlich nur unter `ExternalModuleDependencies` (informativ) und *nicht* unter `RequiredModules` (erzwungen beim `Import-Module` Zeitpunkt):
>
> * **Zahlen Sie nur für das, was Sie nutzen.** Viele Runbooks verwenden nur Graph-basierte Helfer (z. B. `Send-RjRbReportEmail` ohne `-UseNativeGraphRequest`, oder `Invoke-RjRbRestMethodGraph`) und berühren nie ein Az.\*-Cmdlet. Das Hochstufen von `Az.Accounts` zu `RequiredModules` würde jedes konsumierende Runbook dazu zwingen, das Modul mitzuliefern, selbst wenn nichts im Codepfad es benötigt — was die Kaltstartzeit in Azure Automation messbar erhöht.
> * **Versionstreitigkeiten vermeiden.** Eine harte `RequiredModules` Bedingung löst beim Import automatisch eine Auflösung aus und kann eine bestimmte `Az.Accounts` Version nachziehen, die mit dem kollidiert, was das Runbook selbst fest pinnt (Az.\*-Untermodule sind bekanntermaßen versionssensitiv). Indem das Runbook seine eigenen `#Requires -Modules` Versionswahl beim Aufrufer belassen.
> * **Autorität pro Runbook.** In Azure Automation ist der kanonische Ort zum Deklarieren von Modulanforderungen auf Runbook-Ebene über `#Requires`nicht auf Ebene des Hilfsmoduls. Das Hilfsmodul macht die Abhängigkeit informativ sichtbar (über `ExternalModuleDependencies` im Manifest) und über die Laufzeitprüfung oben, sodass eine Fehlkonfiguration lautstark mit einer handlungsorientierten Meldung statt mit einem stillen Verschleiern eines Versionskonflikts fehlschlägt.

`Az.Storage` ist **nicht** erforderlich und sollte nicht im selben Runbook importiert werden, um den oben erwähnten Assembly-Konflikt zu vermeiden.

## Schnellstart

Die minimal lauffähige Aufrufvariante erfordert die lokalen Dateipfade, den Containernamen, die Ressourcengruppe und den Namen des Storage Account:

```powershell
$csvPath = Join-Path $env:TEMP 'devices.csv'
$exportData | Export-Csv -Path $csvPath -NoTypeInformation -Encoding UTF8

$results = Publish-RjRbFilesToStorageContainer `
    -FilePaths          $csvPath `
    -ContainerName      'reports' `
    -ResourceGroupName  'rg-reports' `
    -StorageAccountName 'stcontosoreports'

$results | Format-Table BlobName, EndTime, SASLink
```

Dies lädt `devices.csv` in den `reports` Container in `stcontosoreports` hoch und gibt ein Objekt mit dem Blob-Namen, dem SAS-Ablaufzeitpunkt und einer sofort nutzbaren Download-URL zurück, die standardmäßig 6 Tage gültig ist.

## Parameter

### Erforderlich

| Parameter            | Typ        | Beschreibung                                                                                                                                                                                                                                                                                                          |
| -------------------- | ---------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `FilePaths`          | `string[]` | Ein oder mehrere lokale Dateipfade zum Hochladen. Jeder Pfad muss auf eine vorhandene Datei zeigen (`Test-Path -PathType Leaf`); die Funktion wirft im Vorfeld, wenn ein Eintrag fehlt.                                                                                                                               |
| `ContainerName`      | `string`   | Ziel-Blob-Container. Wird automatisch erstellt, wenn er nicht existiert. Muss den Azure-Container-Namensregeln entsprechen (kleinbuchstaben, 3–63 Zeichen, alphanumerisch + Bindestrich). Der Containername ist eine *pro Runbook* Entscheidung und wird im Runbook festgelegt, nicht in den zentralen Einstellungen. |
| `ResourceGroupName`  | `string`   | Ressourcengruppe, die das Storage Account enthält. Normalerweise über die zentrale Einstellung verdrahtet `RJReport.StorageAccount.ResourceGroup`.                                                                                                                                                                    |
| `StorageAccountName` | `string`   | Name des Azure Storage Account. Normalerweise über die zentrale Einstellung verdrahtet `RJReport.StorageAccount.StorageAccountName`.                                                                                                                                                                                  |

### Optional

| Parameter           | Typ      | Standard          | Beschreibung                                                                                                                                                                                                                                      |
| ------------------- | -------- | ----------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `SubscriptionId`    | `string` | aktueller Kontext | Azure-Abonnement, das das Storage Account hostet. Falls angegeben, `Set-AzContext -Subscription` wird vor jeder Speicheroperation aufgerufen. Weglassen, um den aktuellen `Az` Kontext zu verwenden.                                              |
| `LinkExpiryDays`    | `int`    | `6`               | Gültigkeit des SAS-Links in Tagen. Validiert auf `[1, 3650]`. Derselbe Ablaufzeitpunkt wird für alle Blobs in einem einzelnen Aufruf angewendet. Normalerweise über die zentrale Einstellung verdrahtet `RJReport.StorageAccount.LinkExpiryDays`. |
| `AddBlobNamePrefix` | `bool`   | `$false`          | Wenn `$true`, werden Blob-Namen mit `yyyyMMdd-HHmmss-` (Zeitstempel von `Get-Date` zum Upload-Zeitpunkt) vorangestellt, um Überschreibungen bei wiederholten Läufen zu verhindern. Der ursprüngliche Dateiname bleibt als Suffix erhalten.        |

> **Hinweis:** Die Zuordnung zwischen diesen Parametern und dem zentralen RealmJoin-Anpassungs-JSON (einschließlich der empfohlenen Standardwerte) ist dokumentiert in [Runbook-Report-Einstellungen — Storage Account-Bereitstellung](/de/automatisierung/runbooks/runbook-report-settings.md#storage-account-delivery).

## Anwendungsbeispiele

### Empfohlenes Runbook-Muster

Dies ist das kanonische Muster, das von Reporting-Runbooks verwendet wird. Die Speicher-Konfiguration wird aus der zentralen RealmJoin-Anpassung über `Use-RJInterface -Type Setting`, der Container ist pro Runbook fest verdrahtet, und eine fehlende Konfiguration führt dazu, dass das Runbook mit einer handlungsorientierten Meldung abbricht:

```powershell
#Requires -Modules @{ModuleName = "RealmJoin.RunbookHelper"; ModuleVersion = "0.8.6" }
#Requires -Modules @{ModuleName = "Az.Accounts"; ModuleVersion = "5.3.4" }

param(
    [string] $ContainerName = "my-runbook-output",

    [ValidateScript( { Set-ExecutionPolicy -ExecutionPolicy Bypass -Scope Process; Use-RJInterface -Type Setting -Attribute "RJReport.StorageAccount.ResourceGroup" } )]
    [string] $ResourceGroupName,

    [ValidateScript( { Set-ExecutionPolicy -ExecutionPolicy Bypass -Scope Process; Use-RJInterface -Type Setting -Attribute "RJReport.StorageAccount.StorageAccountName" } )]
    [string] $StorageAccountName,

    [ValidateScript( { Set-ExecutionPolicy -ExecutionPolicy Bypass -Scope Process; Use-RJInterface -Type Setting -Attribute "RJReport.StorageAccount.LinkExpiryDays" } )]
    [ValidateRange(1, 3650)]
    [int] $LinkExpiryDays = 6
)

Connect-RjRbAzAccount

if ((-not $ResourceGroupName) -or (-not $StorageAccountName)) {
    "## Um in ein Storage Account zu exportieren, verwenden Sie bitte RJ Runbooks Customization"
    "## ( https://portal.realmjoin.com/settings/runbooks-customizations ) um Folgendes zu konfigurieren:"
    "##   - RJReport.StorageAccount.ResourceGroup"
    "##   - RJReport.StorageAccount.StorageAccountName"
    throw "Fehlende Storage Account-Konfiguration."
}

# … die Exportdatei erzeugen …
$exportPath = "myReport.csv"

$uploadResults = Publish-RjRbFilesToStorageContainer `
    -FilePaths          @($exportPath) `
    -ContainerName      $ContainerName `
    -ResourceGroupName  $ResourceGroupName `
    -StorageAccountName $StorageAccountName `
    -LinkExpiryDays     $LinkExpiryDays `
    -AddBlobNamePrefix  $true

$uploadResult = $uploadResults[0]
"## Export erstellt."
"## Ablauf des Links: $($uploadResult.EndTime)"
$uploadResult.SASLink | Out-String
```

Einige Konventionen, die man bei der Übernahme dieses Musters beibehalten sollte:

* Die drei zentralen Einstellungen (`ResourceGroup`, `StorageAccountName`, `LinkExpiryDays`) werden als Runbook-Parameter bereitgestellt, die über `Use-RJInterface -Type Setting`, werden aber typischerweise *ausgeblendet* in der Runbook-Anpassung (`"Hide": true`) damit Endbenutzer sie nie sehen.
* Der Containername ist pro Runbook fest verdrahtet (oft über einen `param` Standardwert), damit Lebenszyklusrichtlinien und Zugriffskontrollen pro Exporttyp abgestimmt werden können — es ist absichtlich *nicht* keine zentrale Einstellung.
* `AddBlobNamePrefix $true` ist die sichere Standardvorgabe für periodische Exporte, die bei jedem Lauf einen festen Dateinamen erzeugen.
* Die Funktion wird innerhalb des Haupt- `try { … } catch { throw $_ } finally { Disconnect-AzAccount … }` Blocks aufgerufen, damit Teilfehler an den Automation-Job durchgereicht werden und der Az-Kontext selbst bei Erfolg freigegeben wird.

### Mehrere Dateien in einem Aufruf

`FilePaths` akzeptiert ein Array; jede Datei wird nacheinander hochgeladen und für jeden hochgeladenen Blob wird ein Ergebnisobjekt zurückgegeben.

```powershell
$results = Publish-RjRbFilesToStorageContainer `
    -FilePaths          @($csvPath, $xlsxPath) `
    -ContainerName      'reports' `
    -ResourceGroupName  $ResourceGroupName `
    -StorageAccountName $StorageAccountName

foreach ($r in $results) {
    "Hochgeladen $($r.BlobName) — herunterladen bis $($r.EndTime): $($r.SASLink)"
}
```

### Benutzerdefinierte Link-Laufzeit und explizites Abonnement

```powershell
Publish-RjRbFilesToStorageContainer `
    -FilePaths          $exportPaths `
    -ContainerName      'quarterly-reports' `
    -ResourceGroupName  $ResourceGroupName `
    -StorageAccountName $StorageAccountName `
    -SubscriptionId     '00000000-0000-0000-0000-000000000000' `
    -LinkExpiryDays     30
```

Nützlich, wenn sich das Runbook über mehrere Abonnements erstreckt oder wenn nachgelagerte Empfänger ein längeres Zeitfenster als die standardmäßigen 6 Tage benötigen.

### In Kombination mit `Send-RjRbReportEmail`

Ein gängiges Muster ist es, umfangreiche Daten in den Blob-Speicher hochzuladen und den SAS-Link in eine Berichts-E-Mail einzubetten, damit die E-Mail deutlich unter dem 4-MB-Graph `sendMail` Grenzwert bleibt:

```powershell
$uploaded = Publish-RjRbFilesToStorageContainer `
    -FilePaths          $csvPath `
    -ContainerName      'reports' `
    -ResourceGroupName  $ResourceGroupName `
    -StorageAccountName $StorageAccountName `
    -AddBlobNamePrefix  $true

$linkLine = "[Download {0}]({1}) (gültig bis {2:yyyy-MM-dd HH:mm} UTC)" -f `
    $uploaded[0].BlobName, $uploaded[0].SASLink, $uploaded[0].EndTime.ToUniversalTime()

$reportMd = @"
# Gerätebestand

Die vollständige Geräteliste ist als Download verfügbar:

$linkLine
"@

Send-RjRbReportEmail `
    -EmailFrom       $emailFrom `
    -EmailTo         'it-reports@contoso.com' `
    -Subject         "Gerätebestand — $(Get-Date -Format 'yyyy-MM-dd')" `
    -MarkdownContent $reportMd
```

Siehe [Send-RjRbReportEmail](/de/dev-reference/report-functions/send-rjrbreportemail.md) für den E-Mail-Teil dieses Musters.

## Verhalten und Fehlerbehandlung

### Vorab-Validierung der Datei

Bevor irgendein Azure-Aufruf ausgeführt wird, durchläuft die Funktion `FilePaths` und wirft `Datei '<path>' wurde nicht gefunden.` für den ersten fehlenden Eintrag. Dadurch werden teilweise Uploads verhindert, wenn der Aufrufer sich vertippt hat.

### Auflösung des Azure-Kontexts

`Get-AzContext` wird zuerst geprüft. Wenn kein Kontext oder der Kontext kein `Konto` (z. B. ein frischer Runbook-Lauf) hat, ruft die Funktion `Connect-RjRbAzAccount` auf, um die verwaltete Identität zu authentifizieren. Wenn `-SubscriptionId` angegeben ist, `Set-AzContext -Subscription` wird anschließend aufgerufen.

### Containererstellung

Der Container wird mit einer `PUT …?restype=container` Anfrage erstellt:

* **HTTP 201** — Container erstellt.
* **HTTP 409** — Container existiert bereits; wird als Erfolg gewertet.
* **Jeder andere Status** — die Funktion wirft `Containererstellung fehlgeschlagen (<status>): <body>`.

### Upload-Fehler

Jede Datei wird über `HttpClient.SendAsync`. Ein nicht erfolgreicher Status beendet den Aufruf mit `Blob-Upload fehlgeschlagen (<status>): <body>`, einschließlich der von Azure Storage zurückgegebenen Rohfehlermeldung. Frühere Dateien, die in demselben Aufruf bereits hochgeladen wurden, verbleiben im Storage Account — der Aufrufer sollte den Aufruf ggf. in ein try/catch einbetten und Bereinigung ausführen, wenn teilweise Uploads nicht akzeptabel sind.

### Fehler beim Abrufen der Schlüssel

`Invoke-AzRestMethod` wird verwendet, um den ARM `listKeys` Endpunkt aufzurufen. Wenn der Antwortstatus etwas anderes als 200 ist, wirft die Funktion `Fehler beim Abrufen der Storage-Account-Schlüssel für '<account>' in Ressourcengruppe '<rg>'. Status: <status>`Die häufigsten Ursachen sind:

* Fehlendes `Microsoft.Storage/storageAccounts/listKeys/action` auf der verwalteten Identität.
* Falscher Abonnementkontext (kombinieren mit `-SubscriptionId`).
* Tippfehler in `StorageAccountName` oder `ResourceGroupName`.
* Die zentralen Einstellungen `RJReport.StorageAccount.ResourceGroup` / `RJReport.StorageAccount.StorageAccountName` nicht konfiguriert — siehe [Runbook-Berichtseinstellungen](/de/automatisierung/runbooks/runbook-report-settings.md#storage-account-delivery).

### SAS-Token-Eigenschaften

Die generierten Tokens verwenden:

* `sv=2023-11-03` (signierte Version)
* `sr=b` (blob-beschränkt)
* `sp=r` (nur lesen)
* `spr=https` (nur HTTPS)
* `st` auf 5 Minuten in der Vergangenheit gesetzt (Toleranz für Uhrabweichungen) und `gesetzt` zu `LinkExpiryDays` ab dem Zeitpunkt des Aufrufs.

Die Tokens werden mit dem Storage-Account-Schlüssel signiert. **Jede Person mit dem Link kann den Blob bis zum Ablauf herunterladen** — behandeln Sie die zurückgegebene SAS-URL als Geheimnis.

## Ausgaben

Jeder erfolgreiche Upload erzeugt ein `PSCustomObject` mit diesen Eigenschaften:

| Eigenschaft | Typ        | Beschreibung                                                                                                        |
| ----------- | ---------- | ------------------------------------------------------------------------------------------------------------------- |
| `BlobName`  | `string`   | Der endgültige Blobname im Container, einschließlich des Zeitstempelpräfixes, wenn `AddBlobNamePrefix` ist `$true`. |
| `EndTime`   | `datetime` | Lokale Ablaufzeit des SAS Token (auch in der URL als UTC codiert).                                                  |
| `SASLink`   | `string`   | Vollständig qualifizierte HTTPS-Download-URL mit eingebettetem SAS Token.                                           |

Die Ergebnisse werden in derselben Reihenfolge zurückgegeben wie `FilePaths`. Selbst beim Hochladen einer einzelnen Datei ist der Rückgabewert ein Array — indizieren Sie es (`$results[0]`) oder iterieren Sie mit `foreach` anstatt es als Skalar zu behandeln.

## Siehe auch

* [Runbook-Report-Einstellungen — Storage Account-Bereitstellung](/de/automatisierung/runbooks/runbook-report-settings.md#storage-account-delivery) — zentrale Konfiguration des Storage Account, des Link-Ablaufs und des Blob-Namenspräfixes, das von Reporting-Runbooks verwendet wird.
* [Send-RjRbReportEmail](/de/dev-reference/report-functions/send-rjrbreportemail.md) — begleitender Helfer zum Zustellen von Berichten per E-Mail; wird häufig mit dieser Funktion kombiniert, um die E-Mail-Nutzlast klein zu halten.
* Microsoft Docs: [Autorisieren mit Shared Key](https://learn.microsoft.com/en-us/rest/api/storageservices/authorize-with-shared-key) — vom Helfer verwendetes Signaturschema.
* Microsoft Docs: [Erstellen Sie ein Service-SAS](https://learn.microsoft.com/en-us/rest/api/storageservices/create-service-sas) — SAS Token-Format, das in `SASLink`.


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://docs.realmjoin.com/de/dev-reference/report-functions/publish-rjrbfilestostoragecontainer.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
