values from a foreach loop in a function into an array - arrays

I have a function that replaces PackageID in a SCCM task sequence, I would like to capture all those package IDs into a variable, so I would be able to create a report based on that.
The problem is that I already have a foreach loop doing the work, and I can't figure out how to not overwrite the values.
$Driver.PackageID comes from a foreach loop based on $Drivers, which contains
If I run the code I get this as I have Write-Output defined:
Updated code:
function Set-Drivers{
foreach ($Driver in $Drivers) {
Write-Output "Driver Name: $($Driver.Name)"
Write-Output "DriverPackageID: $($Driver.PackageID)"
}
}
$array = #()
$array = Set-Drivers
$hash = [ordered]#{
'DriverName' = $Driver.Name
'DriverID' = $Driver.PackageID
}
$array += New-Object -Typename PSObject -Property $hash
Can someone explain, why I only get the first result in my $array? I can see the values are being overwritten if I run it in debug mode.

Your code is not iterating over the results, but instead only using one of them. This what you intended.
$array = $drivers | foreach {
[ordered]#{
DriverName = $_.Name
DriverID = $_.PackageID
}
}

Your function doesn't return anything. It only writes lines to the console. Then after the function is finished, you create a single object and add that to your array.
Try something like
function Set-Drivers{
$result = foreach ($Driver in $Drivers) {
[PsCustomObject]#{
'DriverName' = $Driver.Name
'DriverID' = $Driver.PackageID
}
}
# output the result
# the comma wraps the result in a single element array, even if it has only one element.
# PowerShell 'flattens' that upon return from the function, leaving the actual resulting array.
,$result
}
$array = Set-Drivers
# show what you've got
$array

Related

Convert a SharePoint Online list into JSON using arrays

I'm trying to convert a set of SharePoint list items (and associated data) into a JSON object. To do this I'm trying to create a multi-dimensional array and then iterate over my SharePoint objects to populate it.
This is the relevant code so far:
#Lookup Source Address
$rootWeb = $Context.Web
$List = $rootWeb.lists.getByTitle($ListName)
$fields = $List.Fields;
$ListItems = $List.GetItems([Microsoft.SharePoint.Client.CamlQuery]::CreateAllItemsQuery())
#Load the List
$Context.Load($rootWeb)
$Context.Load($List)
$Context.Load($ListItems)
$context.Load($fields)
$Context.ExecuteQuery()
$listArray = #()
$listArray["DisplayTitle"] = #()
$listArray["Description"] = #()
$listArray["Setting"] = #()
$listArray["HealthAreas"] = #()
$listArray["ResourceType"] = #()
$listArray["ExternalURL"] = #()
$listArray["Active"] = #()
Write-Host "List items are"
foreach ($item in $ListItems)
{
$listArray["DisplayTitle"].Add($item["Title"])
$listArray["Description"].Add($item["File Description"])
$listArray["Setting"].Add($item["Setting"])
$listArray["HealthAreas"].Add($item["Health_x0020_Area"])
$listArray["ResourceType"].Add($item["Resource_x0020_Type"])
$listArray["ExternalURL"].Add($item["External_x0020_file_x0020_path"])
$listArray["Active"].Add($item["Currently_x0020_active_x003f_"])
}
Write-Host "############################"
Write-Host $listArray | ConvertTo-Json
I know there's a gap in my thinking here (maybe I need a hashtable) but just can't see it. The error I'm receiving is:
You cannot call a method on a null-valued expression.
However I can't see where my null variable may be originating from as I've confirmed each item in the loop does contain data (by writing to console).
The error that you receive is not related to SharePoint but to PowerShell. You created the PowerShell array and tried to access its elements like it was associative array/hashtable.
Please try this code (I've tested it with my own list with different column names and it works fine):
#Lookup Source Address
$rootWeb = $Context.Web
$List = $rootWeb.lists.getByTitle($ListName)
$fields = $List.Fields;
$ListItems = $List.GetItems([Microsoft.SharePoint.Client.CamlQuery]::CreateAllItemsQuery())
#Load the List
$Context.Load($rootWeb)
$Context.Load($List)
$Context.Load($ListItems)
$context.Load($fields)
$Context.ExecuteQuery()
$listArray = New-Object System.Collections.Generic.List[System.Object]
Write-Host "List items are"
foreach ($item in $ListItems)
{
$listArray.Add([hashtable]#{
DisplayTitle=$item["Title"];
Description= $item["File Description"];
Setting= $item["Setting"];
HealthAreas= $item["Health_x0020_Area"];
ResourceType= $item["Resource_x0020_Type"];
ExternalURL= $item["External_x0020_file_x0020_path"];
Active= $item["Currently_x0020_active_x003f_"];
}
)
}
Write-Host "############################"
$json = $listArray | ConvertTo-Json
Write-Host $json

Using intermediate variable to work with array (reference type)

I am trying to use $a variable in this script for working with intermediate steps so that I don't have to use $array[$array.Count-1] repeatedly. Similarly for $prop as well . However, values are being overwritten by last value in loop.
$guests = Import-Csv -Path C:\Users\shant_000\Desktop\UploadGuest_test.csv
$output = gc '.\Sample Json.json' | ConvertFrom-Json
$array = New-Object System.Collections.ArrayList;
foreach ($g in $guests) {
$array.Add($output);
$a = $array[$array.Count-1];
$a.Username = $g.'EmailAddress';
$a.DisplayName = $g.'FirstName' + ' ' + $g.'LastName';
$a.Password = $g.'LastName' + '123';
$a.Email = $g.'EmailAddress';
foreach ($i in $a.ProfileProperties.Count) {
$j = $i - 1;
$prop = $a.ProfileProperties[$j];
if ($prop.PropertyName -eq "FirstName") {
$prop.PropertyValue = $g.'FirstName';
} elseif ($prop.PropertyName -eq "LastName") {
$prop.PropertyValue = $g.'LastName';
}
$a.ProfileProperties[$j] = $prop;
}
$array[$array.Count-1] = $a;
}
$array;
All array elements are referencing one actual variable: $output.
Create an entirely new object each time by repeating JSON-parsing:
$jsontext = gc '.\Sample Json.json'
..........
foreach ($g in $guests) {
$a = $jsontext | ConvertFrom-Json
# process $a
# ............
$array.Add($a) >$null
}
In case the JSON file is very big and you change only a few parts of it you can use a faster cloning technique on the changed parts (and their entire parent chain) via .PSObject.Copy():
foreach ($g in $guests) {
$a = $output.PSObject.Copy()
# ............
$a.ProfileProperties = $a.ProfileProperties.PSObject.Copy()
# ............
foreach ($i in $a.ProfileProperties.Count) {
# ............
$prop = $a.ProfileProperties[$j].PSObject.Copy();
# ............
}
$array.Add($a) >$null
}
As others have pointed out, appending $object appends a references to the same single object, so you keep changing the values for all elements in the list. Unfortunately the approach #wOxxOm suggested (which I thought would work at first too) doesn't work if your JSON datastructure has nested objects, because Copy() only clones the topmost object while the nested objects remain references to their original.
Demonstration:
PS C:\> $o = '{"foo":{"bar":42},"baz":23}' | ConvertFrom-Json
PS C:\> $o | Format-Custom *
class PSCustomObject
{
foo =
class PSCustomObject
{
bar = 42
}
baz = 23
}
PS C:\> $o1 = $o
PS C:\> $o2 = $o.PSObject.Copy()
If you change the nested property bar on both $o1 and $o2 it has on both objects the value that was last set to any of them:
PS C:\> $o1.foo.bar = 23
PS C:\> $o2.foo.bar = 24
PS C:\> $o1.foo.bar
24
PS C:\> $o2.foo.bar
24
Only if you change a property of the topmost object you'll get a difference between $o1 and $o2:
PS C:\> $o1.baz = 5
PS C:\> $o.baz
5
PS C:\> $o1.baz
5
PS C:\> $o2.baz
23
While you could do a deep copy it's not as simple and straightforward as one would like to think. Usually it takes less effort (and simpler code) to just create the object multiple times as #PetSerAl suggested in the comments to your question.
I'd also recommend to avoid appending to an array (or arraylist) in a loop. You can simply echo your objects inside the loop and collect the entire output as a list/array by assigning the loop to a variable:
$json = Get-Content '.\Sample Json.json' -Raw
$array = foreach ($g in $guests) {
$a = $json | ConvertFrom-Json # create new object
$a.Username = $g.'EmailAddress'
...
$a # echo object, so it can be collected in $array
}
Use Get-Content -Raw on PowerShell v3 and newer (or Get-Content | Out-String on earlier versions) to avoid issues with multiline JSON data in the JSON file.

How to create and populate an array in Powershell based on a dynamic variable?

I've been struggling with this for a couple of days, and I'm not sure how to conquer it. I need to do the following:
Import a csv of users with the following values:
ID, Name, Region
Create an array based on the Region values that I can then use to populate with ID's and Names with that region, ie.
Array_SEA
AA_SCOM, Adam Andrews, SEA
Array_OAK
BB_SCOM, Bob Barker, OAK
Here's the code I've got right now:
$list2 = ipcsv .\TSE_Contact_List.csv | sort-object BU
$arraylist =#()
foreach ($vitem in $list2)
{
$arraylist += New-Object PsObject -Property #{'Array' = "Array_" + $vitem.bu}
}
foreach ($varray in $arraylist)
{
$arr = new-variable -Name $varray
$arr.value += $varray.array
$arr
}
This produces the following error for records with a duplicate regions:
New-Variable: A variable with name '#{Array=Array_SCA}' already exists.
I'm also getting the following when it tries to add values:
Property 'value' cannot be found on this object; make sure it exists and is settable.
I get that I'm not actually creating arrays in the second section, but I'm not sure how to pass the output of the variable to an array name without turning the variable declaration into the array name, if that makes sense.
I've tried the following with hash tables, and it gets closer:
$list2 = ipcsv .\TSE_Contact_List.csv | sort-object BU
$arraylist =#{}
foreach ($vitem in $list2){$arraylist[$vitem.bu] = #()}
foreach ($record in $list2)
{
$arraylist[$vitem.bu] += ($record.SCOMID,$record.Name,$record.BU)
Write-host "Array: "
$arraylist[$vitem.bu]
write-host ""
}
The output on this shows no errors, but it just keeps showing the added fields for all of the records for each iteration of the list, so I don't think that it's actually assigning each unique BU to the array name.
I like the hashtable-approach, but I would finetune it a little. Try:
$list2 = ipcsv .\TSE_Contact_List.csv | sort-object BU
$arraylist = #{}
foreach ($vitem in $list2){
if($arraylist.ContainsKey($vitem.BU)) {
#Array exists, add item
$arraylist[($vitem.BU)] += $vitem
} else {
#Array not found, creating it
$arraylist[($vitem.BU)] = #($vitem)
}
}
#TEST: List arrays and number of entries
$arraylist.GetEnumerator() | % {
"Array '$($_.Key)' has $($_.Value.Count) items"
}
You could also use Group-Object like:
$list2 = ipcsv .\TSE_Contact_List.csv | Group-Object BU
#TEST: List groups(regions) and number of entries
$list2 | % {
"Region '$($_.Name)' has $(#($_.Group).Count) items"
}

Creating profile array with powershell

I'm still learning the basics of powershell, but I have come across an issue I can't seem to resolve as I just don't have enough knowledge.
I'm creating a script to do user profile migrations and I want the code to gather profiles from the local machine, convert the SID back to usernames and list them in a drop down box (which works), but only lists one user. I have this:
$Profiles = gwmi -Class Win32_UserProfile -Filter ("Special = False")
$output = foreach ($Profile in $Profiles)
{
try
{
$objSID = New-Object System.Security.Principal.SecurityIdentifier($profile.sid)
$objuser = $objsid.Translate([System.Security.Principal.NTAccount])
$objusername = $objuser.value
}
catch
{
$objusername = $profile.sid
}
Write-Host $objuser.value
$array = #($objuser)
Any ideas?
TIA!
It appears that you're overwriting the contents of $array on each iteration of your foreach loop. Instead, append to it.
foreach ($Profile in $Profiles)
{
try
{
$objSID = New-Object System.Security.Principal.SecurityIdentifier($profile.sid)
$objuser = $objsid.Translate([System.Security.Principal.NTAccount])
$objusername = $objuser.value
}
catch
{
$objusername = $profile.sid
}
Write-Host $objuser.value
$array += #($objuser)
}
But I may be wrong. You've pasted only part of your script here (the braces for foreach aren't balanced, and we have no insight into how that drop-down list is being populated), so there may be something later on that's messing you up.
See comments in code.
$Profiles = gwmi -Class Win32_UserProfile -Filter ("Special = False")
#You never output anything in your foreach-loop, so $output will be empty.. Removed Write-Host later in code to fix this
$output = foreach ($Profile in $Profiles) {
try
{
$objSID = New-Object System.Security.Principal.SecurityIdentifier($profile.sid)
$objuser = $objsid.Translate([System.Security.Principal.NTAccount])
$objusername = $objuser.value
}
catch
{
$objusername = $profile.sid
}
#You've already saved "objuser.value to a variable... use it.. :) Also, You're catching returned objects with $output = foreach, so I'd suggest outputing the usernames and not just write them to the console. Replace `Write-Host $objuser.value` with `$objusername`
$objusername
#You never closed your foreachloop. Added }
}
#Output collected usernames
$output
#This will always overwrite $array with a new array containing one user, objuser, only. Removed
#$array = #($objuser)

Powershell scope: variable array within loop not populating when adding to second array

In the following Powershell example, how can I get $server to populate within line #5? I'm trying to save the results of a Get-EventLog query to an array for each $server loop iteration.
Example:
$serversArray = #("one", "two", "three")
ForEach ($server in $serversArray) {
echo $server "Variable array iteration populates here correctly"
$eventArray = #()
$eventArray += Get-EventLog -computerName $server #But not here, where I need it
$eventArray #Yet it populates from calling the second array correctly
}
I've tried assigning the scope of the $server variable to global or script.
I've researched other similar issues, but none that I could find had this circumstance
I've tried different combinations of piping, quotes, back ticks, etc.
As always, thanks in advance.
Edit Ok, ISE was caching some of the variables (or something strange), as the above example began working after I restarted ISE. Anyways, the issue is with the $servers array input, which in the full script is from a MySQL query. If I statically assign the array with server names (like in the example above) the script works. If I use the input from the MySQL query, Get-EventLog fails with The network path was not found. So even though the server values look correct (and something could be expanding), it could be a text encoding issue, etc. Sorry to waste time, but discussing it has helped narrow it down. Here's the pertinent part of the actual script:
#Open SQL Connection
[System.Reflection.Assembly]::LoadWithPartialName("MySql.Data")
$connectionString = "server=dbserver;uid=odbc;pwd=password;database=uptime;"
$connection = New-Object MySql.Data.MySqlClient.MySqlConnection
$connection.ConnectionString = $connectionString
$connection.Open()
#Get server names from db
$sqlGetServers = "select server_nm from servers limit 3;"
$command = New-Object MySql.Data.MySqlClient.MySqlCommand($sqlGetServers, $connection)
$dataAdapter = New-Object MySql.Data.MySqlClient.MySqlDataAdapter($command)
$dataSet = New-Object System.Data.DataSet
$recordCount = $dataAdapter.Fill($dataSet, "sample_data")
$server_names = #()
$server_names += $dataSet.Tables["sample_data"]
#$server_names = #("server-1","server-2") <-- this works
#loop through array of server names
foreach($server in $server_names) {
$eventData = #()
$eventData += Get-EventLog -computerName $server -LogName System -Newest 10
foreach($event in $eventdata) {Do-Stuff}
}
I am not 100% certain what the problem is that you're having. I am assuming that the issue is that your $eventArray ends up with just the last result.
If that is correct, then the reason is because you're reinitializing it as empty on every iteration in line 4: $eventArray = #()
Try this:
$serversArray = #("one", "two", "three")
$eventArray = #()
ForEach ($server in $serversArray) {
echo $server "Variable array iteration populates here correctly"
$eventArray += Get-EventLog -computerName $server #But not here, where I need it
$eventArray #Yet it populates from calling the second array correctly
}
or, alternatively, with ForEach-Object like this:
$serversArray = #("one", "two", "three")
$eventArray = $serversArray | ForEach-Object {
echo $_ "Variable array iteration populates here correctly"
Get-EventLog -computerName $_ #But not here, where I need it
#Yet it populates from calling the second array correctly
}
Explanation for Method 1:
Declaring $eventArray as an empty array before the loop starts, then adding items to it within each iteration of the loop, as opposed to initializing it on every iteration.
The way you had it, $eventArray would be reset to an empty array every time, and in the end it would just contain the last result.
Explanation for Method 2:
ForEach-Object is a pipeline cmdlet and returns the result of its code block (which is run once for each object piped into it.
In this case we use $_ to represent the individual object. Instead of assigning the result of Get-EventLog to $eventArray, we simply call it, allowing the return value to be returned from the block.
We assign the entire ForEach-Object call into $eventArray instead, which will end up with the collection of results from the entire call.
Instead....
$serversArray = #("one", "two", "three")
$eventArray = #()
$serversArray | ForEach-Object {
echo $_ "Variable array iteration populates here correctly"
$eventArray += Get-EventLog -computerName $_ #But not here, where I need it
$eventArray #Yet it populates from calling the second array correctly
}
Pipe the array into a for-each loop. then you can use $_

Resources