When I receive an IP address or hostname, the first question is often whether the remote host is running Windows or Linux. That determines which tools, credentials, ports, and remote-management protocols I should try next.

There is no universal detection command that works against every host. A firewall can block ping, WinRM, CIM, and SSH, and a network device can answer in a way that looks like an operating system. The most reliable method is to query an operating-system-specific service after confirming that the host is reachable.

Quick answer

Start with a basic reachability test:

$ComputerName = "server01"

Test-Connection -ComputerName $ComputerName -Count 2

If you can use Windows remote management, query the operating system directly:

Get-CimInstance -ClassName Win32_OperatingSystem -ComputerName $ComputerName |
    Select-Object CSName, Caption, Version

If you can connect with SSH, ask the host itself:

ssh user@$ComputerName 'uname -s; cat /etc/os-release | head -n 1'

The CIM command is a strong Windows indicator. The SSH command is a strong Linux or Unix indicator. If neither works, the result is unknown rather than automatically Windows or Linux.

Method 1: Use ping to check reachability

ping uses ICMP echo requests. It answers the question, “Can I receive an ICMP response from this address?” It does not directly identify the operating system.

Use the PowerShell cmdlet:

Test-Connection -ComputerName server01 -Count 2

Or use the Windows built-in ping.exe command:

ping.exe server01

An unsuccessful ping does not prove that the host is offline. The host or a firewall may be configured to drop ICMP traffic. A successful ping does not prove that the host is Windows or Linux because both systems can respond to ICMP.

Optional: inspect the TTL as a clue

The response time-to-live, or TTL, is sometimes used as a rough operating-system clue. A value near 128 is commonly associated with Windows, while a value near 64 is commonly associated with Linux and other Unix-like systems.

This is only a heuristic. Routers reduce TTL as packets cross the network, operating systems can change their initial TTL, and security devices can generate the response. Use it to decide which test to try next, not as proof.

ping.exe server01

Look for a line similar to TTL=128 or TTL=64 in the response. For a more useful estimate, the observed TTL can be compared with common starting values, but the result is still only an educated guess.

A simple first-pass check

In a small network, this can be a quick way to classify hosts that respond to ping:

PS C:\\Users\\sea> ping cadmon1

Pinging cadmon1 [192.168.0.251] with 32 bytes of data:
Reply from 192.168.0.251: bytes=32 time=3ms TTL=64
Reply from 192.168.0.251: bytes=32 time=6ms TTL=64

PS C:\\Users\\sea> ping dc1

Pinging dc1 [192.168.0.88] with 32 bytes of data:
Reply from 192.168.0.88: bytes=32 time=4ms TTL=128
Reply from 192.168.0.88: bytes=32 time=6ms TTL=128

In this example, dc1 is a Windows host and responds with TTL=128. cadmon1 is a Linux host and responds with TTL=64. That makes TTL a convenient first-pass check when the hosts are on the same local network and their firewall or network equipment is not changing the value.

Checking from Linux or WSL

The displayed TTL depends on where the ping starts. Every router or virtual network hop decrements the original TTL by one. Therefore, when the same hosts are tested from Linux or WSL, the values may appear one lower than they did from Windows.

For example, these pings were run from a Linux shell inside WSL:

sea@LAP3309:~/project/pwshtips/pwshtips.com$ ping 192.168.0.88
PING 192.168.0.88 (192.168.0.88) 56(84) bytes of data.
64 bytes from 192.168.0.88: icmp_seq=1 ttl=127 time=8.81 ms
64 bytes from 192.168.0.88: icmp_seq=2 ttl=127 time=2.28 ms
64 bytes from 192.168.0.88: icmp_seq=3 ttl=127 time=3.48 ms
^C
--- 192.168.0.88 ping statistics ---
7 packets transmitted, 7 received, 0% packet loss

sea@LAP3309:~/project/pwshtips/pwshtips.com$ ping 192.168.0.251
PING 192.168.0.251 (192.168.0.251) 56(84) bytes of data.
64 bytes from 192.168.0.251: icmp_seq=1 ttl=63 time=10.9 ms
64 bytes from 192.168.0.251: icmp_seq=2 ttl=63 time=2.31 ms
64 bytes from 192.168.0.251: icmp_seq=3 ttl=63 time=3.88 ms
64 bytes from 192.168.0.251: icmp_seq=4 ttl=63 time=2.77 ms
^C

In this example, 192.168.0.88 is the Windows host dc1. From Windows, it returned TTL=128; from WSL, it returned TTL=127. The one-count difference is caused by the network path between WSL and the Windows host.

192.168.0.251 is the Linux host cadmon1. From Windows, it returned TTL=64; from WSL, it returned TTL=63. The values still point to the same operating systems because the likely original TTL values are 128 for Windows and 64 for Linux.

For a quick first-pass interpretation:

  • From Windows: TTL=128 commonly indicates Windows, and TTL=64 commonly indicates Linux.
  • From Linux or WSL: TTL=127 commonly indicates Windows, and TTL=63 commonly indicates Linux.
  • From another routed network: the value may be lower again, so account for the number of hops.

Do not classify a host solely from the exact displayed number. Compare the value with the expected starting TTL and consider the network path. A Linux host can be configured with a different TTL, and firewalls, routers, containers, VPNs, and virtualization can change the result.

For a quick interpretation:

  • TTL=128: commonly Windows.
  • TTL=64: commonly Linux or another Unix-like system.
  • Any other value: treat it as unknown until you can query the host directly.

This method is useful because it requires no credentials and only needs the host to answer ping. Use CIM, WinRM, PowerShell remoting, or SSH when the result matters operationally.

Method 2: Query Windows with CIM

Windows Management Instrumentation over CIM is one of the best Windows-specific tests from PowerShell. It queries the Win32_OperatingSystem class and returns the operating-system caption and version.

$ComputerName = "server01"

try {
    $OperatingSystem = Get-CimInstance `
        -ClassName Win32_OperatingSystem `
        -ComputerName $ComputerName `
        -ErrorAction Stop

    [pscustomobject]@{
        ComputerName = $OperatingSystem.CSName
        OperatingSystem = $OperatingSystem.Caption
        Version = $OperatingSystem.Version
        Type = "Windows"
    }
}
catch {
    "Windows CIM query failed: $($_.Exception.Message)"
}

This method normally requires permissions on the remote Windows computer and working firewall rules for remote management. It is not expected to work against a normal Linux host unless that host has been configured to provide a compatible management service.

To query several hosts and keep only successful Windows results:

$ComputerNames = @("server01", "server02", "server03")

foreach ($ComputerName in $ComputerNames) {
    try {
        $OperatingSystem = Get-CimInstance `
            -ClassName Win32_OperatingSystem `
            -ComputerName $ComputerName `
            -ErrorAction Stop

        [pscustomobject]@{
            ComputerName = $ComputerName
            Type = "Windows"
            OperatingSystem = $OperatingSystem.Caption
        }
    }
    catch {
        [pscustomobject]@{
            ComputerName = $ComputerName
            Type = "Unknown"
            OperatingSystem = $null
        }
    }
}

Method 3: Test WinRM and PowerShell remoting

WinRM is another strong Windows indicator. Test-WSMan checks whether the host is responding to the Windows Remote Management protocol.

Test-WSMan -ComputerName server01

A successful response usually means that WinRM is available. It does not necessarily mean that your account is authorized to run commands, so test an actual remoting session when you need a definitive result:

Invoke-Command -ComputerName server01 -ScriptBlock {
    [pscustomobject]@{
        ComputerName = $env:COMPUTERNAME
        Type = "Windows"
        OperatingSystem = (Get-CimInstance Win32_OperatingSystem).Caption
    }
}

If the host is configured for PowerShell remoting over SSH, the remote shell may be PowerShell on either Windows or Linux. In that case, inspect the remote variables instead of assuming that SSH means Linux.

Invoke-Command -HostName server01 -UserName administrator -ScriptBlock {
    [pscustomobject]@{
        ComputerName = $env:COMPUTERNAME
        IsWindows = $IsWindows
        IsLinux = $IsLinux
        IsMacOS = $IsMacOS
    }
}

Method 4: Use SSH and Linux commands

SSH is commonly enabled on Linux, but it can also be enabled on Windows. Therefore, the protocol alone is not enough. Run a command that identifies the remote operating system.

For Linux or Unix-like systems:

ssh user@server01 'uname -s'

Typical output is Linux. To obtain more detailed distribution information:

ssh user@server01 'cat /etc/os-release'

A compact cross-platform check is:

ssh user@server01 'uname -s 2>/dev/null || powershell.exe -NoProfile -Command "$PSVersionTable.OS"'

The first command is intended for a POSIX shell. If the remote SSH service starts PowerShell instead of a POSIX shell, use a PowerShell command explicitly:

ssh administrator@server01 'powershell.exe -NoProfile -Command "$PSVersionTable.OS"'

The exact SSH command depends on the shell configured for the remote account. For a reliable result, use the command that matches the expected remote shell and authenticate with an account that is allowed to run it.

Method 5: Check common ports with Windows built-in tools

Test-NetConnection can check whether common management ports are reachable. This identifies an available service, not the operating system, but it helps select the next test.

$ComputerName = "server01"

22, 135, 139, 445, 5985, 5986 | ForEach-Object {
    Test-NetConnection `
        -ComputerName $ComputerName `
        -Port $_ `
        -InformationLevel Quiet
}

The most useful ports for this purpose are:

  • 22: SSH. Common on Linux, but also available on Windows.
  • 135: Microsoft RPC endpoint mapper. Common on Windows.
  • 139: NetBIOS session service. Common on older Windows configurations.
  • 445: SMB. Common on Windows, but Samba can provide it on Linux.
  • 5985: WinRM over HTTP. Strong Windows indicator.
  • 5986: WinRM over HTTPS. Strong Windows indicator.

Check the port names and results together:

$ComputerName = "server01"
$Ports = 22, 135, 139, 445, 5985, 5986

foreach ($Port in $Ports) {
    $Result = Test-NetConnection `
        -ComputerName $ComputerName `
        -Port $Port `
        -WarningAction SilentlyContinue

    [pscustomobject]@{
        ComputerName = $ComputerName
        Port = $Port
        Open = $Result.TcpTestSucceeded
    }
}

Do not classify a host from one open port. Linux can run Samba and SSH on Windows is common. Use an authenticated query whenever possible.

Method 6: Use Windows built-in name and network tools

These commands are useful for gathering additional evidence:

Resolve-DnsName server01
nslookup.exe server01
tracert.exe server01
arp.exe -a

Resolve-DnsName and nslookup.exe resolve names. tracert.exe shows the network path. arp.exe shows local address-resolution entries. None of these commands identifies the operating system directly, but they help confirm that the hostname, address, and route are correct before deeper testing.

For Windows file and printer sharing, net view may provide additional evidence when SMB is available:

net view \\server01

This is not a universal test. It can fail because of permissions, firewall rules, SMB configuration, or a workgroup/domain boundary. Linux hosts running Samba may also respond.

A practical detection function

The following function first checks reachability, then checks Windows-specific management, and finally checks SSH. It reports the evidence it found instead of claiming certainty when the host cannot be queried.

function Get-RemoteOperatingSystem {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory)]
        [string]$ComputerName,

        [pscredential]$Credential
    )

    $Result = [ordered]@{
        ComputerName = $ComputerName
        Reachable = $false
        Type = "Unknown"
        OperatingSystem = $null
        Method = $null
        Error = $null
    }

    $Result.Reachable = Test-Connection `
        -ComputerName $ComputerName `
        -Count 1 `
        -Quiet `
        -ErrorAction SilentlyContinue

    try {
        $CimParameters = @{
            ClassName = "Win32_OperatingSystem"
            ComputerName = $ComputerName
            ErrorAction = "Stop"
        }

        if ($Credential) {
            $CimParameters.Credential = $Credential
        }

        $OperatingSystem = Get-CimInstance @CimParameters
        $Result.Type = "Windows"
        $Result.OperatingSystem = $OperatingSystem.Caption
        $Result.Method = "CIM"
        return [pscustomobject]$Result
    }
    catch {
        $Result.Error = $_.Exception.Message
    }

    $SshAvailable = Test-NetConnection `
        -ComputerName $ComputerName `
        -Port 22 `
        -InformationLevel Quiet `
        -WarningAction SilentlyContinue

    if ($SshAvailable) {
        $Result.Method = "SSH port 22 is reachable"
    }

    [pscustomobject]$Result
}

Get-RemoteOperatingSystem -ComputerName server01

This function deliberately does not run SSH automatically because SSH usernames, keys, passwords, and remote shells vary. After it reports that port 22 is reachable, run the appropriate ssh command for that host.

How I would decide

Use this order when troubleshooting an unknown host:

  1. Resolve the hostname with Resolve-DnsName.
  2. Run Test-Connection to check basic reachability.
  3. Try Get-CimInstance Win32_OperatingSystem or Test-WSMan for a Windows host.
  4. Try ssh user@host 'uname -s' for a Linux or Unix-like host.
  5. Use Test-NetConnection to check ports 22, 445, 5985, and 5986.
  6. Record the result as Unknown when the host is reachable but its management services are blocked.

The best answer comes from an authenticated operating-system query. Ping, TTL, open ports, DNS, and network paths are useful clues, but they should not be treated as proof.