Automatizar el procesamiento de Excel y CSV con PowerShell — recetas prácticas de agregación, cotejo y generación de informes
· Actualizado el: · Go Komura · PowerShell, Windows, CSV, Excel, Automatización, Eficiencia operativa, Scripts, Aprovechamiento de activos existentes
«Todos los meses abro en Excel el CSV que descargo del sistema central, lo agrego con una tabla dinámica, le doy formato y lo envío por correo»; «Coteo a simple vista los listados de dos sistemas para buscar las diferencias» ── recibimos con frecuencia consultas sobre este tipo de tareas rutinarias. Cada vez puede tomar solo treinta minutos, pero si se repite cada semana, cada mes y entre varias personas, al año se pierde una cantidad considerable de tiempo, y el copiar y pegar manual siempre acaba mezclando algún error.
PowerShell es una herramienta que encaja bien en este terreno. Viene incluido de serie en Windows (Windows PowerShell 5.1), permite leer un CSV como una «tabla de objetos» y encadenar en una sola canalización (pipeline) la agregación, el cotejo y la salida de resultados. Además, con el módulo comunitario ImportExcel se pueden generar incluso informes xlsx sin necesidad de tener Excel instalado.
Por otro lado, este terreno esconde trampas propias del entorno japonés. Como el valor predeterminado de la codificación de caracteres es completamente distinto entre Windows PowerShell 5.1 y PowerShell 7, es muy frecuente el caso de «funcionaba en mi equipo, pero en otro entorno aparecieron caracteres corruptos». En este artículo ordenamos, empezando por la trampa de la codificación de caracteres, las recetas prácticas que el personal de sistemas informáticos y de administración de pequeñas y medianas empresas necesita para automatizar con PowerShell las tareas de CSV y Excel.
Conocimientos previos: el público objetivo es el personal de sistemas informáticos y de administración de pequeñas y medianas empresas, pero este no es un artículo introductorio de PowerShell. Se da por conocido el manejo de variables y canalizaciones, la forma de escribir foreach o if, y el procedimiento para crear y ejecutar un archivo de script (.ps1). Si no está seguro en estos puntos, le recomendamos leer antes «Fundamentos de los comandos de PowerShell — operaciones básicas y uso seguro». También aparecen construcciones de nivel intermedio o superior, como las tablas hash (@{}), try/finally y la liberación de referencias COM, pero en cada punto donde aparecen se explica «para qué se escribe así». El entorno de ejecución previsto abarca tanto Windows PowerShell 5.1 como PowerShell 7, y se indica explícitamente cada vez que el comportamiento difiere entre ambos.
1. Conclusiones principales
- En la lectura y escritura de CSV, especifique siempre -Encoding. El valor predeterminado de codificación de Windows PowerShell 5.1 varía según el cmdlet, mientras que PowerShell 7 usa uniformemente UTF-8 sin BOM: los valores predeterminados de ambos son completamente distintos.1
- El valor predeterminado de Export-Csv en 5.1 es ASCII. Si olvida añadir -Encoding, el japonés se pierde ya en el momento de guardar. Además, Import-Csv en 5.1 interpreta como UTF-8 los archivos sin BOM, por lo que leer directamente un CSV en Shift_JIS produce caracteres corruptos.1
- La agregación se basa en la combinación de Group-Object (agrupación) y Measure-Object (suma, promedio, máximo y mínimo). Buena parte de las agregaciones que se hacen cada vez con una tabla dinámica de Excel se pueden sustituir con estos dos cmdlets.23
- Para cotejar dos CSV, use Compare-Object si solo necesita saber si hay diferencias, y una tabla hash si además necesita combinar columnas. Compare-Object indica mediante SideIndicator en cuál de los dos conjuntos está cada fila.4
- El módulo ImportExcel (comunitario) es la primera opción para leer y escribir xlsx. No requiere tener instalado el propio Excel y permite dar formato de tabla, aplicar estilos e incluso crear tablas dinámicas.5
- La automatización de Excel mediante COM es el último recurso. Es propensa a dejar procesos EXCEL.EXE residuales por falta de liberación de referencias (RCW)67, y además Microsoft no recomienda ni admite la automatización de Office en entornos desatendidos (servicios o ejecuciones programadas).8
- Antes de programar la ejecución en el Programador de tareas, fije explícitamente tres puntos: codificación, rutas y entorno de ejecución. La mayoría de los casos en que un script que funcionaba de forma interactiva se rompe al ejecutarse de forma programada se deben a estos tres puntos.18
2. Fundamentos de Import-Csv y Export-Csv — la trampa principal es la codificación de caracteres
Import-Csv lee un CSV como una tabla en la que «cada fila es un objeto y cada columna es una propiedad». La fila de encabezado se convierte en los nombres de columna, y todo el procesamiento posterior se puede escribir usando esos nombres de propiedad.9 Export-Csv hace lo contrario: escribe las columnas de un objeto en un CSV.10 Hasta aquí es sencillo. El problema es la codificación de caracteres.
Como indica explícitamente la documentación oficial, el valor predeterminado de codificación de Windows PowerShell 5.1 no es coherente entre cmdlets.1 A continuación se muestra en una tabla el alcance que afecta a la práctica en un entorno japonés.
| Operación | Valor predeterminado en Windows PowerShell 5.1 | Valor predeterminado en PowerShell 7 |
|---|---|---|
| Export-Csv | ASCII (se pierde el japonés)1 | UTF-8 sin BOM10 |
| Import-Csv (archivo sin BOM) | Se interpreta como UTF-81 | UTF-8 sin BOM |
| Get-Content (archivo sin BOM) | ANSI = Shift_JIS en entorno japonés1 | UTF-8 sin BOM |
Out-File y redirección (>) |
UTF-16LE (con BOM)1 | UTF-8 sin BOM |
Es decir, en 5.1 ocurren, tal como está previsto, situaciones como «al leer con Get-Content se leía correctamente como Shift_JIS, pero con Import-Csv aparece corrupto» o «al hacer Export-Csv, todo el japonés se convirtió en signos de interrogación». PowerShell 7 es coherente, ya que usa uniformemente UTF-8 sin BOM1, pero entonces si lee sin cambios un CSV en Shift_JIS procedente del sistema central, con el valor predeterminado aparece corrupto. La conclusión es una sola: especifique -Encoding tanto al leer como al escribir.
# Leer un CSV en Shift_JIS que exporta el sistema central
# Windows PowerShell 5.1: Default = página de códigos ANSI del sistema (Shift_JIS en entorno japonés)
$orders = Import-Csv -LiteralPath 'C:\data\orders.csv' -Encoding Default
# En PowerShell 7 se puede especificar por número de página de códigos (932 = Shift_JIS)
# $orders = Import-Csv -LiteralPath 'C:\data\orders.csv' -Encoding 932
# Conviene guardar la salida en UTF-8 con BOM, para que aparezca menos corrupta si se abre con doble clic en Excel
# PowerShell 7: UTF8 queda sin BOM, así que hay que indicar explícitamente utf8BOM para incluirlo
$orders | Export-Csv -LiteralPath 'C:\data\orders_out.csv' -NoTypeInformation -Encoding utf8BOM
# Windows PowerShell 5.1: el valor utf8BOM no existe. Especificando UTF8 ya se incluye el BOM
# $orders | Export-Csv -LiteralPath 'C:\data\orders_out.csv' -NoTypeInformation -Encoding UTF8
Desde PowerShell 6.2 en adelante, -Encoding también admite el número de página de códigos (932) o el nombre registrado, y desde 7.4 también se puede usar el valor ansi.1 Cabe señalar que -NoTypeInformation sirve para suprimir la línea #TYPE que 5.1 añade al principio; desde PowerShell 6 en adelante ya no se añade de forma predeterminada, por lo que no es necesario especificarla (aunque especificarla tampoco produce error).10 En un script que deba funcionar tanto en 5.1 como en 7, es más seguro incluirla siempre.
Las trampas propias del formato CSV en sí (la pérdida de los ceros iniciales al abrir en Excel, los valores que contienen comas o saltos de línea, las medidas contra la inyección) se tratan en detalle en «El CSV no es «simplemente texto»». Consulte «Codificación de caracteres y saltos de línea en Windows» para los fundamentos de la codificación de caracteres y del salto de línea.
Para que este artículo se pueda seguir por sí solo, resumimos por adelantado en tres líneas los puntos esenciales de esos artículos a los que delegamos. No abra el CSV con doble clic en Excel solo para comprobarlo (se pierden los ceros iniciales y los números largos se muestran en notación exponencial, de modo que, aunque crea haberlo comprobado, en realidad está viendo un valor ya alterado; para ver el contenido use un editor de texto o Import-Csv). Los valores que contienen comas, saltos de línea o comillas dobles deben ir entre comillas (Export-Csv hace esto automáticamente, así que no construya el CSV concatenando cadenas de texto por su cuenta). Un valor que empieza por = puede ser interpretado como fórmula por la hoja de cálculo (no abra sin cuidado un CSV recibido del exterior). En cuanto al salto de línea, los archivos que llegan desde equipos que no son Windows, o desde equipos antiguos, a veces usan LF; Import-Csv los lee sin problema, pero si escribe su propio procesamiento con -split, no dé por supuesto que siempre será CRLF.
3. Agregación — cómo crear el equivalente a una tabla dinámica con Group-Object y Measure-Object
Una agregación del tipo «cantidad de registros y suma del importe por departamento» sigue este esquema básico: agrupar con Group-Object y luego sumar cada grupo con Measure-Object.23
Primero fijemos la entrada. Las siguientes recetas parten de un archivo orders.csv como este (se supone una salida del sistema central, en codificación Shift_JIS).
OrderNo,Date,Dept,Customer,Amount
1001,2026/07/01,営業1課,株式会社A,120000
1002,2026/07/01,営業2課,株式会社B,80000
1003,2026/07/02,営業1課,株式会社C,45000
1004,2026/07/03,管理部,株式会社A,15000
1005,2026/07/03,営業2課,株式会社D,230000
# Se lee un CSV en Shift_JIS con PowerShell 7, así que se especifica explícitamente la página de códigos 932
# (en 7, -Encoding Default equivale a UTF-8 y produce corrupción; si se ejecuta en 5.1, especifique Default)
$orders = Import-Csv -LiteralPath 'C:\data\orders.csv' -Encoding 932
# Se obtiene la cantidad de registros y la suma del importe por departamento.
# Como todos los valores de Import-Csv son cadenas de texto, la clave está en convertirlos a [decimal] antes de sumar
# (el Sum de Measure-Object -Sum es de tipo Double, así que el importe se acumula manualmente como decimal
# para no perder precisión en sumas de muchas cifras o con decimales)
$summary = $orders | Group-Object -Property Dept | ForEach-Object {
$total = [decimal]0
foreach ($row in $_.Group) { $total += [decimal]$row.Amount }
[pscustomobject]@{
Dept = $_.Name # valor de la clave de agrupación
Count = $_.Count # cantidad de registros
Total = $total # suma del importe
}
}
$summary | Sort-Object -Property Total -Descending |
Export-Csv -LiteralPath 'C:\data\summary.csv' -NoTypeInformation -Encoding UTF8
Frente a las cinco filas anteriores, los valores que contiene $summary (ya ordenado) son los siguientes. En summary.csv se escriben estas tres filas junto con su encabezado.
| Dept | Count | Total |
|---|---|---|
| 営業2課 | 2 | 310000 |
| 営業1課 | 2 | 165000 |
| 管理部 | 1 | 15000 |
Cuando reproduzca el código, verifique primero que obtiene estas tres filas. Si el importe o la cantidad de registros no coinciden, sospeche de variaciones en la escritura del nombre del departamento (espacio de ancho completo, «営業1課») o del problema de conversión de tipo descrito a continuación.
Lo que más suele confundir es que todos los valores del CSV son cadenas de texto. Lo que devuelve Import-Csv es un conjunto de propiedades de tipo cadena9, así que antes de pasarlas a Measure-Object -Sum pensando que son numéricas, hay que convertirlas explícitamente con [decimal]. Si se olvida la conversión, en 5.1 el código puede llegar a funcionar de todos modos hasta cierto punto, y suele convertirse en un incidente que se detecta solo cuando el importe ya está desviado.
Measure-Object puede obtener simultáneamente, además de -Sum, también -Average, -Maximum y -Minimum.3 Además, usando Group-Object -AsHashTable se obtiene directamente una tabla hash de «clave → arreglo de filas de ese grupo», que también se puede aprovechar en el cotejo descrito más adelante.2 Reunimos el uso de estos comandos puntuales en «Recopilación de comandos prácticos de PowerShell».
4. Cotejo — cuándo usar Compare-Object y cuándo una combinación por tabla hash
4.1. Si solo necesita saber si hay diferencias: Compare-Object
Un cotejo típico como obtener «quiénes se añadieron y quiénes se eliminaron» comparando el listado CSV de ayer con el de hoy se resuelve más rápido con Compare-Object. Si se especifica -Property con la columna clave, la comparación se hace solo con el valor de esa columna, y el SideIndicator del resultado indica el aumento o la disminución según sea => (solo está en el lado de diferencia) o <= (solo está en el lado de referencia).4
# El @() envuelve el resultado porque, cuando hay 0 registros o el archivo está vacío,
# el resultado de Import-Csv puede ser $null. Si ReferenceObject/DifferenceObject es $null,
# Compare-Object se detiene con un error de terminación
$yesterday = @(Import-Csv -LiteralPath '.\users_0716.csv' -Encoding UTF8)
$today = @(Import-Csv -LiteralPath '.\users_0717.csv' -Encoding UTF8)
# Se compara solo por número de empleado. => son las filas que solo están hoy (altas), <= las que solo estaban ayer (bajas)
Compare-Object -ReferenceObject $yesterday -DifferenceObject $today -Property EmpNo |
Sort-Object -Property EmpNo |
Format-Table -Property EmpNo, SideIndicator
Hay dos puntos a tener en cuenta. Primero, al especificar -Property, en el resultado solo quedan esa columna y SideIndicator, así que si también quiere ver otras columnas como el nombre, tendrá que volver a buscar en los datos originales usando la clave del resultado. Segundo, si el lado de referencia o el de diferencia son $null (no cero filas, sino null), se produce un error que detiene la ejecución.4 El motivo por el que el código anterior envuelve el resultado de la lectura entre @() es precisamente esta medida preventiva. Como incluso un CSV de 0 registros se convierte en un arreglo vacío, incluso en un día tan extremo como «hoy se dieron de baja todos los empleados (es decir, todas las filas aparecen como <=)», se recibe el resultado como una diferencia real, no como un error de formato.
4.2. Si necesita además combinar columnas: tabla hash
El equivalente a un JOIN de SQL —«a partir del número de empleado del CSV de detalle, combinar el nombre y el departamento procedentes del CSV maestro»— sigue esta práctica habitual: convertir el lado maestro en una tabla hash de clave a fila y luego consultarla fila por fila. Un doble bucle (recorrer todo el detalle contra todo el maestro) se vuelve notablemente lento con miles de registros por miles de registros, pero con una tabla hash se puede procesar a velocidad práctica incluso con decenas de miles de registros.
Tomemos estas dos entradas.
master.csv
EmpNo,Name,Dept
E001,山田 太郎,営業1課
E002,佐藤 花子,管理部
details.csv
EmpNo,Amount
E001,120000
E003,45000
# Se convierte el lado maestro en una tabla hash de "número de empleado -> fila"
# Si la clave está duplicada, la última fila sobrescribe a la anterior; si puede haber duplicados, compruébelo de antemano
$master = @{}
foreach ($row in (Import-Csv -LiteralPath '.\master.csv' -Encoding UTF8)) {
$master[$row.EmpNo] = $row
}
# Se combina el detalle fila por fila. Las filas no encontradas no se descartan silenciosamente, se guardan en otro archivo
$unmatched = New-Object System.Collections.Generic.List[object]
$joined = foreach ($row in (Import-Csv -LiteralPath '.\details.csv' -Encoding UTF8)) {
$hit = $master[$row.EmpNo]
if ($null -eq $hit) {
$unmatched.Add($row)
continue
}
[pscustomobject]@{
EmpNo = $row.EmpNo
Name = $hit.Name
Dept = $hit.Dept
Amount = $row.Amount
}
}
$joined | Export-Csv -LiteralPath '.\joined.csv' -NoTypeInformation -Encoding UTF8
$unmatched | Export-Csv -LiteralPath '.\unmatched.csv' -NoTypeInformation -Encoding UTF8
Con esta entrada, en joined.csv aparece la única fila de E001 que existía en el maestro (E001,山田 太郎,営業1課,120000), y en unmatched.csv aparece la única fila de E003 que no estaba en el maestro (E003,45000).
La clave de esta receta está en las dos últimas líneas. No descarte silenciosamente las filas cuya clave no se pudo resolver. El valor de una tarea de cotejo reside precisamente en que una persona pueda revisar lo que no coincidió, así que hay que exportar siempre el conjunto unmatched y dejar constancia de su cantidad en el registro o en la salida estándar. En el ejemplo anterior, el hecho de poder darse cuenta de que E003 aparece en unmatched.csv es, en sí mismo, el sentido de ejecutar este proceso.
4.3. Variaciones en los nombres de columna y manejo del encabezado
En el campo, los nombres de columna suelen variar entre «社員番号», «社員No» o «emp_no». La solución se reduce a normalizarlos a nombres internos justo después de la lectura. Si el procesamiento posterior se escribe siempre usando solo los nombres internos, cuando cambie el formato bastará con corregir ese único punto de normalización.
# Un CSV sin fila de encabezado se le asigna con -Header (la primera línea se lee como datos)
# La codificación sigue el mismo criterio que en el capítulo 2: si es Shift_JIS, use 932 en 7 y Default en 5.1
$rows = Import-Csv -LiteralPath '.\no_header.csv' -Header 'EmpNo', 'Name', 'Dept' -Encoding 932
# Un CSV con encabezado en japonés se normaliza justo después de la lectura a nombres internos en alfabeto latino
$normalized = Import-Csv -LiteralPath '.\jinji.csv' -Encoding 932 |
Select-Object -Property @{ Name = 'EmpNo'; Expression = { $_.'社員番号' } },
@{ Name = 'Name'; Expression = { $_.'氏名' } },
@{ Name = 'Dept'; Expression = { $_.'所属部署' } }
Tenga en cuenta que -Header es para archivos sin fila de encabezado, y que al especificarlo, la primera línea también se lee como datos.9 Además, cuando el encabezado tiene celdas vacías, PowerShell asigna automáticamente nombres de columna provisionales que empiezan por H9, así que si no puede acceder a una propiedad con el nombre de columna esperado, sospeche primero de la fila de encabezado.
5. Trabajar con xlsx — ImportExcel como primera opción, COM como último recurso
5.1. Módulo ImportExcel — leer y escribir xlsx sin tener Excel instalado
En los lugares de trabajo japoneses es habitual que pidan el resultado agregado «no en CSV, sino en un archivo Excel, con colores en la tabla». Aquí la primera opción es el módulo comunitario ImportExcel, publicado en PowerShell Gallery. No requiere instalar el propio Excel, permite leer y escribir xlsx, y llega hasta dar formato de tabla, ajustar el ancho de columna y crear tablas dinámicas.5
# Solo la primera vez. Se instala para el usuario actual desde PowerShell Gallery (no requiere permisos de administrador)
# Si Windows PowerShell 5.1 no puede conectarse a Gallery, habilite antes TLS 1.2 y luego ejecute
# [Net.ServicePointManager]::SecurityProtocol =
# [Net.ServicePointManager]::SecurityProtocol -bor [Net.SecurityProtocolType]::Tls12
Install-Module -Name ImportExcel -Scope CurrentUser
# Se comprueba si se instaló y qué versión quedó
Get-InstalledModule -Name ImportExcel | Select-Object -Property Name, Version
# Se lee un xlsx (igual que con Import-Csv, la tabla de la hoja se convierte en un arreglo de objetos)
$budget = Import-Excel -Path 'C:\data\budget.xlsx' -WorksheetName '予算'
# Se exporta el resultado agregado del capítulo 3 a un xlsx con tabla, ajuste automático de ancho de columna y tabla dinámica
$summary | Export-Excel -Path 'C:\data\monthly-report.xlsx' `
-WorksheetName '集計' -TableName 'Summary' -AutoSize `
-IncludePivotTable -PivotRows Dept -PivotData @{ Total = 'Sum' }
En la puesta en marcha suele haber dos puntos donde uno se atasca. Uno es la conexión a PowerShell Gallery: como el uso de Gallery requiere TLS 1.2 o superior, en Windows PowerShell 5.1 puede que Install-Module falle si no se habilita explícitamente en la sesión, tal como se indica en el comentario anterior (si escribirlo cada vez resulta molesto, incluya esa línea en el script de perfil).11 El otro es la comprobación de la versión: la página de distribución de este módulo no indica explícitamente qué versiones de PowerShell admite.5 Si lo va a usar en una organización donde conviven 5.1 y 7, ejecute una vez Export-Excel en el PowerShell que realmente vaya a usar en producción, confirme que el archivo se abre correctamente, y solo entonces despliéguelo (si se va a ejecutar desde el Programador de tareas, compruébelo con la misma cuenta de ejecución con la que se iniciará la tarea; un módulo instalado con -Scope CurrentUser solo es visible para ese usuario).
Al tratarse de un producto comunitario, siga las normas de introducción de software de su organización al adoptarlo (el origen de distribución del módulo es PowerShell Gallery5). Aun así, comparado con la configuración de «instalar Excel en el servidor y manejarlo con COM», es una opción más razonable tanto en licencias como en estabilidad y mantenimiento. La comparación de métodos de generación de informes (COM, Open XML, plantillas) se ordena en «Cómo generar informes de Excel».
5.2. Automatización COM de Excel — si la usa, libere los recursos hasta el final, y no la ejecute de forma desatendida
Solo cuando quiera prescindir de una macro de un xls existente, o cuando necesite una función del propio Excel (recálculo, impresión, resolución de nombres definidos, etc.), manipule el propio Excel mediante COM. Desde PowerShell se puede iniciar con New-Object -ComObject, pero el problema conocido es la permanencia residual del proceso EXCEL.EXE. Si las referencias a los objetos COM (RCW) no se liberan, el proceso permanece aunque se llame a Quit().6 La solución desde .NET consiste en liberarlas explícitamente con Marshal.ReleaseComObject.7
# COM es el último recurso. Se garantiza con finally que "lo que se abre siempre se cierra, y las referencias siempre se liberan"
# Todas las llamadas COM posteriores al inicio de Excel se hacen dentro de try (para que la limpieza se ejecute
# incluso si se produce una excepción justo después de iniciar)
$excel = New-Object -ComObject Excel.Application
$book = $null
try {
$excel.Visible = $false
$excel.DisplayAlerts = $false # para que no se detenga en un cuadro de confirmación
$book = $excel.Workbooks.Open('C:\data\template.xlsx')
$sheet = $book.Worksheets.Item(1)
$sheet.Range('B2').Value2 = 12345
$book.SaveAs('C:\data\output.xlsx')
$book.Close($false)
}
finally {
# Aunque Quit falle (por ejemplo, si Excel deja de responder), la liberación y el GC siempre se ejecutan
try {
$excel.Quit()
}
finally {
# Si no se libera explícitamente el RCW de los objetos COM que se tocaron, EXCEL.EXE tiende a quedar residual
if ($sheet) { [void][System.Runtime.InteropServices.Marshal]::ReleaseComObject($sheet) }
if ($book) { [void][System.Runtime.InteropServices.Marshal]::ReleaseComObject($book) }
[void][System.Runtime.InteropServices.Marshal]::ReleaseComObject($excel)
[GC]::Collect()
[GC]::WaitForPendingFinalizers()
}
}
Lo complicado es que con solo encadenar puntos, como en $excel.Workbooks.Open(...), se genera una referencia a un objeto intermedio (en este ejemplo, la colección Workbooks) que también debe liberarse. En el código anterior, en rigor, todavía queda pendiente la referencia a Workbooks; para hacerlo con total seguridad habría que recibir también los objetos intermedios en variables y liberarlos. Esta dificultad de «escribir hasta el final la gestión de referencias» es, en sí misma, la razón para situar COM como último recurso. Los detalles del mecanismo y el criterio para sustituirlo se tratan en «El problema de EXCEL.EXE residual en la manipulación de Excel desde C#», y las llamadas COM/.NET desde PowerShell en general se tratan, publicado el mismo día, en «Práctica de llamadas a COM y .NET desde PowerShell».
Y hay otra restricción determinante. Microsoft no recomienda ni admite la automatización de Office desde clientes desatendidos y no interactivos (servicios, ejecuciones programadas, etc.). Está documentado oficialmente que Office se diseñó bajo la premisa de que existe un usuario interactivo, y que en entornos desatendidos puede presentar comportamientos inestables o bloqueos.8 En la propia documentación de SSIS también se recomienda, para entornos de ejecución desatendida, sustituir la conexión a Excel por métodos basados en CSV u Open XML.12 Una configuración del tipo «abrir Excel cada noche con el Programador de tareas para generar el informe» es, aunque parezca funcionar, un castillo de naipes. Los procesos que deban ejecutarse periódicamente deben orientarse hacia ImportExcel o hacia métodos basados en Open XML.
6. Lista de comprobación antes de programar la ejecución periódica en el Programador de tareas
Cuando un script que funcionaba en el equipo local se programa tal cual en el Programador de tareas, suele romperse por alguno de los siguientes motivos.
- Codificación de caracteres: el comportamiento no cambia entre la ejecución interactiva y la programada, pero es un incidente frecuente que un script que «funcionaba por casualidad en el 7 del equipo local» aparezca corrupto porque el comando de inicio de la tarea era
powershell.exe(= 5.1), lo que cambia la codificación predeterminada. Tal como se explicó en el capítulo 2, si se especifica-Encodingtanto al leer como al escribir, el resultado será el mismo sin importar con cuál se inicie.1 - Rutas: no use rutas relativas que dependan del directorio actual; escríbalas como rutas absolutas o basadas en
$PSScriptRoot. Las unidades de red (como Z:) no son visibles en la sesión de ejecución programada, así que use rutas UNC. - Entorno de ejecución: indique explícitamente en el comando de inicio con qué PowerShell se va a ejecutar (powershell.exe o pwsh.exe). Consulte, publicado el mismo día, «Diferencias entre Windows PowerShell 5.1 y PowerShell 7» para la diferencia entre 5.1 y 7 y el criterio de uso.
- No programe procesos que incluyan COM de Excel: como se explicó en el capítulo anterior, la ejecución desatendida no está admitida.8
Cómo dejar registros y evidencias al ejecutar desde el Programador de tareas se explica en detalle en «PowerShell avanzado — automatizar de forma segura la investigación de registros, el archivado y la generación de informes».
7. Prácticas recomendadas (tabla de decisión)
| Punto | Opciones | Criterio de decisión |
|---|---|---|
| Codificación de caracteres del CSV | Dejarlo en el valor predeterminado / Especificar -Encoding | Especifíquelo siempre. Como 5.1 y 7 tienen valores predeterminados distintos, esto previene de forma estructural que aparezcan caracteres corruptos al cambiar de entorno1 |
| Agregación | A mano en Excel / Group-Object + Measure-Object | Si se repite cada semana o cada mes, conviértalo en script. El procedimiento queda como código y se vuelve reproducible23 |
| Cotejo de dos CSV | Compare-Object / Combinación con tabla hash | Si solo necesita saber si hay diferencias, Compare-Object. Si necesita combinar otras columnas, tabla hash. Las filas no coincidentes deben exportarse siempre aparte4 |
| Salida en xlsx | Conformarse con CSV / ImportExcel / COM | Si hace falta formato o tabla dinámica, ImportExcel (no requiere el propio Excel). COM solo cuando se necesita una función del propio Excel, como ejecutar una macro5 |
| Forma de ejecución de COM de Excel | Ejecución desatendida en el Programador de tareas / Solo ejecución manual e interactiva | La automatización de Office de forma desatendida no está admitida. Los procesos que deban desatenderse deben orientarse hacia métodos que no requieran el propio Excel8 |
| Ejecución periódica | Ejecutarlo manualmente cada vez / Programador de tareas | Programe la tarea solo después de fijar explícitamente -Encoding, la ruta absoluta y el PowerShell que se va a iniciar |
8. Resumen
- La trampa principal de la automatización de CSV es la codificación de caracteres. En 5.1 el valor predeterminado varía según el cmdlet (Export-Csv usa ASCII); en 7 es uniformemente UTF-8 sin BOM. Especificando -Encoding tanto al leer como al escribir se elimina la diferencia entre entornos.
- La agregación se basa en Group-Object + Measure-Object. Como los valores del CSV son todos cadenas de texto, conviértalos a numéricos antes de agregar.
- Para el cotejo, use Compare-Object si solo necesita saber si hay diferencias, y una tabla hash si necesita combinar datos. Exporte siempre a un archivo aparte las filas que no se pudieron resolver, para que una persona pueda revisarlas.
- Las variaciones en los nombres de columna se absorben normalizando justo después de la lectura, y el resto del procesamiento se escribe usando solo los nombres internos.
- El xlsx se puede leer y escribir sin el propio Excel mediante el módulo ImportExcel. COM es el último recurso, para cuando se puede escribir hasta el final el procesamiento de liberación, y no debe usarse en ejecución desatendida.
- Antes de programar la ejecución en el Programador de tareas, fije explícitamente la codificación, la ruta y el PowerShell que se va a iniciar.
Artículos relacionados
- El CSV no es «simplemente texto» — la práctica del CSV en aplicaciones empresariales de C# (codificación de caracteres, compatibilidad con Excel, medidas contra la inyección)
- Recopilación de comandos prácticos de PowerShell — ampliar las pequeñas funciones que se usan a diario
- Cómo generar informes de Excel - COM/Open XML/plantillas
- El problema de EXCEL.EXE residual en la manipulación de Excel desde C# — patrones de liberación de referencias COM y criterio de sustitución
- Codificación de caracteres y saltos de línea en Windows - fundamentos de los caracteres corruptos y de CRLF/LF
- Diferencias entre Windows PowerShell 5.1 y PowerShell 7 — guía práctica para la migración de scripts internos
Áreas de consultoría relacionadas
KomuraSoft LLC se encarga de la creación de scripts de automatización de tareas rutinarias mediante CSV y Excel, de la migración fuera del procesamiento de informes dependiente de COM de Excel (sustitución por ImportExcel u Open XML), y de la investigación de incidencias como «aparecen caracteres corruptos solo en un entorno determinado» o «queda un proceso EXCEL.EXE residual».
- Desarrollo de aplicaciones Windows
- Consultoría técnica y revisión de diseño
- Migración de activos heredados
- Contacto
Referencias
-
Microsoft Learn, about_Character_Encoding. Sobre que el valor predeterminado de codificación de Windows PowerShell 5.1 no es coherente entre cmdlets (Export-Csv usa ASCII, Set-Content/Get-Content usan el ANSI de Default, Out-File y la redirección usan UTF-16LE, e Import-Csv interpreta como UTF-8 los archivos sin BOM), que PowerShell 6 en adelante usa uniformemente UTF-8 sin BOM como valor predeterminado, que desde 6.2 se puede especificar -Encoding por número de página de códigos o nombre registrado, y sobre el valor ansi de 7.4. ↩ ↩2 ↩3 ↩4 ↩5 ↩6 ↩7 ↩8 ↩9 ↩10 ↩11 ↩12
-
Microsoft Learn, Group-Object. Sobre que Group-Object agrupa objetos por el valor de la propiedad indicada y devuelve la cantidad y los elementos de cada grupo, y que -AsHashTable permite obtener una tabla hash de clave a grupo. ↩ ↩2 ↩3 ↩4
-
Microsoft Learn, Measure-Object. Sobre que Measure-Object puede calcular, además de la cantidad, Sum, Average, Maximum y Minimum (y la desviación estándar). ↩ ↩2 ↩3 ↩4
-
Microsoft Learn, Compare-Object. Sobre que Compare-Object compara dos conjuntos de objetos e indica con SideIndicator (<= / => / ==) en cuál de los lados está cada elemento, que -Property permite comparar solo las columnas indicadas, y que se produce un error de terminación si el lado de referencia o el de diferencia es null. ↩ ↩2 ↩3 ↩4
-
PowerShell Gallery, ImportExcel. Origen de distribución del módulo comunitario ImportExcel. Sobre que es un módulo que permite leer y escribir xlsx, crear tablas dinámicas y aplicar formato sin necesidad de instalar el propio Excel, el método de instalación con Install-Module, y que la página de distribución no indica la versión mínima requerida de PowerShell. ↩ ↩2 ↩3 ↩4 ↩5
-
Microsoft Learn, An active Excel process continues to run after using a VBA macro to programmatically quit Excel. Sobre que, aunque se cierre el libro, se llame a Quit y se limpien las referencias, si una referencia a Excel o a alguno de sus miembros se mantiene de forma global, el proceso EXCEL.EXE sigue quedando residual. ↩ ↩2
-
Microsoft Learn, Marshal.ReleaseComObject(Object) Method. Sobre que ReleaseComObject reduce el contador de referencias del RCW (contenedor invocable en tiempo de ejecución) asociado a un objeto COM, que se usa para controlar explícitamente la vida útil del objeto COM, y que hay que tener cuidado porque acceder a un objeto ya liberado produce una excepción. ↩ ↩2
-
Microsoft Learn, Considerations for unattended automation of Office in the Microsoft 365 for unattended RPA environment. Sobre que Microsoft no recomienda ni admite la automatización de aplicaciones de Office desde clientes desatendidos y no interactivos (ASP, DCOM, servicios NT, etc.), que Office puede presentar comportamientos inestables o bloqueos en entornos desatendidos, y que se recomiendan alternativas como Open XML. ↩ ↩2 ↩3 ↩4 ↩5
-
Microsoft Learn, Import-Csv. Sobre que Import-Csv crea objetos personalizados con forma de tabla a partir de un CSV, que la primera línea se interpreta como encabezado, que -Header permite asignar nombres de columna a archivos sin encabezado, que a las celdas de encabezado vacías se les asignan nombres de columna provisionales que empiezan por H, y que los valores se leen como cadenas de texto. ↩ ↩2 ↩3 ↩4
-
Microsoft Learn, Export-Csv. Sobre que Export-Csv escribe cada propiedad de un objeto como una columna del CSV, que el valor predeterminado de codificación en PowerShell 7 es UTF8NoBOM, y que desde PowerShell 6.0 la línea #TYPE ya no se escribe de forma predeterminada, quedando NoTypeInformation implícito. ↩ ↩2 ↩3
-
Microsoft Learn, Install a package manager for PowerShell. Sobre que el acceso a PowerShell Gallery requiere seguridad de la capa de transporte (TLS) 1.2 o superior, el comando para habilitar TLS 1.2 en la sesión, y cómo dejarlo escrito en el script de perfil. ↩
-
Microsoft Learn, Import data from Excel or export data to Excel with SQL Server Integration Services (SSIS). Sobre que el uso de componentes de Excel no está admitido en entornos desatendidos y no interactivos, y que en el procesamiento automatizado de producción se recomienda sustituirlos por métodos basados en archivos planos (CSV) o en Open XML. ↩
Artículos relacionados
Artículos recientes con las mismas etiquetas para profundizar en temas cercanos.
Cómo invocar COM y .NET desde PowerShell en la práctica ── ampliar de un salto el alcance de sus scripts
Cómo invocar clases .NET desde PowerShell, integrar C# y la API Win32 con Add-Type, operar COM, gestionar los procesos residuales de Exce...
Diseño de parámetros y modularización de scripts de PowerShell — de un «script que funciona» a un «script que se puede entregar»
Explicamos cómo llevar un script de PowerShell a una calidad entregable: param, [CmdletBinding()], validación, pipeline, -WhatIf, módulos...
Diferencias entre Windows PowerShell 5.1 y PowerShell 7 ── Guía práctica de migración de scripts internos
Explicamos la relación entre Windows PowerShell 5.1 y PowerShell 7 (coexistencia y pwsh.exe), la política oficial de no añadir funciones ...
Dónde mirar cuando un script de PowerShell es lento — claves de arrays, pipeline y cruces de datos
Analizamos las causas típicas de la lentitud en PowerShell: += en arrays, pipeline vs. foreach, cruces con tablas hash, E/S de archivos y...
Deje de usar Write-Host — Flujos de salida de PowerShell y diseño de registros
Explica los seis flujos de salida de PowerShell, los problemas de Write-Host y su uso correcto, por qué se contamina el valor de retorno ...
Temas relacionados
Estas páginas sitúan el tema en un contexto más amplio de servicios y decisiones.
Temas técnicos de Windows
Portal sobre desarrollo de Windows, investigación de fallos y aprovechamiento de activos existentes.
Servicios relacionados con este tema
El artículo está directamente relacionado con los siguientes servicios.
Desarrollo de aplicaciones para Windows
Aplicaciones empresariales, integración de dispositivos y herramientas de comunicación, de los requisitos al desarrollo.
Reutilización y migración de activos existentes
Reutilización y migración de activos COM / ActiveX / OCX y dependencias de 32 o 64 bits.
Preguntas frecuentes
Preguntas habituales en las consultas sobre el tema del artículo.
- ¿Por qué se producen caracteres corruptos al leer y escribir CSV con PowerShell?
- Porque la codificación de caracteres predeterminada es distinta entre Windows PowerShell 5.1 y PowerShell 7. En 5.1 el valor predeterminado varía según el cmdlet: Export-Csv usa ASCII (se pierde el japonés), Import-Csv interpreta como UTF-8 los archivos sin BOM, y Get-Content usa ANSI (Shift_JIS en un entorno japonés). En PowerShell 7 el valor predeterminado es uniformemente UTF-8 sin BOM. La única medida segura es especificar siempre -Encoding, tanto al leer como al escribir.
- ¿Cómo se cotejan (comprueban las diferencias entre) dos archivos CSV?
- Si solo necesita saber si hay diferencias, lo más sencillo es usar Compare-Object con -Property indicando la columna clave. SideIndicator le indica las filas añadidas (=>) y las eliminadas (<=). Si además necesita combinar columnas como el nombre o el departamento procedentes del otro CSV, la práctica habitual es convertir el archivo maestro en una tabla hash de clave a fila y luego consultarla fila por fila; este enfoque procesa con rapidez incluso decenas de miles de registros. Las filas cuya clave no se encuentra no deben descartarse silenciosamente: hay que exportarlas a un archivo aparte para que una persona pueda revisarlas.
- ¿Necesito tener instalado Excel para crear un archivo Excel (xlsx) con PowerShell?
- No es necesario. Con el módulo comunitario ImportExcel puede leer y escribir xlsx, dar formato de tabla, aplicar estilos e incluso crear tablas dinámicas en una máquina sin Excel instalado. Se instala con Install-Module desde PowerShell Gallery. Manipular el propio Excel mediante COM debe considerarse el último recurso, reservado para los casos en que se necesita una función del propio Excel, como ejecutar una macro.
- ¿Por qué queda un proceso EXCEL.EXE residual al automatizar Excel desde PowerShell mediante COM?
- Porque si las referencias a los objetos COM (RCW) no se liberan, el proceso de Excel no finaliza aunque se llame a Quit. Cada vez que se accede a un libro o a un rango de celdas se crean referencias a objetos intermedios adicionales, así que hay que liberarlas explícitamente con Marshal.ReleaseComObject al terminar y forzar la recolección con GC.Collect. Además, Microsoft no recomienda ni admite la automatización de Office en entornos desatendidos como el Programador de tareas, por lo que los procesos que deban ejecutarse periódicamente deben orientarse hacia métodos que no requieran el propio Excel, como ImportExcel.
- ¿Es mejor agregar datos de CSV con una tabla dinámica de Excel o con PowerShell?
- Para un análisis puntual, Excel es suficiente. Si el mismo procedimiento se repite cada semana o cada mes, vale la pena convertirlo en un script con Group-Object y Measure-Object. El procedimiento queda como código en lugar de como documentación, de modo que el resultado es reproducible aunque cambie la persona responsable, y se puede enlazar con la ejecución periódica en el Programador de tareas. Si el resultado agregado se exporta a xlsx con ImportExcel, quien lo recibe puede tratarlo como el archivo de Excel habitual.
Perfil del autor
Página de presentación del autor del artículo.
Go Komura
Representante de KomuraSoft LLC
Especializado en desarrollo de software para Windows, consultoría técnica e investigación de fallos, sobre todo en proyectos con sistemas existentes y errores difíciles de reproducir.