I have read an input CSV file and generated a chart in PowerShell form.
This is my code:
$datasource = Import-Csv "D:\Users\janaja\Desktop\test.csv"
[void] [Reflection.Assembly]::loadWithPartialName('System.Windows.Forms')
[void][Reflection.Assembly]::loadWithPartialName('System.Windows.Forms.DataVisualization')
$datasource = "D:\Users\janaja\Desktop\test.csv"
$outputXLSX = "D:\Users\janaja\Desktop\test.xlsx"
$excel = New-Object -ComObject excel.application
$xlChart=[Microsoft.Office.Interop.Excel.XLChartType]
$workbook = $excel.Workbooks.Add(1)
$worksheet = $workbook.worksheets.Item(1)
$TxtConnector = ("TEXT;" + $datasource)
$Connector = $worksheet.QueryTables.add($TxtConnector,$worksheet.Range("A1"))
$query = $worksheet.QueryTables.item($Connector.name)
$query.TextFileOtherDelimiter = $Excel.Application.International(5)
$query.AdjustColumnWidth = 1
$query.Refresh()
$query.Delete()
$Workbook.SaveAs($outputXLSX,51)
$i = 1
$j=1
while($j -le 20){
$excel.Cells.Item($i, $j).Font.ColorIndex = 3
$excel.Cells.Item($i, $j).Font.Bold = $True
$j++
}
$Chart = New-Object -TypeName
System.Windows.Forms.DataVisualization.Charting.Chart
$Chart.Size = '600,750'
$ChartArea = New-Object -TypeName
System.Windows.Forms.DataVisualization.Charting.ChartArea
$ChartArea.AxisX.Title = 'VM Name'
$ChartArea.AxisY.Title = 'CPU and Memory Utilization'
$ChartArea.AxisX.lnterval = '1'
$ChartArea.AxisX.LabelStyle.Enabled = $true
$ChartArea.AxisX.LabelStyle.Angle = 90
$Chart.ChartAreas.Add($ChartArea)
$Chart.Series.Add('Memory')
$Chart.Series.Add('CPU')
$Chart.Series['Memory'].ChartType =
[System.Windows.Forms.DataVisualization.Charting.SeriesChartType]::Bar
$Chart.Series['CPU'].ChartType =
[System.Windows.Forms.DataVisualization.Charting.SeriesChartType]::Bar
Foreach($VM in $datasource)
{
$var1 = $Chart.Series['Memory'].Points.AddXY($VM.VMName,$VM.MemoryGB)
}
Foreach($CPU in $datasource)
{
$var2 = $Chart.Series['CPU'].Points.AddXY($VM.VMName,$VM.CPU)
}
$Title = New-Object -TypeName
System.Windows.Forms.DataVisualization.Charting.Title
$Chart.Titles.Add($Title)
$Chart.Titles[O].Text = 'VM details'
$Chart.Anchor = [System.Windows.Forms.AnchorStyles]::Bottom -bor
[System.Windows.Forms.AnchorStyles]::Right -bor
[System.Windows.Forms.AnchorStyles]::Top -bor
[System.Windows.Forms.AnchorStyles]::Left
$Form = New-Object Windows.Forms.Form
$Form.Text= "VM Memory and CPU usage Chart"
$Form.Width = 600
$Form.Height = 600
$Form.controls.add($Chart)
$Form.Add_Shown({$Form.Activate()})
$Form.ShowDialog()
$Chart.Savelmage($Env:USERPROFILE + "\Desktop\Chart.png", "PNG")
This code converts the file from CSV to Excel format and then reads the CSV file data to display a chart on the Powershell Form.
How do I Generate the same chart which takes only 3 column values out of 14 columns and display a clustered bar chart?
You cannot embed pictures in CSV files the way you probably intend, because CSV is a plaintext format. You could embed a path to a picture file, or you could encode the picture (e.g. using base64 encoding) and include the encoded data. But even with the latter you'd still need a viewer that would decode the encoded data and render them as a picture.
Essentially, what you're asking is not possible.
However, you could use the ImportExcel to create an aribtrary Excel spreadsheet from an existing CSV file. The module supports creating Excel charts as well, which should be able to reproduce a similar chart to the one you're creating with your image. You may also be able to just embed the image file, but I'm not sure if that's possible. You may need to use the much older Excel COM API that ships with Office to do that.
Related
I'm trying to fill an Excel sheet using powershell :
0. Declaring Excel object
$Excel = New-Object -ComObject Excel.Application
$Excel.DisplayAlerts = $false
$Excel.ScreenUpdating = $false
$Excel.DisplayStatusBar = $false
$Excel.EnableEvents = $false
$Excel.Visible = $False
1. Reading data from a database table :
$dt1 = New-Object System.Data.Dataset
2. Getting the table :
$dt_table1 = $dt1.Tables[0]
3. Filling Excel file :
for ([Int]$m = 0; $m -lt $dt_table1.Rows.Count; $m++)
{
for ([Int]$r = 0; $r -lt $dt_table1.Columns.Count; $r++)
{
$incolumn = $r + 1;
$inrow = $inheaderlenght + 2 + $m;
if($incolumn -gt 2)
{
$Workbook.ActiveSheet.Cells.Item($inrow, $incolumn) = [System.Convert]::ToDecimal($dt_table1.Rows[$m].ItemArray[$r])
}
else
{
$Workbook.ActiveSheet.Cells.Item($inrow, $incolumn) = $dt_table1.Rows[$m].ItemArray[$r].ToString()
}
}
}
With a few hundreds of rows the sheet is filling in seconds, the problem is when i got thousands of rows, is very slow, for example to fill 21.500 rows it need 15 min at least.
I'm executing this code in my production server, with 32GB of RAM and an Intel Xeon processor.
I would like to improve the performance, i need to fill an Excel file with 32 sheets and only few sheets have thousands of rows.
UPDATE: I wanted to fill directly an array into the Excel sheet :
$excelArray = New-Object 'object[,]' $dt_table1.Rows.Count, $dt_table1.Columns.Count
$excelArray = ForEach($Row in $dt1.Tables[0].Rows){
$Record = New-Object PSObject
ForEach($Col in $dt1.Tables[0].Columns.ColumnName){
Add-Member -InputObject $Record -NotePropertyName $Col -NotePropertyValue $Row.$Col
}
$Record
}
But now, the next line fails:
$range = $WorkSheet.Range('A1', ([char](64 + $dt_table1.Columns.Count)).ToString() + ($dt_table1.Rows.Count).ToString() )
$range.Value2 = $excelArray
Shamelessly using some of f6a4's answer, I think this could work:
# 3. Filling Excel file :
# convert to array of objects
$tableData = $dt_table1 | Select-Object * -ExcludeProperty ItemArray, Table, RowError, RowState, HasErrors
# select the range in the worksheet
$endRange = '{0}{1}' -f ([char](64 + $dt_table1.Columns.Count)), $dt_table1.Rows.Count
$range = $Workbook.ActiveSheet.Range('A1', $endRange)
# copy the array to excel range
$range.Value2 = $tableData
Ok, now i found solution.
$excelArray | ConvertTo-CSV -NoType -Del "`t" | Select -Skip 1 | Clip
[void]$WorkSheet.columns.Item(1).cells.Item(2).PasteSpecial()
From 15 min to 2 min. This really helps me a lot, i have to produce like 500+ Excel files.
I am trying to import an XML file into a MS SQL table using PowerShell, I have modified a PowerShell script I already had that worked fine but could do with some help as the new XML file has multiple entries for some elements.( I have included a sample of the XML file)
Table layout is below and as is a sample of the file I am trying to import and also my script I am trying to modify to get working.
Table layout is:
------------------------------
:supplier_id : name : Symbol :
and here is an part of the XML file itself
<SupplierMapping supplier_id="4536" name="Joby">
<Symbol>Joby</Symbol>
</SupplierMapping>
<SupplierMapping supplier_id="4537" name="ACT">
<Symbol>ACT</Symbol>
<Symbol>ADVANCED CABLE TECH</Symbol>
<Symbol>ADVANCED CABLE TECHNOLOGY</Symbol>
<Symbol>IEC LOCK</Symbol>
</SupplierMapping>
As you can see some supplier id's and names will have multiple <Symbol> names so I would like to have multiple entries in my table for those, however the script I have modified only seems to pull in the supplier_id and name elements and misses the <Symbol> part entirely. any help or guidance would be much appreciated.
Set-ExecutionPolicy Unrestricted -Scope LocalMachine
[String]$global:connectionString = "Data Source=Apps2\Apps2;Initial Catalog=DTEDATA;Integrated Security=SSPI";
[System.Data.DataTable]$global:dt = New-Object System.Data.DataTable;
[System.Xml.XmlTextReader]$global:xmlReader = New-Object System.Xml.XmlTextReader("C:\Scripts\icecat\supplier_mapping.xml");
[Int32]$global:batchSize = 100;
function Add-FileRow() {
$newRow = $dt.NewRow();
$null = $dt.Rows.Add($newRow);
$newRow["supplier_id"] = $global:xmlReader.GetAttribute("supplier_id");
$newRow["name"] = $global:xmlReader.GetAttribute("name");
$newRow["Symbol"] = $global:xmlReader.GetAttribute("Symbol");
}
# init data table schema
$da = New-Object System.Data.SqlClient.SqlDataAdapter("SELECT * FROM Supplier_Mapping_Test WHERE 0 = 1", $global:connectionString);
$null = $da.Fill($global:dt);
$bcp = New-Object System.Data.SqlClient.SqlBulkCopy($global:connectionString);
$bcp.DestinationTableName = "dbo.Supplier_Mapping_Test";
$recordCount = 0;
while ($xmlReader.Read() -eq $true) {
if (($xmlReader.NodeType -eq [System.Xml.XmlNodeType]::Element) -and ($xmlReader.Name -eq "SupplierMapping"))
Add-FileRow -xmlReader $xmlReader;
$recordCount += 1;
if (($recordCount % $global:batchSize) -eq 0) {
$bcp.WriteToServer($dt);
$dt.Rows.Clear();
Write-Host "$recordCount file elements processed so far";
}
}
}
if ($dt.Rows.Count -gt 0) {
$bcp.WriteToServer($dt);
}
$bcp.Close();
$xmlReader.Close();
Write-Host "$recordCount file elements imported ";
catch {
throw;
}
I have a script I need to use for multiple parameter data collection, as follows:
function Build-FormPanel($FormTitle){
Add-Type -Assembly System.Windows.Forms ## Load the Windows Forms assembly
## Create the main form
$form = New-Object Windows.Forms.Form
$form.FormBorderStyle = "FixedToolWindow"
$form.Text = $FormTitle
$form.AutoScroll = $True
$form.StartPosition = "CenterScreen"
$form.Width = 740 ; $form.Height = 480 # Make the form wider
#Add Buttons- ## Create the button panel to hold the OK and Cancel buttons
$buttonPanel = New-Object Windows.Forms.Panel
$buttonPanel.Size = New-Object Drawing.Size #(400,40)
$buttonPanel.Dock = "Bottom"
$cancelButton = New-Object Windows.Forms.Button
$cancelButton.Top = $buttonPanel.Height - $cancelButton.Height - 10; $cancelButton.Left = $buttonPanel.Width - $cancelButton.Width - 10
$cancelButton.Text = "Cancel"
$cancelButton.DialogResult = "Cancel"
$cancelButton.Anchor = "Right"
## Create the OK button, which will anchor to the left of Cancel
$okButton = New-Object Windows.Forms.Button
$okButton.Top = $cancelButton.Top ; $okButton.Left = $cancelButton.Left - $okButton.Width - 5
$okButton.Text = "Ok"
$okButton.DialogResult = "Ok"
$okButton.Anchor = "Right"
## Add the buttons to the button panel
$buttonPanel.Controls.Add($okButton)
$buttonPanel.Controls.Add($cancelButton)
## Add the button panel to the form
$form.Controls.Add($buttonPanel)
## Set Default actions for the buttons
$form.AcceptButton = $okButton # ENTER = Ok
$form.CancelButton = $cancelButton # ESCAPE = Cancel
return $form
}
$LeftMargin = 25
$BottomMargin = 30
$i = 0
$form = Build-FormPanel "Please update server configurations"
foreach($param in $hash){#Where $hash is an "dictionary" of key/value pairs
$k = $param.Key
$v = $param.Value
$lblValue = New-Object System.Windows.Forms.Label
$lblValue.Text = $k+":"
$lblValue.Top = 20*$i ; $lblValue.Left = $LeftMargin; $lblValue.Width=150 ;$lblValue.AutoSize = $true
$form.Controls.Add($lblValue) # Add to Form
#
$txtValue = New-Object Windows.Forms.TextBox
$txtValue.Top = 20*$i; $txtValue.Left = 160; $txtValue.Width = 320;
$txtValue.Text = $v
$form.Controls.Add($txtValue) # Add to Form
$i++
}
$form.Topmost = $True
$form.Add_Shown( { $form.Activate(); } )
$result = $form.ShowDialog()
if($result -eq "OK")
{
$j = 0;
foreach($param in $hash){
${"txtValue_$j"}.Text
$j++
}
}
else {Write-Host "Cancel"}
Basically, this works OK to display the form and inputs. But after submission, I am unable to capture all the user inputs. Only the last input value is captured, obviously because the variables get overwritten in the loop.
How can I achieve capturing the data as described?
The issue as you have mentioned is because of the its getting overwritten.
I can give you a logical set off.
YOu can use it in a loop and in the loop , you store all the data either in array or if its dynamic then you can use arraylist by using
New-Object System.Collections.ArrayList
. But recommended is to create PSCustomObject , store in that and add that to the arraylist each time.
Finally you can get the output captured in the arraylist.
Further you can try making the arraylist Global so that it will be available for the entire script.
Hope it helps.
I have some web script that I've adapted to run 7 T-SQL queries and output the results into 1 Excel workbook, one worksheet per query. I've just been asked if I can combine all 7 worksheets into one.
Here's my sample code which does copy a worksheet, however the entire column(s) are selected instead of just the UsedData. Also, the first worksheet's data on the destination worksheet is replaced by the second worksheets data.
Questions: Would it be simpler to get Powershell to output the 7 queries into One Excel Worksheet separated by two blank rows? Or modify the existing Powershell script to create the 7 worksheets then combine them into one?
Code is not pretty! I also have been really lost using $Excel = New-Object -ComObject excel.application followed by $Excel | Get-Member to explore how to get PowerShell to work with Excel. References on MSDN are usually for VB or C languages and I can't translate that into PowerShell.
--Edit, add code that stores 7 Query results in an array and outputs to the console. The data is correct but I'm just unsure how to approach piping that data into a single Excel Worksheet.
$docs = "C:\Temp\SQL\test.xlsx"
If (Test-Path $docs){Remove-Item $docs}
Function First-Query {
param([string[]]$queries)
$xlsObj = New-Object -ComObject Excel.Application
$xlsObj.DisplayAlerts = $false
## - Create new Workbook and Sheet (Visible = 1 / 0 not visible)
$xlsObj.Visible = 0
$xlsWb = $xlsobj.Workbooks.Add(1)
$xlsSh = $xlsWb.Worksheets.Add([System.Reflection.Missing]::Value, $xlsWb.Worksheets.Item($xlsWb.Worksheets.Coun))
$xlsSh.Name = 'Test'
for ($i = 0; $i -lt $queries.Count; $i++){
$query = $queries[$i]
$SQLServer = 'Server'
$Database = 'DataBase'
## - Connect to SQL Server using non-SMO class 'System.Data':
$SqlConnection = New-Object System.Data.SqlClient.SqlConnection
$SqlConnection.ConnectionString = "Server = $SQLServer; Database = $Database; Integrated Security = True"
$SqlCmd = New-Object System.Data.SqlClient.SqlCommand
$SqlCmd.CommandText = $query
$SqlCmd.Connection = $SqlConnection
## - Extract and build the SQL data object '$DataSetTable':
$SqlAdapter = New-Object System.Data.SqlClient.SqlDataAdapter
$SqlAdapter.SelectCommand = $SqlCmd;
$tables = New-Object System.Data.DataSet;
$SqlAdapter.Fill($tables)
$TableArray = #($tables)
$SqlConnection.Close()
$DataSetTable = $TableArray.Tables[0]
}#End For Loop
## - Build the Excel column heading:
[Array] $getColumnNames = $DataSetTable.Columns | Select ColumnName;
## - Build column header:
[Int] $RowHeader = 1;
foreach ($ColH in $getColumnNames){
$xlsSh.Cells.item(1, $RowHeader).font.bold = $true;
$xlsSh.Cells.item(1, $RowHeader) = $ColH.ColumnName;
$RowHeader++;
}
## - Adding the data start in row 2 column 1:
[Int] $rowData = 2;
[Int] $colData = 1;
foreach ($rec in $DataSetTable.Rows){
foreach ($Coln in $getColumnNames){
## - Next line convert cell to be text only:
$xlsSh.Cells.NumberFormat = "#";
## - Populating columns:
$xlsSh.Cells.Item($rowData, $colData) = `
$rec.$($Coln.ColumnName).ToString()
$ColData++
}
$rowData++; $ColData = 1
}
## - Adjusting columns in the Excel sheet:
$xlsRng = $xlsSH.usedRange
$xlsRng.EntireColumn.AutoFit() | Out-Null
#End for loop.
#Delete unwanted Sheet1.
$xlsWb.Sheets.Item('Sheet1').Delete()
#Set Monday to Active Sheet upon opening Workbook.
$xlsWb.Sheets.Item('Monday').Activate()
## ---------- Saving file and Terminating Excel Application ---------- ##
$xlsFile = "C:\Temp\SQL\test.xlsx"
$xlsObj.ActiveWorkbook.SaveAs($xlsFile) | Out-Null
$xlsObj.Quit()
## - End of Script - ##
start-sleep 2
While ([System.Runtime.Interopservices.Marshal]::ReleaseComObject($xlsRng)) {'cleanup xlsRng'}
While ([System.Runtime.Interopservices.Marshal]::ReleaseComObject($xlsSh)) {'cleanup xlsSh'}
While ([System.Runtime.Interopservices.Marshal]::ReleaseComObject($xlsWb)) {'cleanup xlsWb'}
While ([System.Runtime.Interopservices.Marshal]::ReleaseComObject($xlsObj)) {'cleanup xlsObj'}
[gc]::collect() | Out-Null
[gc]::WaitForPendingFinalizers() | Out-Null
}#End Function
$queries = #()
$queries += #'
SELECT DISTINCT
'#
First-Query -queries $queries
Not sure I really understand what your problem is but below is a "template" that might help you to do what you want. It shows you how you can create sheets and handle them. You'll have to fill the blanks (see the commented section, where you have to call your query function).
param (
[string] $ExcelFile = (Read-Host "Enter full path for Excel file")
)
try
{
$Error.Clear()
# http://support.microsoft.com/kb/320369
[System.Threading.Thread]::CurrentThread.CurrentCulture = [System.Globalization.CultureInfo] "en-US"
Push-Location
$scriptPath = Split-Path -parent $MyInvocation.MyCommand.Path
Set-Location $scriptPath
$Excel = New-Object -comobject Excel.Application
$Excel.Visible = $True
$WorksheetCount = 7
$Workbook = $Excel.Workbooks.Add()
$Workbook.Title = 'My Workbook'
$weekdays = #("Monday","Tuesday","Wednesday","Thursday","Friday","Saturday","Sunday")
($WorksheetCount - 1)..0 | %{
$sheet = $Excel.Worksheets.Add()
$sheet.Name = $weekdays[$_]
# $dataTable = Execute-Your-Query-Here-Returning-A-Data-Table
# $x = 0
# $dataTable | %{
# $sheet.cells.item($x, 1) = ...
# $sheet.cells.item($x, 2) = ...
# $x++
# }
}
$excel.ActiveWorkbook.SaveAs("$ExcelFile")
}
catch
{
"$($MyInvocation.InvocationName): $Error"
}
finally
{
Pop-Location
$Excel.Quit()
$Excel = $null
[gc]::collect()
[gc]::WaitForPendingFinalizers()
}
Ok, time to start this question over. I found the following script http://blogs.technet.com/b/heyscriptingguy/archive/2010/09/09/copy-csv-columns-to-an-excel-spreadsheet-by-using-powershell.aspx
My question is how would I get powershell to loop that script for any number of CSV files. The speed of the script is not important. I've received some answers of which, in some way, have their problems. Using the Technet script provides the correct output.
I have come up with this but am having a tough time getting the code to loop through multiple CSV files.
Function Excel-Stuff {
[cmdletBinding()]
Param([Parameter(ValueFromPipeline=$true)][string]$junk)
$excel.cells.item(1,1) = "Server"
$excel.cells.item(1,2) = "Rack"
$excel.cells.item(1,3) = "Environment"
$excel.cells.item(1,4) = "RebootTime"
$excel.cells.item(1,5) = "Schedule"
$i = 2
$processes = Import-Csv 'C:\Monday.csv'
foreach ($process in $processes){
$excel.cells.item($i,1) = $process.Server
$excel.cells.item($i,2) = $process.Rack
$excel.cells.item($i,3) = $process.Environment
$excel.cells.item($i,4) = $process.RebootTime
$excel.cells.item($i,5) = $process.Schedule
$i++
} #end foreach process
$autofit = $Global:worksheet.UsedRange
$autofit.EntireColumn.AutoFit() | Out-Null
}#End Function.
$Excel = New-Object -ComObject excel.application
$workbook = $Excel.workbooks.add(1)
$Global:worksheet = $workbook.WorkSheets.Item(1)
$Global:worksheet.Name='Monday'
Excel-Stuff
$Excel.visible = $True
Ok, so let's not do this the hard way. Copy/paste is your friend, and you can do it easily here.
You have Monday-Friday tabs, and I assume 7 CSV files. Loop this thing 7 times, and in each loop Create a tab, name it, then copy the entire CSV, convert it to a tab delimited CSV, and pipe it to CLIP.exe. Then just select A1 on the current sheet and paste. Start the next loop with the next CSV file.
$path = "c:\tmp\mytest.xlsx"
$day = #("Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday")
$Excel = New-Object -ComObject excel.application
$Excel.visible = $false
$workbook = $Excel.workbooks.add(1)
$day | %{
$processes = Import-Csv -Path "D:\Scripts\work\$_.csv"
$worksheet = $workbook.WorkSheets.add()
$processes | ConvertTo-Csv -Delimiter "`t" -NoTypeInformation | Clip.exe
$worksheet.select()
$worksheet.Name = $_
[void]$Excel.ActiveSheet.Range("A1:A1").Select()
[void]$Excel.ActiveCell.PasteSpecial()
[void]$worksheet.UsedRange.EntireColumn.AutoFit()
}
#Clean up extra sheets
$Excel.DisplayAlerts = $false
$workbook.Worksheets|?{$day -notcontains $_.name}|%{$_.Delete()}
$Excel.DisplayAlerts = $true
[void]$Excel.ActiveSheet.Range("A1:A1").Select()
$workbook.saveas($path)
$Excel.Quit()
Remove-Variable -Name excel
[gc]::collect()
[gc]::WaitForPendingFinalizers()
Then your only concern is that the CSV files are in the right order. If you named your CSV files Monday.csv, Tuesday.csv etc this could probably be simpler, but so long as they're named so that Monday's is the first, and it goes in alpha-numeric order you'll be just fine with that code.
(Edited to reflect comment suggestions for single cell selection before saving and changed -notin to -notcontains)