I have a hash table from AD containing usernames and service accounts. I'm trying to find all the service accounts. All usernames are uniquely identified by their first letters such as 'r' and service accounts with 's'. For example: r398831 and s882443. Here's an example of the array I have:
$null = $usrArr.Add([pscustomobject] #{
sName = $user.name
sGivenName = $user.GivenName
sSurname = $user.Surname
sEnabled = $user.Enabled
})
$lastSeen = $usrArr | select * | Where {$_.sEnabled -eq $true}
$lastSeen = $lastSeen | Sort-Object -Unique -Property sName
I've tried using -contains and -match but it doesn't seem to pull back what I'm after:
$svcAcc = $lastSeen | where-object {$_.sName -like "s"}
Do I have to perform a for each or have I just messed up my Syntax on this?
I'm thinking maybe I need to sort-object but there has to be a more simple way to pull off something so basic. Appreciate any advice on it.
Thanks in advance :)
Best,
Pete
The -like operator matches the wildcard pattern on the RHS against the entire LHS, so -like "s" would only ever match values that are in full identical to literal s.
To instead match inputs that start with literal s, use wildcard pattern s*:
$svcAcc = $lastSeen | where-object { $_.sName -like 's*' }
As an aside: Your use of select * seems redundant; similarly, -eq $true is not strictly necessary, so assigning to $lastSeen can be simplified to:
$lastSeen = $usrArr | Where-Object sEnabled | Sort-Object -Unique -Property sName
Note that Where-Object sEnabled is short for Where-Object { $_.sEnabled }, which is a syntax simplification introduced in PSv3, called a comparison statement.
You can use -match like you were trying to do but just turn your query into a regular expression using the caret anchor character.
$usrArr | Where-Object { $_.sName -match "^s" }
Related
I am working on a Powershell script (with a GUI) to help my colleagues easier find redundant and disabled AD accounts.
Here is a little preview...
$props = "Name, Enabled, PasswordExpired, Company,passwordNeverExpires, Office"
$propsAsArray = $props -split ',\s*'
Get-ADUser -filter * -properties $propsAsArray | where {$_.Enabled -eq $true} | where {$_.PasswordNeverExpires -eq $false}| where {$_.passwordexpired -eq $false} | Select-Object $propsAsArray | Export-csv -Path "C:\report.csv"
This all works fine and outputs a CSV report.
The snag though is how to assign all the possible combinations and permutations of AD account status to a variable and later substitute in the variable into the Get-ADUser cmdlet (depending on which radio button the user clicks in the GUI).
I've tried all can think of but only ever get back the error Expressions are only allowed as the first element of a pipeline.
I'm sure that $accountStatus = "where {$_.Enabled -eq $true} | where {$_.PasswordNeverExpires -eq $false}" (or subtle variants) are not how it is done.
I'm relatively new to Powershell and am eager to get experience. Thanks, William.
Note: This answer addresses the question as asked, using a generalized Where-Object-based solution based on script blocks ({ ... }), but in the case at hand a string-based solution based on Get-ADUser's -Filter parameter, which efficiently filters at the source, as shown in the second command in Thomas' answer, is preferable.
Store an array of script blocks ({ ... }) representing the conditionals in a variable, and use an array of indices to select which conditionals to apply situationally, based on the user's GUI selections:
# All individual conditions to test, expressed as an array of script blocks.
# Note: No need for `-eq $true` to test Boolean properties, and
# `-eq $false` is better expressed via the `-not` operator.
$blocks =
{ $_.Enabled },
{ -not $_.PasswordNeverExpires },
{ $_.PasswordExpired }
# Select the subset of conditions to apply using AND logic, using
# an array of indices, based on the GUI selections.
$indices = 0..2 # e.g., all 3 conditions (same as: 0, 1, 2)
Get-ADUser -Filter * -properties $propsAsArray | Where-Object {
# The following is equivalent to combining the conditionals of interest
# with -and (AND logic):
foreach ($block in $blocks[$indices]) { # Loop over selected conditionals
if (-not (& $block)) { return $false } # Test, and return $false instantly if it fails.
}
$true # Getting here means that all conditions were met.
}
Note how each block is executed via &, the call operator.
You can condense your multiple Where-Object calls by concatenating each condition with and:
Get-ADUser -Filter * -Properties $propsAsArray | Where-Object {(($_.Enabled -eq $true) -and ($_.PasswordNeverExpires -eq $false)) -and ($_.passwordexpired -eq $false)} | Select-Object $propsAsArray | Export-csv -Path "C:\report.csv"
But as Olaf already pointed out in the comments, it is even better to already use the -Filter parameter of Get-ADUser. There, you can use a similar combination of your conditions:
Get-ADUser -Filter {((Enabled -eq $true) -and (PasswordNeverExpires -eq $true)) -and (passwordexpired -eq $false)} -Properties $propsAsArray | Select-Object $propsAsArray | Export-csv -Path "C:\report.csv"
I want to get a list of all ad computers excluding the servers that are in a text file. Here's my code:-
$excludedServers = (Get-Content
"C:\Users\testuser\Documents\RdpDisconnectedSessions\ExcludedServers.txt").name #| Sort-Object
Get-ADComputer -Filter * | Where { $_.DistinguishedName -like "*Computers*" -and $_.DistinguishedName -notmatch $excludedServers } | Select-Object Name
Any advise please ?
First, Get-Content is not going to bring back objects so the .name portion is not going to work. If it's just a list of computernames, then simply change it to.
$excludedServers = Get-Content "C:\Users\testuser\Documents\RdpDisconnectedSessions\ExcludedServers.txt"
If it's a CSV with a name column, then you can do it a few ways. Sticking with the format you had this would work
$excludedServers = (Import-Csv "C:\Users\testuser\Documents\RdpDisconnectedSessions\ExcludedServers.txt").name
Now that you have your list of names, you can filter like this (assuming it is actually the names of the servers and not their distinguished name)
Get-ADComputer -Filter * | Where { $_.DistinguishedName -like "*Computers*" -and $_.name -notin $excludedServers } | Select-Object Name
I am trying to get a list of computers from AD excluding some computers which are not in use anymore.
Here is my code:
$ServerList = Get-ADComputer -Filter * | Where {
$_.DistinguishedName -like "*Computers*" -and $_.DistinguishedName -notlike #("*server1*","*Server2*")
} | Select-Object Name
I am trying to put the computers which I was to exclude in an array instead of using
-and $_.DistinguishedName -notlike "*serverIwantToExclude*"
Can you guys give me any idea on how I could amend it ?
-notlike does not support collections on the right-hand side (RHS). A similarly intended approach is to use -notmatch, which is a regex string:
$ServerList = Get-ADComputer -Filter * |
Where { $_.DistinguishedName -like "*Computers*" -and $_.DistinguishedName -notmatch 'server1|Server2'} |
Select-Object Name
If you want your server names in a list first, you can create a regex string from that.
$serverdown = 'server1','server2'
$regex = $serverdown -join '|'
$ServerList = Get-ADComputer -Filter * |
Where { $_.DistinguishedName -like "*Computers*" -and $_.DistinguishedName -notmatch $regex} |
Select-Object Name
If you do not anchor your regex strings, it looks for the regex match anywhere within the target string (effectively having surrounding wildcards). | is an alternation (an effective OR).
There are other operators that support collections like -contains, -in, -notin, and -notcontains. However, they must match exactly and cannot use wildcards.
you can use the -inotin operator if you know the full name of the servers you wish to exclude, meaning you can not use a wildcard or regex with the -inotin operator. If this is the case and you know the names I recommend using the Name property of the ADComputer object.
[string[]] $excludedServers = 'server1','server2'
$ServerList = Get-ADComputer -Filter * | Where {
$_.DistinguishedName -like "*Computers*" -and $_.Name -inotin $excludedServers
} | Select-Object Name
another way to exclude them is to add all those computers to a group and exclude it
#Provide DN of the group
$groupname = "CN=groupname,OU=Groups,DC=Domain,DC=COM"
get-adcomputer -filter {(OperatingSystem -like "Windows")} | -properties * | ?{([string]$_.memberof -notmatch $groupname)} | select name, lastlogondate
I have a file containing a list of email addresses:
e.g.
personB#placeB.com
nameA#location2.com
nameB#location2.com
I want to order them, but by the domain, and then by the local, and then output the ordered list. so I end up with:
nameA#location2.com
nameB#location2.com
personB#placeB.com
I can use sort-object to order them, but this is by the whole string.
So far I have got this far:
SELECT-STRING -path path_To_My_File |
FOREACH{
$a = $_.split"(#)"
$local = $a[0]
$domain = $a[1]
}
but can't see how to incorporate the sort-object.
Similar to Matt's answer, but will give you the sorted email addresses as a list:
$addresses = #()
Get-Content .\path\to\file | % {
$n = $($_ -split '#')[0]
$d = $($_ -split '#')[1]
$o = New-Object PSObject -property #{name=$n;domain=$d;email=$_}
$addresses += ,$o
}
$addresses | sort domain,name | select -ExpandProperty email
The $addresses array can be sorted by the object you created on the fly in the Foreach-Object or % loop, and then you can just select the email property to give you the sorted list
Sort-Object obviously needs an object to sort on. So we can create an object like this. ( There are other ways as well). Also we dont need to use select-string since you are applying no patterns
Get-Content -path "path_To_My_File" |
Select-Object #{Name="Local";Expression={($_.split("#"))[0]}},#{Name="Domain";Expression={($_.split("#"))[1]}} |
Sort-Object domain,local
Create properties using Select-Object for Local and Domain using the same split that you had. The select on its own would output this:
Local Domain
----- ------
personB placeB.com
nameA location2.com
nameB location2.com
Then we can sort to get this:
Local Domain
----- ------
nameA location2.com
nameB location2.com
personB placeB.com
If you need the full address as well we can shorten the code and add that variable.
Get-Content -path "path_To_My_File" |
Select #{N="Address";E={$_}},
#{N="Local";E={($_.split("#"))[0]}},
#{N="Domain";E={($_.split("#"))[1]}} |
Sort domain,local
My goal is to get a list of AD computer objects not following naming conventions with the whenCreated property. I am trying to export out a HTML file using the following PowerShell function:
function get_bad_names
{
$result = Get-ADComputer -searchbase $OU
-Filter {Name -notlike 'name1' -and
Name -notlike 'name2' -and
Name -notlike 'name3'}
-Properties whenCreated | sort whenCreated
$result | ConvertTo-Html | Out-File $dir\badnames.html
}
get_bad_names
Is there a more elegant way of structuring the Name filter lines? We have over 40 lines of different "names." In other words, over 40 lines in my script say Name -notlike 'name#' -and
Ideally I'd like for the code to read from a text file and export out the same results.
FYI: this function works but it is not ideally written. Any suggestions would be appreciated.
If you'd like to filter the query, construct an LDAP filter:
# Define the names to exclude
$NamesToExclude = "Name1","Name2","Name3"
# Turn them into LDAP clauses in the form of "(!(Name=Name1))"
$LDAPClauses = $NamesToExclude |ForEach-Object {
'(!(Name={0}))' -f $_
}
# Create a single query filter string with all the clauses from above
$LDAPFilter = "(&$LDAPClauses)"
$result = Get-ADComputer -LDAPFilter $LDAPFilter -SearchBase $OU -Properties whenCreated |Sort-Object whenCreated
You could filter with a regular expression using Select-Object but then you should get all computers in the OU with the -filter *, and that might strain your network. I would do the filtering and if more properties are needed, run Get-Computer again only for the matches:
Get-ADComputer -searchbase $OU -Filter * |
? { $_.name -match "name\d{1,2}" } | # Match if string "name" is followed one or two digits
Get-ADComputer -property * # Get all properties for the computers if needed
Is there a more elegant way of structuring the Name filter lines?
Not exactly. You have a fairly limited set of operators when you use the -Filter parameter of commands in the ActiveDirectory module because it has to translate them all to LDAP filters.
However, you can do something like this:
$ExcludedPatterns = 'Name1', 'Name2', 'Name3'
$ComputerFilter = foreach ($Pattern in $ExcludedPatterns) { "(Name -notlike '$Pattern')" }
$ComputerFilter = $ComputerFilter -join ' -and '
Get-ADComputer -Filter $ComputerFilter
Now you can maintain a list of name patterns.
Keep in mind that the -Filter property is not a ScriptBlock on the ActiveDirectory commands. They're all Strings and in spite of the documentation for these commands they should be written that way. Writing them as ScriptBlocks will eventually cause you problems with variable expansion.
Compare:
$ComputerName = hostname
Get-ADComputer -Filter "Name -eq '$ComputerName'"
Get-ADComputer -Filter { Name -eq '$ComputerName' }
Thank you to everyone for their feedback. I was impressed how fast the responses were.
I was able to achieve my goal using regular expressions reading from a text file.
My code now looks like this:
[regex]$Expression = Get-Content $dir\regex.txt
function get_bad_names
{
$result = Get-ADComputer -searchbase $OU
-Filter {Enabled -eq $true}
-Properties whenCreated | Where-Object {$_.Name -notmatch $Expression}| sort whenCreated
$result | ConvertTo-Html | Out-File $dir\badnames.html
}
get_bad_names