Foreach -parallel object - powershell

Recently we started working on scripts that take a very long time to complete. So we dug into PowerShell workflows. After reading some documentation I understand the basics. However, I can't seem to find a way to create a [PSCustomObject] for each individual item within a foreach -parallel statement.
Some code to explain:
Workflow Test-Fruit {
foreach -parallel ($I in (0..1)) {
# Create a custom hashtable for this specific object
$Result = [Ordered]#{
Name = $I
Taste = 'Good'
Price = 'Cheap'
}
Parallel {
Sequence {
# Add a custom entry to the hashtable
$Result += #{'Color' = 'Green'}
}
Sequence {
# Add a custom entry to the hashtable
$Result += #{'Fruit' = 'Kiwi'}
}
}
# Generate a PSCustomObject to work with later on
[PSCustomObject]$Result
}
}
Test-Fruit
The part where it goes wrong is in adding a value to the $Result hashtable from within the Sequence block. Even when trying the following, it still fails:
$WORKFLOW:Result += #{'Fruit' = 'Kiwi'}

Okay here you go, tried and tested:
Workflow Test-Fruit {
foreach -parallel ($I in (0..1)) {
# Create a custom hashtable for this specific object
$WORKFLOW:Result = [Ordered]#{
Name = $I
Taste = 'Good'
Price = 'Cheap'
}
Parallel {
Sequence {
# Add a custom entry to the hashtable
$WORKFLOW:Result += #{'Color' = 'Green'}
}
Sequence {
# Add a custom entry to the hashtable
$WORKFLOW:Result += #{'Fruit' = 'Kiwi'}
}
}
# Generate a PSCustomObject to work with later on
[PSCustomObject]$WORKFLOW:Result
}
}
Test-Fruit
You're supposed to define it as $WORKFLOW:var and repeat that use throughout the workflow to access the scope.

You could assign $Result to the output of the Parallel block and add the other properties afterwards :
$Result = Parallel {
Sequence {
# Add a custom entry to the hashtable
[Ordered]#{'Color' = 'Green'}
}
Sequence {
# Add a custom entry to the hashtable
[Ordered] #{'Fruit' = 'Kiwi'}
}
}
# Generate a PSCustomObject to work with later on
$Result += [Ordered]#{
Name = $I
Taste = 'Good'
Price = 'Cheap'
}
# Generate a PSCustomObject to work with later on
[PSCustomObject]$Result

Related

How to dynamically add PSCustomObjects to a list [duplicate]

This question already has answers here:
Add items into a collection array dynamically
(2 answers)
Closed 1 year ago.
I am creating a script to parse a CSV file, where I store the content of each indexed field in the CSV as a NoteProperty in a PSCustomObject.
As I parse the file line by line, I add the PSCustomObject to a list type. When I output my list, I want to be able to do something like:
$list | Format-Table
and have a nice view of each row in the csv file, separated into columns with the heading up top.
Problem
When I add a PSCustomObject to the list, it changes the type of the list to a PSCustomObject. In practice, this has the apparent effect of applying any updates made to that PSCustomObject to every element in the list retroactively.
Here is a sample:
$list = [System.Collections.Generic.List[object]]::new()
$PSCustomObject = [PSCustomObject]#{ count = 0}
Foreach ($i in 1..5) {
$PSCustomObject.count +=1
$list.Add($PSCustomObject)
}
Expected Output:
PS>$list
count
-----
1
2
3
4
5
Actual Output:
PS>$list
count
-----
5
5
5
5
5
Question
Is there any way to get the expected output?
Limitations / additional context if it helps
I'm trying to optimize performance, as I may parse very large CSV files. This is why I am stuck with a list. I understand the Add method in lists is faster than recreating an array with += for every row. I am also using a runspace pool to parse each field separately and update the object via $list.$field[$lineNumber] = <field value>, so this is why I need a way to dynamically update the PSCustomObject. A larger view of my code is:
$out = [hashtable]::Synchronized(#{})
$out.Add($key, #{'dataSets' = [List[object]]::new() } ) ### $key is the file name as I loop through each csv in a directory.
$rowTemplate = [PSCustomObject]#{rowNum = 0}
### Additional steps to prepare the $out dictionary and some other variables
...
...
try {
### Skip lines prior to the line with the headers
$fileParser = [System.IO.StreamReader]$path
Foreach ( $i in 1..$headerLineNumber ) {
[void]$fileParser.ReadLine()
}
### Load the file into a variable, and add empty PSCustomObjects for each line as a placeholder.
while ($null -ne ($line = $fileParser.ReadLine())) {
[void]$fileContents.Add($line)
$rowTemplate.RowNum += 1
[void]$out.$key.dataSets.Add($rowTemplate)
}
}
finally {$fileParser.close(); $fileParser.dispose()}
### Prepare the script block for each runspace
$runspaceScript = {
Param( $fileContents, $column, $columnIndex, $delimiter, $key, $out )
$columnValues = [System.Collections.ArrayList]::new()
$linecount = 0
Foreach ( $line in $fileContents) {
$entry = $line.split($delimiter)[$columnIndex]
$out.$key.dataSets[$linecount].$column = $entry
$linecount += 1
}
}
### Instantiate the runspace pool.
PS Version (5.1.19041)
You're (re-)adding the same object to the list, over and over.
You need to create a new object every time your loop runs, but you can still "template" the objects - just use a hashtable/dictionary instead of a custom object:
# this hashtable will be our object "template"
$scaffold = #{ Count = 0}
foreach($i in 1..5){
$scaffold.Count += 1
$newObject = [pscustomobject]$scaffold
$list.Add($newObject)
}
As mklement0 suggests, if you're templating objects with multiple properties you might want to consider using an ordered dictionary to retain the order of the properties:
# this hashtable will be our object "template"
$scaffold = [ordered]#{ ID = 0; Count = 0}
foreach($i in 1..5){
$scaffold['ID'] = Get-Random
$scaffold['Count'] = $i
$newObject = [pscustomobject]$scaffold
$list.Add($newObject)
}

How to iterate through an array of objects in Powershell

I'm doing some basic validation on form fields. What's the correct way to iterate through an array of objects to validate them? When I try the below I get
The property 'BackColor' cannot be found on this object. Verify that the property exists and can be set.
I guess what I'm missing is a way of telling Powershell these are references to other variables, rather than variables themselves.
$MandatoryFields = #(
'txtUsername',
'txtFirst',
'txtLast',
'txtEmail',
'ComboLicense'
)
ForEach ($Field in $MandatoryFields) {
If ([string]::IsNullOrWhitespace($Field.text)) {
$Validation = "failed"
$Field.BackColor = "red"
}
}
EDIT: Okay, what I needed was the actual variables in the array, like so:
$MandatoryFields = #(
$txtUsername,
$txtFirst,
$txtLast,
$txtEmail,
$ComboLicense
)
Try adding your objects to an array like below
$objects = [System.Collections.ArrayList]#()
$myObject = [PSCustomObject]#{
Name = 'Kevin'
Language = 'PowerShell'
State = 'Texas'
}
$objects.add($myObject)
$myObject1= [PSCustomObject]#{
Name = 'Kevin'
Language = 'PowerShell'
State = 'Texas'
}
$objects.add($myObject1)
foreach($obj in $objects){
$obj.firstname
}
I'm assuming your using System.Windows.Forms to create your form. If that's the case when adding controls you should assign a name to the control. Then you can loop through your controls and check if the control name matches your list of mandatory controls and then execute your check.
$MandatoryFields = #(
'txtUsername',
'txtFirst',
'txtLast',
'txtEmail',
'ComboLicense'
)
$Controls = MyForm.controls
ForEach ($c in $Controls) {
ForEach ($Field in $MandatoryFields) {
if ($c.Name -eq $Field) {
If ([string]::IsNullOrWhitespace($c.text)) {
$Validation = "failed"
$c.BackColor = "red"
}
}
}
}

PS Object unescape character

I have small error when running my code. I assign a string to custom object but it's parsing the string by itself and throwing an error.
Code:
foreach ($item in $hrdblistofobjects) {
[string]$content = Get-Content -Path $item
[string]$content = $content.Replace("[", "").Replace("]", "")
#here is line 43 which is shown as error as well
foreach ($object in $listofitemsdb) {
$result = $content -match $object
$OurObject = [PSCustomObject]#{
ObjectName = $null
TestObjectName = $null
Result = $null
}
$OurObject.ObjectName = $item
$OurObject.TestObjectName = $object #here is line 52 which is other part of error
$OurObject.Result = $result
$Resultsdb += $OurObject
}
}
This code loads an item and checks if an object exists within an item. Basically if string part exists within a string part and then saves result to a variable. I am using this code for other objects and items but they don't have that \p part which I am assuming is the issue. I can't put $object into single quotes for obvious reasons (this was suggested on internet but in my case it's not possible). So is there any other option how to unescape \p? I tried $object.Replace("\PMS","\\PMS") but that did not work either (this was suggested somewhere too).
EDIT:
$Resultsdb = #(foreach ($item in $hrdblistofobjects) {
[string]$content = Get-Content -Path $item
[string]$content = $content.Replace("[", "").Replace("]", "")
foreach ($object in $listofitemsdb) {
[PSCustomObject]#{
ObjectName = $item
TestObjectName = $object
Result = $content -match $object
}
}
}
)
$Resultsdb is not defined as an array, hence you get that error when you try to add one object to another object when that doesn't implement the addition operator.
You shouldn't be appending to an array in a loop anyway. That will perform poorly, because with each iteration it creates a new array with the size increased by one, copies all elements from the existing array, puts the new item in the new free slot, and then replaces the original array with the new one.
A better approach is to just output your objects in the loop and collect the loop output in a variable:
$Resultsdb = foreach ($item in $hrdblistofobjects) {
...
foreach ($object in $listofitemsdb) {
[PSCustomObject]#{
ObjectName = $item
TestObjectName = $object
Result = $content -match $object
}
}
}
Run the loop in an array subexpression if you need to ensure that the result is an array, otherwise it will be empty or a single object when the loop returns less than two results.
$Resultsdb = #(foreach ($item in $hrdblistofobjects) {
...
})
Note that you need to suppress other output on the default output stream in the loop, so that it doesn't pollute your result.
I changed the match part to this and it's working fine $result = $content -match $object.Replace("\PMS","\\PMS").
Sorry for errors in posting. I will amend that.

Unable to Create Object in Foreach Loop in PowerShell

I am trying to create object using the foreach loop in PowerShell. Tried using "while" loop, it failed as well. Apparently, the looping methods are not allowing me to create objects...
Without further ado...
I have two scripts - Class.psm1 and Main.ps1.
On Class.psm1
Class Car {
[string]$brand
[string]$model
[string]$color
#Constructor
Car ([string]$brand) {
$this.brand = $brand
switch -wildcard ($this.brand) {
('Toyota') {$this.model = 'ABC'; $this.color = 'red'; break}
('Honda') {$this.model = 'FGH'; $this.color = 'blue'; break}
}
}
}
And on Main.ps1
Using module ".\Class.psm1"
$AllCars = {'Toyota', 'Honda'}
[array]$Objects = #()
foreach ($car in $AllCars) {
$temp = New-Object Car("$car")
$Objects += $temp
}
The output from Main.ps1, is that $Objects are just returning back "Toyota" and "Honda", instead of objects (and the properties it supposed to have).
However, if I were to just create the object individually, it will works fine.
For example:
$temp = New-Object Car('Toyota')
$Objects += $temp
$temp = New-Object Car('Honda')
$Objects += $temp
However, this is too manual work or rather unpractical.
May I know in which area did the codes went wrong...? How do I create the objects within the loop?
This issue is you are using {'Toyota', 'Honda'} instead of ('Toyota', 'Honda')
{'Toyota', 'Honda'} is a code block. When you pass it to New-Object Car("$car") it is actually passing New-Object Car("'Toyota', 'Honda'")
$AllCars = ('Toyota', 'Honda')
[array]$Objects = #()
foreach ($car in $AllCars) {
$temp = New-Object Car("$car")
$Objects += $temp
}
Since i was asked why the kangaroo code I decided to post a shorter response
$Objects = 'Toyota', 'Honda' | %{
New-Object Car("$car")
}

How can I pass dynamic parameters to powershell script and iterate over the list?

I want to create a powershell script that accepts dynamic parameters and I also want to iterate through them.
eg:
I call the powershell script in the following manner.
ParametersTest.ps1 -param1 value1 -param2 value2 -param3 value3
And I should be able to access my params inside the script as follows:
for($key in DynamicParams) {
$paramValue = DynamicParams[$key];
}
Is there anyway to do this in powershell? Thanks in advance.
There is nothing built-in like that (essentially you're asking for PowerShell parameter parsing in the absence of any definition of those parameters). You can emulate it, though. With $args you can get at all arguments of the function as an array. You can then iterate that and decompose it into names and values:
$DynamicParams = #{}
switch -Regex ($args) {
'^-' {
# Parameter name
if ($name) {
$DynamicParams[$name] = $value
$name = $value = $null
}
$name = $_ -replace '^-'
}
'^[^-]' {
# Value
$value = $_
}
}
if ($name) {
$DynamicParams[$name] = $value
$name = $value = $null
}
To iterate over dynamic parameters you can either do something like you wrote
foreach ($key in $DynamicParams.Keys) {
$value = $DynamicParams[$key]
}
(note the foreach, not for, the latter of which cannot work like you wrote it) or just iterate normally over the hash table:
$DynamicParams.GetEnumerator() | ForEach-Object {
$name = $_.Key
$value = $_.Value
}