> 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/es/dev-reference/report-functions/publish-rjrbfilestostoragecontainer.md).

# Publish-RjRbFilesToStorageContainer

## Resumen

`Publish-RjRbFilesToStorageContainer` es el asistente estándar para entregar archivos de informe (CSV, XLSX, ZIP, …) desde runbooks de informes de RealmJoin a través de Azure Blob Storage. Sube uno o más archivos locales a un contenedor de destino y devuelve un vínculo de descarga SAS con tiempo limitado para cada blob, apto para incluirse en correos de informe, mensajes de Teams o salidas de runbook.

Características clave:

* **Sin `Az.Storage` dependencia** — las operaciones de blob se realizan directamente contra la Azure Storage REST API (creación de contenedores, carga, generación de SAS Token). Esto elimina el conocido conflicto de ensamblados entre `Az.Storage` y `ExchangeOnlineManagement` que aparece en runbooks de informes mixtos.
* **Auto-conectado** — si no hay `Az` contexto activo, la función llama de forma transparente a `Connect-RjRbAzAccount`. Un `-SubscriptionId` opcional cambia el contexto antes de cualquier operación de almacenamiento.
* **Contenedor creado automáticamente** — si el contenedor de destino aún no existe, se crea sobre la marcha; un contenedor existente (HTTP 409) se considera éxito.
* **Cargas basadas en HttpClient** — usa `System.Net.Http.HttpClient` directamente porque el `Invoke-RestMethod` interceptor de Azure Automation elimina encabezados personalizados requeridos (`x-ms-blob-type`) con cuerpos binarios.
* **Vínculos SAS de solo lectura** — cada URL devuelta se firma con la clave de la Storage Account, acotada a un único blob, solo HTTPS y válida durante `LinkExpiryDays` días (predeterminado 6).

La configuración central de almacenamiento (grupo de recursos, nombre de la cuenta, días de expiración, prefijo del nombre del blob) que consume un runbook típico vive en el JSON de personalización de RealmJoin y se documenta en [Runbook Report Settings — Storage Account Delivery](/es/automatizacion/runbooks/runbook-report-settings.md#storage-account-delivery). Este documento se centra en llamar a la función desde un runbook.

## Requisitos previos

### Azure Storage Account

Se requiere una Azure Storage Account existente (se recomienda general-purpose v2). El contenedor de destino no necesita existir de antemano — se crea automáticamente en el primer uso.

### Azure RBAC en la Storage Account

La identidad administrada de la Automation Account (o el Service Principal usado por el runbook) necesita los siguientes permisos en la Storage Account o su grupo de recursos:

| Acción                                              | Requerido para                                                                                |
| --------------------------------------------------- | --------------------------------------------------------------------------------------------- |
| `Microsoft.Storage/storageAccounts/read`            | Lectura de la Storage Account                                                                 |
| `Microsoft.Storage/storageAccounts/listKeys/action` | Obtención de la clave de la cuenta usada para la firma SharedKey y la generación de SAS Token |

El rol integrado **Storage Account Contributor** cubre ambos. **Storage Blob Data Contributor** por sí solo *no* es suficiente porque la función firma las solicitudes con la clave de la cuenta en lugar de usar operaciones de blob respaldadas por AAD.

### Conectividad del módulo

La función requiere el módulo `Az.Accounts` en el entorno del runbook (`Get-AzContext`, `Set-AzContext`, `Connect-AzAccount`, `Invoke-AzRestMethod`). Declárelo explícitamente en el runbook consumidor:

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

Si `Az.Accounts` no está disponible en tiempo de ejecución, la función falla rápidamente con un mensaje de error claro — comprueba `Get-AzContext` de antemano y lanza *"Publish-RjRbFilesToStorageContainer requiere el módulo 'Az.Accounts'. Agregue #Requires -Modules @{ModuleName = 'Az.Accounts'; ModuleVersion = '5.3.4'} al runbook que llama."* antes de que se realice cualquier llamada a Azure.

> **Por qué no está `Az.Accounts` declarado como `RequiredModules` en `RealmJoin.RunbookHelper.psd1`?**
>
> `Az.Accounts` se incluye intencionalmente solo bajo `ExternalModuleDependencies` (informativo) y *no* bajo `RequiredModules` (aplicado en `Import-Module` tiempo):
>
> * **Paga solo por lo que usas.** Muchos runbooks consumen solo helpers basados en Graph (p. ej. `Send-RjRbReportEmail` sin `-UseNativeGraphRequest`, o `Invoke-RjRbRestMethodGraph`) y nunca tocan ningún cmdlet Az.\*. Promover `Az.Accounts` a `RequiredModules` obligaría a que cada runbook consumidor envíe el módulo incluso cuando nada en su ruta de código lo necesita, aumentando de forma medible el tiempo de arranque en frío en Azure Automation.
> * **Evita disputas de versiones.** Una `RequiredModules` restricción dura activa la autorresolución en tiempo de importación y puede arrastrar una versión específica de `Az.Accounts` que entra en conflicto con lo que el propio runbook fija (los submódulos Az.\* son notoriamente sensibles a la versión). Permitir que el runbook declare sus propios `#Requires -Modules` mantiene la elección de versión en el llamador.
> * **Autoridad por runbook.** En Azure Automation, el lugar canónico para declarar requisitos de módulo es a nivel de runbook mediante `#Requires`, no a nivel del módulo helper. El módulo helper expone la dependencia de forma informativa (mediante `ExternalModuleDependencies` en el manifiesto) y a través de la comprobación en tiempo de ejecución anterior, de modo que una mala configuración falla en voz alta con un mensaje accionable en lugar de ocultar silenciosamente un conflicto de versiones.

`Az.Storage` es **no** necesario y no debe importarse en el mismo runbook para evitar el conflicto de ensamblados mencionado anteriormente.

## Inicio rápido

La llamada mínima viable requiere la(s) ruta(s) local(es) del archivo, el nombre del contenedor, el grupo de recursos y el nombre de la 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
```

Esto sube `devices.csv` al `reports` contenedor en `stcontosoreports` y devuelve un objeto con el nombre del blob, la marca de tiempo de expiración del SAS y una URL de descarga lista para compartir válida durante los 6 días predeterminados.

## Parámetros

### Requerido

| Parámetro            | Tipo       | Descripción                                                                                                                                                                                                                                                                                                  |
| -------------------- | ---------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `FilePaths`          | `string[]` | Una o más rutas locales de archivo para cargar. Cada ruta debe apuntar a un archivo existente (`Test-Path -PathType Leaf`); la función lanza un error de antemano si falta alguna entrada.                                                                                                                   |
| `ContainerName`      | `string`   | Contenedor blob de destino. Se crea automáticamente si no existe. Debe cumplir las reglas de nomenclatura de contenedores de Azure (minúsculas, 3–63 caracteres, alfanumérico + guion). El nombre del contenedor es una *decisión por runbook* y se establece en el runbook, no en la configuración central. |
| `ResourceGroupName`  | `string`   | Grupo de recursos que contiene la Storage Account. Normalmente se vincula a la configuración central `RJReport.StorageAccount.ResourceGroup`.                                                                                                                                                                |
| `StorageAccountName` | `string`   | Nombre de la Azure Storage Account. Normalmente se vincula a la configuración central `RJReport.StorageAccount.StorageAccountName`.                                                                                                                                                                          |

### Opcional

| Parámetro           | Tipo     | Predeterminado  | Descripción                                                                                                                                                                                                                                  |
| ------------------- | -------- | --------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `SubscriptionId`    | `string` | contexto actual | Suscripción de Azure que aloja la Storage Account. Si se proporciona, `Set-AzContext -Subscription` se llama antes de cualquier operación de almacenamiento. Omítalo para usar el `Az` contexto.                                             |
| `LinkExpiryDays`    | `int`    | `6`             | Validez del vínculo SAS en días. Validado a `[1, 3650]`. La misma marca de expiración se aplica a todos los blobs en una sola llamada. Normalmente se vincula a la configuración central `RJReport.StorageAccount.LinkExpiryDays`.           |
| `AddBlobNamePrefix` | `bool`   | `$false`        | Cuando `$true`, los nombres de blob se prefijan con `yyyyMMdd-HHmmss-` (marca de tiempo de `Get-Date` en el momento de la carga) para evitar sobrescrituras en ejecuciones repetidas. El nombre de archivo original se conserva como sufijo. |

> **Nota:** El mapeo entre estos parámetros y el JSON de personalización central de RealmJoin (incluidos los valores predeterminados recomendados) se documenta en [Runbook Report Settings — Storage Account Delivery](/es/automatizacion/runbooks/runbook-report-settings.md#storage-account-delivery).

## Ejemplos de uso

### Patrón de runbook recomendado

Este es el patrón canónico usado por los runbooks de informes. La configuración de almacenamiento se toma de la personalización central de RealmJoin mediante `Use-RJInterface -Type Setting`, el contenedor está codificado por runbook y una configuración faltante hace que el runbook se aborte con un mensaje accionable:

```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)) {
    "## Para exportar a una Storage Account, use la personalización de RJ Runbooks"
    "## ( https://portal.realmjoin.com/settings/runbooks-customizations ) para configurar:"
    "##   - RJReport.StorageAccount.ResourceGroup"
    "##   - RJReport.StorageAccount.StorageAccountName"
    throw "Falta la configuración de la Storage Account."
}

# … producir el archivo de exportación …
$exportPath = "myReport.csv"

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

$uploadResult = $uploadResults[0]
"## Exportación creada."
"## Expiración del vínculo: $($uploadResult.EndTime)"
$uploadResult.SASLink | Out-String
```

Algunas convenciones que conviene mantener al adoptar este patrón:

* Las tres configuraciones centrales (`ResourceGroup`, `StorageAccountName`, `LinkExpiryDays`) se exponen como parámetros del runbook conectados mediante `Use-RJInterface -Type Setting`, pero normalmente *ocultos* en la personalización del runbook (`"Hide": true`) para que los usuarios finales nunca las vean.
* El nombre del contenedor está codificado por runbook (a menudo mediante un valor predeterminado de `param` ) para que las políticas de ciclo de vida y los controles de acceso puedan ajustarse por tipo de exportación — intencionalmente no es *no* una configuración central.
* `AddBlobNamePrefix $true` es el valor predeterminado seguro para exportaciones periódicas que generan un nombre de archivo fijo en cada ejecución.
* La función se llama dentro del bloque principal del runbook `try { … } catch { throw $_ } finally { Disconnect-AzAccount … }` para que los fallos parciales se propaguen al trabajo de Automation y el contexto Az se libere incluso en caso de éxito.

### Múltiples archivos en una sola llamada

`FilePaths` acepta un array; cada archivo se carga secuencialmente y se devuelve un objeto de resultado por cada blob cargado.

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

foreach ($r in $results) {
    "Cargado $($r.BlobName) — descarga hasta $($r.EndTime): $($r.SASLink)"
}
```

### Vida útil personalizada del vínculo y suscripción explícita

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

Útil cuando el runbook abarca varias suscripciones, o cuando los destinatarios posteriores necesitan una ventana más larga que el valor predeterminado de 6 días.

### Combinación con `Send-RjRbReportEmail`

Un patrón común es cargar datos voluminosos al almacenamiento de blobs e incrustar el vínculo SAS en un correo de informe, manteniendo el correo muy por debajo del límite de Graph de 4 MB `sendMail` :

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

$linkLine = "[Descargar {0}]({1}) (válido hasta {2:yyyy-MM-dd HH:mm} UTC)" -f `
    $uploaded[0].BlobName, $uploaded[0].SASLink, $uploaded[0].EndTime.ToUniversalTime()

$reportMd = @"
# Inventario de dispositivos

La lista completa de dispositivos está disponible para descarga:

$linkLine
"@

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

Consulte [Send-RjRbReportEmail](/es/dev-reference/report-functions/send-rjrbreportemail.md) la parte de correo de este patrón.

## Comportamiento y manejo de errores

### Validación de archivos previa

Antes de realizar cualquier llamada a Azure, la función recorre `FilePaths` y lanza `"No se encontró el archivo '<path>'."` para la primera entrada faltante. Esto evita cargas parciales cuando el llamador introduce un error tipográfico.

### Resolución del contexto de Azure

`Get-AzContext` se comprueba primero. Si no hay contexto o el contexto no tiene `Account` (por ejemplo, una ejecución nueva del runbook), la función llama a `Connect-RjRbAzAccount` para autenticar la identidad administrada. Si `-SubscriptionId` se proporciona, `Set-AzContext -Subscription` se invoca después.

### Creación del contenedor

El contenedor se crea con una solicitud `PUT …?restype=container` :

* **HTTP 201** — contenedor creado.
* **HTTP 409** — el contenedor ya existe; se trata como éxito.
* **Cualquier otro estado** — la función lanza `Falló la creación del contenedor (<status>): <body>`.

### Fallos de carga

Cada archivo se carga mediante `HttpClient.SendAsync`. Un estado no exitoso termina la llamada con `Falló la carga del blob (<status>): <body>`, incluido el error sin procesar devuelto por Azure Storage. Los archivos anteriores que ya se cargaron en la misma llamada permanecen en la Storage Account — es posible que el llamador quiera envolver la llamada en un try/catch y ejecutar limpieza si las cargas parciales no son aceptables.

### Fallos en la recuperación de claves

`Invoke-AzRestMethod` se usa para llamar al ARM `listKeys` endpoint. Si el estado de la respuesta es distinto de 200, la función lanza `No se pudieron recuperar las claves de la Storage Account para '<account>' en el grupo de recursos '<rg>'. Estado: <status>`. Las causas más comunes son:

* Falta `Microsoft.Storage/storageAccounts/listKeys/action` en la identidad administrada.
* Contexto de suscripción incorrecto (combinar con `-SubscriptionId`).
* Error tipográfico en `StorageAccountName` o `ResourceGroupName`.
* Las configuraciones centrales `RJReport.StorageAccount.ResourceGroup` / `RJReport.StorageAccount.StorageAccountName` no están configuradas — vea [Configuración de informes del runbook](/es/automatizacion/runbooks/runbook-report-settings.md#storage-account-delivery).

### Características del token SAS

Los tokens generados usan:

* `sv=2023-11-03` (versión firmada)
* `sr=b` (acotado al blob)
* `sp=r` (solo lectura)
* `spr=https` (solo HTTPS)
* `st` establecido 5 minutos en el pasado (tolerancia al desfase del reloj) y `se` a `LinkExpiryDays` desde el momento de la llamada.

Los tokens se firman con la clave de la Storage Account. **Cualquiera con el vínculo puede descargar el blob hasta la expiración** — trate la URL SAS devuelta como un secreto.

## Salidas

Cada carga exitosa produce un `PSCustomObject` con estas propiedades:

| Propiedad     | Tipo           | Descripción                                                                                                          |
| ------------- | -------------- | -------------------------------------------------------------------------------------------------------------------- |
| `BlobName`    | `string`       | El nombre final del blob en el contenedor, incluido el prefijo de marca de tiempo si `AddBlobNamePrefix` es `$true`. |
| `Hora de fin` | `fecha y hora` | Vencimiento local del SAS (también codificado en la URL como UTC).                                                   |
| `Enlace SAS`  | `string`       | URL HTTPS de descarga completamente calificada con token SAS incrustado.                                             |

Los resultados se devuelven en el mismo orden que `FilePaths`. Incluso al cargar un solo archivo, el valor devuelto es una matriz — indíquela por índice (`$results[0]`) o itere con `foreach` en lugar de tratarlo como un escalar.

## Ver también

* [Runbook Report Settings — Storage Account Delivery](/es/automatizacion/runbooks/runbook-report-settings.md#storage-account-delivery) — configuración central de la cuenta de almacenamiento, la expiración del enlace y el prefijo del nombre del blob utilizados por los runbooks de informes.
* [Send-RjRbReportEmail](/es/dev-reference/report-functions/send-rjrbreportemail.md) — función auxiliar complementaria para entregar informes por correo electrónico; normalmente se combina con esta función para mantener pequeña la carga útil del correo electrónico.
* Documentación de Microsoft: [Autorizar con clave compartida](https://learn.microsoft.com/en-us/rest/api/storageservices/authorize-with-shared-key) — esquema de firma utilizado por la función auxiliar.
* Documentación de Microsoft: [Crear un SAS de servicio](https://learn.microsoft.com/en-us/rest/api/storageservices/create-service-sas) — formato del token SAS devuelto en `Enlace SAS`.


---

# 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/es/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.
