Automatizar el kitting de PC con winget + PowerShell ── Convertir el manual de procedimientos en algo ejecutable

· Actualizado el: · · winget, PowerShell, Windows, Kitting, Sistemas de información, Automatización, Mejora operativa, Eficiencia empresarial

Cada vez que se incorpora un nuevo empleado, o cada vez que se sustituye un equipo, el responsable configura las PC una por una siguiendo un manual de procedimientos ── en los departamentos de sistemas de las pequeñas y medianas empresas, esta sigue siendo la escena habitual. El problema no es solo el tiempo. El trabajo manual no es reproducible, y ese hecho termina pasando factura más tarde en forma de problemas del tipo «solo este equipo tiene una configuración distinta». El manual queda desactualizado sin que nadie lo revise, y cuando cambia el responsable, se pierden los detalles.

Windows incluye de serie el gestor de paquetes winget, con el que la instalación de una aplicación se puede escribir en una sola línea. Además, con WinGet Configuration se pueden expresar la aplicación y su configuración en un único archivo YAML, de forma declarativa (un método en el que, en lugar de enumerar pasos, se escribe «el estado final que se desea»). Y las áreas que winget no cubre (impresoras, unidades de red, ajustes de registro estándar de la empresa, etc.) se pueden complementar con PowerShell.

En este artículo resumimos, con el nivel de detalle necesario para operarlo en la práctica, cómo sustituir el manual de procedimientos de kitting por «archivos ejecutables».

1. Primero, la conclusión

  • Deje la instalación de aplicaciones en manos de winget. winget install admite de forma estándar la instalación silenciosa.1
  • La configuración de un PC existente se puede extraer con winget export. Sin embargo, se limita a los paquetes gestionados por winget y no incluye la configuración.2
  • Si quiere hacerlo de forma declarativa, use WinGet Configuration (winget configure). Se basa en PowerShell DSC y permite escribir la aplicación y su configuración en un único YAML. Requiere Windows 10 1809 o posterior y winget 1.6 o posterior.3
  • Complete con PowerShell lo que winget no cubre: impresoras, unidades compartidas, registro, características de Windows, cuentas locales, etc.
  • Si desea manejarlo desde PowerShell, existe el módulo Microsoft.WinGet.Client. Puede usar cmdlets como Install-WinGetPackage.4
  • Tenga especial cuidado con el contexto del sistema (la ejecución con una cuenta distinta del usuario que ha iniciado sesión, como la cuenta SYSTEM). Microsoft lo sitúa todavía como un elemento de desarrollo futuro, por lo que es imprescindible verificarlo con la cuenta de ejecución real.3
  • Asegure siempre la idempotencia (que el resultado sea el mismo sin importar cuántas veces se ejecute). El kitting es algo que falla a mitad de camino, y debe poder volver a ejecutarse las veces que sea necesario.
  • No fuerce las aplicaciones internas propias de la empresa a encajar en winget; lo realista es ejecutarlas de forma silenciosa con PowerShell.

2. Fundamentos de winget ── Cómo escribir una instalación desatendida

Primero, repasemos la plantilla básica para instalar de forma fiable sin ninguna interacción.1

# Especifique el ID con precisión (-e es coincidencia exacta, --id especifica el ID)
#   --silent                     : no muestra la interfaz
#   --accept-package-agreements  : acepta las condiciones de uso del paquete
#   --accept-source-agreements   : acepta las condiciones de uso del origen
#   --scope machine              : instala para todos los usuarios (solo paquetes compatibles)
# La comilla invertida de continuación de línea va al final de la línea. Si escribe un comentario después, deja de ser una continuación
winget install --id Google.Chrome -e --silent `
    --accept-package-agreements --accept-source-agreements --scope machine

# Buscar el ID
winget search "Visual Studio Code"
winget show --id Microsoft.VisualStudioCode

Si olvida añadir --accept-*, la ejecución desatendida se detiene esperando la aceptación. Este es el primer tropiezo habitual al automatizar el kitting.

--scope machine no se puede usar con todos los paquetes: hay aplicaciones que solo se pueden instalar por usuario. En ese caso, configure la ejecución en el contexto del usuario durante el primer inicio de sesión.

Puede comprobar antes de instalar si la aplicación es compatible. winget show acepta --scope, por lo que puede verificar de antemano si existe un instalador de ámbito de máquina.5

# Comprobar si existe un instalador de ámbito de máquina
winget show --id Google.Chrome -e --scope machine

# Consultar también el lado del ámbito de usuario, para comparar
winget show --id Google.Chrome -e --scope user

Si no existe un instalador para el ámbito indicado, se muestra un mensaje que lo indica. Comprobar así todos los paquetes objetivo antes de escribir "scope": "machine" en el archivo de configuración del kitting evita descubrir el fallo a mitad de la ejecución desatendida.

3. Extraer la configuración de un PC existente ── export / import

Si ya dispone de un «PC estándar» preparado, puede convertir su configuración en un archivo.2

# Volcar a JSON, desde el PC estándar, la lista de paquetes ya instalados
winget export --output D:\kitting\apps.json --include-versions

# Restaurar en un PC nuevo
winget import --import-file D:\kitting\apps.json `
    --accept-package-agreements --accept-source-agreements --ignore-unavailable

El JSON que se genera tiene esta estructura (los valores son de ejemplo).2

{
  "CreationDate": "2026-07-25T10:40:00.000-00:00",
  "Sources": [
    {
      "Packages": [
        { "PackageIdentifier": "Google.Chrome", "Version": "126.0.6478.127" },
        { "PackageIdentifier": "Microsoft.VisualStudioCode", "Version": "1.101.2" },
        { "PackageIdentifier": "7zip.7zip", "Version": "24.09" }
      ],
      "SourceDetails": {
        "Argument": "https://cdn.winget.microsoft.com/cache",
        "Identifier": "Microsoft.Winget.Source_8wekyb3d8bbwe",
        "Name": "winget",
        "Type": "Microsoft.PreIndexed.Package"
      }
    }
  ],
  "WinGetVersion": "1.9.25180"
}

Como puede ver, el contenido es una lista de identificadores de paquete y versiones (si no se añade --include-versions, no se incluye Version e import instala la versión más reciente).2 Es decir, lo único que hace winget import es «instalar el paquete de este identificador»: ni la configuración de las aplicaciones ni las aplicaciones instaladas por métodos distintos de winget aparecen aquí, ni una sola letra. Al abrir realmente el JSON, esta limitación se aprecia de forma concreta.

Es importante tener presente esta limitación.

Lo que sí se puede Lo que no se puede
Reproducir la lista de paquetes gestionados por winget Migrar la configuración interna de las aplicaciones
Fijar la versión (--include-versions) Aplicaciones instaladas por métodos distintos de winget, aplicaciones internas propias
Omitir paquetes inexistentes (--ignore-unavailable) Activación de licencias, estado de inicio de sesión

En definitiva, winget import es un punto de partida, no la totalidad del kitting. El tema central es cómo cubrir el resto.

4. Escribirlo de forma declarativa ── WinGet Configuration

winget configure es un mecanismo que declara en YAML el estado «que se desea que exista al final» y lo aplica a través de PowerShell DSC.3 Frente a un script procedimental, tiene tres ventajas.

  • Si el estado deseado ya existe, no hace nada (idempotencia)
  • Permite expresar en un solo archivo tanto la instalación de aplicaciones como la configuración de Windows y de las aplicaciones
  • Aunque falle a mitad de camino, basta con volver a ejecutar el mismo archivo
# kitting.winget ── declara el estado deseado del equipo estándar
properties:
  configurationVersion: 0.2.0
  resources:
    - resource: Microsoft.WinGet.DSC/WinGetPackage
      id: chrome
      directives:
        description: Instalar Google Chrome
        allowPrerelease: true
      settings:
        id: Google.Chrome
        source: winget

    - resource: Microsoft.WinGet.DSC/WinGetPackage
      id: vscode
      directives:
        description: Instalar Visual Studio Code
      settings:
        id: Microsoft.VisualStudioCode
        source: winget

    - resource: Microsoft.Windows.Developer/DeveloperMode
      id: devmode
      directives:
        description: Habilitar el modo de desarrollador (solo equipos de desarrollo)
        allowPrerelease: true
      settings:
        Ensure: Present
# Comprobar el contenido antes de aplicar (muestra lo que se va a ejecutar)
winget configure show --file D:\kitting\kitting.winget

# Aplicar
winget configure --file D:\kitting\kitting.winget --accept-configuration-agreements

Los requisitos son Windows 10 versión 1809 (compilación 17763) o posterior, o Windows 11, y winget 1.6.2631 o posterior.3 En entornos donde aún quedan equipos antiguos, la configuración mediante PowerShell del siguiente capítulo resulta más fiable.

4.1. Cómo leer directives ── allowPrerelease no se refiere a la aplicación

Cuando se combina el YAML a partir de fragmentos de otros ejemplos, lo que siempre genera confusión es directives. Lo que se escribe aquí son instrucciones sobre cómo tratar el recurso, no la especificación de la aplicación que se va a instalar (eso corresponde al lado de settings).

  • description: el texto descriptivo que se muestra en tiempo de ejecución. Como se convierte tal cual en algo legible en la salida de winget configure show, escríbalo siempre
  • allowPrerelease: es una instrucción que permite usar la versión preliminar del módulo de PowerShell que proporciona el recurso DSC. No significa «instalar la versión preliminar (beta) de la aplicación».

Esta distinción tiene consecuencias prácticas. Como allowPrerelease es un asunto por módulo, cuando el valor difiere entre elementos que usan el mismo resource:, casi siempre se trata de una inconsistencia producida al pegar fragmentos de distintos ejemplos. En el ejemplo anterior, de los dos elementos con Microsoft.WinGet.DSC/WinGetPackage, solo chrome lo lleva, pero no hay ninguna razón para diferenciarlo según la aplicación, así que unifíquelo dentro del mismo tipo de recurso. El único criterio de decisión es si el módulo que proporciona ese recurso tiene publicada una versión estable: si la tiene, quítelo, y solo añádalo cuando use un módulo del que todavía solo existe una versión preliminar.

4.2. Reparto de funciones entre WinGet Configuration y PowerShell propio

Es lógico preguntarse: «si WinGet Configuration ya es idempotente, ¿por qué escribir código idempotente propio en el siguiente capítulo?». La respuesta es que el alcance que cada uno cubre es distinto; no se trata de una relación de sustitución. Organicemos esto por requisito.

Requisito WinGet Configuration PowerShell propio (cap. 5) Elección recomendada en la práctica
Instalación de aplicaciones publicadas en winget ◎ Recurso estándar WinGetPackage ○ Se puede escribir, pero hay que hacerlo idempotente uno mismo Configuration
Configuración de Windows (modo de desarrollador, etc.) ○ Si existe el recurso DSC correspondiente Configuration, si existe el recurso
Valor de registro arbitrario (configuración estándar interna) △ Depende del recurso disponible ◎ Se puede escribir cualquier cosa PowerShell
Aplicación interna en carpeta compartida (MSI/EXE) △ Fuera del alcance de los recursos estándar PowerShell
Unidad de red / impresora compartida × Procesamiento por usuario, en el inicio de sesión PowerShell (fase de usuario)
Diferencias por departamento o modelo △ Hay que separar el YAML ◎ Basta con sustituir el JSON de configuración PowerShell
El SO del equipo es antiguo (anterior a 1809 o a winget 1.6) × No cumple los requisitos PowerShell
Devolver al distribuidor la necesidad de reiniciar mediante el código de salida △ Difícil de controlar ◎ Se puede decidir libremente PowerShell

En conclusión, «lo que encaja en el terreno de winget va a Configuration, y lo que no, va a PowerShell». Ahora bien, si usa ambos, coloque la lista de aplicaciones en uno solo de los dos lugares. Si escribe los paquetes en el YAML de Configuration y además los repite en wingetPackages de kitting.config.json (capítulo 5), cuando corrija uno de los dos, el otro quedará desactualizado. Si adopta una configuración que combina ambos, lo práctico es retirar del lado de PowerShell el procesamiento de instalación de aplicaciones, y dejar en el JSON solo los elementos que Configuration no puede manejar, como los valores de registro o las impresoras compartidas. Por el contrario, intentar cubrirlo todo únicamente con Configuration, buscando el recurso correspondiente para cada cosa, suele ser un trabajo que no compensa la inversión.

5. Complementar con PowerShell ── Lo que winget no hace

En el kitting real, la mayor parte del esfuerzo no está, de hecho, en la instalación de aplicaciones. Aquí es donde entra PowerShell. El punto clave es escribirlo todo de forma que «si ya se ejecutó, no haga nada».

Como este capítulo es largo, primero mostramos el panorama general. Solo intervienen tres archivos: la estructura consiste en tener un único JSON que define «qué instalar», que es leído por dos scripts con permisos distintos.

Fase de usuario (sin elevar, en cada inicio de sesión del usuario)Fase de administrador (elevada, una vez por equipo)Vuelve a leer la configuración distribuidaInvoke-KsUserKitting.ps1Registro HKCU /unidades de red /impresoras compartidasInvoke-KsKitting.ps1Carpetas / registro HKLM /características de Windows / paquetes winget /aplicaciones internas (MSI/EXE)Distribuye el JSON de configuración bajo ProgramDatakitting.config.jsonDefinición de 'cómo debe estar este equipo'

Figura 1: La estructura de tres partes del kitting. La misma definición es leída por dos fases con permisos distintos.

El principio de separación es uno solo: la configuración que afecta a todo el equipo va en la fase de administrador, y la configuración que se crea por cada usuario va en la fase de usuario. Si se mezclan estas dos, como se explica más adelante, se produce sin falta un incidente del tipo «la unidad que debería estar asignada no se ve».

El código que sigue es largo, así que primero mostramos dónde está escrito qué. Dentro del script de administrador, las secciones están separadas por comentarios --- N. ---, así que lea solo la parte que necesite.

Sección Qué hace Quién debería leerla
Inicio (preprocesamiento) Carga del JSON de configuración, distribución a C:\ProgramData, inicio del registro con Start-Transcript Todos
--- 1. Carpetas estándar --- Creación de carpetas. El ejemplo más simple de escritura idempotente Quien quiera conocer la forma de escribir código idempotente
--- 2. Configuración del registro --- Escritura en HKLM. El punto clave es comparar no solo el valor, sino también el tipo Quien quiera distribuir la configuración estándar interna
--- 3. Características de Windows --- Habilitación de características y cómo recoger la necesidad de reiniciar Quien necesite, por ejemplo, .NET Framework 3.5
--- 4. Paquetes winget --- Determinación de si ya está instalado, y cómo escribir sin confiar en el código de salida Quien solo quiera ver la parte de winget
--- 5. Aplicaciones internas --- Ejecución silenciosa de MSI/EXE de una carpeta compartida. Comparación de versión y manejo del código de salida Quien quiera distribuir aplicaciones internas
Final (procesamiento de cierre) Distinción entre devolver 3010 / 1641 / 1 / 0 Quien conecte con una herramienta de distribución (Intune, etc.)

Este script carga kitting.config.json. El ejemplo completo del archivo de configuración se incluye como kitting.config.json en el código de ejemplo de este artículo (el zip al final del artículo), pero aquí mostramos primero solo la estructura.

{
  "folders": [ "C:\\Work", "C:\\KsTools" ],
  "registry": [
    { "key": "HKLM:\\SOFTWARE\\Policies\\Microsoft\\Edge",
      "name": "HomepageLocation", "value": "https://intra.example.co.jp/", "type": "String" }
  ],
  "userRegistry": [
    { "key": "HKCU:\\Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\Advanced",
      "name": "HideFileExt", "value": 0, "type": "DWord" }
  ],
  "windowsFeatures": [ "NetFx3" ],
  "wingetPackages": [ { "id": "Google.Chrome", "scope": "machine" } ],
  "internalApps": [
    { "displayName": "KsApp Cliente del sistema de gestión empresarial",
      "installer": "\\\\fileserver\\deploy\\KsApp\\KsAppSetup.msi",
      "arguments": "/quiet /norestart INSTALLDIR=\"C:\\Program Files\\KsApp\"",
      "version": "3.2.0" }
  ],
  "drives":   [ { "letter": "S", "path": "\\\\fileserver\\share" } ],
  "printers": [ { "name": "\\\\printserver\\MFP-1F", "connection": "\\\\printserver\\MFP-1F" } ]
}

Cada clave y qué fase la lee se muestran en la tabla. La intención del diseño está toda contenida en esta tabla.

Clave Contenido Fase que la lee Notas
folders Rutas de las carpetas estándar que se crean Administrador (elevado) Si ya existe, no hace nada
registry Configuración estándar interna que se escribe en HKLM Administrador (elevado) Del tipo de directiva (página de inicio de Edge, etc.). Afecta a todos los usuarios
userRegistry Configuración por usuario que se escribe en HKCU Usuario (sin elevar) Por ejemplo, la visualización de extensiones. Escribirlo en HKLM no tiene efecto
windowsFeatures Características opcionales de Windows que se habilitan Administrador (elevado) Puede requerir reiniciar
wingetPackages id y scope de los paquetes que se instalan con winget Administrador (elevado) El scope se comprueba de antemano con winget show --scope (capítulo 2)
internalApps Aplicaciones internas de la carpeta compartida (MSI/EXE) Administrador (elevado) displayName debe coincidir exactamente con el nombre mostrado en «Programas y características»
drives Asignación de unidades de red Usuario (sin elevar) Es específico de la sesión de inicio de sesión. Si se crea en el lado elevado, no lo ve el usuario
printers Conexión a impresoras compartidas Usuario (sin elevar) Igual que arriba. Se crea una conexión por cada usuario

Que la columna de fase se divida en dos tipos es, en sí misma, el diseño de este archivo de configuración. Al añadir un elemento nuevo, decida primero si «es una configuración de la máquina o del usuario» y, a partir de ahí, elija en qué clave incluirlo.

registry y userRegistry están separados porque el destino de aplicación es distinto. Una configuración como «Mostrar las extensiones de archivo» (HideFileExt) del Explorador no consulta la directiva de HKLM, sino HKCU, por cada usuario. Aunque se escriba en HKLM desde la fase de administrador, la visualización no cambia. Este tipo de configuración por usuario se incluye en userRegistry y se aplica en la fase sin elevar que se describe más adelante.

Haga que el displayName de internalApps coincida exactamente con el nombre que se muestra en «Programas y características». Si especifica version, se comprueba incluso si la versión instalada es igual o superior a esa (si se omite, la determinación se basa solo en la coincidencia del nombre).

La propia instalación de aplicaciones también se puede hacer con winget import o WinGet Configuration del capítulo anterior, pero este script también incluye la instalación de wingetPackages. Al reunir el archivo de configuración en uno solo, la definición de «qué instalar en este equipo» queda concentrada en un único lugar, y basta con una sola ejecución. Por el contrario, si deja un elemento escrito en el archivo de configuración sin que el script lo lea, acabará registrando como «kitting exitoso» un equipo en el que en realidad no se instaló nada.

#Requires -RunAsAdministrator
[CmdletBinding()]
param(
    [string] $ConfigPath = "$PSScriptRoot\kitting.config.json"
)

$ErrorActionPreference = 'Stop'
# Get-Content de la sección 5.1 lee con la página de códigos ANSI si no hay BOM.
# Como leer un JSON en UTF-8 en un entorno en japonés produce texto corrupto, se especifica explícitamente
$config = Get-Content $ConfigPath -Raw -Encoding utf8 | ConvertFrom-Json
$rebootRequired  = $false
$rebootInitiated = $false
$log    = "C:\ProgramData\KsKitting\kitting_$(Get-Date -f yyyyMMdd_HHmmss).log"
$null   = New-Item -Path (Split-Path $log) -ItemType Directory -Force

# La fase por usuario que se describe más adelante corre en otro proceso, así que
# se distribuye la configuración a una ubicación que puedan leer todos los usuarios.
# Si se coloca el paquete distribuido en esta misma ruta y se ejecuta tal cual, el origen y el destino de la copia
# terminan siendo el mismo archivo. Copy-Item trata la copia sobre sí mismo como un error, y con
# $ErrorActionPreference = 'Stop' el script entero se detendría antes de que empezara el kitting
$sharedConfig = 'C:\ProgramData\KsKitting\kitting.config.json'
$sourceFull   = (Resolve-Path $ConfigPath).ProviderPath
$sharedFull   = if (Test-Path $sharedConfig) { (Resolve-Path $sharedConfig).ProviderPath } else { '' }
if (-not [string]::Equals($sourceFull, $sharedFull, 'OrdinalIgnoreCase')) {
    Copy-Item $ConfigPath $sharedConfig -Force
}
Start-Transcript -Path $log -Append

try {
    # --- 1. Carpetas estándar ----------------------------------------------
    foreach ($dir in $config.folders) {
        if (-not (Test-Path $dir)) {
            $null = New-Item -Path $dir -ItemType Directory
            Write-Verbose "Creada: $dir"
        }
    }

    # --- 2. Configuración de registro estándar interna ---------------------
    foreach ($reg in $config.registry) {
        if (-not (Test-Path $reg.key)) { $null = New-Item -Path $reg.key -Force }
        $item   = Get-Item -Path $reg.key
        $exists = $item.GetValueNames() -contains $reg.name

        # Se compara no solo el valor, sino también el "tipo". El "1" de REG_SZ y el 1 de DWORD
        # resultan iguales en la comparación de PowerShell, lo que hace que una configuración que en realidad
        # no está aplicada se determine erróneamente como "ya aplicada"
        $sameValue = $exists -and $item.GetValue($reg.name) -eq $reg.value
        $sameKind  = $exists -and $item.GetValueKind($reg.name).ToString() -eq $reg.type

        if (-not ($sameValue -and $sameKind)) {
            Set-ItemProperty -Path $reg.key -Name $reg.name -Value $reg.value -Type $reg.type
            Write-Verbose "Configurado: $($reg.key)\$($reg.name) = $($reg.value) ($($reg.type))"
        }
    }

    # --- 3. Características de Windows --------------------------------------
    foreach ($feature in $config.windowsFeatures) {
        $state = Get-WindowsOptionalFeature -Online -FeatureName $feature
        if ($state.State -ne 'Enabled') {
            # Si se suprime el reinicio con -NoRestart, la necesidad de reiniciar aparece en RestartNeeded del valor devuelto.
            # Si no se recoge, se terminaría "saliendo con 0 aun necesitando reiniciar"
            $result = Enable-WindowsOptionalFeature -Online -FeatureName $feature -NoRestart
            if ($result.RestartNeeded) { $rebootRequired = $true }
        }
    }

    # --- 4. Paquetes winget --------------------------------------------------
    # El código de salida de winget puede no ser 0 aunque el paquete "ya esté instalado",
    # y a la inversa, puede ser 0 aunque en realidad no se haya instalado. El éxito se determina consultando el estado.
    # Si no se añade --scope, se recoge la instalación por usuario del propio administrador y
    # se determina erróneamente como "instalado en machine"
    function Test-KsWingetPackage {
        param([string] $Id, [string] $Scope)
        $arguments = @('list', '--id', $Id, '--exact', '--accept-source-agreements')
        if ($Scope) { $arguments += @('--scope', $Scope) }
        $null = winget @arguments 2>&1
        return ($LASTEXITCODE -eq 0)
    }

    foreach ($package in $config.wingetPackages) {
        if (Test-KsWingetPackage -Id $package.id -Scope $package.scope) { continue }

        # La comilla invertida de continuación de línea va al final de la línea. Si escribe un comentario después, deja de ser una continuación
        winget install --id $package.id -e --silent `
            --accept-package-agreements --accept-source-agreements `
            --scope $package.scope
        $wingetExit = $LASTEXITCODE

        # Si aquí solo se muestra una advertencia y se continúa, la herramienta de distribución acabará
        # registrando como "kitting exitoso" un equipo al que no se le instaló una aplicación obligatoria
        if (-not (Test-KsWingetPackage -Id $package.id -Scope $package.scope)) {
            throw "No se pudo instalar $($package.id) (código de salida de winget=$wingetExit)"
        }
    }

    # --- 5. Aplicaciones internas (ejecución silenciosa del instalador de la carpeta compartida) ----
    # La lista de aplicaciones instaladas se abre indicando explícitamente ambas vistas, de 32 y 64 bits.
    # Si PowerShell de 32 bits corre sobre Windows de 64 bits (algo que puede ocurrir según la configuración de Intune),
    # la redirección de WOW64 hace que HKLM:\SOFTWARE\... apunte a la vista de 32 bits, lo que determina
    # erróneamente como "no instalada" una aplicación de 64 bits y la reinstala cada vez
    $installedApps = foreach ($view in 'Registry64', 'Registry32') {
        $baseKey = [Microsoft.Win32.RegistryKey]::OpenBaseKey('LocalMachine', $view)
        try {
            $uninstall = $baseKey.OpenSubKey('SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall')
            if (-not $uninstall) { continue }
            try {
                foreach ($name in $uninstall.GetSubKeyNames()) {
                    $appKey = $uninstall.OpenSubKey($name)
                    if (-not $appKey) { continue }
                    try {
                        $displayName = $appKey.GetValue('DisplayName')
                        if ($displayName) {
                            [pscustomobject]@{
                                DisplayName    = [string] $displayName
                                DisplayVersion = [string] $appKey.GetValue('DisplayVersion')
                            }
                        }
                    }
                    finally { $appKey.Dispose() }
                }
            }
            finally { $uninstall.Dispose() }
        }
        finally { $baseKey.Dispose() }
    }

    foreach ($app in $config.internalApps) {
        $installed = $installedApps | Where-Object DisplayName -eq $app.displayName

        # No determine "instalado" solo por la coincidencia de DisplayName. En un equipo donde quedó
        # una versión antigua, la nueva no se instalaría, y al volver a ejecutar tampoco convergería al estado del archivo de configuración.
        # Si en la configuración hay version, se comprueba incluso si la versión instalada es igual o superior
        $upToDate = if ($app.version) {
            $wanted = [version] $app.version
            [bool]($installed | Where-Object {
                $cur = $_.DisplayVersion -as [version]   # las notaciones que no tienen formato de versión quedan excluidas
                $cur -and $cur -ge $wanted
            })
        } else {
            [bool] $installed
        }
        if ($upToDate) { continue }

        # -ArgumentList de Start-Process concatena el arreglo con espacios para formar una sola línea de comandos,
        # así que los valores que contengan espacios deben llevar comillas ya en el archivo de configuración
        # Ejemplo: '/quiet /norestart INSTALLDIR="C:\Program Files\KsApp"'
        # Un .msi no es un archivo ejecutable, así que si se pasa tal cual a Start-Process
        # falla con "no es una aplicación válida". Se inicia a través de msiexec
        if ([System.IO.Path]::GetExtension($app.installer) -eq '.msi') {
            $msiArgs = @('/i', "`"$($app.installer)`"") + $app.arguments
            $proc = Start-Process -FilePath 'msiexec.exe' -ArgumentList $msiArgs -Wait -PassThru -NoNewWindow
        }
        else {
            $proc = Start-Process -FilePath $app.installer -ArgumentList $app.arguments -Wait -PassThru -NoNewWindow
        }
        # El código de salida de Windows Installer no significa que "solo 0 es éxito".
        # 3010 y 1641 son ambos éxito; solo cambia el tratamiento del reinicio
        switch ($proc.ExitCode) {
            0    { }                                   # Éxito
            3010 { $rebootRequired = $true }           # Éxito. Requiere reiniciar (ERROR_SUCCESS_REBOOT_REQUIRED)
            1641 { $rebootInitiated = $true }          # Éxito. El instalador ya inició el reinicio
            default {
                throw "No se pudo instalar $($app.displayName) (código de salida=$($proc.ExitCode))"
            }
        }
        # Si el reinicio ya se inició, las instalaciones siguientes se interrumpirían aunque se ejecutaran
        if ($rebootInitiated) { break }
    }

    if ($rebootInitiated) {
        # 1641 significa "éxito, pero ya se inició el reinicio". Si se devuelve 0, la herramienta de distribución
        # lo interpretaría como "terminó, pero se reinició por su cuenta", así que se transmite tal cual
        Write-Host 'El instalador inició el reinicio. Vuelva a ejecutar después de reiniciar' -ForegroundColor Yellow
        exit 1641
    }

    if ($rebootRequired) {
        # Si se devuelve 3010 tal cual, Intune u otra herramienta de distribución lo interpreta como "éxito, requiere reiniciar",
        # y puede programar el reinicio e informarlo. Si aquí se devuelve 0, se olvida el reinicio
        Write-Host 'Kitting completado (requiere reiniciar)' -ForegroundColor Yellow
        exit 3010
    }

    Write-Host 'Kitting completado' -ForegroundColor Green
    exit 0
}
catch {
    Write-Warning "Fallo: $($_.Exception.Message)"
    Write-Warning $_.InvocationInfo.PositionMessage
    exit 1
}
finally {
    Stop-Transcript
}

Observe que en este script de administrador se ha excluido intencionadamente la asignación de unidades de red. Como la asignación de letras de unidad es una configuración por sesión de inicio de sesión, aunque se cree en una sesión elevada a administrador, bajo UAC no se ve desde el Explorador normal del usuario. Si se ejecuta con privilegios de sistema desde Intune u otra herramienta, la asignación termina en la sesión de SYSTEM y no tiene relación alguna con el usuario. Lo correcto es ejecutar la configuración específica del usuario, sin elevar, en el momento en que ese usuario inicia sesión.

# Configuración por usuario (se ejecuta sin elevar, en el momento en que el usuario inicia sesión. El método de registro se explica más adelante)

# Como es un proceso distinto del script de administrador, $config no se hereda.
# Se vuelve a leer la configuración que la fase de administrador distribuyó a ProgramData
$configPath = 'C:\ProgramData\KsKitting\kitting.config.json'
if (-not (Test-Path $configPath)) {
    Write-Warning "No se encuentra el archivo de configuración: $configPath"
    exit 1
}
$config = Get-Content $configPath -Raw -Encoding utf8 | ConvertFrom-Json

# Configuración de registro por usuario (HideFileExt, etc. Escribirlo en HKLM no tiene efecto)
foreach ($reg in $config.userRegistry) {
    if (-not (Test-Path $reg.key)) { $null = New-Item -Path $reg.key -Force }

    $item   = Get-Item -Path $reg.key
    $exists = $item.GetValueNames() -contains $reg.name
    $same   = $exists -and
              $item.GetValue($reg.name) -eq $reg.value -and
              $item.GetValueKind($reg.name).ToString() -eq $reg.type

    if (-not $same) {
        Set-ItemProperty -Path $reg.key -Name $reg.name -Value $reg.value -Type $reg.type
    }
}

# Unidades de red
foreach ($drive in $config.drives) {
    $local    = "$($drive.letter):"
    $existing = Get-SmbMapping -LocalPath $local -ErrorAction SilentlyContinue
    if ($existing) {
        # Si se determina solo por "si existe una asignación", cuando se cambia la configuración por un traslado del recurso compartido
        # quedaría la asignación antigua y se reportaría como "éxito". Se compara también el destino de conexión
        if ($existing.RemotePath.TrimEnd('\') -eq $drive.path.TrimEnd('\')) { continue }
        Remove-SmbMapping -LocalPath $local -Force
    }
    New-SmbMapping -LocalPath $local -RemotePath $drive.path -Persistent $true
}

# La conexión a la impresora compartida también es por usuario. Si se ejecuta como administrador o como SYSTEM,
# la conexión se crea solo en esa cuenta y el usuario no la ve
foreach ($printer in $config.printers) {
    if (-not (Get-Printer -Name $printer.name -ErrorAction SilentlyContinue)) {
        Add-Printer -ConnectionName $printer.connection
    }
}

La conexión a la impresora compartida (Add-Printer -ConnectionName) recibe el mismo tratamiento. Como es una operación que crea una conexión por usuario, aunque se realice en el script de administrador o mediante la ejecución como SYSTEM desde Intune, no la verá el empleado que inicie sesión después. Si desea que esté disponible en todos los equipos por igual, use un mecanismo que despliegue la impresora a nivel de máquina (por ejemplo, la distribución de directivas del servidor de impresión), o ejecútelo en esta fase sin elevar.

Por la misma razón, la colocación de archivos bajo el perfil de usuario, la escritura en HKCU y la creación de accesos directos para el usuario también se agrupan en esta fase sin elevar. El esquema básico de un script de kitting es esta estructura de dos etapas: «la configuración de todo el equipo, una vez, como administrador; la configuración específica del usuario, sin elevar, en cada inicio de sesión». Sobre las precauciones con rutas UNC y la asignación de unidades, consulte también «Los escollos de los recursos compartidos de red y las rutas UNC».

Cómo iniciar la fase de usuario. La clave de esta estructura de dos etapas es el mecanismo que hace correr este script sin elevar «en el momento en que el usuario inicia sesión, con los propios permisos de ese usuario». Si este enlace falta, solo se ejecuta la fase de administrador y se obtiene un equipo «sin la unidad ni la impresora configuradas». Hay tres métodos.

Método Momento en que se ejecuta Entorno adecuado Notas
Clave Run de HKLM En cada inicio de sesión de cualquier usuario Sin unión a dominio. Se quiere completar todo con un único script desde Intune u otra herramienta de distribución Se ejecuta cada vez, así que la idempotencia es un requisito. Hace falta indicar que no se muestre ninguna ventana
Programador de tareas (desencadenador al iniciar sesión) En cada inicio de sesión de cualquier usuario Se quiere conservar el historial de resultados de ejecución. Se quiere una ejecución retardada Hay que dejar desactivada la opción «Ejecutar con los privilegios más altos» (si se activa, se eleva y pierde su sentido)
Script de inicio de sesión (directiva de grupo) En cada inicio de sesión de cualquier usuario Ya está unido a un dominio Se puede incorporar a la operación de GPO existente, pero resulta poco práctico en un equipo aislado

Método 1: registrarlo en la clave Run de HKLM (se ejecuta al final de la fase de administrador)

# Coloca el script de la fase de usuario en una ubicación que puedan leer todos los usuarios
$userScript = 'C:\ProgramData\KsKitting\Invoke-KsUserKitting.ps1'
Copy-Item "$PSScriptRoot\Invoke-KsUserKitting.ps1" $userScript -Force

# El Run de HKLM se ejecuta con los permisos del usuario que inició sesión (sin elevar).
# Aunque se intente escribir en HKCU, como se está corriendo como administrador o SYSTEM, lo que se escribiría
# sería "el propio HKCU del administrador", no el HKCU del empleado que va a usar el equipo
$runKey  = 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Run'
$command = 'powershell.exe -NoProfile -ExecutionPolicy Bypass ' +
           "-WindowStyle Hidden -File `"$userScript`""
Set-ItemProperty -Path $runKey -Name 'KsUserKitting' -Value $command

Método 2: registrarlo en el Programador de tareas (cuando se desea conservar el historial)

Por si el lector llega directamente aquí sin pasar por el método 1, se repite desde la colocación del script.

# Coloca en una ubicación que puedan leer todos los usuarios (igual que en el método 1)
$userScript = 'C:\ProgramData\KsKitting\Invoke-KsUserKitting.ps1'
New-Item -ItemType Directory -Path (Split-Path $userScript) -Force | Out-Null
Copy-Item -Path '.\Invoke-KsUserKitting.ps1' -Destination $userScript -Force

$action = New-ScheduledTaskAction -Execute 'powershell.exe' `
    -Argument "-NoProfile -ExecutionPolicy Bypass -WindowStyle Hidden -File `"$userScript`""

$trigger = New-ScheduledTaskTrigger -AtLogOn

# Al especificar BUILTIN\Users (S-1-5-32-545), se ejecuta con los permisos de la persona que inició sesión.
# RunLevel Limited es la indicación de "no elevar". Si aquí se pone Highest, correría en una sesión
# elevada y la asignación de la unidad dejaría de ser visible para el usuario
$principal = New-ScheduledTaskPrincipal -GroupId 'S-1-5-32-545' -RunLevel Limited

Register-ScheduledTask -TaskName 'KsUserKitting' `
    -Action $action -Trigger $trigger -Principal $principal -Force

Método 3: script de inicio de sesión de la directiva de grupo

La ubicación de configuración es Configuración de usuario > Directivas > Configuración de Windows > Scripts (inicio/cierre de sesión) > Inicio de sesión (en el gpedit.msc de un equipo aislado no existe el nivel Directivas, así que sería Configuración de usuario > Configuración de Windows > Scripts (inicio/cierre de sesión)). Agréguelo en la pestaña «Scripts de PowerShell» del cuadro de diálogo. Si escribe un .ps1 directamente en la pestaña «Scripts», se trata como archivo ejecutable y no se comporta como se pretende. En el caso de una directiva local, el script real se coloca en %SystemRoot%\System32\GroupPolicy\User\Scripts\Logon.

Con cualquiera de los métodos, lo común es que se ejecuta en cada inicio de sesión. Esta es también la razón por la que el script de la fase de usuario está escrito de forma idempotente (comprobar el estado actual antes de cambiar algo). Si desea que se ejecute «solo una vez», escriba un marcador de finalización bajo HKCU y compruébelo al principio del script.

Enumeramos los puntos clave del diseño.

  • Separe la configuración que requiere elevación de la configuración por usuario. Como se indicó antes, mezclarlas provoca el incidente de «la unidad que debería estar asignada no se ve»
  • Externalice la configuración en un JSON. Permite expresar las diferencias por departamento o por modelo sin modificar el script
  • Compruebe el estado actual antes de cada operación. Esto hace que sea seguro ejecutarlo cualquier número de veces
  • Deje constancia con Start-Transcript. Así se puede rastrear después «qué se hizo en este equipo» («Los flujos de salida de PowerShell y el diseño del registro»)
  • Devuelva un código de salida. Así Intune u otra herramienta de distribución puede determinar el éxito o el fracaso. Tenga en cuenta que en Windows Installer no solo el 0 significa éxito. Tanto 3010 (ERROR_SUCCESS_REBOOT_REQUIRED, requiere reiniciar) como 1641 (ERROR_SUCCESS_REBOOT_INITIATED, ya se inició el reinicio) son éxitos.6 Si estos caen en default y se tratan como fallo, una instalación que tuvo éxito quedará registrada en rojo en la herramienta de distribución. El punto clave es tratarlos como éxito y, al final, devolver ese mismo código tal cual a quien invocó el script. Si se devuelve 0, la herramienta de distribución pierde la forma de saber que hace falta reiniciar («El diseño del manejo de errores y los reintentos en PowerShell»)
  • Preste atención a las comillas en los argumentos del instalador. Start-Process -ArgumentList solo concatena el arreglo con espacios, así que no se conserva la separación entre argumentos. Ponga entre comillas, en el propio archivo de configuración, las rutas que contengan espacios, o use ProcessStartInfo.ArgumentList (PowerShell 7) («Cómo llamar correctamente a un exe externo desde PowerShell»)

6. Usar winget desde un módulo de PowerShell

Usar un módulo de PowerShell es más robusto que analizar como texto la salida de la línea de comandos. Microsoft.WinGet.Client incluye cmdlets como Find-WinGetPackage / Install-WinGetPackage / Get-WinGetPackage / Update-WinGetPackage.4

Install-Module -Name Microsoft.WinGet.Client -Scope AllUsers -Force

# Comprobar si ya está instalado antes de instalarlo (idempotente)
foreach ($id in 'Google.Chrome', 'Microsoft.VisualStudioCode', '7zip.7zip') {
    if (Get-WinGetPackage -Id $id -ErrorAction SilentlyContinue) {
        Write-Verbose "Ya instalado: $id"
        continue
    }
    Install-WinGetPackage -Id $id -Mode Silent -Scope System
}

Como el resultado se devuelve en forma de objeto, la ventaja es que se puede escribir directamente la determinación de éxito o el cotejo con una lista.

7. Precauciones sobre la ejecución desatendida y el contexto del sistema

Al intentar automatizar por completo el kitting, siempre se termina topando con la cuestión de «con qué cuenta ejecutarlo».

  • winget tiene partes que presuponen su ejecución en el contexto de un usuario. La ejecución en el contexto del sistema es, todavía, algo que Microsoft menciona como función futura3
  • Separe el procesamiento que requiere elevación del procesamiento específico del usuario. Una estructura en la que la configuración de todo el equipo se hace con privilegios de administrador, y la configuración bajo el perfil de usuario se ejecuta en el primer inicio de sesión, resulta más fácil de manejar
  • Verifique siempre con la cuenta de ejecución real. «Funcionó con mi cuenta de administrador, pero al distribuirlo no funciona» es el fallo más frecuente en este terreno («Las tareas del Programador de tareas no se ejecutan»)

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

Qué hacer Medio Notas
Instalación de productos comerciales u OSS winget install / configure --silent --accept-* es obligatorio1
Extraer la configuración de un equipo estándar existente winget export No incluye la configuración. Úselo como punto de partida2
Aplicación + configuración de Windows de forma declarativa winget configure (YAML) Win10 1809 o posterior + winget 1.6 o posterior3
Registro, características de Windows, aplicaciones internas PowerShell (administrador) Cambiar solo tras comprobar el estado (idempotente)
Unidad compartida, impresora compartida, configuración específica del usuario PowerShell (al iniciar sesión, sin elevar) Si se crea elevado o como SYSTEM, el usuario no lo ve
Aplicaciones internas propias PowerShell + instalador silencioso Construir un repositorio dedicado es excesivo para una escala pequeña
Se desea controlar desde PowerShell Microsoft.WinGet.Client Elimina la necesidad de analizar la salida como texto4
Ejecución desatendida Verificar con la cuenta de ejecución El contexto del sistema tiene restricciones3
Registro de lo realizado Start-Transcript + código de salida Deja constancia de «qué se hizo en este equipo»
Cuando se requiere reiniciar Devolver el código de salida 3010 / 1641 Ambos son éxito. Si se devuelve 0, la herramienta de distribución no puede reconocer el reinicio6

9. Resumen

  • El manual de procedimientos de kitting se puede sustituir por archivos ejecutables. Lo realista es repartir las tareas: winget para la instalación de aplicaciones, y PowerShell para el resto de la configuración.
  • En winget install, añada siempre --silent junto con --accept-package-agreements y --accept-source-agreements. Si los olvida, la ejecución desatendida se detiene.
  • winget export permite extraer la configuración de un equipo estándar existente, pero no incluye la configuración de las aplicaciones ni las que no están gestionadas por winget.
  • Con WinGet Configuration (winget configure) se pueden reunir la aplicación y su configuración en un único archivo declarativo, obteniendo una estructura resistente a las reejecuciones.
  • Asegure la idempotencia del procesamiento en el lado de PowerShell escribiéndolo siempre de forma que «se compruebe el estado actual antes de cambiar algo».
  • Separe las fases de ejecución: la configuración de todo el equipo con privilegios de administrador, y la configuración específica del usuario, como las unidades de red o las impresoras compartidas, sin elevar en el momento del inicio de sesión. Las conexiones creadas en una sesión elevada o como SYSTEM no las ve el usuario.
  • En la ejecución desatendida, lo más importante es verificar la cuenta de ejecución. Diseñe partiendo de que la ejecución de winget en el contexto del sistema tiene restricciones.

Descarga del código de ejemplo

El código tratado en este artículo se distribuye reunido en una forma que se puede ejecutar tal cual. Incluye ejemplos completos de la fase de administrador, la fase de usuario y el archivo de configuración.

Descargar el código de ejemplo (zip)

Los ejemplos de este artículo dependen de Windows y del tenant, por lo que no se ha verificado su ejecución. Se ha realizado el análisis de sintaxis y el análisis estático con PSScriptAnalyzer en todos los archivos, pero confirme siempre el funcionamiento en su propio equipo de pruebas.

# Análisis de sintaxis + análisis estático (se puede ejecutar incluso fuera de Windows)
./Invoke-SampleTests.ps1

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

Artículos relacionados

Áreas de consultoría relacionadas

KomuraSoft LLC se ocupa del kitting de PC, la automatización del entorno estándar interno, la conversión en algo ejecutable de operaciones que dependen de una sola persona a través de manuales de procedimientos, y el apoyo al diseño de scripts de distribución.

Referencias

  1. Microsoft Learn, Comando install (winget). Sobre la especificación del objetivo mediante –id / -e, la instalación desatendida mediante –silent, la aceptación de las condiciones de uso mediante –accept-package-agreements / –accept-source-agreements, y la especificación del ámbito de instalación (user / machine) mediante –scope. Junto con la lista de comandos de Use WinGet to install and manage applications 2 3

  2. Microsoft Learn, Comando export (winget). Sobre la posibilidad de volcar a JSON la lista de paquetes instalados, el registro de la versión mediante –include-versions, la restauración mediante el comando import y el comportamiento de –ignore-unavailable, el hecho de que el objeto de la exportación se limita a los paquetes gestionados por winget, y la jerarquía del JSON de salida (Sources / Packages / PackageIdentifier / Version, siendo Version opcional). La estructura del JSON también está definida en packages.schema.2.0.json, sobre el hecho de que el nivel superior tiene WinGetVersion, CreationDate y Sources, de que cada elemento de Sources tiene SourceDetails (Name / Identifier / Argument / Type) y Packages, y de que cada elemento de Packages requiere PackageIdentifier y tiene Version y otros campos como opcionales.  2 3 4 5

  3. Microsoft Learn, WinGet Configuration. Sobre el hecho de que WinGet Configuration es un mecanismo que declara en YAML el estado deseado y lo aplica mediante PowerShell DSC, que se puede usar para configuraciones desatendidas, que se requiere Windows 10 versión 1809 (compilación 17763) o posterior, o Windows 11, junto con WinGet v1.6.2631 o posterior, el tratamiento de UAC al ejecutarlo desde un shell de administrador, y el hecho de que la ejecución en el contexto del sistema se menciona como un elemento de desarrollo futuro. Junto con show / –accept-configuration-agreements del comando configure 2 3 4 5 6 7

  4. GitHub, microsoft/winget-cli ─ Módulo de PowerShell Microsoft.WinGet.Client. Sobre el hecho de que se puede instalar el módulo Microsoft.WinGet.Client desde PowerShell Gallery, y de que se proporcionan cmdlets como Find-WinGetPackage / Get-WinGetPackage / Install-WinGetPackage / Update-WinGetPackage / Uninstall-WinGetPackage, con los que el resultado se puede manejar como objeto.  2 3

  5. Microsoft Learn, Comando show (winget). Sobre el hecho de que es un comando que muestra los detalles (metadatos e información del instalador) de la aplicación especificada, que la opción –scope permite elegir el ámbito de instalación (user / machine), y que la información del instalador que se muestra se basa en los argumentos especificados y en la determinación de WinGet. 

  6. Microsoft Learn, Códigos de error de Windows Installer. Sobre el hecho de que ERROR_SUCCESS_REBOOT_REQUIRED (3010) significa «se requiere reiniciar para que los cambios surtan efecto; la instalación en sí tuvo éxito», y de que ERROR_SUCCESS_REBOOT_INITIATED (1641) significa «el instalador inició el reinicio; es un código que indica éxito».  2

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.

El artículo está directamente relacionado con los siguientes servicios.

Preguntas frecuentes

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

¿Con winget import se puede automatizar todo el kitting?
Hasta la instalación de aplicaciones sí se puede automatizar, pero eso no basta. Lo que winget import reproduce es la lista de paquetes, y quedan fuera la configuración interna de las aplicaciones, la adición de impresoras, la asignación de unidades de red, la configuración de energía y los ajustes de registro estándar de la empresa. Además, las aplicaciones instaladas por métodos distintos de winget y las aplicaciones internas propias de la empresa no se incluyen en la exportación. En la práctica, lo realista es un esquema de dos etapas: dejar la instalación de aplicaciones en manos de winget y cubrir el resto de la configuración con un script de PowerShell.
¿En qué se diferencian winget y WinGet Configuration (winget configure)?
install/import de winget son instrucciones procedimentales del tipo «instala esto en este orden», mientras que WinGet Configuration es un método en el que se escribe en un archivo YAML la declaración «se desea que el estado final sea este». Internamente usa PowerShell DSC, y permite expresar en un solo archivo tanto la instalación de aplicaciones como la configuración de Windows y de las propias aplicaciones. Si el estado deseado ya se cumple, no hace nada, de modo que, si falla a mitad de camino, basta con volver a ejecutar el mismo archivo; esta resistencia a repetir el kitting es su ventaja. Requiere Windows 10 1809 o posterior y winget 1.6 o posterior.
¿Es seguro ejecutar winget con privilegios de SYSTEM desde el Programador de tareas o Intune?
Hay que tener cuidado. winget tiene partes que presuponen su ejecución en el contexto de un usuario, y la ejecución en el contexto del sistema es, todavía, un elemento que Microsoft menciona como desarrollo futuro. En la práctica se recurre a soluciones alternativas como usar --scope machine para instalar para todos los usuarios, ejecutarlo a través del módulo de PowerShell (Microsoft.WinGet.Client), o hacerlo correr en el contexto del usuario durante el primer inicio de sesión. En cualquier caso, verifique siempre con la cuenta de ejecución que realmente se va a usar.
¿El script de kitting debe ser seguro de ejecutar cualquier número de veces?
Sí. Considere obligatoria la idempotencia (que el resultado sea siempre el mismo por muchas veces que se ejecute). Es habitual que el kitting falle a mitad de camino, y cada vez debe poder reiniciarse desde el principio. Si escribe el script de modo que la creación de carpetas compruebe antes su existencia con Test-Path, la configuración del registro compruebe antes el valor actual, y la instalación de aplicaciones compruebe antes si ya están instaladas, podrá reanudar desde el punto que falló. WinGet Configuration incorpora esta idea desde el principio.
¿Se pueden distribuir con winget las aplicaciones internas propias de la empresa?
Es posible si se prepara un repositorio privado para uso interno (una fuente de tipo REST API), pero eso implica el trabajo de construir y mantener un servidor para ello. Si se trata de solo unas pocas aplicaciones, resulta más sencillo ejecutar de forma silenciosa, desde un script de PowerShell, el instalador ubicado en una carpeta compartida. Dejar los productos comerciales y de código abierto en manos de winget, e instalar las aplicaciones internas con PowerShell, es la separación más realista en organizaciones pequeñas.

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