PowerShell If statement not equating properly - powershell

What am I doing wrong here?
Why do the 2 variables not equal each other?
When I run this script
$temp1 = "#{Dhcp=Disabled}"
$temp2 = Get-NetIPInterface My_Ethernet | select Dhcp
write-host ""
write-host "1" $temp1
write-host "2" $temp2
write-host ""
if ($temp2 -eq $temp1){
write-host "IP address is Static "
}
Else {
write-host "IP address is Not Static"
}
I get this result
1 #{Dhcp=Disabled}
2 #{Dhcp=Disabled}
IP address is Not Static

With the helpful suggestion from Mathias this is now working as expected
$temp1 = "Disabled"
$temp2 = Get-NetIPInterface My_Ethernet | select Dhcp
write-host ""
write-host ""
write-host "1" $temp1
write-host "2" $temp2.dhcp
write-host ""
write-host ""
if ($temp2.dhcp -eq $temp1){
write-host "IP address is Static "
}
Else {
write-host "IP address is Not Static"
}

Just to complement your own effective solution:
Since your intent was to compare a property's value to another value, select -ExpandProperty Dhcp would have returned that value directly (see the docs for Select-Object):
if ((Get-NetIPInterface My_Ethernet | select -ExpandProperty Dhcp) -eq $temp1) { # ...
However, it would be much simpler to use direct property access, using ., the member-access operator:
if ((Get-NetIPInterface My_Ethernet).Dhcp -eq $temp1) { # ...
Note that .Dhcp would work even if your Get-NetIPInterface call returned multiple objects, in which case an array of values is returned, courtesy of PowerShell's member-access enumeration feature.[1]
Finally, note that Write-Host is typically the wrong tool to use, unless the intent is to write to the display only, bypassing the success output stream and with it the ability to send output to other commands, capture it in a variable, or redirect it to a file. To output a value, use it by itself; e.g. $value, instead of Write-Host $value (or use Write-Output $value); see this answer.
To explicitly print only to the display but with rich formatting, use Out-Host, given that the .ToString() stringification that Write-Host uses on its input is often unhelpful - see this post.
[1] Note that PowerShell's comparison operators such as -eq exhibit filtering behavior when their LHS is an array (collection); that is, instead of returning $true or $false, they then return the sub-array of matching elements - see about_Comparison_Operators.

Related

Nested if and for loop errors

would any of you be able to help me with the below code
$pcname = "jwang"
#We set $test as a string to be called later
$test = get-adcomputer -filter "name -like '$pcname'" | select -ExpandProperty name
#We define a null string to test our IF statement
$nothing = $null
$number = 1
if ($test -ne $null) {
Write-Host "$pcname is currently in use"
for($number = 1; ($test = get-adcomputer -filter "name -like '$pcname + $number'" | select -ExpandProperty name) -ne $null; $number++ ){
Write-Host $pcname + $number
}
}
else {
Write-host "AD lookup complete, new PC name will be $pcname"
}
The IF statement works correctly, but the trouble starts when I add the nested FOR loop.
The end goal is to have AD tell me if the name is available or not.
Right now in AD there is
JWANG
JWANG1
JWANG2
JWANG3
JWANG4
I want the for loop to eventuelly tell me that "JWANG5" is available, any help is appreciated, thank you
The immediate problem with your code is that $pcname + $number - an expression - isn't enclosed in $(...), the subexpression operator inside your expandable (double-quoted) string ("...").
Therefore, use $($pcname + $number) or, relying on mere string interpolation, $pcname$number
An aside re testing for $null:
It is advisable to make $null the LHS for reliably null testing (if ($null -ne $test)), because on the RHS it acts as a filter if the LHS happens to be an array - see about_Comparison_Operators.
However, often - such as when objects are being returned from a command, you can simply use implicit to-Boolean conversion (if ($test)) - see the bottom section of this answer for the complete rules.
Taking a step back:
Querying AD multiple times is inefficient, and since you're already using the -like operator, you can use it with a wildcard pattern to find all matching names at once:
$pcname = 'jwang'
# Note the use of "*" to match all names that *start with* $pcname
$names = get-adcomputer -filter "name -like '$pcname*'" | select -ExpandProperty name
if ($names) { # at least one name found
Write-Host "$pcname is currently in use"
# Find the highest number embedded in the matching names.
$seqNums = [int[]] ($names -replace '\D')
# The new sequence number is the next number following the currently highest.
$nextSeqNum = 1 + [Linq.Enumerable]::Max($seqNums)
# Form the new name and output it.
$pcname + $nextSeqNum
}
else {
Write-host "AD lookup complete, new PC name will be $pcname"
}

Comparing multiple email address using powershell match

I have a CSV file of 2000 email addresses. I am using PowerShell to check if the user is active in AD. Another developer wrote a PowerShell script for me to do this but he only used the main domain for the email format to match, he didn't add the subdomian that it could have. Some of our email addresses have a 3 part email address after the # symbol.
For example, his code:
foreach ($user in $users) {
Write-Host $user.email
if ($user.email -match "\#mycompany\.com$") {
$status = "NOT FOUND"
# loop through possible AD domains until there is a hit
foreach ($domain in "na","au","eu","as") {
if ($status -eq "NOT FOUND") {
Write-Host " $($domain)" -NoNewline
$status = Get-UserFromEmail -EMail $user.email -ADDomain $domain
Write-Host $status
}
else {
break
}
}
Write-Host
Add-Content -Path $outcsv -Value "$($user.email),$($user.type),`"$($status)`""
}
else {
Add-Content -Path $outcsv -Value "$($user.email),$($user.type),NOT MYCOMPANY"
}
What I need to be able to do is get the match to check if it is a two or three part email address.
#consultant.mycompany.com or #mycompany.com.
Any insight for this PowerShell newbie would be appreciated.
here is one way to test for membership in more than one email domain. all of the domains are all in the same example.com, but they could easily be in testing.com or wizbang.org.
this demos the idea, i presume you can insert it into your script as needed. [grin]
what it does ...
builds a list of email addresses to test
you will get that from your source ... just be sure they are plain strings, not a string inside a property of an object.
builds a domain list
uses the built in regex escape method to escape things like dots as they are needed
adds a $ to the end of each escaped string to anchor the pattern to the end of the email address
uses the escaped strings to build a regex OR of that list
iterates thru the email address list and gets the ones that match one of the domain list items
saves the matches to a $Var
displays the content of that $Var on screen
the code ...
$EmailList = #(
'ABravo#example.com'
'BCharlie#more.example.com'
'CDelta#example.com'
'DEcho#zigzag.papers.com'
'EFoxtrot#even.more.example.com'
)
$DomainList = #(
'#example.com'
'#more.example.com'
'#even.more.example.com'
)
$Regex_DL = $DomainList.ForEach({
[regex]::Escape($_) + '$'
}) -join '|'
$ValidEmailAddressList = $EmailList -match $Regex_DL
$ValidEmailAddressList
output ...
ABravo#example.com
BCharlie#more.example.com
CDelta#example.com
EFoxtrot#even.more.example.com
You can always use the -or operator to chain multiple expressions inside the if condition:
if ($user.email -match "\#mycompany\.com$" -or $user.email -match '#consultant\.mycompany\.com$'){
# ...
}
Alternatively, you can construct a regex pattern that'll match both:
if($user.email -match '#(?:consultant\.)?mycompany\.com$'){
# ...
}
If you're ever unsure about how to escape a literal string in a reguar expression, use [regex]::Escape():
PS C:\> [regex]::Escape('#consultant.mycompany.com')
#consultant\.mycompany\.com

Parameter not being separated for foreach loop

I am trying to get monitor information from workstations. When only one monitor my code works all right. But on multiple monitors it fails.
$connections = get-ciminstance -namespace root/wmi -classname
WmiMonitorConnectionParams
$videooutput = "$($($connections.VideoOutputTechnology))"
foreach ($output in $videooutput){
if ($output -eq 10) {write-host "DP"}
if ($output -eq 4) {write-host "DVI"}
if ($output -eq 5) {write-host "HDMI"}
if ($output -eq 0) {write-host "VGA"}
else {write-host "unknown"}
}
The issue is the output of the $ videooutput. It returns multiple monitors like this:
4 10
instead of this:
4
10
So in a multiple monitor situation I get "unknown"
Replace "$($($connections.VideoOutputTechnology))" with $connections.VideoOutputTechnology.
"$($($connections.VideoOutputTechnology))", aside from needlessly using $(...) twice, stringifies the array returned by $connections.VideoOutputTechnology, which by default creates a single string containing the (stringified) elements as a space-separated list.
As an aside, just to clarify: Write-Host is typically the wrong tool to use, unless the intent is to write to the display only, bypassing the success output stream and with it the ability to send output to other commands, capture it in a variable, redirect it to a file. (In PSv5+ Write-Host writes to the information stream, whose output can be captured, but only via 6>; see this answer.)
Also, using a switch statement instead of a foreach loop with multiple if statements enables a simpler solution.
To put it all together:
$connections = get-ciminstance -namespace root/wmi -classname WmiMonitorConnectionParams
$videooutput = $connections.VideoOutputTechnology
switch ($videooutput) {
10 { "DP"; continue }
4 { "DVI"; continue }
5 { "HDMI"; continue}
0 { "VGA"; continue }
default { "unknown"}
}

Pass a single space-delimited string as multiple arguments

I have a Powershell function in which I am trying to allow the user to add or remove items from a list by typing the word "add" or "remove" followed by a space-delimited list of items. I have an example below (slightly edited, so you can just drop the code into a powershell prompt to test it "live").
$Script:ServerList = #("Server01","Server02","Server03")
Function EditServerList (){
$Script:ServerList = $Script:ServerList |Sort -Unique
Write-host -ForegroundColor Green $Script:ServerList
$Inputs = $args
If ($Inputs[0] -eq "start"){
$Edits = Read-Host "Enter `"add`" or `"remove`" followed by a space-delimited list of server names"
#"# EditServerList $Edits
# EditServerList $Edits.split(' ')
EditServerList ($Edits.split(' ') |Where {$_ -NotLike "add","remove"})
EditServerList start
} Elseif ($Inputs[0] -eq "add"){
$Script:ServerList += $Inputs |where {$_ -NotLike $Inputs[0]}
EditServerList start
} Elseif ($Inputs[0] -eq "remove"){
$Script:ServerList = $Script:ServerList |Where {$_ -NotLike ($Inputs |Where {$_ -Notlike $Inputs[0]})}
EditServerList start
} Else {
Write-Host -ForegroundColor Red "ERROR!"
EditServerList start
}
}
EditServerList start
As you can see, the function takes in a list of arguments. The first argument is evaluated in the If/Then statements and then the rest of the arguments are treated as items to add or remove from the list.
I have tried a few different approaches to this, which you can see commented out in the first IF evaluation.
I have two problems.
When I put in something like "add Server05 Server06" (without quotes) it works, but it also drops in the word "add".
When I put in "remove Server02 Server03" (without quotes) it does not edit the array at all.
Can anybody point out where I'm going wrong, or suggest a better approach to this?
To address the title's generic question up front:
When you pass an array to a function (and nothing else), $Args receives a single argument containing the whole array, so you must use $Args[0] to access it.
There is a way to pass an array as individual arguments using splatting, but it requires an intermediate variable - see bottom.
To avoid confusion around such issues, formally declare your parameters.
Try the following:
$Script:ServerList = #("Server01", "Server02", "Server03")
Function EditServerList () {
# Split the arguments, which are all contained in $Args[0],
# into the command (1st token) and the remaining
# elements (as an array).
$Cmd, $Servers = $Args[0]
If ($Cmd -eq "start"){
While ($true) {
Write-host -ForegroundColor Green $Script:ServerList
$Edits = Read-Host "Enter `"add`" or `"remove`" followed by a space-delimited list of server names"
#"# Pass the array of whitespace-separated tokens to the recursive
# invocation to perform the requested edit operation.
EditServerList (-split $Edits)
}
} ElseIf ($Cmd -eq "add") {
# Append the $Servers array to the list, weeding out duplicates and
# keeping the list sorted.
$Script:ServerList = $Script:ServerList + $Servers | Sort-Object -Unique
} ElseIf ($Cmd -eq "remove") {
# Remove all specified $Servers from the list.
# Note that servers that don't exist in the list are quietly ignored.
$Script:ServerList = $Script:ServerList | Where-Object { $_ -notin $Servers }
} Else {
Write-Host -ForegroundColor Red "ERROR!"
}
}
EditServerList start
Note how a loop is used inside the "start" branch to avoid running out of stack space, which could happen if you keep recursing.
$Cmd, $Servers = $Args[0] destructures the array of arguments (contained in the one and only argument that was passed - see below) into the 1st token - (command string add or remove) and the array of the remaining arguments (server names).
Separating the arguments into command and server-name array up front simplifies the remaining code.
The $var1, $var2 = <array> technique to split the RHS into its first element - assigned as a scalar to $var1 - and the remaining elements - assigned as an array to $var2, is commonly called destructuring or unpacking; it is documented in Get-Help about_Assignment Operators, albeit without giving it such a name.
-split $Edits uses the convenient unary form of the -split operator to break the user input into an array of whitespace-separated token and passes that array to the recursive invocation.
Note that EditServerList (-split $Edits) passes a single argument that is an array - which is why $Args[0] must be used to access it.
Using PowerShell's -split operator (as opposed to .Split(' ')) has the added advantage of ignoring leading and trailing whitespace and ignoring multiple spaces between entries.
In general, operator -split is preferable to the [string] type's .Split() method - see this answer of mine.
Not how containment operator -notin, which accepts an array as the RHS, is used in Where-Object { $_ -notin $Servers } in order to filter out values from the server list contained in $Servers.
As for what you tried:
EditServerList ($Edits.split(' ') |Where {$_ -NotLike "add","remove"}) (a) mistakenly attempts to remove the command name from the argument array, even though the recursive invocations require it, but (b) actually fails to do so, because the RHS of -like doesn't support arrays. (As an aside: since you're looking for exact strings, -eq would have been the better choice.)
Since you're passing the arguments as an array as the first and only argument, $Inputs[0] actually refers to the entire array (command name + server names), not just to its first element (the command name).
You got away with ($Inputs[0] -eq "add") - even though the entire array was compared - because the -eq operator performs array filtering if its LHS is an array, returning a sub-array of matching elements. Since add was among the elements, a 1-element sub-array was returned, which, in a Boolean context, is "truthy".
However, your attempt to weed out the command name with where {$_ -NotLike $Inputs[0]} then failed, and add was not removed - you'd actually have to compare to $Inputs[0][0] (sic).
Where {$_ -NotLike ($Inputs |Where {$_ -Notlike $Inputs[0]})} doesn't filter anything out for the following reasons:
($Inputs |Where {$_ -Notlike $Inputs[0]}) always returns an empty array, because, the RHS of -Notlike is an array, which, as stated, doesn't work.
Therefore, the command is the equivalent of Where {$_ -NotLike #() } which returns $True for any scalar on the LHS.
Passing an array as individual arguments using splatting
Argument splatting (see Get-Help about_Splatting) works with arrays, too:
> function foo { $Args.Count } # function that outputs the argument count.
> foo #(1, 2) # pass array
1 # single parameter, containing array
> $arr = #(1, 2); foo #arr # splatting: array elements are passed as indiv. args.
2
Note how an intermediate variable is required, and how it must be prefixed with # rather than $ to perform the splatting.
I'd use parameters to modify the ServerList, this way you can use a single line to both add and remove:
Function EditServerList {
param(
[Parameter(Mandatory=$true)]
[string]$ServerList,
[array]$add,
[array]$remove
)
Write-Host -ForegroundColor Green "ServerList Contains: $ServerList"
$Servers = $ServerList.split(' ')
if ($add) {
$Servers += $add.split(' ')
}
if ($remove) {
$Servers = $Servers | Where-Object { $remove.split(' ') -notcontains $_ }
}
return $Servers
}
Then you can call the function like this:
EditServerList -ServerList "Server01 Server02 Server03" -remove "Server02 Server03" -add "Server09 Server10"
Which will return:
Server01
Server09
Server10

Passing a string with a function and always getting $null variable

I am using the following in the start of a function that is meant to query a computer for various wmi objects. If just get_volumes is ran alone it should use 127.0.0.1. It seems that when I run get_volumes without any string this it passes right on to the else segment. What is the proper or a better way to accomplish this?
PS> function get_volumes([string]$a){
if ($a -eq $null){
write-host 'Using localhost'
$a = '127.0.0.1'
}else{ write-host 'Using' $a}
}
PS>get_volumes
Using
Thanks
$a is an empty string and not $null. Try this instead:
function Get-Volumes([string]$IPAddress = '127.0.0.1'){
Write-Host "Using $IPAddress"
}
Note that you can set a default value for when the user doesn't supply an argument for a parameter.