Removing data from Array once pulled from AD - arrays

I'm currently pulling user data from Ad by OU, then updating certain fields, which works fine.
I want to modify the script to only update certain users but struggling, to remove any of the entries from the array as it is a fixed size. I converted to ArrayList and can get count of object, and can query then individually etc..
$users = Get-ADUser -Filter * -SearchBase "DN" -Properties GivenName, Surname,mail,UserPrincipalName,SAMAccountName,proxyAddresses | Select GivenName, Surname,mail,UserPrincipalName,SAMAccountName,proxyAddresses
$WorkingSet =[System.Collections.ArrayList]($users)
$WorkingSet.count gives 47 as result with last element being:
GivenName: LauraSurname:Willoxmail:WilloxL#domainUserPrincipalName :Laura.Willox#domain
SAMAccountName : Laura.Willox
proxyAddresses : {smtp:laura.willox#domain, SMTP:WilloxL#domain}
but trying $WorkingSet.IndexOf('Laura.Willox') gives -1 instead of 46
So then I can't do something like $WorkingSet.RemoveAt($WorkingSet.IndexOf('Laura.Willox'))
Is something about this data that I am not understanding,that it can't be queried like this?

You absolutely do not need to wrap your data in an ArrayList, it'll only complicate your code unnecessarily.
Instead of trying to modify the output from Get-ADUser inline in a list, use PowerShell's Where-Object cmdlet to filter the data:
$users = Get-ADUser -Filter * -SearchBase "DN" -Properties GivenName, Surname,mail,UserPrincipalName,SAMAccountName,proxyAddresses | Select GivenName, Surname,mail,UserPrincipalName,SAMAccountName,proxyAddresses
# use `Where-Object` to filter the data based on individual property values
$usersSansLaura = $users |Where-Object SAMAccountName -ne 'Laura.Willox'
Here, we pipe any user objects contained in $users to Where-Object SAMAccountName -ne 'Laura.Willox' - the -ne operator is the "not equal" operator, so the output will be any input object that does not have a SAMAccountName property with the exact value Laura.Willox, and then assign those to $usersSansLaura

Mathias' helpful answer is worth considering:
In PowerShell, it is unusual to directly manipulate resizable collections.
Instead, collection processing in PowerShell usually involves creating new collections by filtering the original collection, using the Where-Object in the pipeline or, for collections already in memory, the .Where() array method.
If you do need to deal with in-place resizing of a list data type, I suggest using System.Collections.Generic.List`1 instead, whose .FindIndex() method allows you to do what you wanted:
# Note: I'm using [object] as the type parameter for simplicity, but you
# could use [Microsoft.ActiveDirectory.Management.ADUser] for strict typing.
$WorkingSet = [System.Collections.Generic.List[object]]::new(
#(Get-ADUser -Filter * -SearchBase "DN" -Properties GivenName, Surname,mail,UserPrincipalName,SAMAccountName,proxyAddresses | Select GivenName, Surname,mail,UserPrincipalName,SAMAccountName,proxyAddresses)
)
# Find the index of the user with a given SAMAccountName:
$ndx = $WorkingSet.FindIndex({ $args[0].SAMAccountName -eq 'Laura.Willox' })
# If found, remove the user from the list
# (-1 indicates that no matching element was found)
if ($ndx -ne -1) {
$WorkingSet.RemoveAt($ndx)
}
Generally, note that both System.Collections.ArrayList and System.Collections.Generic.List`1 have a .Remove() method that allows you to pass the object (element) to remove directly, irrespective of its index.
As for what you tried:
Since your array list is composed of ADUser instances, the .IndexOf() method requires passing such an instance in order to locate it among the elements - you can't just pass a string referring to one of the properties among the elements.
Instead, you need a predicate (a Boolean test) that compares the string to the property of interest (.SamAccountName), which is what the .FindIndex() call above does.

Related

How to get the "difference" between two objects in another object with the same structure using powershell?

TL;DR: This but in powershell
I'm trying to get the "difference" between a list1 and a list2 where list2 is just a sub-set of list1. (or list1 is a super-set of list2).
Keep in mind that this is not the 'real scenario' but the structures of both objects are the same as this:
#List of all the IDs
$list1='2','5','6','11'
$CustObject1=foreach($i in $list1){
[pscustomobject]#{
id=$i
}
}
#Another list, where invalid IDs are not listed
$list2='2','5'
$CustObject2=foreach($i in $list2){
[pscustomobject]#{
id=$i
}
}
#I am looking for $CustObject3 = {id = '6',id = '11'}
Now, I want to create a new "object" that contains a list of all the 'invalid' IDs, in our example the $CustObject3 would contain just id's '6','11'.
The reason behind this is because I have a list of 3000 ids that are "valid" and just a couple that are "invalid" (at most 10).
I've tried a lot of options but I can't get it to work, used "where", used double foreach loops, honestly I'm just frustrated at this point but I did try to make it work.
Is this feasible?
Thanks.
You can use the -notin or -notcontains operators to perform the check.
#List of all the IDs
$list1='2','5','6','11'
$CustObject1=foreach($i in $list){
[pscustomobject]#{
id=$i
}
}
#Another list, where invalid IDs are not listed
$list2='2','5'
$CustObject2=foreach($i in $list){
[pscustomobject]#{
id=$i
}
}
$CustObject1 | Where-Object {$_.id -notin $CustObject2.id}
Unpack the ids from the PSCustomObjects, put them in HashSets, and then do ExceptWith():
$Ids = [Collections.Generic.HashSet[string]]::new([string[]]$CustObject1.id)
$validIds = [Collections.Generic.HashSet[string]]::new([string[]]$CustObject2.id)
$Ids.ExceptWith($validIds)
Here $Ids starts with all the Ids, then removes the valid ones. It's updated internally and ends with only the invalid ones.
Another option is to use the Compare-Object cmdlet:
$CustObject3 =
Compare-Object -Property id -PassThru $CustObject1 $CustObject2 |
Select-Object * -Exclude SideIndicator
Note:
Compare-Object decorates the passed-through objects with a .SideIndicator property indicating which of the input collections a given object is unique to (<= for objects unique to the first collection, => for those unique to the second).
Since this information is not of interest here (all result objects are by definition unique to the first collection), Select-Object * -Exclude SideIndicator then removes this extra property.

Powershell Remove objects from an array list

Is there a non for-loop way to remove some items from a arrayList?
$remotesumerrors = $remoteFiles | Select-String -Pattern '^[a-f0-9]{32}( )' -NotMatch
I want to remove the output of the above from the $remoteFiles var.. is there some pipe way to remove them?
Assuming all of the following:
you do need the results captured in $remotesumerrors separately
that $remoteFiles is a collection of System.IO.FileInfo instances, as output by Get-ChildItem, for instance
it is acceptable to save the result as an invariably new collection back to $remoteFiles,
you can use the .Where() array method as follows (this outperforms a pipeline-based solution based on the Where-Object cmdlet):
# Get the distinct set of the full paths of the files of origin
# from the Select-String results stored in $remotesumerrors
# as a hash set, which allows efficient lookup.
$errorFilePaths =
[System.Collections.Generic.HashSet[string]] $remotesumerrors.Path
# Get those file-info objects from $remoteFiles
# whose paths aren't in the list of the paths obtained above.
$remoteFiles = $remoteFiles.Where({ -not $errorFilePaths.Contains($_.FullName) })
As an aside:
Casting a collection to [System.Collections.Generic.HashSet[T]] is a fast and convenient way to get a set of distinct values (duplicates removed), but note that the resulting hash set's elements are invariably unordered and that, with strings, lookups are by default case-sensitive - see this answer for more information.
Use the Where-Object cmdlet to filter the list:
$remoteFiles = $remoteFiles |Where-Object { $_ |Select-String -Pattern '^[a-f0-9]{32}( )' -NotMatch }
If it truly was a [collections.arraylist], you could remove an element by value. There's also .RemoveAt(), to remove by array index.
[System.Collections.ArrayList]$array = 'a','b','c','d','e'
$array.remove
OverloadDefinitions
-------------------
void Remove(System.Object obj)
void IList.Remove(System.Object value)
$array.remove('c')
$array
a
b
d
e
Let assume that $remoteFiles is a file object of type System.IO.FileInfo. I also assume that you want to filter based on filename.
$remotesumerrors = $remoteFiles.name | Select-String -Pattern '^[a-f0-9]{32}' -NotMatch
What are trying to do with "( )" or what is query that you want to do.
edit: corrected answer based on comment

GET-ADUSER into .CSV file, but distinguishedName in a single column

After hours of tries and reading almost all posts here, I could not find the solution to what I need.
I would like to retrieve adusers but only specific fields. Something like this:
Area | Enabled | Name | userPrincipalName(email)
The problem is, the field DistinguishedName has everything I need but it's separated into different columns when I export it to .CSV.
I could only read this line and separate it in the columns I need, but then I have another problem that is there's some users with more than 2,3,4 OU.
is there a way to read, like only one OU ( the first one is the one I need, because it says IT, EXPEDITION , ETC) or at least separate CN as a field, OU as another field, and then name, email ...?
I create this code to run on PowerShell:
Get-ADUser -Filter * -Properties DistinguishedName,SamAccountName,DisplayName,EmailAddress,OfficePhone | Select-Object DistinguishedName,EmailAddress,OfficePhone,DisplayName,SamAccountName | export-csv -path path\to\adusers.csv -NoTypeInformation -Encoding "UTF8"
and then I import it to SQL Server.
The way I would use this is to create a custom property in the select-object part of your code. eg:
Get-ADUser -Filter * -Properties DistinguishedName,SamAccountName,DisplayName,EmailAddress,OfficePhone | Select-Object DistinguishedName,EmailAddress,OfficePhone,DisplayName,SamAccountName
would become
Get-ADUser -Filter * -Properties DistinguishedName,SamAccountName,DisplayName,EmailAddress,OfficePhone | Select-Object #{Label='DistinguishedName';expression={$_.DistinguishedName.Split(',')[1]}},EmailAddress,OfficePhone,DisplayName,SamAccountName
#{Label = 'DistinguishedName';Expression={$_.Distinguishedname.split(',')[1]}} this is how you create a custom property within the pipeline, so here we are saying I want a custom property and the Label is the name of the property, the expression is what we want the property to be, so in this case we want the current distinguishedname in the pipelin $_.DistinguishedName but then we want to split this on , as that is what separates the parts of the DistinguishedName from each other so we use the .split(',') method which you enter in the character you want to split on. Now based on your question you are only ever interested in the 1st OU in the distinguishedname field so we select the 2nd object in the array created by .split(',') using [1] the first object in the array will be the CN= entry.
This will give you something like below.
Which you can then export to CSV
Link to further info on custom properties https://4sysops.com/archives/add-a-calculated-property-with-select-object-in-powershell/

Is it possible to make IndexOf case-insensitive in PowerShell?

I've got a problem searching an INDEX in an array made up by query sessions command in a terminal server.
This is the problematic script:
# Array of logged users in terminal servers
$a=Get-RDUsersession -CollectionName "BLABLA" -ConnectionBroker BLABLA.BLA.BL
# Array of all users with two columns from active directory
$b=Get-ADUser -filter * -properties TelephoneNumber,SamAccountName
Now imagine logging in the terminal server using the account name TEST instead of test.
If I do:
$c = $b[$b.SamAccountName.indexof("test")].TelephoneNumber
then I don't get the telephone number.
I think that's because of the case sensitivity, isn't it? If I type TEST in the search command, I get the correct number.
Is there any simple way to solve this problem and make the search of the index case-insensitive?
I've read about using the method [StringComparison]"CurrentCultureIgnoreCase", but it seems not working with array.
Thanks.
Since $b is an Object[] type, then you would probably want to do a Where-Object.
$b | Where-Object -FilterScript {$_.Samaccountname -like '*Smith*'} | Select-Object -ExpandProperty 'telephoneNumber'
That being said, an array in Powershell can be indexed case-insensitively if it is converted to a [Collections.Generic.List[Object]] type.
$b = [Collections.Generic.List[Object]]$b
$b.FindIndex( {$args[0].sAMAccountName -eq 'test'} )
Note that pulling every single user object in AD and filtering using where-object or index matching can be very slow. You can instead Get-ADUser as needed or pull all ADusers using a filter that pulls only the users returned in $a.
If you insist on having all ADUsers in one spot with one pull, consider looping over the list once to make a hash lookup so you can easily index the hash value.
#Create account lookup hash
$accountlookup = #{}
foreach ($element in $accounts) {
$accountlookup[$element.SamAccountName] = $element
}
Hope that helps!

Comparing Arrays in Powershell

There is probably a simple way to do this, but I've been hitting my head against a wall for hours at this point. I'm trying to grab several user attributes out of AD, compare two of those attributes, and then modify them based on the differences. However since some users have null values for either their office or department fields which causes compare-object to fail, I have those going into other arrays with a -replace to get rid of the nulls, so my variables look like this:
$UserData = Get-ADuser -filter * -properties physicaldeliveryofficename,department | select samaccountname,physicaldeliveryofficename,department
$Offices = $UserData.physicaldeliveryofficename -replace "^$","N/A"
$Departments = $UserData.department -replace "^$","N/A"
So far so good, but when I loop through to compare values, I start to run into trouble. Looping through the users like this seems to be comparing every element to every other element:
Foreach ($user in $UserData.samaccountname) {
Compare-object $offices $departments -includeqeual}
While not having a loop and using compare-object by itself gives accurate results, but then I'd need a loop to check for matches anyway.
Assuming I just want to determine which users have matching office and department fields (and based off that do a pretty simple Set-ADUser command), how would I go about comparing the values without checking every element against every other element?
Your ForEach loop won't work properly because even though you are going through each user account, you are always comparing the same collection of offices and departments. I wrote this that might give you better results and saves the compare results as part of an object so you can see the user account as well.
Get-ADuser -Filter * -properties physicaldeliveryofficename,department | ForEach {
$Offices = $_.physicaldeliveryofficename -replace "^$","N/A"
$Departments = $_.department -replace "^$","N/A"
$Results = Compare-object $offices $departments -IncludeEqual
[pscustomobject]#{
User = $_.samaccountname
compare = $Results
}
}

Resources