Represent values from different arrays in an excel table using powershell - excel

I have couple arrays with values in them. Corresponding to each other.I will use dummy data to represent my problem. I have four different arrays.
Name = #("John","Mary","Sarah") Age = #("23","44","31") Hobby = #("Flying, Fishing","Dancing","Singing") Kids = #("Lucas","Simon, Lisa","Anna")
Each entry from each array corresponds to another. Entry 1 from array one belongs to entry 1 from array 2,3 and 4. I would like to list them as a table with the arraynames as headers. The values from each array should be displayed in a single cell along the column. If a value contains a comma, it should seperate it and put each entry into a single cell below each other.
I'm currently creating a script in powershell to create this excel file. The array data is given I only need to create the excel sheet.
My desired outcome would be this(top part of picture) but instead I get this (bottom part of picture)(https://i.stack.imgur.com/sUq2J.png)
I tried several pieces of code and solutions found on the internet but nothing satisfies my needs. Can someone help me?

Here is an example step-thru of what I mean to guide you.
Initializing multidimensional array
Clear-Host
$MDArrayData = #(
#('John','Mary','Sarah'),
#('23','44','31'),
#('Flying, Fishing','Dancing','Singing'),
#('Lucas','Simon, Lisa','Anna')
)
Cherry-picking your target sample data by index from each array for successful validation. Note: I am using PowerShell variable squeezing to assign results to the variable and output to the screen simultaneously.
(
$MyDataObject = [PSCustomObject]#{
Name = $MDArrayData[0][0]
Age = $MDArrayData[1][0]
Hobby = $MDArrayData[2][0]
Kids = $MDArrayData[3][0]
}
)
# Results
<#
Name Age Hobby Kids
---- --- ----- ----
John 23 Flying, Fishing Lucas
#>
Enumerate the primary array, array 0 to formulate the report
Clear-Host
$MDArrayData = #(
#('John','Mary','Sarah'),
#('23','44','31'),
#('Flying, Fishing','Dancing','Singing'),
#('Lucas','Simon, Lisa','Anna')
)
0..(($MDArrayData[0]).Count -1) |
ForEach-Object {
(
$MyDataObject = [PSCustomObject]#{
Name = $MDArrayData[0][$PSItem]
Age = $MDArrayData[1][$PSItem]
Hobby = $MDArrayData[2][$PSItem]
Kids = $MDArrayData[3][$PSItem]
}
)
}
# Results
<#
Name Age Hobby Kids
---- --- ----- ----
John 23 Flying, Fishing Lucas
Mary 44 Dancing Simon, Lisa
Sarah 31 Singing Anna
#>
You can choose to skip the multidimensional array and just use your single arrays this way, and the results are the same, then move on to the MS Excel part of your use case.
Clear-Host
$Name = #("John","Mary","Sarah")
$Age = #("23","44","31")
$Hobby = #("Flying, Fishing","Dancing","Singing")
$Kids = #("Lucas","Simon, Lisa","Anna")
Using the PowerShell ForEach method and a custom object.
$DataIndex = 0
$Name.ForEach(
{
(
$MyDataObject = [PSCustomObject]#{
Name = $PSItem
Age = $Age[$DataIndex]
Hobby = $Hobby[$DataIndex]
Kids = $Kids[$DataIndex]
}
)
$DataIndex++
}
)
# Results
<#
Name Age Hobby Kids
---- --- ----- ----
John 23 Flying, Fishing Lucas
Mary 44 Dancing Simon, Lisa
Sarah 31 Singing Anna
#>
Final Csv output required for MSExcel conversion
Clear-Host
$MDArrayData = #(
#('John','Mary','Sarah'),
#('23','44','31'),
#('Flying, Fishing','Dancing','Singing'),
#('Lucas','Simon, Lisa','Anna')
)
0..(($MDArrayData[0]).Count -1) |
ForEach-Object {
(
$MyDataObject = [PSCustomObject]#{
Name = $MDArrayData[0][$PSItem]
Age = $MDArrayData[1][$PSItem]
Hobby = $MDArrayData[2][$PSItem]
Kids = $MDArrayData[3][$PSItem]
}
)
} |
# Create or overwrite and existing report
Export-Csv -Path 'D:\Temp\FamilyReport.csv' -NoTypeInformation -Force
Create MSExcel COM object to open the file
Add-Type -AssemblyName Microsoft.Office.Interop.Excel
$Excel = New-Object -ComObject excel.application
$Excel.Visible = $True
Open your report for formatting
$Workbook = $Excel.workbooks.open('D:\Temp\FamilyReport.csv')
$Range = $Excel.Range("A:D")
$Range.VerticalAlignment = -4160
$Range.WrapText = $True
$Range.EntireColumn.AutoFit()
1..4 |
ForEach-Object {$Workbook.ActiveSheet.Cells.Item(1, $PSItem).Interior.ColorIndex = 15}
$Workbook.ActiveSheet.Cells.Item(1, 1).EntireRow.Font.Bold = $True
Other formatting
$Workbook.ActiveSheet.ListObjects.Add(1, $range, $true)
You still must remember to clean-up behind yourself all the stuff instantiated. aka GarbageCollection.

Related

Display Count of records in excel

I Have the below data in one excel Sheet
Error Code type object
-Ignored:31 Modified src data *file MINOSFIC/UTMNUP10
-Ignored:33 Modified src & tgt data *file MINOSFIC/UVEGAP10
*Error: 08 Different data *file MINOSFIC/VM010P50
I need to count the records based on Error Code and put the data in same Sheet
ErrorCode Count
Ignored 2
Error 1
I was trying Pivot table, but seems can't use it in existing excel sheet.
Update:
I am able to get the count using below code, but need help to put it in excel sheet in some table or some other way
$Excel = Import-Excel -Path "C:\Verify.xlsx" -WorksheetName "EDH_VFN"
$err = 0
$ign = 0
foreach($line in $Excel )
{
$line_1 = $line.'Error Code'
if($line_1 -match "Ignored")
{
$ign+=1
}
if($line_1 -match "Error")
{
$err+=1
}
}
write-host "Error:"$err
write-host "Ignored:"$ign
Please need help in doing this
If you don't want to do this with Excel function COUNTIFS, I would suggest simply exporting that Excel file to a CSV file which makes things a lot easier in PowerShell.
Example CSV file
Error Code,type,object
-Ignored:31 Modified src data,*file,MINOSFIC/UTMNUP10
-Ignored:33 Modified src & tgt data,*file,MINOSFIC/UVEGAP10
*Error: 08 Different data,*file,MINOSFIC/VM010P50
Once you have this, getting the count values could be done like this:
Import-Csv -Path 'X:\TheExcelToCsvExportedFile.csv' |
Group-Object #{Expression = {($_.'Error Code' -split ':')[0].Substring(1)}} |
Select-Object #{Name = 'ErrorCode' ; Expression = {$_.Name}},Count
This will output:
ErrorCode Count
---------- -----
Ignored 2
Error 1
you can simply copy/paste in that your Excel file anywhere you like.
Beware though that this does not comply with the columns you already have there..

Searching a single Excel column for a keyword returns only the first cell details

I have an excel sheet with two colums. Col A is a list of hostnames. Col B says either True OR False. I am using below script to search for FALSE and get respective cell row and column numbers. But it returns only the very first cell which contains FALSE and ends there. Do I need to tell it to loop/recurse or something like that?
Heres my code:
$excel = New-Object -ComObject Excel.Application
$excel.Visible = $false
$excel.DisplayAlerts = $False
$workbook = $excel.Workbooks.Open("C:\temp\extest3.xlsx")
$sheet = $workbook.ActiveSheet
$getStatus = $sheet.UsedRange.find("False")
$cellrow = $getStatus.Row
$cellcol = $getstatus.Column
$celladdress = $cellrow,$cellcol
$celladdress
$workbook.Save()
$workbook.Close()
The output I get is only one cell row and col number. Not getting other cells which contains false.
While it is possible to use the Excel Com Object with PowerShell as AdminOfThings good answer shows,
it is (especially for simple flat data structures) quite clumsy and requires Excel to be installed locally.
I suggest to use Doug Finkes famous ImportExcel module.
Just for demonstration create a sample .xlsx file:
# generate test file with random data
1..10|ForEach-Object{
[PSCustomObject]#{
HostName = 'Test{0:D2}' -f $_
Online = [bool](0..1|Get-Random)
}
} | Export-Excel .\extest3.xlsx
HostName Online
-------- ------
Test01 False
Test02 False
Test03 True
Test04 True
Test05 True
Test06 False
Test07 False
Test08 True
Test09 True
Test10 True
And output the HostNames with False in the next column what I presume is what you want:
Import-Excel .\extest3.xlsx |
Where-Object {-not $_.Online} |
Select-Object -ExpandProperty HostName
Test01
Test02
Test06
Test07
You can continue using the Find() method with the After parameter. A while loop with a break statement can be used to halt the search once you return to the top of the sheet.
$getStatus = $sheet.UsedRange.Find("False")
$firstrow = $getStatus.Row
$firstcol = $getstatus.Column
$celladdress = $firstrow,$firstcol
$celladdress
while ($getStatus) {
$getStatus = $sheet.UsedRange.Find("False",$getStatus)
$cellrow = $getStatus.Row
$cellcol = $getstatus.Column
$celladdress = $cellrow,$cellcol
if ($celladdress[0] -eq $firstrow) {
break
}
$celladdress
}
The value of the After parameter is always $getStatus.

Exception from HRESULT: 0x800A03EC (Trying to modify cell in Excel through PowerShell)

I just recently joined an IAM team, and this month had to send out hundreds of emails to people notifying them of an account expiration (they are asked to either request for an extension or termination of the account). Thankfully, there's already a script made to do that part, but for dealing with the responses there is not. There's an excel spreadsheet where I record what is to happen to each account. I was hoping to make a script that can go through each of the responses and mark in the desired field in the spreadsheet accordingly. I've been having trouble with the part of the script where I modify the value under the desired field for the user.
I'm fairly new to PowerShell, so I'm not sure what the issue is. I already spent a few hours looking online and found quite a few possible solutions, but none of them have worked for me. A common problem is apparently using an older excel file, but it's fresh and it's Excel 2016. Another one is not having the correct file type, but I checked and that's not it either. The line of code in question is $extend.Cells.Item($modifyCell.Cells.Row) = "$data".
Any ideas what the problem could be?
Code:
# Path to .msg files
$msgDir = "C:\Users\me\Desktop\Test"
# Array to store results
$msgArray = New-Object System.Collections.Generic.List[object]
# Loop throuch each .msg file
Get-ChildItem "$msgDir" -Filter *.msg |
ForEach-Object {
# Open .msg file
$outlook = New-Object -comobject outlook.application
$msg = $outlook.Session.OpenSharedItem($_.FullName)
# Add .msg file Subject and Body to array
$msgArray.Add([pscustomobject]#{Subject=$msg.Subject;Body=$msg.Body;})
$msg.Close(0) # Close doesn't always work, see KB2633737 -- restart ISE/PowerShell
}
# Loop though / parse each message
ForEach ($message in $msgArray) {
$subject = $message.subject
$body = $message.body
$regex = [regex] '\s*(\w*)\s*\|$'
If ($body -match $regex) {
$username = $body
}
$parse = $body | Select-String -Pattern "Please extend"
If ($parse -eq "Please extend") {
$data = "Y"
}
}
# Open Excel
$Excel = New-Object -ComObject Excel.Application
$Excel.Visible = $True
$OpenFile = $Excel.Workbooks.Open("C:\Users\me\Desktop\test.xlsx")
$Workbook = $OpenFile.Worksheets
$Worksheet = $Workbook.Item(1)
# Get the values for each column
$samacctname = $Worksheet.Cells | where {$_.value2 -eq "SAM Account Name"} | select -First 1
$extend = $Worksheet.Cells | where {$_.value2 -eq "Extend"} | select -First 1
# Get the values for each row in SAM Account Name
$userValues = #()
for($i=2; $samacctname.Cells.Item($i).Value2 -ne $null; $i++ ){
$userValues += $samacctname.Cells.Item($i)
}
# Get the values where the cell value of SAM Account matches the username
$modifyCell = $userValues | where {$_.Value2 -eq $username}
# Modify the Extend cell using the username's row position
$extend.Cells.Item($modifyCell.Cells.Row) = "$data"
# Save the file
$OpenFile.Save()
Edit 1: I went back into my code and first tried to hard-code the data value I was trying to add to the cell, but I still got the same error. I then tried hard-coding it right when I call the line $extend.Cells.Item($modifyCell.Cells.Row) = "Y" and it works as it should. So how I'm trying to use regex to pull the username is likely not right. Probably how I'm pulling the data as well.

Import Excel, Export CSV with PowerShell

Due to restrictions I either need to use VB or PowerShell for this task.
I have an Excel that looks like:
ColumA, ColumB,ColumC,ColumD,ColumE,ColumF
000|Txt,MoreTxt , ColumB,ColumC,ColumD,ColumE,ColumF
I read about import_csv -header, but I'm under to successfully do it. I'll post my script below. The export I expect is:
ColumA, ColumB, ColumC, ColumD, ColumE, ColumF
000, ColumB, ColumC, ColumD, ColumE, ColumF
Only Colum gets modified, and I -only- need the digits from before that pipe. It also has to stay three digits, so 1 becomes 001, etc.
This is the script I modified based on some previous inquiries I saw, and the MS Tutorial.
$file = import-csv "C:\path\to\my\file\test.csv"
foreach ($row in $file){
$tempfile = New-Object psobject -Property #{
ColumA = $row. 'ListName'.substring(0,2)
ColumB = $row. 'ColumB'
ColumC = $row. 'ColumC'
ColumE = $row. 'ColumE'
ColumF = $row. 'ColumF'
}
$expandfile = #()
$expandfile += $tempfile | select ColumA, ColumB, ColumC, ColumD, ColumE, ColumF
}
PS gives me both errors on not liking everything I have in quotes (Which I thought was the column name, but I guess not. And also a parse error on the entire array. Essentially the entire script.
UPDATE
Providing real examples of source.
"Tiam
Name",SiamName,Siam,Ciam,Piam,Liam,Niam,Diam
"002|City, State","City, State - Some text (15092)",1,"3,408",99,"3,408",780,22.89%
"009|City, State","City, State - Some Text (E) (15450)",1,"1,894",81,"1,894",543,28.67%
Edit:
$expandfile = Import-Csv "C:\path\to\my\file\test.csv" | ForEach-Object {
$_."Tiam`r`nName" = $_."Tiam`r`nName".SubString(0,3)
$_
}

Why is it so slow to read an Excel file with Powershell?

I have a small Excel file with 28 KB in XLSX format and I would like to modify it with Powershell. The file contains 59 rows and 366 columns.
My code walks through the first column and searches for a specific entry and after that it walks through the column found and outputs the content of the found row and the fist row. This is the code:
# Define some parameters.
$year = "2015"
$filename = "C:\...\file.xlsx"
$person = "Lastname, Firstname"
# Open Excel file and select worksheet.
$excel = New-Object -ComObject Excel.Application
$excel.Visible = $false
$workbook = $excel.Workbooks.Open($filename)
$worksheet = $workbook.sheets.item($year)
$cells = $worksheet.cells
# Search person name in first column.
$rows = $worksheet.UsedRange.Rows.count
"Rows: $rows"
$row = 1
while ($row -le $rows)
{
$cell = $cells.item($row,1).value2
if ($person -eq $cell) {
break
}
$row++
}
# List row
$cols = $worksheet.UsedRange.Columns.count
"Cols: $cols"
foreach ($col in 2..$cols)
{
$date = $cells.item(1,$col).value2
$data = $cells.item($row,$col).value2
$date = [DateTime]::FromOADate($date)
$msg = $date.ToString("yyyy-MM-dd") + " " + $data
"$msg"
}
# Close workbook and Excel file and release COM object.
$workbook.close()
$excel.quit()
[System.Runtime.Interopservices.Marshal]::ReleaseComObject($excel)
My problem: The program is terrible slow. It takes more than 5 minutes to iterate the 366 columns!
PS C:\...> Measure-Command { .\program.ps1 }
Days : 0
Hours : 0
Minutes : 5
Seconds : 33
Milliseconds : 580
Ticks : 3335806616
TotalDays : 0,00386088728703704
TotalHours : 0,0926612948888889
TotalMinutes : 5,55967769333333
TotalSeconds : 333,5806616
TotalMilliseconds : 333580,6616
I can hardly believe that this is normal. Instead I think that there is something really wrong with my program. But I have no idea what it is.
What do I have to change to make it faster?
Using loop and find to replace cell values in Excel will take you forever... I have 111 cells to replace and it takes about 40 secs to complete.
However, you may exploit the command Replace which is considerably faster. But to provide a value from a relative cell you have to change your Excel application Reference style to xlR1c1.
Below is my take on how I can replace all cells with string "No registered hostname" with a value of the cell to the left which is IP address for my data.
I have commented out the while loop which I previously used
Since you intend to do an update you may consider this...
$Range=$WorkSheet.Range("B1").EntireColumn
# Replace Cells with No registered hostname
$SearchString="No registered hostname"
# Using Excel reference style xlR1C1 to set the formula for replace
$xls.Application.ReferenceStyle=2
$Range.Replace($SearchString, "=RC[-1]")
# while ($NoDNS=$Range.find($SearchString))
# {
# $NoDNS.Activate()
# $RefRow=$NoDNS.Row
# $NoDNS.value()=$WorkSheet.Cells.Item($RefRow, 1).Text
# }
$xls.Application.ReferenceStyle=1
Using replace only takes a split second to complete all the necessary changes compare to previous while loop.

Resources