$array[index] doesn't work for some collections? - arrays

Just looking for someone to point me in the right direction please, repeatedly in PowerShell I come across collections that I can't get elements of using [x].
The one I've just hit is in IIS:
Import-Module WebAdministration
[System.Reflection.Assembly]::LoadFrom("C:\windows\system32\inetsrv\Microsoft.Web.Administration.dll") | Out-Null
$serverManager = New-Object Microsoft.Web.Administration.ServerManager
$site = $servermanager.sites[0]
This returns nothing. However, $servermanager.sites | foreach-object {$site = $_} correctly loops through each object.
Using gm only returns the members of the object, not the collection, and I've been unable to find anything online to explain this behaviour.

Actually managed to solve it just after finishing the question using gettype() then looking it up.
In this particular case you need to use $servermanager.sites.item(0) to get the item at index 0.

Related

Why can't I store the return value of a cmdlet [CimInstance] inside an arraylist?

I'm currently working on a script where i retrieve the following data on a host:
List of installed printers
List of installed printer drivers
List of used printer drivers
At the moment i simply achieve it this way:
$installedPrinters = Get-Printer
$installedDrivers = Get-PrinterDriver | Sort-Object -Property Name
$usedDrivers = $printerList | Sort-Object -Property DriverName | Select-Object DriverName | Get-Unique -AsString
For convenience reasons i'm now trying to use an arraylist instead of 3 different variables for storing this data but i somehow don't seem to get this to work.
As soon as i try something like that ...
$data.Add({Get-Printer})
or
Get-Printer | $data.Add($_)
... i get either a bunch of errors or simply the value 'Get-Printers' as string stored in the arraylist.
Weirdly enough it seems to work if i first store the returned data from the Get-Printer cmdlet inside a dedicated variable and then add this variable to the arraylist.
Can somebody please help me get my head around this? As of now this behaviour doesn't really seem to make any sense to me.
Your syntax is flawed.
Use one of the following (assuming that $data contains a System.Collections.ArrayList instance):
$data.AddRange((Get-Printer))
Or, less efficiently:
Get-Printer | ForEach-Object { $null = $data.Add($_) }

Count occurrences of something in an array inside a foreach loop

I have a product CSV file that I have imported into $products
If something occurs more than once with the same name I want to populate the ParentSKU field, otherwise leave it blank.
Excuse the pseudocode but I'm imagining something like this:
foreach ($item in $products) {
if ($item.name.count -gt 1) {
$item.ParentSKU = $item.name }
else { } # do nothing
}
$item.name.count isn't correct but I hope my thinking is on the right track?
Many thanks for any advice
Powershell Object lists aren't smart enough to know that there's multiple of any one item, so you're going to have to iterate through (manually or otherwise) to find whether there's multiples here.
Since you're going to be making modifications to any duplicates, it may make sense to loop through and find duplicates manually, but it doesn't really follow the "powershell" philosophy / approach.
If you want to use powershell's built-in & powerful piping features, you might try a solution like this, which would grab all the PSObjects with duplicates using Where-Object, then sets the values for all those PSObjects.
$products |
Group-Object -Property Name |
Where-Object -FilterScript {
$_.Count -gt 1
} |
Select-Object -ExpandProperty Group |
Foreach-Object { $_.ParentSKU = $_.Name }
Since everything is passed by reference, your $products object will have the modified values!

Using and filling an ArrayList with .Add() questions using Powershell

I'm attempting to add performance to a script that has an array of data of over 10,000 entries, then use it in a foreach-object statement to fill a blank ArrayList with new data by calling another function. I've been reading how I shouldn't use +=, which is how I learned, because the performance is dreadful as it tears down the array and rebuilds it for each item.
The issue I have is I need to call a function to fill an empty ArrayList, but I don't seem to be able to do this inside the .Add() method.
Old code:
Function get_gfe
Function get_os
$gfe = [System.Collections.ArrayList]#()
$gfe = get_gfe
$getos = [System.Collections.ArrayList]#()
$gfe | foreach { $getos += get_os $_}
This takes over an hour to fill $getos with the data.
I was hoping to use something like this instead, but it doesn't work, any help would be appreciated
$gfe | foreach { [void]$getos.Add(get_os $_)}
I know that you can use .Add($_), but that doesn't meet my needs and I couldn't find any references to using other code or calling functions inside the .Add()method.
Thanks in advance!
Why not expand the foreach-loop to something like this:
foreach ($entry in $gfe){
$os = get_os $entry
[void]$getos.add($os)
}
A foreach-loop also saves time compared to | piping into foreach-object.
Although of course since I don't know what your functions are actually doing, this could not be the most effective way to save time. You can determine that with measure-command.
Is it absolutely vital that $getos is of type System.Collections.ArrayList instead of a 'normal' array (System.Object[]) ?
If not, I think the next code could perform faster:
$getos = foreach ($entry in $gfe) {
get_os $entry # output the result of function get_os and collect in variable $getos
}
Thanks to all for the recommendations, they've helped me to gain a better understanding of foreach, arrays, and arrayLists. We've suspect the slowness is related to the foreach loop accessing a function, which uses an API for each serial number. We had recently upgrade our MDM console and swapped out the underlying hardware.

Powershell: Comparing a value between two arrays, and extracting a related value

so here is what I'm trying to accomplish.
I have a form for a new starter, New Starter Form.csv, that has the following headers and information:
firstname,lastname,teamname,startdate
Joe,Bloggs,Security Admin,01/01/18
I have a different csv called Team List.csv, that has the following headers and information:
teamlead,teamname,resgroup
A B,Marketing,RESMARKETING01G
C D,Product,RESPRODUCT01G
E F,Advertising,RESADVERTISING01G
G H,Security Admin,RESSECURITYADMIN01G
I want to import both CSV files into Powershell, run a comparisson that takes the team name from the New Starter Form, and checks if there are any matches in the Team List, and if so, add the relevant RES group to the new starter in AD.
Currently, I can import them, compare them, find a match, and find an index number for the record, but I'm struggling to the take this index number, and use it to get the relevant RES group. So far the code looks like this:
$teamlist = import-csv "\\location\Team List.csv"
$newstarter = import-csv "\\otherlocation\New Starter Form.csv"
[string]$teamname = Compare-Object -includeequal -excludedifferent -PassThru $newstarter.teamname $teamlist.teamname
$teamname
[array]::indexof($teamlist,$teamname)
And running that, provides us with this in the console, showing that we can indeed see the match, and that the matching record is the last (-1) one:
PS C:\WINDOWS\system32> $teamlist = import-csv "\\location\Team List.csv"
$newstarter = import-csv "\\otherlocation\New Starter Form.csv"
[string]$teamname = Compare-Object -includeequal -excludedifferent -PassThru $newstarter.teamname $teamlist.teamname
$teamname
[array]::indexof($teamlist,$teamname)
Security Administration
-1
I've not got a lot of experience with Powershell, and my coding knowledge is pretty limited overall, but I'm used to the concept that I can save the index value as a variable, and then I could call that variable back to do something like $teamlist.resgroup[VARIABLE HERE].
But if I try and declare a new variable before [array]::indexof($teamlist,$teamname), Powershell isn't happy.
Whilst I've not looked into it, I believe a possible alternative could be to add in a huge switch statement, but I may be looking at having 100+ teams overall, and I'd like to avoid inefficient code wherever I can. Am I missing something obvious though? Is there a better way (Or even just a functioning way would be great!) that this could work?
Any help you can provide would be greatly appreciated!
$teamlist = import-csv "\\location\Team List.csv"
$newstarter = import-csv "\\otherlocation\New Starter Form.csv"
# get a single new starter
$person = $newstarter | Where-Object { $_.firstname -eq 'Joe' -and $_.lastname -eq 'Bloggs' }
# get the new starters team
$team = $teamlist | Where-Object { $_.teamname -eq $person.teamname }
# get the new starters resource group
$resgroup = $team.resgroup
# use the resource group - this simply writes it to the console
Write-Host $resgroup
The code above will:
import your two csvs
grab a single new starter from your new starter csv, based on first &
last name
grab the team & resource group for that new starter from the team list
display the resgroup (this is where you will need to use to populate AD)

Selecting certain properties from an object in PowerShell

The ADSI query works fine, it returns multiple users.
I want to select the 'name' and 'email' from each object that is returned.
$objSearcher = [adsisearcher] "()"
$objSearcher.searchRoot = [adsi]"LDAP://dc=admin,dc=domain,dc=co,dc=uk"
$objSearcher.Filter = "(sn=Smith)"
$ADSearchResults = $objSearcher.FindAll()
$SelectedValues = $ADSearchResults | ForEach-Object { $_.properties | Select -property mail, name }
$ADSearchResults.properties.mail gives me the email address
When I omit the 'select -properties' it will return all the properties, but trying to select certain properties comes back with nothing but empty values.
Whenever working with ADSI I find it easier to expand the objects returned using .GetDirectoryEntry()
$ADSearchResults.GetDirectoryEntry() | ForEach-Object{
$_.Name
$_.Mail
}
Note: that doing it this way gives you access to the actual object. So it is possible to change these values and complete the changes with something like $_.SetInfo(). That was meant to be a warning but would not cause issues simply reading values.
Heed the comment from Bacon Bits as well from his removed answer. You should use Get-Aduser if it is available and you are using Active Directory.
Update from comments
Part of the issue is that all of these properties are not string but System.DirectoryServices.PropertyValueCollections. We need to get that data out into a custom object maybe? Lets have a try with this.
$SelectedValues = $ADSearchResults.GetDirectoryEntry() | ForEach-Object{
New-Object -TypeName PSCustomObject -Property #{
Name = $_.Name.ToString()
Mail = $_.Mail.ToString()
}
}
This simple approach uses each objects toString() method to break the data out of the object. Note that while this works for these properties be careful using if for other and it might not display the correct results. Experiment and Debug!
Have you tried adding the properties?
$objSearcher.PropertiesToLoad.Add("mail")
$objSearcher.PropertiesToLoad.Add("name")

Resources