Cómo automatizar la desinstalación de aplicaciones en Windows mediante PowerShell

En el mundo de la informática, gestionar software en varios equipos es una tarea compleja y crítica. Uno de los retos habituales a los que se enfrentan los administradores de TI es desinstalar aplicaciones de forma eficaz y estandarizada.

La desinstalación de aplicaciones manual, especialmente en varios endpoints, no sólo lleva mucho tiempo, sino que también es propensa a errores. Aquí es donde entran en juego los scripts PowerShell, como el que vamos a comentar.

Este script en particular sirve para automatizar la desinstalación de aplicaciones utilizando UninstallString y argumentos personalizados, proporcionando un enfoque racionalizado para gestionar las eliminaciones de software.

Necesidad de un script de desinstalación automatizada

La desinstalación de software, especialmente en grandes organizaciones, no es una tarea sencilla. Los profesionales de TI y los proveedores de servicios gestionados (MSP) tratan a menudo con un conjunto diverso de aplicaciones instaladas en varios equipos. Cada aplicación puede requerir diferentes comandos de desinstalación o seguir protocolos distintos.

El script que estamos analizando está diseñado para manejar estos matices, añadiendo automáticamente los argumentos necesarios como /qn /norestart o /S, que son esenciales para las desinstalaciones silenciosas y sin reinicios. Esta automatización es crucial para mantener la coherencia y la eficacia en entornos de TI a gran escala.

El script para automatizar la desinstalación de aplicaciones

#Requires -Version 5.1

<#
.SYNOPSIS
    Uninstall an application using the UninstallString and custom arguments. This script will auto-add /qn /norestart or /S arguments.

    This script will only uninstall apps that follow typical uninstall patterns such as msiexec /X{GUID} /qn /norestart.
.DESCRIPTION
    Uninstall an application using the UninstallString and custom arguments. This script will auto-add /qn /norestart or /S arguments.

    This script will only uninstall apps that follow typical uninstall patterns such as msiexec /X{GUID} /qn /norestart.
.EXAMPLE
    -Name "VLC media Player"
    
    Beginning uninstall of VLC media player using MsiExec.exe /X{9675011C-2395-4AD7-B1CC-92910F991F58} /qn /norestart...
    Exit Code for VLC media player: 0
    Successfully uninstalled your requested apps!

PARAMETER: -Name "ReplaceMeWithNameOfApp"
    Exact name of the application to uninstall, separated by commas. E.g., 'VLC media player, Everything 1.4.1.1024 (x64)'.

PARAMETER: -Arguments "/SILENT, /NOREBOOT"
    Additional arguments to use when uninstalling the app, separated by commas. E.g., '/SILENT, /NOREBOOT'.

PARAMETER: -Reboot
    Schedules a reboot for 1 minute after the uninstall process succeeds.

PARAMETER: -Timeout "ReplaceMeWithTheNumberOfMinutesToWait"
    Specify the amount of time in minutes to wait for the uninstall process to complete. 
    If the process exceeds this time, the script and uninstall process will be terminated.

.NOTES
    Minimum OS Architecture Supported: Windows 10, Windows Server 2016
    Release Notes: Initial Release
.COMPONENT
    Misc
#>

[CmdletBinding()]
param (
    [Parameter()]
    [String]$Name,
    [Parameter()]
    [String]$Arguments,
    [Parameter()]
    [switch]$Reboot = [System.Convert]::ToBoolean($env:reboot),
    [Parameter()]
    [int]$Timeout = 10
)

begin {
    # Replace parameters with dynamic script variables
    if ($env:nameOfAppToUninstall -and $env:nameOfAppToUninstall -notlike "null") { $Name = $env:nameOfAppToUninstall }
    if ($env:arguments -and $env:arguments -notlike "null") { $Arguments = $env:arguments }
    if ($env:timeoutInMinutes -and $env:timeoutInMinutes -notlike "null") { $Timeout = $env:timeoutInMinutes }

    # Check if application name is provided
    if (-not $Name) {
        Write-Host -Object "[Error] No name given, please enter in the name of an app to uninstall!"
        exit 1
    }

    # Check if timeout is provided
    if (-not $Timeout) {
        Write-Host -Object "[Error] No timeout given!"
        Write-Host -Object "[Error] Please enter in a timeout that's greater than or equal to 1 minute or less than or equal to 60 minutes."
        exit 1
    }

    # Validate the timeout is within the acceptable range
    if ($Timeout -lt 1 -or $Timeout -gt 60) {
        Write-Host -Object "[Error] An invalid timeout was given of $Timeout minutes."
        Write-Host -Object "[Error] Please enter in a timeout that's greater than or equal to 1 minute or less than or equal to 60 minutes."
        exit 1
    }

    # Create a list to hold application names after splitting
    $AppNames = New-Object System.Collections.Generic.List[String]
    $Name -split ',' | ForEach-Object {
        $AppNames.Add($_.Trim())
    }

    # Function to check if the script is run with elevated permissions
    function Test-IsElevated {
        $id = [System.Security.Principal.WindowsIdentity]::GetCurrent()
        $p = New-Object System.Security.Principal.WindowsPrincipal($id)
        $p.IsInRole([System.Security.Principal.WindowsBuiltInRole]::Administrator)
    }

    # Get all users registry hive locations
    function Get-UserHives {
        param (
            [Parameter()]
            [ValidateSet('AzureAD', 'DomainAndLocal', 'All')]
            [String]$Type = "All",
            [Parameter()]
            [String[]]$ExcludedUsers,
            [Parameter()]
            [switch]$IncludeDefault
        )
    
        # User account SID's follow a particular pattern depending on if they're Azure AD, a Domain account, or a local "workgroup" account.
        $Patterns = switch ($Type) {
            "AzureAD" { "S-1-12-1-(\d+-?){4}$" }
            "DomainAndLocal" { "S-1-5-21-(\d+-?){4}$" }
            "All" { "S-1-12-1-(\d+-?){4}$" ; "S-1-5-21-(\d+-?){4}$" } 
        }
    
        # We'll need the NTUSER.DAT file to load each user's registry hive. So we grab it if their account SID matches the above pattern. 
        $UserProfiles = Foreach ($Pattern in $Patterns) { 
            Get-ItemProperty "HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\ProfileList\*" |
                Where-Object { $_.PSChildName -match $Pattern } | 
                Select-Object @{Name = "SID"; Expression = { $_.PSChildName } },
                @{Name = "UserName"; Expression = { "$($_.ProfileImagePath | Split-Path -Leaf)" } }, 
                @{Name = "UserHive"; Expression = { "$($_.ProfileImagePath)\NTuser.dat" } }, 
                @{Name = "Path"; Expression = { $_.ProfileImagePath } }
        }
    
        # There are some situations where grabbing the .Default user's info is needed.
        switch ($IncludeDefault) {
            $True {
                $DefaultProfile = "" | Select-Object UserName, SID, UserHive, Path
                $DefaultProfile.UserName = "Default"
                $DefaultProfile.SID = "DefaultProfile"
                $DefaultProfile.Userhive = "$env:SystemDrive\Users\Default\NTUSER.DAT"
                $DefaultProfile.Path = "C:\Users\Default"
    
                $DefaultProfile | Where-Object { $ExcludedUsers -notcontains $_.UserName }
            }
        }
    
        $UserProfiles | Where-Object { $ExcludedUsers -notcontains $_.UserName }
    }

    # Function to find the uninstallation key of an application
    function Find-UninstallKey {
        [CmdletBinding()]
        param (
            [Parameter(ValueFromPipeline = $True)]
            [String]$DisplayName,
            [Parameter()]
            [Switch]$UninstallString
        )
        process {
            $UninstallList = New-Object System.Collections.Generic.List[Object]

            # Search for uninstall key in 32-bit registry location
            $Result = Get-ChildItem "Registry::HKEY_USERS\*\Software\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall\*" | Get-ItemProperty | Where-Object { $_.DisplayName -match "$([regex]::Escape($DisplayName))" }
            if ($Result) { $UninstallList.Add($Result) }

            # Search for uninstall key in 32-bit user locations
            $Result = Get-ChildItem HKLM:\Software\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall\* | Get-ItemProperty | Where-Object { $_.DisplayName -match "$([regex]::Escape($DisplayName))" }
            if ($Result) { $UninstallList.Add($Result) }

            # Search for uninstall key in 64-bit registry location
            $Result = Get-ChildItem HKLM:\Software\Microsoft\Windows\CurrentVersion\Uninstall\* | Get-ItemProperty | Where-Object { $_.DisplayName -match "$([regex]::Escape($DisplayName))" }
            if ($Result) { $UninstallList.Add($Result) }

            # Search for uninstall key in 64-bit user locations
            $Result = Get-ChildItem "Registry::HKEY_USERS\*\Software\Microsoft\Windows\CurrentVersion\Uninstall\*" | Get-ItemProperty | Where-Object { $_.DisplayName -match "$([regex]::Escape($DisplayName))" }
            if ($Result) { $UninstallList.Add($Result) }
    
            # Optionally return the DisplayName and UninstallString
            if ($UninstallString) {
                $UninstallList | ForEach-Object { $_ | Select-Object DisplayName, UninstallString -ErrorAction SilentlyContinue }
            }
            else {
                $UninstallList
            }
        }
    }

    # Initialize the exit code variable
    $ExitCode = 0
}
process {
    # Check for administrative privileges
    if (-not (Test-IsElevated)) {
        Write-Host -Object "[Error] Access Denied. Please run with Administrator privileges."
        exit 1
    }

    # Load unloaded profiles
    $UserProfiles = Get-UserHives -Type "All"
    $ProfileWasLoaded = New-Object System.Collections.Generic.List[string]

    # Loop through each profile on the machine.
    Foreach ($UserProfile in $UserProfiles) {
        # Load user's NTUSER.DAT if it's not already loaded.
        If ((Test-Path Registry::HKEY_USERS\$($UserProfile.SID)) -eq $false) {
            Start-Process -FilePath "cmd.exe" -ArgumentList "/C reg.exe LOAD HKU\$($UserProfile.SID) `"$($UserProfile.UserHive)`"" -Wait -WindowStyle Hidden
            $ProfileWasLoaded.Add("$($UserProfile.SID)")
        }
    }

    # Retrieve similar applications based on names provided
    $SimilarAppsToName = $AppNames | ForEach-Object { Find-UninstallKey -DisplayName $_ -UninstallString }
    if (-not $SimilarAppsToName) {
        Write-Host "[Error] The requested app(s) was not found and none were found that are similar!"
        exit 1
    }

    # Unload all hives that were loaded for this script.
    ForEach ($UserHive in $ProfileWasLoaded) {
        If ($ProfileWasLoaded -eq $false) {
            [gc]::Collect()
            Start-Sleep 1
            Start-Process -FilePath "cmd.exe" -ArgumentList "/C reg.exe UNLOAD HKU\$($UserHive)" -Wait -WindowStyle Hidden | Out-Null
        }
    }

    # Create a list to store apps that are confirmed for uninstallation
    $AppsToUninstall = New-Object System.Collections.Generic.List[Object]
    $SimilarAppsToName | ForEach-Object {
        foreach ($AppName in $AppNames) {
            if ($AppName -eq $_.DisplayName) {
                # A matching app has been found
                $ExactMatch = $True
    
                if ($_.UninstallString) {
                    # Uninstall string is available
                    $UninstallStringFound = $True
                    # Add app to uninstall list
                    $AppsToUninstall.Add($_)
                }
            }
        }
    }

    # Check if any exact matches were found
    if (-not $ExactMatch) {
        Write-Host "[Error] Your requested apps were not found. Please see the below list and try again."
        $SimilarAppsToName | Format-Table DisplayName | Out-String | Write-Host
        exit 1
    }

    # Check if uninstall strings were found for the apps
    if (-not $UninstallStringFound) {
        Write-Host "[Error] No uninstall string found for any of your requested apps!"
        exit 1
    }

    # Check if there are apps without uninstall strings or not found at all
    $AppNames | ForEach-Object {
        if ($AppsToUninstall.DisplayName -notcontains $_) {
            Write-Host "[Error] Either the uninstall string was not present or the app itself was not found for one of your selected apps! See the below list of similar apps and try again."
            $SimilarAppsToName | Format-Table DisplayName | Out-String | Write-Host
            $ExitCode = 1
        }
    }

    # Convert timeout from minutes to seconds
    $TimeoutInSeconds = $Timeout * 60
    $StartTime = Get-Date

    # Process each app to uninstall
    $AppsToUninstall | ForEach-Object {
        $AdditionalArguments = New-Object System.Collections.Generic.List[String]

        # If the uninstall string contains msiexec that's what our executable will be.
        if($_.UninstallString -match "msiexec"){
            $Executable = "msiexec.exe"
        }

        # If it contains a filepath we'll use that as our executable.
        if($_.UninstallString -notmatch "msiexec" -and $_.UninstallString -match '[a-zA-Z]:\\(?:[^\\\/:*?"<>|\r\n]+\\)*[^\\\/:*?"<>|\r\n]*'){
            $Executable = $Matches[0]
        }

        # Confirm we have an executable.
        if(-not $Executable){
            Write-Host -Object "[Error] Unable to find uninstall executable!"
            exit 1
        }

        # Split uninstall string into executable and possible arguments
        $PossibleArguments = $_.UninstallString -split ' ' | ForEach-Object { $_.Trim() } | Where-Object { $_ -match "^/"}

        # Decide executable and additional arguments based on uninstall string analysis
        $i = 0
        foreach ($PossibleArgument in $PossibleArguments) {
            if (-not ($PossibleArgument -match "^/I{") -and $PossibleArgument) {
                $AdditionalArguments.Add($PossibleArgument)
            }

            if ($PossibleArgument -match "^/I{") {
                $AdditionalArguments.Add("$($PossibleArgument -replace '/I', '/X')")
            }

            $i++
        }

        # Add custom arguments from the user
        if ($Arguments) {
            $Arguments.Split(',') | ForEach-Object {
                $AdditionalArguments.Add($_.Trim())
            }
        }

        # Add the usual silent uninstall arguments if not present
        if($Executable -match "Msiexec"){
            if($AdditionalArguments -notcontains "/qn"){
                $AdditionalArguments.Add("/qn")
            }

            if($AdditionalArguments -notcontains "/norestart"){
                $AdditionalArguments.Add("/norestart")
            }
        }elseif($Executable -match "\.exe"){
            if($AdditionalArguments -notcontains "/S"){
                $AdditionalArguments.Add("/S")
            }

            if($AdditionalArguments -notcontains "/norestart"){
                $AdditionalArguments.Add("/norestart")
            }
        }

        # Verify that executable for uninstallation is found
        if (-not $Executable) {
            Write-Host "[Error] Could not find the executable from the uninstall string!"
            exit 1
        }

        # Start the uninstallation process
        Write-Host -Object "Beginning uninstall of $($_.DisplayName) using $Executable $AdditionalArguments..."
        try{
            if ($AdditionalArguments) {
                $Uninstall = Start-Process $Executable -ArgumentList $AdditionalArguments -NoNewWindow -PassThru
            }
            else {
                $Uninstall = Start-Process $Executable -NoNewWindow -PassThru
            }
        }catch{
            Write-Host "[Error] $($_.Exception.Message)"
            return
        }

        # Calculate the remaining time for the uninstall process and enforce timeout
        $TimeElapsed = (Get-Date) - $StartTime
        $RemainingTime = $TimeoutInSeconds - $TimeElapsed.TotalSeconds

        # Wait for the uninstall process to complete within the remaining time
        try {
            $Uninstall | Wait-Process -Timeout $RemainingTime -ErrorAction Stop
        }
        catch {
            Write-Host -Object "[Alert] The uninstall process for $($_.DisplayName) has exceeded the specified timeout of $Timeout minutes."
            Write-Host -Object "[Alert] The script is now terminating."
            $Uninstall | Stop-Process -Force
            $ExitCode = 1
        }

        # Check and report the exit code of the uninstallation process
        Write-Host -Object "Exit code for $($_.DisplayName): $($Uninstall.ExitCode)"
        if ($Uninstall.ExitCode -ne 0) {
            Write-Host -Object "[Error] Exit code does not indicate success!"
            $ExitCode = 1
        }
    }

    # Pause for 30 seconds before final checks
    Start-Sleep -Seconds 30

    $UserProfiles = Get-UserHives -Type "All"
    $ProfileWasLoaded = New-Object System.Collections.Generic.List[string]

    # Loop through each profile on the machine.
    Foreach ($UserProfile in $UserProfiles) {
        # Load user's NTUSER.DAT if it's not already loaded.
        If ((Test-Path Registry::HKEY_USERS\$($UserProfile.SID)) -eq $false) {
            Start-Process -FilePath "cmd.exe" -ArgumentList "/C reg.exe LOAD HKU\$($UserProfile.SID) `"$($UserProfile.UserHive)`"" -Wait -WindowStyle Hidden
            $ProfileWasLoaded.Add("$($UserProfile.SID)")
        }
    }

    # Re-check for any remaining apps to confirm they were uninstalled
    $SimilarAppsToName = $AppNames | ForEach-Object { Find-UninstallKey -DisplayName $_ }
    $SimilarAppsToName | ForEach-Object {
        foreach ($AppName in $AppNames) {
            if ($_.DisplayName -eq $AppName) {
                Write-Host -Object "[Error] Failed to uninstall $($_.DisplayName)."
                $UninstallFailure = $True
                $ExitCode = 1
            }
        }
    }

    # Unload all hives that were loaded for this script.
    ForEach ($UserHive in $ProfileWasLoaded) {
        If ($ProfileWasLoaded -eq $false) {
            [gc]::Collect()
            Start-Sleep 1
            Start-Process -FilePath "cmd.exe" -ArgumentList "/C reg.exe UNLOAD HKU\$($UserHive)" -Wait -WindowStyle Hidden | Out-Null
        }
    }

    # Confirm successful uninstallation if no failures detected
    if (-not $UninstallFailure) {
        Write-Host "Successfully uninstalled your requested apps!"
    }

    # Handle reboot if requested and there were no uninstall failures
    if ($Reboot -and -not $UninstallFailure) {
        Write-Host -Object "[Alert] a reboot was requested. Scheduling restart for 60 seconds from now..."
        Start-Process shutdown.exe -ArgumentList "/r /t 60" -Wait -NoNewWindow
    }

    # Exit script with the final exit code
    exit $ExitCode
}
end {
    
    
    
}

 

Cómo funciona el script

Este script está diseñado para desinstalar aplicaciones basándose en sus nombres y maneja una variedad de escenarios que pueden surgir durante el proceso de desinstalación de aplicaciones. A continuación se detalla paso a paso el funcionamiento del script:

1. Definición de parámetros y tratamiento de entradas

  • El script comienza definiendo varios parámetros, como -Name para el nombre de la aplicación, -Arguments para argumentos adicionales de desinstalación, -Reboot para programar un reinicio y -Timeout para especificar cuánto tiempo debe esperar el script el proceso de desinstalación.
  • A continuación, el script comprueba si se proporcionan estos parámetros y los valida, asegurándose de que cumplen criterios específicos, como que el valor del tiempo de espera esté entre 1 y 60 minutos.

2. Gestión de perfiles y colmenas de usuarios

  • Incluye una función para cargar y manejar perfiles de usuario y archivos comprimidos del registro, necesarios para acceder a las cadenas de desinstalación de cada aplicación. Esto es crucial porque la desinstalación de una aplicación a menudo requiere acceder al registro para localizar la ruta de desinstalación correcta.

3. Identificación de aplicaciones y recuperación de claves de desinstalación

  • El script utiliza una función llamada Find-UninstallKey para buscar en el registro y localizar la clave de desinstalación asociada con el nombre de la aplicación proporcionada. Esto se realiza en diferentes ubicaciones del registro, tanto de 32 como de 64 bits, para garantizar una cobertura completa.

4. Proceso de desinstalación de aplicaciones

  • Una vez identificada la cadena de desinstalación, el script la procesa para extraer la ruta del ejecutable y los argumentos necesarios para la desinstalación de aplicaciones.
  • Garantiza que se añadan los argumentos de desinstalación silenciosa necesarios, como /qn, /norestart o /S, si no están ya presentes. Esto garantiza que la desinstalación se realice sin interacción del usuario y sin forzar el reinicio del sistema, a menos que se especifique lo contrario.

5. Tratamiento de errores y comprobaciones finales

  • El script incluye una sólida gestión de errores, lo que garantiza que si algo va mal durante la desinstalación, se informe al administrador y el script salga con un código de error apropiado.
  • Tras la desinstalación, el script vuelve a comprobar que la aplicación se ha eliminado, asegurándose de que no quedan componentes residuales.

Aplicación real: un estudio de caso

Consideremos un escenario en el que un administrador de TI necesita desinstalar una versión obsoleta de VLC Media Player de 200 ordenadores de una organización. Hacerlo manualmente llevaría muchísimo tiempo. En su lugar, el administrador podría desplegar este script PowerShell en todos los equipos, especificando “VLC Media Player” como nombre de la aplicación.

El script encontraría automáticamente la cadena de desinstalación adecuada, ejecutaría la desinstalación en silencio e incluso programaría un reinicio si fuera necesario. Todo el proceso, que podría haber durado días, se completa en cuestión de minutos.

Comparación con otros métodos

Tradicionalmente, los administradores pueden utilizar el Panel de control o una herramienta de desinstalación de terceros para eliminar aplicaciones. Sin embargo, estos métodos requieren intervención manual, no son escalables y a menudo carecen de la flexibilidad necesaria para añadir argumentos personalizados o gestionar eficazmente las desinstalaciones silenciosas.

Este script, por otro lado, ofrece un enfoque altamente automatizado y personalizable, permitiendo a los profesionales de TI gestionar las desinstalaciones en múltiples máquinas de manera eficiente y con una mínima intervención manual.

Preguntas frecuentes

  1. ¿Qué ocurre si no se encuentra el nombre de la aplicación? Si el script no puede encontrar una aplicación con el nombre exacto proporcionado, buscará nombres similares y proporcionará una lista de posibles coincidencias. Esto permite al administrador ajustar la entrada y volver a intentarlo.
  2. ¿Puede el script gestionar varias aplicaciones a la vez? Sí, el parámetro -Name puede aceptar una lista de aplicaciones separadas por comas, lo que permite al script desinstalar varias aplicaciones de una sola vez.
  3. ¿Qué ocurre si el proceso de desinstalación supera el tiempo de espera? El script terminará el proceso de desinstalación y saldrá con un código de error, asegurando que ningún proceso se ejecute indefinidamente.

Implicaciones para la seguridad y la gestión de las TI

Automatizar el proceso de desinstalación con scripts como éste tiene importantes implicaciones para la seguridad y la gestión de TI. Al asegurarse de que el software obsoleto o vulnerable se elimina de forma rápida y coherente en todos los equipos, las organizaciones pueden reducir el riesgo de brechas de seguridad. Además, la automatización reduce los errores humanos, garantizando que ninguna aplicación se quede sin instalar por descuido.

Prácticas recomendadas para utilizar este script

  • Pruébalo siempre en un entorno controlado: antes de desplegar el script en varios equipos, pruébalo en un entorno controlado para asegurarte de que funciona como se espera.
  • Realiza registros: mantén registros de los procesos de desinstalación con fines de auditoría y solución de problemas.
  • Haz una copia de seguridad antes de proceder a la desinstalación de aplicaciones: asegúrate de hacer una copia de seguridad de los datos importantes antes de ejecutar los scripts de desinstalación, sobre todo en el caso de las aplicaciones que puedan almacenar datos localmente.
  • Actualiza el script regularmente: a medida que salgan nuevas aplicaciones y actualizaciones, actualiza el script para manejar nuevas cadenas y métodos de desinstalación.

Reflexiones finales

Los scripts de PowerShell como el comentado son herramientas indispensables para los profesionales de TI que gestionan grandes redes. Al automatizar la desinstalación de aplicaciones, ahorran tiempo, reducen los errores y mejoran la seguridad. NinjaOne puede complementar aún más estos scripts con sus herramientas integrales de gestión de TI, proporcionando capas adicionales de automatización, supervisión y control, asegurando que tu infraestructura de TI permanezca robusta, segura y actualizada.

Categorías:

Quizá también te interese…

×

¡Vean a NinjaOne en acción!

Al enviar este formulario, acepto la política de privacidad de NinjaOne.

Términos y condiciones de NinjaOne

Al hacer clic en el botón “Acepto” que aparece a continuación, estás aceptando los siguientes términos legales, así como nuestras Condiciones de uso:

  • Derechos de propiedad: NinjaOne posee y seguirá poseyendo todos los derechos, títulos e intereses sobre el script (incluidos los derechos de autor). NinjaOne concede al usuario una licencia limitada para utilizar el script de acuerdo con estos términos legales.
  • Limitación de uso: solo podrás utilizar el script para tus legítimos fines personales o comerciales internos, y no podrás compartirlo con terceros.
  • Prohibición de republicación: bajo ninguna circunstancia está permitido volver a publicar el script en ninguna biblioteca de scripts que pertenezca o esté bajo el control de cualquier otro proveedor de software.
  • Exclusión de garantía: el script se proporciona “tal cual” y “según disponibilidad”, sin garantía de ningún tipo. NinjaOne no promete ni garantiza que el script esté libre de defectos o que satisfaga las necesidades o expectativas específicas del usuario.
  • Asunción de riesgos: el uso que el usuario haga del script corre por su cuenta y riesgo. El usuario reconoce que existen ciertos riesgos inherentes al uso del script, y entiende y asume cada uno de esos riesgos.
  • Renuncia y exención: el usuario no hará responsable a NinjaOne de cualquier consecuencia adversa o no deseada que resulte del uso del script y renuncia a cualquier derecho o recurso legal o equitativo que pueda tener contra NinjaOne en relación con su uso del script.
  • CLUF: si el usuario es cliente de NinjaOne, su uso del script está sujeto al Contrato de Licencia para el Usuario Final (CLUF).