PowerShell if statement gets boolean value wrong - powershell

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
}
}
}

Related

Using PowerShell to identify a machine as a server or PC

I'm trying to write a PowerShell script that will give me a list if of roles and features if run on a server but if run on a client machine will say "Only able to execute command on a server."
I've played around with this script a lot and can get it to run on either a client machine or server (depending on what I've tweaked) but not both. Here's the latest iteration:
$MyOS="wmic os get Caption"
if("$MyOS -contains *Server*") {
Get-WindowsFeature | Where-Object {$_. installstate -eq "installed"
}}else{
echo "Only able to execute command on a server."}
What am I doing wrong?
The quotes around your wmic command will create the $MyOS variable with a String and not execute the command. Still, I would recommend you use native PowerShell commands such as Get-CimInstance. Like the $MyOS variable your if statement condition will always equal true as the quotes will make it a String.
$MyOS = Get-CimInstance Win32_OperatingSystem
if ($MyOS.Caption -like "*Server*") {
Get-WindowsFeature | Where-Object { $_. installstate -eq "installed" }
}
else {
Write-Output "Only able to execute command on a server."
}
You can also use the ProductType property. This is a (UInt32) number with the following values:
1 - Work Station
2 - Domain Controller
3 - Server
$MyOS = (Get-CimInstance Win32_OperatingSystem).ProductType
if ($MyOS -gt 1) {
Get-WindowsFeature | Where-Object { $_. InstallState -eq "installed" }
}
else {
Write-Output "Only able to execute command on a server."
}
Try to use '-like' instead of 'contains', it should work
Generally, I try to avoid pre-checks like this that make assumptions about functionality that may not be true forever. There's no guarantee that Get-WindowsFeature won't start working on client OSes in a future update.
I prefer to just trap errors and proceed accordingly. Unfortunately, this particular command produces a generic Exception rather than a more specifically typed exception. So you can't really do much other than string matching on the error message to verify specifically what happened. But there's very little that can go wrong with this command other than the client OS error. So it's pretty safe to just assume what went wrong if it throws the exception.
try {
Get-WindowsFeature | Where-Object { $_. InstallState -eq "installed" }
} catch {
Write-Warning "Only able to execute command on a server."
}
If you don't want to accidentally hide an error that's not the client OS one, change the warning message to just use the actual text from the error. This also gets you free localization if you happen to be running this code in a location with a different language than your own.
Write-Warning $_.Exception.Message

Powershell Test-NetConnection returns False in loop script

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?

using powershell to check active IP's or MAC's connected to router

I want to run a Powershell script that talks to the router/AP and figures out what IP (I have Reserved IPS) or MAC address is currently connected to the Router/AP. The script would output what is connected so that I could see "who's home".
At first I used IE though powershell logging into the router and trying to capture data of the wifi client page but I don't think that is the way to go. Is there another way to do this? A way to scan the network without worrying about logging into the router?
If you have DNS resolution on the names of your PCs, you can try this out.
$Computer = "value or foreach loop of values"
$IPAddress = ([System.Net.Dns]::GetHostByName($Computer).AddressList[0]).IpAddressToString
$IPMAC = Get-WmiObject -Class Win32_NetworkAdapterConfiguration -ComputerName $Computer
$MACAddress = ($IPMAC | where { $_.IpAddress -eq $IPAddress}).MACAddress
I have tested this with a couple individual names and a foreach loop getting the names from a txt or csv file, and I tested using
write-host $IPAddress $MACAddress
at the end for a sanity check.
If you want to verify an up/down state of the computer before querying, try using the 'test-connection' powershell command (basically a ping that grabs the results)

Detect if process executes inside a Windows Container

It's simple.
I would like to detect with code if my process is running inside a windows container. There are examples but they are all for linux based containers.
I'm looking for something unique and explicit to docker that can be used to make a safe conclusion whether a process is executing inside a container hosted windows operating system and not otherwise.
My preferred language is PowerShell but if someone points out the how to detect, I'll port it to PowerShell.
New readers can skip ahead to the part marked with "Update" which contains the accepted solution.
A quick check with whoami on the command prompt showed that the combination of domain and username that is used inside a container seems to be rather unusual. So I used this code to solve the problem:
function Test-IsInsideContainer {
if( ($env:UserName -eq "ContainerAdministrator") -and ($env:UserDomain -eq "User Manager") ) {
$true
}
else {
$false
}
}
Update: Another option is to check if the service cexecsvc exist. An internet search did not yield much information about this service, but its name (Container Execution Agent) suggests that it only exists inside of containers (which I verified with some quick test on Win10 and a Server2016 Docker-host).
So maybe this code meets your requirements (I am a newbie in Powershell):
function Test-IsInsideContainer {
$foundService = Get-Service -Name cexecsvc -ErrorAction SilentlyContinue
if( $foundService -eq $null ) {
$false
}
else {
$true
}
}
There's a "ContainerType" registry value under HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control.
So:
function Test-IsInsideContainer {
$containerType = (Get-ItemProperty "HKLM:\SYSTEM\CurrentControlSet\Control").ContainerType
$containerType -ne $null
}
Will below work?
PS C:\> (Get-NetAdapter).Name -match "container"
True
As of Docker 17.06 onward you could use the DNS entry docker.for.mac.localhost and docker.for.mac.localhost to determine if the container runs on a mac or windows host. if none of these host names can be pinged you might safely assume its a linux host. This will probably not work for Swarms.
I am not an expert in Bash but an example could look like this.
#!/bin/bash
ping -c 1 docker.for.mac.localhost &>/dev/null
if [ $? -eq 0 ]; then
echo "Mac Host"
fi
ping -c 1 docker.for.win.localhost &>/dev/null
if [ $? -eq 0 ]; then
echo "Windows Host"
fi;
I hope this helps you writing the script in PowerShell and please do share for whenever I need something like this in Windows.

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