> 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

Carga archivos locales a un contenedor de Azure Storage desde runbooks de Azure Automation y devuelve enlaces de descarga SAS con tiempo limitado.

## Resumen

`Publish-RjRbFilesToStorageContainer` es el helper 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 enlace SAS de descarga con tiempo limitado para cada blob, adecuado para incluir 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 API REST de Azure Storage (creación de contenedores, carga, generación de SAS Token). Esto elimina el conocido conflicto de ensamblado entre `Az.Storage` y `ExchangeOnlineManagement` que aparece en runbooks de informes mixtos.
* **Conexión automática** — si no `Az` contexto activo, la función llama transparentemente a `Connect-RjRbAzAccount`. Un opcional `-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 trata como éxito.
* **Cargas basadas en HttpClient** — usa `System.Net.Http.HttpClient` directamente porque el `Invoke-RestMethod` interceptor de Azure Automation 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 expiración, prefijo del nombre del blob) usada por un runbook típico vive en el JSON de personalización de RealmJoin y se documenta en [Configuración de informes 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 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:

| 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 |

Asigne el rol integrado **Reader and Data Access** en la Storage Account: es el rol integrado más pequeño que cubre ambos. La creación del contenedor, la carga y la generación de SAS se autorizan con la clave de la cuenta y no necesitan más permisos de Azure RBAC. **Storage Account Contributor** también funciona, pero además permite administrar y eliminar la Storage Account. **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.

Como la función se autentica con la clave de la cuenta, el acceso por clave compartida debe estar permitido en la Storage Account (`allowSharedKeyAccess` no debe ser `false`). La clave concede acceso a todos los datos de la Storage Account, por lo que se recomienda una Storage Account dedicada para informes.

### 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 forma inmediata 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 lo llama."* antes de que se realice cualquier llamada a Azure.

> **¿Por qué no está `Az.Accounts` declarado como una `RequiredModules` entrada en `RealmJoin.RunbookHelper.psd1`?**
>
> `Az.Accounts` se enumera intencionalmente solo bajo `ExternalModuleDependencies` (informativo) y *no* bajo `RequiredModules` (aplicado en `Import-Module` en tiempo de importación):
>
> * **Pagas solo por lo que usas.** 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 necesita, aumentando de forma medible el tiempo de arranque en frío en Azure Automation.
> * **Evite disputas de versiones.** Una `RequiredModules` restricción estricta desencadena 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). Dejar 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 auxiliar. El módulo auxiliar expone la dependencia de forma informativa (a través de `ExternalModuleDependencies` en el manifiesto) y mediante la comprobación en tiempo de ejecución anterior, por lo que una mala configuración falla de forma evidente 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 ensamblado mencionado arriba.

## Inicio rápido

La llamada mínima viable requiere la(s) ruta(s) local(es) de 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 carga `devices.csv` al `reports` contenedor en `stcontosoreports` y devuelve un objeto con el nombre del blob, la marca de tiempo de vencimiento 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 archivos 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éricos + 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 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 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 vencimiento 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 original del archivo se conserva como sufijo.   |

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

## Ejemplos de uso

### Patrón recomendado de runbook

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 se codifica de forma fija por runbook y una configuración ausente hace que el runbook se detenga 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, utilice 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 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."
"## Vencimiento del enlace: $($uploadResult.EndTime)"
$uploadResult.SASLink | Out-String
```

Conviene mantener algunas convenciones al adoptar este patrón:

* Las tres configuraciones 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 — de forma intencionada no es *no* una configuración 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 principal `try { … } catch { throw $_ } finally { Disconnect-AzAccount … }` bloque para que los fallos parciales lleguen al trabajo de Automation y el contexto de Az se libere incluso si todo sale bien.

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

### Vida útil 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.

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

Un patrón común es cargar datos voluminosos en blob storage 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 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 `El archivo '<path>' no se encontró.` 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 `Cuenta` (por ejemplo, una ejecución nueva de 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 `Falló la creación del contenedor (<status>): <body>`. Un HTTP 403 con `KeyBasedAuthenticationNotPermitted` significa que el acceso por clave compartida está deshabilitado en la Storage Account.

### 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 — puede que el llamador quiera envolver la llamada en un try/catch y ejecutar una limpieza si las cargas parciales no son aceptables.

### Errores al obtener las claves

`Invoke-AzRestMethod` se usa para llamar al endpoint ARM `listKeys` . Si el estado de respuesta es cualquier cosa distinta 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 (combine con `-SubscriptionId`).
* Error tipográfico en `StorageAccountName` o `ResourceGroupName`.
* La configuración central `RJReport.StorageAccount.ResourceGroup` / `RJReport.StorageAccount.StorageAccountName` no configurado — vea [Configuración del informe de 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` (del ámbito del blob)
* `sp=r` (solo lectura)
* `spr=https` (solo HTTPS)
* `st` establecido a 5 minutos en el pasado (tolerancia al desfase de reloj) y `se` a `LinkExpiryDays` desde el momento de la llamada.

Los tokens se firman con la clave de la cuenta de almacenamiento. **Cualquiera que tenga el enlace puede descargar el blob hasta que expire** — 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`  | `fecha y hora` | Expiración SAS en hora local (también codificada en la URL como UTC).                                                |
| `SASLink`  | `string`       | URL de descarga HTTPS completa 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 — indícalo (`$results[0]`) o itera con `foreach` en lugar de tratarlo como un escalar.

## Véase también

* [Configuración de informes de Runbook — entrega de Storage Account](/es/automatizacion/runbooks/runbook-report-settings.md#storage-account-delivery) — configuración central de la cuenta de almacenamiento, expiración del enlace y prefijo del nombre del blob utilizados 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 el asistente.
* 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 by asking a question.

Perform an HTTP GET request on the following URL with the `ask` and `goal` query parameters:

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

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is what the user is ultimately trying to achieve, the reason they need the answer. Sharing it helps GitBook give you a better, more relevant answer. A goal is most helpful when it describes the outcome the user wants rather than restating the question. For example, with `ask=how do I create an API token`, a goal like `automate deployments from our CI pipeline` lets GitBook tailor the answer to that use case.

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.
