Why is line continuation needed inside a scriptblock? - powershell

Why does PowerShell need a line continuation backtick after the first condition of the Where-Object scriptblock? It should be clear that the scriptblock has not yet ended as there has been no closing curly brace }.
Why does this work:
Get-Process | Where-Object {
($_.Responding -ne $true) `
-or ($_.ProcessName -like 'W*')
}
and this doesn't:
Get-Process | Where-Object {
($_.Responding -ne $true)
-or ($_.ProcessName -like 'W*')
}
This also fails.
Get-Process | Where-Object {
(
($_.Responding -ne $true)
-or ($_.ProcessName -like 'W*')
)
}

Your first example works, because you tell PowerShell that the statement is continued in the next line (by escaping the linebreak).
The other two examples fail, because the expression on the first line is a complete expression in and by itself, whereas the expression on the second line is not. PowerShell first evaluates ($_.Responding -ne $true), then tries to evaluate -or ($_.ProcessName -like 'W*'), which is an invalid statement and thus fails.
To avoid this you need to make it clear to the interpreter that the statement on the first line is continued in the next line. That can be done either by escaping the linebreak like you do in your first example:
Get-Process | Where-Object {
($_.Responding -ne $true) `
-or ($_.ProcessName -like 'W*')
}
or by constructing your statement in a way that allows the interpreter to detect that there is more to come, e.g. by putting the operator at the end of the first line instead of at the beginning of the second line:
Get-Process | Where-Object {
($_.Responding -ne $true) -or
($_.ProcessName -like 'W*')
}
The curly braces can't be used to detect the end of the statement, because it's valid to put multiple statements in a Where-Object scriptblock.

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.

Powershell: -or in a while loop

What is wrong with my formatting. the code runs when only one condition is in there. When I add the -or with the second condition to check it appears to not check either.
while(($TempLog.id -ne "18") -or ($TempLog.id -ne "32"))
{
$TempLog = Get-WinEvent -computername F9P8NW2-5300 -LogName 'Microsoft-Windows-RemoteAssistance/Operational'-MaxEvents 1
}
enter code here

Powershell where-object complex logic

I am having trouble coming up with a way to do complex logic using the where-object in PowerShell
I have the following code, but separating the and from the ors is not working as intended.
Get-ADComputer -Filter * | Where-Object {($_.Enabled) -and
(($_.DistinguishedName -Contains "world") -or
($_.DistinguishedName -Contains "foo")) -and
(($_.SID -Contains "bar") -or
($_.SID-Contains "something else"))}
If I do this in c# I get results, but in powershell I do not.
Any thoughts on how to get around this?
TIA
This is how you would perform your query using AD PS Module:
Get-ADComputer -LDAPFilter "(!userAccountControl:1.2.840.113556.1.4.803:=2)"|where-object{
$_.DistinguishedName -match 'World|Foo' -and $_.SID -match 'bar|something else'
}
-LDAPFilter "(!userAccountControl:1.2.840.113556.1.4.803:=2)" = Enabled Computer Object
-match allows the use of regex, you can use the pipeline as OR.
-contains is the operator you would use to lookup an item on an array. Example:
PS /> #(
'apple'
'banana'
'pineapple'
) -contains 'apple'
True
In addition, as Dave Wyatt pointed out in a nice post on powershell.org a while ago, you might want to avoid where-object whenever possible since it's the slowest way to filter collections. It's only advantages are the low memory consumption and pipeline streaming.
Here are a few examples of faster efficient code:
#Example 1:
$computers=Get-ADComputer -LDAPFilter "(!userAccountControl:1.2.840.113556.1.4.803:=2)"
$collection=[system.collections.generic.list[Microsoft.ActiveDirectory.Management.ADComputer]]::new()
foreach($computer in $computers)
{
if($computer.DistinguishedName -match 'World|Foo' -and $computer.SID -match 'bar|something else')
{
$collection.add($computer)
}
}
#Example 2:
filter myFilter{
if($_.DistinguishedName -match 'World|Foo' -and $_.SID -match 'bar|something else')
{
$_
}
}
$computers=Get-ADComputer -LDAPFilter "(!userAccountControl:1.2.840.113556.1.4.803:=2)"|myFilter
#Example 3
$computers=(Get-ADComputer -LDAPFilter "(!userAccountControl:1.2.840.113556.1.4.803:=2)").where({
$_.DistinguishedName -match 'World|Foo' -and $_.SID -match 'bar|something else'
})
There is plenty on information on the different ways of filtering Collections on PowerShell and their pros / cons on Google.
this is not an Answer - so please let me know when you have read it so that i can delete it.
this is your code with more informative indentation, with the needless extra parens removed, and with spaces around the operators.
Get-ADComputer -Filter * |
Where-Object {
$_.Enabled -and
($_.DistinguishedName -Contains "world" -or
$_.DistinguishedName -Contains "foo") -and
($_.SID -Contains "bar" -or
$_.SID -Contains "something else")
}
note that the -contains operator is NOT for strings ... it is for membership in a collection. if you want to test against strings, use -match, or .Contains(), or -like with wildcards.

Data changing between functions

After looking at what I could find, a few culprits that i've looked at would be the return being unrolled into a simple String, or other data being in the pipe.
My problem is cmdlets run against the data in the 1st function, do not work on the data by the time it reaches the 2nd function.
function Get-FileInput() {
$fileData = Import-Csv $filePath -Delimiter "`t"
if ($fileData | Get-Member | Where-Object {$_.name -eq "DistinguishedName" -or $_.name -eq "dn" -or $_.name -eq "name"})
{
Write-Host -ForegroundColor Green "Confirmed.
Processing"$fileData.Count"objects."
$invalidFilePath = $false
$fileData
}
$data = Get-FileInput
Write-Host $data
}
$data = Get-FileInput
Set-MultipleComputersEnabledStatus -data $data
Everything works fine up until this point. Before it gets passed into the 2nd function, it can be used with Format-Table and and Get-Member and everything appears perfectly fine. Inside Set-MultipleComputersEnabledStatus however, I can't perform the exact same checks on the $data object that I had done inside Get-FileInput
It is looking for a property named "DistinguishedName" which it finds in the 1st function, but won't recognize in the data by the time it gets to the 2nd function.
if ($filterType -eq "DistinguishedName" -and $data | Get-Member | Where-Object {$_.Name -eq 'DistinguishedName'})
{
Write-Host -ForegroundColor Green "Valid filters. Processing list."
}
I assume this means that after being passed in as an argument to the 2nd function, PowerShell changed the data for me somehow, but I can't find anything documenting what it did, or how to prevent it/work around it.
You need to use brackets around the embedded command part so that it is executed first and then the result can be evaluated:
if ($filterType -eq "DistinguishedName" -and ($data | Get-Member | Where-Object {$_.Name -eq 'DistinguishedName'})) { }

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