I have have become handy enough in Powershell lately to write a script (with a GUI) that can quickly find expired AD accounts etc and export the data to CSV.
It prompts the user for to enter the values (e.g Name, LastLoginDate) sought and then makes an array by splitting the string (using commas) - this is then assigned to a variable and later substituted back in (when the Get-ADUser cmdlet) gets called.
However I get nothing back.
I have isolated the problem by assigning some properties by hand (and the end of the script) and all works OK.
$manualProps = "Name, Enabled"
$manualpropsAsArray = $manualProps -split ',\s*'
$exampleAccountStatus = {(($_.Enabled -eq $false -or $_.Enabled -eq $true ) -and ($_.PasswordNeverExpires -eq $false -or $_.PasswordNeverExpires -eq $true)) -and ($_.passwordexpired -eq $false -or $_.passwordexpired -eq $true)}
Get-ADUser -SearchBase $OU -filter $filter -Properties $manualpropsAsArray | Where-Object $accountStatus | Select-Object $manualpropsAsArray | Sort-Object $properySortBy #sortOrder | ft
I can get my user-inputted properties to return data but only if I set the -Properties parameter to *
For example,
Get-ADUser -SearchBase $OU -filter $filter -Properties * | Where-Object $accountStatus | Select-Object $guiPropsAsArray | Sort-Object $properySortBy #sortOrder | ft
It takes much longer to get the data however.
I would much rather set things so that the value for -Properties match those of Select-Object. I was able to do this easily after following the advise I received over at Bug when submitting properties to Get-ADUser? Saving to CSV File (Powershell)
I"m sure it's something small I'm doing wrong and is easy to fix. Thanks.
It looks like the 'Where-Object' statement is causing the problem. If I remove that (and its pipe) then everything is OK - I can use identical values for '-Properties' and 'Select-Object'. It's a mystery why i am having this problem when the arrays I am testing are effectively identical!! Not a dealbreaker, but I'll set the '-Properties' parameter to the wildstar. Reports will take a little longer, but life goes on.
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" }
I'm writing a simple PowerShell script to get all the users from a specific OU in Active Directory (AD) and all the folders from a directory, then compare each username with each foldername to verify that the user has a folder there, and do something.
The script shown below works fine, but when it matches the strings, I want it to break and return to the outer For-EachObject, switching the username. (It's not necessary to verify the rest of the list if it already found one), but the break is breaking the entire script, it just ends.
$PerfilFolder = Get-ChildItem $FileServerPath | Sort
Get-ADUser -SearchBase $SB -Filter * | Sort SamAccountName | % {
$UserName = $_.SamAccountName
$PerfilFolder | % {
if($UserName -match (($_.Name).Split(".")[0])){
#Match!
#Do something
break
}
}
}
I already tried return and continue, but all of them have the same behavior. What should I do?
Also general improvements tips are welcome :)
Use a Do{} While() loop:
$PerfilFolder = Get-ChildItem $FileServerPath | Sort
Get-ADUser -SearchBase $SB -Filter * | Sort SamAccountName | % {
$UserName = $_.SamAccountName
$NoMatch = $true
Do{$PerfilFolder | % {
if ($UserName -match (($_.Name).Split(".")[0])){
#Match!
#Do something
$NoMatch = $false
}
} While($NoMatch)
}
I am pretty sure that'll end your loop where you want it to. That answers the question, but it really doesn't solve your problem if that's what you're really trying to do. I think including a Where statement would serve you better:
Do{$PerfilFolder | Where{$UserName -match (($_.Name).Split(".")[0])} |
%{
#Match!
#Do something
$NoMatch = $false
}
} While($NoMatch)
That may well make the Do{} While() unnecessary.
Check smeltplate's answer to this question. It seems your break perfectly exits the foreach loop, but not the pipeline.
I usually avoid this by setting up two sets of objects, and then filtering one on the property of the other.
$PerfilFolder = Get-ChildItem $FileServerPath | Sort
foreach ($user in Get-ADUser -SearchBase $SB -Filter * | Sort SamAccountName){
$matchingFolder = $PerfilFolder | ? {$user.SamAccountName -match (($_.Name).Split(".")[0])
if (($matchingFolder | Measure-Object).Count -gt 0){
#do something to $matchingFolder | Select -First 1
}
else {
# Do something else
}
}
I would like to find all directories at the top level from the location of the script that are stored in subversion.
In C# it would be something like this
Directory.GetDirectories(".")
.Where(d=>Directories.GetDirectories(d)
.Any(x => x == "_svn" || ".svn"));
I'm having a bit of difficulty finding the equivalent of "Any()" in PowerShell, and I don't want to go through the awkwardness of calling the extension method.
So far I've got this:
Get-ChildItem | ? {$_.PsIsContainer} | Get-ChildItem -force | ? {$_.PsIsContainer -and $_.Name -eq "_svn" -or $_.Name -eq ".svn"
This finds me the svn directories themselves, but not their parent directories - which is what I want. Bonus points if you can tell me why adding
| Select-Object {$_.Directory}
to the end of that command list simply displays a sequence of blank lines.
To answer the immediate question with a PowerShell v3+ solution:
(Get-ChildItem -Force -Directory -Recurse -Depth 2 -Include '_svn', '.svn').Parent.FullName
-Directory limits the matches to directories, -Recurse -Depth 2 recurses up to three levels (children, grandchildren, and great-grandchildren), Include allows specifying multiple (filename-component) filters, and .Parent.FullName returns the full path of the parent dirs. of the matching dirs., using member-access enumeration (implicitly accessing a collection's elements' properties).
As for the bonus question: select-object {$_.Directory} does not work,
because the \[System.IO.DirectoryInfo\] instances returned by Get-ChildItem have no .Directory property, only a .Parent property; Select-Object -ExpandProperty Parent should have been used.
In addition to only returning the property value of interest, -ExpandProperty also enforces the existence of the property. By contrast, Select-Object {$_.Directory} returns a custom object with a property literally named $_.Directory, whose value is $null, given that the input objects have no .Directory property; these $null values print as empty lines in the console.
As for the more general question about a PowerShell equivalent to LINQ's .Any() method, which indicates [with a Boolean result] whether a given enumerable (collection) has any elements at all / any elements satisfying a given condition:
Natively, PowerShell offers no such equivalent, but the behavior can be emulated:
Using the PowerShell v4+ intrinsic.Where() method:
Caveat: This requires collecting the entire input collection in memory first, which can be problematic with large collections and/or long-running input commands.
(...).Where({ $_ ... }, 'First').Count -gt 0
... represents the command of interest, and $_ ... the condition of interest, applied to each input object, where PowerShell's automatic $_ variable refers to the input object at hand; argument 'First' ensures that the method returns once the first match has been found.
For example:
# See if there's at least one value > 1
PS> (1, 2, 3).Where({ $_ -gt 1 }, 'First').Count -gt 0
True
Using the pipeline: Testing whether a command produced at least one output object [matching a condition]:
The advantage of a pipeline-based solution is that it can act on a command's output one by one, as it is being produced, without needing to collect the entire output in memory first.
If you don't mind that all objects are enumerated - even if you only care if there is at least one - use Paolo Tedesco's helpful extension to JaredPar's helpful answer.
The down-side of this approach is that you always have to wait for a (potentially long-running) command to finish producing all output objects, even though - logically - the determination whether there are any output objects can be made as soon as the first object is received.
If you want to exit the pipeline as soon as one [matching] object has been encountered, you have two options:
[Ad-hoc: Easy to understand, but cumbersome to implement]
Enclose the pipeline in a dummy loop and use break to break out of the pipeline and that loop (... represents the command whose output to test, and $_ ... match the condition):
# Exit on first input object.
[bool] $haveAny = do { ... | % { $true; break } } while ($false)
# Exit on first input object that matches a condition.
[bool] $haveAny = do { ... | % { if ($_ ...) { $true ; break } } } while ($false)
[Use a PowerShell v3+ self-contained utility function that is nontrivial to implement]
See the implementation of function Test-Any below.
It can be added to scripts or, for use in interactive sessions, to your $PROFILE file.
PowerShell v3+: Optimized utility function Test-Any
The function is nontrivial, because as of PowerShell (Core) v7.2.x, there is no direct way to exit a pipeline prematurely, so a workaround based on .NET reflection and a private type is currently necessary.
If you agree that there should be such a feature, take part in the conversation in GitHub issue #3821.
#requires -version 3
Function Test-Any {
[CmdletBinding()]
param(
[ScriptBlock] $Filter,
[Parameter(ValueFromPipeline = $true)] $InputObject
)
process {
if (-not $Filter -or (Foreach-Object $Filter -InputObject $InputObject)) {
$true # Signal that at least 1 [matching] object was found
# Now that we have our result, stop the upstream commands in the
# pipeline so that they don't create more, no-longer-needed input.
(Add-Type -Passthru -TypeDefinition '
using System.Management.Automation;
namespace net.same2u.PowerShell {
public static class CustomPipelineStopper {
public static void Stop(Cmdlet cmdlet) {
throw (System.Exception) System.Activator.CreateInstance(typeof(Cmdlet).Assembly.GetType("System.Management.Automation.StopUpstreamCommandsException"), cmdlet);
}
}
}')::Stop($PSCmdlet)
}
}
end { $false }
}
if (-not $Filter -or (Foreach-Object $Filter -InputObject $InputObject)) defaults to true if $Filter wasn't specified, and otherwise evaluates the filter (script block) with the object at hand.
The use of ForEach-Object to evaluate the filter script block ensures that $_ binds to the current pipeline object in all scenarios, as demonstrated in PetSerAl's helpful answer here.
The (Add-Type ... statement uses an ad-hoc type created with C# code that uses reflection to throw the same exception that Select-Object -First (PowerShell v3+) uses internally to stop the pipeline, namely [System.Management.Automation.StopUpstreamCommandsException], which as of PowerShell v5 is still a private type.
Background here:
http://powershell.com/cs/blogs/tobias/archive/2010/01/01/cancelling-a-pipeline.aspx
A big thank-you to PetSerAl for contributing this code in the comments.
Examples:
PS> #() | Test-Any false
PS> Get-EventLog Application | Test-Any # should return *right away* true
PS> 1, 2, 3 | Test-Any { $_ -gt 1 } # see if any object is > 1 true
Background information
JaredPar's helpful answer and Paolo Tedesco's helpful extension fall short in one respect: they don't exit the pipeline once a match has been found, which can be an important optimization.
Sadly, even as of PowerShell v5, there is no direct way to exit a pipeline prematurely.
If you agree that there should be such a feature, take part in the conversation in GitHub issue #3821.
A naïve optimization of JaredPar's answer actually shortens the code:
# IMPORTANT: ONLY EVER USE THIS INSIDE A PURPOSE-BUILT DUMMY LOOP (see below)
function Test-Any() { process { $true; break } end { $false } }
The process block is only entered if there's at least one element in the pipeline.
Small caveat: By design, if there's no pipeline at all, the process block is still entered, with $_ set to $null, so calling Test-Any outside of a pipeline unhelpfully returns $true. To distinguish between between $null | Test-Any and Test-Any, check $MyInvocation.ExpectingInput, which is $true only in a pipeline: Thanks, PetSerAl
function Test-Any() { process { $MyInvocation.ExpectingInput; break } end { $false } }
$true, written to the output stream, signals that at least one object was found.
break then terminates the pipeline and thus prevents superfluous processing of additional objects. HOWEVER, IT ALSO EXITS ANY ENCLOSING LOOP - break is NOT designed to exit a PIPELINEThanks, PetSerAl
.
If there were a command to exit the pipeline, this is where it would go.
Note that return would simply move on to the next input object.
Since the process block unconditionally executes break, the end block is only reached if the process block was never entered, which implies an empty pipeline, so $false is written to the output stream to signal that.
Unfortunately there is no equivalent in PowerShell. I wrote a blog post about this with a suggestion for a general purpose Test-Any function / filter.
function Test-Any() {
begin {
$any = $false
}
process {
$any = $true
}
end {
$any
}
}
Blog post: Is there anything in that pipeline?
A variation on #JaredPar's answer, to incorporate the test in the Test-Any filter:
function Test-Any {
[CmdletBinding()]
param($EvaluateCondition,
[Parameter(ValueFromPipeline = $true)] $ObjectToTest)
begin {
$any = $false
}
process {
if (-not $any -and (& $EvaluateCondition $ObjectToTest)) {
$any = $true
}
}
end {
$any
}
}
Now I can write "any" tests like
> 1..4 | Test-Any { $_ -gt 3 }
True
> 1..4 | Test-Any { $_ -gt 5 }
False
You can use the original LINQ Any:
[Linq.Enumerable]::Any($list)
It's actually quite simple - just select first $true (formatted for clarity):
[bool] ($source `
| foreach { [bool] (<predicate>) } `
| where { $_ } `
| select -first 1)
Alternative way:
($source `
| where { <predicate> } `
| foreach { $true } `
| select -first 1)
My approach now was:
gci -r -force `
| ? { $_.PSIsContainer -and $_.Name -match "^[._]svn$" } `
| select Parent -Unique
The reason why
select-object {$_.Directory}
doesn't return anything useful is that there is no such property on a DirectoryInfo object. At least not in my PowerShell.
To elaborate on your own answer: PowerShell can treat most non-empty collections as $true, so you can simply do:
$svnDirs = gci `
| ? {$_.PsIsContainer} `
| ? {
gci $_.Name -Force `
| ? {$_.PSIsContainer -and ($_.Name -eq "_svn" -or $_.Name -eq ".svn") }
}
I ended up doing it with a count:
$directoryContainsSvn = {
(Get-ChildItem $_.Name -force | ? {$_.PsIsContainer -and $_.Name -eq "_svn" -or $_.Name -eq ".svn"} | Measure-Object).Count -eq 1
}
$svnDirs = Get-ChildItem | ? {$_.PsIsContainer} | ? $directoryContainsSvn
You can tighten this up a bit:
gci -fo | ?{$_.PSIsContainer -and `
(gci $_ -r -fo | ?{$_.PSIsContainer -and $_ -match '[_.]svn$'})}
Note - passing $__.Name to the nested gci is unnecessary. Passing it $_ is sufficent.
I recommend the following solution:
<#
.SYNOPSIS
Tests if any object in an array matches the expression
.EXAMPLE
#( "red", "blue" ) | Where-Any { $_ -eq "blue" } | Write-Host
#>
function Where-Any
{
[CmdletBinding()]
param(
[Parameter(Mandatory = $True)]
$Condition,
[Parameter(Mandatory = $True, ValueFromPipeline = $True)]
$Item
)
begin {
[bool]$isMatch = $False
}
process {
if (& $Condition $Item) {
[bool]$isMatch = $true
}
}
end {
Write-Output $isMatch
}
}
# optional alias
New-Alias any Where-Any
This is the best method that I found so far (does not iterate over all elements if already found a true, and does not break the pipeline):
From LINQ Any() equivalent in PowerShell
It’s possible to use a built-in $input variable that contains the whole pipeline in a scope of function.
So, the desired code could look like the following:
function Test-Any([scriptBlock] $scriptBlock = {$true}, [scriptBlock] $debugOut = $null)
{
if ($debugOut)
{
Write-Host(“{0} | % {{{1}}}” -f $input, $scriptBlock)
}
$_ret = $false;
$_input = ($input -as [Collections.IEnumerator])
if ($_input)
{
while ($_input.MoveNext())
{
$_ = $_input.Current;
Write-Host $_
if ($debugOut)
{
Write-Host(“Tested: [{0}]” -f (&$debugOut))
}
if (&$scriptBlock)
{
if ($debugOut)
{
Write-Host(“Matched: [{0}]” -f (&$debugOut))
}
$_ret = $true
break
}
}
}
$_ret
}
I think that the best answer here is the function proposed by #JaredPar, but if you like one-liners as I do I'd like to propose following Any one-liner:
# Any item is greater than 5
$result = $arr | %{ $match = $false }{ $match = $match -or $_ -gt 5 }{ $match }
%{ $match = $false }{ $match = $match -or YOUR_CONDITION }{ $match } checks that at least one item match condition.
One note - usually the Any operation evaluates the array until it finds the first item matching the condition. But this code evaluates all items.
Just to mention, you can easily adjust it to become All one-liner:
# All items are greater than zero
$result = $arr | %{ $match = $false }{ $match = $match -and $_ -gt 0 }{ $match }
%{ $match = $false }{ $match = $match -and YOUR_CONDITION }{ $match } checks that all items match condition.
Notice, that to check Any you need -or and to check All you need -and.
I took a more linq-style approach.
I know this question is probably super old. I used this to accomplish my needs:
PS> $searchData = "unn"
PS> $StringData = #("unn", "dew", "tri", "peswar", "pymp")
PS> $delegate = [Func[string,bool]]{ param($d); return $d -eq $searchData }
PS> [Linq.Enumerable]::Any([string[]]$StringData, $delegate)
Taken from here:
https://www.red-gate.com/simple-talk/dotnet/net-framework/high-performance-powershell-linq/#post-71022-_Toc482783751
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