Come utilizzare uno script PowerShell per verificare la presenza di file o cartelle in Windows

Nel panorama IT moderno, la gestione e la manutenzione dei file system su numerosi server e workstation può essere impegnativa. I professionisti IT, in particolare quelli che lavorano nei Managed Service Provider (MSP), hanno spesso bisogno di monitorare file e directory specifici per garantire la conformità, la sicurezza e l’efficienza operativa.

Un modo efficace per svolgere questo compito è lo scripting di PowerShell, che offre automazione e controllo dettagliato degli ambienti Windows. Lo script PowerShell fornito è progettato per avvisare gli utenti se viene trovato un file o una cartella specifica all’interno di una determinata directory o sottodirectory, rendendolo uno strumento prezioso per i professionisti IT.

Background

Il monitoraggio della presenza e della posizione dei file è fondamentale per diversi motivi. Contribuisce a mantenere la sicurezza assicurando l’assenza di file non autorizzati, supporta la conformità verificando la presenza dei file necessari e favorisce l’efficienza operativa automatizzando i controlli di routine. Questo script è particolarmente utile per i professionisti IT che devono gestire reti di grandi dimensioni o ambienti con più client. L’automatizzazione del processo di ricerca e di avvisi consente di risparmiare tempo e di ridurre il rischio di errori umani.

Lo script per verificare la presenza di file o cartelle in Windows

#Requires -Version 5.1

<#
.SYNOPSIS
    Alert if a specified file or folder is found in a directory or subdirectory you specify.
.DESCRIPTION
    Alert if a specified file or folder is found in a directory or subdirectory you specify.

.EXAMPLE
    -SearchPath "C:" -FileOrFolder "autounattend"

    WARNING: Backslash missing from the search path. Changing it to C:\.
    [Alert] File Found.
    C:\Users\Administrator\Desktop\ExampleFolder\Test Folder 1\autounattend.xml
    C:\Users\Administrator\Desktop\ExampleFolder\Test Folder 2\autounattend.xml
    C:\Users\Administrator\Desktop\ExampleFolder\TestFolder1\Test Folder 1\autounattend.xml
    C:\Users\Administrator\Desktop\ExampleFolder\TestFolder1\Test Folder 2\autounattend.xml
    C:\Users\Administrator\Desktop\ExampleFolder\TestFolder2\Test Folder 1\TestFolder1\autounattend.xml
    Attempting to set Custom Field 'multiline'.
    Successfully set Custom Field 'multiline'!

PARAMETER: -SeachPath "C:\ReplaceMeWithAvalidPath"
    Enter the starting directories for the search, separated by commas. This will include all subdirectories as well.

PARAMETER: -FileOrFolder "ReplaceMeWithNameToSearchFor"
    Specify the full or partial name of a file or folder to find. E.g., 'config' or '.exe'.

PARAMETER: -SearchType "Files and Folders"
    Limit the search to either files, folders, or both.

PARAMETER: -Timeout "30"
    Maximum search time in minutes, halts search if exceeded.

PARAMETER: -CustomField "ReplaceMeWithNameOfMultilineCustomField"
    Optional multiline field to record search results. Leave blank if unused.

.NOTES
    Minimum OS Architecture Supported: Windows 10, Windows Server 2016
    Release Notes: Initial Release
By using this script, you indicate your acceptance of the following legal terms as well as our Terms of Use at https://www.ninjaone.com/terms-of-use.
    Ownership Rights: NinjaOne owns and will continue to own all right, title, and interest in and to the script (including the copyright). NinjaOne is giving you a limited license to use the script in accordance with these legal terms. 
    Use Limitation: You may only use the script for your legitimate personal or internal business purposes, and you may not share the script with another party. 
    Republication Prohibition: Under no circumstances are you permitted to re-publish the script in any script library or website belonging to or under the control of any other software provider. 
    Warranty Disclaimer: The script is provided “as is” and “as available”, without warranty of any kind. NinjaOne makes no promise or guarantee that the script will be free from defects or that it will meet your specific needs or expectations. 
    Assumption of Risk: Your use of the script is at your own risk. You acknowledge that there are certain inherent risks in using the script, and you understand and assume each of those risks. 
    Waiver and Release: You will not hold NinjaOne responsible for any adverse or unintended consequences resulting from your use of the script, and you waive any legal or equitable rights or remedies you may have against NinjaOne relating to your use of the script. 
    EULA: If you are a NinjaOne customer, your use of the script is subject to the End User License Agreement applicable to you (EULA).
#>

[CmdletBinding()]
param (
    [Parameter()]
    [String]$SearchPath = "C:\Windows,C:\Program Files",
    [Parameter()]
    [String]$FileOrFolder,
    [Parameter()]
    [String]$SearchType = "Files and Folders",
    [Parameter()]
    [Int]$Timeout = 30,
    [Parameter()]
    [String]$CustomField
)

begin {
    # Set parameters using dynamic script variables.
    if ($env:searchPath -and $env:searchPath -notlike "null") { $SearchPath = $env:searchPath }
    if ($env:fileNameOrFolderNameToSearchFor -and $env:fileNameOrFolderNameToSearchFor -notlike "null") { $FileOrFolder = $env:fileNameOrFolderNameToSearchFor }
    if ($env:searchType -and $env:searchType -notlike "null") { $SearchType = $env:searchType }
    if ($env:timeoutInMinutes -and $env:timeoutInMinutes -notlike "null") { $Timeout = $env:timeoutInMinutes }
    if ($env:customFieldName -and $env:customFieldName -notlike "null") { $CustomField = $env:customFieldName }

    # Error out if no search path was given.
    if (-not $SearchPath) {
        Write-Host "[Error] No search path given!"
        exit 1
    }

    # If given a comma-separated list, split the paths.
    $PathsToSearch = New-Object System.Collections.Generic.List[String]
    if ($SearchPath -match ",") {
        $SearchPath -split "," | ForEach-Object { $PathsToSearch.Add($_.Trim()) }
    }
    else {
        $PathsToSearch.Add($SearchPath)
    }

    # Initialize a generic list for paths to remove or replace.
    $ReplacementPaths = New-Object System.Collections.Generic.List[Object]
    $PathsToRemove = New-Object System.Collections.Generic.List[String]

    # If given a drive without the backslash add it in.
    $PathsToSearch | ForEach-Object {
        if ($_ -notmatch '^[A-Z]:\\$' -and $_ -match '^[A-Z]:$') {
            $NewPath = "$_\"
            $ReplacementPaths.Add(
                [PSCustomObject]@{
                    Index   = $PathsToSearch.IndexOf("$_")
                    NewPath = $NewPath
                }
            )
                
            Write-Warning "Backslash missing from the search path. Changing it to $NewPath."
        }
    }

    # Apply replacements.
    $ReplacementPaths | ForEach-Object {
        $PathsToSearch[$_.index] = $_.NewPath 
    }

    # Check if the search path is valid.
    $PathsToSearch | ForEach-Object {
        if (-not (Test-Path $_)) {
            Write-Host -Object "[Error] $_ does not exist!"
            $PathsToRemove.Add($_)
            $ExitCode = 1
        }
    }

    # Remove Paths that do not exist.
    $PathsToRemove | ForEach-Object {
        $PathsToSearch.Remove($_) | Out-Null
    }

    # Error out if no valid paths to search.
    if ($($PathsToSearch).Count -eq 0) {
        Write-Host "[Error] No valid paths to search!"
        exit 1
    }

    # If we're not given a file or folder error out.
    if (-not $FileOrFolder) {
        Write-Host -Object "[Error] No file or folder given to search for!"
        exit 1
    }

    # Timeout must be within a given range in minutes.
    if ($Timeout -lt 1 -or $Timeout -gt 120) {
        Write-Host -Object "[Error] Timeout is greater than 120 minutes or less than 1 minute."
        exit 1
    }

    # Scope the search to either files only or folders only.
    $ValidSearchTypes = "Files and Folders", "Files Only", "Folders Only"
    if ($ValidSearchTypes -notcontains $SearchType) {
        Write-Host -Object "[Error] Invalid search type."
        exit 1
    }

    # Test for local administrator rights.
    function Test-IsElevated {
        $id = [System.Security.Principal.WindowsIdentity]::GetCurrent()
        $p = New-Object System.Security.Principal.WindowsPrincipal($id)
        $p.IsInRole([System.Security.Principal.WindowsBuiltInRole]::Administrator)
    }

    # Handy function to set a custom field.
    function Set-NinjaProperty {
        [CmdletBinding()]
        Param(
            [Parameter(Mandatory = $True)]
            [String]$Name,
            [Parameter()]
            [String]$Type,
            [Parameter(Mandatory = $True, ValueFromPipeline = $True)]
            $Value,
            [Parameter()]
            [String]$DocumentName
        )
    
        $Characters = $Value | Measure-Object -Character | Select-Object -ExpandProperty Characters
        if ($Characters -ge 10000) {
            throw [System.ArgumentOutOfRangeException]::New("Character limit exceeded, value is greater than 10,000 characters.")
        }
        
        # If we're requested to set the field value for a Ninja document we'll specify it here.
        $DocumentationParams = @{}
        if ($DocumentName) { $DocumentationParams["DocumentName"] = $DocumentName }
        
        # This is a list of valid fields that can be set. If no type is given, it will be assumed that the input doesn't need to be changed.
        $ValidFields = "Attachment", "Checkbox", "Date", "Date or Date Time", "Decimal", "Dropdown", "Email", "Integer", "IP Address", "MultiLine", "MultiSelect", "Phone", "Secure", "Text", "Time", "URL", "WYSIWYG"
        if ($Type -and $ValidFields -notcontains $Type) { Write-Warning "$Type is an invalid type! Please check here for valid types. https://ninjarmm.zendesk.com/hc/en-us/articles/16973443979789-Command-Line-Interface-CLI-Supported-Fields-and-Functionality" }
        
        # The field below requires additional information to be set
        $NeedsOptions = "Dropdown"
        if ($DocumentName) {
            if ($NeedsOptions -contains $Type) {
                # We'll redirect the error output to the success stream to make it easier to error out if nothing was found or something else went wrong.
                $NinjaPropertyOptions = Ninja-Property-Docs-Options -AttributeName $Name @DocumentationParams 2>&1
            }
        }
        else {
            if ($NeedsOptions -contains $Type) {
                $NinjaPropertyOptions = Ninja-Property-Options -Name $Name 2>&1
            }
        }
        
        # If an error is received it will have an exception property, the function will exit with that error information.
        if ($NinjaPropertyOptions.Exception) { throw $NinjaPropertyOptions }
        
        # The below type's require values not typically given in order to be set. The below code will convert whatever we're given into a format ninjarmm-cli supports.
        switch ($Type) {
            "Checkbox" {
                # While it's highly likely we were given a value like "True" or a boolean datatype it's better to be safe than sorry.
                $NinjaValue = [System.Convert]::ToBoolean($Value)
            }
            "Date or Date Time" {
                # Ninjarmm-cli expects the  Date-Time to be in Unix Epoch time so we'll convert it here.
                $Date = (Get-Date $Value).ToUniversalTime()
                $TimeSpan = New-TimeSpan (Get-Date "1970-01-01 00:00:00") $Date
                $NinjaValue = $TimeSpan.TotalSeconds
            }
            "Dropdown" {
                # Ninjarmm-cli is expecting the guid of the option we're trying to select. So we'll match up the value we were given with a guid.
                $Options = $NinjaPropertyOptions -replace '=', ',' | ConvertFrom-Csv -Header "GUID", "Name"
                $Selection = $Options | Where-Object { $_.Name -eq $Value } | Select-Object -ExpandProperty GUID
        
                if (-not $Selection) {
                    throw [System.ArgumentOutOfRangeException]::New("Value is not present in dropdown")
                }
        
                $NinjaValue = $Selection
            }
            default {
                # All the other types shouldn't require additional work on the input.
                $NinjaValue = $Value
            }
        }
        
        # We'll need to set the field differently depending on if its a field in a Ninja Document or not.
        if ($DocumentName) {
            $CustomField = Ninja-Property-Docs-Set -AttributeName $Name -AttributeValue $NinjaValue @DocumentationParams 2>&1
        }
        else {
            $CustomField = Ninja-Property-Set -Name $Name -Value $NinjaValue 2>&1
        }
        
        if ($CustomField.Exception) {
            throw $CustomField
        }
    }

    $ExitCode = 0
}
process {
    # Error out if local administrator rights are not present.
    if (-not (Test-IsElevated)) {
        Write-Host "[Error] Access Denied. Please run with Administrator privileges."
        exit 1
    }

    # Initialize generic lists.
    $SearchJobs = New-Object System.Collections.Generic.List[object]
    $CustomFieldValue = New-Object System.Collections.Generic.List[string]

    # For each given path to search, create a PowerShell job with the provided parameters.
    $PathsToSearch | ForEach-Object {
        $SearchJobs.Add(
            (
                Start-Job -ScriptBlock {
                    param($SearchPath, $FileOrFolder, $SearchType)
                    # We're going to wildcard search either files or folders depending on the parameters given.
                    switch ($SearchType) {
                        "Files and Folders" {
                            Get-ChildItem -Path $SearchPath -Filter "*$FileOrFolder*" -Recurse | Select-Object -Property FullName, Attributes | ConvertTo-Csv
                        }
                        "Folders Only" {
                            Get-ChildItem -Path $SearchPath -Filter "*$FileOrFolder*" -Recurse -Directory | Select-Object -Property FullName, Attributes | ConvertTo-Csv
                        }
                        "Files Only" { 
                            Get-ChildItem -Path $SearchPath -Filter "*$FileOrFolder*" -Recurse -File | Select-Object FullName, Attributes | ConvertTo-Csv
                        }
                    }
                } -ArgumentList $_, $FileOrFolder, $SearchType
            )
        )
    }

    # Convert timeout to seconds as Wait-Job requires seconds.
    $TimeoutInSeconds = $Timeout * 60
    $StartTime = Get-Date

    # Wait for all jobs to complete or timeout.
    foreach ($SearchJob in $SearchJobs) {
        # Calculate the remaining time.
        $TimeElapsed = (Get-Date) - $StartTime
        $RemainingTime = $TimeoutInSeconds - $TimeElapsed.TotalSeconds
    
        # If there is no remaining time, break the loop.
        if ($RemainingTime -le 0) {
            break
        }
    
        # Wait for the current job with the remaining time as the timeout.
        $SearchJob | Wait-Job -Timeout $RemainingTime | Out-Null
    }

    # Output a warning if the job fails to complete.
    $IncompleteJobs = $SearchJobs | Get-Job | Where-Object { $_.State -eq "Running" }
    if ($IncompleteJobs) {
        Write-Host "[Error] The timeout period of $Timeout minutes has been reached, but not all files or directories have been searched!"
        $CustomFieldValue.Add("[Error] The timeout period of $Timeout minutes has been reached, but not all files or directories have been searched!")
        $ExitCode = 1
    }

    # Our PowerShell Job outputs in CSV format; we'll convert it here.
    $MatchingItems = $SearchJobs | ForEach-Object {
        $_ | Get-Job | Receive-Job -ErrorAction SilentlyContinue -ErrorVariable JobErrors | ConvertFrom-Csv
    }

    # Identify whether or not we have a match for a file or folder here.
    $FileMatch = $MatchingItems | Where-Object { $_.Attributes -ne "Directory" }
    $FolderMatch = $MatchingItems | Where-Object { $_.Attributes -eq "Directory" }

    # If we have a match for a file we'll output that here.
    if ($FileMatch) { 
        Write-Host -Object "[Alert] File Found."
        $CustomFieldValue.Add("[Alert] File Found.")
    }

    # If we have a match for a folder we'll output that here.
    if ($FolderMatch) { 
        Write-Host -Object "[Alert] Folder Found." 
        $CustomFieldValue.Add("[Alert] Folder Found.")
    }

    # If we have no matches we'll output that here.
    if (-not $FileMatch -and -not $FolderMatch) {
        Write-Host -Object "Unable to find $FileOrFolder."
        $CustomFieldValue.Add("Unable to find $FileOrFolder.")
    }

    # For each matching file we'll output their full path.
    $MatchingItems | ForEach-Object { 
        Write-Host "$($_.FullName)"
        $CustomFieldValue.Add("$($_.FullName)") 
    }

    # Output any failures or errors received.
    $FailedJobs = $SearchJobs | Get-Job | Where-Object { $_.State -ne "Completed" -and $_.State -ne "Running" }
    if ($FailedJobs -or $JobErrors) {
        Write-Host ""
        Write-Host "[Error] Failed to search certain files or directories due to an error."

        $CustomFieldValue.Add("")
        $CustomFieldValue.Add("[Error] Failed to search certain files or directories due to an error.")
        if ($JobErrors) {
            Write-Host ""
            $CustomFieldValue.Add("")

            $JobErrors | ForEach-Object { 
                Write-Host "[Error] $($_.Exception.Message)" 
                $CustomFieldValue.Add("[Error] $($_.Exception.Message)")
            }
        }
        $ExitCode = 1
    }

    $SearchJobs | Get-Job | Remove-Job -Force

    # Attempt to set the custom field using the Set-NinjaProperty function, if provided.
    if ($CustomField) {
        try {
            Write-Host "Attempting to set Custom Field '$CustomField'."
            Set-NinjaProperty -Name $CustomField -Value ($CustomFieldValue | Out-String)
            Write-Host "Successfully set Custom Field '$CustomField'!"
        }
        catch {
            if (-not $_.Exception.Message) {
                Write-Host "[Error] $($_.Message)"
            }
            else {
                Write-Host "[Error] $($_.Exception.Message)"
            }
            $ExitCode = 1
        }
    }

    exit $ExitCode
}
end {
    
    
    
}

 

Analisi dettagliata

Lo script opera in diverse fasi, ognuna delle quali contribuisce alla sua funzionalità complessiva. Ecco una spiegazione passo per passo:

1. Inizializzazione dei parametri: Lo script inizia con la definizione di alcuni parametri:

  • Percorso di ricerca: Le directory da ricercare.
  • File o cartella: Il nome del file o della cartella da cercare.
  • Tipo di ricerca: Se cercare file, cartelle o entrambi.
  • Timeout: La durata massima della ricerca.
  • Campo personalizzato: Un campo opzionale per registrare i risultati della ricerca.

2. Sovrascrittura delle variabili d’ambiente: Lo script controlla se sono state impostate variabili d’ambiente che dovrebbero sovrascrivere i parametri di input. Ciò consente di effettuare regolazioni dinamiche in base ai diversi ambienti.

3. Convalida: Lo script esegue diversi controlli di convalida:

  • Assicurarsi che venga fornito un percorso di ricerca.
  • Convalida e formattazione del percorso di ricerca.
  • Verifica dell’esistenza dei percorsi specificati.
  • Convalida del nome del file o della cartella.
  • Assicurarsi che il valore di timeout rientri in un intervallo accettabile.
  • Confermare che il tipo di ricerca è valido.

4. Controllo dei diritti di amministratore: Lo script include una funzione per verificare se viene eseguito con i privilegi di amministratore, necessari per alcune operazioni sui file.

5. Esecuzione della ricerca: La funzionalità principale prevede la creazione di job PowerShell per ogni percorso da ricercare. Questi job eseguono ricerche ricorsive in base ai parametri specificati e restituiscono i risultati in formato CSV.

6. Gestione dei risultati: Lo script raccoglie ed elabora i risultati della ricerca:

  • Identifica le corrispondenze di file e cartelle.
  • Fornisce i percorsi completi di tutti gli elementi corrispondenti.
  • Registra errori o ricerche incomplete.

7. Impostazione del campo personalizzato: Se viene specificato un campo personalizzato, lo script tenta di impostarlo con i risultati della ricerca, sfruttando una funzione di aiuto per gestire diversi tipi di campo.

8. Gestione degli errori e pulizia: Lo script assicura che tutti gli errori vengano registrati e che tutti i job vengano puliti correttamente prima di terminare con un codice di uscita appropriato.

Casi d’uso potenziali

Consideriamo uno scenario in cui un professionista IT gestisce una rete di workstation per un cliente aziendale. Devono assicurarsi che nelle directory degli utenti non siano presenti file eseguibili non autorizzati, come parte di un controllo di sicurezza. Distribuendo questo script, è possibile automatizzare la ricerca su tutte le postazioni di lavoro, identificando rapidamente eventuali istanze di file non autorizzati e intervenendo se necessario. Tale automazione non solo migliora la sicurezza, ma consente anche al team IT di dedicare tempo prezioso a compiti più strategici.

Confronti

Questo script offre un approccio completo e automatizzato alla ricerca di file e cartelle rispetto ai metodi manuali o agli script batch di base. I metodi tradizionali spesso prevedono la navigazione manuale nelle directory o l’uso di semplici script che non dispongono di funzionalità avanzate come la gestione dei timeout, la registrazione di campi personalizzati e l’esecuzione di job multi-thread. La capacità dello script PowerShell di gestire ambienti complessi e di fornire risultati dettagliati lo rende una scelta superiore per i professionisti IT.

Domande frequenti

1) Come posso specificare più directory per la ricerca?

Utilizza un elenco separato da virgole per il parametro SearchPath, ad esempio “C:Path1,C:Path2”.

2) È possibile limitare la ricerca ai soli file o alle sole cartelle?

Sì, utilizza il parametro SearchType con i valori “Solo file” o “Solo cartelle”.

3) Cosa succede se la ricerca supera il timeout specificato?

Lo script terminerà qualsiasi ricerca incompleta e registrerà un messaggio di errore.

4) Ho bisogno di disporre dei privilegi di amministratore per eseguire questo script?

Sì, lo script verifica la presenza dei diritti di amministratore e termina se non viene eseguito con privilegi sufficienti.

Implicazioni

I risultati di questo script hanno implicazioni significative per la sicurezza IT. Identificando i file e le cartelle non autorizzati o smarriti, i professionisti IT possono intervenire immediatamente per ridurre i potenziali rischi per la sicurezza. L’esecuzione regolare di questo script come parte di una routine di manutenzione può contribuire a garantire la conformità alle policy organizzative e ai requisiti normativi, migliorando così la sicurezza complessiva.

Raccomandazioni

Quando utilizzi questo script, tieni in considerazione le seguenti best practice:

  • Aggiorna regolarmente lo script per adattarlo alle modifiche dell’ambiente.
  • Integra lo script nelle routine di manutenzione automatica.
  • Controlla e agisci tempestivamente sui risultati della ricerca per mantenere la sicurezza.
  • Personalizza i parametri dello script per adattarli a esigenze e ambienti specifici.

Considerazioni finali

Questo script PowerShell è un potente strumento per i professionisti IT, che fornisce funzionalità di ricerca automatizzata ed efficiente di file e cartelle. Utilizzando questo script, i team IT possono migliorare l’efficienza operativa, mantenere la sicurezza e garantire la conformità ai criteri organizzativi. Per chi utilizza NinjaOne, l’integrazione di questo script può semplificare ulteriormente le attività di gestione, consentendo il controllo e il monitoraggio centralizzato di più endpoint.

Passi successivi

La creazione di un team IT efficiente ed efficace richiede una soluzione centralizzata che funga da principale strumento per la fornitura di servizi. NinjaOne consente ai team IT di monitorare, gestire, proteggere e supportare tutti i dispositivi, ovunque essi si trovino, senza la necessità di una complessa infrastruttura locale.

Per saperne di più sulla distribuzione remota di script con NinjaOne, fai un tour dal vivo, o inizia la tua prova gratuita della piattaforma NinjaOne.

Categorie:

Ti potrebbe interessare anche

×

Guarda NinjaOne in azione!

Inviando questo modulo, accetto La politica sulla privacy di NinjaOne.

Termini e condizioni NinjaOne

Cliccando sul pulsante “Accetto” qui sotto, dichiari di accettare i seguenti termini legali e le nostre condizioni d’uso:

  • Diritti di proprietà: NinjaOne possiede e continuerà a possedere tutti i diritti, i titoli e gli interessi relativi allo script (compreso il copyright). NinjaOne ti concede una licenza limitata per l’utilizzo dello script in conformità con i presenti termini legali.
  • Limitazione d’uso: Puoi utilizzare lo script solo per legittimi scopi personali o aziendali interni e non puoi condividere lo script con altri soggetti.
  • Divieto di ripubblicazione: In nessun caso ti è consentito ripubblicare lo script in una libreria di script appartenente o sotto il controllo di un altro fornitore di software.
  • Esclusione di garanzia: Lo script viene fornito “così com’è” e “come disponibile”, senza garanzie di alcun tipo. NinjaOne non promette né garantisce che lo script sia privo di difetti o che soddisfi le tue esigenze o aspettative specifiche.
  • Assunzione del rischio: L’uso che farai dello script è da intendersi a tuo rischio. Riconosci che l’utilizzo dello script comporta alcuni rischi intrinseci, che comprendi e sei pronto ad assumerti.
  • Rinuncia e liberatoria: Non riterrai NinjaOne responsabile di eventuali conseguenze negative o indesiderate derivanti dall’uso dello script e rinuncerai a qualsiasi diritto legale o di equità e a qualsiasi rivalsa nei confronti di NinjaOne in relazione all’uso dello script.
  • EULA: Se sei un cliente NinjaOne, l’uso dello script è soggetto al Contratto di licenza con l’utente finale (EULA) applicabile.