Read Excel values and create variables from column names in PowerShell - powershell

Say I have an Excel file:
I need to create n variables called $col1 up to $coln and read in their respective values.
What is the best approch? I have been trying with a hashtable but I need to be able loop through the columns. This is my code. I have not wraped it around a loop yet. I can create the column names manually but I need to be able to index the columns in the Excel file.
$ExcelRowToRead = 2;
$ListOfColumns = #{"Job_name" = 1 ; "Run_time_start" = 2}
$excel = New-Object -ComObject excel.application;
$workbook = $excel.Workbooks.Open("pathtofile.xlsx");
$workbook.sheets.item(1).activate()
$WorkbookTotal=$workbook.Worksheets.item(1)
$ListOfColumns["Job_name"] = $WorkbookTotal.Cells.Item($ExcelRowToRead, 1) # This needs to be an index
$ListOfColumns["Job_name"].Text

Taking Lee_Dailey's advice here and saving first to a CSV:
$excel = New-Object -ComObject excel.application
$workbook = $excel.Workbooks.Open("pathtofile.xlsx")
$sheet = $workbook.Sheets |
Where-Object Name -eq "SheetName" |
Select-Object -First 1
$tempCsv = New-TemporaryFile
$sheet.SaveAs($tempCsv, 6)
$sheetData = Get-Content $tempCsv | ConvertFrom-Csv
At this point you have an object where you can extract its properties via $sheetData.col1, $sheetData.col2, etc. but if you really need them as separate variables, perhaps:
$sheetData |
Get-Member |
Where-Object MemberType -Eq NoteProperty |
ForEach-Object {
New-Variable -Name $_.Name -Value $sheetData.$($_.Name)
}

In case you don't have / want Excel:
via GitHub - dfinke/ImportExcel
> # Install-Package ImportExcel
> $sheetData = Import-Excel '.\Excel 001.xlsx'
> $sheetData[0]
col1 col2 col3
---- ---- ----
value1 value2 value3
> $sheetData[0] |
Get-Member |
Where-Object { $_.MemberType -eq 'NoteProperty' } |
Select-Object -Property Name
Name
----
col1
col2
col3
> $sheetData |
Get-Member |
Where-Object MemberType -Eq NoteProperty |
ForEach-Object {
New-Variable -Name $_.Name -Value $sheetData.$($_.Name)
}
> Get-Variable col*
Name Value
---- -----
col1 value1
col2 value2
col3 value3

Related

SQL GROUP BY with SUM in PowerShell

What is the simplest way to write this in PowerShell:
SELECT col1, SUM(col3) AS SumOfValues
FROM dbo.Table
GROUP BY col1
How can I combine Group-Object and Measure-Object?
I have found this answer link, but there must be a simpler way.
Like this? With a calculated property at the end.
'id,amount
1,4
1,5
2,4
2,6
3,3
3,5' | convertfrom-csv | group id | select name,
#{n='Sum';e={$_.group.amount | measure -sum | % sum}}
Name Sum
---- ---
1 9
2 10
3 8
You need to use the property GROUP. The script outputs "$group | Format-Table" which shows what the real issue is.
This is what each row of the Group Looks like :
Count Name Group
----- ---- -----
3 a {#{col1=a; col2=x; col3=1}, #{col1=a; col2=x; col3=2}, #{col1=a; col2=x; col3=3}}
See code below :
$input = #"
col1,col2,col3
a,x,1
a,x,2
a,x,3
b,x,4
b,x,5
b,x,6
c,x,7
c,x,8
c,x,9
"#
$table = $input | ConvertFrom-Csv
$table | Format-Table
$groups = $table | Group-Object {$_.col1}
$outputTable = [System.Collections.ArrayList]::new()
foreach($group in $groups)
{
$group | Format-Table
$newRow = New-Object -TypeName psobject
$newRow | Add-Member -NotePropertyName col1 -NotePropertyValue $group.Name
$sum = 0
$group.Group | foreach{$sum += $_.col3}
Write-Host "sum = " $sum
$newRow | Add-Member -NotePropertyName SumOfValues -NotePropertyValue $sum
$outputTable.Add($newRow) | Out-Null
}
$outputTable | Format-Table

If a user exists in both objects, return their value from object 1 [Powershell]

I'm trying to compare two different objects and return the ID of the user from Object 1 if their email address exists in object 2.
I.e. Object 1
| user | id | emailaddress |
+-----------+------------+--------------------+
| test user | asfasfasdf | test.user#test.com |
| ima test | bsvxcffasd | ima.test#test.com |
+-----------+------------+--------------------+
Object 2
+--------------------+
| email |
+--------------------+
| test.user#test.com |
| ima.test#test.com |
+--------------------+
Consider the 2 objects above, my goal objective is to check if a user exists in Object 2 and Object 1. If they exist in Object 2 then I want to return their ID value.
This code is where i'm up to, this will return the users who email address exists in both objects but not their ID:
$x = $object1 | Select-Object -ExpandProperty emailaddress
$y = $object2 | Select-Object -ExpandProperty email
$z = Compare-Object $x $y -IncludeEqual -ExcludeDifferent
$userids = #()
foreach($a in $z.inputobject){
if($object2.email -contains $a){
$userids += $a
}
}
Attempt 2 based on Olaf's reply:
$object1 = New-Object -Typename psobject
$object1 | Add-Member -MemberType NoteProperty -Name email -Value $otherobject.members.email
$object1 | Add-Member -MemberType NoteProperty -Name id -Value $otherobject.members.id
$object2 = New-Object -Typename psobject
$object2 | Add-Member -MemberType NoteProperty -Name email -Value $otherobject2.emailaddress
$ComparedUsers = Compare-Object -ReferenceObject $object1 -DifferenceObject $object2 -IncludeEqual -ExcludeDifferent -PassThru
You should not use -ExpandProperty when you want to use other properties of the object as well. And I'd recommend to use the same property names for both objects.
Something like this should push you to the right direction:
$object1 =
#'
user,id,email
testuser,asfasfasdf,test.user#test.com
imatest,bsvxcffasd,ima.test#test.com
other,lkjshfdlakjs,other.test#test.com
'# |
ConvertFrom-Csv
$object2 =
#'
email
test.user#test.com
ima.test#test.com
any.test#test.com
'# |
ConvertFrom-Csv
Compare-Object -ReferenceObject $object1 -DifferenceObject $object2 -Property 'email' -IncludeEqual -ExcludeDifferent -PassThru
The output of that would be ...
user id email SideIndicator
---- -- ----- -------------
testuser asfasfasdf test.user#test.com ==
imatest bsvxcffasd ima.test#test.com ==

Change arraylist rows to column using Powershell

I am trying to change the format of an arraylist after I have used
group-object
to count all the entries in the list.
this is a sample
$list = [System.Collections.ArrayList]#()
$list = "letter","solo","nap","nap","nap","sharp","ignite","tap","tap","tap","tap","evoke"
$list | Group-Object | select name,count
This is the sample output
Name Count
---- -----
letter 1
solo 1
nap 3
sharp 1
ignite 1
tap 4
evoke 1
What I would like is
letter solo nap sharp ignite tap evoke
-------- ----- ---- ---- ----- ------ ----
1 1 3 4 1 4 1
Then when exporting to excel it would format like this
Everything I have tried doesn't seem to pay off, or even get close to what I am trying to do and I think I am missing something obvious or have hit my PowerShell skill limitations. Could someone please help. Thank you
You could create a PSObject, add the properties to it with Add-Member, then format the output to a table with Format-Table:
$list = "letter","solo","nap","nap","nap","sharp","ignite","tap","tap","tap","tap","evoke"
$groups = $list | Group-Object | Select-Object Name, Count
$psObject = New-Object -TypeName psobject
foreach ($group in $groups) {
$psObject | Add-Member -NotePropertyName $group.Name -NotePropertyValue $group.Count
}
$psObject | Format-Table
Output:
evoke ignite letter nap sharp solo tap
----- ------ ------ --- ----- ---- ---
1 1 1 3 1 1 4
Skip Group-Object altogether - instead, use a dictionary to keep track of the count, then cast the whole dictionary to a custom object:
$properties = [ordered]#{}
$list |ForEach-Object {
$properties[$_]++
}
$counts = [pscustomobject]$properties
$counts will now hold an object like what you describe, formatting as a table gives you:
PS C:\> $counts |Format-Table
letter solo nap sharp ignite tap evoke
------ ---- --- ----- ------ --- -----
1 1 3 1 1 4 1
You may try something like:
$list = [System.Collections.ArrayList]#()
$list = "letter","solo","nap","nap","nap","sharp","ignite","tap","tap","tap","tap","evoke"
$group = $list | Group-Object | select name,count
$a = [PSCustomObject]#{}
foreach ($item in $group) {
$a | Add-Member -NotePropertyName $item.name -NotePropertyValue $item.count
}
$a | ft
One solution would be to put it into an PsObject and then export that object into a CSV:
$list = [System.Collections.ArrayList]#()
$list = "letter","solo","nap","nap","nap","sharp","ignite","tap","tap","tap","tap","evoke"
$hash = $list | Group-Object | select name,count
$object = New-Object psobject
foreach( $item in $hash ) {
$column_name = $item.Name
$row_value = $item.Count
$object | Add-Member -MemberType NoteProperty -Name $column_name -Value $row_value
}
$object | Export-csv 'Path to your CSV' -NoTypeInformation

How to add header to a CSV file initially with PowerShell

I want to add headers to a CSV file initially. The reason I want to add the headers initially is for some rows, some column values might be empty.
As per Microsoft documentation, the export-csv only takes headers/columns which is present in first row.
When you submit multiple objects to Export-CSV, Export-CSV organizes the file >based on the properties of the first object that you submit. If the remaining >objects do not have one of the specified properties, the property value of >that object is null, as represented by two consecutive commas. If the >remaining objects have additional properties, those property values are not >included in the file.
https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.utility/export-csv?view=powershell-6#notes
What I have tried so far:
$csvContents =#()
foreach ($item in $items) {
$row = New-Object System.Object # Create an object to append to the array
$row | Add-Member -MemberType NoteProperty -Name "header1" -Value "value1"
$row | Add-Member -MemberType NoteProperty -Name "header2" -Value "value2"
$row | Add-Member -MemberType NoteProperty -Name "header3" -Value "value3"
$csvContents += $row # append the new data to the array
}
$csvContents | Export-CSV -NoTypeInformation -Path $ResultCsvPath
The problem is for example Item1 may have only header1 and header3, means these columns are dynamic. So after export the result csv will only contain header1 and header3 only and header2 will be missing.
What I expect is that I want to add header1, header2, header3 initially.
With large collections this may take up some time, but here's something that might work for you:
Suppose this is your collection
$obj = #(
[pscustomobject]#{
'header1' = 'value1'
'header3' = 'value3'
},
[pscustomobject]#{
'header1' = 'value1'
'header2' = 'value2'
},
[pscustomobject]#{
'header3' = 'value3'
},
[pscustomobject]#{
'header1' = 'value1'
'header2' = 'value2'
'header3' = 'value3'
}
)
Then you can add the missing properties like:
# Try and find all headers by looping over all items.
# You could change this to loop up to a maximum number of items if you like.
# The headers will be captured in the order in which they are found.
$headers = $obj | ForEach-Object {($_.PSObject.Properties).Name} | Select-Object -Unique
# Find the missing headers in the first item of the collection
# and add those with value $null to it.
$headers | Where-Object { ($obj[0].PSObject.Properties).Name -notcontains $_ } | ForEach-Object {
$obj[0] | Add-Member -MemberType NoteProperty -Name $_ -Value $null
}
# output on console
$obj
# output to csv file
$obj | Export-Csv -Path 'D:\test.csv' -NoTypeInformation
Output:
header1 header3 header2
------- ------- -------
value1 value3
value1 value2
value3
value1 value3 value2
Make sure if the data is missing for the column in a row that you use a $null value. I just tested this and get the output you are expecting.
$row = New-Object System.Object # Create an object to append to the array
$row | Add-Member -MemberType NoteProperty -Name "header1" -Value "value1"
$row | Add-Member -MemberType NoteProperty -Name "header2" -Value $null
$row | Add-Member -MemberType NoteProperty -Name "header3" -Value "value3"
$row | Export-Csv -NoTypeInformation test.csv
Output (from CSV)
"header1","header2","header3"
"value1",,"value3"
Depending on how complex you want to go, Ive done something similar to this in the past (I;ve changed it to use [pscustomobject] too):
$csvcontents = $items | foreach-object {
If (-not $_.Header1) { $value1 = '' } Else { $value1 = $_.Value1 }
If (-not $_.Header2) { $value2 = '' } Else { $value1 = $_.Value2 }
If (-not $_.Header3) { $value3 = '' } Else { $value1 = $_.Value3 }
[pscustomobject]#{header1 = $_.value1;header2=$value2;$header3=$value3}
}
Disclaimer, not tested the above, but it gives you the gist of the idea.
Please find here an example creating a CSV based of a generated directory output:
$basedir = "E:\20220205MassUpload1"
$picDir = "$($baseDir)\pics"
$res=ls $picDir
$outputArray = #()
foreach($line in $res) {
$name = $($line.name).Replace(".png","")
$outputArray += (
[pscustomobject]#{
Title = "$name"
Brand = 'Brand'
BulletPoint1='BulletPoint1'
BulletPoint2='BulletPoint2'
Description='Description'
Price='19.95'
Tags='Tags'
ImagePath="$($picDir)\$($line.name)"
});
}
$outputArray | Export-csv -NoTypeInformation "$basedir\upload.csv"
What it does it, take a list of all elements in a directory.
Create a "pscustomobject" for each result and create the values for the column, some are "fixed" and some "calculated".
Last the Array will be piped to a CSV.
I'm sure the other methods are the "right" way to do it, but I stumbled on a much easier way at some point in the past.
When I start a new CSV, I use something like this:
$vOutputFileName = ".\DDG-Info.csv"
"Name,DisplayName,RecipientFilter" | Out-File $vOutputFileName -encoding ASCII
Then in the script, I do the same thing:
$vOutput = "$vDDGName,$vDisplayName,$vRF"
"$vOutput" | Out-File $vOutputFileName -Append -encoding ASCII
Seems to work just fine, no messing around with explicitly creating objects etc.

Powershell Retrieve IO Operations for Each Process and List with Process Information

I'm trying to get a list of processes with heavy I/O Reads along with the associated ProductVersion. The code would look something like this:
$counter = "\Process*\IO Read Operations/sec"
get-counter | ? {$counter -gt 10} | gps | select name,productversion,reads
and the output would look something like this:
Name ProductVersion Reads
----- -------------- -----
p1 16.1.723.2342 15.98324
p2 12.3.234.1231 11.34323
I think you can use Format-Table
I am using a different counter for fetching result on my system. You can draw out an analogy and use accordingly :-
$Proc = Get-counter "\Process(*)\% processor time"
$Proc.CounterSamples | where {$_.instanceName -ne "idle"} | where {$_.instanceName -ne "_total"} | Format-Table -auto
Output:-
Path InstanceName CookedValue
---- ------------ -----------
\\angshuman\process(system)\% processor time system 1.54907723252374
\\angshuman\process(smss)\% processor time smss 0
\\angshuman\process(csrss#1)\% processor time csrss 1.54907723252374
To create a custom table from multiple sources you need to create an array and then pipe each variable as a new object:
$counter = "\Process*\IO Read Operations/sec"
$processes = gps | select id | ForEach {$_.id}
$ccounter = get-counter -listset process | get-counter -maxsamples 1 | select -expandproperty countersamples | where {$_.path -like $counter -and $_cookedvalue -eq $processes} | select cookedvalue | ForEach {$_.cookedvalue}
function Get-CounterValue ($mypid) { Code Here.... }
function GetProductVersion ($mypid) { ...code here... }
function GetProcessName ($mypid) { ...code here... }
$myresults = #()
$x = foreach ($procc in $processes) {
$thisname = GetProcessName $procc
$thisprod = GetProductVersion $procc
$thisread = GetCounterValue $procc
$robj = New-Object System.Object
$robj | Add-Member -type NoteProperty -name Name -value $thisname
$robj | Add-Member -type NoteProperty -name ProductVersion -value $thisprod
$robj | Add-Member -type NoteProperty -name Reads -value $thisread
$myresults += $robj
}
$myresults | ft -auto