Nesting ForEach within If statement (PowerShell) - 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.

Related

Powershell if condition returning no results when some exist

The code block below executes without the CSV being generated suggesting it has no return. However the conditions given are definitely valid for at least one object.
A Mail Enabled Security group in O365 has the "SecurityEnabled" property set to true, the "MailEnabled" property set to true and the property "GroupTypes" is an empty array? string? "{}" whatever the curly brackets are supposed to represent but they're empty.
$Groups = Get-MgGroup -Property "Members" -All
foreach($group in $Groups){
if ( (($group.SecurityEnabled) -and ($group.MailEnabled)) -and ($group.GroupTypes -ne 'Unified')){
$members = Get-MgGroupMember -GroupId $group.Id
[PSCustomObject]#{
'Display Name' = $group.DisplayName
'Members' = $members.AdditionalProperties.displayName -join ";"
} | Export-Csv -path "$OutputFolder\MailEnabledSecurityGroups.csv" -Append -NoTypeInformation
}
continue
}
The GroupTypes property from the MicrosoftGraphGroup instance is an array, in this case you want to use containment operators, more specifically -notin or -notcontains, the condition would look like:
# with `-notcontains`
if ($group.SecurityEnabled -and $group.MailEnabled -and $group.GroupTypes -notcontains 'Unified') {
# with `-notin`
if ($group.SecurityEnabled -and $group.MailEnabled -and 'Unified' -notin $group.GroupTypes) {
As for why it was not working before, -ne and -eq can act as a filter when the LHS (left hand side) of the comparison is an array and because $group.GroupTypes was an empty array, the comparison returned null and the if condition evaluated to false because of this.
$obj = [pscustomobject]#{
GroupTypes = #()
}
$obj.GroupTypes -ne 'Unified' # => null
$obj = [pscustomobject]#{
GroupTypes = #('something else')
}
$obj.GroupTypes -ne 'Unified' # => something else
As aside, it's probably a better idea to export the output all at once instead of appending to the CSV file on each loop iteration (Disk I/O is expensive and will slow down your script a lot!):
Get-MgGroup -Property "Members" -All | ForEach-Object {
if ($_.SecurityEnabled -and $_.MailEnabled -and 'Unified' -notin $_.GroupTypes) {
$members = Get-MgGroupMember -GroupId $_.Id
[PSCustomObject]#{
'Display Name' = $_.DisplayName
'Members' = $members.AdditionalProperties.displayName -join ";"
}
}
} | Export-Csv -path "$OutputFolder\MailEnabledSecurityGroups.csv" -NoTypeInformation
Filtering on Azure side may increase the speed of your code and also reduce the amount of conditions being evaluated on client side:
Get-MgGroup -Property "Members" -Filter "securityEnabled eq true and mailenabled eq true" | ForEach-Object {
if ('Unified' -notin $_.GroupTypes) {
# code here
}
}

Where object as variable

After reading many of the posted solutions here, none fully applies mine.
This works (returns expected number of instancesNames based on criteria):
$response.result | Where-Object {$_.targetType -eq "webserver" -and ($_.agentHostName -eq "ServerA" -or $_.agentHostName -eq "ServerB")} | select-Object "instanceName"
However, since n number of servers may be found, I created a loop to dynamically create this query:
[System.Text.StringBuilder]$clause = " {`$_.targetType -eq ""webserver"" -and ("
$i = 1;
foreach ($server in $serversArray) {
if ( $i -eq $serversArray.Count ) {
$clause.Append("`$_.agentHostName -eq ""${server}"")}")
} else {
$clause.Append( "`$_.agentHostName -eq ""${server}"" -or ")
}
$i++
}
$clause.Append(" | select-Object ""instanceName""")
$filter = [scriptblock]::Create($clause)
$instances = $response.result | where-object $filter
debugging:
the $clause variable contains:
{$_.targetType -eq "webserver" -and ($_.agentHostName -eq "serverA" -or $_.agentHostName -eq "serverB")} | select-Object "instanceName"
However, it returns all instanceNames (not filtered) instead of the ones that meet the criteria. What am I doing wrong here?
The -in operator would simplify your code. For example:
$response.result |
Where-Object {($_.targetType -eq 'webserver') -and ($_.agentHostName -in $serversArray)} |
Select-Object 'instanceName'

Where clause not filtering properly

Using the script below I am trying to filter out applications that don't have a $requirement like Windows 10. When I run this I am still getting returned results with application requirements containing windows 10.
| Where { $_ -notlike 'All_x64_Windows_10_and_higher_Clients' };
Any idea what I am doing wrong here? Possible issue with the line above?
$warningpreference = "SilentlyContinue"
Get-Content C:\temp\Applications.txt | foreach-object {
$app = Get-CMApplication -Name "$_";
[XML]$appXML =$app.SDMPackageXML;
$Requirement = $appXML.AppMgmtDigest.DeploymentType.Requirements.Rule.OperatingSystemExpression.Operands.RuleExpression.RuleID | Where { $_ -notlike 'All_x64_Windows_10_and_higher_Clients' };
If ($Requirement -ne $null -or $Requirement.length -gt 0) {
Write-output "Application Name: $_ | Requirement: $Requirement "
}
}
The -Like Operator is used for WildCard searches in PowerShell. So you need an * somewhere in your filter.
Try this:
| Where { $_ -notlike "*All_x64_Windows_10_and_higher_Clients*" };

Conditional PowerShell operators

I am trying to get a list where the title does not have temp or temporary or *contractor or contractor*.
This code is working, meaning I get a list which does not have temporary records.
$pTitle = $profile["Title"]
if ($pTitle -ne "Temporary")
However the following code does not work when I add -or and -notlike for the wildcard.
$pTitle = $profile["Title"]
if ($pTitle -ne "Temporary" -or $pTitle -notlike "Temporary" -or $pTitle -notlike "contractor" -or $pTitle -notlike "Temp")
You actually want -and here. Your expression will only evaluate to false if all of the words are found currently.
$List = #(1,2,3)
If I ask you to return all the items in the list that are:
not equal to 1
or
not equal to 2
or
not equal to 3
the answer will be the whole list
$List | where { ($_ -ne 1) -or ($_ -ne 2) -or ($_ -ne 3) }
1
2
3
You are getting your logical operators mixed up.
Look at replacing -or with -and

Use -notlike to filter out multiple strings in PowerShell

I'm trying to read the event log for a security audit for all users except two, but is it possible to do that with the -notlike operator?
It's something like that:
Get-EventLog -LogName Security | where {$_.UserName -notlike #("*user1","*user2")}
I have it working for a single user, like:
Get-EventLog -LogName Security | where {$_.UserName -notlike "*user1"}
V2 at least contains the -username parameter that takes a string[], and supports globbing.
V1 you want to expand your test like so:
Get-EventLog Security | ?{$_.UserName -notlike "user1" -and $_.UserName -notlike "*user2"}
Or you could use "-notcontains" on the inline array but this would only work if you can do exact matching on the usernames.
... | ?{#("user1","user2") -notcontains $_.username}
I think Peter has the right idea. I would use a regular expression for this along with the -notmatch operator.
Get-EventLog Security | ?{$_.Username -notmatch '^user1$|^.*user$'}
In order to support "matches any of ..." scenarios, I created a function that is pretty easy to read. My version has a lot more to it because its a PowerShell 2.0 cmdlet but the version I'm pasting below should work in 1.0 and has no frills.
You call it like so:
Get-Process | Where-Match Company -Like '*VMWare*','*Microsoft*'
Get-Process | Where-Match Company -Regex '^Microsoft.*'
filter Where-Match($Selector,[String[]]$Like,[String[]]$Regex) {
if ($Selector -is [String]) { $Value = $_.$Selector }
elseif ($Selector -is [ScriptBlock]) { $Value = &$Selector }
else { throw 'Selector must be a ScriptBlock or property name' }
if ($Like.Length) {
foreach ($Pattern in $Like) {
if ($Value -like $Pattern) { return $_ }
}
}
if ($Regex.Length) {
foreach ($Pattern in $Regex) {
if ($Value -match $Pattern) { return $_ }
}
}
}
filter Where-NotMatch($Selector,[String[]]$Like,[String[]]$Regex) {
if ($Selector -is [String]) { $Value = $_.$Selector }
elseif ($Selector -is [ScriptBlock]) { $Value = &$Selector }
else { throw 'Selector must be a ScriptBlock or property name' }
if ($Like.Length) {
foreach ($Pattern in $Like) {
if ($Value -like $Pattern) { return }
}
}
if ($Regex.Length) {
foreach ($Pattern in $Regex) {
if ($Value -match $Pattern) { return }
}
}
return $_
}
don't use -notLike, -notMatch with Regular-Expression works in one line:
Get-MailBoxPermission -id newsletter | ? {$_.User -NotMatch "NT-AUTORIT.*|.*-Admins|.*Administrators|.*Manage.*"}
Easiest way I find for multiple searches is to pipe them all (probably heavier CPU use) but for your example user:
Get-EventLog -LogName Security | where {$_.UserName -notlike "*user1"} | where {$_.UserName -notlike "*user2"}
Scenario:
List all computers beginning with XX1 but not names where 4th character is L or P
Get-ADComputer -Filter {(name -like "XX1*")} | Select Name | Where {($_.name -notlike "XX1L*" -and $_.name -notlike "XX1P*")}
You can also count them by enclosing the above script in parens and adding a .count method like so:
(Get-ADComputer -Filter {(name -like "XX1*")} | Select Name | Where {($_.name -notlike "XX1L*" -and $_.name -notlike "XX1P*")}).count
Using select-string:
Get-EventLog Security | where {$_.UserName | select-string -notmatch user1,user2}
$listOfUsernames = #("user1", "user2", "etc", "and so on")
Get-EventLog -LogName Security |
where { $_.Username -notmatch (
'(' + [string]::Join(')|(', $listOfUsernames) + ')') }
It's a little crazy I'll grant you, and it fails to escape the usernames (in the unprobable case a username uses a Regex escape character like '\' or '(' ), but it works.
As "slipsec" mentioned above, use -notcontains if possible.