Checking if two variables either both not null or both null - powershell

I have been developing a script to send mail based on variables. I have a script like below.
Each of these 2 variables may be $null or not $null. What's the best practice to check for such condition?
Here is my script:
$variableA = ""
$variableB = ""
if($variableA) {
Write-Host "mail send variableA"
} else {
Write-Host "mail not send variableA"
}
if($variableB) {
Write-Host "mail send variableB"
} else {
Write-Host "mail not send variableB"
}

You can use negated -xor operator:
# Either both nulls or both have values
(-not (($null -eq $a) -xor ($null -eq $b)))
One can disagree what's more readable. I'd personally just go with more explicit formula:
(($null -eq $a) -and ($null -eq $b)) -or
(($null -ne $a) -and ($null -ne $b))
Remember to put $null in the left side of the comparison, it's considered as a best practice.

Related

Nesting ForEach within If statement (PowerShell)

I am trying to loop through a list of Windows services and if any of them match a certain criteria, I would like to return exit code 1.
If there is no match, I would like to return exit code 0.
I am struggling to put this within an If statement, I think I'm putting the code in the wrong place!
Could anyone lend me a hand? Script below.
Thanks in advance.
Adrian
try
{
#Pull list of services from registry
$svclist = Get-ChildItem HKLM:\SYSTEM\CurrentControlSet\services | ForEach-Object {Get-ItemProperty $_.PsPath}
#Ignore anything after .exe, filter for vulnerable services
ForEach ($svc in $svclist) {
$svcpath = $svc.ImagePath -split ".exe"
if(($svcpath[0] -like "* *") -and ($svcpath[0] -notlike '"*') -and ($svcpath[0] -notlike "\*")) {
$svc | fl -Property DisplayName,ImagePath,PsPath
}
}
if (($svc -ne $null)){
Write-Host "Match"
Return $svc.count
exit 1
}
else{
Write-Host "No_Match"
exit 0
}
}
catch{
$errMsg = $_.Exception.Message
Write-Error $errMsg
exit 1
}
You can either return early or use a variable with a single [bool] value to keep track of whether anything was matched:
return early
foreach($svc in $svcList)
{
$svcpath = $svc.ImagePath -split ".exe"
if(($svcpath[0] -like "* *") -and ($svcpath[0] -notlike '"*') -and ($svcpath[0] -notlike "\*")) {
return 1
}
}
# if we've reached this point then no matches occurred
return 0
Using a [bool]
$matchFound = $false
foreach($svc in $svcList)
{
$svcpath = $svc.ImagePath -split ".exe"
if(($svcpath[0] -like "* *") -and ($svcpath[0] -notlike '"*') -and ($svcpath[0] -notlike "\*")) {
$matchFound = $true
}
}
return [int]$matchFound # $false = 0, $true = 1
I always try to use the most powershell-correct methods to achieve what I want. Especially when going through data I prefer to try filtering with the x-object cmdlets.
In your case my suggestion would be to simply loop over the original list with the Where-Object command, this allows you to retrieve a list of items that conform to your search, kind of like an SQL query:
$resultList = $svclist | Where-Object {
($_.ImagePath -like "* *") -and ($_.ImagePath -notlike '"*') -and ($_.ImagePath -notlike "\*")
}
In this case I skipped over the -split ".exe" part, as I didn't quite understand it's purpose, but you could also put that in your filter using regular expressions with the -match operator instead of the -like and -notlike values you make one regex match
Then you can check if that list is populated or not:
if ($resultList) {
return 1
}
else {
return 0
}
It is also considered best practice to only use the aliases for commands (e.g. fl should be Format-List). This will increase readability for future maintenance, of course if it's a one-time script is would be more appropriate. I just try to avoid it as much as I can these days.

Search multiple variables with Where-Object [duplicate]

function processSearch {
Get-Process -Name "$processSearch*"
}
function processKill {
Stop-Process -Name "$processSearch*"
}
$processSearch = Read-Host -Prompt "Enter the full or partial name of the process: "
processSearch
if ((Get-Process -Name "$processSearch*") -eq $null) {
Write-Output "ERROR: NO PROCESS FOUND."
[Threading.Thread]::Sleep(3000)
}
if ((Get-Process -Name "$processSearch*") -ne $null) {
$userInput= Read-Host -Prompt "Kill process?"
}
if ($userInput -eq "y" -or "Y") {
processKill
}
elseif ($userInput -eq "n" -or "N") {
Write-Output "Process not killed."
}
else {
Write-Output "ERROR: UNHANDLED INPUT."
}
When my script gets to $userInput= Read-Host -Prompt "Kill process?", and I enter any text, the script will terminate the selected process.
I'm new to scripting, so please let me know where my logic is flawed, thank you.
The issue you're experiencing is because when you're checking the $userInput variable you've forgotten to declare what variable to check against in your -or part of the clause.
This code:
if ($userInput -eq "y" -or "Y") {
processKill
}
elseif ($userInput -eq "n" -or "N") {
Write-Output "Process not killed."
}
else {
Write-Output "ERROR: UNHANDLED INPUT."
}
should become:
if ($userInput -eq "y" -or $userInput -eq "Y") {
processKill
}
elseif ($userInput -eq "n" -or $userInput -eq "N") {
Write-Output "Process not killed."
}
else {
Write-Output "ERROR: UNHANDLED INPUT."
}
You could also make the if statements a little less verbose by using the case-sensitive -cin operator as this will check if the value is in an array of given values as below:
if ($userInput -cin "y", "Y") {
processKill
}
elseif ($userInput -cin "n", "N") {
Write-Output "Process not killed."
}
else {
Write-Output "ERROR: UNHANDLED INPUT."
}
NiMux's answer makes good points, but it's worth taking a step back:
PowerShell's operators are case-insensitive by default:
'y' -eq 'Y' is $true.
To perform case-sensitive operations, prefix the operator name with c; e.g. -ceq:
'Y' -ceq 'Y' is $true, but 'y' -ceq 'Y' is $false.
Therefore:
$userInput -eq 'y' and $userInput -eq 'n' are sufficient in your case.
In case you truly have multiple values to test against, use the -inoperator; e.g.:
$userInput -in 'y', 'n', 'c'
As for what you tried:
Due to PowerShell's operator precedence,
$userInput -eq "y" -or "Y"
is evaluated as:
($userInput -eq "y") -or ("Y")
That is, the RHS operand of the -or operation is string "Y" alone, and evaluating a non-empty string in PowerShell in a Boolean context always yields $true (irrespective of what the string contains).
In effect, your attempt was therefore equivalent to the following, which is always $true:
($userInput -eq "y") -or $true
The immediate (but suboptimal) fix to your attempt would have been what's shown in NiMux's answer: use of two separate -eq operations ($userInput -eq 'y' -or $userInput -eq 'Y').

Powershell bitlocker check

Hi im having diffeculties to get my script working:
It keeps failing on the first write output, even when the powershell version is higher then 4. It only works when I remove And $winver -eq $os1 -or $os2 -or $os3.
Otherwise it keeps telling me my powershell version needs to be upgraded. Im on V5 currently and $PSVersionTable.PSVersion.Major does says it 5 indeed.
What am i doing wrong?
$winver = (Get-WmiObject -class Win32_OperatingSystem).Caption
$powershellversion = $PSVersionTable.PSVersion.Major
$os1 = "Microsoft Windows 7 Professional"
$os2 = "Microsoft Windows 10 Pro"
$os3 = "Microsoft Windows 10 Enterprise"
if($winver -ne ($os1, $os2, $os3) -contains $winver){
Write-Host "Bitlocker not supported on $winver"
Exit 0
}
if($powershellversion -lt 4){
Write-Host "Upgrade Powershell Version"
Exit 1010
}
else
{
$bitlockerkey = (Get-BitLockerVolume -MountPoint C).KeyProtector.RecoveryPassword
$pcsystemtype = (Get-WmiObject -Class Win32_ComputerSystem).PCSystemType
if ($pcsystemtype -eq "2"){
$setsystemtype = "Laptop"
}
else {
$setsystemtype = "Desktop"
}
if ($setsystemtype -eq "laptop" -And $bitlockerkey -eq $null -and ($os1, $os2, $os3) -contains $winver){
Write-Host "$setsystemtype without bitlocker"
Exit 1010
}
if ($setsystemtype -eq "desktop" -And $bitlockerkey -eq $null -and ($os1, $os2, $os3) -contains $winver){
Write-Host "$setsystemtype without bitlocker"
Exit 0
}
if ($winver -eq ($os1, $os2, $os3) -contains $winver){
Write-Host "$bitlockerkey"
Exit 0
}
}
Let's see what this actually does:
if ($powershellversion -lt 4 -And $winver -eq $os1 -or $os2 -or $os3) { ... }
If your powershell version is less than 4, and Win version is equal
to os1, then proceed
If os2 has a value, then proceed
If os3 has a value, then proceed
The topic here is Operator Precedence, specifically, what happens first when evaluating a line of code, what happens second, third, and so on. Just like in algebra math, adding parens around part of the formula changes the order in whcih you read it.
So, you can mess around with parens to get your logic to work:
if($powershellversion -lt 4 -and ( ($winver -eq $os1) -or ($winver -eq $os2) -or ($winver -eq $os3) ))
In other words
evaluate if PS version is < 4 ($powershellversion -lt 4), -and
evaluate if winver is either os1, os2 or os3: ( ($winver -eq $os1) -or ($winver -eq $os2) -or ($winver -eq $os3) ).
Or, you can rearrange your logic a bit by putting your os variables into an array, and seeing if $winver is in there:
if($powershellversion -lt 4 -and $winver -in ($os1, $os2, $os3)) { ... }
Edit: Or
if($powershellversion -lt 4 -and ($os1, $os2, $os3) -contains $winver) { ... }
for backwards compatibility with v2.0.

Powershell - foreach loop is exiting prematurely?

Forgive me, but I'm very new to Powershell. I'm having a problem understanding why my code is abruptly exiting the foreach loop once the code finds the book with an ID called "bk103". I'm expecting that the foreach loop iterate through the for loop 12 times since there are 12 books in the xml file. Why is it exiting early?
Thank you!
Set-Location 'C:\PowershellPractice\' #'
[int]$index = 0
[string] $xmlFilePath=’C:\PowershellPractice\books.xml’
[xml] $xmlContent = [xml] (Get-Content -Path $xmlFilePath)
$newSubNode = $xmlContent.CreateElement("security")
$newSubNode.SetAttribute("Mode", "transport")
$collection = $xmlContent.catalog.ChildNodes
Write-Host "Collection has " $collection.Count " elements in it!"
foreach ($item in $collection){
Write-Host "In foreach, index is " $index
if ($item.id -eq "bk103" -OR
$item.id -eq "bk105" -OR
$item.id -eq "bk108" -OR
$item.id -eq "bk109"){
Write-Host "Found book called " $ $item.id
$elementCopy = $xmlContent.catalog.book[$index].Clone()
$elementCopy.AppendChild($newSubNode)
$xmlContent.catalog.RemoveChild($item)
$xmlContent.catalog.InsertBefore($elementCopy,$xmlContent.catalog.book[$index])
}
$index++
}
$xmlContent.Save('C:\PowershellPractice\books-edited.xml')
As mentioned in the comments, you cannot modify the collection while you're currently iterating over it.
Since you only really need to modify the elements in the collections themselves, I would recommend just doing that (instead of cloning the book node and re-attaching it):
foreach ($item in $collection){
Write-Host "In foreach, index is " $index
if ($item.id -eq "bk103" -OR
$item.id -eq "bk105" -OR
$item.id -eq "bk108" -OR
$item.id -eq "bk109"){
# Add subnode to matching $item
[void]$item.AppendChild($newSubNode)
}
}
If you ever find yourself in a situation where you cannot modify the element in place, use two loops - one to find the interesting elements, and another one to replace them by iterating over the resulting subset:
# collect matching results
$foundbooks = foreach ($item in $collection){
Write-Host "In foreach, index is " $index
if ($item.id -eq "bk103" -OR
$item.id -eq "bk105" -OR
$item.id -eq "bk108" -OR
$item.id -eq "bk109"){
# return matching $item
$item
}
}
# now modify based on initial results
foreach($book in $foundbooks){
Write-Host "Found book called " $ $book.id
$elementCopy = $book.Clone()
[void]$elementCopy.AppendChild($newSubNode)
[void]$xmlContent.catalog.InsertBefore($elementCopy,$book)
[void]$xmlContent.catalog.RemoveChild($book)
}

-match several $vars in if statement

I have this, which doesn't work:
$var1 = "6.0.6001"
$var2 = "6.1.7001"
$var3 = "6.2.8074"
$var4 = "6.3.8074"
if($var1 -match "6.1.?" -or "6.2.?" -or "6.3.?") {
write-host "1"
}else{
write-host "2"
}
No matter what, 1 is returned.
What should this really look like?
Thanks.
Run this code:
if("6.2.?" -or "6.3.?") {
write-host "1"
}else{
write-host "2"
}
It'll also return 1 no matter what.
Change your condition to:
if($var1 -match "6.1.?" -or $var1 -match "6.2.?" -or $var1 -match "6.3.?")
Or even better:
if($var1 -match "6.[1-3].?")
Just to elaborate on Adam 's answer. You If statement was not working as intended since PowerShell only saw three conditions you didnt intend
if($var1 -match "6.1.?" -or "6.2.?" -or "6.3.?")
if(($var1 -match "6.1.?") -or ("6.2.?" -or "6.3.?"))
The two lines above function the same. To break down the second operation ("6.2.?" -or "6.3.?") just a little more: A non-zero length string converted to boolean will always be $true. Comparing two non-zero length string with -or will always return $true. The first clause ($var1 -match "6.1.?") in your example is $false. If($true -or $false) is essentially what your If statement boils down to which, again, would always return $true. Adams answer show how to get the logic you are looking for
if($var1 -match "6.1.?" -or $var1 -match "6.2.?" -or $var1 -match "6.3.?")
if(($var1 -match "6.1.?") -or ($var1 -match "6.2.?") -or ($var1 -match "6.3.?"))
Both the above statements are the same. The second one helps understand the logic. For more information see about_Logical_Operators. You only need brackets when the logic is not acting the way you want it to.