diff --git a/src/classes/public/IPConfig.ps1 b/src/classes/public/IPConfig.ps1 new file mode 100644 index 0000000..21da2f7 --- /dev/null +++ b/src/classes/public/IPConfig.ps1 @@ -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 '.') + } +} diff --git a/src/formats/IPConfig.Format.ps1xml b/src/formats/IPConfig.Format.ps1xml new file mode 100644 index 0000000..bef5a11 --- /dev/null +++ b/src/formats/IPConfig.Format.ps1xml @@ -0,0 +1,57 @@ + + + + + IPConfigTable + + IPConfig + + + + + + + + + + + + + + + + + + + + + + + + + + + Name + + + Status + + + Type + + + IPAddress + + + Gateway + + + DNSServers + + + + + + + + diff --git a/src/functions/private/Get-SubnetMaskFromPrefix.ps1 b/src/functions/private/Get-SubnetMaskFromPrefix.ps1 new file mode 100644 index 0000000..46ce559 --- /dev/null +++ b/src/functions/private/Get-SubnetMaskFromPrefix.ps1 @@ -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() +} diff --git a/src/functions/public/Get-NetIPConfiguration.ps1 b/src/functions/public/Get-NetIPConfiguration.ps1 new file mode 100644 index 0000000..d4d108c --- /dev/null +++ b/src/functions/public/Get-NetIPConfiguration.ps1 @@ -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) + } + } +} diff --git a/src/functions/public/Test-PSModuleTest.ps1 b/src/functions/public/Test-PSModuleTest.ps1 deleted file mode 100644 index 26be2b9..0000000 --- a/src/functions/public/Test-PSModuleTest.ps1 +++ /dev/null @@ -1,18 +0,0 @@ -function Test-PSModuleTest { - <# - .SYNOPSIS - Performs tests on a module. - - .EXAMPLE - Test-PSModule -Name 'World' - - "Hello, World!" - #> - [CmdletBinding()] - param ( - # Name of the person to greet. - [Parameter(Mandatory)] - [string] $Name - ) - Write-Output "Hello, $Name!" -} diff --git a/src/types/IPConfig.Types.ps1xml b/src/types/IPConfig.Types.ps1xml new file mode 100644 index 0000000..41289a6 --- /dev/null +++ b/src/types/IPConfig.Types.ps1xml @@ -0,0 +1,20 @@ + + + + IPConfig + + + Type + AddressFamily + + + Name + InterfaceName + + + Mask + PrefixLength + + + + diff --git a/tests/PSModuleTest.Tests.ps1 b/tests/Net.Tests.ps1 similarity index 57% rename from tests/PSModuleTest.Tests.ps1 rename to tests/Net.Tests.ps1 index b28ab23..a3ffab3 100644 --- a/tests/PSModuleTest.Tests.ps1 +++ b/tests/Net.Tests.ps1 @@ -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' + } } }