In der sich ständig weiterentwickelnden Welt der IT-Sicherheit ist es von entscheidender Bedeutung, den Überblick über die Netzwerkverbindungen zu behalten. Ganz gleich, ob Sie ein großes Unternehmensnetzwerk verwalten oder ein kleines bis mittleres Unternehmen beaufsichtigen, es ist wichtig zu wissen, welche IP-Adressen aktiv mit Ihren Systemen kommunizieren.
In diesem Blogbeitrag wird ein spezielles PowerShell-Skript vorgestellt, das Administratoren vor bestimmten IP-Adressen warnt, die entweder Listening-IP-Adressen sind oder sich in einem etablierten Status befinden. Dieses Tool ist von unschätzbarem Wert für IT-Experten, Managed Service Provider (MSPs) und Sicherheitsteams, die ihre Netzwerküberwachungsfunktionen verbessern möchten. Der folgende Inhalt erklärt, wiewie man TCP- und UDP-Verbindungen unter Windows mit PowerShell überwacht.
Verstehen des PowerShell-Skripts
Das bereitgestellte Skript dient als Warnmechanismus für Netzwerkverbindungen und identifiziert bestimmte IP-Adressen im Zustand “Listening” oder “Established”. Sie geht über grundlegende Firewall-Prüfungen hinaus und bietet Einblicke in aktive Verbindungen, die herkömmliche Sicherheitsfilter umgehen können. Das Skript gibt wichtige Details wie Adresse, Prozess-ID, Zustand, Protokoll, lokale Adresse und Prozessnamen aus. Bei der Verwendung von NinjaOne können die Ergebnisse automatisch in einem benutzerdefinierten Feld zur weiteren Analyse gespeichert werden.
Bedeutung für IT-Experten und MSPs
In der heutigen digitalen Welt, in der Cyber-Bedrohungen allgegenwärtig sind, ist eine minutiöse Kontrolle der Netzwerkverbindungen unerlässlich. Dieses Skript berücksichtigt die Notwendigkeit, die Netzwerkaktivitäten in Echtzeit zu überwachen, sodass IT-Experten schnell auf potenzielle Sicherheitsbedrohungen reagieren können. Für MSPs, die mehrere Kundennetzwerke verwalten, bietet dieses Skript eine standardisierte Methode zum Monitoring und zur Berichterstattung über die Netzwerkaktivität und gewährleistet so eine einheitliche und gründliche Überwachung.
Das Skript:
#Requires -Version 5.1 <# .SYNOPSIS Alert on specified addresses that are Listening or Established and optionally save the results to a custom field. .DESCRIPTION Will alert on addresses, regardless if a firewall is blocking them or not. Checks for addresses that are in a 'Listen' or 'Established' state. UDP is a stateless protocol and will not have a state. Outputs the addresses, process ID, state, protocol, local address, and process name. When a Custom Field is provided this will save the results to that custom field. PARAMETER: -IpAddress "192.168.11.1, 192.168.1.1/24" A comma separated list of IP Addresses to check. Can include IPv4 CIDR notation for ranges. IPv6 CIDR notation not supported. (e.g. 192.168.1.0/24, 10.0.10.12) .EXAMPLE -IpAddress "192.168.1.0/24, 10.0.10.12" ## EXAMPLE OUTPUT WITH IpAddress ## [Info] Valid IP Address: 192.168.11.1 [Info] Valid IP Network: 192.168.1.1/24 [Alert] Found Local Address: 192.168.1.18, Local Port: 139, Remote Address: 0.0.0.0, Remote Port: None, PID: 4, Protocol: TCP, State: Listen, Process: System [Alert] Found Local Address: 192.168.1.18, Local Port: 138, Remote Address: None, Remote Port: None, PID: 4, Protocol: UDP, State: None, Process: System [Alert] Found Local Address: 192.168.1.18, Local Port: 137, Remote Address: None, Remote Port: None, PID: 4, Protocol: UDP, State: None, Process: System PARAMETER: -CustomField "ReplaceMeWithAnyMultilineCustomField" Name of the custom field to save the results to. .EXAMPLE -IpAddress "192.168.11.1, 192.168.1.1/24" -CustomField "ReplaceMeWithAnyMultilineCustomField" ## EXAMPLE OUTPUT WITH CustomField ## [Info] Valid IP Address: 192.168.11.1 [Info] Valid IP Network: 192.168.1.1/24 [Alert] Found Local Address: 192.168.1.18, Local Port: 139, Remote Address: 0.0.0.0, Remote Port: None, PID: 4, Protocol: TCP, State: Listen, Process: System [Alert] Found Local Address: 192.168.1.18, Local Port: 138, Remote Address: None, Remote Port: None, PID: 4, Protocol: UDP, State: None, Process: System [Alert] Found Local Address: 192.168.1.18, Local Port: 137, Remote Address: None, Remote Port: None, PID: 4, Protocol: UDP, State: None, Process: System [Info] Saving results to custom field: ReplaceMeWithAnyMultilineCustomField [Info] Results saved to custom field: ReplaceMeWithAnyMultilineCustomField .OUTPUTS None .NOTES Supported Operating Systems: Windows 10/Windows Server 2016 or later with PowerShell 5.1 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]$IpAddress, [String]$CustomFieldName ) begin { function Test-IsElevated { $id = [System.Security.Principal.WindowsIdentity]::GetCurrent() $p = New-Object System.Security.Principal.WindowsPrincipal($id) $p.IsInRole([System.Security.Principal.WindowsBuiltInRole]::Administrator) } 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 = $NinjaValue | Ninja-Property-Set-Piped -Name $Name 2>&1 } if ($CustomField.Exception) { throw $CustomField } } function Test-IPNetwork { param([string]$Text) $Ip, $Prefix = $Text -split '/' $Ip -as [System.Net.IPAddress] -and $Prefix -as [int] -and $Prefix -ge 0 -and $Prefix -le 32 } function Get-IPNetwork { [CmdletBinding()] Param( [Parameter(Mandatory, Position = 0)] [ValidateScript({ $_ -eq ([IPAddress]$_).IPAddressToString })] [string]$IPAddress, [Parameter(Mandatory, Position = 1, ParameterSetName = "SubnetMask")] [ValidateScript({ $_ -eq ([IPAddress]$_).IPAddressToString })] [ValidateScript({ $SMReversed = [IPAddress]$_ $SMReversed = $SMReversed.GetAddressBytes() [array]::Reverse($SMReversed) [IPAddress]$SMReversed = $SMReversed [convert]::ToString($SMReversed.Address, 2) -match "^[1]*0{0,}$" })] [string]$SubnetMask, [Parameter(Mandatory, Position = 1, ParameterSetName = "CIDRNotation")] [ValidateRange(0, 32)] [int]$PrefixLength, [switch]$ReturnAllIPs ) [IPAddress]$IPAddress = $IPAddress if ($SubnetMask) { [IPAddress]$SubnetMask = $SubnetMask $SMReversed = $SubnetMask.GetAddressBytes() [array]::Reverse($SMReversed) [IPAddress]$SMReversed = $SMReversed [int]$PrefixLength = [convert]::ToString($SMReversed.Address, 2).replace(0, '').length } else { [IPAddress]$SubnetMask = ([Math]::Pow(2, $PrefixLength) - 1) * [Math]::Pow(2, (32 - $PrefixLength)) } $FullMask = [UInt32]'0xffffffff' $WildcardMask = [IPAddress]($SubnetMask.Address -bxor $FullMask) $NetworkId = [IPAddress]($IPAddress.Address -band $SubnetMask.Address) $Broadcast = [IPAddress](($FullMask - $NetworkId.Address) -bxor $SubnetMask.Address) # Used for determining first usable IP Address $FirstIPByteArray = $NetworkId.GetAddressBytes() [Array]::Reverse($FirstIPByteArray) # Used for determining last usable IP Address $LastIPByteArray = $Broadcast.GetAddressBytes() [Array]::Reverse($LastIPByteArray) # Handler for /31, /30 CIDR prefix values, and default for all others. switch ($PrefixLength) { 31 { $TotalIPs = 2 $UsableIPs = 2 $FirstIP = $NetworkId $LastIP = $Broadcast $FirstIPInt = ([IPAddress]$FirstIPByteArray).Address $LastIPInt = ([IPAddress]$LastIPByteArray).Address break } 32 { $TotalIPs = 1 $UsableIPs = 1 $FirstIP = $IPAddress $LastIP = $IPAddress $FirstIPInt = ([IPAddress]$FirstIPByteArray).Address $LastIPInt = ([IPAddress]$LastIPByteArray).Address break } default { # Usable Address Space $TotalIPs = [Math]::pow(2, (32 - $PrefixLength)) $UsableIPs = $TotalIPs - 2 # First usable IP $FirstIPInt = ([IPAddress]$FirstIPByteArray).Address + 1 $FirstIP = [IPAddress]$FirstIPInt $FirstIP = ($FirstIP).GetAddressBytes() [Array]::Reverse($FirstIP) $FirstIP = [IPAddress]$FirstIP # Last usable IP $LastIPInt = ([IPAddress]$LastIPByteArray).Address - 1 $LastIP = [IPAddress]$LastIPInt $LastIP = ($LastIP).GetAddressBytes() [Array]::Reverse($LastIP) $LastIP = [IPAddress]$LastIP } } $AllIPs = if ($ReturnAllIPs) { if ($UsableIPs -ge 500000) { Write-Host ('[Warn] Generating an array containing {0:N0} IPs, this may take a little while' -f $UsableIPs) } $CurrentIPInt = $FirstIPInt Do { $IP = [IPAddress]$CurrentIPInt $IP = ($IP).GetAddressBytes() [Array]::Reverse($IP) | Out-Null $IP = ([IPAddress]$IP).IPAddressToString $IP $CurrentIPInt++ } While ($CurrentIPInt -le $LastIPInt) } $obj = [PSCustomObject]@{ NetworkId = ($NetworkId).IPAddressToString Broadcast = ($Broadcast).IPAddressToString SubnetMask = ($SubnetMask).IPAddressToString PrefixLength = $PrefixLength WildcardMask = ($WildcardMask).IPAddressToString FirstIP = ($FirstIP).IPAddressToString LastIP = ($LastIP).IPAddressToString TotalIPs = $TotalIPs UsableIPs = $UsableIPs AllIPs = $AllIPs } Write-Output $obj } } process { if (-not (Test-IsElevated)) { Write-Error -Message "Access Denied. Please run with Administrator privileges." exit 1 } if ($env:ipAddress -and $env:ipAddress -ne 'null') { $IpAddress = $env:ipAddress } if ($env:customFieldName -and $env:customFieldName -ne 'null') { $CustomFieldName = $env:customFieldName } # Parse the Addresses to check $Addresses = if ($IpAddress) { # Validate the IP Address $IpAddress -split ',' | ForEach-Object { "$_".Trim() } | ForEach-Object { if (($_ -as [System.Net.IPAddress])) { Write-Host "[Info] Valid IP Address: $_" [System.Net.IPAddress]::Parse($_) } elseif ($(Test-IPNetwork $_)) { Write-Host "[Info] Valid IP Network: $_" $Address, $PrefixLength = $_ -split '/' try { Get-IPNetwork -IPAddress $Address -PrefixLength $PrefixLength -ReturnAllIPs | Select-Object -ExpandProperty AllIPs } catch { Write-Host "[Error] Invalid IP CIDR: $_" exit 1 } } else { Write-Host "[Error] Invalid IP Address: $_" exit 1 } } } else { $null } # Get the open ports $FoundAddresses = $( Get-NetTCPConnection | Select-Object @( 'LocalAddress' 'LocalPort' @{Name = "RemoteAddress"; Expression = { if ($_.RemoteAddress) { $_.RemoteAddress }else { "None" } } } @{Name = "RemotePort"; Expression = { if ($_.RemotePort) { $_.RemotePort }else { "None" } } } 'State' @{Name = "Protocol"; Expression = { "TCP" } } 'OwningProcess' @{Name = "Process"; Expression = { (Get-Process -Id $_.OwningProcess -ErrorAction SilentlyContinue).ProcessName } } ) Get-NetUDPEndpoint | Select-Object @( 'LocalAddress' 'LocalPort' @{Name = "RemoteAddress"; Expression = { "None" } } @{Name = "RemotePort"; Expression = { "None" } } @{Name = "State"; Expression = { "None" } } @{Name = "Protocol"; Expression = { "UDP" } } 'OwningProcess' @{Name = "Process"; Expression = { (Get-Process -Id $_.OwningProcess -ErrorAction SilentlyContinue).ProcessName } } ) ) | Where-Object { $( <# When Addresses are specified select just those addresses. #> if ($Addresses) { $_.LocalAddress -in $Addresses -or $_.RemoteAddress -in $Addresses } else { $true } ) -and ( <# Filter out anything that isn't listening or established. #> $( $_.Protocol -eq "TCP" -and $( $_.State -eq "Listen" -or $_.State -eq "Established" ) ) -or <# UDP is stateless, return all UDP connections. #> $_.Protocol -eq "UDP" ) } | Sort-Object LocalAddress, RemoteAddress | Select-Object * -Unique if (-not $FoundAddresses -or $FoundAddresses.Count -eq 0) { Write-Host "[Info] No Addresses were found listening or established with the specified network or address" } # Output the found Addresses $FoundAddresses | ForEach-Object { Write-Host "[Alert] Found Local Address: $($_.LocalAddress), Local Port: $($_.LocalPort), Remote Address: $($_.RemoteAddress), Remote Port: $($_.RemotePort), PID: $($_.OwningProcess), Protocol: $($_.Protocol), State: $($_.State), Process: $($_.Process)" } # Save the results to a custom field if one was provided if ($CustomFieldName -and $CustomFieldName -ne 'null') { try { Write-Host "[Info] Saving results to custom field: $CustomFieldName" Set-NinjaProperty -Name $CustomFieldName -Value $( $FoundAddresses | ForEach-Object { "Local Address: $($_.LocalAddress), Local Port: $($_.LocalPort), Remote Address: $($_.RemoteAddress), Remote Port: $($_.RemotePort), PID: $($_.OwningProcess), Protocol: $($_.Protocol), State: $($_.State), Process: $($_.Process)" } | Out-String ) Write-Host "[Info] Results saved to custom field: $CustomFieldName" } catch { Write-Host $_.Exception.Message Write-Host "[Warn] Failed to save results to custom field: $CustomFieldName" exit 1 } } } end { }
Wie das Skript funktioniert
1. Ersteinrichtung
Das Skript erfordert PowerShell Version 5.1 oder höher und ist für die Verwendung unter Windows 10 oder Windows Server 2016 und neuer vorgesehen. Zunächst wird geprüft, ob das Skript mit Administratorrechten ausgeführt wird, die für den Zugriff auf Netzwerkverbindungsinformationen erforderlich sind.
2. Parsen der Parameter
Das Skript akzeptiert zwei Hauptparameter: -IpAddress und -CustomField. Mit dem Parameter -IpAddress können Sie eine Liste von IP-Adressen oder CIDR-notierten IP-Bereichen zur Überwachung angeben. Der Parameter -CustomField ist optional und wird verwendet, um die Ergebnisse in einem benutzerdefinierten Feld von NinjaOne zu speichern.
3. Validierung von Adressen
Sobald die Parameter eingegeben sind, überprüft das Skript jede IP-Adresse oder jeden Netzwerkbereich. Wird eine gültige Adresse oder ein gültiges Netzwerk gefunden, werden alle entsprechenden IPs innerhalb dieses Bereichs abgerufen.
4. Monitoring von Netzwerkverbindungen
Das Skript verwendet die cmdlets Get-NetTCPConnection und Get-NetUDPEndpoint, um alle aktuellen TCP- und UDP-Verbindungen auf dem System zu erfassen. Es filtert diese Verbindungen, um diejenigen zu identifizieren, die sich im “Listening”- oder “Established”-Status für TCP befinden, und erfasst alle UDP-Verbindungen aufgrund ihrer zustandslosen Natur.
5. Ausgabe und optionale Speicherung
Die identifizierten Verbindungen werden dann in einem strukturierten Format angezeigt, das Details wie lokale und entfernte Adressen, Ports, Prozess-IDs, Protokolle und Zustände enthält. Wenn ein benutzerdefiniertes Feld angegeben wird, speichert das Skript diese Ergebnisse zur späteren Analyse in NinjaOne.
Anwendungsfall: Anwendung aus der Praxis
Stellen Sie sich ein Szenario vor, in dem ein MSP die IT-Infrastruktur eines Finanzdienstleisters verwaltet. Das Unternehmen hat strenge Sicherheitsanforderungen und muss alle ein- und ausgehenden Verbindungen auf unbefugten Zugriff überwachen. Der MSP setzt dieses Skript auf allen Servern und Endpunkten ein und gibt die internen IP-Bereiche des Unternehmens an, um sicherzustellen, dass nur zugelassene Geräte mit dem Netzwerk kommunizieren. Wenn das Skript eine Verbindung von einer nicht genehmigten IP-Adresse feststellt, benachrichtigt es den Administrator, der dann sofort Maßnahmen ergreifen kann, um das Risiko zu untersuchen und zu minimieren.
Vergleich mit anderen Methoden
Während andere Tools wie Wireshark oder Netstat detaillierte Einblicke in den Netzwerkverkehr liefern können, bietet dieses PowerShell-Skript einen optimierten und automatisierten Ansatz. Im Gegensatz zu Wireshark, das eine manuelle Paketanalyse erfordert, gibt dieses Skript automatisch Warnmeldungen zu bestimmten Verbindungen aus, wodurch es für das laufende Monitoring geeigneter ist. Im Vergleich zu Netstat bietet das Skript einen Mehrwert, indem es Verbindungen in einem strukturierteren und besser umsetzbaren Format filtert und meldet.
Häufig gestellte Fragen
F: Kann dieses Skript IPv6-Adressen überwachen?
A: Derzeit unterstützt das Skript nur IPv4-Adressen und -Netze. Die Unterstützung von IPv6 würde Änderungen am Skript erfordern, um das unterschiedliche Adressformat zu verarbeiten.
F: Was passiert, wenn eine ungültige IP-Adresse eingegeben wird?
A: Das Skript enthält Validierungschecks und bricht die Ausführung ab, wenn eine ungültige IP-Adresse oder ein ungültiges Netzwerk festgestellt wird, und gibt den Benutzer:innen eine Fehlermeldung.
F: Wie werden die Ergebnisse in NinjaOne gespeichert?
A: Wenn der Parameter -CustomField angegeben wird, verwendet das Skript CLI von NinjaOne, um die Ergebnisse in das angegebene benutzerdefinierte Feld zu speichern. Es wird so sichergestellt, dass die Daten für zukünftige Referenzen oder Berichte zugänglich sind.
Auswirkung der Ergebnisse
Die Ausgabe dieses Skripts kann erhebliche Auswirkungen auf die IT-Sicherheit haben. Durch die Identifizierung und Benachrichtigung bei aktiven Verbindungen können IT-Experten unberechtigten Zugriff, potenzielle Malware-Kommunikation oder falsch konfigurierte Dienste schnell erkennen. Die regelmäßige Verwendung dieses Skripts verbessert die Transparenz der Netzwerkaktivitäten und trägt zu einer sichereren und widerstandsfähigeren IT-Umgebung bei.
Best Practices für die Verwendung des Skripts
- Lassen Sie es regelmäßig laufen: Planen Sie die Ausführung des Skripts in regelmäßigen Abständen, um ein kontinuierliches Monitoring der Netzwerkverbindungen zu gewährleisten.
- Integrieren Sie es mit NinjaOne: Nutzen Sie die Möglichkeiten von NinjaOne, um die Ergebnisse zu speichern und zu analysieren und so eine langfristige Überwachung und Trendanalyse zu ermöglichen.
- Passen Sie es Ihrer Umgebung an: Ändern Sie das Skript nach Bedarf, um bestimmte IP-Bereiche oder zusätzliche Protokollierungsanforderungen zu berücksichtigen.
- Testen Sie es in einer sicheren Umgebung: Bevor Sie das Skript in der Produktion einsetzen, testen Sie es in einer kontrollierten Umgebung, um sicher zu sein, dass es wie erwartet funktioniert.
Abschließende Überlegungen
Dieses PowerShell-Skript bietet eine zuverlässige Lösung für das Monitoring von TCP- und UDP-Verbindungen auf Windows-Systemen. Durch die Integration in Ihr Netzwerküberwachungs-Toolkit, insbesondere in NinjaOne, können Sie mehr Transparenz und Kontrolle über Ihre IT-Umgebung erreichen. IT-Experten und MSPs bietet dieses Skript einen praktischen Ansatz zur Sicherung der Netzwerkintegrität und ermöglicht proaktive Reaktionen auf potenzielle Bedrohungen.