> 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 asistente estándar para entregar archivos de informe (CSV, XLSX, ZIP, …) desde los 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 enlace 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:

* **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 SAS Token). Esto elimina el conocido conflicto de ensamblados entre `Az.Storage` y `ExchangeOnlineManagement` que aparece en runbooks de informes mixtos.
* **Autoconectable** — si no hay `Az` contexto activo, la función llama transparentemente a `Connect-RjRbAzAccount`. Un `-SubscriptionId` opcional cambia de 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 trata como éxito.
* **Cargas basadas en HttpClient** — usa `System.Net.Http.HttpClient` directamente porque el interceptor de `Invoke-RestMethod` de Azure Automation elimina los encabezados personalizados obligatorios (`x-ms-blob-type`) con cuerpos binarios.
* **Enlaces SAS de solo lectura** — cada URL devuelta está firmada 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 de blob) que consume un runbook típico vive en el JSON de personalización de RealmJoin y está documentada en [Configuración del informe de Runbook — 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 de uso general 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 en 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. **Colaborador de datos de blobs de almacenamiento** 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 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 requires the 'Az.Accounts' module. Add #Requires -Modules @{ModuleName = 'Az.Accounts'; ModuleVersion = '5.3.4'} to the calling runbook."* antes de que se haga cualquier llamada a Azure.

> **¿Por qué no está `Az.Accounts` declarado como `RequiredModules` entrada en `RealmJoin.RunbookHelper.psd1`?**
>
> `Az.Accounts` se enumera intencionalmente solo bajo `ExternalModuleDependencies` (informativo) y *no* en `RequiredModules` (aplicado en tiempo de `Import-Module` ) :
>
> * **Se paga solo por lo que se usa.** Muchos runbooks consumen solo ayudantes basados en Graph (por ejemplo, `Send-RjRbReportEmail` sin `-UseNativeGraphRequest`, o `Invoke-RjRbRestMethodGraph`) y nunca tocan ningún cmdlet Az.\*. Promover `Az.Accounts` a `RequiredModules` obligaría a cada runbook consumidor a incluir el módulo incluso cuando nada en su ruta de código lo necesite, aumentando de forma medible el tiempo de arranque en frío en Azure Automation.
> * **Evita conflictos de versiones.** Una `RequiredModules` restricción estricta provoca la resolución automática en tiempo de importación y puede arrastrar una `Az.Accounts` versión específica 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 su propio `#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 helper. El módulo helper expone la dependencia de forma informativa (mediante `ExternalModuleDependencies` en el manifiesto) y mediante la comprobación en tiempo de ejecución anterior, de modo que una configuración incorrecta falle de forma ruidosa con un mensaje accionable en lugar de ocultar silenciosamente un conflicto de versiones.

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

## 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 contenedor `reports` 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 de archivo locales para subir. 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 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éricos + 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 conecta con la configuración central `RJReport.AzureStorage.ResourceGroup`.                                                                                                                                                                     |
| `StorageAccountName` | `string`   | Nombre de la Azure Storage Account. Normalmente se conecta con la configuración central `RJReport.AzureStorage.StorageAccountName`.                                                                                                                                                                               |

### Opcional

| Parámetro           | Tipo     | Predeterminado  | Descripción                                                                                                                                                                                                                                     |
| ------------------- | -------- | --------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `SubscriptionId`    | `string` | contexto actual | Suscripción de Azure que hospeda 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 enlace SAS en días. Validado para `[1, 3650]`. La misma marca de tiempo de expiración se aplica a todos los blobs en una sola llamada. Normalmente se conecta con la configuración central `RJReport.AzureStorage.LinkExpiryDays`.  |
| `AddBlobNamePrefix` | `bool`   | `$false`        | Cuando `$true`, los nombres de blob se anteponen con `yyyyMMdd-HHmmss-` (marca de tiempo de `Get-Date` en el momento de la subida) 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 [Configuración del informe de Runbook — 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 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 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, use la personalización de RJ Runbooks"
    "## ( https://portal.realmjoin.com/settings/runbooks-customizations ) para configurar:"
    "##   - RJReport.AzureStorage.ResourceGroup"
    "##   - RJReport.AzureStorage.StorageAccountName"
    throw "Falta la configuración de la Storage Account."
}

# … produce 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 enlace: $($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 los vean.
* El nombre del contenedor está codificado por runbook (a menudo mediante un `param` predeterminado) para que las directivas 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 seguro predeterminado para exportaciones periódicas que generan un nombre de archivo fijo en cada ejecución.
* La función se llama dentro del principal `try { … } catch { throw $_ } finally { Disconnect-AzAccount … }` del runbook, de modo que los fallos parciales suban al trabajo de Automation y el contexto Az se libere incluso en caso de éxito.

### Varios archivos en una llamada

`FilePaths` acepta un arreglo; cada archivo se sube 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) {
    "Subido $($r.BlobName) — descarga hasta $($r.EndTime): $($r.SASLink)"
}
```

### Tiempo de vida personalizado 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 mayor que el valor predeterminado de 6 días.

### Combinándolo con `Send-RjRbReportEmail`

Un patrón común es subir datos voluminosos al 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 como descarga:

$linkLine
"@

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

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

## Comportamiento y manejo de errores

### Validación de archivos previa

Antes de hacer 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` (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 a continuación.

### 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 `Error al crear el contenedor (<status>): <body>`.

### Fallos de carga

Cada archivo se carga mediante `HttpClient.SendAsync`. Un estado no exitoso 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 habían cargado en la misma llamada permanecen en la Storage Account; el llamador puede querer envolver la llamada en un try/catch y ejecutar limpieza si las cargas parciales no son aceptables.

### Fallos al recuperar claves

`Invoke-AzRestMethod` se usa para llamar al endpoint ARM `listKeys` . 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`.
* Las configuraciones centrales `RJReport.AzureStorage.ResourceGroup` / `RJReport.AzureStorage.StorageAccountName` no están configuradas — consulte [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` a partir del momento de la llamada.

Los tokens se firman con la clave de la Storage Account. **Cualquiera con el enlace puede descargar el blob hasta la expiración** — 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` | Expiración SAS en hora local (también codificada en la URL como UTC).                                                |
| `SASLink`  | `string`   | URL de descarga HTTPS completamente calificada con token SAS incrustado.                                             |

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

## Véase también

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