Where object as variable - powershell

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'

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 to match from Left to Right on all prerequires?

See the below example code.
I have several objects I need to match on several prerequires.
Lets say I want my Where-Object to not match when the NameRegex is one of the 2 values, the name not equals DisplayName1 and the AccessRights not match.
This means that when I run the below code I want to get Object $User1 back.
This is not happening what so ever.
Should I do this the other way around and then match them in some sort of hashtable?
Has anyone figured this out for a single Where-Object?
$User1 = [PSCustomObject]#{
Name = 'Name1'
DisplayName = 'DisplayName1'
AccessRights = 'AccessRights1'
}
$User2 = [PSCustomObject]#{
Name = 'Name2'
DisplayName = 'DisplayName2'
AccessRights = 'AccessRights2'
}
$User3 = [PSCustomObject]#{
Name = 'Name3'
DisplayName = 'DisplayName3'
AccessRights = 'AccessRights3'
}
$Test = $User1,$User2,$User3
$NameRegex = '^Name2$|^Name3'
$ACRegex = '^AccessRights2$|^AccessRights3$'
$Test | Where-Object { $_.Name -notmatch $NameRegex -and $_.DisplayName -ne 'DisplayName1' -and $_.AccessRights -notmatch $ACRegex }
And some others I've tried:
$Test | Where-Object { ($_.Name -notmatch $NameRegex -and $_.DisplayName -ne 'DisplayName1' -and $_.AccessRights -notmatch $ACRegex) }
$Test | Where-Object { ((($_.Name -notmatch $NameRegex) -and $_.DisplayName -ne 'DisplayName1') -and $_.AccessRights -notmatch $ACRegex) }
$Test | Where-Object {$_ -notmatch ((($_.Name -notmatch $NameRegex) -and $_.DisplayName -ne 'DisplayName1') -and $_.AccessRights -notmatch $ACRegex) }

Powershell script to log out users without specific process

I'm trying to write a script that will log out users which don't have specific process running in their session.
I guess the workflow should look like:
Get usernames of all the users logged in, get list of all the processes runnning on the workstation, check if X process is running for Y user, if not, then log user Y out.
So far i managed to create object with all the users currently logged in:
((quser) -replace '^>', '') -replace '\s{2,}', ',' | ConvertFrom-Csv
And list of all the processes running with usernames:
get-process -IncludeUserName
I believe foreach with if inside should do the job, but I can't quite get my head around it. I would appreciate any help.
Thanks!
edit:
$process = get-process -IncludeUserName|Where-Object{
($_.ProcessName -Like "processIneed1*") -or
($_.ProcessName -Like "processIneed2*") -or
($_.ProcessName -Like "processIneed") -or
($_.ProcessName -like 'processIneed3*') -and
($_.username -notlike '*userIwantToSkip1*') -and
($_.username -notlike '*userIwantToSkip2*')
}
$users = Get-RDUserSession | Where-Object -Property UserName -NE 'administrator'
$ActiveUsers = $users.username
foreach ($ProcUser in $process.username){
foreach ($ActiveUser in $ActiveUsers){
if ($ProcUser -like "*$ActiveUser*"){
$ActiveUsers = $ActiveUsers -notlike "*$ActiveUser*"
}
}
}
in $activeusers we are left with inactive users.
I will most likely change username to session ID and check it this way.
Next step will be logging off disconnected users.
$process = get-process -IncludeUserName|Where-Object{
($_.ProcessName -Like "processIneed1*") -or
($_.ProcessName -Like "processIneed2*") -or
($_.ProcessName -Like "processIneed") -or
($_.ProcessName -like 'processIneed3*') -and
($_.username -notlike '*userIwantToSkip1*') -and
($_.username -notlike '*userIwantToSkip2*')
}
$users = Get-RDUserSession | Where-Object -Property UserName -NE 'administrator'
$ActiveUsers = $users.username
foreach ($ProcUser in $process.username){
foreach ($ActiveUser in $ActiveUsers){
if ($ProcUser -like "*$ActiveUser*"){
$ActiveUsers = $ActiveUsers -notlike "*$ActiveUser*"
}
}
}
foreach ($inactiveusers in $ActiveUsers){
logoff $inactiveuser
}

Acting on every 100 elements of an array

I am using Get-ADComputer to return all the computers in Active Directory.
We intend to call the DELL Warranty web-service API and retrieve warranty information for each computer.
The DELL Warranty web-service takes a pipe separated list of service tags, maximum 100, and we have 1483 computers ...
So I need to act on every 100 elements of the array populated by my code, but am having trouble.
clear-host
$Counter=0
$ServiceTag::Empty
$ServiceTagsMasterList=#()
(Get-ADComputer `
-properties OperatingSystem -filter {(operatingsystem -like "*Windows 7*")} `
|Where-Object {$_.name -like "*-*"}`
|Where-Object {$_.name -NotLike "V7-*"})`
| Select -Exp Name|ForEach-Object{
$ServiceTag = $_.substring(3) #remove w7- l7- etc....
if ($ServiceTag.Length -eq "7"){
#write-host "service tag:" $ServiceTag
$ServiceTagsMasterList += $ServiceTag + ','
$Counter+=1
}
}
write-host $ServiceTagsMasterList.length, $Counter, $Counter % 100
write-host $ServiceTagsMasterList
I am having trouble getting the Mod right - I expect $Counter % 100 to return 14 with remainder 83, but the output is 1483 % 100
I need to be able to call the DELL Warranty api for each block of 100 service tags, so I can either do it like that or perhaps split the array?
What's the best approach?
gpunktschmitz already mentioned whats wrong in your code. This is how your batch could look like:
$computer = Get-ADComputer -properties OperatingSystem -filter {(operatingsystem -like "*Windows 7*")} |
Where-Object {$_.name -like "*-*"} |
Where-Object {$_.name -NotLike "V7-*"} |
Select-Object -Expand
for ($i = 0; $i -lt $computer.length; $i+=100)
{
$computer | Select-Object -Skip $i -First 100 | ForEach-Object {
# your code here
}
}
Here an example with your 1483 computers:
$computer = 1 .. 1483 | ForEach-Object { "DellPc$($_)" }
for ($i = 0; $i -lt $computer.length; $i+=100)
{
$computer | Select-Object -Skip $i -First 100 | ForEach-Object {
Write-Host "batch: $i computer: $_"
}
}
you have to write it like this to be calculated:
write-host $ServiceTagsMasterList.length, $Counter, $($Counter % 100)
and % is the remainder not modulo
http://www.madwithpowershell.com/2016/11/powershell-modulus-operator-is.html

Filter a few computers before exporting from pipeline

I need to filter out 4 machines prior to exporting to a csv file. I have no clue how to filter them out. I tried the IF clause but this produced nothing. Please help.
$old = (Get-Date).AddDays(-90) # Modify the -90 to match your threshold
$oldComputers = Get-ADComputer -SearchBase "OU=Workstations,DC=Corporate,DC=Local" -SearchScope 2 -Filter { PasswordLastSet -le $old } -Properties *
$oldComputers
if ($_.name -notlike "1919DD" -or $_.name -notlike "1919SMAHESHWARE" -or $_.name -notlike "1919IETEST" -or $_.name -notlike "1920BPASCERITB") {
Export-Csv c:\temp\Over90DaysMachines.csv -NoTypeInformation -Force -Append
}
For one thing, to be able to use the current object variable ($_) you need a pipeline context. Simply putting an if statement after echoing a variable doesn't automagically feed the echoed value(s) into the if statement. You need to change this:
$oldComputers
if ($_.Name -notlike "1919DD" -or ...) {
Export-Csv c:\temp\Over90DaysMachines.csv -NoTypeInformation -Force -Append
}
into something like this:
$oldComputers | Where-Object {
$_.Name -notlike "1919DD" -or ...
} | Export-Csv c:\temp\Over90DaysMachines.csv -NoType -Force
However, even with that change your filter won't work correctly, because you connected the -notlike clauses via -or when you should have used -and. You obviously meant to process objects only if their name doesn't match any of the given values. But for your logical expression to evaluate to $false the name would have to match all of the reference value at the same time. Which clearly isn't possible, thus your expression always evaluates to $true.
Example:
Assume that you have a variable $v that should not be equal to either A, B, or C. Applying your logic, the expression would look somewhat like this in PowerShell:
($v -notlike 'A') -or ($v -notlike 'B') -or ($v -notlike 'C')
If $v takes for instance the value A that expression becomes
('A' -notlike 'A') -or ('A' -notlike 'B') -or ('A' -notlike 'C')
⇔ ($false) -or ($true) -or ($true)
⇔ $true
To check if a give value equals neither of the reference values you need to connect the clauses via -and:
('A' -notlike 'A') -and ('A' -notlike 'B') -and ('A' -notlike 'C')
⇔ ($false) -and ($true) -and ($true)
⇔ $false
$oldComputers | Where-Object {
$_.Name -notlike "1919DD" -and
$_.Name -notlike "1919SMAHESHWARE" -and
$_.Name -notlike "1919IETEST" -and
$_.Name -notlike "1920BPASCERITB"
} | Export-Csv c:\temp\Over90DaysMachines.csv -NoType -Force
Note BTW, that the -notlike operator behaves exactly like the -ne operator when the reference string doesn't contain wildcard characters. If you're not doing fuzzy matches anyway you could simplify your expression by checking if the given name is (not) found in an array of names instead of doing multiple checks for (in)equality:
$excludes = '1919DD', '1919SMAHESHWARE', '1919IETEST', '1920BPASCERITB'
$oldComputers | Where-Object {
$excludes -notcontains $_.Name
} | Export-Csv c:\temp\Over90DaysMachines.csv -NoType -Force
Another option would be a regular expression (non-)match:
$oldComputers | Where-Object {
$_.Name -notmatch '^1919DD|1919SMAHESHWARE|1919IETEST|1920BPASCERITB$'
} | Export-Csv c:\temp\Over90DaysMachines.csv -NoType -Force
I'm guessing the code in the OP is a fragment from a larger script. Presumably it is the body or part of the body of a ForEach-Object. (If not then $_ doesn't make sense in this context). However a ForEach-Object isn't necessary. You can filter out the unwanted computers as follows:
$old = (Get-Date).AddDays(-90) # Modify the -90 to match your threshold
$oldComputers = Get-ADComputer -SearchBase "OU=Workstations,DC=Corporate,DC=Local" -SearchScope 2 -Filter { PasswordLastSet -le $old } -Properties *
$oldComputers | Where-Object {
$_.name -notin "1919SMAHESHWARE","1919IETEST", "1920BPASCERITB"
} | Export-Csv c:\temp\Over90DaysMachines.csv -NoTypeInformation -Force -Append
This assumes that $oldComputers is an array of object where each object has a property name and the value of name is a string like "server1", "server2", etc. The script in the OP outputs $oldComputers so verify it looks like a set of objects, with a name property consisting of a string where the servers to be excluded are spelled exactly as listed in the OP.
Please try below code
$old = (Get-Date).AddDays(-90) # Modify the -90 to match your threshold
$oldComputers = Get-ADComputer -searchbase "OU=Workstations,DC=Corporate,DC=Local" -SearchScope 2 -Filter { PasswordLastSet -le $old } -Properties *
$oldComputers = $oldComputers | where {$_.name -notlike "1919DD"
` -or $_.name -notlike "1919SMAHESHWARE"
` -or $_.name -notlike "1919IETEST"
` -or $_.name -notlike "1920BPASCERITB"}
Export-Csv c:\temp\Over90DaysMachines.csv -NoTypeInformation -force -append