0

All, quite new to powershell scripting.

For various reasons, writing a very basic port scanner that can take single IPs, a range of IP and a list of ports.

Script is called, parameters read from command line startIP, endIP, port, whether you require the scan to be randomised, resolved etc.

Really struggling with the script interpreting a port as an IP address which is throwing off the script if an endip isn't specified on the command line. Have tried experimenting with parameter validation, but am not getting very far, would be grateful if someone could give me some pointers:

Param(
 [Parameter(Mandatory=$true)]
  [System.Net.IPAddress]$StartIPAddress,
  [System.Net.IPAddress]$EndIPAddress,
  [ValidateScript({if($endipaddress.gettype().notequals(typeof(System.net.ipaddress))) {$endipaddress = $null}})]
  [Int[]]$ports,
  [Switch]$random,
  [Switch]$resolve)

H:>.\scan.ps1 127.0.0.1 127.0.0.3 445 works for the 3 ipaddresses in the range specified and connects to port 445 for each IP and reports if open etc.

H:>.\scan.ps1 127.0.0.1 445 wants to interpret 445 as the end IP address and messes with the range calculation. There is an if statement to compare if $endipaddress is $null to just ignore the range calc, but the problem arises because the script treats the port number as $endipaddress and doesn't see it as $null. How can I get the script to realise that there's not a second IP in there an treat it as using a single IP? Been trying to solve this for about a day now.

Am I barking up the wrong tree with the validatescript section and needlessly overcomplicating it? Or am I on the right lines and merely using validatescript incorrectly?

Thanks in advance!

Edit, solution update!: Been busy with other stuff, so only recently had chance to go back to it once it was just working; so now, I have looked up parameter sets and the like, have this and seems to work well:

Param(
 [Parameter(Mandatory=$true,Position = 0,ParameterSetName="nofile")][System.Net.IPAddress[]]$IPAddress
 ,[Parameter(Mandatory=$true,Position = 1)][Int[]]$ports
 ,[Parameter(Mandatory=$true,ParameterSetName="file")]$inFile
 ,[Switch]$randomise
 ,[Switch]$portRange
 ,[Switch]$resolve
 ,[Switch]$delay)

Which forces some mutual exclusivity on IPAddress or inFile for the command line, as well as ports (which is required always, no matter which method IP addresses are delivered). The positional stuff is more for UX, so can just use IP and ports w/out specifying the alias should you so desire, if I think I've got the point of that correct?

Thank you to all. :)

6
  • 1
    The Mandatory attribute you've specified only applies to your first parameter. You need to specify that attribute for every parameter that is mandatory. Commented May 3, 2019 at 14:28
  • 1
    also, your ValidateScript must result in a [bool] and you cannot use it to make assignments, only to test for valid input. [grin] also also, the ValidateScript needs to be BEFORE the thing it is validating. you see to be trying to validate the $EndIPAddress but you have the validation AFTER that ... so it is validating the next parameter. ///// is there some reason you CANNOT use the parameter names in your calls? that is exactly why they are there ... to disambiguate what goes where. best practice is to ALWAYS use the full parameter name. Commented May 3, 2019 at 14:34
  • 1
    I suggest to read about parameter sets Commented May 3, 2019 at 14:39
  • You "could" just reorder the parameters with $StartIPAddress,$ports,$EndIPAddress or use the position parameter attribute. Then scan.ps1 1.1.1.1 443 and scan.ps1 1.1.1.1 443 1.1.1.4 both work. Commented May 3, 2019 at 15:46
  • I will check out Parameter Sets again and attempt to understand them better so I'm not just wildly stabbing in the dark. @Lee_Dailey Thanks for the validatescript hint. I need to spend some more time on this construct, I think! @Bill_Stewart thanks for the clarification. Commented May 7, 2019 at 8:12

2 Answers 2

1

You could also set the first parameter to be an array of IPAddresses. Then you only have to separate the Start and optional End address from that.
The only difference in calling the script or function is that you separate the two IP addresses using a comma, so that PowerShell interprets it as an array.

To demontrate an small function that simply writes to console what parameters it received:

function Scan {
    Param(
        [Parameter(Mandatory = $true, Position = 0)]
        [System.Net.IPAddress[]]$IPAddress,

        [Parameter(Mandatory = $false, Position = 1)]
        [Int[]]$ports,
        [Switch]$random,
        [Switch]$resolve
  )
  # split out the IP StartAddress (index [0]) and optional EndAddress (index [1])
  $StartIPAddress, $EndIPAddress = $IPAddress

  # You dont have to do funky stuff with `[ValidateScript(...)]` because you have already 
  # defined the accepted variable type by precasting to `[System.Net.IPAddress[]]`.
  # Powershell will then accept only values that are true `System.Net.IPAddress`
  # objects or variables that can be converted to that.

  Write-Host "StartIPAddress = $StartIPAddress"
  Write-Host "EndIPAddress   = $EndIPAddress"
  Write-Host "ports          = $ports"
  Write-Host "random         = $random"
  Write-Host "resolve        = $resolve"
}

Calling it with both the start and end address:

Scan 127.0.0.1, 127.0.0.3 445

Output:

StartIPAddress = 127.0.0.1
EndIPAddress   = 127.0.0.3
ports          = 445
random         = False
resolve        = False

Or with just the start address

Scan 127.0.0.1 445

Output:

StartIPAddress = 127.0.0.1
EndIPAddress   = 
ports          = 445
random         = False
resolve        = False

Hope that helps

Sign up to request clarification or add additional context in comments.

3 Comments

You sir, are a gentleman and a scholar. This appears to do exactly what I want. I already use an array for ports, no idea why I didn't think about using it for the IP addresses too. Perhaps too close to it. I've got write-debug statements all over my code in an effort to track down where it was all going wrong, it was that that pointed me to the script reading the port as an IP address. With this simple change, all my code has just started working as intended, ending hours of frustration. It is a beautiful thing to see all the variable values reporting what I think they should be. Thank you!
@Ian Wow.. thanks for the nice feedback! My pleasure to have helped you with this.
No worries @Theo! Amazing how such a little change just made everything drop into place. If you're interested in the whole script: github.com/meta-l/portscan
1

I could not find anything about .notequals. Also, I find that placing the COMMA at the beginning of the next parameter to make it easier to move them around.

Using parameter sets would be a good idea. https://learn.microsoft.com/en-us/powershell/developer/cmdlet/cmdlet-parameter-sets

Param(
  [Parameter(Mandatory=$true)]
  [System.Net.IPAddress]$StartIPAddress

  ,[Parameter(Mandatory=$false)]
  [ValidateScript({if($endipaddress.gettype() -ne (typeof(System.net.ipaddress)) ) {$false}})]
  [System.Net.IPAddress]$EndIPAddress

  ,[Parameter(Mandatory=$true)]
  [Int[]]$ports

  ,[Parameter(Mandatory=$false)]
  [Switch]$random

  ,[Parameter(Mandatory=$false)]
  [Switch]$resolve)
)

1 Comment

Good tip about the comma, thanks I shall use that. I will freely admit that .notequals was a desperation move, more of an illustration of what I require it to do than actual proper syntax. I had seen an example that used .equals and followed (internal!) logic. I will read up some more with Parameter Sets, didn't really understand them fully at first read, still feeling my way with some of the more esoteric constructs available.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.