Working two Array PowerShell - arrays

I have two arrays: array1 [POP1, POP2, POP3 .... POP30] and array2 [61,61,62 ... 61]. I need to create a new object with value 62 and its POP.
In this example:
POP3 62.
I am simplifying the explanation because I've already been able to get the value from the database.
Can someone help me?
Code:
$target = #( )
$ini = 0 | foreach {
$apiurl = "http://xxxxxxxxx:8080/fxxxxp/events_xxxx.xml"
[xml]$ini = (New-Object System.Net.WebClient).downloadstring($apiurl)
$target = $ini.events.event.name
$nodename = $target
$target = $ini.events.event.statuscode
$statuscode = $target
}
$column1 = #($nodename)
$column2 = #($statuscode)
$i = 0
($column1,$column2)[0] | foreach {
New-Object PSObject -Property #{
POP = $Column1[$i]
Status = $column2[$i++]
} | ft -AutoSize

I really couldn't figure out what you were trying to do, but you definitely over complicated it. Here is what I thought of your code:
# Here you have an empty array
$target = #( )
# Here you set call a Foreach, but you don't even need it
$ini = 0 | foreach {
$apiurl = "http://xxxxxxxxx:8080/fxxxxp/events_xxxx.xml"
[xml]$ini = (new-object System.Net.WebClient).downloadstring($apiurl)
# You duplicated variables here. Just set $nodename = $ini.events.event.name
$target = $ini.events.event.name
$nodename = $target
# You duplicate variables here. Just set $statuscode = $ini.events.event.name
$target = $ini.events.event.statuscode
$statuscode = $target
}
# You should already have arrays, so now you're making making more arrays duplicating variables again
$column1 = #($nodename)
$column2 = #($statuscode)
# counter, but you won't need it
$i = 0
# So here, youre making a new array again, but this contains two nested arrays. I don't get it.
($column1,$column2)[0] | foreach {
New-Object PSObject -Property #{
POP = $Column1[$i]
Status = $column2[$i++]
} | ft -AutoSize
} # You were missing a closing bracket for your foreach loop
Here is a solution that should probable work for you:
# Download the file
$apiurl = "http://xxxxxxxxx:8080/fxxxxp/events_xxxx.xml"
[xml]$ini = (New-Object System.Net.WebClient).DownloadString($apiurl)
# Set arrays
$nodename = $ini.events.event.name
$statuscode = $ini.events.event.statuscode
# Create $TableValues by looping through one array
$TableValues = foreach ( $node in $nodename )
{
[pscustomobject] #{
# The current node
POP = $node
# use the array method IndexOf
# This should return the position of the current node
# Then use that index to get the matching value of $statuscode
Status = $statuscode[$nodename.IndexOf($node)]
}
}
# Add a custom value
$TableValues += [pscustomobject] #{
POP = 'POP100'
Status = 100
}
$TableValues | Format-Table -AutoSize

Assuming that your intent is to create an array of custom objects constructed from the pairs of corresponding elements of 2 arrays of the same size:
A concise pipeline-based solution (PSv3+; a for / foreach solution would be faster):
$arr1 = 'one', 'two', 'three'
$arr2 = 1, 2, 3
0..$($arr1.Count-1) | % { [pscustomobject] #{ POP = $arr1[$_]; Status = $arr2[$_] } }
This yields:
POP Status
--- ------
one 1
two 2
three 3

Related

Powershell populate word table from an array

I have a PS script that will import a csv into several arrays and I need it to populate a table in word. I am able to get the data into the arrays, and create a table with headers and the correct number of rows, but cannot get the data from the arrays into the table. Doing lots of google searches led me to the following code. Any help is greatly appreciated.
Sample of My_File.txt
Number of rows will vary, but the header row is always there.
component,id,iType,
VCT,AD-1234,Story,
VCT,Ad-4567,DR,
$component = #()
$id = #()
$iType =#()
$vFile = Import-CSV ("H:\My_file.txt")
$word = New-Object -ComObject "Word.Application"
$vFile | ForEach-Object {
$component += $_.components
$id += $_.id
$iType +=_.iType
}
$template = $word.Documents.Open ("H:\Test.docx")
$template = $word.Document.Add()
$word.Visible = $True
$Number_rows = ($vFile.count +1)
$Number_cols = 3
$range = $template.range()
$template.Tables.add($range, $Number_rows, $Number_cols) | out-null
$table = $template.Tables.Item(1)
$table.cell(1,1).Range.Text = "Component"
$table.cell(1,2).Range.Text = "ID"
$table.cell(1,3).Range.text = "Type"
for ($i=0; $i -lt; $vFile.count+2, $i++){
$table.cell(($i+2),1).Range.Text = $component[$i].components
$table.cell(($i+2),2).Range.Text = $id[$i].id
$table.cell(($i+2),3).Range.Text = $iType[$i].iType
}
$Table.Style = "Medium Shading 1 - Accent 1"
$template.SaveAs("H:\New_Doc.docx")
Don't separate the rows in the parsed CSV object array into three arrays, but leave the collection as-is and use the data to fill the table using the properties of that object array directly.
I took the liberty of renaming your variable $vFile into $data as to me at least this is more descriptive of what is in there.
Try
$data = Import-Csv -Path "H:\My_file.txt"
$word = New-Object -ComObject "Word.Application"
$word.Visible = $True
$template = $word.Documents.Open("H:\Test.docx")
$Number_rows = $data.Count +1 # +1 for the header
$Number_cols = 3
$range = $template.Range()
[void]$template.Tables.Add($range, $Number_rows, $Number_cols)
$table = $template.Tables.Item(1)
$table.Style = "Medium Shading 1 - Accent 1"
# write the headers
$table.cell(1,1).Range.Text = "Component"
$table.cell(1,2).Range.Text = "ID"
$table.cell(1,3).Range.text = "Type"
# next, add the data rows
for ($i=0; $i -lt $data.Count; $i++){
$table.cell(($i+2),1).Range.Text = $data[$i].component
$table.cell(($i+2),2).Range.Text = $data[$i].id
$table.cell(($i+2),3).Range.Text = $data[$i].iType
}
$template.SaveAs("H:\New_Doc.docx")
When done, do not forget to close the document, quit word and clean up the used COM objects:
$template.Close()
$word.Quit()
$null = [System.Runtime.Interopservices.Marshal]::ReleaseComObject($template)
$null = [System.Runtime.Interopservices.Marshal]::ReleaseComObject($word)
[System.GC]::Collect()
[System.GC]::WaitForPendingFinalizers()

How to create an array like this in powershell?

I need to store data like below for TextToColumns Excel automation.
I need to implement Code-2 or Code-3 or Code-4 is that any way to achieve?
I have more than 350+ data so I cant use Code-1, that's not fair for me.
Code-1: working fine
$var = (1,2),(2,2),(3,2),(4,2),(5,2),(6,2)........(300,2)
$ColumnA.texttocolumns($colrange,1,-412,$false,$false,$false,$false,$false,$true,"|",$var)
Code-2: not Working
$var = #((1,2)..(300,2))
$ColumnA.texttocolumns($colrange,1,-412,$false,$false,$false,$false,$false,$true,"|",$var)
Code-3: not Working
$var = #()
#forloop upto 300
{ $var += ($i,2) }
$ColumnA.texttocolumns($colrange,1,-412,$false,$false,$false,$false,$false,$true,"|",$var)
Code-4: not Working
[array]$var = 1..300 | foreach-object { ,#($_, 2) }
$ColumnA.texttocolumns($colrange,1,-412,$false,$false,$false,$false,$false,$true,"|",$var)
I can't fully explain what happens here but I guess that it is related to the fact that the texttocolumns requires an (deferred) expression rather than an (evaluated) object.
Meaning that the following appears to work for the Minimal, Reproducible Example from #mclayton:
$Var = Invoke-Expression ((1..6 |% { "($_, `$xlTextFormat)" }) -Join ',')
And expect the following to work around the issue in the initial question:
$Var = Invoke-Expression ((1..300 |% { "($_, 2)" }) -Join ',')
Not an answer - just documenting some research to save others some time...
I can repro the issue here with the following code:
$xl = new-object -com excel.application;
$xl.Visible = $true;
$workbook = $xl.Workbooks.Add();
$worksheet = $workbook.Worksheets.Item(1);
$worksheet.Range("A1") = "aaa|111";
$worksheet.Range("A2") = "bbb|222";
$worksheet.Range("A3") = "ccc|333";
$worksheet.Range("A4") = "ddd|444";
$worksheet.Range("A5") = "eee|555";
$worksheet.Range("A6") = "fff|666";
which builds a new spreadsheet like this:
If you then run the following it will parse the contents of column A and put the results into columns B and C:
$range = $worksheet.Range("A:A");
$target = $worksheet.Range("B1");
# XlColumnDataType enumeration
# see https://learn.microsoft.com/en-us/office/vba/api/excel.xlcolumndatatype
$xlTextFormat = 2;
# XlTextParsingType enumeration
# see https://learn.microsoft.com/en-us/office/vba/api/excel.xltextparsingtype
$xlDelimited = 1;
# XlTextQualifier enumeration
# https://learn.microsoft.com/en-us/office/vba/api/excel.xltextqualifier
$xlTextQualifierNone = -4142;
$var = (1,$xlTextFormat),(2,$xlTextFormat),(3,$xlTextFormat),(4,$xlTextFormat),(5,$xlTextFormat),(6,$xlTextFormat);
# parse the values in A1:A6 and puts the values in a 2-dimensional array starting at B1
# see https://learn.microsoft.com/en-us/office/vba/api/excel.range.texttocolumns
$result = $range.TextToColumns(
$target, # Destination
$xlDelimited, # DataType
$xlTextQualifierNone, # TextQualifier
$false, # ConsecutiveDelimiter
$false, # Tab
$false, # Semicolon
$false, # Comma
$false, # Space
$true, # Other
"|", # OtherChar
$var # FieldInfo
);
which then looks like this:
However, if you change the declaration for $var to
$var = 1..6 | % { ,#($_, $xlTextFormat) };
you get the following error:
OperationStopped: The remote procedure call failed. (0x800706BE)
and the Excel instance terminates.
So there's something different about these two declarations:
$var = (1,$xlTextFormat),(2,$xlTextFormat),(3,$xlTextFormat),(4,$xlTextFormat),(5,$xlTextFormat),(6,$xlTextFormat);
$var = 1..6 | % { ,#($_, $xlTextFormat) };
but what that is eludes me :-S

Powershell Compare and Merge two arrays

I'm very new to Powershell and what I want to do is to compare two arrays of data and then merge them together to one large array that I can either export to Excel or POST request to a web server using restful API and json.
To do this in Python is simple by using Pandas and adding search data but in Powershell I can't really get it done.
Example: I have 2 arrays
$a1 = #(('Name1', 'Link1', 'URL1'),
('Name2', 'Link2', 'URL2'),
('Name3', 'Link3', 'URL3')
)
$a2 = #(('Name4', 'URL4', 'TEXT4'),
('Name2', 'URL2', 'TEXT1'),
('Name1', 'URL1', 'TEXT2')
)
I want to do a compare $a1 with $a2 and the other way around so I don't miss any values.
Merge them together so I will end up with a $a3 array that look something like this.
$a3 = #(('Name1', 'Link1', 'URL1', 'TEXT1),
('Name2', 'Link2', 'URL2', 'TEXT2),
('Name3', 'Link3', 'URL3', ''),
('Name4', 'URL4', '', 'TEXT4')
)
And as more I dig in to different alternatives as more confused do I get.
I'd recommend to make a collection of (at least) simple objects to work with.
This is almost classic task of combining two different sets of properties by common key. Just convert those arrays-of-arrays to arrays-of-objects-with-properties and combine them next.
Please note that powershell is primary sysadmin's language, there is almost no place for any guesses or chances when processing tasks.
Also you can use almost any external .Net-DLL library in PowerShell (search Deedle as alternative to Pandas)
$a1 = #(('Name1', 'Link1', 'URL1'),
('Name2', 'Link2', 'URL2'),
('Name3', 'Link3', 'URL3')
)
$a2 = #(('Name4', 'URL4', 'TEXT4'),
('Name2', 'URL2', 'TEXT1'),
('Name1', 'URL1', 'TEXT2')
)
# Make Hashtable K=Name;V={Name, Link, Url, Text}
$AObjects = #{}
# Fill Hashtable from A1
#($a1) |
ForEach-Object {
$name = $_[0]
$AObjects[$name] = #{
Name = $name
Link = $_[1]
Url = $_[2]
Text = ''
}
}
# Fill Hashtable from A2
#($a2) |
ForEach-Object {
$name = $_[0]
$url = $_[1]
if (-not $AObjects.ContainsKey($name)) {
Write-Warning "A2 has [$name] that was not listed in A1!"
$AObjects[$name] = #{Name = $name; Url = $url; Link = ''; Text = ''}
} elseif($AObjects[$name].Url -ne $url) {
Write-Warning "For $($name), A1's URL and A2's URL differ! Using A1's"
}
$AObjects[$name].Text = $_[2]
}
# Convert Hashtable to array of objects with props. Select is required for better output.
$AObjects = #($AObjects.Values) | % { [PSCustomObject]$_ } | Select-Object #('Name', 'Link', 'URL', 'Text')
# Convert array of objects with props back to array
$result = $AObjects |
ForEach-Object {
if ([string]::IsNullOrWhiteSpace($_.Link)) { # Didn't get your logic swapping elements in example
return #(,#($_.Name, $_.Url, $_.Link, $_.Text))
} else {
return #(,#($_.Name, $_.Link, $_.Url, $_.Text))
}
}
#Print
$result | % { "[$($_ -join ',')]" }
# [Name3,Link3,URL3,]
# [Name1,Link1,URL1,TEXT2]
# [Name2,Link2,URL2,TEXT1]
# [Name4,URL4,,TEXT4]

PowerShell array getting new entries

In this snippet I have a function (FDiskScan) that gets a computer name as an input and should return an array of objects.
function FDiskScan ([String] $name)
{
$outarray = [System.Collections.ArrayList]#()
$diskscan = Get-WmiObject Win32_logicaldisk -ComputerName $name
foreach ($diskobj in $diskscan)
{
if($diskobj.VolumeName -ne $null )
{
$max = $diskobj.Size/1024/1024/1024
$free = $diskobj.FreeSpace/1024/1024/1024
$full = $max - $free
$obj = #{
'ID' = $diskobj.deviceid
'Name' = $diskobj.VolumeName
'TotalSpace' = $max
'FreeSpace' = $free
'OccupiedSpace' = $full }
$TMP = New-Object psobject -Property $obj
$outarray.Add($TMP)
}
}
return $outarray
}
$pc = "INSERT PC NAME HERE"
$diskdata = [System.Collections.ArrayList]#()
$diskdata = FDiskScan($pc)
foreach ($disk in $diskdata)
{
Write-Host "Disco: " $disk.ID
Write-Host "Espaço Total: " ([math]::Round($disk.TotalSpace, 2)) "GB"
Write-Host "Espaço Ocupado: " ([math]::Round($disk.OccupiedSpace, 2)) "GB"
Write-Host "Espaço Livre" ([math]::Round($disk.FreeSpace, 2)) "GB" "`n"
}
Within the function with debugging and going into the variables I can see that everything is alright, and when the array gets out of the function and into the script scope it adds 2 more entries.
While in debug mode it tells me that $outarry within FDiskScan has the two disks that I have on my system organised as they should be.
However on:
$diskdata = FDiskScan($pc)
It says that it has an entry of value 0 on index 0 and of value 1 on index 1, then the disks follow suit, first disk C: in index 3 and disk D in index 4.
The expected behaviour was for index 0 and 1 having disks C and D respectively not a phantom 0 and 1 entries.
When adding an object to an array list in PowerShell (i.e. $outarray.Add($TMP)) the index, the object was added at, gets returned. As you don't assign the return value to a variable the function returns a System.Array containing the indexes and the entries of the array list returned by return $outarray. That's the reason why your functions return value contains 4 elements. Furthermore your functions return value in this case is not of type System.Collections.ArrayList but of type System.Array.
To avoid that behaviour do the following.
$null = $outarray.Add($TMP);
You are seeing 0, 1 because of this line - $outarray.Add($TMP). Change it to $outarray.Add($TMP) | Out-Null. I think PowerShell is printing the index when adding to the array.

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.

Resources