Bitwise operators in PowerShell and why you should know them
2024-01-17For what bitwise operators are? I will show you this here by writing a simple subnet calculator in PowerShell. But you can use bitwise operator for much more.
What are bitwise-operators
With bitwise operators, it is possible to manipulate single bits in a bitfield. Bitwise operators are useful if you have to work with status flags. You can use it:
- if you would like to know if a specific flag is set
- or toggle the status of a specific flag
- or set a specific flag
- to negate all bits in a value
- to move bits to a higher or lower position
Bitwise operators work relatively low-level.
But also you can use bitwise operators to build a subnet-calculator.
Operator name | Opearator | Functionality |
---|---|---|
Binary AND | -band | This operator performs a bitwise AND operation on two numbers. It sets each bit to 1 if both bits are 1. |
Binary OR | -bor | This operator performs a bitwise OR operation on two numbers. It sets each bit to 1 if either of the corresponding bits is 1. |
Binary Exclusive OR | -bxor | This operator performs a bitwise XOR (exclusive OR) operation on two numbers. It sets each bit to 1 if only one of the corresponding bits is 1. |
shift right | -shr | This operator shifts the bits of a number to the right by a specified number of positions. |
shift left | -shl | This operator shifts the bits of a number to the left by a specified number of positions. |
binary NOT | -bnot | This operator performs a bitwise NOT operation on a number. It flips each bit from 0 to 1 and vice versa. |
Suppose we have 6 bits for storing numbers. The highest value we can store is 2⁶ -1 = 63d.
This is because every number is a power of 2. So the position of the bit minus 1 is the corresponding power of 2.
The bits are counted from right to left.
Number of bit | 6 | 5 | 4 | 3 | 2 | 1 | Value |
---|---|---|---|---|---|---|---|
Worth of bit | 32 | 16 | 8 | 4 | 2 | 1 | |
Calculation of value | 2⁵ | 2⁴ | 2³ | 2² | 2¹ | 2⁰ | |
1 | 1 | 1 | 1 | 1 | 1 | 63d |
Binary AND
32 | 16 | 8 | 4 | 2 | 1 | Decimal Value |
---|---|---|---|---|---|---|
0 | 0 | 1 | 1 | 1 | 0 | 14 |
-BAND | ||||||
0 | 1 | 0 | 0 | 1 | 1 | 19 |
= | ||||||
0 | 0 | 0 | 0 | 1 | 0 | 2 |
With binary AND you can also easily determine if a number is an odd number or an even number.
Mostly this is done with the modulo operator % aka integer division with a reminder.
For example:
6 : 2 = 3 R 0 so 6 is an even number.
5 : 2 = 2 R 1 so 5 is an odd number.
$number = 3
if(($number % 2) -ne 0) {
write-output "Number $number is a odd number"
} else {
write-output "Number $number is a even number"
}
With binary AND you check if the first bit, 2⁰ is set, if so then the number is an odd number.
$number=3
if($number -band 1){
write-output "Number $number is a odd number"
} else {
write-output "Number $number is a even number"
}
Binary OR
32 | 16 | 8 | 4 | 2 | 1 | Decimal Value |
---|---|---|---|---|---|---|
0 | 0 | 1 | 1 | 1 | 0 | 14 |
-BOR | ||||||
0 | 1 | 0 | 0 | 1 | 1 | 19 |
= | ||||||
0 | 1 | 1 | 1 | 1 | 1 | 31 |
As you can see the binary OR "merge" the two values but it is NOT the same as an addition
14 -bor 19
The binary OR is mostly used to add flags together. For example, let's say we would like to open a file for Read and Write and we would allow other processes to read the file but not to write.
The flag for read-write mode is 0x00000002h = 000010b = 2d
The flag for deny write to other processes is 0x00000020h = 100000b = 32d
To get these two flags together you do a binary OR
0x00000002 -bor 0x00000020
The result is 0x00000022h = 100010b = 34d
Binary XOR
32 | 16 | 8 | 4 | 2 | 1 | Decimal Value |
---|---|---|---|---|---|---|
0 | 0 | 1 | 1 | 1 | 0 | 14 |
-BXOR | ||||||
0 | 1 | 0 | 0 | 1 | 1 | 19 |
= | ||||||
0 | 1 | 1 | 1 | 0 | 1 | 29 |
In PowerShell, this looks as follows:
14 -bxor 19
A funny usage of the binary XOR is to switch the value of two variables without a temporary variable. Ok in PowerShell you do not need a temporary variable for content switching you normally do this, this way:
$a = 14
$b = 19
$a,$b = $b,$a
but you can do it also this way if the value is an integer:
$a = 14
$b = 19
$a = $a -bxor $b
$b = $a -bxor $b
$a = $a -bxor $b
The binary XOR is used in RAID Systems or encryption algorithms.
(Binary) shift left (by two bits)
32 | 16 | 8 | 4 | 2 | 1 | Decimal Value | 0 | 0 | 1 | 1 | 1 | 0 | 14 |
---|---|---|---|---|---|---|
-SHL 2 | ||||||
= | ||||||
1 | 1 | 1 | 0 | 0 | 0 | 56 |
14 -shl 2
(Binary) shift right (by two bits)
32 | 16 | 8 | 4 | 2 | 1 | Decimal Value |
---|---|---|---|---|---|---|
1 | 1 | 1 | 0 | 0 | 0 | 56 |
-SHR 2 | ||||||
= | ||||||
0 | 0 | 1 | 1 | 1 | 0 | 14 |
56 -shr 2
Binary NOT
32 | 16 | 8 | 4 | 2 | 1 | Decimal Value |
---|---|---|---|---|---|---|
-BNOT | ||||||
0 | 0 | 1 | 1 | 1 | 0 | 14 |
= | ||||||
1 | 1 | 0 | 0 | 0 | 1 | 49 |
32 | 16 | 8 | 4 | 2 | 1 | Decimal Value |
---|---|---|---|---|---|---|
-BNOT | ||||||
1 | 1 | 0 | 0 | 0 | 1 | 49 |
= | ||||||
0 | 0 | 1 | 1 | 1 | 0 | 14 |
We have to set the 6 lowest bits of a 32-bit unsigned integer to 1. This represents our "mask" This is calculated as follows: 2⁶ = 64d = 1000000b so we can see the 7th bit is set to 1 it represents 64 but we need the 6 bits on the right side set to 1 so we have to subtract 1. 2⁶ -1 = 63d = 111111b
In PowerShell, we have multiple ways to do that. The easiest is to use the .NET System.Math library and do exactly what we have done above:
$mask=[uint32]([math]::pow(2,6)-1)
An other way is to get the highest possible 32-bit (unsigned) integer either by using the .NET System.Math library or
the .NET System.UInt32 static property MaxValue
.
[uint32]([math]::pow(2,32)-1)
4294967295
[uint32]::MaxValue
4294967295
but now further steps are necessary to get the right result. One is to do a shift right by 26 bits (32-6).
4294967295 -shr 26
63
An other is to do a shift left by 6 bits and then do a binary not
4294967295 -shl 6
4294967232
-bnot [uint32]4294967232
63
Now we have our "mask" and can use it to get the right value if we assume that we have a 6-bit binary value representing 14d.
(-bnot 14) -band $mask
49
(-bnot 49) -band $mask
14
the binary AND is necessary to "drop" the unneeded 26 bits (bits 7 to 32) which have also the value 1 after the binary not.
The binary NOT on 14 yields either the integer -15 (signed) or the integer 4294967281 (unsigned). If we now do a binary AND with the result and 63, only the bits that remain at 1 in the result and 63 remain at 1
The easy IPv4 subnet calculator.
Now we have enough information to build up our easy subnet calculator in PowerShell, using binary operators. Trust me it sounds much more complicated than it is. It's more complicated to do this with math calculations I've seen in a script.
What is an IPv4 address?
For a computer, an IPv4 address is an unsigned 32-bit integer, nothing else. For better human readability and because computers of the area when IPv4 was developed do not have 32-bit integers the IPv4 address is split into 4 8-bit unsigned bytes. each byte can store a value between 0 and 255.
But nowadays this problem does not exist anymore.
Get the IP address of the first connected NIC
To make it easy for us, we will get the first IPv4 address of the first connected NIC
$FirstIP=((Get-NetAdapter | Where-Object {$_.Status -eq 'Up'})[0] | Get-NetIPAddress)[0]
in my case, the following is stored in $FirstIP
:
IPAddress | : | 172.26.1.105 |
---|---|---|
InterfaceIndex | : | 10 |
InterfaceAlias | : | Wi-Fi |
AddressFamily | : | IPv4 |
Type | : | Unicast |
PrefixLength | : | 24 |
PrefixOrigin | : | Dhcp |
SuffixOrigin | : | Dhcp |
AddressState | : | Preferred |
ValidLifetime | : | 29.21:16:38 |
PreferredLifetime | : | 29.21:16:38 |
SkipAsSource | : | False |
Store | : | ActiveStore |
There are only 2 values of interest: IPAddress, PrefixLength
[ipaddress]$IpAddress = $FirstIP.IPAddress
[byte]$CIDR = $FirstIP.PrefixLength
In $IpAddress
is stored the following information:
Address | : | 1761680044 |
---|---|---|
AddressFamily | : | InterNetwork |
ScopeId | : | |
IsIPv6Multicast | : | False |
IsIPv6LinkLocal | : | False |
IsIPv6SiteLocal | : | False |
IsIPv6Teredo | : | False |
IsIPv4MappedToIPv6 | : | False |
IPAddressToString | : | 172.26.1.105 |
therefore we store it in a variable of type unsigned integer.
[uint32]$IPAddressAsInt = $IpAddress.Address
now remember, an IPv4 address has 4 address bytes these are stored all together in a 32-bit unsigned integer. Ok you can
get the address bytes by calling the method GetAddressBytes()
$IpAddress.GetAddressBytes()
but for this use case, this does not bring any advantage to us. So we go further with the 32-bit unsigned integer.
In the next step, we check if the value of $IPAddressAsInt
is what we expect. we do this with a binary AND and a
mask of 255
$IPAddressAsInt -band 255
172
ok, the good thing is that 172 is part of our IP address the bad one is that it is on the wrong "side" of the integer. So we have to clarify what's going on.
Because IP addresses are used on different systems and they all must be able to understand each other, they all must use a standardized storage format for IP addresses. Therefore IP addresses are stored on all systems in the so-called network byte order, which is also known as Big-Endian.
We need the integer in Little-Endian format so we must switch the position of the 1st and 4th byte and the position of the 2nd and 3rd byte. this sounds complicated but it is not.
For a better understanding, I will explain what we do:
First, we do a binary AND between $IPAddressAsInt
and 255 then we do a 24-bit shift-left to the result
Next, we do an 8-bit shift-left to 255 and do a binary AND with $IPAddressAsInt
then follow an 8-bit shift-left to
the result.
Next, we do a 16-bit shift-left to 255 and do a binary AND with $IPAddressAsInt
then follow an 8-bit shift-right
to the result.
Finally, we do a 24-bit shift-left to 255 and do a binary AND with $IPAddressAsInt
then follow a 24-bit
shift-right to the result.
all 4 steps are concatenated with binary OR and the final result is stored in $IPAddressAsInt
.
$IPAddressAsInt = ($IPAddressAsInt -band 255) -shl 24 -bor ($IPAddressAsInt -band (255 -shl 8)) -shl 8 -bor
($IPAddressAsInt -band (255 -shl 16)) -shr 8 -bor ($IPAddressAsInt -band (255 -shl 24)) -shr 24
you can easily check the result by explicitly casting it to [ipaddress]
. The result is our IP address in the
"wrong" order.
[ipaddress]$IPAddressAsInt
Address | : | 2887385449 |
---|---|---|
AddressFamily | : | InterNetwork |
ScopeId | : | |
IsIPv6Multicast | : | False |
IsIPv6LinkLocal | : | False |
IsIPv6SiteLocal | : | False |
IsIPv6Teredo | : | False |
IsIPv4MappedToIPv6 | : | False |
IPAddressToString | : | 105.1.26.172 |
Converting PrefixLength / CIDR to subnet mask as an integer
To get the subnet mask from the number of network bits we have two easy possibilities
- Calculate the value for the number of set network bits: 2²⁴ - 1 and do an 8-bit shift-left
- calculate the value of the host bits 2⁸ -1 and then negate the result with a binary NOT
# Method 1
$SubnetMask = [uint32]([math]::pow(2,$CIDR) - 1) -shl (32 - $CIDR)
# Method 2
$SubnetMask = -bnot ([uint32][math]::pow(2,(32 - $CIDR)) - 1)
to make life easier at this point we also get the host mask aka subnet wildcard mask or inverted subnet mask.
I think you all can guess how we can do that...
$HostMask = [uint32][math]::pow(2,(32 - $CIDR)) - 1
with this information, we can start our work.
Getting the network address, first IP address, last IP address, and the broadcast address
Now it is time to get the network address. We get our network address by doing a binary AND with the IP address and the subnet mask.
$NetworkAddress = $IPAddressAsInt -band $SubnetMask
For the broadcast address, we do a binary OR with the network address and the host mask.
$BroadcastAddress = $NetworkAddress -bor $HostMask
Now getting the first and the last IP address is simple math. For the first IP address, we simply add 1 to the network address and for the last IP address, we simply subtract 1 from the broadcast address.
$FirstIpAddress = $NetworkAddress + 1
$LastIpAddress = $BroadcastAddress - 1
Getting the maximum available IP addresses
To get the number of maximum usable IP addresses you simply subtract 1 from the host mask.
If you now want to know if an IP address, is in the subnet you simply check if the value of this IP address is greater or equal to the first IP address, and lower or equal to the last IP address.
Required functions
So it is time to build functions with the stuff from above.
Switch byte order
function switchByteOrder{
param(
[Parameter(Mandatory=$true)]
[uint32]$inputInteger
)
return ($inputInteger -band 255) -shl 24 -bor ($inputInteger -band (255 -shl 8)) -shl 8 -bor ($inputInteger -band (255
-shl 16)) -shr 8 -bor ($inputInteger -band (255 -shl 24)) -shr 24
}
Get IP address as integer
function Get-IPAddressAsInteger{
param(
[ipaddress]$IPAddress
)
return (switchByteOrder -inputInteger $IPAddress.Address)
}
Get subnet mask from network bits
function Get-SubnetMaskFromNtworkBits{
param(
[Parameter(Mandatory=$true)]
[ValidateRange(0,32)]
[byte]$CIDR
)
return -bnot ([uint32][math]::pow(2,(32 - $CIDR)) - 1)
}
Get host mask from network bits
function Get-HostMaskFromNtworkBits{
param(
[Parameter(Mandatory=$true)]
[ValidateRange(0,32)]
[byte]$CIDR
)
return ([uint32][math]::pow(2,(32 - $CIDR)) - 1)
}
Get network info from IP address and network bits
function Get-NetInfoFromIPasIntAndCIDR {
param(
[Parameter(Mandatory = $true)]
[uint32]$IpAddressAsInteger,
[Parameter(Mandatory = $true)]
[ValidateRange(0, 32)]
[byte]$CIDR
)
[uint32]$NetAddr = $IpAddressAsInteger -band (Get-SubnetMaskFromNtworkBits -CIDR $CIDR)
[uint32]$BCastAddr = $NetAddr -bor (Get-HostMaskFromNtworkBits -CIDR $CIDR)
[uint32]$SubnetMask = (Get-SubnetMaskFromNtworkBits -CIDR $CIDR)
[uint32]$HostMask = (Get-HostMaskFromNtworkBits -CIDR $CIDR)
return (New-Object psobject -Property ([ordered]@{
NetworkAddress = $NetAddr;
BroadcastAddress = $BCastAddr;
FirstIpAddress = $NetAddr + 1;
LastIpAddress = $BCastAddr - 1;
MaxHosts = $HostMask - 1;
SubnetMask = $SubnetMask;
HostMask = $HostMask
}))
}
Get IP address from IP address as integer
function Convert-IntIPAddressAsIPAddress{
param(
[Parameter(Mandatory=$true)]
[uint32]$IPAsInteger
)
return [ipaddress](switchByteOrder -inputInteger $IPAsInteger)
}
Get network bits from the network mask as a string
function Get-NetMaskBitsFromDottedMask {
param(
[ipaddress]$mask
)
$count=0
[uint32]$UintIP = (switchByteOrder -inputInteger $mask.Address)
for($i=0;$i -lt 32;$i++){
if($UintIP -band 1){
$count++
} else {
if($count -ne 0){
throw "Invalid SubnetMask"
}
}
$UintIP=$UintIP -shr 1
}
return $count
}
Some other helpful functions
Convert IP address as integer to string
function Convert-IntIPAddressToString{
param(
[Parameter(Mandatory=$true)]
[uint32]$IPAsInteger
)
return ,$($($IPAsInteger -shr 24 -band 255).ToString()+"."+$($IPAsInteger -shr 16 -band
255).ToString()+"."+$($IPAsInteger -shr 8 -band 255).ToString()+"."+$($IPAsInteger -band 255).ToString())
}
Convert an unsigned integer to a string in binary format
function Convert-UInt32ToBinaryStr {
param(
[uint32]$Integer
)
$(for($i=31;$i -ge 0 ;$i--){
if( $Integer -band (1 -shl $i) ){
1
} else {
0
}
}) -join '|'
}
Of course, you can do this also with [convert]::ToString($integer,2)
but this method has the "problem", that
leading zeros are cut off.
This does not matter mostly but comparing two binary displayed numbers is much easier if all bits are displayed.
Time to get all together
[CmdletBinding(DefaultParameterSetName = 'SubnetMaskAsCIDR')]
param(
[Parameter(Mandatory, ValueFromPipelineByPropertyName)]
[Alias('Address', 'NetworkAddress')]
[ipaddress]$IPAddress,
[Parameter(Mandatory, ValueFromPipelineByPropertyName, ParameterSetName = "SubnetMaskAsCIDR")]
[ValidateRange(0, 32)]
[Alias('PrefixLength')]
[byte]$CIDR,
[Parameter(Mandatory, ValueFromPipelineByPropertyName, ParameterSetName = "SubnetMaskAsIP")]
[Alias('SubnetMask')]
[ipaddress]$Mask,
[Parameter(Mandatory = $false, ValueFromPipelineByPropertyName)]
[ipaddress]$IPAddressToCheck
)
#region functions
function switchByteOrder {
param(
[Parameter(Mandatory = $true)]
[uint32]$inputInteger
)
return ($inputInteger -band 255) -shl 24 -bor ($inputInteger -band (255 -shl 8)) -shl 8 -bor ($inputInteger -band (255
-shl 16)) -shr 8 -bor ($inputInteger -band (255 -shl 24)) -shr 24
}
function Get-IPAddressAsInteger {
param(
[ipaddress]$IPAddress
)
return (switchByteOrder -inputInteger $IPAddress.Address)
}
function Get-SubnetMaskFromNtworkBits {
param(
[Parameter(Mandatory = $true)]
[ValidateRange(0, 32)]
[byte]$CIDR
)
return -bnot ([uint32][math]::pow(2, (32 - $CIDR)) - 1)
}
function Get-HostMaskFromNtworkBits {
param(
[Parameter(Mandatory = $true)]
[ValidateRange(0, 32)]
[byte]$CIDR
)
return ([uint32][math]::pow(2, (32 - $CIDR)) - 1)
}
function Get-NetInfoFromIPasIntAndCIDR {
param(
[Parameter(Mandatory = $true)]
[uint32]$IpAddressAsInteger,
[Parameter(Mandatory = $true)]
[ValidateRange(0, 32)]
[byte]$CIDR
)
[uint32]$NetAddr = $IpAddressAsInteger -band (Get-SubnetMaskFromNtworkBits -CIDR $CIDR)
[uint32]$BCastAddr = $NetAddr -bor (Get-HostMaskFromNtworkBits -CIDR $CIDR)
[uint32]$SubnetMask = (Get-SubnetMaskFromNtworkBits -CIDR $CIDR)
[uint32]$HostMask = (Get-HostMaskFromNtworkBits -CIDR $CIDR)
return (New-Object psobject -Property ([ordered]@{
NetworkAddress = $NetAddr;
BroadcastAddress = $BCastAddr;
FirstIpAddress = $NetAddr + 1;
LastIpAddress = $BCastAddr - 1;
MaxHosts = $HostMask -1 ;
SubnetMask = $SubnetMask;
HostMask = $HostMask
}))
}
function Get-IntIPAddressAsIPAddress {
param(
[Parameter(Mandatory = $true)]
[uint32]$IPAsInteger
)
return [ipaddress](switchByteOrder -inputInteger $IPAsInteger)
}
function Get-NetMaskBitsFromDottedMask {
param(
[ipaddress]$mask
)
$count = 0
[uint32]$UintIP = (switchByteOrder -inputInteger $mask.Address)
for ($i = 0; $i -lt 32; $i++) {
if ($UintIP -band 1) {
$count++
}
else {
if ($count -ne 0) {
throw "Invalid SubnetMask"
}
}
$UintIP = $UintIP -shr 1
}
return $count
}
#endregion functions
if ($PSCmdlet.ParameterSetName -eq 'SubnetMaskAsIP') {
$CIDR = Get-NetMaskBitsFromDottedMask -mask $Mask
}
$IpAddressAsInteger = Get-IPAddressAsInteger -IPAddress $IPAddress
$NetWorkInfo = Get-NetInfoFromIPasIntAndCIDR -CIDR $CIDR -IpAddressAsInteger $IpAddressAsInteger
$NetworkInfoToReturn = New-Object psobject -Property $([ordered]@{
NetworkAddress = (Get-IntIPAddressAsIPAddress -IPAsInteger $NetWorkInfo.NetworkAddress);
BroadcastAddress = (Get-IntIPAddressAsIPAddress -IPAsInteger $NetWorkInfo.BroadcastAddress);
FirstIpAddress = (Get-IntIPAddressAsIPAddress -IPAsInteger $NetWorkInfo.FirstIpAddress);
LastIpAddress = (Get-IntIPAddressAsIPAddress -IPAsInteger $NetWorkInfo.LastIpAddress);
MaxUsableHosts = $NetWorkInfo.MaxHosts
SubnetMask = (Get-IntIPAddressAsIPAddress -IPAsInteger $NetWorkInfo.SubnetMask);
WildcardMask = (Get-IntIPAddressAsIPAddress -IPAsInteger $NetWorkInfo.HostMask);
CIDR = $CIDR
IpIsInHostRange = $null;
IPAdressToCheck = $null
})
if ($PSBoundParameters.ContainsKey("IPAddressToCheck")) {
$IpAddressToCheckAsInteger = Get-IPAddressAsInteger -IPAddress $IPAddressToCheck
$NetworkInfoToReturn.IPAdressToCheck = $IPAddressToCheck
if ($IpAddressToCheckAsInteger -ge $($NetWorkInfo.FirstIpAddress) -and $IpAddressToCheckAsInteger -le
$($NetWorkInfo.LastIpAddress)) {
$IpIsInHostRange = $true
}
else {
$IpIsInHostRange = $false
}
$NetworkInfoToReturn.IpIsInHostRange = $IpIsInHostRange
}
return $NetworkInfoToReturn
Some examples
PS C:\Users\DLR> D:\Daniel\SubnetCalcps1.ps1 -IPAddress 10.12.28.123 -CIDR 24
NetworkAddress : 10.12.28.0
BroadcastAddress : 10.12.28.255
FirstIpAddress : 10.12.28.1
LastIpAddress : 10.12.28.254
MaxUsableHosts : 254
SubnetMask : 255.255.255.0
WildcardMask : 0.0.0.255
CIDR : 24
IpIsInHostRange :
IPAdressToCheck :
PS C:\Users\DLR> D:\Daniel\SubnetCalcps1.ps1 -IPAddress 10.12.28.123 -CIDR 29
NetworkAddress : 10.12.28.120
BroadcastAddress : 10.12.28.127
FirstIpAddress : 10.12.28.121
LastIpAddress : 10.12.28.126
MaxUsableHosts : 6
SubnetMask : 255.255.255.248
WildcardMask : 0.0.0.7
CIDR : 29
IpIsInHostRange :
IPAdressToCheck :
.\SubnetCalcps1.ps1 -IPAddress 10.12.28.123 -CIDR 27 -IPAddressToCheck 10.12.28.100
NetworkAddress : 10.12.28.96
BroadcastAddress : 10.12.28.127
FirstIpAddress : 10.12.28.97
LastIpAddress : 10.12.28.126
MaxUsableHosts : 30
SubnetMask : 255.255.255.224
WildcardMask : 0.0.0.31
CIDR : 27
IpIsInHostRange : True
IPAdressToCheck : 10.12.28.100
.\SubnetCalcps1.ps1 -IPAddress 10.12.28.123 -CIDR 28 -IPAddressToCheck 10.12.28.100
NetworkAddress : 10.12.28.112
BroadcastAddress : 10.12.28.127
FirstIpAddress : 10.12.28.113
LastIpAddress : 10.12.28.126
MaxUsableHosts : 14
SubnetMask : 255.255.255.240
WildcardMask : 0.0.0.15
CIDR : 28
IpIsInHostRange : False
IPAdressToCheck : 10.12.28.100
.\SubnetCalcps1.ps1 -IPAddress 10.12.28.123 -CIDR 24 -IPAddressToCheck 10.12.28.100
NetworkAddress : 10.12.28.0
BroadcastAddress : 10.12.28.255
FirstIpAddress : 10.12.28.1
LastIpAddress : 10.12.28.254
MaxUsableHosts : 254
SubnetMask : 255.255.255.0
WildcardMask : 0.0.0.255
CIDR : 24
IpIsInHostRange : True
IPAdressToCheck : 10.12.28.100
Now I think you have an idea of what you can do with binary operators. But as always everything here is provided without warranty.
And yes I have not done error handling in this script but I will be sure you can do this on your own