Guida completa all’utilizzo di PowerShell per un’efficiente ricerca dei log degli eventi

Nel panorama in continua evoluzione dell’IT, il monitoraggio e l’analisi dei log degli eventi sono fondamentali per mantenere l’integrità del sistema, diagnosticare i problemi e garantire la conformità alla sicurezza.

I log degli eventi forniscono una ricca fonte di informazioni sullo stato e sul comportamento dei sistemi e la possibilità di cercare e filtrare in modo efficiente questi log può migliorare notevolmente la capacità di un professionista IT di gestire la propria infrastruttura.

Questo post analizza uno script PowerShell che semplifica il processo di ricerca dei log degli eventi, rendendo più facile individuare eventi specifici in base a criteri quali il nome del log degli eventi, la fonte, l’ID dell’evento e altro ancora.

L’importanza della gestione dei log degli eventi nell’IT

I log degli eventi sono registrazioni di eventi di sistema, sicurezza e applicazione su un computer. Questi log sono indispensabili per la risoluzione dei problemi, la verifica degli incidenti di sicurezza e la garanzia di conformità agli standard normativi.

Per i fornitori di servizi gestiti (MSP) e i professionisti IT, la capacità di effettuare ricerche rapide e accurate tra questi log è fondamentale. Senza strumenti efficienti, questo compito può diventare complicato, soprattutto quando si tratta di un gran numero di sistemi o di ambienti complessi.

PowerShell, un potente linguaggio di scripting sviluppato da Microsoft, offre solide funzionalità per interagire con i log degli eventi. Lo script di cui parliamo oggi è stato progettato per semplificare il processo di ricerca, consentendo ai professionisti IT di individuare rapidamente gli eventi rilevanti all’interno del Visualizzatore eventi.

Lo script per la ricerca dei log degli eventi:

#Requires -Version 5.1

<#
.SYNOPSIS
    Search for specific events in Event Viewer based on the event log they were in, the source of the event, or the specific event IDs used. One of these three options is required for the search.
.DESCRIPTION
    Search for specific events in Event Viewer based on the event log they were in, the source of the event, or the specific event IDs used. One of these three options is required for the search.
.EXAMPLE
    -EventLogName "Application"
    
    Matching Events Found!

    LogName      : Application
    ProviderName : Microsoft-Windows-Security-SPP
    Id           : 16384
    TimeCreated  : 4/4/2024 10:00:48 AM
    Message      : Successfully scheduled Software Protection service for re-start at 2024-04-04T21:19:48Z. Reason: Rul...

    LogName      : Application
    ProviderName : Microsoft-Windows-Security-SPP
    Id           : 16394
    TimeCreated  : 4/4/2024 10:00:17 AM
    Message      : Offline downlevel migration succeeded.

    LogName      : Application
    ProviderName : Microsoft-Windows-Security-SPP
    Id           : 16384
    TimeCreated  : 4/4/2024 9:59:59 AM
    Message      : Successfully scheduled Software Protection service for re-start at 2024-04-04T21:19:59Z. Reason: Rul...

PARAMETER: -EventLogName "Application"
    Specify the name of the Event Log from which to retrieve events.

PARAMETER: -EventLogSource "Microsoft-Windows-Kernel-General"
    Determines the source of the events to retrieve.

PARAMETER: -EventLogMessage "Alert"
    Filters events by the text contained in the event's message.

PARAMETER: -EventIDs "12, 13, 6008"
    A comma-separated list of event IDs to include in the search.

PARAMETER: -excludeEventIDs "13"
    A comma-separated list of event IDs to exclude from the search.

PARAMETER: -StartDate "12/24/2021"
    Defines the start date and time for the event search. Events logged before this time will not be included in the results.

PARAMETER: -EndDate "12/29/2021"
    Sets the end date and time for the event search. Events logged after this time will not be included.

PARAMETER: -MultilineCustomField "replaceMeWithAcustomFieldName"
    Specify the name of a multiline custom field to optionally store the search results in. Leave blank to not set a multiline field.

PARAMETER: -WysiwygCustomField "replaceMeWithACustomFieldName"
    Specify the name of a WYSIWYG custom field to optionally store the search results in. Leave blank to not set a WYSIWYG field.
.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://ninjastage2.wpengine.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]$EventLogName,
    [Parameter()]
    [String]$EventLogSource,
    [Parameter()]
    [String]$EventLogMessage,
    [Parameter()]
    [String]$EventIDs,
    [Parameter()]
    [String]$ExcludeEventIDs,
    [Parameter()]
    [datetime]$StartDate,
    [Parameter()]
    [datetime]$EndDate,
    [Parameter()]
    [String]$MultilineCustomField,
    [Parameter()]
    [String]$WysiwygCustomField
)

begin {
    # Set parameters using dynamic script variables.
    if ($env:eventLogName -and $env:eventLogName -notlike "null") { $EventLogName = $env:eventLogName }
    if ($env:eventLogSource -and $env:eventLogSource -notlike "null") { $EventLogSource = $env:eventLogSource }
    if ($env:eventLogMessage -and $env:eventLogMessage -notlike "null") { $EventLogMessage = $env:eventLogMessage }
    if ($env:eventIds -and $env:eventIds -notlike "null") { $EventIDs = $env:eventIds }
    if ($env:excludeEventIds -and $env:excludeEventIds -notlike "null") { $ExcludeEventIDs = $env:excludeEventIds }
    if ($env:eventStart -and $env:eventStart -notlike "null") { $StartDate = $env:eventStart }
    if ($env:eventEnd -and $env:eventEnd -notlike "null") { $EndDate = $env:eventEnd }
    if ($env:multilineCustomFieldName -and $env:multilineCustomFieldName -notlike "null") { $MultilineCustomField = $env:multilineCustomFieldName }
    if ($env:wysiwygCustomFieldName -and $env:wysiwygCustomFieldName -notlike "null") { $WysiwygCustomField = $env:wysiwygCustomFieldName }

    # Check if both StartDate and EndDate are provided and if StartDate is earlier than EndDate
    if (($StartDate -and $EndDate) -and $StartDate -gt $EndDate) {
        Write-Host -Object "[Error] Start date cannot be earlier than end date!"
        exit 1
    }

    # Verify that WysiwygField and MultiLineField are not the same, exiting with an error if they are.
    if ($WysiwygCustomField -and $MultilineCustomField -and ($WysiwygCustomField -eq $MultilineCustomField)) {
        Write-Host -Object "[Error] Wysiwyg Field and Multiline Field are the same! Custom fields cannot be the same type."
        Write-Host -Object "https://ninjarmm.zendesk.com/hc/en-us/articles/18601842971789-Custom-Fields-by-Type-and-Functionality"
        exit 1
    }

    # Ensure that at least one of Event ID, Event Log Name, or Event Source is provided for the query
    if (!$EventIDs -and !$EventLogName -and !$EventLogSource) {
        Write-Host -Object "[Error] You must provide either an Event ID, Event Log Name or Event Source."
        exit 1
    }

    # Trimming trailing spaces.
    if ($EventLogName) {
        $EventLogName = $EventLogName.Trim()
    }

    # Retrieve and sort all event log names available on the system
    $EventLogNamesOnSystem = Get-WinEvent -ListLog * -ErrorAction SilentlyContinue | Sort-Object LogName
    # Check if the provided EventLogName exists in the system's event logs
    if ($EventLogName -and ($EventLogNamesOnSystem).LogName -notcontains $EventLogName) {
        # If not found, print an error message and a list of valid event log names, then exit the script
        Write-Host -Object "[Error] Event Log '$EventLogName' doesn't exist! See the list below for valid event log names."
        Write-Host -Object "### Valid Event Log Names ###"
        $EventLogNamesOnSystem | Select-Object -ExpandProperty LogName | Write-Host
        exit 1 
    }

    $InvalidEventSourceCharacters = "[\\/<>&`"%\|']"
    if ($EventLogSource) {
        if ($EventLogSource -match $InvalidEventSourceCharacters) {
            Write-Host -Object "[Error] Event Log Source '$EventLogSource' contains an invalid character!"
            exit 1
        }

        if ($EventLogSource.Length -gt 255) {
            Write-Host -Object "[Error] Event Log Source '$EventLogSource' is too large to be an event source!"
            exit 1
        }

        # Trims the event log source for trailing spaces
        $EventLogSource = $EventLogSource.Trim()
    }

    # Prepare a list to hold valid event IDs to search for
    $EventIdsToSearch = New-Object System.Collections.Generic.List[int]
    # Process the input event IDs, removing any that are not purely numerical
    if ($EventIDs -and $EventIDs -match ",") {
        # If multiple event IDs are provided and separated by commas, split them
        $EventIDs -split "," | ForEach-Object {
            $EventId = $_.Trim()
            # Validate each event ID to ensure it's numerical
            if ($EventId -match '[a-zA-Z]|\W') {
                # If not, print an error and skip adding this ID to the list
                Write-Host "[Error] Event ID '$EventId' is not a valid event id. Removing it from the search."
                $ExitCode = 1
                return
            }
            # Check size of event id
            if ([long]$EventId -gt 65535 -or [long]$EventId -lt 0) {
                Write-Host "[Error] Event ID '$EventId' is not a valid event id. Event ID's must be less than or equal to 65535 and greater than or equal to 0. Removing it from the search."
                $ExitCode = 1
                return
            }
             
            # Add the validated event ID to the list
            $EventIdsToSearch.Add($EventId)
        }
    }
    elseif ($EventIDs) {
        $EventId = $EventIDs.Trim()
        # Handle a single event ID input
        if ($EventId -match '[a-zA-Z]|\W') {
            Write-Host "[Error] Event ID '$EventId' is not a valid event id. Removing it from the search."
            $ExitCode = 1
        }
        elseif ([long]$EventId -gt 65535 -or [long]$EventId -lt 0) {
            Write-Host "[Error] Event ID '$EventId' is not a valid event id. Event ID's must be less than or equal to 65535 and greater than or equal to 0. Removing it from the search."
            $ExitCode = 1
        }
        else {
            $EventIdsToSearch.Add($EventId)
        }
    }
 
    # Prepare a list to hold event IDs that should be excluded from the search
    $EventsToExclude = New-Object System.Collections.Generic.List[int]
     
    # Similar process for excluded event IDs as regular event IDs
    if ($ExcludeEventIDs -and $ExcludeEventIDs -match ",") {
        $ExcludeEventIDs -split "," | ForEach-Object {
            $ExcludeEventId = $_.Trim()
            if ($ExcludeEventId -match '[a-zA-Z]|\W') {
                Write-Host "[Error] Event ID '$ExcludeEventId' is not a valid event id. Removing it from the exclusions."
                $ExitCode = 1
                return
            }
 
            if ([long]$ExcludeEventId -gt 65535 -or [long]$ExcludeEventId -lt 0) {
                Write-Host "[Error] Event ID '$ExcludeEventId' is not a valid event id. Event ID's must be less than or equal to 65535 and greater than or equal to 0. Removing it from the exclusions."
                $ExitCode = 1
                return
            }
             
            $EventsToExclude.Add($ExcludeEventId)
        }
    }
    elseif ($ExcludeEventIDs) {
        $ExcludeEventId = $ExcludeEventIDs.Trim()
        if ($ExcludeEventId -match '[a-zA-Z]|\W') {
            Write-Host "[Error] Event ID '$ExcludeEventId' is not a valid event id. Removing it from the exclusions."
            $ExitCode = 1
        }
        elseif ([long]$ExcludeEventId -gt 65535 -or [long]$ExcludeEventId -lt 0) {
            Write-Host "[Error] Event ID '$ExcludeEventId' is not a valid event id. Event ID's must be less than or equal to 65535 and greater than or equal to 0. Removing it from the exclusions."
            $ExitCode = 1
        }
        else {
            $EventsToExclude.Add($ExcludeEventId)
        }
    }
 
    # Check if there are any event IDs to exclude and if the list of event IDs to search for is not empty.
    if ($EventsToExclude.Count -gt 0 -and $EventIdsToSearch.Count -gt 0) {
        $EventsToExclude | ForEach-Object {
            # Check if the current event ID from the exclusion list is also in the list of event IDs to search for.
            if ($EventIdsToSearch -contains $_) {
                Write-Warning "Event ID $_ has been specified for both inclusion and exclusion. It will be excluded."
            }
        }
    }
 
    # Check if there's no valid event ID, log name, or log source provided and exit if true
    if ($EventIdsToSearch.Count -eq 0 -and !$EventLogName -and !$EventLogSource) {
        Write-Host "[Error] No valid Event ID given and no Event Log Name or Event Log Source given."
        exit 1
    }

    # 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 | Out-String | Measure-Object -Character | Select-Object -ExpandProperty Characters
        if ($Characters -ge 200000) {
            throw [System.ArgumentOutOfRangeException]::New("Character limit exceeded, value with $Characters characters is greater than or equal to 200,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 GUID of the option to be selected. Therefore, the given value will be matched with a GUID.
                $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 = $NinjaValue | Ninja-Property-Set-Piped -Name $Name 2>&1
        }
    
        if ($CustomField.Exception) {
            throw $CustomField
        }
    }

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

    if (!$ExitCode) {
        $ExitCode = 0
    }
}
process {
    # Check if the script is running with elevated (Administrator) privileges
    if (!(Test-IsElevated)) {
        # If not, display an error message and exit with status code 1
        Write-Host -Object "[Error] Access Denied. Please run with Administrator privileges."
        exit 1
    }

    # Prepare a list to hold event log names to search for
    $EventLogNamesToSearch = New-Object System.Collections.Generic.List[string]

    # If no event log name was required we'll search all the event
    if (!$EventLogName) {
        $EventLogNamesOnSystem | Where-Object { $_.RecordCount -gt 0 } | Select-Object -ExpandProperty LogName | ForEach-Object {
            $EventLogNamesToSearch.Add($_)
        }
    }
    else {
        $EventLogNamesToSearch.Add($EventLogName)
    }

    # Create XML object.
    [xml]$XML = New-Object System.Xml.XmlDocument

    # Add QueryList element to xml.
    $QueryList = $XML.CreateElement("QueryList")
    $QueryList = $XML.AppendChild($QueryList)

    # Create query element and nest it under QueryList.
    $Query = $XML.CreateElement("Query")
    $Query.SetAttribute("Id", "0")
    $Query = $QueryList.AppendChild($Query)
    

    # Foreach event log to search we're going to create a select element.
    $EventLogNamesToSearch | ForEach-Object {
        # We'll start each loop by selecting the query element to add to.
        $Query = $XML.SelectSingleNode("//Query")

        # The select element starts off with the event log to search.
        $Select = $XML.CreateElement("Select")
        $Select.SetAttribute("Path", "$_")
        
        # Reset the inner text between runnings
        $XMLInnerText = $Null

        # The inner text of each element (<Element1>InnerText</Element1>) will need to be built differently depending on the parameters.
        if ($EventLogSource) {
            $XMLInnerText = "*[System[Provider[@Name='$EventLogSource']]]"
        }
        
        # If we're given a select number of event id's to search we'll filter them here.
        if ($EventIdsToSearch.Count -gt 0) {
            $EventIDSearchText = $Null
            $EventIdsToSearch | ForEach-Object {
                # We may have been given one event id or more than one.
                if ($EventIDSearchText) {
                    $EventIDSearchText = "$EventIDSearchText or EventID=$_"
                }
                else {
                    $EventIDSearchText = "EventID=$_"
                }
            }

            # We'll replace the two ending brackets with our given search text
            if ($XMLInnerText) {
                $XMLInnerText = $XMLInnerText -replace ']]$', " and ($EventIDSearchText)]]"
            }
            else {
                $XMLInnerText = "*[System[($EventIDSearchText)]]"
            }
        }

        # If we're also asked to filter based on the date the event was created we'll create the filter text here.
        if ($StartDate -or $EndDate) {
            $DateFilter = $Null 
            if ($StartDate) {
                $XMLstartDate = Get-Date $StartDate -Format "yyyy-MM-ddTHH:mm:ss"
                # PowerShell will convert the < or > symbol for us when we go to save to the xml.
                $DateFilter = "@SystemTime>='$XMLstartDate'"
            }

            # We may or may not have been given a start date.
            if ($EndDate -and $DateFilter) {
                $XMLendDate = Get-Date $EndDate -Format "yyyy-MM-ddTHH:mm:ss"
                $DateFilter = "$DateFilter and @SystemTime<='$XMLendDate'"
            }
            elseif ($EndDate) {
                $XMLendDate = Get-Date $EndDate -Format "yyyy-MM-ddTHH:mm:ss"
                $DateFilter = "@SystemTime<='$XMLendDate'"
            }

            # Replace the last two closing brackets and add our filter text.
            if($XMLInnerText){
                $XMLInnerText = $XMLInnerText -replace ']]$', " and TimeCreated[$DateFilter]]]"
            }else{
                $XMLInnerText = "*[System[TimeCreated[$DateFilter]]]"
            }
        }

        # If no filters were given (other than the event log name) we'll need to select everything in that log
        if(!$XMLInnerText){
            $XMLInnerText = "*"
        }

        # Save our filter text to the select statement
        $Select.InnerText = $XMLInnerText

        # Append our select statement to our xml file
        $Query.AppendChild($Select) | Out-Null
    }

    # Search for matching events using the XML filter
    $MatchingEvents = Get-WinEvent -FilterXml $XML -ErrorAction SilentlyContinue

    # Exclude events based on the excluded event IDs if any are specified
    if ($EventsToExclude.Count -gt 0) {
        $MatchingEvents = $MatchingEvents | Where-Object { $EventsToExclude -notcontains $_.ID }
    }

    # Exclude events that do not match the keywords you specified.
    if ($EventLogMessage) {
        $MatchingEvents = $MatchingEvents | Where-Object { $_.Message -like "*$EventLogMessage*" }
    }

    # If the event log message is larger than 100 characters trim it and add ...
    if ($MatchingEvents) {
        $MatchingEvents = $MatchingEvents | Select-Object LevelDisplayName, LogName, ProviderName, Id, TimeCreated, @{
            Name       = 'Message'
            Expression = {
                $Characters = $_.Message | Measure-Object -Character | Select-Object -ExpandProperty Characters
                if ($Characters -gt 100) {
                    "$(($_.Message).SubString(0,100))(...)"
                }
                else {
                    $_.Message
                }
            }
        }

        # Sort the object by newest event to oldest
        $MatchingEvents = $MatchingEvents | Sort-Object TimeCreated -Descending
    }

    # Set a Wysiwyg custom field if any matching events are found and it was requested.
    if ($WysiwygCustomField -and $MatchingEvents) {
        try {
            Write-Host "Attempting to set Custom Field '$WysiwygCustomField'."

            # Prepare the custom field output.
            $CustomFieldValue = New-Object System.Collections.Generic.List[string]

            # Convert the matching events into an html report.
            $htmlTable = $MatchingEvents | Select-Object -Property LevelDisplayName, LogName, ProviderName, Id, TimeCreated, Message | ConvertTo-Html -Fragment
            
            # Set color coding
            $htmlTable = $htmlTable -replace "<tr><td>Verbose</td>", "<tr class=`"other`"><td>Verbose</td>"
            $htmlTable = $htmlTable -replace "<tr><td>Warning</td>", "<tr class=`"warning`"><td>Warning</td>"
            $htmlTable = $htmlTable -replace "<tr><td>Error</td>", "<tr class=`"danger`"><td>Error</td>"
            $htmlTable = $htmlTable -replace "<tr><td>Critical Error</td>", "<tr class=`"danger`"><td>Critical Error</td>"

            # Remove Level Display Name
            $LevelDisplayNames = $MatchingEvents | Select-Object -Property LevelDisplayName -Unique
            $LevelDisplayNames | ForEach-Object {
                $htmlTable = $htmlTable -replace "<td>$([Regex]::Escape($_.LevelDisplayName))</td>"
            }
            $htmlTable = $htmlTable -replace "<th>LevelDisplayName</th>"

            # Add the newly created html into the custom field output.
            $CustomFieldValue.Add($htmlTable)

            # Check that the output complies with the hard character limits.
            $Characters = $CustomFieldValue | Out-String | Measure-Object -Character | Select-Object -ExpandProperty Characters
            if ($Characters -ge 199500) {
                Write-Warning "200,000 Character Limit has been reached! Trimming output until the character limit is satisified..."
                
                # If it doesn't comply with the limits we'll need to recreate it with some adjustments.
                $i = 0
                do {
                    # Recreate the custom field output starting with a warning that we truncated the output.
                    $CustomFieldValue = New-Object System.Collections.Generic.List[string]
                    $CustomFieldValue.Add("<h1>This info has been truncated to accommodate the 200,000 character limit.</h1>")

                    # The custom field information is sorted from newest to oldest. We'll remove the oldest first by flipping the array upside down.
                    [array]::Reverse($htmlTable)
                    # If the next entry is a row we'll delete it.
                    if ($htmlTable[$i] -match '<tr><td>' -or $htmlTable[$i] -match '<tr class=') {
                        $htmlTable[$i] = $null
                    }
                    $i++
                    # We'll flip the array back to right side up.
                    [array]::Reverse($htmlTable)

                    # Add it back to the output.
                    $CustomFieldValue.Add($htmlTable)

                    # Check that we now comply with the character limit. If not restart the do loop.
                    $Characters = $CustomFieldValue | Out-String | Measure-Object -Character | Select-Object -ExpandProperty Characters
                }while ($Characters -ge 199500)
            }

            # Set the custom field.
            Set-NinjaProperty -Name $WysiwygCustomField -Value $CustomFieldValue
            Write-Host "Successfully set Custom Field '$WysiwygCustomField'!"
        }
        catch {
            Write-Host "[Error] $($_.Exception.Message)"
            $ExitCode = 1
        }
    }

    # Set a multiline custom field if any matching events are found and it was requested.
    if ($MultilineCustomField -and $MatchingEvents) {
        try {
            Write-Host "Attempting to set Custom Field '$MultilineCustomField'."
            $CustomFieldValue = New-Object System.Collections.Generic.List[string]

            # We don't want to edit the matching Events array if we have to truncate later so we'll create a duplicate here.
            $CustomFieldList = $MatchingEvents | Select-Object -Property LogName, ProviderName, Id, TimeCreated, Message

            # Format the matching items into a nice list with the relevant properties.
            $CustomFieldValue.Add(($CustomFieldList | Format-List -Property LogName, ProviderName, Id, TimeCreated, Message | Out-String))
            
            # Check that the output complies with the hard character limits.
            $Characters = $CustomFieldValue | Out-String | Measure-Object -Character | Select-Object -ExpandProperty Characters
            if ($Characters -ge 9500) {
                Write-Warning "10,000 Character Limit has been reached! Trimming output until the character limit is satisified..."
                
                # If it doesn't comply with the limits we'll need to recreate it with some adjustments.
                $i = 0
                do {
                    # Recreate the custom field output starting with a warning that we truncated the output.
                    $CustomFieldValue = New-Object System.Collections.Generic.List[string]
                    $CustomFieldValue.Add("This info has been truncated to accommodate the 10,000 character limit.")
                    
                    # The custom field information is sorted from newest to oldest. We'll remove the oldest events first by flipping the array upside down.
                    [array]::Reverse($CustomFieldList)

                    # Remove the next item which in this case will be the oldest item.
                    $CustomFieldList[$i] = $null
                    $i++

                    # We'll flip the array back to right side up.
                    [array]::Reverse($CustomFieldList)

                    # Add it back to the output.
                    $CustomFieldValue.Add(($CustomFieldList | Format-List -Property LogName, ProviderName, Id, TimeCreated, Message | Out-String))

                    # Check that we now comply with the character limit. If not restart the do loop.
                    $Characters = $CustomFieldValue | Out-String | Measure-Object -Character | Select-Object -ExpandProperty Characters
                }while ($Characters -ge 9500)
            }

            Set-NinjaProperty -Name $MultilineCustomField -Value $CustomFieldValue
            Write-Host "Successfully set Custom Field '$MultilineCustomField'!"
        }
        catch {
            Write-Host "[Error] $($_.Exception.Message)"
            $ExitCode = 1
        }
    }

    # If any matching events were found output them into the activity log.
    if ($MatchingEvents) {
        Write-Host "Matching Events Found!"
        $MatchingEvents | Format-List LogName, ProviderName, Id, TimeCreated, Message | Out-String | Write-Host
    }
    else {
        Write-Host "No matching events found!"
    }

    exit $ExitCode
}
end {
    
    
    
}

 

Comprendere lo script PowerShell per la ricerca dei log degli eventi

Questo script PowerShell è uno strumento versatile che consente agli utenti di cercare eventi specifici nel Visualizzatore eventi in base a vari criteri. Lo script può filtrare gli eventi in base al nome del log, alla fonte, all’ID dell’evento, al contenuto del messaggio e all’intervallo di date. Inoltre, offre opzioni per escludere determinati ID di eventi dalla ricerca e per memorizzare i risultati in campi personalizzati.

Ecco una descrizione dettagliata del funzionamento dello script per la ricerca dei log degli eventi:

1. Parametri e variabili d’ambiente:

  • Lo script inizia impostando i parametri che consentono agli utenti di definire i criteri di ricerca, come EventLogName, EventLogSource, EventIDs, StartDate e EndDate. Supporta anche variabili d’ambiente per questi parametri, consentendo l’integrazione con altri flussi di lavoro di automazione.

2. Convalida e gestione degli errori:

  • Lo script include più livelli di convalida per garantire che i parametri di input siano corretti. Ad esempio, controlla se la data di inizio fornita è precedente alla data di fine e convalida che gli ID degli eventi siano numerici e rientrino nell’intervallo accettabile.
  • Se i parametri WysiwygCustomField e MultilineCustomField sono impostati sullo stesso valore, lo script mostra un errore, evitando configurazioni in conflitto.

3. Logica della ricerca dei log degli eventi:

  • Lo script costruisce una query XML per filtrare gli eventi in base ai criteri forniti. Questa query viene quindi utilizzata con il cmdlet Get-WinEvent per recuperare gli eventi corrispondenti dai registri specificati.
  • Lo script consente di escludere determinati ID di eventi e può filtrare gli eventi in base al contenuto del messaggio, rendendolo altamente personalizzabile.

4. Impostazione del campo personalizzato:

  • Se vengono trovati eventi corrispondenti, lo script può memorizzare i risultati in campi personalizzati, sia come campo di testo multilinea che come report in formato HTML. Questa funzionalità è particolarmente utile per gli MSP che devono documentare i risultati o automatizzare la reportistica.

5. Output e ordinamento:

  • Lo script fornisce i risultati in un formato leggibile, ordinati in base all’ora in cui gli eventi sono stati creati, dal più recente al più vecchio. Inoltre, riduce i messaggi lunghi per evitare di sovraccaricare l’output.

Applicazioni pratiche dello script per la ricerca dei log degli eventi

Consideriamo uno scenario in cui un professionista IT è incaricato di identificare gli errori ricorrenti su più server in un ambiente aziendale. Esaminare manualmente i log degli eventi su ogni server richiederebbe molto tempo e sarebbe soggetto a errori.

Sfruttando questo script PowerShell, il professionista può automatizzare il processo di ricerca, filtrando i log in base a specifici ID di eventi di errore e restringendo i risultati a un particolare intervallo di tempo. Questo approccio non solo fa risparmiare tempo, ma migliora anche l’accuratezza, consentendo al team IT di diagnosticare e risolvere rapidamente i problemi.

Un altro caso d’uso potrebbe riguardare la verifica della conformità. Un MSP potrebbe aver bisogno di verificare che determinati eventi di sicurezza, come i tentativi di accesso riusciti o falliti, si verifichino come previsto nei suoi sistemi gestiti. Questo script può essere utilizzato per estrarre questi eventi e generare report che dimostrino la conformità ai criteri di sicurezza.

Approcci a confronto per la ricerca dei log degli eventi

Sebbene esistano diversi metodi per la ricerca dei log degli eventi, tra cui l’interfaccia grafica integrata del Visualizzatore eventi o altri strumenti di terze parti, questo script PowerShell offre vantaggi distinti. A differenza delle ricerche manuali tramite l’interfaccia grafica, questo script può essere automatizzato e integrato in flussi di lavoro più ampi. Inoltre, rispetto ad altri strumenti, PowerShell offre un elevato grado di personalizzazione, consentendo ai professionisti IT di adattare i criteri di ricerca alle proprie esigenze specifiche.

Domande frequenti

D: Lo script per la ricerca dei log degli eventi può essere utilizzato su sistemi con versioni precedenti di Windows?

R: Lo script richiede Windows PowerShell 5.1 ed è supportato su Windows 10 e Windows Server 2016 o versioni successive.

D: Cosa succede se si supera il limite di caratteri quando si imposta un campo personalizzato?

R: Lo script include una logica per ridurre l’output in modo da rientrare nel limite di caratteri, assicurando che lo script non fallisca a causa di set di dati troppo grandi.

D: È possibile cercare tutti i log degli eventi di un sistema utilizzando questo script?

R: Sì, se non viene fornito un nome specifico per il log degli eventi, lo script cercherà in tutti i log degli eventi disponibili sul sistema.

Implicazioni per la sicurezza informatica

La capacità di effettuare un’efficiente ricerca dei log degli eventi ha implicazioni significative per la sicurezza informatica. Identificando rapidamente gli eventi sospetti o anomali, i professionisti IT possono rispondere in modo più efficace a potenziali incidenti di sicurezza. L’automazione fornita da questo script riduce il rischio di errore umano e garantisce che gli eventi critici non vengano trascurati.

Best practice per l’utilizzo dello script

  • Testa sempre lo script per impostare il tipo di avvio del servizio host di dispositivi UPnP in un ambiente controllato prima di distribuirlo in produzione.
  • Utilizza le variabili d’ambiente per integrare lo script in flussi di lavoro di automazione più ampi.
  • Aggiorna regolarmente lo script per incorporare nuove funzionalità o risolvere potenziali vulnerabilità di sicurezza.

Considerazioni finali

Una gestione efficiente dei log degli eventi è essenziale per mantenere l’integrità del sistema, garantire la sicurezza e ottenere la conformità. Questo script PowerShell fornisce ai professionisti IT un potente strumento per la ricerca e l’analisi dei log degli eventi, consentendo loro di svolgere le proprie mansioni in modo più efficace.

Per gli MSP e i team IT che gestiscono più sistemi, NinjaOne offre una suite completa di strumenti che possono migliorare ulteriormente la capacità di monitorare e gestire la tua infrastruttura IT. Integrando questo script nei tuoi flussi di lavoro, potrai semplificare le ricerche dei log degli eventi e migliorare le tue operazioni IT complessive.

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ù su NinjaOne Endpoint Management, fai un tour dal vivo, o inizia la tua prova gratuita della piattaforma NinjaOne.

Categorie:

Ti potrebbe interessare anche

Guarda una demo×
×

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.