Distribución y actualización interna de módulos de PowerShell ── PSResourceGet y el repositorio interno

· Actualizado el: · · PowerShell, Módulos, Distribución, Gestión de versiones, Mejora operativa, Mantenibilidad, Sistemas de información, Automatización

«Escribí un script útil, así que lo dejé en la carpeta compartida» ── a partir de ese momento empieza a acumularse, en silencio, deuda de mantenimiento. Alguien lo copia y lo modifica localmente, las correcciones al archivo original no se reflejan en esa copia, y nadie sabe qué versión está funcionando dónde. Unos años después, en la carpeta compartida conviven informe.ps1, informe_v2.ps1 e informe_corregido_final.ps1.

En el mundo de PowerShell, la respuesta a este problema es clara: convertirlo en un módulo, asignarle una versión y distribuirlo desde un repositorio. Con solo eso ya se puede responder a preguntas como «¿qué versión está instalada?» y «¿llega la actualización a todo el mundo?». Y desde PowerShell 7.4, el mecanismo para hacerlo (PSResourceGet) viene incluido de serie.

En este artículo se explica cómo convertir en módulo los scripts que se comparten internamente, levantar un repositorio interno y gestionar su distribución y actualización, con una configuración realista que no requiere montar un servidor dedicado. Para la modularización de funciones en sí, conviene leer antes «Diseño de parámetros y modularización en PowerShell», lo que facilitará la comprensión.

Público objetivo y entorno previsto

Elemento Contenido
Público objetivo Personal de sistemas de información/operaciones que distribuye archivos .ps1 desde una carpeta compartida
Versiones cubiertas Tanto Windows PowerShell 5.1 como PowerShell 7.x. Sin embargo, PSResourceGet solo viene incluido a partir de PowerShell 7.4; en 5.1 hace falta instalarlo de antemano tanto en el lado de distribución como en el de uso (capítulo 5)1
Coexistencia con el entorno existente PSResourceGet puede coexistir con el PowerShellGet 2.2.5 tradicional. Se puede introducir sin reescribir los scripts existentes1
Entorno de verificación de los ejemplos El código de ejemplo distribuido al final del artículo se ha verificado ejecutándolo en PowerShell 7.6
Permisos necesarios Se requieren permisos de administrador para crear y configurar la carpeta compartida del repositorio, y también para instalar con -Scope AllUsers

1. Conclusión primero

  • PSResourceGet (Microsoft.PowerShell.PSResourceGet) viene incluido en PowerShell 7.4. Coexiste con el PowerShellGet 2.2.5 tradicional, por lo que se puede usar sin romper los scripts existentes. No viene incluido en Windows PowerShell 5.1, así que hay que instalarlo de antemano tanto en el lado de uso como en el de distribución (siguiente capítulo).1
  • El repositorio interno se puede poner en marcha con un recurso compartido de archivos. Basta con indicar una ruta UNC a Register-PSResourceRepository. No hace falta un servidor dedicado.2
  • Considere obligatorio el manifiesto (.psd1). Sin un número de versión no se puede ni actualizar ni aislar problemas.3
  • No use un comodín en FunctionsToExport; declare las funciones de forma explícita en un arreglo. Esto acelera la búsqueda de comandos y evita la exposición no intencionada de funciones internas.3
  • Use versionado semántico. Si los cambios que rompen compatibilidad no se reflejan en la versión mayor, quien consume el módulo no podrá actualizar con confianza.4
  • Para publicar se usa Publish-PSResource, para obtener Install-PSResource y para actualizar Update-PSResource.56
  • Las rutas de búsqueda de módulos son distintas en 5.1 y en 7. Si no se entiende la diferencia en $env:PSModulePath, se produce el clásico «lo instalé pero no lo encuentra».7
  • Si la directiva de ejecución es AllSigned, es obligatorio firmar con Authenticode cada archivo de script. La firma de catálogo sirve para verificar la integridad del paquete, pero no satisface la directiva de ejecución.8
  • Sea prudente con la actualización automática de los módulos de los que dependen las ejecuciones desatendidas. Lo más seguro es verificarlos y luego subir de versión de forma planificada.

En conjunto, el mecanismo que se construye en este artículo tiene la siguiente forma.

Publish-PSResourceInstall-PSResourceInstall-PSResource -Scope AllUsersLado de desarrolloEl personal de sistemas escribe el móduloPruebas y análisis estáticoPester / PSScriptAnalyzerRepositorio internoRecurso compartido (ruta UNC) ofeed compatible con NuGetPC del lado de usoFind / Install / Update-PSResourceServidor del lado de usoEjecución desatendida (batch nocturno, etc.)

Los comandos que aparecen sobre las flechas son el núcleo de este artículo. El repositorio se registra una única vez, al principio, tanto en el lado de distribución como en el de uso, mediante Register-PSResourceRepository (capítulo 5); a partir de ahí todo funciona solo con los comandos de publicación, obtención y actualización (capítulo 6). Que únicamente el responsable de distribución tenga permiso de escritura es el único límite de seguridad de este diagrama.

2. Los problemas del «.ps1 en la carpeta compartida»

Antes que nada, aclaremos qué problema se busca resolver.

Síntoma Causa raíz
No se sabe qué versión está en ejecución No existe el concepto de número de versión
Las correcciones no llegan a todo el mundo Cada quien tiene su propia copia
No se sabe quién lo usa No queda registro de quién lo obtuvo (*este punto, como se explica más adelante, depende de la forma de distribución elegida)
Se rompe solo en algunos entornos Las dependencias (módulos necesarios, versión de PowerShell) no están declaradas
Se quiere corregir, pero no se puede prever el alcance del impacto No hay distinción entre funciones públicas y funciones internas

La modularización y la distribución mediante repositorio resuelven directamente los cuatro primeros puntos. Sin embargo, «quién lo usa» depende únicamente del mecanismo de distribución elegido. El repositorio de recurso compartido de archivos que se presenta a partir del próximo capítulo es cómodo, pero, a cambio, no deja registro de quién lo obtuvo y cuándo (Get-InstalledPSResource solo muestra el estado del equipo en el que se ejecuta ese comando). Si necesita conocer el estado de adopción, combínelo con alguna de las siguientes opciones.

3. La configuración mínima de un módulo

La forma mínima de un módulo distribuible consta de tres elementos: la carpeta, el .psm1 y el .psd1.

KsOps\
  KsOps.psd1     ← manifiesto (versión, funciones públicas, dependencias)
  KsOps.psm1     ← implementación (o dot-sourcing desde las carpetas Public/Private)
  Public\
    Get-KsShareUsage.ps1
    Invoke-KsArchive.ps1
  Private\
    ConvertTo-KsSize.ps1

El manifiesto se genera como plantilla con New-ModuleManifest, y luego se completan los campos necesarios.3

$manifest = @{
    Path              = '.\KsOps\KsOps.psd1'
    RootModule        = 'KsOps.psm1'
    ModuleVersion     = '1.0.0'
    GUID              = [guid]::NewGuid().Guid
    Author            = 'Departamento de Sistemas de Información'
    CompanyName       = 'Empresa de Ejemplo S.A.'
    Description       = 'Módulo común de scripts de operación interna (inventario del servidor de archivos y archivado)'
    PowerShellVersion = '5.1'
    CompatiblePSEditions = @('Desktop', 'Core')      # si se usa tanto en 5.1 como en 7
    # No usar comodín. Declarar explícitamente solo lo que se publica
    FunctionsToExport = @('Get-KsShareUsage', 'Invoke-KsArchive')
    CmdletsToExport   = @()
    VariablesToExport = @()
    AliasesToExport   = @()
    RequiredModules   = @()                          # declarar aquí las dependencias, si las hay
    Tags              = @('internal', 'operations')
    ProjectUri        = 'https://git.example.co.jp/it/ksops'
}
New-ModuleManifest @manifest

El .psm1 se puede escribir con una plantilla estándar que carga los scripts de Public/Private y exporta solo las funciones públicas.

# KsOps.psm1
$public  = @(Get-ChildItem -Path "$PSScriptRoot\Public\*.ps1"  -ErrorAction SilentlyContinue)
$private = @(Get-ChildItem -Path "$PSScriptRoot\Private\*.ps1" -ErrorAction SilentlyContinue)

foreach ($file in @($public + $private)) {
    try   { . $file.FullName }
    catch { throw "Error al cargar el módulo: $($file.FullName) ── $_" }
}

# Solo se publican las funciones de Public (debe coincidir con lo declarado en el manifiesto)
Export-ModuleMember -Function $public.BaseName

Hay dos razones para no usar un comodín en FunctionsToExport. Una es el rendimiento del descubrimiento de comandos: al declararlo explícitamente, se puede determinar «qué comando está en qué módulo» sin analizar el módulo en sí. La otra es de diseño: si los auxiliares internos se pueden invocar desde fuera, se convierten de facto en una API pública y ya no se podrán modificar más adelante.3

En el ejemplo anterior se declaran tanto Desktop (Windows PowerShell 5.1) como Core (PowerShell 7) en CompatiblePSEditions, pero declararlo no basta para que funcione en ambos. La preparación necesaria para usar PSResourceGet en 5.1 se explica en el capítulo 5, y el «lo instalé pero no lo encuentra» que provoca la diferencia de rutas de búsqueda entre 5.1 y 7 se explica en el capítulo 7. Si tiene previsto distribuir el módulo para ambas ediciones, lea antes esos dos capítulos.

4. Cómo decidir el versionado

Que quien consume el módulo pueda actualizar con confianza depende de cómo se asignen los números de versión. Adopte el versionado semántico (mayor.menor.parche) y exprese siempre los cambios que rompen compatibilidad en la versión mayor.4

Tipo de cambio Qué componente subir
Cambio de nombre de parámetro, eliminación de una función, cambio en la forma del valor de retorno Mayor (1.2.3 → 2.0.0)
Adición de funciones o parámetros (lo existente sigue funcionando igual) Menor (1.2.3 → 1.3.0)
Solo corrección de errores Parche (1.2.3 → 1.2.4)

Cuando se necesite distribuir una versión de verificación, se puede usar una versión de prelanzamiento. Si se establece una cadena como beta1 en PrivateData.PSData.Prerelease del manifiesto (el guion que la separa de la versión se añade automáticamente, quedando 1.3.0-beta1), esa versión no se obtiene con una instalación normal y solo se instala cuando se especifica explícitamente -Prerelease. En la cadena solo se pueden usar caracteres alfanuméricos ASCII y guiones; no se admiten puntos ni +.4

Para el tratamiento de los cambios que rompen compatibilidad, resultan útiles los criterios de diseño de interfaces (véase «Compatibilidad hacia atrás de interfaces DLL y COM»).

5. Crear el repositorio interno ── un recurso compartido de archivos es suficiente

PSResourceGet puede tratar una carpeta de un recurso compartido de archivos como repositorio.2 Esta es la configuración con menor coste de implementación.

Primero, confirmemos el requisito previo. Viene incluido a partir de PowerShell 7.4, pero no está presente en Windows PowerShell 5.1. Para usar en 5.1 los comandos que siguen (Register-PSResourceRepository, etc.), instale el módulo de antemano.1

# 【Solo Windows PowerShell 5.1】Instalar PSResourceGet.
# Como 5.1 y 7 cargan módulos desde rutas distintas, ejecútelo en la edición que vaya a usar
if (-not (Get-Module -ListAvailable -Name Microsoft.PowerShell.PSResourceGet)) {
    Install-Module -Name Microsoft.PowerShell.PSResourceGet -Scope AllUsers -Force
}
# 【Ejecutar una sola vez, tanto en el lado de distribución como en el de uso】Registrar el repositorio interno
# Trusted: se trata como confiable por ser material de distribución interna / Priority: se busca antes que PSGallery
$repo = @{
    Name     = 'KsInternal'
    Uri      = '\\fileserver\PSRepository'
    Trusted  = $true
    Priority = 10
}
Register-PSResourceRepository @repo

Get-PSResourceRepository | Format-Table Name, Uri, Trusted, Priority

Configure los permisos de acceso de la carpeta compartida como «solo el responsable de distribución puede escribir, los usuarios solo pueden leer». Si esto queda demasiado abierto, se convierte en una vía por la que cualquiera podría distribuir código arbitrario a toda la empresa. Configure ambos tipos de permisos, los del recurso compartido y los de NTFS (el permiso efectivo será el más restrictivo de los dos).9

# Ejecutar en el servidor de archivos (requiere permisos de administrador)
$path = 'D:\PSRepository'
$null = New-Item -Path $path -ItemType Directory -Force

# Recurso compartido: los usuarios solo leen; solo el responsable de distribución escribe
New-SmbShare -Name 'PSRepository' -Path $path `
    -ReadAccess 'EXAMPLE\Domain Users' -ChangeAccess 'EXAMPLE\Responsables de distribución de módulos'

# NTFS: no es "añadir", sino "sustituir por una lista de permitidos".
# Como New-Item -Force también tiene éxito sobre una carpeta ya existente, si esta
# carpeta estuvo antes publicada en otro recurso compartido, o alguien recibió permiso
# de modificación de forma temporal, con solo añadir icacls /grant esa vía de escritura
# seguiría ahí. Y como todos los equipos registran este repositorio como Trusted, la persona que la conserve podría distribuir código a toda la empresa
$acl = Get-Acl -Path $path
$acl.SetAccessRuleProtection($true, $false)          # cortar la herencia sin arrastrar las ACE heredadas
foreach ($ace in @($acl.Access)) {                   # eliminar también las ACE asignadas directamente
    [void]$acl.RemoveAccessRuleSpecific($ace)
}

# Solo se conservan los sujetos indicados aquí
$allow = @(
    @{ Id = 'EXAMPLE\Responsables de distribución de módulos'; Rights = 'Modify' } # pueden publicar
    @{ Id = 'EXAMPLE\Domain Users';       Rights = 'ReadAndExecute' } # solo obtienen
    @{ Id = 'BUILTIN\Administrators';     Rights = 'FullControl' }
    @{ Id = 'NT AUTHORITY\SYSTEM';        Rights = 'FullControl' }
)
foreach ($a in $allow) {
    $acl.AddAccessRule([System.Security.AccessControl.FileSystemAccessRule]::new(
        $a.Id, $a.Rights, 'ContainerInherit, ObjectInherit', 'None', 'Allow'))
}
# Aplicar "vaciar" y "volver a cargar" con Set-Acl por separado deja un instante en el
# que nadie tiene acceso. Aplíquelo de una sola vez
Set-Acl -Path $path -AclObject $acl

icacls $path        # confirmar el resultado; comprobar visualmente que no hay sujetos inesperados

No se conforme con añadir simplemente icacls /grant. /grant no elimina las ACE existentes. Si hay otra persona con una vía para escribir en esta carpeta, esa vía se mantiene intacta.9 Y, además, este repositorio parte de la premisa de que todos los equipos lo registran con Trusted = $true. Los módulos alojados en un repositorio de confianza se instalan sin confirmación. La persona que conserve ese acceso podría distribuir código arbitrario a todo el entorno de ejecución de PowerShell de la empresa, momento en el que se derrumba la premisa de que «solo el responsable de distribución puede publicar».

Tampoco basta con cortar la herencia (SetAccessRuleProtection($true, $false)), porque este método solo gestiona las ACE heredadas; las ACE asignadas directamente a esa carpeta quedan fuera de su alcance.9 Por eso, como en el ejemplo anterior, primero se eliminan también las ACE asignadas directamente y luego se vuelven a introducir solo los sujetos de la lista de permitidos. Si la carpeta se acaba de crear, el resultado es el mismo, pero se trata de un tipo de problema que solo aparece cuando se reutiliza una carpeta existente, así que es más seguro incluir este paso en el procedimiento.

Haga que «el responsable de distribución» sea un grupo, no una cuenta individual. En la práctica ocurre que el traslado de la persona a otro puesto deja sin posibilidad de actualizar el módulo. Si utiliza un feed compatible con NuGet que requiera autenticación, emita para el lado de uso credenciales de solo lectura, separadas de las credenciales (clave de API) destinadas a la publicación. Para el diseño de permisos de la carpeta compartida en sí, consulte también «Inventariar un servidor de archivos con PowerShell».

Para una operación más formal, registre un feed compatible con NuGet como Azure Artifacts o GitHub Packages. En los repositorios que requieren autenticación, se puede configurar para que las credenciales se obtengan desde el almacén de SecretManagement (véase «Manejo seguro de credenciales en PowerShell»).2

6. Publicación, obtención y actualización

# 【Lado de distribución】Publicar el módulo
Publish-PSResource -Path .\KsOps -Repository 'KsInternal'

# 【Lado de uso】Buscarlo e instalarlo
Find-PSResource -Name 'KsOps' -Repository 'KsInternal'
Install-PSResource -Name 'KsOps' -Repository 'KsInternal' -Scope CurrentUser

# Instalar con la versión fijada (recomendado para servidores de producción)
Install-PSResource -Name 'KsOps' -Version '1.2.3' -Repository 'KsInternal' -Scope AllUsers

# Actualizar
Update-PSResource -Name 'KsOps' -Repository 'KsInternal'

# Comprobar qué hay instalado (solo refleja el estado de "este equipo")
Get-InstalledPSResource -Name 'KsOps' | Format-Table Name, Version, Repository, InstalledDate

# Si se quiere conocer la adopción en toda la empresa, ejecutarlo en cada equipo y consolidar
Invoke-Command -ComputerName $servers -ScriptBlock {
    Get-InstalledPSResource -Name 'KsOps' -ErrorAction SilentlyContinue |
        Select-Object Name, Version
} | Sort-Object PSComputerName

Distinguir el uso de -Scope es importante. Los módulos que utiliza una ejecución desatendida del Programador de tareas deben instalarse en AllUsers (o en el entorno propio de la cuenta de servicio). La causa típica de «en mi entorno funciona, pero el batch nocturno falla con no se reconoce el cmdlet» es haberlo instalado en el ámbito CurrentUser.67

Además, como -Scope AllUsers escribe en una ubicación común a todos los usuarios (bajo Program Files), fallará si PowerShell no se inició como administrador.7 Si en la primera instalación aparece «acceso denegado», compruebe antes que la sesión está elevada.

7. Rutas de búsqueda de módulos y las diferencias entre 5.1 y 7

PowerShell busca los módulos en las carpetas enumeradas en $env:PSModulePath. Las rutas predeterminadas son distintas en Windows PowerShell 5.1 y en PowerShell 7.7

Edición Ruta predeterminada del ámbito de usuario
Windows PowerShell 5.1 %USERPROFILE%\Documents\WindowsPowerShell\Modules
PowerShell 7 %USERPROFILE%\Documents\PowerShell\Modules

Para un módulo que se use en ambas ediciones, declare tanto Desktop como Core en CompatiblePSEditions y distribúyalo solo después de probarlo en los dos entornos. Para verificar la compatibilidad resulta útil PSUseCompatibleSyntax de PSScriptAnalyzer (véase «Mantener la calidad de los scripts de PowerShell con PSScriptAnalyzer»).

Cuando surja un problema, compruebe estos tres puntos.

$env:PSModulePath -split ';'                       # rutas de búsqueda
Get-Module -Name KsOps -ListAvailable              # si se encuentra y en qué versión
(Get-Module KsOps -ListAvailable).ModuleBase       # ubicación real desde la que se carga

8. Firma y directiva de ejecución

Existen dos mecanismos de firma con propósitos distintos, y confundirlos provoca el problema de «lo firmé, pero no se puede ejecutar».8

Mecanismo Qué garantiza ¿Satisface la directiva de ejecución AllSigned?
Firma Authenticode (Set-AuthenticodeSignature) El editor y la integridad de cada archivo de script individual Sí la satisface (cada archivo debe estar firmado)
Firma de catálogo (New-FileCatalog + firma) La integridad del conjunto del módulo (el paquete) No la satisface

La directiva de ejecución verifica la firma Authenticode del propio archivo .ps1 / .psm1 que se intenta cargar. Aunque se firme el archivo de catálogo (.cat), los archivos de script que contiene siguen sin firmar, así que en un entorno AllSigned la ejecución queda bloqueada. Por lo tanto, si se opera con AllSigned, es obligatorio firmar cada archivo que se va a ejecutar.

# (1) Obligatorio en un entorno AllSigned: firmar con Authenticode cada archivo que se ejecuta
Get-ChildItem .\KsOps -Recurse -Include *.ps1, *.psm1, *.psd1 | ForEach-Object {
    Set-AuthenticodeSignature -FilePath $_.FullName -Certificate $cert `
        -TimestampServer 'http://timestamp.digicert.com'
}

# (2) Además, usar un catálogo para detectar manipulaciones en el paquete completo
New-FileCatalog -Path .\KsOps -CatalogFilePath .\KsOps\KsOps.cat -CatalogVersion 2
Set-AuthenticodeSignature -FilePath .\KsOps\KsOps.cat -Certificate $cert `
    -TimestampServer 'http://timestamp.digicert.com'

# Verificación en el lado de uso (aparte de la directiva de ejecución, comprueba que el paquete no esté dañado)
Test-FileCatalog -Path .\KsOps -CatalogFilePath .\KsOps\KsOps.cat -Detailed

El valor del catálogo está en poder confirmar si «el conjunto del módulo obtenido es idéntico al que se distribuyó», y PSResourceGet también dispone de -AuthenticodeCheck para verificar la firma y el catálogo.6 Entienda los roles por separado: la firma Authenticode determina si se puede ejecutar, y el catálogo garantiza la integridad del material distribuido.

Si se añade una marca de tiempo, la firma sigue siendo válida incluso después de que caduque el certificado de firma. La visión general de la directiva de ejecución y la operación de firma se resume en «Directiva de ejecución y firma de scripts en PowerShell».

9. Reglas operativas que conviene fijar de antemano

La operación importa más que la tecnología. Como mínimo, defina lo siguiente.

  • Quién puede publicar. Quien tiene permiso de escritura en la carpeta compartida es quien puede distribuir código a toda la empresa
  • Dónde se registra el historial de cambios. Anote los cambios que rompen compatibilidad en ReleaseNotes (dentro de PrivateData.PSData del manifiesto) o en un CHANGELOG
  • Si el servidor de producción fija la versión. Para los módulos de los que depende una ejecución desatendida, lo seguro es fijar la versión y actualizarla de forma planificada
  • El procedimiento de retirada. Al eliminar una función, suba la versión mayor y anuncie la obsolescencia con antelación
  • Publicar solo después de pasar pruebas y lint. Lo ideal es que la publicación se realice desde CI (véase «Organización de pruebas de PowerShell con Pester»)

10. Reglas prácticas habituales (tabla de decisión)

Punto a decidir Opciones Criterio de decisión
Formato de distribución .ps1 en carpeta compartida / Módulo + repositorio El punto de decisión es si se puede gestionar la versión y la actualización
Gestión de módulos PowerShellGet 2.x / PSResourceGet A partir de 7.4 viene incluido. Para código nuevo, PSResourceGet1
Repositorio Recurso compartido de archivos / feed compatible con NuGet Empiece con un recurso compartido de archivos. Si necesita autenticación o auditoría, use un feed2
Manifiesto Omitirlo / Obligatorio Sin versión, la operación no es viable3
Funciones publicadas '*' / Declaradas explícitamente en un arreglo Por el rendimiento de la búsqueda y para no exponer funciones internas3
Destino de instalación CurrentUser / AllUsers para ejecución desatendida El criterio es si resulta visible desde la cuenta de servicio7
Actualización en producción Automática / Versión fijada + actualización planificada Para evitar que el batch nocturno pase a ejecutarse con la nueva versión sin aviso
Firma Ninguna / Firma Authenticode de cada archivo (+ catálogo) En un entorno AllSigned es obligatoria la firma por archivo. El catálogo es para verificar la integridad8

11. Resumen

  • La distribución de .ps1 en una carpeta compartida deja sin control la versión, la actualización, las dependencias y el alcance de lo publicado. La modularización y la distribución mediante repositorio lo resuelven. Sin embargo, «quién lo usa» es la única excepción. Como el repositorio de recurso compartido de archivos no deja registro de las obtenciones, combínelo con alguna de estas opciones: auditoría de lectura de la carpeta compartida, un feed con estadísticas de descarga, o la consolidación de Get-InstalledPSResource ejecutado en cada equipo.
  • El manifiesto es obligatorio. Declare FunctionsToExport de forma explícita con un arreglo y no publique las funciones internas.
  • El repositorio interno se puede poner en marcha con solo registrar un recurso compartido de archivos mediante una ruta UNC. La gestión del permiso de escritura es, en la práctica, el límite de seguridad.
  • La publicación se hace con Publish-PSResource, la obtención con Install-PSResource y la actualización con Update-PSResource. Instale en el ámbito AllUsers los módulos que use una ejecución desatendida.
  • Las rutas de búsqueda son distintas en 5.1 y en 7. Si el módulo debe funcionar en ambas, declare CompatiblePSEditions y pruébelo en los dos entornos.
  • En un entorno AllSigned cada archivo de script necesita firma Authenticode. La firma de catálogo sirve para verificar la integridad del material distribuido y es independiente de si se puede ejecutar. No olvide añadir siempre la marca de tiempo.

Descarga del código de ejemplo

El código tratado en este artículo se distribuye ya empaquetado, listo para ejecutarse. Incluye un conjunto completo de módulo con las funciones públicas y privadas separadas, además del procedimiento de distribución al repositorio interno.

Descargar el código de ejemplo (zip)

Los ejemplos de este artículo se han verificado ejecutándolos realmente en PowerShell 7.6 (16 pruebas de Pester). Si ejecuta Invoke-SampleTests.ps1, incluido en el zip, podrá reproducir la misma verificación en su propio equipo.

# Análisis de sintaxis + análisis estático + pruebas de Pester
./Invoke-SampleTests.ps1

Los valores de configuración (rutas, nombres de servidor, ID de inquilino, etc.) son ejemplos. No los ejecute tal cual en un entorno de producción; adáptelos al entorno de su propia empresa.

Artículos relacionados

Áreas de consultoría relacionadas

KomuraSoft LLC se ocupa de la modularización de los activos de scripts internos y la puesta a punto de la infraestructura de distribución, la estandarización de operaciones que dependen de una sola persona, y la mejora de la mantenibilidad de scripts existentes.

Referencias

  1. Microsoft Learn, Package management for PowerShell. Sobre que Microsoft.PowerShell.PSResourceGet es el módulo que sustituye a PowerShellGet y PackageManagement, que viene incluido en PowerShell 7.4 y coexiste con el PowerShellGet 2.2.5 tradicional, y que también se puede instalar en Windows PowerShell 5.1 desde PowerShell Gallery.  2 3 4 5

  2. Microsoft Learn, Register-PSResourceRepository. Sobre cómo registrar un repositorio indicando en -Uri una carpeta local, un recurso compartido de archivos (ruta UNC) o la URL de un feed compatible con NuGet, la configuración de confianza con -Trusted, el orden de búsqueda mediante -Priority, y la especificación de credenciales en repositorios que requieren autenticación.  2 3 4

  3. Microsoft Learn, How to write a PowerShell module manifest. Sobre la creación del manifiesto con New-ModuleManifest, claves como RootModule, ModuleVersion, GUID, PowerShellVersion, CompatiblePSEditions y RequiredModules, y la razón (rendimiento del descubrimiento de comandos) por la que las declaraciones de exportación como FunctionsToExport deben especificarse de forma explícita en lugar de con comodines.  2 3 4 5 6

  4. Microsoft Learn, Prerelease module versions. Sobre el versionado basado en versionado semántico, la especificación de versiones de prelanzamiento mediante PrivateData.PSData.Prerelease, y el hecho de que las versiones de prelanzamiento no se obtienen de forma predeterminada.  2 3

  5. Microsoft Learn, Publish-PSResource. Sobre cómo publicar en el repositorio la carpeta de módulo indicada en -Path, la especificación del destino de publicación mediante -Repository, y la autenticación mediante -ApiKey. 

  6. Microsoft Learn, Install-PSResource. Sobre la instalación mediante -Name / -Version / -Repository, la especificación del destino con -Scope (CurrentUser / AllUsers), y la omisión de la confirmación con -TrustRepository. Junto con la actualización mediante Update-PSResource 2 3

  7. Microsoft Learn, about_PSModulePath. Sobre cómo PowerShell busca los módulos en las carpetas enumeradas en $env:PSModulePath, y sobre las diferencias entre Windows PowerShell y PowerShell 7 en las rutas predeterminadas de los ámbitos de usuario y de todos los usuarios.  2 3 4 5

  8. Microsoft Learn, New-FileCatalog. Sobre cómo generar un archivo de catálogo (.cat) que incluye los hash de los archivos de una carpeta, la posibilidad de firmar el catálogo con Set-AuthenticodeSignature, y cómo Test-FileCatalog permite detectar manipulaciones comparando el catálogo con el conjunto de archivos. Sobre el hecho de que lo que verifica la directiva de ejecución es la firma Authenticode del propio archivo de script que se ejecuta, véase about_Execution_Policies (en AllSigned solo se pueden ejecutar scripts firmados por un editor de confianza) y Set-AuthenticodeSignature (aplicar una firma Authenticode a un archivo, y añadir una marca de tiempo mediante -TimestampServer).  2 3

  9. Microsoft Learn, New-SmbShare. Sobre cómo -FullAccess / -ChangeAccess / -ReadAccess permiten especificar, cuenta por cuenta, los permisos de acceso a nivel del recurso compartido. Para la configuración de permisos del lado de NTFS, véase icacls (concesión de permisos con /grant; máscaras de permiso como RX = lectura y ejecución, M = modificar, F = control total; y (OI) = herencia de objeto, (CI) = herencia de contenedor). /grant funciona «añadiendo al permiso explícito ya concedido», mientras que /grant:r, con el modificador :r, reemplaza el permiso explícito de ese sujeto (las ACE de otros sujetos se conservan en ambos casos). Para cortar la herencia, véase ObjectSecurity.SetAccessRuleProtection (el primer argumento protege frente a la herencia y el segundo indica si se conservan las ACE ya heredadas). Este método solo puede especificar el tratamiento de las ACE heredadas; las ACE asignadas directamente a ese objeto quedan fuera de su alcance. Para eliminar las asignaciones directas, retírelas de forma individual con métodos como ObjectSecurity.RemoveAccessRuleSpecific 2 3

Artículos recientes con las mismas etiquetas para profundizar en temas cercanos.

Estas páginas sitúan el tema en un contexto más amplio de servicios y decisiones.

Preguntas frecuentes

Preguntas habituales en las consultas sobre el tema del artículo.

¿En qué se diferencian PowerShellGet y PSResourceGet? ¿Cuál debo usar?
PSResourceGet (Microsoft.PowerShell.PSResourceGet) es el nuevo mecanismo de gestión de módulos que sustituye a los antiguos PowerShellGet y PackageManagement, y viene incluido de serie en PowerShell 7.4. Puede coexistir con el PowerShellGet 2.2.5 tradicional, por lo que es posible migrar sin romper los scripts existentes. Para código nuevo, se recomienda usar PSResourceGet, cuyos cmdlets siguen la serie -PSResource (Install-PSResource, Publish-PSResource, etc.). También puede utilizarse en Windows PowerShell 5.1 si se instala desde PowerShell Gallery.
¿Necesito un servidor dedicado para levantar un repositorio interno?
No es necesario. La forma más sencilla es registrar una carpeta de un recurso compartido de archivos como repositorio: basta con indicar la ruta UNC a Register-PSResourceRepository para que funcione. No hace falta ni un servidor dedicado ni una base de datos. Si la organización necesita control de acceso o auditoría más estrictos, o si también se debe obtener el módulo desde fuera de la empresa, conviene usar un feed compatible con NuGet como Azure Artifacts o GitHub Packages. Lo más realista es empezar con un recurso compartido de archivos y migrar cuando surja la necesidad.
¿Es imprescindible el manifiesto de módulo (.psd1)?
En la práctica, considérelo obligatorio. Un archivo .psm1 por sí solo ya se puede cargar como módulo, pero sin manifiesto no hay número de versión, y entonces no se sabe «qué versión está instalada». Sin versión tampoco se puede gestionar la actualización ni aislar el origen de un problema cuando surge. Además, el manifiesto permite declarar explícitamente las funciones que se exportan, los módulos de los que depende y las versiones y ediciones de PowerShell compatibles. Como New-ModuleManifest genera la plantilla automáticamente, el coste de crearlo es mínimo.
¿Qué problema causa escribir '*' en FunctionsToExport?
El descubrimiento automático de comandos se vuelve más lento y, además, se exponen funciones internas que no debían publicarse. PowerShell necesita saber qué comando pertenece a qué módulo antes de cargarlo, y con un comodín solo puede averiguarlo analizando el propio módulo. Si se declaran explícitamente las funciones que se publican mediante un arreglo, ese análisis deja de ser necesario. Además, si las funciones auxiliares de uso interno se pueden invocar desde fuera, pasan a ser en la práctica una API pública y ya no se podrán modificar más adelante.
¿Se puede hacer que el módulo distribuido se actualice automáticamente en el lado de uso?
Sí, se puede actualizar con Update-PSResource, pero diseñe con cuidado la actualización automática de los módulos de los que dependen los scripts de producción. El motivo es que un proceso desatendido nocturno pasaría a ejecutarse con la nueva versión sin que nadie se dé cuenta. En la práctica, lo más seguro es verificar la nueva versión en un entorno de pruebas y luego planificar la actualización en producción. Si aun así necesita automatizarlo, incorpore algún freno, como fijar la versión mayor al actualizar (por ejemplo, con un rango como -Version '1.*').

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.

Volver al blog