I am extracting some data from an API which I have finally stored in the form of two strings which looks something like this:
$String1:
Client 1
Client 2
Client 3
$String2:
Product a
Product b
Product c
The above is a long list in reality.
I also have fetched a different set of data from a different API which is stored in an array.
$Array:
Server State
abcd.com Normal
defg.com Dead
fguy.com Normal
Note, the way I have fetched $String1 and $String2 are values of foreach of Server from a different API output extracted as findstr Client and findstr Product.
Now, what I want to achieve from these 3 $s is a final table which will look something like this:
$Final:
Server State Client Product
abcd.com Normal 1 a
defg.com Dead 2 b
fguy.com Normal 3 c
So what I first tried to do is create and intermediate table which might look as
Client Product
1 a
2 b
3 c
and then merge with the $Array into the Final table.
And currently I am going nowhere with this, I have tried a lot of different ways which look stupid and getting me nowhere.
Since it looks like $Array3 is an array of objects with two properties: Server and State, I think this could help you out:
$Array1 = 'Client 1','Client 2','Client 3'
$Array2 = 'Product a','Product b','Product c'
$Array3 = #(
[PsCustomObject]#{'Server' = 'abcd.com'; 'State' = 'Normal'},
[PsCustomObject]#{'Server' = 'defg.com'; 'State' = 'Dead'},
[PsCustomObject]#{'Server' = 'fguy.com'; 'State' = 'Normal'}
)
for ($i = 0; $i -lt $Array3.Count; $i++) {
$Array3[$i] | Select-Object *,
#{Name = 'Client'; Expression = { $Array1[$i] -replace '^Client\s*'}},
#{Name = 'Product'; Expression = { $Array2[$i] -replace '^Product\s*'}}
}
Output:
Server State Client Product
------ ----- ------ -------
abcd.com Normal 1 a
defg.com Dead 2 b
fguy.com Normal 3 c
If you want to, you can capture the result of the for(..) loop and save that as CSV file somewhere. In that case just do
$result = for ($i = 0; $i -lt $Array3.Count; $i++) {
$Array3[$i] | Select-Object *,
#{Name = 'Client'; Expression = { $Array1[$i] -replace '^Client\s*'}},
#{Name = 'Product'; Expression = { $Array2[$i] -replace '^Product\s*'}}
}
$result | Export-Csv -Path 'D:\serverresult.csv' -NoTypeInformation
Update
Apparently the arrays 1 and 2 you mention are not arrays at all, but (multiline) strings.
In that case, split the lines inside these strings so you will end up with true arrays:
$string1 = #"
Client 1
Client 2
Client 3
"#
$string2 = #"
Product a
Product b
Product c
"#
# split the multiline strings (examples) on the Newline character into arrays
$Array1 = $string1 -split '\r?\n'
$Array2 = $string2 -split '\r?\n'
# now, both arrays will have 3 elements:
# $Array1 = 'Client 1','Client 2','Client 3'
# $Array2 = 'Product a','Product b','Product c'
# array no. 3 is an array of objects as we saw earlier
$Array3 = #(
[PsCustomObject]#{'Server' = 'abcd.com'; 'State' = 'Normal'},
[PsCustomObject]#{'Server' = 'defg.com'; 'State' = 'Dead'},
[PsCustomObject]#{'Server' = 'fguy.com'; 'State' = 'Normal'}
)
# Finally, you can use the `for(..)` loop unaltered
$result = for ($i = 0; $i -lt $Array3.Count; $i++) {
$Array3[$i] |
Select-Object *,
#{Name = 'Client'; Expression = { $Array1[$i] -replace '^Client\s*'}},
#{Name = 'Product'; Expression = { $Array2[$i] -replace '^Product\s*'}}
}
# output on console
$result
# output to CSV file
$result | Export-Csv -Path 'D:\serverresult.csv' -NoTypeInformation
This should work:
# Example-Arrays
$Array1 = #( 'Client 1', 'Client 2', 'Client 3' )
$Array2 = #( 'Product a', 'Product b', 'Product c' )
$Array3 = #( [PsCustomObject]#{'Server' = 'abcd.com'; 'State' = 'Normal'},
[PsCustomObject]#{'Server' = 'defg.com'; 'State' = 'Dead'},
[PsCustomObject]#{'Server' = 'fguy.com'; 'State' = 'Normal'} )
# Create datatable
$dt = New-Object system.Data.DataTable
[void]$dt.Columns.Add('Server',[string]::empty.GetType() )
[void]$dt.Columns.Add('State',[string]::empty.GetType() )
[void]$dt.Columns.Add('Client',[string]::empty.GetType() )
[void]$dt.Columns.Add('Product',[string]::empty.GetType() )
for( $counter = 0; $counter -lt $Array1.Count; $counter++ ) {
# Add new rows:
$newRow = $dt.NewRow()
$newRow.Server = $Array3.Server[$counter]
$newRow.State = $Array3.State[$counter]
$newRow.Client = $Array1[$counter] -replace '^.+(\d+)$', '$1'
$newRow.Product = $Array2[$counter] -replace '^.+[ \n\t\r]+(.*)$', '$1'
[void]$dt.Rows.Add( $newRow )
}
# Output-Datatable
$dt
# To File
$dt | Out-File 'test.txt'
Two cmdlets I wrote might be help for this:
ConvertFrom-SourceTable
This cmdlet is able to restore objects from a fixed width table.
For tables that do not contain a header line (as in your case for $String1 and $String2), you can separately define it with the -Header parameter where the header has basically to functions: it defines the property names and the column alignment (along with a possible ruler and the data contained by the table).
$Client = ConvertFrom-SourceTable $String1 -Header 'Name Client'
$Product = ConvertFrom-SourceTable $String2 -Header 'Name Product'
It is not clear whether the $Array is also a string or an object list. Presuming it is a string, you simply might restore the object list as follows:
$Server = ConvertFrom-SourceTable $Array
Join-Object
The other cmdlet is initially written to join objects on an common property relation. Nevertheless, if you omit the -On parameter (which defines the relation), it will simply join the objects based on the line index. The -Property parameter will just select the properties you need (and skip the Name property, defined in the header of the $String1 and $String2):
$Server | Join $Client | Join $Product -Property Server, State, Client, Product
(See also: In Powershell, what's the best way to join two tables into one?)
Related
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()
I have a file with multiple expressions like "$REGX('CareMedic.2_0','CustomerInformation','Customer Information')". The file can be a xml file, text file or any other type. If the file contains 9 of those expressions, I'm trying to pull all nine and send the values to a database.
I've tried my code as below:
$input_path = ‘C:\Users\Administrator\Desktop\test2.xml’
$SQLServer = "WIN-17V7QT0IJVK"
$SQLDBName = "Test"
$uid ="WIN-17V7QT0IJVK\Administrator"
$pwd = "letmebackinplease"
$SqlQuery = "SELECT * from product_schema;"
$ConnectionString = "Server = $SQLServer; Database = $SQLDBName; Integrated Security = True;"
$SqlConnection = New-Object System.Data.SqlClient.SqlConnection $ConnectionString
$SqlConnection.open()
if($SqlConnection.state -eq "Open"){
Write-Host "Test connection successful"
}
$regex = '()\(.*?\)'
$output = select-string -Path $input_path -Pattern $regex -AllMatches | % { $.Matches } | % { $.Value } |
ForEach-Object {
($_ -split "\(|\)")[1]
}
foreach ($line in $output){
$line = $line -replace "\(",""
$line = $line -replace "\)",""
$line = $line -replace "\'",""
$col1,$col2,$col3 = $line -split ","
[PSCustomObject]#{
col1 = $col1
col2 = $col2
col3 = $col3
} | select col1,col2,col3
$insert_query = "INSERT INTO [$SQLDBName].[dbo].[product_schema]
([version]
,[field]
,[value])
VALUES
($col1, $col2, $col3);"
$execute_query = New-Object System.Data.SqlClient.SqlCommand
$execute_query.connection = $SQLConnection
$execute_query.commandtext = $insert_query
$execute_query.ExecuteNonQuery()
}
$SqlConnection.close()
If the file has two of the below:
('Medic.2_0','AgeInformation','Age Information')
('Medic.2_0','TransactionID','Transaction ID')
My actual output should be:
'Medic.2_0' stored in Version Column
'AgeInformation' stored in the Field Column
'Age Information' stored in the value column
'Medic.2_0' stored in Version Column
'TransactionID' stored in the Field Column
'Transaction ID' stored in the value column
I have to take each of the values and store it in a column in a temp table setup on MySQL server like below:
**Version** **Field** **Value**
Medic.2_0 AgeInformation Age Information
Medic.2_0 TransactionID Transaction ID
Error Encountered:
Exception calling "ExecuteNonQuery" with "0" argument(s): "Incorrect syntax near '.2'."
At C:\Users\Administrator\Desktop\test.ps1:47 char:10
+ $execute_query.ExecuteNonQuery()
+ ~~~~~~~~~~~~
+ CategoryInfo : NotSpecified: (:) [], MethodInvocationException
+ FullyQualifiedErrorId : SqlException
Can someone please recommend how shall I change my code to solve this?
In answer to your original question before editing: Assuming your output looks like this and is saved in a variable named $output
('Medic.2_0','AgeInformation','Age Information')
('Medic.2_0','TransactionID','Transaction ID')
Try this:
foreach ($line in $output){
$line = $line -replace "\(",""
$line = $line -replace "\)",""
$line = $line -replace "\'",""
$col1,$col2,$col3 = $line -split ","
[PSCustomObject]#{
col1 = $col1
col2 = $col2
col3 = $col3
} | select col1,col2,col3 | export-csv d:\test.csv -append -NoTypeInformation
}
We are looping through the $output line by line removing the brackets and the single quotes, splitting the remaining text on the comma, then assigning each of the three entries into the relevant variables. Once they are in variables we can then easily create a PSObject and use it to select our requirements for our export-csv
Try to add this code:
$info=#() #for store your values
foreach($item in $output){
$z=$item.split(',') #for split to 3 strings
$info+=[PSCustomObject]#{ #create custom object which have named columns and store our values
Version = $z[0]
Field = $z[1]
Value = $z[2]
}
}
Write-Output $info #variable that store all columns
Then you must run foreach loop to each object in $info .
you can run it like this:
foreach($data in $info){
$data.Version #to access Version field
$data.Field #to access Field field
$data.Value #to access Value field
.......your SQL query......
}
I have the following code:
$DataType = "X,Y,Z"
$Data = "1,2,3"
$Table = #()
for ($i = 0; $i -le ($DataType.Count-1); $i++)
{
$Properties = #{$DataType[$i]=$Data[$i]}
$Object = New-Object -TypeName PSCustomObject -Property $Properties
$Table += $Object
}
$Table | Format-Table -AutoSize
I get this output:
X
-
1
What I would like to get is:
X Y Z
- - -
1 2 3
Thanks for your help!
Cutting a long story short:
$DataType, $Data | ConvertFrom-Csv
X Y Z
- - -
1 2 3
Ok, it needs a little explanation:
PowerShell will automatically unroll the array of strings ($DataType, $Data) and supply it as individual line items to the pipeline. The ConvertFrom-Csv cmdlet supports supplying the input table through the pipeline as separate lines (strings).
You can do the following instead:
$DataType = "X","Y","Z"
$Data = 1,2,3
$hash = [ordered]#{}
for ($i = 0; $i -lt $DataType.Count; $i++) {
$hash.Add($DataType[$i],$Data[$i])
}
$table = [pscustomobject]$hash
Explanation:
The code creates two collections, $DataType and $Data, of three items. $hash is an ordered hash table. [ordered] is used to preserve the order at which key-value pairs are added to the hash table. Since $hash is the object type hashtable, it contains the .Add(key,value) method for adding key-value pairs.
Since the [pscustomobject] type accelerator can be cast on a hash table, we can simply use the syntax [pscustomobject]$hash to create a new object.
If we consider your attempt, your variables are actually single strings rather than collections. Surrounding a value with quotes causes PowerShell to expand the inner contents as a string. When you index a string rather than a collection, you index the characters in the string rather than the entire item. You need to quote the individual elements between the commas so that the , acts as a separator rather than part of the string. You can see this behavior in the code below:
# DataType as a string
$DataType = "X,Y,Z"
$DataType[1]
,
# DataType as an array or collection
$DataType = "X","Y","Z"
$DataType[1]
Y
If you receive your data from another output in the current format, you can manipulate using $DataType = $DataType.Split(',') in order to create a collection. Alternatively you can treat the data as comma-separated and use the Import-Csv or ConvertFrom-Csv commands as in iRon's answer provided you order your strings properly.
Inside of your loop, you are adding three new objects to your collection $table rather than creating one object with three properties. $table += $Object creates an array called $table that appends a new item to the previous list from $table. If this was your original intention, you can view your collection by running $table | Format-List once you fix your $DataType and $Data variables.
When a collection is enumerated, the default table view displays the properties of the first object in a collection. Any succeeding objects will only display values for the first object's matching properties. So if object1 has properties X and Y and object2 has properties Y and Z, the console will only display values for properties X and Y for both objects. Format-List overrides this view and displays all properties of all objects. See below for an example of this behavior:
$obj1
X Y
- -
1 2
$obj2
Y Z
- -
3 4
$array = $obj1,$obj2
# Table View
$array
X Y
- -
1 2
3
# List View
$array | Format-List
X : 1
Y : 2
Y : 3
Z : 4
It seems that you want to create a single object with a property for each value in the arrays $DataType/$Data, but the problems are...
Neither $DataType nor $Data are arrays.
By creating your object inside the for loop you will create one object per iteration.
Since $DataType is a scalar variable $DataType.Count returns 1. Ordinarily, testing for $DataType.Count-1 would mean the loop never gets entered, but by the grace of using -le (so 0 -le 0 returns $true) instead of -lt, it does for exactly one iteration. Thus, you do get your single result object, but with only the first property created.
To fix this, let's create $DataType and $Data as arrays, as well as creating one set of properties before the loop to be used to create one result object after the loop...
...
$DataType = "X,Y,Z" -split ','
$Data = "1,2,3" -split ','
$Properties = #{}
for ($i = 0; $i -lt $DataType.Count; $i++)
{
$Properties[$DataType[$i]] = $Data[$i]
}
New-Object -TypeName PSCustomObject -Property $Properties | Format-Table -AutoSize
You'll also notice that $i -le ($DataType.Count-1) has been simplified to $i -lt $DataType.Count. On my system the above code outputs...
Y Z X
- - -
2 3 1
The properties are correct, but the order is not what you wanted. This is because Hashtable instances, such as $Properties, have no ordering among their keys. To ensure that the properties are in the order you specified in the question, on PowerShell 3.0 and above you can use this to preserve insertion order...
$Properties = [Ordered] #{}
What if you initialized $Table as an appendable like so:
$Table = New-Object System.Collections.ArrayList
for ($i = 0; $i -le ($DataType.Count-1); $i++)
{
$Properties = #{$DataType[$i]=$Data[$i]}
$Object = New-Object -TypeName PSCustomObject -Property $Properties
$Table.Add ( $Object )
}
Reformat your logic as needed.
One solution to this problem (if the inputs were two separate arrays):
$DataType = #( 'X','Y','Z' )
$Data = #( '1','2','3' )
$Table = New-Object psobject
for ($i = 0; $i -le ( $DataType.Count-1 ); $i++)
{
$Table | Add-Member -Name "$( $DataType[$i] )" -Value ( $Data[$i] ) -MemberType NoteProperty
}
$Table
I'm currently working on a script for automation. This script should have a global count variable that does not reset itself when the script is executed again. Therefore, I need a configuration file that stores this count variable and uses it when it is called up again. This counting variable is also dependent on an ID. There is therefore a count variable for each ID. The configuration file can be in XML or INI format. Can someone tell me how to create such a file the easiest way and how to add IDs or get the count variable? I dont think "csv-import/export" is the right way.
I've already tried this...
$results = #()
$details = #{
Key1 = $ID
Key2 = $count
Key3 = "sth"
Key4 = "sth"
Key5 = "sth"
}
$results += New-Object PSObject -Property $details
$results | export-csv -Path C:\Users\...\configure.txt -NoTypeInformation
Unfortunately, I can't get any further here, because it overwrites the previous entry every time the ID changes and I don't know how to add additional entries (if the ID already exists), update entries (count variable) and call this count variable to use it in Powershell.
Anybody got a suggestion?
Best Regards
You can use a hash table, Export-CliXml and Import-CliXml to save and load you ID counts to a XML file:
$xmlFilePath = 'idCounts.xml'
# If the XML file exists, it is loaded
if( Test-Path -Path $xmlFilePath -PathType Leaf )
{
$hashTable = Import-Clixml -Path $xmlFilePath
}
# Else a new hash table is initialized
else
{
$hashTable = #{}
}
# Set the count of ID '001' to 1
$hashTable['001'] = 1
# Increment the count of ID '002'
$hashTable['002'] += 1
# Save the hash table to the XML file
$hashTable | Export-Clixml -Path $xmlFilePath
Thank you for all the tips. In the end, I managed it myself in the following way:
if(!((import-csv "C:\Users\...\Desktop\ini.txt") | where-object {$_.Key1 -eq $ID}))
{
$results = #()
$details = #{
Key 1 = $ID
Key 2 = 1
Key 3 = "something"
Key 4 = "something"
Key 5 = "something"
Key 6 = "something"
}
$results += New-Object PSObject -Property $details
$results | export-csv -Path C:\Users\...\Desktop\ini.txt -append -NoTypeInformation
}
The system first checks whether there is an entry with the corresponding ID. If not, an object is created that has that ID. The count variable is set to 1 when it is newly created. The entry is attached to the file with "Export CSV".
$select = (import-csv "C:\Users\...\Desktop\ini.txt" | where{$_.Key1 -eq $ID})
[int]$global:number = [convert]::ToInt32($select.Key2)
To use the count variable, the configuration file is imported. I have set it to "global" because it has to operate over several functions.
($csv = Import-Csv "C:\Users\...\Desktop\ini.txt") | ForEach {
if ($_.Key1 -eq $ID) {
$_.Key2 = $global:number}
}
$csv | Export-Csv "C:\Users\...\Desktop\ini.txt" -NoTypeInformation
At the end, the count variable is updated and transferred back to the file with "Export CSV".
Nevertheless thank you for all the interesting suggestions!
Best Regards
I got a script that creates two arrays (each has 1 column and variable number of lines). I want to format these two arrays and e-mail it to an Outlook account. Code and sample data below.
$Values2 = #(Get-Content *\IdealOutput.csv)
$OutputLookUp2 = #()
foreach ($Value in $Values2) {
$OutputLookUp2 += $Excel.WorksheetFunction.VLookup($Value,$range4,3,$false)
}
$Excel.Workbooks.Close()
$Excel.Quit()
$EmailFrom = "sample#sample.com"
$EmailTo = "sample#sample.com"
$EmailBody = "$Values2 $OutputLookup2"
$EmailSubject = "Test"
$Username = "sample"
$Password = "sample"
$Message = New-Object Net.Mail.MailMessage `
($EmailFrom, $EmailTo, $EmailSubject, $EmailBody)
$SMTPClient = New-Object Net.Mail.SmtpClient `
("smtp.outlook.com", portnumber) #Port can be changed
$SMTPClient.EnableSsl = $true
$SMTPClient.Credentials = New-Object System.Net.NetworkCredential `
($Username, $Password);
$SMTPClient.Send($Message)
Both $OutputLookUp2 and $Values2 are one column with variable number of lines.
Example:
$Outputlookup2 =
X1
X2
$Values2 =
Y1
Y2
I would like the output to the body of the e-mail to be:
X1 Y1
X2 Y2
And I would like to avoid HTML as it will be sent via text as well.
Assuming my interpretation is correct this seems simple enough. For every $Values2, which is just a line from a text file, find its similar value in the open spreadsheet. You are have the loop that you need. Problem is you are building the item lists independent of each other.
$Values2 = #(Get-Content *\IdealOutput.csv)
$OutputLookUp2 = #()
foreach ($Value in $Values2){
$OutputLookUp2 += "$Value $($Excel.WorksheetFunction.VLookup($Value,$range4,3,$false))"
}
Now $OutputLookUp2 should contain your expected output in array form.
If the array does not work you could also just declare it as a string and the add newlines as you are building it. You will notice the "`r`n" at the end of the string.
$Values2 = #(Get-Content *\IdealOutput.csv)
$OutputLookUp2 = ""
foreach ($Value in $Values2){
$OutputLookUp2 += "$Value $($Excel.WorksheetFunction.VLookup($Value,$range4,3,$false))`r`n"
}
In both example you can just flip the order of the $value and the lookup easy. If you need a header you can add that when you declare $OutputLookUp2.
There is always room for improvement
If you want to take this a little further in the direction that Ansgar Wiechers was eluding to...
$OutputLookUp2 = Get-Content *\IdealOutput.csv | ForEach-Object{
"$_ $($Excel.WorksheetFunction.VLookup($_,$range4,3,$false))"
} | Out-String