Matching entries in arrays powershell - arrays

I have two data sets one is a data table pulled back from a SQL date base and the other is a data set pulled back from Active directory
$HRusers = Invoke-SQL ## this calls a function to get data from SQL
$adusers = get-aduser -filter * -Properties surname,EmployeeID,DisplayName
I want to then match the two together so my code at the moment is
$HRusers | ForEach-Object {
foreach ($user in $aduser){
if ((($_.Surname -eq $user.surname) -And ($_.'First Name' -eq $user.Givenname)) -or (($_.Surname -eq $user.surname) -And ($_.'Known As' -eq $user.Givenname)))
{ do some stuff}
}
}
This does work but as the list for each is several 1000 long it is a lot of looping.
is there any way I can do a search rather than a loop. so the logic is more like
$HRusers | ForEach-Object {
find match in AD list where name matchs and then do some stuff.
}
EDIT
So I tried this
if (($adusers.surname -eq $_.surname) -And ($adusers.Givenname -eq $_.'First Name'))
{
write-host "Found" $adusers.surname
}
Which does give a true / false answer, but i need to extract the $aduser record that it giving the match.
Looking on technet
https://technet.microsoft.com/en-us/library/ee692798.aspx
"note that, with –like, you get back the actual values rather than a Boolean True or False"
So I expected
$users = ($adusers.Givenname -like ($_.'First Name') -and $adusers.surname -like ($_.'surname'))
write-host $users
to return the value but it still only returns true / false?

You can search the array with where-object:
foreach ($hruser in $hrusers) {
$adusers | where-object {$hruser.'First Name' -eq $_.givenname} | ForEach-Object {
'do some stuff'
}
}
Or use the fact that Invoke-SqlCmd returns DataTable which has Select method:
foreach ($aduser in $adusers){
$SelectedHrUsers = $hrusers.Select("surname='$($aduser.surname)'")
}
The former won't be much faster that nested arrays, but DataTable.Select might have good performance.

Related

Create a String array from an object array in powershell

Im getting the names of these computers and putting them into an array. Now what i want to do is to convert them into a string array to be able to check which policy they are on using a Get-ADComputer for loop or using a foreach loop (Can you recommend which one to use)
$global:arrComputers = #()
$computerStrings = Get-ADComputer -Filter 'SamAccountName -like "*Name*"' | Select -Expand Name
foreach ($line in $computerStrings)
{
$a = $line.ToString()
$b = $a.split()
$temp = #{}
$temp = New-Object object
$temp | Add-Member -MemberType "noteproperty" -Name Name -Value $b[0]
$global:arrComputers += $temp
}
$global:arrComputers
This is the command i want to run to check the policy they are under
Get-ADComputer "Name" -Properties MemberOf | %{if ($_.MemberOf -like "*POLICY_NAME*") {Write-Host "ON"} else {Write-Host "NOT ON"}}
I have tested both blocks of code and they are working the only problem im having is turning that array into a string array. I also tried the ToString() To be able to loop through it with the Get-ADComputer "Name"
"memberOf" property in objects returned by "Get-ADComputer" returns a list of strings containing Distinguished Name of each group this computer is a member of.
Therefore, I assume when you say "This is the command i want to run to check the policy they are under", you are referring to a group membership that a group policy is targeting right?
Below code then will do it:
$computers = #();
Get-ADComputer -Filter * -Properties Name,MemberOf | %{if ($_.MemberOf -like "*computer_group_name*") { $computers += $_.Name } }
Explanation:
First line, define an array $computers
Second line, query AD for computer object properties Name,MemberOf
then, $_.MemberOf contains group name in string, add Name property(string) to array of strings you defined on line 1

Comparing AD users with File Names

I'm trying to find files that are named after members of a specific AD group with a foreach loop using the code below. The script seems to have a problem which causes the loop to stop after the first exception. I think I need to throw the exception because there seems to be no default return value or error if no file for one of the group members is found.
$ErrorActionPreference = "Stop"
$BVAU = Get-ADGroupMember ADGroupName | Select-Object -Property Name
foreach($entry in $BVAU) {
trap [System.IO.DirectoryNotFoundException]{
Write-Host $_.Exception.Message
continue
}
}
if (-not (Get-ChildItem "\\samplepath" -Recurse | Where-Object FullName -like "*$entry*")) {
throw [System.IO.DirectoryNotFoundException] "$entry not found"
}
}
I only want to display the group members that don't have an equally named file. (A PDF form that legally qualifies the AD group membership)
The if statement belongs inside the loop, and the trap should be defined before the loop. However, you don't need the trap in the first place, because it's pointless to throw exceptions just for echoing a username. Simply echo the name right away and continue. Also, avoid running code multiple times if it doesn't need to.
$files = Get-ChildItem "\\samplepath" -Recurse |
Select-Object -Expand Basename -Unique
Get-ADGroupMember ADGroupName |
Select-Object -Expand Name |
Where-Object { $files -notcontains $_ } |
ForEach-Object { "$_ not found" }
The above is assuming that the usernames aren't just partial matches of the file basenames. Otherwise the Where-Object filter becomes slightly more complicated:
...
Where-Object { $n = $_; -not ($files | Where-Object {$_ -like '*$n*'}) } |
...

Filtering Search Results Against a CSV in Powershell

Im interested in some ideas on how one would approach coding a search of a filesystem for files that match any entries contained in a master CSV file. I have a function to search the filesystem, but filtering against the CSV is proving harder than I expect. I have a csv with headers in it for Name & IPaddr:
#create CSV object
$csv = import-csv filename.csv
#create filter object containing only Name column
$filter = $csv | select-object Name
#Now run the search function
SearchSubfolders | where {$_.name -match $filter} #returns no results
I guess my question is this: Can I filter against an array within a pipeline like this???
You need a pair of loops:
#create CSV object
$csv = import-csv filename.csv
#Now run the search function
#loop through the folders
foreach ($folder in (SearchSubfolders)) {
#check that folder against each item in the csv filter list
#this sets up the loop
foreach ($Filter in $csv.Name) {
#and this does the checking and outputs anything that is matched
If ($folder.name -match $Filter) { "$filter" }
}
}
Usually CSVs are 2-dimensional data structures, so you can't use them directly for filtering. You can convert the 2-dimensional array into a 1-dimensional array, though:
$filter = Import-Csv 'C:\path\to\some.csv' | % {
$_.PSObject.Properties | % { $_.Value }
}
If the CSV has just a single column, the "mangling" can be simplified to this (replace Name with the actual column name):
$filter = Import-Csv 'C:\path\to\some.csv' | % { $_.Name }
or this:
$filter = Import-Csv 'C:\path\to\some.csv' | select -Expand Name
Of course, if the CSV has just a single column, it would've been better to make it a flat list right away, so it could've been imported like this:
$filter = Get-Content 'C:\path\to\some.txt'
Either way, with the $filter prepared, you can apply it to your input data like this:
SearchSubFolders | ? { $filter -contains $_.Name } # ARRAY -contains VALUE
The -match operator won't work, because it compares a value (left operand) against a regular expression (right operand).
See Get-Help about_Comparison_Operators for more information.
Another option is to create a regex from the filename collection and use that to filter for all the filenames at once:
$filenames = import-csv filename.csv |
foreach { $_.name }
[regex]$filename_regex = ‘(?i)^(‘ + (($filenames | foreach {[regex]::escape($_)}) –join “|”) + ‘)$’
$SearchSubfolders |
where { $_.name -match $filename_regex }
You can use Compare-Object to do this pretty easily if you are matching the actual Names of the files to names in the list. An example:
$filter = import-csv files.csv
ls | Compare-Object -ReferenceObject $filter -IncludeEqual -ExcludeDifferent -Property Name
This will print the files in the current directory that match the any Name in files.csv. You could also print only the different ones by dropping -IncludeEqual and -ExcludeDifferent flags. If you need full regex matching you will have to loop through each regex in the csv and see if it is a match.
Here's any alternate solution that uses regular expression filters. Note that we will create and cache the regex instances so we don't have to rely on the runtime's internal cache (which defaults to 15 items). First we have a useful helper function, Test-Any that will loop through an array of items and stop if any of them satisfies a criteria:
function Test-Any() {
param(
[Parameter(Mandatory=$True,ValueFromPipeline=$True)]
[object[]]$Items,
[Parameter(Mandatory=$True,Position=2)]
[ScriptBlock]$Predicate)
begin {
$any = $false
}
process {
foreach($item in $items) {
if ($predicate.Invoke($item)) {
$any = $true
break
}
}
}
end { $any }
}
With this, the implementation is relatively simple:
$filters = import-csv files.csv | foreach { [regex]$_.Name }
ls -recurse | where { $name = $_.Name; $filters | Test-Any { $_.IsMatch($name) } }
I ended up using a 'loop within a loop' construct to get this done after much trial and error:
#the SearchSubFolders function was amended to force results in a variable, SearchResults
$SearchResults2 = #()
foreach ($result in $SearchResults){
foreach ($line in $filter){
if ($result -match $line){
$SearchResults2 += $result
}
}
}
This works great after collapsing my CSV file down to a text-based array containing only the necessary column data from that CSV. Much thanks to Ansgar Wiechers for assisting me with that particular thing!!!
All of you presented viable solutions, some more complex than I cared for, nevertheless if I could mark multiple answers as correct, I would!! I chose the correct answer based on not only correctness but also simplicity.....

How to remove item from an array in PowerShell?

I'm using Powershell 1.0 to remove an item from an Array. Here's my script:
param (
[string]$backupDir = $(throw "Please supply the directory to housekeep"),
[int]$maxAge = 30,
[switch]$NoRecurse,
[switch]$KeepDirectories
)
$days = $maxAge * -1
# do not delete directories with these values in the path
$exclusionList = Get-Content HousekeepBackupsExclusions.txt
if ($NoRecurse)
{
$filesToDelete = Get-ChildItem $backupDir | where-object {$_.PsIsContainer -ne $true -and $_.LastWriteTime -lt $(Get-Date).AddDays($days)}
}
else
{
$filesToDelete = Get-ChildItem $backupDir -Recurse | where-object {$_.PsIsContainer -ne $true -and $_.LastWriteTime -lt $(Get-Date).AddDays($days)}
}
foreach ($file in $filesToDelete)
{
# remove the file from the deleted list if it's an exclusion
foreach ($exclusion in $exclusionList)
{
"Testing to see if $exclusion is in " + $file.FullName
if ($file.FullName.Contains($exclusion)) {$filesToDelete.Remove($file); "FOUND ONE!"}
}
}
I realize that Get-ChildItem in powershell returns a System.Array type. I therefore get this error when trying to use the Remove method:
Method invocation failed because [System.Object[]] doesn't contain a method named 'Remove'.
What I'd like to do is convert $filesToDelete to an ArrayList and then remove items using ArrayList.Remove. Is this a good idea or should I directly manipulate $filesToDelete as a System.Array in some way?
Thanks
The best way to do this is to use Where-Object to perform the filtering and use the returned array.
You can also use #splat to pass multiple parameters to a command (new in V2). If you cannot upgrade (and you should if at all possible, then just collect the output from Get-ChildItems (only repeating that one CmdLet) and do all the filtering in common code).
The working part of your script becomes:
$moreArgs = #{}
if (-not $NoRecurse) {
$moreArgs["Recurse"] = $true
}
$filesToDelete = Get-ChildItem $BackupDir #moreArgs |
where-object {-not $_.PsIsContainer -and
$_.LastWriteTime -lt $(Get-Date).AddDays($days) -and
-not $_.FullName.Contains($exclusion)}
In PSH arrays are immutable, you cannot modify them, but it very easy to create a new one (operators like += on arrays actually create a new array and return that).
I agree with Richard, that Where-Object should be used here. However, it's harder to read.
What I would propose:
# get $filesToDelete and #exclusionList. In V2 use splatting as proposed by Richard.
$res = $filesToDelete | % {
$file = $_
$isExcluded = ($exclusionList | % { $file.FullName.Contains($_) } )
if (!$isExcluded) {
$file
}
}
#the files are in $res
Also note that generally it is not possible to iterate over a collection and change it. You would get an exception.
$a = New-Object System.Collections.ArrayList
$a.AddRange((1,2,3))
foreach($item in $a) { $a.Add($item*$item) }
An error occurred while enumerating through a collection:
At line:1 char:8
+ foreach <<<< ($item in $a) { $a.Add($item*$item) }
+ CategoryInfo : InvalidOperation: (System.Collecti...numeratorSimple:ArrayListEnumeratorSimple) [], RuntimeException
+ FullyQualifiedErrorId : BadEnumeration
This is ancient. But, I wrote these a while ago to add and remove from powershell lists using recursion. It leverages the ability of powershell to do multiple assignment . That is, you can do $a,$b,$c=#('a','b','c') to assign a b and c to their variables. Doing $a,$b=#('a','b','c') assigns 'a' to $a and #('b','c') to $b.
First is by item value. It'll remove the first occurrence.
function Remove-ItemFromList ($Item,[array]$List(throw"the item $item was not in the list"),[array]$chckd_list=#())
{
if ($list.length -lt 1 ) { throw "the item $item was not in the list" }
$check_item,$temp_list=$list
if ($check_item -eq $item )
{
$chckd_list+=$temp_list
return $chckd_list
}
else
{
$chckd_list+=$check_item
return (Remove-ItemFromList -item $item -chckd_list $chckd_list -list $temp_list )
}
}
This one removes by index. You can probably mess it up good by passing a value to count in the initial call.
function Remove-IndexFromList ([int]$Index,[array]$List,[array]$chckd_list=#(),[int]$count=0)
{
if (($list.length+$count-1) -lt $index )
{ throw "the index is out of range" }
$check_item,$temp_list=$list
if ($count -eq $index)
{
$chckd_list+=$temp_list
return $chckd_list
}
else
{
$chckd_list+=$check_item
return (Remove-IndexFromList -count ($count + 1) -index $index -chckd_list $chckd_list -list $temp_list )
}
}
This is a very old question, but the problem is still valid, but none of the answers fit my scenario, so I will suggest another solution.
I my case, I read in an xml configuration file and I want to remove an element from an array.
[xml]$content = get-content $file
$element = $content.PathToArray | Where-Object {$_.name -eq "ElementToRemove" }
$element.ParentNode.RemoveChild($element)
This is very simple and gets the job done.

Powershell - Searching and Comparing Arrays with Quest CMDlets

Trying to determine if there are any user folders on the network that don’t have an associated user account. All results return "Missing" when the majority should return "Found". Any ideas?
$Dir = "\\ServerName\Share\"
$FolderList = Get-ChildItem($Dir) | where {$_.psIsContainer -eq $true}
$UserList = get-qaduser -sizelimit 0 | select LogonName
foreach ($Folder in $FolderList)
{
if ($UserList -contains $Folder.name)
{
"Found: " + $Folder.name
}
Else
{
"Missing: " + $Folder.name
}
}
How about trying a slightly different approach that uses a hashtable (which offers exceptionally fast lookups of keys):
$users = #{}
Get-QADUser -sizelimit 0 | Foreach {$users["$($_.LogonName)"] = $true}
$dir = "\\ServerName\Share\"
Get-ChildItem $dir | Where {$_.PSIsContainer -and !$users["$($_.Name)"]}
If the folder name doesn't exactly match the LogonName, then as EBGreen notes, you will need to adjust the key ($users["$($.LogonName)"]) or the folder name when you use it to index the hashtable (!$users["$($.Name)"]).
-contains will match if the item in the collection is identical to what you are testing so be sure that the $Folder.Name is exactly the same as LogonName. Usually it wouldn't be. Most companies would have the folder name be foo$ for a user named foo.

Resources