How to use a $_. variable in a herestring? - powershell

I can't seem to figure out how to use a variable in a herestring, and for the variable to be expanded at a later time in a piped command. I have experimented with single ' and double " quotes, and escape ` characters.
I am trying to use the herestring for a list (e.g. like an array) of Exchange groups, and a corresponding list of conditions to apply to those groups. Here is a simplified example which fails to use the $Conditions variable correctly (it does not expand the $_.customattribute2 variable):
# List of groups and conditions (tab delimitered)
$records = #"
Group1 {$_.customattribute2 -Like '*Sales*'}
Group2 {$_.customattribute2 -Like '*Marketing*' -OR $_.customattribute2 -Eq 'CEO'}
"#
# Loop through each line in $records and find mailboxes that match $conditions
foreach ($record in $records -split "`n") {
($DGroup,$Conditions) = $record -split "`t"
$MailboxList = Get-Mailbox -ResultSize Unlimited
$MailboxList | where $Conditions
}

No, no that's not going to work. The whole good bit about PowerShell is not having to make everything a string and then drag it to the moon and back trying to get the important stuff back out of the string. {$_.x -eq "y"} is a scriptblock. It's a thing by itself, you don't need to put it in a string.
#Array of arrays. Pairs of groups and conditions
[Array]$records = #(
('Group1', {$_.customattribute2 -Like '*Sales*'}),
('Group2', {$_.customattribute2 -Like '*Marketing*' -OR $_.customattribute2 -Eq 'CEO'})
)
#Loop through each line in $records and find mailboxes that match $conditions
foreach ($pair in $records) {
$DGroup, $Condition = $pair
$MailboxList = Get-Mailbox -ResultSize Unlimited
$MailboxList | where $Condition
}

TessellatingHeckler's explanation is right. However, if you insist on herestring, it's possible as well. See the following example (created merely for demonstration):
$records=#'
Group1 {$_.Extension -Like "*x*" -and $_.Name -Like "m*"}
Group2 {$_.Extension -Like "*p*" -and $_.Name -Like "t*"}
'#
foreach ($record in $records -split "`n") {
($DGroup,$Conditions) = $record -split "`t"
"`r`n{0}={1}" -f $DGroup,$Conditions
(Get-ChildItem |
Where-Object { . (Invoke-Expression $Conditions) }).Name
}
Output:
PS D:\PShell> D:\PShell\SO\47108347.ps1
Group1={$_.Extension -Like "*x*" -and $_.Name -Like "m*"}
myfiles.txt
Group2={$_.Extension -Like "*p*" -and $_.Name -Like "t*"}
Tabulka stupnic.pdf
ttc.ps1
PS D:\PShell>
Caution: some text/code editors could convert tabulators to space sequences!

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

Powershell syntax to use where condition with a foreach loop

I'm new to powershell and have been practicing to get better so would greatly appreciate all your help.
I have a txt file with just a list of servers that I need to add an account to. However, I need to exclude the server names that does not contain "-" in the servername.
This is what I have right now which obviously doesn't work and I keep getting errors.
$servers = Get-Content "$PSScriptRoot\SQLServers.txt"
foreach ($server in $servers| Where $server -contains "*-*" )
For a wildcard string comparison, you'll want the -like operator, and then refer to the current pipeline item being tested by Where-Object with $_:
foreach ($server in $servers |Where { $_ -like "*-*" }) {
# work with each matching $server here
}
-like can also be used to filter a collection of strings directly, so in your case you could do:
foreach ($server in #($servers) -like "*-*") {
# work with each matching $server here
}
If your file have only one column you can use import csv and directly set the name of one and uniq colum, like this :
Import-Csv "$PSScriptRoot\SQLServers.txt" -Header server | where server -Like "*-*" | foreach{
#user serveur name here
$_.Server
}
Otherwise you can directly filter on $_ variable like this :
Get-Content "$PSScriptRoot\SQLServers.txt" | where {$_ -like "*-*"} | foreach{
#user serveur name here
$_
}

Comparing two CSV's for similar strings

I am currently trying to compare two CSV's. doc1 has 5000 strings in it, and doc2 has 100 strings in it. Every string in doc2 ends in "admin". I want to compare doc1 & doc2 and find all the strings that match up to the point it reaches "admin".
so as an example:
a string in doc1
a string in doc1 admin
it will output both of these to a new CSV
results = foreach ($OU in $OUs) {
Get-ADGroup -SearchBase $OU -Filter * -Properties CanonicalName,Created,Description,ManagedBy,Member,MemberOf,Members,ObjectGUID,whenChanged |
? {($_.ManagedBy -like $null) -and ($_.Description -notlike "*owner*") -and ($_.CanonicalName -notlike "*admin")}
}
$results | select CanonicalName,Description,ManagedBy,Member,MemberOf,Members,ObjectGUID,Created,whenChanged |
Export-Csv .\doc1.csv -NoTypeInformation
$results0 = foreach ($OU in $OUs) {
Get-ADGroup -SearchBase $OU -Filter * -Properties CanonicalName,Created,Description,ManagedBy,Member,MemberOf,Members,ObjectGUID,whenChanged |
? {($_.ManagedBy -like $null) -and ($_.Description -notlike "*owner*") -and ($_.CanonicalName -like "*admin")}
}
$results0 | select CanonicalName,Description,ManagedBy,Member,MemberOf,Members,ObjectGUID,Created,whenChanged |
Export-Csv .\doc2.csv -NoTypeInformation
$csvPath1 = ".\doc1.csv"
$csvPath2 = '.\doc2.csv'
$propertyToCompare = 'CanonicalName'
$csv1 = Import-Csv -Path $csvPath1
$csv2 = Import-Csv -Path $csvPath2
$duplicates = Compare-Object $csv1 $csv2 -Property $propertyToCompare -ExcludeDifferent -PassThru | Select-Object -ExpandProperty $propertyToCompare
$csv1 | Where-Object { $_.$propertyToCompare -in $duplicates } | Export-Csv -Path .\AdminsAndNotAdminsInOneFile.csv -NoTypeInformation
With Compare-Object I don't know how to make it ignore the last few characters in the string in doc2. Is there some way for me to modify the string?
You can take advantage of the fact that Compare-Object supports calculated properties as comparison properties (here, only a script block ({...}) is passed, which is the same as passing
#{ Expression = { ... } }):
Compare-Object $csv1 $csv2 -ExcludeDifferent -IncludeEqual -PassThru -Property {
$_.$propertyToCompare -replace ' admin$'
} | Select-Object -ExpandProperty $propertyToCompare
Note that -PassThru ensures that the input objects are passed through, which in the case of objects that compare as equal means that the LHS ($csv1) object is passed through.
Expression $_.$propertyToCompare -replace ' admin$', which is evaluated for each input object, uses regex admin$ to replace ' admin' at the end ($) of the value of property $propertyToCompare; if that string isn't present, the value is used as-is.
In effect, the objects are compared as if the trailing ' admin' weren't present.

Can I use splatting within a Where-Object using PowerShell?

What I want to find out if I can do, is splatting in a Where-Object clause or something similar. I know you can do this with parameters already.
I am trying to filter out multiple values from one property using -notlike.
I have looked at https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_splatting?view=powershell-5.1 but nothing is mentioned in here.
An example of what I'm currently trying to do:
$allUsers | Where-Object {($_.UserPrincipalName -notlike "Health*") -and ($_.UserPrincipalName -notlike "admin*")}
I am trying to do it this way as there are lots of accounts I want to exclude that contain the the word "admin" in their UPN. Unfortunately there is a long list of what I need to exclude, as I am running through a cleanup process.
I have been suggested to use an array of exclusions and then try using -notcontains or -notin but this has not worked for me, I'm assuming because I need it to be wildcard friendly.
I think it is a lot easier to use the regex -notmatch operator:
# create a regular expression string by combining the keywords with the OR '|' character
$exclude = ('test','admin','health' | ForEach-Object { [regex]::Escape($_) }) -join '|'
# get all users except the users that have any of the keywords in their UserPrincipalName
Get-ADUser -Filter * | Where-Object { $_.UserPrincipalName -notmatch $exclude }
You can do the following to build a dynamic filter by just comma-separating your exclusion strings, which effectively creates an array of strings ($Exclusions).
$Exclusions = 'Health*','admin*'
$FilterArray = $Exclusions | Foreach-Object {
"UserPrincipalName -notlike '$_'"
}
$Filter = #{'Filter' = "{0}" -f ($FilterArray -join " -and ")}
Get-ADUser #Filter
-notin works fine for me, maybe supply a bit more of your data in your question to see what you;re trying to filter out? Is it the wildcards causing an issue?
$listofexclusions = #("userid1","userid2")
Get-ADUser -Filter * | Where-Object { $_.SamAccountName -notin $listofexclusions } | Select Name,SamAccountName | ft
Edit:
If you need wildcards, there are some previous threads about this here
If you're using something like Get-ADUser you can do what other answers have suggested and let the cmdlet filter the results for you using its built in functionality.
However, if you just want to apply a bunch of -like operations to a collection of objects you can write your own Test-LikeAny function as follows:
function Test-LikeAny
{
param( [string] $value, [string[]] $patterns )
foreach( $pattern in $patterns )
{
if( $value -like $pattern )
{
return $true
}
}
return $false
}
and then you can use it like this:
$values = #(
(new-object pscustomobject #{ "Property1" = "...value1..." }),
(new-object pscustomobject #{ "Property1" = "...value2..." }),
(new-object pscustomobject #{ "Property1" = "...value3..." })
)
$patterns = #( "*value1*", "*value2*" )
$values | Where-Object { -not (Test-LikeAny $_.Property1 $patterns) }
which gives:
Name Value
---- -----
Property1 ...value3...

Using Quest Powershell to gather info and report

I need some help writing a script as i am struggling to understand the logic.
I basically have a list of user ids that i need to check to see if they have two certain AD groups. If they have, these need to be outputted into a csv and highlighted.
Can anyone help to get me started? I need to use the Quest Powershell cmdlets
Here is the code
$textFileContents = Get-Content C:\temp\powershell\users.txt
$results = #()
foreach($username in $textFileContents){
$groups = get-qaduser $username |select -expand memberof
if ($groups -match "grpuip1" -and $groups -match "group2"){
echo $group
}
}
check this to begin :
"user1","user2" | foreach {
$groups = get-qaduser $_ |select -expand memberof
if ($groups -match "GROUP1" -and $groups -match "GROUP2"){
echo $_
}
}
I'd use the cmdlet Get-QADMemberOf instead of Get-QADUser. There's nothing wrong with what you're doing, but it's retrieving more information than you need.
Try this to start with:
$textFileContents = Get-Content C:\temp\powershell\users.txt
# Rather than initializing the array, and adding new elements,
# just output each element of the loop to the pipeline, and
# assign the results of the whole pipeline to the variable.
# This is *much* faster than adding to an array
$results = $textFileContents | ForEach-Object {
$userGroups = Get-QADMemberOf $_
if ($userGroups -contains "group1" -and $userGroups -contains "group2") {
New-Object -TypeName PSObject -Property #{"UserName" = $_; "Groups" = ($userGroups -join ",");}
}
}
$results | ConvertTo-Csv -NoTypeInformation | Set-Content C:\Filename.txt