Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
73 changes: 73 additions & 0 deletions src/classes/public/IPConfig.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
class IPConfig {
# The interface name
[string] $InterfaceName

# The interface description
[string] $Description

# The interface status
[System.Net.NetworkInformation.OperationalStatus] $Status

# The address family
[string] $AddressFamily

# The IP address
[string] $IPAddress

# The prefix length
[int] $PrefixLength

# The subnet mask
[string] $SubnetMask

# The gateway
[string] $Gateway

# The DNS servers
[string] $DNSServers

IPConfig(
[System.Net.NetworkInformation.NetworkInterface] $Interface,
[System.Net.NetworkInformation.UnicastIPAddressInformation] $AddressInformation,
[System.Net.NetworkInformation.IPInterfaceProperties] $InterfaceProperties
) {
$this.InterfaceName = $Interface.Name
$this.Description = $Interface.Description
$this.Status = $Interface.OperationalStatus
switch ($AddressInformation.Address.AddressFamily) {
([System.Net.Sockets.AddressFamily]::InterNetwork) { $this.AddressFamily = 'IPv4'; break }
([System.Net.Sockets.AddressFamily]::InterNetworkV6) { $this.AddressFamily = 'IPv6'; break }
default { $this.AddressFamily = $AddressInformation.Address.AddressFamily.ToString() }
}
$this.IPAddress = $AddressInformation.Address.IPAddressToString
$this.PrefixLength = $AddressInformation.PrefixLength

if ($AddressInformation.Address.AddressFamily -eq [System.Net.Sockets.AddressFamily]::InterNetwork) {
$this.SubnetMask = [IPConfig]::ConvertPrefixToMask($AddressInformation.PrefixLength)
} else {
# IPv6 masks are represented by prefix length
$this.SubnetMask = $null
}

$this.Gateway = ($InterfaceProperties.GatewayAddresses | ForEach-Object { $_.Address.IPAddressToString }) -join ', '
$this.DNSServers = ($InterfaceProperties.DnsAddresses | ForEach-Object { $_.IPAddressToString }) -join ', '
}

hidden static [string] ConvertPrefixToMask([int] $prefixLength) {
if ($prefixLength -le 0) { return '0.0.0.0' }
if ($prefixLength -ge 32) { return '255.255.255.255' }

[int[]] $octets = 0, 0, 0, 0
$bits = $prefixLength
for ($i = 0; $i -lt 4; $i++) {
$take = [Math]::Min(8, $bits)
if ($take -le 0) {
$octets[$i] = 0
} else {
$octets[$i] = 255 - ([math]::Pow(2, (8 - $take)) - 1)
}
$bits -= $take
}
return ($octets -join '.')
}
}
57 changes: 57 additions & 0 deletions src/formats/IPConfig.Format.ps1xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
<?xml version="1.0" encoding="utf-8"?>
<Configuration>
<ViewDefinitions>
<View>
<Name>IPConfigTable</Name>
<ViewSelectedBy>
<TypeName>IPConfig</TypeName>
</ViewSelectedBy>
<TableControl>
<TableHeaders>
<TableColumnHeader>
<Label>Name</Label>
</TableColumnHeader>
<TableColumnHeader>
<Label>Status</Label>
</TableColumnHeader>
<TableColumnHeader>
<Label>Type</Label>
</TableColumnHeader>
<TableColumnHeader>
<Label>Address</Label>
</TableColumnHeader>
<TableColumnHeader>
<Label>Gateway</Label>
</TableColumnHeader>
<TableColumnHeader>
<Label>DNSServers</Label>
</TableColumnHeader>
</TableHeaders>
<TableRowEntries>
<TableRowEntry>
<TableColumnItems>
<TableColumnItem>
<PropertyName>Name</PropertyName>
</TableColumnItem>
<TableColumnItem>
<PropertyName>Status</PropertyName>
</TableColumnItem>
<TableColumnItem>
<PropertyName>Type</PropertyName>
</TableColumnItem>
<TableColumnItem>
<PropertyName>IPAddress</PropertyName>
</TableColumnItem>
<TableColumnItem>
<PropertyName>Gateway</PropertyName>
</TableColumnItem>
<TableColumnItem>
<PropertyName>DNSServers</PropertyName>
</TableColumnItem>
</TableColumnItems>
</TableRowEntry>
</TableRowEntries>
</TableControl>
</View>
</ViewDefinitions>
</Configuration>
58 changes: 58 additions & 0 deletions src/functions/private/Get-SubnetMaskFromPrefix.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
function Get-SubnetMaskFromPrefix {
<#
.SYNOPSIS
Converts a CIDR prefix length into a subnet mask in dotted decimal notation.

.DESCRIPTION
The Get-SubnetMaskFromPrefix function accepts an integer prefix (e.g., 24) and converts it into a corresponding
subnet mask (e.g., 255.255.255.0). It supports prefix lengths from 0 through 32. If the input prefix is outside
this valid range, the function returns `$null`. This is useful when translating CIDR-style network definitions
into traditional subnet mask format.

.EXAMPLE
Get-SubnetMaskFromPrefix -prefix 24

Output:
```powershell
255.255.255.0
```

Converts a /24 prefix to the subnet mask 255.255.255.0.

.OUTPUTS
System.String

.NOTES
The subnet mask string in dotted decimal format (e.g., 255.255.255.0).
Returns `$null` if the prefix is not within the valid range (0–32).

.LINK
https://psmodule.io/Net/Functions/Get-SubnetMaskFromPrefix
#>
[OutputType([string])]
[CmdletBinding()]
param(
# The CIDR prefix length (0–32) to convert into a subnet mask.
[Parameter(Mandatory)]
[int] $prefix
)

if ($prefix -lt 0 -or $prefix -gt 32) { return $null }

$bytes = [byte[]](0..3 | ForEach-Object {
# Calculate the number of subnet bits for this octet (max 8, min 0)
$bits = [Math]::Max([Math]::Min($prefix - (8 * $_), 8), 0)
if ($bits -le 0) {
# If no bits are set for this octet, value is 0
0
} elseif ($bits -ge 8) {
# If all bits are set for this octet, value is 255
255
} else {
# For partial octets, shift 0xFF left by (8 - $bits) to set the correct number of bits,
# then mask with 0xFF to ensure only 8 bits are used
((0xFF -shl (8 - $bits)) -band 0xFF)
}
})
[System.Net.IPAddress]::new($bytes).ToString()
}
83 changes: 83 additions & 0 deletions src/functions/public/Get-NetIPConfiguration.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
function Get-NetIPConfiguration {
<#
.SYNOPSIS
Retrieves IP configuration details for network interfaces on the system.

.DESCRIPTION
This function gathers IP configuration data, including IP addresses, subnet masks, gateway addresses,
and DNS servers for all network interfaces. It supports optional filtering by interface operational status
(Up or Down) and address family (IPv4 or IPv6). The output includes detailed per-address information in
a structured object format for each network interface and IP address combination.

.EXAMPLE
Get-NetIPConfiguration

Output:
```powershell
InterfaceName : Ethernet
Description : Intel(R) Ethernet Connection
Status : Up
AddressFamily : InterNetwork
IPAddress : 192.168.1.10
PrefixLength : 24
SubnetMask : 255.255.255.0
Gateway : 192.168.1.1
DNSServers : 8.8.8.8, 1.1.1.1
```

Retrieves the IPv4 configuration for all network interfaces that are currently operational (Up).

.OUTPUTS
IPConfig

.LINK
https://psmodule.io/Net/Functions/Get-NetIPConfiguration
#>

[Alias('IPConfig')]
[OutputType([IPConfig])]
[CmdletBinding()]
param(
# Filters interfaces based on operational status ('Up' or 'Down')
[Parameter()]
[ValidateSet('Up', 'Down')]
[string] $InterfaceStatus,

# Filters IP addresses by address family ('IPv4' or 'IPv6')
[Parameter()]
[ValidateSet('IPv4', 'IPv6')]
[string] $AddressFamily
)

# Map AddressFamily parameter to .NET enum
$familyEnum = $null
if ($AddressFamily) {
$familyEnum = if ($AddressFamily -eq 'IPv4') {
[System.Net.Sockets.AddressFamily]::InterNetwork
} else {
[System.Net.Sockets.AddressFamily]::InterNetworkV6
}
}

$interfaces = [System.Net.NetworkInformation.NetworkInterface]::GetAllNetworkInterfaces()

# Apply optional interface status filter using enum for robustness
if ($InterfaceStatus) {
$statusEnum = [System.Net.NetworkInformation.OperationalStatus]::$InterfaceStatus
$interfaces = $interfaces | Where-Object { $_.OperationalStatus -eq $statusEnum }
}

foreach ($adapter in $interfaces) {
$ipProps = $adapter.GetIPProperties()

# Filter unicast addresses by address family if requested
$unicast = $ipProps.UnicastAddresses
if ($familyEnum) {
$unicast = $unicast | Where-Object { $_.Address.AddressFamily -eq $familyEnum }
}

foreach ($addr in $unicast) {
[IPConfig]::new($adapter, $addr, $ipProps)
}
}
}
18 changes: 0 additions & 18 deletions src/functions/public/Test-PSModuleTest.ps1

This file was deleted.

20 changes: 20 additions & 0 deletions src/types/IPConfig.Types.ps1xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
<?xml version="1.0" encoding="utf-8"?>
<Types>
<Type>
<Name>IPConfig</Name>
<Members>
<AliasProperty>
<Name>Type</Name>
<ReferencedMemberName>AddressFamily</ReferencedMemberName>
</AliasProperty>
<AliasProperty>
<Name>Name</Name>
<ReferencedMemberName>InterfaceName</ReferencedMemberName>
</AliasProperty>
<AliasProperty>
<Name>Mask</Name>
<ReferencedMemberName>PrefixLength</ReferencedMemberName>
</AliasProperty>
</Members>
</Type>
</Types>
20 changes: 17 additions & 3 deletions tests/PSModuleTest.Tests.ps1 → tests/Net.Tests.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,22 @@
[CmdletBinding()]
param()

Describe 'Module' {
It 'Function: Test-PSModuleTest' {
Test-PSModuleTest -Name 'World' | Should -Be 'Hello, World!'
Describe 'Net' {
Context 'Get-NetIPConfiguration' {
It 'returns expected results' {
$results = Get-NetIPConfiguration
LogGroup 'Results' {
Write-Host "$($results | Out-String)"
}
$results | Should -BeOfType 'IPConfig'
}

It 'IPConfig alias works' {
$results = IPConfig
LogGroup 'Results' {
Write-Host "$($results | Format-List | Out-String)"
}
$results | Should -BeOfType 'IPConfig'
}
}
}
Loading