Powershell Test-NetConnection returns False in loop script - powershell

Test-NetConnection returns TRUE when run manually but when in a looping script, only some of the ports returns TRUE.
I wrote a powershell script that loops through port numbers to do a Test-NetConnection:
$machine = '[targetmachinename]'
$this_machine = $env:COMPUTERNAME
$port_arr = #(8331, 8332, 8333, 8334, 8335, 8310, 8311)
foreach ($port in $port_arr) {
Test-NetConnection $machine.domain.name.com -port $port -InformationLevel Quiet
}
When I run the script, it always returns TRUE on the same two port numbers and returns FALSE on the other ports.
When I manually run the code for each port, they each come back as TRUE for all ports.
I have tried messing around with the port numbers by removing, adding, and moving them around but it always gives the same results with only the same two port numbers returning TRUE.
I suspected maybe the variable, array, foreach loop or something might be bad, but if that was the case, why would it work for the same two ports and not for the others even when I change up the array?
I was thinking about putting a delay or wait in between loops but have not tested it yet.
This script works fine when run locally from the target machine. Having this issue when running the script from another machine.
UPDATE:
Looking at the powershell log:
Command start time: 20191111121539
**********************
PS>TerminatingError(New-Object): "Exception calling ".ctor" with "2" argument(s): "No connection could be made because the target machine actively refused it [IPADDRESS]:[PORT]""
I noticed that the IPADDRESS does not match up with the target machine name, but instead matches up with the source machine.
I replaced the $machine.domain.name.com to the actual ip address of the machine and that got the script working as expected.
Why does $machine.domain.name.com resolve to the source machine? Even if I concatenate that incorrectly, wouldn't that normally become an unresolved address and error? Shouldn't all port checks have failed at that point?

tl;dr
Replace argument
$machine.domain.name.com
with
"$machine.domain.name.com"
While unquoted command arguments in PowerShell are typically treated as expandable strings - i.e., as if they were implicitly enclosed in "...", this is not the case if your argument starts with a variable reference such as $machine.
In that case, PowerShell tries to evaluate the argument as an expression, and since [string] variable $machine has no .domain property (and subsequent nested properties), the entire argument effectively evaluates to $null[1] - resulting in inadvertent targeting of the local machine by Test-NetConnection.
The subtleties around how PowerShell parses unquoted command arguments:
are explored in this answer.
what the design rationale behind these subtleties may be is the subject of this GitHub issue.
Conversely, to learn about how expandable strings (string interpolation) - variable references and expressions embedded in "..." - work in PowerShell,
see this answer.
Additionally, BACON observes the following regarding the use of -InformationLevel Quiet with Test-NetConnection:
I think passing -InformationLevel Quiet was actively impairing debugging in this case. Given $machine = 'foo', compare the output (particularly the ComputerName property) of:
Test-NetConnection $machine.domain.name.com -InformationLevel Quiet
vs.
Test-NetConnection $machine.domain.name.com
vs.
Test-NetConnection "$machine.domain.name.com".
In other words, [it's best to] ensure that the cmdlet (and its parameters) is behaving as expected before passing the parameter that says "I don't care about all that information. Just tell me if it passed or failed."
[1] $null is the effective result by default or if Set-StrictMode -Version 1 is in effect; with Set-StrictMode -Version 2 or higher, you would actually get an error.

A common mistake I've seen people make (myself included) is in your variable name and usage in powershell. For example I forgot $ all the time. This is just looping through my machine as an example, but it tests all these ports correctly.
$port_arr = #(139,3389,5040)
$mac = #("myComputer")
foreach ($mc in $mac){
foreach ($i in $port_arr) {
Test-NetConnection $mc -port $i
}
}
Do you have an example of your powershell code? Also, have you stepped through to determine that it's working as expected?

Related

Faulty PowerShell cmdlets filling up $Error automatic variable

In order to be informed when PowerShell Startup / Logon scripts running on remote computers have bugs, I tend to end scripts with the following:
If ($Error) {
(Code that sends a notification email to system administrators attaching the contents of the $Error variable for troubleshooting)
}
This is a great 'tell tale' to pick up edge cases / bugs. However, I've found some basic built-in PowerShell cmdlets dump data into $Error even on successful runs - for example, try:
$Error.Clear()
Get-NetIPConfiguration
$Error
And you'll see a load of errors in $Error that are not shown during normal output but look like:
Get-NetRoute : No matching MSFT_NetRoute objects found by CIM query for instances of the ROOT/StandardCimv2/MSFT_NetRoute class on the CIM server: SELECT * FROM
MSFT_NetRoute WHERE ((DestinationPrefix LIKE '0.0.0.0/0')) AND ((InterfaceAlias LIKE 'OpenVPN Wintun')). Verify query parameters and retry.
Get-NetConnectionProfile : No MSFT_NetConnectionProfile objects found with property 'InterfaceAlias' equal to 'Local Area Connection'. Verify the value of the property and
retry.
or
$Error.Clear()
Get-NetIPAddress
$Error
will return:
“Infinite : The term '“Infinite' is not recognized as the name of a cmdlet, function, script file, or operable program. Check the spelling of the name, or if a path was
included, verify that the path is correct and try again.
(A nice little bug for Microsoft to solve at some point, doubtless!)
Since it's unlikely that the cmdlets will be fixed any time soon, is there a way to run these cmdlets without them clogging up $Error with their useless information?
This is not a duplicate of Powershell: How can I stop errors from being displayed in a script? since that covers errors that actually display in red on the PowerShell console during a 'failed' run of the cmdlet; this is about errors generated by some cmdlets in the background during an apparently 'successful' run of a cmdlet which for some reason only get written to the automatic $Error variable.
Nonetheless I have already tried a number of solutions suggested in that post:
Running the cmdlets with -ErrorAction Ignore
Running the cmdlets with -ErrorAction SilentlyContinue
Running the cmdlets inside try {} catch {}
Running the cmdlets inside try {} catch {} with -ErrorAction Stop
Running the cmdlets with 2>$null following them
Setting $ErrorActionPreference = "SilentlyContinue" before running the cmdlets
I may be asking the impossible, but the way these cmdlets behave does make $Error very hard to use as an actual log, just want to know if I'm missing a trick.
I would like to be able to encapsulate buggy cmdlets in such a way that 'hidden' errors do not go into the automatic $Error variable.
I agree with #zett42' comment: I think you can't really prevent cmdlets from adding to $Error.
Also knowing that these "phantom errors" might already occur with a simple (Try/Catch) statement like:
Try { 1/0 } Catch {}
Anyways, you might consider to mark the last one and remove the errors added after that/ Like:
$HashCode = if ($Error) { $Error[0].GetHashCode() }
Get-NetIPAddress
While($Error -and $Error[0].GetHashCode() -ne $HashCode) { $Error.RemoveAt(0) }
Use the common -ErrorVariable parameter in order to collect only the (non-terminating) errors directly emitted or intentionally passed through by a cmdlet (those that it internally silences or ignores will not be captured):
# $errs is a self-chosen variable; note that it must be specified WITHOUT $
Get-NetIPAddress -ErrorVariable errs
# $errs now contains any (non-terminating) errors emitted by the
# Get-NetIPAddress call, as a [System.Collections.ArrayList] instance.
# (If no errors occurred, the list is empty).
Note: To also silence errors, combine -ErrorVariable errs with -ErrorAction SilentlyContinue (-ErrorAction SilentlyContinue does not work - see below).
The automatic $Error variable is designed to provide a session-wide log of all errors.
However, (script) cmdlets that deliberately ignore errors can avoid logging unnecessary errors by using -ErrorAction Ignore in internal calls - assuming that the errors are not only to be silenced, but also needn't be inspected.
(If errors need to be inspected after having collected them with -ErrorVariable, use of -ErrorAction Ignore is not an option, because it prevents error collection.)
The CDXML-based cmdlets from the NetTCPIP module, such as Get-NetIPAddress unfortunately use -ErrorAction SilentlyContinue in cases where -ErrorAction Ignore would suffice.
Conceivably, the cmdlet-generation code predates v3 of PowerShell, when the Ignore value was introduced.

PowerShell if statement gets boolean value wrong

I ran into a following problem (not exactly a problem, just something I don't understand). I have a class in Powershell that collects MAC addresses from the company network. Since there are different VLANs in the domain, I can't simply get the addresses from the ARP table, so I wrote a script that collects the MAC Addresses from the VLAN I'm in, and runs a remote command on the computers that are on a different subnet. It works perfectly, but I ran into a problem that I've never seen before.
When the MAC address of a computer isn't found in the local ARP table, my script sets the value of the MAC address $false and the following if statement (that calls the function to collect the MAC address from the remote machine) should run when the value of this variable is $false. Now, if I write the statement as if(!$this.MACAddress) then even though the value is $false, it steps through the conditional and runs like it was $true. If the value of the variable is empty, then it enters. If I write the statement as if($this.MACAddress -eq $false) it also enters the conditional, but for some reason it doesn't recognize the ! version right. I'm not saying I'm expert at PowerShell, but I used boolean variables before, and it always worked right. What can cause it not to work as I want it to here?
A little more of the code:
Class NetDevice
{
$devicename
$MACAddress
$IPAddress
NetDevice($devicename)
{
$this.name = $devicename
$this.IPAddres = $this.GetIPbyHostname($devicename) ## this is a method in the same class that simply gets the IP address by the provided hostname
$this.GetMACfromARP()
if(!$this.MACAddress) ## This is the conditional the scirpt never steps into, unless the variable is empty, or I use the full ($this.MACAddress -eq $false) form.
{
$this.GetMACfromADcomputer ## This is the method that invokes the command on the remote machine to get the MAC address from there
}
}
GetMACfromARP()
{
$MAC = arp -a | ConvertFrom-String | Where-Object { $_.P2 -eq $this.IPAddress }
if($MAC)
{
$this.MACAddress = $MAC.P3
}
else
{
$this.MACAddress = $false
}
}
}

Why does the `using` scope work locally with Start-Job, but not Invoke-Command?

Why doesn't PowerShell allow the use of the using scope when using Invoke-Command locally? According to the documentation, the using modifier can only be used on remote commands. To quote:
Beginning in PowerShell 3.0, you can use the Using scope modifier to identify a local variable in a remote command.
This behavior can be demonstrated when running Invoke-Command locally:
$myServerName = 'www.google.com'
Invoke-Command { ping $using:myServerName }
Which throws the following error:
A Using variable cannot be retrieved. A Using variable can be used only with Invoke-Command, Start-Job, or InlineScript in the script workflow. When it is used with Invoke-Command, the
Using variable is valid only if the script block is invoked on a remote computer.
The error indicates that the remote use of the using modifier is only valid remotely, with Invoke-Command. So, if we try running the same thing using Start-Job, what happens?
$myServerName = 'www.google.com'
$j = Start-Job { ping $using:myServerName }
while( $j.State -eq 'Running' ){ Start-Sleep -s 1 }
Receive-Job $j
Which doesn't throw an error, and I get the output I expect:
Pinging www.google.com [172.217.6.132] with 32 bytes of data:
Reply from 172.217.6.132: bytes=32 time=20ms TTL=56
Reply from 172.217.6.132: bytes=32 time=19ms TTL=56
Reply from 172.217.6.132: bytes=32 time=19ms TTL=56
Reply from 172.217.6.132: bytes=32 time=19ms TTL=56
Why does the documentation state that the using scope modifier only works remotely when it can be clearly used in local contexts as well? And similarly, if it works in the context of a local Start-Job, what stops it from working with a local Invoke-Command?
This is true when using "using" because the definition of using states,
Beginning in PowerShell 3.0, you can use the Using scope modifier to identify a local variable in a remote command
Anytime you use the $using, you have to provide -ComputerName or -Session arguments whether the target server is localhost or remote.
Ex.
$myServerName = 'www.google.com'
Invoke-Command { ping $using:myServerName }
### BIG ERROR.
$myServerName = 'www.google.com'
Invoke-Command { ping $using:myServerName } -computername $env:COMPUTERNAME
### Ping response.
$myServerName = 'www.google.com'
Invoke-Command { ping $myServerName }
### Ping Reponse.
$using: is only supported in a few, specific contexts, which have one thing in common: code that is being run outside the current runspace - all other contexts neither require nor support it. (#mklement0)
[Invoke-Command, Start-Job, and InlineScript are known contexts which support the use of $using: to pass variables in current local session.]
Documentation on where you can use $using
Note: This answer doesn't cover PowerShell workflows, because they are obsolescent technology no longer supported in PowerShell [Core] v6+ - see this blog post.
For anything that executes out-of-runspace, i.e. doesn't execute directly in the caller's runspace (thread), you need the $using: scope in order to embed variable values[1] from the caller's scope, so that the out-of-runspace code can access it.
Conversely, all other contexts neither require nor support $using:.
This includes local Invoke-Command calls, such as yours, (due to the absence of a -ComputerName or a -Session argument); however, such calls are rarely necessary (see below).
Overview of out-of-runspace contexts:
Remotely executed commands, started with Invoke-Command's -ComputerName parameter.
Runspace-local use of Invoke-Command - which is what happens without -ComputerName or -Session - neither requires nor supports $using: references (it runs in a child scope of the caller's scope, or, with -NoNewScope, directly in the caller's scope).
Runspace-local use of Invoke-Command is rarely necessary, because the &, the call (execute) operator (execution in a child scope), and ., the (dot-)source operator (execution directly in the caller's scope), are more concise and efficient alternatives.
Note that if you use the -ComputerName parameter to target the local computer, the command is still treated as if it were a remote execution, i.e., it goes through PowerShell's remoting infrastructure, and the same rules as for true remote execution apply.
Background jobs, started with Start-Job
Thread jobs, started via Start-ThreadJob.
In PowerShell [Core] v7+, this also includes script blocks passed to
ForEach-Object with the -Parallel switch.
Remotely executed commands and background jobs run out of process[2], and for values to cross these process boundaries they undergo XML-based serialization and deserialization, which typically involves loss of type fidelity - both on input and output.
See this answer for background information.
Note that this doesn't just apply to values embedded via $using:, but also to values passed as arguments via the -ArgumentList (-Args) parameter to Invoke-Command [-ComputerName] and Start-Job.
Thread jobs, by contrast, because they run in a different runspace (thread) in the same process, receive $using: variable values as their original, live objects and, similarly, return such objects.
The caveat is that explicit synchronization across runspaces (threads) may be needed, if they all access a given, mutable reference-type instance - which is most likely to happen with ForEach-Object -Parallel.
Generally, though, thread jobs are the better alternative to background jobs in most cases, due to their significantly better performance, lower resource use, and type fidelity.
[1] Note that this means that out-of-runspace code can never modify variables in the caller's scope. However, in the case of thread jobs (but not during remoting and not in background jobs), if the variable value happens to be an instance of a reference type (e.g., a collection type), it is possible to modify that instance in another thread, which requires synchronizing the modifications across threads, should multiple threads perform modifications.
[2] Unlike remote commands, background jobs run on the same computer, but in a (hidden) PowerShell child process.
You don't need the using if it's not remote:
invoke-command { ping $myservername }
Note that you have to be admin to invoke on localhost.

I cannot redirect or hide Get-VM output

So I have a line of code to retrieve the VMID from a hyper-v vm. Running on Server 2016 so it should be at least Powershell 5.0.
[string]$vmid = (Get-VM $VMName).VMID
Pulls out the ID just how I need. The problem is the script also prints out the full result of Get-VM. I cannot for the life of me figure out how to mute or redirect it.
[void] doesn't work cause I'm pulling it into a string.
*> $null, 2> $null, 1> $null all do nothing, and I've tried both in the parenthesis and after the expression.
Piping to Out-Null has no effect either in those locations.
Any idea how I'm supposed to hide this? I really don't need all this info.
On my hyper-v server I had to run
$vmid = (Get-VM $VMName).VMid.Guid
to save only the string to variable
The above command works for me, so it's probably somewhere else in your code that you are getting the unwanted output?
So instead of the above, I ended up defining a $vm variable when creating the VM, then using it in the operations below.
$vm = New-VM ...
...
[string]$vmid = $vm.VMID.GUID

Powershell DHCP scope options extraction failure using Invoke-Command

I'm trying to extract dhcp scope option info from a list of servers, the list obtained by querying AD for authorized dhcp servers in the domain. I'm using powershell's invoke-command to pass netsh dhcp server \\$servername scope $IP show optionvalue to a remote server. The $IP variable isn't passing the way the command wants to see it. It throws the The command needs a valid Scope IP Address. error.
I'm getting the scope ip address by first running netsh dhcp server \\$servername show scope and extracting the scope ip from that output, storing it in $IP.
I can type the IP in manually into the script and it returns the scope options but passing in the variable always returns the error. I've tested the command itself in a powershell console, both by manually typing in the IP and by creating a variable with the IP (as a string) and using it in the command, which works as well. There are no special characters, that i can tell, or white spaces when i store the IP in the script. I trim those out. I've also tried converting the string to an IP address using [IPAddress], to no avail.
Here is the code that gathers the scope info and then attempts to get the scope options:
foreach ($n in $name) {
$n
$showScopes = Invoke-command -computername $n -ScriptBlock {netsh dhcp server \\$n show scope}
$formatScopeInfo = $showScopes | ? {$_.Trim() -ne "" -and ($_.Contains("Disabled") -or $_.Contains("Active"))}
foreach ($en in $formatScopeInfo) {
$scopeIps = $en.Split("-")
$IP = [IPAddress]$scopeIps[0].Trim()
$IP.IPAddressToString
Invoke-Command -ComputerName $n -ScriptBlock {netsh dhcp server \\$n scope $IP.IPAddressToString show optionvalue}
}
The first foreach works and removes the lines that don't contain scope info. The second foreach partially works, it does strip out the IP. Initially i just stored it as a string, $IP = $scopeIps[0].Trim() but that wasn't working. I tried a number of things. I tried converting the octets to integers and joining them with ".", I tried to store the whole command as as a string and pass that into the Invoke-Command. Like so:
$command = "netsh dhcp server \\$n scope $IP show optionvalue"
Invoke-Command -ComputerName $n -ScriptBlock {$command}
The ultimate goal is to be able to extract any configured scope options, wherever they may be configured (server, reservation...etc). I fear I've gotten to that point where I'm so hyper-focused on what I think is the problem, that I may be missing something simple and/or crucial elsewhere. My opinion is that the command wants to see an actual IP address but my attempts to pass the variable that way have failed (and it works in the powershell console when saved as a string).
Fair disclosure, I'm still very much a novice and I was reluctant to post my code. I see so many on here with incredibly elegant solutions to things and, by comparison, my stuff seems extremely clunky. I've never had to post before as most times I can find/figure out where i've gone wrong. But I endeavor to learn and I have spent the better part of this weekend googling with no results. I have seen the script out there that works for pre 2012 servers but I really enjoy writing my own. I'm not looking to have anyone "do it for me", if you can point me down the appropriate rabbit hole; I'm happy to venture down it. Any suggestions on the code itself (appearance, better way of doing something...etc) are appreciated as well.
Apologies for the verbosity. I'm stuck and appreciate any help.
In your invoke-command you are not passing parameter,it should be like this:
$showScopes = Invoke-command -computername $n -ScriptBlock {
param($n)
netsh dhcp server \\$n show scope
} -argumentlist $n
and
Invoke-Command -ComputerName $n -ScriptBlock {
param($n,$IP)
netsh dhcp server \\$n scope $IP.IPAddressToString show optionvalue
} -argumentlist $n,$IP