How to Use PowerShell to Detect Open and Established Ports in Windows

In the ever-evolving landscape of IT security and management, monitoring network activity and identifying potential vulnerabilities is crucial. Open ports can serve as entry points for unauthorized access, making it vital for IT professionals to regularly audit and manage them.

PowerShell, with its powerful scripting capabilities, offers a flexible and efficient way to monitor these ports. In this post, we’ll explore a PowerShell script designed to detect open and established ports on a Windows system, discuss its practical applications, and provide insights into its operation and usage.

Understanding the Need for Port Monitoring

Ports are an essential aspect of network communication, enabling different services and applications to interact with one another over a network. However, open ports, especially those that are not actively monitored, can become security risks.

Cybercriminals often exploit these vulnerabilities to gain unauthorized access to systems. This makes it imperative for IT professionals, particularly those in Managed Service Providers (MSPs), to regularly audit their network for open ports, ensuring that only necessary ports are open and listening.

This PowerShell script is designed to aid IT professionals in this task by automatically detecting open and established ports and providing detailed information about them. It can also save the results to a custom field, facilitating better tracking and documentation.

The Script:

#Requires -Version 5.1

<#
.SYNOPSIS
    Alert on specified ports that are Listening or Established and optionally save the results to a custom field.
.DESCRIPTION
    Will alert on open ports, regardless if a firewall is blocking them or not.
    Checks for open ports that are in a 'Listen' or 'Established' state.
    UDP is a stateless protocol and will not have a state.
    Outputs the open ports, process ID, state, protocol, local address, and process name.
    When a Custom Field is provided this will save the results to that custom field.

.EXAMPLE
    (No Parameters)
    ## EXAMPLE OUTPUT WITHOUT PARAMS ##
    [Alert] Found open port: 80, PID: 99, State: Listen, Local Address: 0.0.0.0, Process: nginx
    [Alert] Found open port: 500, PID: 99, State: Listen, Local Address: 0.0.0.0, Process: nginx

PARAMETER: -Port "100,200,300-350, 400"
    A comma separated list of ports to check. Can include ranges (e.g. 100,200,300-350, 400)
.EXAMPLE
    -Port "80,200,300-350, 400"
    ## EXAMPLE OUTPUT WITH Port ##
    [Alert] Found open port: 80, PID: 99, State: Listen, Local Address: 0.0.0.0, Process: nginx

PARAMETER: -CustomField "ReplaceMeWithAnyMultilineCustomField"
    Name of the custom field to save the results to.
.EXAMPLE
    -Port "80,200,300-350, 400" -CustomField "ReplaceMeWithAnyMultilineCustomField"
    ## EXAMPLE OUTPUT WITH CustomField ##
    [Alert] Found open port: 80, PID: 99, State: Listen, Local Address: 0.0.0.0, Process: nginx
    [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]$PortsToCheck,
    [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
        }
    }
}
process {
    if (-not (Test-IsElevated)) {
        Write-Error -Message "Access Denied. Please run with Administrator privileges."
        exit 1
    }
    if ($env:portsToCheck -and $env:portsToCheck -ne 'null') {
        $PortsToCheck = $env:portsToCheck
    }
    if ($env:customFieldName -and $env:customFieldName -ne 'null') {
        $CustomFieldName = $env:customFieldName
    }

    # Remove any whitespace
    $PortsToCheck = $PortsToCheck -replace '\s', ''

    # Parse the ports to check
    $Ports = if ($PortsToCheck) {
        # Split the ports by comma and handle ranges
        $PortsToCheck -split ',' | ForEach-Object {
            # Trim the whitespace
            $Ports = "$_".Trim()
            # If the port is a range, expand it
            if ($Ports -match '-') {
                # Split the range and expand it
                $Range = $Ports -split '-' | ForEach-Object { "$_".Trim() } | Where-Object { $_ }
                if ($Range.Count -ne 2) {
                    Write-Host "[Error] Invalid range formatting, must be two number with a dash in between them (eg 1-10): $PortsToCheck"
                    exit 1
                }
                try {
                    $Range[0]..$Range[1]
                }
                catch {
                    Write-Host "[Error] Failed to parse range, must be two number with a dash in between them (eg 1-10): $PortsToCheck"
                    exit 1
                }
            }
            else {
                $Ports
            }
        }
    }
    else { $null }

    if ($($Ports | Where-Object { [int]$_ -gt 65535 })) {
        Write-Host "[Error] Can not search for ports above 65535. Must be with in the range of 1 to 65535."
        exit 1
    }

    # Get the open ports
    $FoundPorts = $(
        Get-NetTCPConnection | Select-Object @(
            'LocalAddress'
            'LocalPort'
            'State'
            @{Name = "Protocol"; Expression = { "TCP" } }
            'OwningProcess'
            @{Name = "Process"; Expression = { (Get-Process -Id $_.OwningProcess -ErrorAction SilentlyContinue).ProcessName } }
        )
        Get-NetUDPEndpoint | Select-Object @(
            'LocalAddress'
            'LocalPort'
            @{Name = "State"; Expression = { "None" } }
            @{Name = "Protocol"; Expression = { "UDP" } }
            'OwningProcess'
            @{Name = "Process"; Expression = { (Get-Process -Id $_.OwningProcess -ErrorAction SilentlyContinue).ProcessName } }
        )
    ) | Where-Object {
        $(
            <# When Ports are specified select just those ports. #>
            if ($Ports) { $_.LocalPort -in $Ports }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 LocalPort | Select-Object * -Unique

    if (-not $FoundPorts -or $FoundPorts.Count -eq 0) {
        Write-Host "[Info] No ports were found listening or established with the specified: $PortsToCheck"
    }

    # Output the found ports
    $FoundPorts | ForEach-Object {
        Write-Host "[Alert] Found open port: $($_.LocalPort), PID: $($_.OwningProcess), Protocol: $($_.Protocol), State: $($_.State), Local IP: $($_.LocalAddress), 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 $(
                $FoundPorts | ForEach-Object {
                    "Open port: $($_.LocalPort), PID: $($_.OwningProcess), Protocol: $($_.Protocol), State: $($_.State), Local Address: $($_.LocalAddress), 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 {
    
    
    
}

 

Access over 300+ scripts in the NinjaOne Dojo

Get Access

How the Script Works

The script is designed with a clear objective: to identify and report on open TCP and UDP ports that are either in a ‘Listening’ or ‘Established’ state. Below is a detailed breakdown of how the script achieves this:

1. Initial Setup and Functions:

  • The script begins by defining several utility functions, including Test-IsElevated, which checks if the script is running with administrator privileges. This is essential as checking for open ports requires elevated permissions.
  • Another key function, Set-NinjaProperty, is included to handle the saving of results to a custom field, if specified. This function manages different data types and ensures that the character limit for the field is not exceeded.

2. Parameter Handling:

  • The script accepts two parameters: PortsToCheck, a comma-separated list of ports to monitor, and CustomFieldName, the name of the custom field where results will be stored.
  • It then processes these parameters, expanding any port ranges and removing whitespace for accurate processing.

3. Port Detection:

  • The script uses Get-NetTCPConnection and Get-NetUDPEndpoint cmdlets to retrieve information about active TCP and UDP connections on the system.
  • It filters the results to include only those ports that are listening or established (for TCP), and all UDP ports, as UDP is a stateless protocol.
  • The filtered results are then sorted and formatted for output.

4. Output and Custom Field Saving:

  • The script outputs the detected ports, along with relevant details like the process ID, state, local address, and process name.
  • If a custom field is specified, the script attempts to save the results using the Set-NinjaProperty function, handling any errors that may occur during this process.

Potential Use Cases

Imagine an IT professional managing the security of a mid-sized company’s network. Regular port audits are part of their routine to ensure that only necessary services are running and accessible. By implementing this PowerShell script, the IT professional can automate the process of identifying open ports, reducing the risk of leaving vulnerable ports exposed.

For instance, after running the script, the IT professional notices that an unexpected port is open and linked to a non-essential service. They can then take steps to close this port, thereby reducing the potential attack surface of their network.

Comparisons to Other Methods

Traditionally, IT professionals might use tools like netstat or third-party network scanning tools to identify open ports. While these tools are effective, they often require manual intervention and may not integrate easily with automated systems. This PowerShell script offers a more integrated approach, allowing for automation within existing workflows and providing flexibility through parameters like custom field saving.

Frequently Asked Questions

Q: Do I need to run this script with administrator privileges?

Yes, the script requires administrator privileges to accurately detect open ports and their associated processes.

Q: Can the script check for UDP ports as well?

Yes, the script checks for both TCP and UDP ports, with TCP ports being filtered by their state and UDP ports being listed regardless of state.

Q: What happens if I specify an invalid port range?

The script includes error handling to ensure that only valid port ranges are processed. If an invalid range is specified, the script will provide an error message and exit.

Implications for IT Security

The results generated by this script can have significant implications for IT security. Regular monitoring of open ports can help detect unauthorized services running on a network, potentially indicating a security breach. By identifying and closing unnecessary ports, IT professionals can mitigate risks and enhance the overall security posture of their organization.

Best Practices for Using This Script

  • Run Regularly: Schedule the script to run regularly to ensure continuous monitoring of your network’s open ports.
  • Use Custom Fields Wisely: When saving results to a custom field, ensure that the field is appropriately named and managed to avoid overwriting important data.
  • Combine with Other Security Measures: Use this script as part of a broader security strategy, combining it with other tools and practices to ensure comprehensive protection.

Final Thoughts

This PowerShell script is a powerful tool for IT professionals looking to automate the process of detecting and monitoring open ports. By integrating this script into your regular security practices, you can enhance your network’s defense mechanisms and quickly respond to potential vulnerabilities. NinjaOne offers a range of tools that complement such scripts, providing a robust platform for IT management and security monitoring.

Add a Comment

Your email address will not be published. Required fields are marked *

Next Steps

Building an efficient and effective IT team requires a centralized solution that acts as your core service deliver tool. NinjaOne enables IT teams to monitor, manage, secure, and support all their devices, wherever they are, without the need for complex on-premises infrastructure.

Learn more about NinjaOne Remote Script Deployment, check out a live tour, or start your free trial of the NinjaOne platform.

Categories:

You might also like

×

See NinjaOne in action!

By submitting this form, I accept NinjaOne's privacy policy.

NinjaOne Terms & Conditions

By clicking the “I Accept” button below, you indicate your acceptance of the following legal terms as well as our 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 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).