How to find a column number in excel sheet with PowerShell - powershell

I have a working script that search rows in the specific column but I need to set the number of the column, So before I run the script I am writing =column() in a specific row in that column. I want to know how to get this number according to the columns name. This will give me the option to let the user choose on which column to work.
Open the sheet and search in a specific workbook:
$excel = New-Object -ComObject Excel.Application
$Excel.Visible = $false
$Excel.DisplayAlerts = $False # Disable comfirmation prompts
$workbook = $excel.Workbooks.Open($ExcelFile)
$worksheet = $workbook.Worksheets.Item("VIP List")
Search for a specific row color:
$columnNumber = #
$patches = for ($row = 1; $row -le $rowMax; $row++) {
$val = $worksheet.Cells.Item($row, $columnNumber).Interior.ColorIndex # 2 is column B
if ($val -eq $searchcolorForPatch) {
# output an object with both values from columns A and B
[PsCustomObject]#{Patch = $worksheet.Cells.Item($row, 1).Value2}
}
}
I want to display all the column names in the workbook with his number and let the user to choose.
It is possible?

You can get the names and belonging indices like this:
$colMax = $sheet.UsedRange.Columns.Count
# create a hash with column header names and their indices
$columns = [ordered]#{}
for ($col = 1; $col -le $colMax; $col++) {
$name = $sheet.Cells.Item(1, $col).Value() # assuming the first row has the headers
$columns[$name] = $col
}
You now have an ordered Hashtable of the column names and their indices
For instance:
$columns['FirstColumn'] # --> 1
$columns['SomeColumnInBetween'] # --> 12

Related

PowerShell-How to get values from this JSON file

This is the partial data after removed the confidential information.
{
"WebhookName":"Azure-CustomAlert-Webhook",
"RequestBody":"{\"schemaId\":\"azureMonitorCommonAlertSchema\",\"data\":{\"essentials\":{\"alertId\":\"/subscriptions/XXXXXXXXX/providers/Microsoft.AlertsManagement/alerts/XXXXXXX\",\"alertRule\":\"Low Memory\",\"severity\":\"Sev3\",\"signalType\":\"Log\",\"monitorCondition\":\"Fired\",\"monitoringService\":\"Log Analytics\",\"alertTargetIDs\":[\"/subscriptions/XXXXX/resourcegroups/XXXX-RG/providers/microsoft.operationalinsights/workspaces/workspacename\"],\"configurationItems\":[\"USE2V5TMP9001\"],\"originAlertId\":\"XXXXX\",\"firedDateTime\":\"2022-03-09T17:49:41.4631455Z\",\"description\":\"Triggers an alert for a low memory condition\",\"essentialsVersion\":\"1.0\",\"alertContextVersion\":\"1.1\"},\"alertContext\":{\"SearchQuery\":\"Perf | where ( CounterName == \\\"% Used Memory\\\" or CounterName == \\\"% Committed Bytes In Use\\\" ) | where Computer contains (\\\"TMP\\\") | summarize AggregatedValue = avg(CounterValue) by Computer, bin(TimeGenerated, 5m)\",\"SearchIntervalStartTimeUtc\":\"2022-03-09T17:42:10Z\",\"SearchIntervalEndtimeUtc\":\"2022-03-09T17:47:10Z\",\"ResultCount\":1,\"SeverityDescription\":\"Informational\",\"WorkspaceId\":\"XXXXX\",\"SearchIntervalDurationMin\":\"5\",\"AffectedConfigurationItems\":[\"USE2V5TMP9001\"],\"AlertType\":\"Metric measurement\",\"IncludeSearchResults\":true,\"Dimensions\":[{\"Name\":\"Computer\",\"Value\":\"USE2V5TMP9001\"}],\"SearchIntervalInMinutes\":\"5\",\"SearchResults\":{\"tables\":[{\"name\":\"PrimaryResult\",\"columns\":[{\"name\":\"Computer\",\"type\":\"string\"},{\"name\":\"TimeGenerated\",\"type\":\"datetime\"},{\"name\":\"AggregatedValue\",\"type\":\"real\"}],\"rows\":[[\"USE2V5TMP9001\",\"2022-03-09T17:42:10Z\",38.267662048339851]]}],\"dataSources\":[{\"resourceId\":\"/subscriptions/XXXXX/resourcegroups/XXXX/providers/microsoft.operationalinsights/workspaces/XXXX\",\"region\":\"eastus2\",\"tables\":[\"Perf\"]}]},\"Threshold\":9,\"Operator\":\"Greater Than\",\"IncludedSearchResults\":\"True\"},\"customProperties\":null}}",
"RequestHeader":{
"Connection":"Keep-Alive",
"Expect":"100-continue",
"Host":"xxxx.webhook.eus2.azure-automation.net",
"User-Agent":"IcMBroadcaster/1.0",
"X-CorrelationContext":"RkkKACgAAAACAAAAEADvqM+sXFG+SYkp7Tcy2IZaAQAQAMflO8/GhoFLrHCgd8ILz2o=",
"x-ms-request-id":"8fdd10d2-4a36-43a5-8e65-4eb20f3b9865"
}
}
The above json i got it from the Azure Log Search alert and trying to customize it.
From the above json can i get the values if i refer the column section keys?.
Ex: If i mention computer then i should be able to get value USE2V5TMP9001 and AggregatedValue is 38.267662048339851.
Use the columns array to map the individual row values to the correct type and property name:
$json = #'
<json goes here>
'#
$data = $json |ConvertFrom-Json
$columnDefinitions = $data.columns
$rows = foreach($row in $data.rows){
# prepare dictionary to hold the individual column values
$properties = [ordered]#{}
for($i = 0; $i -lt $row.Length; $i++){
# extract value and column metadata
$value = $row[$i]
$name = $columnDefinitions[$i].name
$type = $columnDefinitions[$i].type
# make sure to translate any type names if necessary
# eg. translate `real` -> `decimal`
if($type -eq 'real'){ $type = 'decimal' }
# convert value to correct type and store in property dictionary
$properties[$name] = $value -as $type
}
# create new object based on the row values
[pscustomobject]$properties
}
$rows will now contain 1 or more objects with the expected values so you can now do:
$rows |ForEach-Object {
# this now resolves `USE2V5TMP9001`
$_.computer
}

Powershell: get column number from CSV file header

I need to get the column number from an imported CSV based on a particular column name ($_."Status"). Once I have the correct column number, I can assign it to a variable and use it in a foreach loop to write text to the corresponding cells. $wsSource.cells.item($tr,49) = "Added by xyz)" Note that the column position often varies from file to file.
I already have the index/row number via $tr = $source.IndexOf($row) ...but struggling with the col number.
Thanks in advance,
Jason
Incomplete code from much larger PS script that writes two different excel files in the one loop:
$source = Import-Csv $csvFile
$i = 2
foreach($row in $source.where{$_.Contacted -like "*Invalid"})
{
$tr = ($source.IndexOf($row)+2)
$wsTemp.cells.item($i,4) = $timeStamp
$wsTemp.cells.item($i,10) = $row."Last Name"
$wsSource.cells.item($tr,49) = "Added by xyz)"
$wsSource.cells.item($tr,49).Interior.ColorIndex =19
$i++
}
}
elseif ...
You need to create a Hashtable to map the Excel column names with their index:
# create a hash with Excel column header names and their indices
$colMax = $wsSource.UsedRange.Columns.Count
$xlHeaders = #{}
for ($col = 1; $col -le $colMax; $col++) {
$name = $wsSource.Cells.Item(1, $col).Value() # assuming the first row has the headers
$xlHeaders[$name] = $col
}
Now you can match the column from the Csv with the column index in Excel like
if (!$xlHeaders.ContainsKey('Status')) {
Write-Warning "Excel sheet does not have a column named 'Status'"
}
else {
$xlColumn = $xlHeaders['Status']
$wsSource.Cells.Item($tr, $xlColumn) = "Added by xyz)"
$wsSource.Cells.Item($tr, $xlColumn).Interior.ColorIndex = 19
}

How to confirm data changes in DataGridView GUI (PowerShell)?

In my case i want to use PS script to build WinForm with some elements including DGV contains of 3 columns (#, Page_Name, shrt). First row need to be template row with default values(1;index;NDX)so i get it from csv-file.My code:
$DataGridView1 = New-Object system.Windows.Forms.DataGridView
$DataGridView1.location = New-Object System.Drawing.Point(20,121)
$DataGridView1.Name = "Page-List"
$DataGridView1.AllowUserToAddRowsChanged = $true
$DataGridView1.AllowUserToAddRows = $true
# $DataGridView1.DataBindings
$DataGridView1.width = 363
$DataGridView1.height = 150
$DataGridView1.ColumnCount = 3
$DataGridView1.ColumnHeadersVisible = $true
$DataGridView1.Columns[0].Name = '#'
$DataGridView1.Columns[0].Width = "40"
$DataGridView1.Columns[1].Name = "Page_Name"
$DataGridView1.Columns[1].Width = "205"
$DataGridView1.Columns[2].Name = "shrt"
$DataGridView1.Columns[2].Width = "75"
$DataGridView1.ReadOnly = $false
$DataGridView1.EditMode = "EditOnEnter"
$templateROW = #(Import-Csv -Delimiter ";" "C:\Users\vkons\OneDrive\Документы\PowerShell\Scripts\test\DGV\index.csv" -Header "#", "Page_Name", "shrt" )
$datatable = ($templateROW + $DataGridView1Rows)
$DataGridView1Data = $datatable
foreach ($Row in $DataGridView1Data){
$DataGridView1.Rows.Add($Row.'#', $Row.Page_Name, $Row.shrt)
}
If user will change Page_Name cells value in first row or will fill Page_Name cell`s in the next row (or rowS) - cells value in column "#" and column "shrt" in edited row(s) would get values programmly by this part code:
$DataGridView1.Add_CellValueChanged({autofill})
Function autofill{
$Numbr = $DataGridView1.CurrentRow.Index+1
$DataGridView1.CurrentRow.Cells[0].value = $Numbr
$Name_Page = $DataGridView1.CurrentRow.Cells[1].value
$preshrt = $Name_Page.ToString($Value) -ireplace "[aoueyi]"
$preshrt = $preshrt.ToUpper()
$shrt = $preshrt[0]+$preshrt[1]+$preshrt[2]
$DataGridView1.CurrentRow.Cells[2].value = $shrt
}
My main target is getting the values of all cells in a column Page_Name as a variable(or as array). So I tried to add next string to the function above.
$Pages = $Row.Page_Name+$DataGridView1.CurrentRow.Cells[1].value
But it returns nothing...(Either $Row.Page_Name)
I can get values of all cells in all rows by
$Page_NamesListRows = #($DataGridView1.Rows.Cells.Value)
(Unfortunately) it returns varriable, consist of all existing cells, not rows array.But when i try
$Page_Names = $DataGridView1.Rows.Cells[1].Value
or
$Page_Names = $DataGridView1.Columns[1].Cells.Value
to get only Names of the Pages, it returns error "cannot get value of a null-array" (either in case with #(...) for right part)
Could anybody answer... Is there any way to get values of all existing cells in "Page_Name" Column.Honestly it doesnt matter would the DGVData automaticly edit by changing cells value event or not.
I need to get only column "Page_Name" values.
In the end I want to apologize for my english language. It has rather poor level. And thank the moderator in advance for corrections my mistakes.
I'm afraid you will have to get the array of values by looping over the rows in the "Page_Name" column.
The last row in the DataGridView will always be the "New" row to create by the user, so you need to skip that one.
Either by doing this:
# -1 to skip the always present empty row at the bottom
$Page_Names = for($row = 0; $row -lt $DataGridView1.Rows.Count - 1; $row++) {
$DataGridView1.Rows[$row].Cells.Item("Page_Name").Value
}
Or something like:
$Page_Names = $DataGridView1.Rows | ForEach-Object {
$data = $_.Cells.Item("Page_Name").Value
if ($data) { $data }
}
Or:
$Page_Names = foreach ($row in $DataGridView1.Rows) {
$row.Cells.Item("Page_Name").Value
}
$Page_Names = $Page_Names[0..($Page_Names.Count - 2)]
The last alternative is costly, because it needs to recreate the entire array when removing the last item
P.S.1 Don't forget to call the Dispose() methods on both the $DataGridview1 object and the main form when done with the GUI
P.S.2 I don't see a property called AllowUserToAddRowsChanged on the DataGridView..
EDIT
To hopefully show better what I mean, here's a demo form with a DataGridView control on it.
The initial data comes from a dummy CSV file with this inside:
"1";"Page number 1";"PN1"
"2";"Page number 2";"PN2"
"3";"Page number 3";"PN3"
"4";"Page number 4";"PN4"
"5";"Page number 5";"PN5"
$form = New-Object System.Windows.Forms.Form
$form.ClientSize = New-Object Drawing.Size 580, 505
$form.text = "20/4/2020 v. 0.1 All Right reserved (c) "
$form.TopMost = $true
$DataGridView1 = New-Object system.Windows.Forms.DataGridView
$DataGridView1.Location = New-Object System.Drawing.Point 20,25
$DataGridView1.Width = 363
$DataGridView1.Height = 150
$DataGridView1.AllowUserToAddRows = $true
$DataGridView1.Name = "Page-List"
$DataGridView1.ColumnCount = 3
$DataGridView1.Columns[0].Name = '#'
$DataGridView1.Columns[0].Width = "40"
$DataGridView1.Columns[1].Name = 'Page_Name'
$DataGridView1.Columns[1].Width = "205"
$DataGridView1.Columns[2].Name = "shrt"
$DataGridView1.Columns[2].Width = "75"
$DataGridView1.AllowUserToAddRows = $true
$DataGridView1.ReadOnly = $false
# Populate the DGV with the data from the CSV
$CsvData = Import-Csv -Path 'D:\Test\TEMPLATE_ROW.csv' -Delimiter ";" -Header "#", "Page_Name", "shrt"
foreach ($row in $CsvData) {
[void]$DataGridView1.Rows.Add($row.'#', $row.Page_Name, $row.shrt)
}
# add the DGV to the form
$form.Controls.Add($DataGridView1)
# show the form and capture the result so you can check if the user cancelled or pressed OK
$result = $form.ShowDialog()
# at this point, you can read the data from the DataGridView column of interest
$Page_Names = for($row = 0; $row -lt $DataGridView1.Rows.Count - 1; $row++) {
$DataGridView1.Rows[$row].Cells.Item("Page_Name").Value
}
# cleanup memory by destroying the DGV and the from
$DataGridView1.Dispose()
$form.Dispose()
In variable $Page_Names you will now have the data from the "Page_Name" column in the DataGridView control.
# show on screen
$Page_Names

Global variable loses value outside loop

I'm working on a script to extract data from BLOBs in a SQL database. The extraction process works great. I want to add some sort of progress indication to the script. I have a total record count from a SQL query, and an incremental counter that increases for each file exported. The incremental counter works, but the total record count - which I attempted to assign to a global variable - does not seem to hold its value. Am I declaring it incorrectly?
## Export of "larger" Sql Server Blob to file
## with GetBytes-Stream.
# Configuration data
$StartTime = Get-Date
$Server = "server";
$UserID = "user";
$Password = "password";
$Database = "db";
$Dest = "C:\Users\me\Desktop\Test\";
$bufferSize = 8192;
# Counts total rows
$CountSql = "SELECT Count(extension) as countall from
(
SELECT p.[people_id], right(pi.[file_name],4) as extension
FROM dbo.pictures as pi
INNER JOIN dbo.people AS p ON p.person_picture = pi.pictures_id
where left([image_type], 5) = 'image'
) as countall"
# Selects Data
$Sql = "SELECT p.[people_id], pi.[image_file], right(pi.[file_name],4), ROW_NUMBER() OVER (ORDER BY people_id) as count
FROM dbo.pictures as pi
INNER JOIN dbo.people AS p ON p.person_picture = pi.pictures_id
where left([image_type], 5) = 'image'";
# Open ADO.NET Connection
$con = New-Object Data.SqlClient.SqlConnection;
$con.ConnectionString = "Data Source=$Server;" +
"Integrated Security=False;" +
"User ID=$UserID;" +
"Password=$Password;" +
"Initial Catalog=$Database";
$con.Open();
# New Command and Reader for total row count
$CountCmd = New-Object Data.SqlClient.SqlCommand $CountSql, $con;
$crd = $CountCmd.ExecuteReader();
While ($crd.Read())
{
$crd.GetValue($global:1)
}
$crd.Close();
# New Command and Reader for rest of data
$cmd = New-Object Data.SqlClient.SqlCommand $Sql, $con;
$rd = $cmd.ExecuteReader();
# Create a byte array for the stream.
$out = [array]::CreateInstance('Byte', $bufferSize)
# Looping through records
While ($rd.Read())
{
$total = $global:1
$counter = ($rd.GetValue(3));
Write-Output ("Exporting $counter of $total`: {0}" -f $rd.GetGUID(0));
# New BinaryWriter
$fs = New-Object System.IO.FileStream ($Dest + $rd.GetGUID(0) + $rd.GetString(2)), Create, Write;
$bw = New-Object System.IO.BinaryWriter $fs;
$start = 0;
# Read first byte stream
$received = $rd.GetBytes(1, $start, $out, 0, $bufferSize - 1);
While ($received -gt 0)
{
$bw.Write($out, 0, $received);
$bw.Flush();
$start += $received;
# Read next byte stream
$received = $rd.GetBytes(1, $start, $out, 0, $bufferSize - 1);
}
$bw.Close();
$fs.Close();
}
# Closing & Disposing all objects
$fs.Dispose();
$rd.Close();
$cmd.Dispose();
$con.Close();
$EndTime = Get-Date
$TotalTime = $EndTime - $StartTime
Write-Host ("Finished in {0:g}" -f $TotalTime)
OUTPUT
PS C:\Users\me> C:\Scripts\ExportImagesFromNTST.ps1
21380
Exporting 1 of : 3089b464-e667-4bf4-80b3-0002d582d4fa
Exporting 2 of : 04cf7738-ae19-4771-92b8-0003c5f27947
Exporting 3 of : 94485b5d-fe71-438d-a097-000ad185c915
and so on. 21380 should be $1 which should also be $total.
I think PetSerAl hit the nail on the head here. You create a SqlCommand object ($CountCmd), and from that create a SqlDataReader ($crd), and then tell $crd to use the GetValue() method that accepts an integer as a parameter, so that it knows which column to return the value of, but you reference a global variable with a name of '1', which is never defined, so you effectively pass $null to that method, so it doesn't get any value. I'm honestly surprised that it doesn't throw errors at you right there. You would probably want to just pass the integer 1 as the argument for that method, and assign it to $Total in the While loop. I'm honestly guessing here, but from what I see I think it should be:
$crd = $CountCmd.ExecuteReader();
While ($crd.Read())
{
$Total = $crd.GetValue(0)
}
$crd.Close();
I'm pretty sure that will assign the value of the first column (which for that sql command should just be 1 row with 1 column, right? Just the total count?), anyway, assign the first column's value for the current row to $Total. Then later you can reference $Total just fine to update your progress.
You sir, need to look into the write-progress cmdlet if you want to track progress, it's perfect for your script.

Powershell Word Table single row error

I have the following function that I provide 3 arrays as variables
$columnHeaders = #('Ticket ID', 'Date Raised', 'Title', 'Status' )
$columnproperties = #('number', 'opened_at', 'short_description', 'state')
$contents
$contents has multiple rows of data matching the columns above, however sometimes may only have 1 row. When the $contents only has 1 row the below function errors out and doesnt print the data.
Using ISE I traced the issue to the $contents.count not showing a value, why is this? is there a way to get around it?
function TableOutput ($columnHeaders, $columnProperties, $contents){
# Number of columns
$columnCount = $columnHeaders.Count
# Create a new table
$docTable = $Word.ActiveDocument.Tables.Add($Word.Selection.Range,$contents.Count,$columnCount)
# Table style
$doctable.Style = "Adapt Table"
# Insert the column headers into the table
for ($col = 0; $col -lt $columnCount; $col++) {
$cell = $docTable.Cell(1,$col+1).Range
$cell.Font.Bold=$true
$cell.InsertAfter($columnHeaders[$col])
}
$doctable.Rows.Add() > Null
# Load the data into the table
$i = 1
$j = $contents.Count
for($row = 2; $row -lt ($contents.Count + 2); $row++){
if($row -gt 2){
}
for ($col = 1; $col -le $columnCount; $col++){
Write-Progress -Activity "Processing Table Information" -Status "Adding Row entry $i of $j" -PercentComplete (100*$i/$j)
$cell = $docTable.Cell($row,$col).Range
$cell.Font.Name="Calibri"
$cell.Font.Size="10"
$cell.Font.Bold=$FALSE
$cell.Text = $contents[$row-2].($columnProperties[$col-1])
}
$i++
}
$doctable.Columns.AutoFit()
}
any help is greatly appreciated.
Cast $content as an array of strings and see if that doesn't work better for you.
function TableOutput ($columnHeaders, $columnProperties, [string[]]$contents){
Edit: Sorry, my bad, you are passing objects with properties ad descripbed in $columnheaders, so you would need to cast it as an array of objects instead:
function TableOutput ($columnHeaders, $columnProperties, [object[]]$contents){
Tested on my end, it works fine with 1 object being passed to the function, as well as an array of two objects being passed to the function.