> 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

## Descripción general

`Publish-RjRbFilesToStorageContainer` es el ayudante estándar para entregar archivos de informe (CSV, XLSX, ZIP, …) desde runbooks de informes de RealmJoin mediante Azure Blob Storage. Carga uno o más archivos locales a un contenedor de destino y devuelve un enlace de descarga SAS con límite de tiempo para cada blob, adecuado para incluirlo en correos de informe, mensajes de Teams o salidas del runbook.

Características principales:

* **No `Az.Storage` dependencia** — las operaciones de blob se realizan directamente contra la API REST de Azure Storage (creación de contenedores, carga, generación de token SAS). Esto elimina el conocido conflicto de ensamblados entre `Az.Storage` y `ExchangeOnlineManagement` que aparece en runbooks de informes mixtos.
* **Autoconexión** — si no hay `Az` contexto activo, la función llama transparentemente a `Connect-RjRbAzAccount`. Una opción `-SubscriptionId` 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 elimina los encabezados personalizados requeridos (`x-ms-blob-type`) con cuerpos binarios.
* **Enlaces SAS de solo lectura** — cada URL devuelta está firmada con la clave de la Storage Account, limitada a un único blob, solo HTTPS y válida durante `LinkExpiryDays` días (valor predeterminado 6).

La configuración central de almacenamiento (grupo de recursos, nombre de la cuenta, días de caducidad, prefijo del nombre del blob) que consume un runbook típico vive en el JSON de personalización de RealmJoin y está documentada en [Runbook Report Settings — entrega de Storage Account](/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). No es necesario que el contenedor de destino exista previamente; 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 |

El rol integrado **Storage Account Contributor** cubre ambos. **Storage Blob Data Contributor** por sí solo es *no* 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 de módulos

La función requiere el `Az.Accounts` módulo 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 de inmediato 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 se `Az.Accounts` declara como una `RequiredModules` entrada en `RealmJoin.RunbookHelper.psd1`?**
>
> `Az.Accounts` se enumera intencionalmente solo en `ExternalModuleDependencies` (informativo) y *no* en `RequiredModules` (aplicado en `Import-Module` tiempo):
>
> * **Pague solo por lo que usa.** Muchos runbooks consumen solo ayudantes basados en Graph (p. ej., `Send-RjRbReportEmail` sin `-UseNativeGraphRequest`, o `Invoke-RjRbRestMethodGraph`) y nunca tocan ningún cmdlet Az.\*. Elevar `Az.Accounts` a `RequiredModules` haría que cada runbook consumidor tuviera que incluir 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.
> * **Evite conflictos de versiones.** Una `RequiredModules` restricción rígida activa la resolución automática en tiempo de importación y puede arrastrar una `Az.Accounts` versión 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 manos del llamador.
> * **Autoridad por runbook.** En Azure Automation, el lugar canónico para declarar los requisitos de módulos es a nivel de runbook mediante `#Requires`, no a nivel del módulo auxiliar. El módulo auxiliar expone la dependencia de forma informativa (mediante `ExternalModuleDependencies` en el manifiesto) y mediante la comprobación de tiempo de ejecución anterior, de modo que una configuración incorrecta falla de forma visible con un mensaje accionable en lugar de ocultar silenciosamente un conflicto de versiones.

`Az.Storage` es **no** requerido 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/los archivo(s), 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 carga `devices.csv` al `reports` contenedor en `stcontosoreports` y devuelve un objeto con el nombre del blob, la marca de tiempo de caducidad del SAS y una URL de descarga lista para compartir válida durante los 6 días predeterminados.

## Parámetros

### Obligatorio

| 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 inmediato si falta alguna entrada.                                                                                                                      |
| `ContainerName`      | `string`   | Contenedor de blobs 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 *por runbook* decisión y se establece en el runbook, no en la configuración central. |
| `ResourceGroupName`  | `string`   | Grupo de recursos que contiene la Storage Account. Normalmente vinculado al ajuste central `RJReport.AzureStorage.ResourceGroup`.                                                                                                                                                                                |
| `StorageAccountName` | `string`   | Nombre de la Azure Storage Account. Normalmente vinculado al ajuste central `RJReport.AzureStorage.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 actual.                                           |
| `LinkExpiryDays`    | `int`    | `6`             | Validez del enlace SAS en días. Validado para `[1, 3650]`. La misma marca de tiempo de caducidad se aplica a todos los blobs en una sola llamada. Normalmente vinculado al ajuste central `RJReport.AzureStorage.LinkExpiryDays`.                 |
| `AddBlobNamePrefix` | `bool`   | `$false`        | Cuando `$true`, los nombres de blob llevan como prefijo `yyyyMMdd-HHmmss-` (marca de tiempo de `Get-Date` en el momento de la carga) para evitar sobrescrituras en ejecuciones repetidas. El nombre original del archivo se conserva como sufijo. |

> **Nota:** La relación entre estos parámetros y el JSON de personalización central de RealmJoin (incluidos los valores predeterminados recomendados) está documentada en [Runbook Report Settings — entrega de Storage Account](/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 obtiene de la personalización central de RealmJoin mediante `Use-RJInterface -Type Setting`, el contenedor está codificado de forma fija por runbook y una configuración ausente hace que el runbook se interrumpa 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.AzureStorage.ResourceGroup" } )]
    [string] $ResourceGroupName,

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

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

Connect-RjRbAzAccount

if ((-not $ResourceGroupName) -or (-not $StorageAccountName)) {
    "## Para exportar a una Storage Account, utilice la personalización de los Runbooks de RJ"
    "## ( https://portal.realmjoin.com/settings/runbooks-customizations ) para configurar:"
    "##   - RJReport.AzureStorage.ResourceGroup"
    "##   - RJReport.AzureStorage.StorageAccountName"
    throw "Falta la configuración de la Storage Account."
}

# … generar 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.
## Caducidad del enlace: $($uploadResult.EndTime)
$uploadResult.SASLink | Out-String
```

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

* Los tres ajustes centrales (`ResourceGroup`, `StorageAccountName`, `LinkExpiryDays`) se exponen como parámetros del runbook vinculados mediante `Use-RJInterface -Type Setting`, pero normalmente *ocultos* en la personalización del runbook (`"Hide": true`) para que los usuarios finales nunca los vean.
* El nombre del contenedor está codificado de forma fija por runbook (a menudo mediante un `param` predeterminado) para que las políticas de ciclo de vida y los controles de acceso puedan ajustarse por tipo de exportación — intencionalmente es *no* un ajuste central.
* `AddBlobNamePrefix $true` es el valor predeterminado seguro para exportaciones periódicas que producen 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 de Az se libere incluso en caso de éxito.

### Varios 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)"
}
```

### Caducidad personalizada del enlace 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.

### Combinado con `Send-RjRbReportEmail`

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

```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 su descarga:

$linkLine
"@

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

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

## Comportamiento y control de errores

### Validación previa de archivos

Antes de realizar cualquier llamada a Azure, la función recorre `FilePaths` y lanza `No se encontró el archivo '<path>'.` para la primera entrada que falte. 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 `cuenta` (p. ej., 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 a continuación.

### Creación del contenedor

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

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

### Errores de carga

Cada archivo se carga mediante `HttpClient.SendAsync`. Un estado no satisfactorio termina la llamada con `Error al cargar el 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; el llamador puede envolver la llamada en un try/catch y ejecutar limpieza si las cargas parciales no son aceptables.

### Fallos al obtener claves

`Invoke-AzRestMethod` se usa para llamar al punto de conexión 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 (combínelo con `-SubscriptionId`).
* Error tipográfico en `StorageAccountName` o `ResourceGroupName`.
* Los ajustes centrales `RJReport.AzureStorage.ResourceGroup` / `RJReport.AzureStorage.StorageAccountName` no están configurados — vea [Runbook Report Settings](/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` (con ámbito de blob)
* `sp=r` (solo lectura)
* `spr=https` (solo HTTPS)
* `st` establecer en 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 cuenta de almacenamiento. **Cualquiera con el enlace puede descargar el blob hasta el vencimiento** — trate la URL SAS devuelta como un secreto.

## Salidas

Cada carga correcta 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`. |
| `EndTime`  | `datetime` | Vencimiento del SAS en hora local (también codificado en la URL como UTC).                                           |
| `SASLink`  | `string`   | URL de descarga HTTPS completamente calificada con el token SAS incrustado.                                          |

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

## Véase también

* [Runbook Report Settings — entrega de Storage Account](/es/automatizacion/runbooks/runbook-report-settings.md#storage-account-delivery) — configuración central de la cuenta de almacenamiento, vencimiento del enlace y prefijo del nombre de blob usados por los runbooks de informes.
* [Send-RjRbReportEmail](/es/dev-reference/report-functions/send-rjrbreportemail.md) — ayuda complementaria para enviar informes por correo electrónico; normalmente se combina con esta función para mantener pequeño el contenido del correo.
* Microsoft Docs: [Autorizar con clave compartida](https://learn.microsoft.com/en-us/rest/api/storageservices/authorize-with-shared-key) — esquema de firma usado por la ayuda.
* Microsoft Docs: [Crear un SAS de servicio](https://learn.microsoft.com/en-us/rest/api/storageservices/create-service-sas) — formato del token SAS devuelto en `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:

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

The question should be specific, self-contained, and written in natural language.
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.
