PowerShell force [int] to wrap (IP to Long conversion) - powershell

Just a quick question as I'm kind of new to math in PowerShell
I need to make a converter in PowerShell that converts IP to Long. It works fine for lower values, but on higher ones it is causing an Integer Overflow.
[int]$a = Read-Host "First bit of IP address"
[int]$b = Read-Host "2nd bit of IP address"
[int]$c = Read-Host "3rd bit of IP address"
[int]$d = Read-Host "4th bit of IP address"
$a *= 16777216
$b *= 65536
$c *= 256
$base10IP = $a + $b + $c + $d
Write-Host $base10IP
Works fine if I input some low-int IP address, like 10.10.10.10 (out 168430090)
But there are cases where this leads to Int overflow. I would like PowerShell to wrap around if [int] reaches maximum value and provide me a negative value.
I work as a service desk and one of the software I support needs IP in Long form, including negative values.
Is it doable in PowerShell ?
Please advise if you need more details or something is not clear.
Alex

A simple solution would be to use [long] instead of [int].
[long]$a = Read-Host "First bit of IP address"
[long]$b = Read-Host "2nd bit of IP address"
[long]$c = Read-Host "3rd bit of IP address"
[long]$d = Read-Host "4th bit of IP address"
$a *= 16777216
$b *= 65536
$c *= 256
$base10IP = $a + $b + $c + $d
Write-Host $base10IP
And you can do what you want in even less lines of code
[IPAddress]$ip = Read-Host "IP address"
$ip.Address
UPDATE
Your comment explains a long wasn't what you where after. It sounds like an int you are looking for.
I could not find a way to have PowerShell do unchecked (losing precision) conversions or arithmetic, but using the BitConverter class you can get it working.
[byte]$a = 10
[byte]$b = 113
[byte]$c = 8
[byte]$d = 203
[BitConverter]::ToInt32(($a, $b, $c, $d), 0)
or
[IPAddress]$ip = "10.113.8.203"
$bytes = [BitConverter]::GetBytes($ip.Address)
[BitConverter]::ToInt32($bytes, 0)
Please note that IPAddress also supports IPv6 addresses, but this last conversion to an int clearly can't hold an IPv6 address.

Related

Powershell - only if statement executes despite input

I'm trying to start my Powershell portfolio with and easy script that calculates Network ID or BroadcastID depending on input from the user. That being said, I can only get my if statement to run. I am unsure what I am missing here. I have tried searching this as I know I cannot be the only one to have this issue, but I am unable to find it. Any education on my error would be appreciated as it seems like a basic flaw.
Thanks!
#prompt for IP and subnet mask
$ip = Read-Host -Prompt "Enter your IP address"
$mask = Read-Host - Prompt "Enter your subnet mask"
[String]$UserDecision = Read-Host -Prompt "Enter N if you would like to calculate your Network ID or B if you would like to calculate your Broadcast address."
$splitmask=$mask.split(".")
$wildcard="$(255 - $splitmask[0]).$(255 - $splitmask[1]).$(255 - $splitmask[2]).$(255 - $splitmask[3])"
# ip and mask variable to ip addresses
$ip = [ipaddress] $ip
$mask = [ipaddress] $mask
#determine networkID
function CalculateNetID {
$networkID = [ipaddress] ($ip.Address -band $mask.Address)
#print NetworkID to console
echo "The Network id is $($networkID.IPAddressToString)"
}
function CalculateBroadcastID {
$networkID = [ipaddress] ($ip.Address -band $mask.Address)
#convert wildcard to IP addresses
$wildcard= [ipaddress] $wildcard
$broadcast = [ipaddress] $($wildcard.Address -bor $NetworkID.Address)
#print broadcast to console
echo "The Broadcast id is $broadcast"
}
if ($UserDecision -eq "N" -or "n"){
CalculateNetID
}
elseif($UserDecision -eq "B" -or "b"){
CalculateBroadcastID
}
else{
echo "Please retry and enter the character associated with the ID you would like to calculate"
}
Here is your problem:
$false -or 'n' # => True
As you can see, this is what's happening in your first if statement, the correct syntax for your condition should be:
if($UserDecision -eq "N" -or $UserDecision -eq "n") {
Same thing applies for the elseif.
Note that, PowerShell comparison operators are not case-sensitive by default, so the -or conditions could be just removed:
if($UserDecision -eq "n") {
You might also want to consider using a switch statement instead of chained if, elseif, else:
switch($UserDecision) {
N { CalculateNetID }
B { CalculateBroadcastID }
Default {
"Please retry and enter the character associated with the ID you would like to calculate"
}
}

IP Address Input from Jenkins to Variable powershell

I Have Jenkins job that asks for IP Address
$ip = $env:Lan_ip
what the user enter goes to $ip
now $ip is 192.168.10.10 for Example
now I'm trying to insert this variable to FortiGate SSH
Invoke-SshCommand $Firewall -command ‘config system interface
edit port1
set ip $ip 255.255.255.0
end’
but he can not read the $ip I need to make it like INT separate with .
im getting this Error
node_check_object fail! for ip $ip
how can i convert the sting im getting from the user when he enter the ip address in for example --> 192.168.10.10
to usable variable in the code
From what I gather from here is that you need to give the subnet mask as a CIDR-formatted subnet mask like 255.255.255.0/24
To get that CIDR value off a subnet IP address, you can use this function:
function Get-SubnetMaskLength ([string]$SubnetMask) {
# $cidr = Get-SubnetMaskLength "255.255.240.0" --> 20
$result = 0
[IPAddress]$ip = $SubnetMask
foreach ($octet in $ip.GetAddressBytes()) {
while ($octet) {
$octet = ($octet -shl 1) -band [byte]::MaxValue
$result++
}
}
$result
}
So
$subNet = '255.255.255.0'
$cidr = Get-SubnetMaskLength $subNet # --> 24
$subNetAndCidr = '{0}/{1}' -f $subNet, $cidr # --> 255.255.255.0/24
P.S. Always use straight quotes instead of the curly thingies ‘ and ’ in code!

Powershell - Checking IP Address range based on CSV file

I have been developing VM provision script. My question is : I have here-string like below. now , I want to add route based on ip address range. I am using CSV file with BACKUPIP column.
if an BACKUPIP is in range 10.10.104.1 to 10.10.107.254 it will work route add xx.xx.xx.xx mask 255.255.255.0 xx.xx.xx.xx -p
if an BACKUPIP is in range 10.10.180.1 to 10.10.185.254 it will work route add yy.yy.yy.yy mask 255.255.255.0 yy.yy.yy.yy -p
Here is my script:
Import-Csv -Path .\vm.csv -UseCulture -PipelineVariable row |
ForEach-Object -Process {
# Create the VM, store result in $vm
if($($row.IP) -eq '???'){
route add xx.xx.xx.xx mask 255.255.255.0 xx.xx.xx.xx -p
}
else{
route add yy.yy.yy.yy mask 255.255.255.0 yy.yy.yy.yy -p
}
}
LAST UPDATE :
$rangeFrom104 = '10.10.104.1'
$rangeTo107 = '10.10.107.254'
$rangeFrom180 = '10.10.180.1'
$rangeTo185 = '10.10.185.254'
if (([version]$rangeFrom104) -lt ([version]$($row.IP)) -and ([version]$($row.IP)) -lt ([version]$rangeTo107) )
{
route add xx.xx.xx.xx mask 255.255.255.0 xx.xx.xx.xx -p
}
elseif (([version]$rangeFrom180) -lt ([version]$($row.IP)) -and ([version]$($row.IP)) -lt ([version]$rangeTo185) )
{
route add yy.yy.yy.yy mask 255.255.255.0 yy.yy.yy.yy -p
}
Really like the [Version] approach Lee_Dailey suggested.
Here's another approach that converts the IP addresses to their numeric values:
function Convert-IPv4ToDecimal ([string]$IpAddress){
# helper function to return the numeric value (uint32) of a dotted IP
# address string used for testing if an IP address is in range.
$n = [uint32[]]$IpAddress.Split('.')
# or use: $n = [uint32[]]([IpAddress]$IpAddress).GetAddressBytes()
# to get the obsolete property ([IpAddress]$IpAddress).Address
# you need to do the math in reversed order.
# return [uint32] ($n[3] -shl 24) + ($n[2] -shl 16) + ($n[1] -shl 8) + $n[0]
# for comparing different ranges as in this question, do not reverse the byte order
return [uint32] ($n[0] -shl 24) + ($n[1] -shl 16) + ($n[2] -shl 8) + $n[3]
}
$startRange1 = Convert-IPv4ToDecimal '172.25.104.1'
$endRange1 = Convert-IPv4ToDecimal '172.25.107.254'
$startRange2 = Convert-IPv4ToDecimal '172.25.112.1'
$endRange2 = Convert-IPv4ToDecimal '172.25.115.254'
Import-Csv -Path .\vm.csv -UseCulture | ForEach-Object {
# Create the VM, store result in $vm
# convert the .BACKUPIP to numeric value
$backupIp = Convert-IPv4ToDecimal $_.BACKUPIP
# test the IP range
if ($backupIp -ge $startRange1 -and $backupIp -le $endRange1) {
Write-Host "BACKUPIP '$($_.BACKUPIP)' is in Range 1"
route add xx.xx.xx.xx mask 255.255.255.0 xx.xx.xx.xx -p
}
elseif ($backupIp -ge $startRange2 -and $backupIp -le $endRange2) {
Write-Host "BACKUPIP '$($_.BACKUPIP)' is in Range 2"
route add yy.yy.yy.yy mask 255.255.255.0 yy.yy.yy.yy -p
}
else {
Write-Warning "No range defined for IP address '$($_.BACKUPIP)'"
}
}
There exists a IPAddress class in .Net:
$MyIPAddress = [System.Net.IPAddress]'10.10.105.7'
$rangeFrom104 = [System.Net.IPAddress]'10.10.104.1'
$rangeTo107 = [System.Net.IPAddress]'10.10.107.254'
If ($rangeFrom104.Address -lt $MyIPAddress.Address -and $MyIPAddress.Address -lt $rangeTo107.Address) {
# route add xx.xx.xx.xx mask 255.255.255.0 xx.xx.xx.xx -p
}
As #Theo commented, the Address property is obsolete:
This property has been deprecated. It is address family dependent.
Please use IPAddress.Equals method to perform comparisons.
I guess this is due to compliance with IPv6 (but I presume that the property won't easily cease to exist as that would probably break some legacy programs). Anyways, that doesn't mean that the whole [System.Net.IPAddress] class is deprecated. Meaning that you might also use the GetAddressBytes method which I think better than a custom function or relying on a (smart! [version]) type but also are both limited to IPv4 (~4 bytes).
With using the GetAddressBytes method, you might simple convert the bytes to a hexadecimal string, which format is comparable (e.g. '10' -gt '0A') as long as the byte arrays are of the same size (e.g. both IPv4):
function Convert-IPAddressToHexadecimal ([Net.IPAddress]$IPAddress, [Switch]$IPv6) {
If ($IPv6) {$IPAddress = $IPAddress.MapToIPv6()}
[BitConverter]::ToString($IPAddress.GetAddressBytes())
}; Set-Alias IP2Hex Convert-IPAddressToHexadecimal
$MyIPAddress = IP2Hex '10.10.105.7' # 0A-0A-69-07
$rangeFrom104 = IP2Hex '10.10.104.1' # 0A-0A-68-01
$rangeTo107 = IP2Hex '10.10.107.254' # 0A-0A-6B-FE
If ($rangeFrom104 -lt $MyIPAddress -and $MyIPAddress -lt $rangeTo107) {
# route add xx.xx.xx.xx mask 255.255.255.0 xx.xx.xx.xx -p
}
If you do need to make your script IPv6 compliant and comparing IP addresses to both IPv4 ranges and IPv6 ranges, you might consider to map all IP addresses to an IPv6 address: using: $MyIPAddress.MapToIPv6().GetAddressBytes() (the -IPv6 switch):
IP2Hex -IPv6 '10.10.105.7' # 00-00-00-00-00-00-00-00-00-00-FF-FF-0A-0A-69-07
Update 2020-09-06:
It is unclear whether the Address property is really obsolete. See: Avoid byte[] allocations once IPAddress.Address is back #18966.
Anyhow, there is a pitfall in using the Address property for comparison as it appears that the address is stored as Big-Endian read from memory as Little-Endian format, see: System.Net.IPAddress returning weird addresses, causing the last byte in 10.10.104.1 (1) to become most significant.
This means that comparing the Address property might give an incorrect result if there are differences between multiple bytes in the concerned IP Addressed:
([IPAddress]'0.0.0.1').Address -lt ([IPAddress]'0.0.1.0').Address
False

How to get IP Address range from subnet and netmask

Team,
I am new to the forum, also new to the development, i am currently using windows 2016, 2012 & 2008 servers in the environment. The script primarily should work on all the environment.
I wanted to find out the IP start ip address and end ip address.
$params = #{
"ComputerName" = "."
"Class" = "Win32_NetworkAdapterConfiguration"
"Filter" = "IPEnabled=TRUE"
}
$netConfigs = Get-WMIObject #params
foreach ( $netConfig in $netConfigs ) {
for ( $i = 0; $i -lt $netConfig.IPAddress.Count; $i++ ) {
if ( $netConfig.IPAddress[$i] -match '(\d{1,3}\.){3}\d{1,3}' ) {
$ipString = $netConfig.IPAddress[$i]
$ip = [IPAddress] $ipString
$maskString = $netConfig.IPSubnet[$i]
$mask = [IPAddress] $maskString
$netID = [IPAddress] ($ip.Address -band $mask.Address)
"IP address: {0}" -f $ip.IPAddressToString
"Subnet mask: {0}" -f $mask.IPAddressToString
"Network ID: {0}" -f $netID.IPAddressToString
}
}
}
Convert IP address to the subnet
[IPAddress] (([IPAddress] "192.168.100.45").Address -band ([IPAddress] "255.255.255.0").Address)
I am currently using 2016 & i am not getting how to proceed further to get the start ip address and end ip address in a single line of code.
Please advise
You can do the following to get the network and broadcast addresses:
$IP = '192.168.4.5'
$mask = '255.255.0.0'
$IPBits = [int[]]$IP.Split('.')
$MaskBits = [int[]]$Mask.Split('.')
$NetworkIDBits = 0..3 | Foreach-Object { $IPBits[$_] -band $MaskBits[$_] }
$BroadcastBits = 0..3 | Foreach-Object { $NetworkIDBits[$_] + ($MaskBits[$_] -bxor 255) }
$NetworkID = $NetworkIDBits -join '.'
$Broadcast = $BroadcastBits -join '.'
# Output
$NetworkID
192.168.0.0
$Broadcast
192.168.255.255
Explanation:
Since bitwise operators (see About Arithmetic Operators) are only supported on integer types, you must do a string to integer conversion to successfully use the operator[1].
The IP and Mask are split on the . character creating a two string array of the octets. The [int[]] cast converts the array into an Int32 array.
For the network address, we perform a -band (bitwise and) on the same index from each array. Since IPs have four octets, we only need to loop over the 0..3 range. The resulting Int32 array ($NetworkIDBits) items joined by the . character, putting the result in IP address format.
For the broadcast address, we perform a -bxor (bitwise XOR) on the integer array derived from the mask with 255. The goal is to flip all of the ones and zeroes in the mask. The result will be an increment value per octet that can be added to the octets of the network address. The final, calculated result is converted to IP address form using -join.
[1]: You don't always need to explicitly cast strings to integers for the conversion. PowerShell can automatically do this in some cases. For example, in my shell, I do not have to cast with [int[]]
The first ip is just the network address plus 1, although that is usually the gateway. For the broadcast address, I'll just point to this link: https://www.indented.co.uk/powershell-subnet-math/
Getting to the Broadcast Address is a bit more complicated than the
Network Address. A Bitwise Or is executed against an Inverted Subnet
Mask. For example, the Inverted form of 255.255.255.0 is 0.0.0.255.

Is there a one-liner for using default values with Read-Host?

I've written something like this to specify default values for prompts.
$defaultValue = 'default'
$prompt = Read-Host "Press enter to accept the default [$($defaultValue)]"
if ($prompt -eq "") {} else {
$defaultValue = $prompt
}
Can it be shortened further?
Here is my attempt.
$defaultValue = 'default'
$prompt = Read-Host "Press enter to accept the default [$($defaultValue)]"
if (!$prompt -eq "") {$defaultValue = $prompt}
I want a one-liner, so I'm gonna hold out accepting an answer until then.
N.b. $defaultValue should be stored independently of the one liner. Similar to the example above.
I've accepted the answer which lead me to the solution I was looking for.
$defaultValue = 'default'
if (($result = Read-Host "Press enter to accept default value $defaultValue") -eq '') {$defaultValue} else {$result}
And for those of you asking why. The reason is because it is easier on the eyes of whoever comes after me. Less is always more, when clarity is not sacrificed. IMHO.
EDIT;
Instead of a single line, perhaps I should have said a single phrase?
I've added this edit clarify whilst a few answers I have seen use are using a semi-colon.
$defaultValue = 'default'
$prompt = Read-Host "Press enter to accept the default [$($defaultValue)]"
$prompt = ($defaultValue,$prompt)[[bool]$prompt]
If you absolutely have to have it in one line:
$defaultValue = 'default'
($defaultValue,(Read-Host "Press enter to accept the default [$($defaultValue)]")) -match '\S' |% {$prompt = $_}
Shortest Version I could came up with:
if (!($value = Read-Host "Value [$default]")) { $value = $default }
This version doesn't have to use else.
if(($result = Read-Host "Press enter to accept default value [default]") -eq ''){"default"}else{$result}
$DefaultValue="Foobar"
.... (Optional other code) ....
$Value=if($Value=(Read-Host "Enter value [$DefaultValue]")){$Value}else{$DefaultValue}
Just threw this together to re-use a previously entered config value while still allowing user to change it if needed... The accepted answer is missing the assignment portion and uses a hardcoded "Default" value...
There is a function (from other languages) called "Ternary Operator" or "Binary Operator"(https://en.wikipedia.org/wiki/%3F:) (the 'xx : yy ? zz' style one) and there are several people who have submitted functions to implement its behavior in Powershell.
For me this is the shortest, easiest to use, and doesn't repeat the variable value or name:
$ChosenValue = 'Default Value' | %{ If($Entry = Read-Host "Enter a value ($_)"){$Entry} Else {$_} }
The magic sauce is the |%{} that pipes the default value to ForEach, allowing repeating the default value (aliased as $_) in the message and return value. It also depends on the fact that the assignment operator returns the value assigned for the purposes of the If statement. This assigned value will be treated as $true when an entry is provided, or $false(empty) when the [Enter] key is pressed instead.
Thanks go to #ojk's excellent answer which I considered the easiest to use and read, but I agree with #bluekeys that it's better to avoid repeating the default value and assigned variable name. Having the intermediate $Entry variable hasn't proven to be an inconvenience.
I use this method frequently to define function or script parameters with defaults that can be overridden at run time:
param(
$hostname = ('google.com' | %{ If($Entry = Read-Host "Host name ($_)"){$Entry} Else {$_} })
)
Write-Host $hostname
Running:
Host name (google.com):
google.com
Host name (google.com): facebook.com
facebook.com
Also, as #E.Sundin pointed out, Powershell 7 finally has a ternary operator that makes this syntax much simpler. Unfortunately many of us work on legacy systems that won't get version 7 in the near future.
EDIT: here's a slightly shorter version without an intermediate $Entry variable:
$hostname = 'google.com' | %{(Read-Host "Host name ($_)"),$_} | ?{$_} | Select -First 1
It makes an array containing the Read-Host response and the default value, then filters to exclude empty values, and selects the first (there would be two if the user responded).
$prompt = ( ($defaultValue='a_default_value'), (Read-Host "Please enter something [$defaultValue]")) -match '\S' | select -last 1
The given answers doesn't satisfy me (and don't work for me) but gave me enough input to came up with the shortest one-liner.
if (($prompt = Read-Host -Prompt "Your Text [your default]") -eq "") {$prompt= "your default"}
Why adding an else? If user inputs nothing it does $prompt="your default" (could be a variable of course). If he adds something, its stored in %prompt already and you can leave the statement.
I am using a function for that:
Function Read-HostDefault($Prompt, $Default) {
if ($default) {
$prompt = "$prompt [$default]"
}
$val = Read-Host $prompt
($default,$val)[[bool]$val]
}
Which gives:
PS C:\Windows\system32> Read-HostDefault "Enter port" 5432
Enter port [5432] :
5432
PS C:\Windows\system32> Read-HostDefault "Enter port" 5432
Enter port [5432] : 1234
1234
PS C:\Windows\system32> Read-HostDefault "Enter port"
Enter port : 2468
2468
You could also use a switch statement in a single line like this:
param([string]$myVariable = $($($val = $(Read-Host "Enter value")); $(Switch -regex ($val) { ".+" { $val } default { "my default value" } })))
The -regex .+ will match one or more characters, this would include any white space characters so maybe you want to strip out white space when doing the switch match i.e. \S.+.
Assuming you already have $defaultValue:
$newValue = if ($value = Read-Host -Prompt "Please enter a value ($defaultValue)") { $value } else { $defaultValue }
This should work