Net.psm1
[Diagnostics.CodeAnalysis.SuppressMessageAttribute( 'PSAvoidAssignmentToAutomaticVariable', 'IsWindows', Justification = 'IsWindows doesnt exist in PS5.1' )] [Diagnostics.CodeAnalysis.SuppressMessageAttribute( 'PSUseDeclaredVarsMoreThanAssignments', 'IsWindows', Justification = 'IsWindows doesnt exist in PS5.1' )] [CmdletBinding()] param() $baseName = [System.IO.Path]::GetFileNameWithoutExtension($PSCommandPath) $script:PSModuleInfo = Import-PowerShellDataFile -Path "$PSScriptRoot\$baseName.psd1" $script:PSModuleInfo | Format-List | Out-String -Stream | ForEach-Object { Write-Debug $_ } $scriptName = $script:PSModuleInfo.Name Write-Debug "[$scriptName] - Importing module" if ($PSEdition -eq 'Desktop') { $IsWindows = $true } #region [classes] - [public] Write-Debug "[$scriptName] - [classes] - [public] - Processing folder" #region [classes] - [public] - [IPConfig] Write-Debug "[$scriptName] - [classes] - [public] - [IPConfig] - Importing" 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 '.') } } Write-Debug "[$scriptName] - [classes] - [public] - [IPConfig] - Done" #endregion [classes] - [public] - [IPConfig] Write-Debug "[$scriptName] - [classes] - [public] - Done" #endregion [classes] - [public] #region [functions] - [private] Write-Debug "[$scriptName] - [functions] - [private] - Processing folder" #region [functions] - [private] - [Get-SubnetMaskFromPrefix] Write-Debug "[$scriptName] - [functions] - [private] - [Get-SubnetMaskFromPrefix] - Importing" 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() } Write-Debug "[$scriptName] - [functions] - [private] - [Get-SubnetMaskFromPrefix] - Done" #endregion [functions] - [private] - [Get-SubnetMaskFromPrefix] Write-Debug "[$scriptName] - [functions] - [private] - Done" #endregion [functions] - [private] #region [functions] - [public] Write-Debug "[$scriptName] - [functions] - [public] - Processing folder" #region [functions] - [public] - [Get-NetIPConfiguration] Write-Debug "[$scriptName] - [functions] - [public] - [Get-NetIPConfiguration] - Importing" 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) } } } Write-Debug "[$scriptName] - [functions] - [public] - [Get-NetIPConfiguration] - Done" #endregion [functions] - [public] - [Get-NetIPConfiguration] Write-Debug "[$scriptName] - [functions] - [public] - Done" #endregion [functions] - [public] #region Class exporter # Get the internal TypeAccelerators class to use its static methods. $TypeAcceleratorsClass = [psobject].Assembly.GetType( 'System.Management.Automation.TypeAccelerators' ) # Ensure none of the types would clobber an existing type accelerator. # If a type accelerator with the same name exists, throw an exception. $ExistingTypeAccelerators = $TypeAcceleratorsClass::Get # Define the types to export with type accelerators. $ExportableEnums = @( ) $ExportableEnums | Foreach-Object { Write-Verbose "Exporting enum '$($_.FullName)'." } foreach ($Type in $ExportableEnums) { if ($Type.FullName -in $ExistingTypeAccelerators.Keys) { Write-Verbose "Enum already exists [$($Type.FullName)]. Skipping." } else { Write-Verbose "Importing enum '$Type'." $TypeAcceleratorsClass::Add($Type.FullName, $Type) } } $ExportableClasses = @( [IPConfig] ) $ExportableClasses | Foreach-Object { Write-Verbose "Exporting class '$($_.FullName)'." } foreach ($Type in $ExportableClasses) { if ($Type.FullName -in $ExistingTypeAccelerators.Keys) { Write-Verbose "Class already exists [$($Type.FullName)]. Skipping." } else { Write-Verbose "Importing class '$Type'." $TypeAcceleratorsClass::Add($Type.FullName, $Type) } } # Remove type accelerators when the module is removed. $MyInvocation.MyCommand.ScriptBlock.Module.OnRemove = { foreach ($Type in ($ExportableEnums + $ExportableClasses)) { $null = $TypeAcceleratorsClass::Remove($Type.FullName) } }.GetNewClosure() #endregion Class exporter #region Member exporter $exports = @{ Alias = '*' Cmdlet = '' Function = 'Get-NetIPConfiguration' Variable = '' } Export-ModuleMember @exports #endregion Member exporter |