Find a value in Excel via PowerShell - excel

I have a script that find a row with a specific background color. I want to add a condition that if the cell has a color 14 and contains the word cab and I will copy it to a different folder. All the greens (color 14)will copy to other folder. currently all the green cells (14) copied to the same folder
Maybe I need more if condition ? or one more object that holds all the cell that has color 14 and with the string inside? (patch is the name of the column)
I need an object with all the 14 colors and one object with all the 14 colors and has a name like cab
$ExcelFile = "C:\Temp\SharedFolder\Side VIP - Bulk Tool.xlsx"
$searchValue = ''
$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")
# get the number of rows in the sheet
$rowMax = $worksheet.UsedRange.Rows.Count
# loop through the rows to test if the value in column 1 equals whatever is in $searchValue
# and capture the results in variable $result
$result = for ($row = 1; $row -le $rowMax; $row++) {
$val = $worksheet.Cells.Item($row, 27).Interior.ColorIndex
if ($val -eq 14 -and $val -ne "cab") {
[PsCustomObject]#{Patch = $worksheet.Cells.Item($row, 1).Value2}
}
}
write-host
-join("Number of patches:" + $result.count)
write-host
#$val = $worksheet.Cells.Item($row, 1).Interior.ColorIndex; if ($val -eq 3) { ... }
foreach ($res in $result)
{$vars = foreach ($res in $result) { "\\google.com\global\Patch Managment\$($res.patch)\*" }}
$des = "C:\Temp\SharedFolder\SideVIP"
foreach ($var in $vars)
{
write-host $var
Copy-Item -Path $var -include "*.VIP","*.ZIP"-Destination $des -Force
}

Related

Powershell - Convert list to UI with Out-Gridview

I have a script that read from excel and let the user to choose a column. The issue is that the list is not readable and I want to show the user the option to choose the version with UI with Out-Gridview
One more thing, I need that the answer will be a number
Here is the script:
$ExcelObject = New-Object -ComObject Excel.Application
$ExcelWorkBook = $ExcelObject.Workbooks.Open($SharePointSiteURL)
$ExcelWorkSheet = $ExcelWorkBook.Sheets.Item("VIP List")
$rowMax = $ExcelWorkSheet.UsedRange.Rows.Count
$colMax = $ExcelWorkSheet.UsedRange.Columns.Count
$columns = [ordered]#{}
for ($col = 1; $col -le $colMax; $col++) {
$name = $ExcelWorkSheet.Cells.Item(1, $col).Value() # assuming the first row has the headers
if ($name -ne $null){
$columns[$name] = $col}
}
$columns.GetEnumerator() | ForEach-Object {
# {0,2} means to write the index number from $_.Value right aligned for two digits
'{0,2}: {1}' -f $_.Value, $_.Name
}
do {
$answer = Read-Host "Please enter the number of the column you want to read from" #. Press Q to exit
# ask this question until the user enters a number or 'Q'
} until ($answer -eq 'Q' -or $answer -match '^\d{1,2}$')
switch ($answer) {
'Q' { break } # user wants to quit
{1..$columns.Count} {
# get the Name from the chosen value
$action = $columns.Keys | Where-Object {$columns["$_"] -eq $answer}
Write-Host "You chose to perform: '$action'" -ForegroundColor Cyan
<# run $action #>
}
}
It looks like this:
To let the user select the tool version using Out-GridView, you need to build an array of objects, like below:
$ExcelObject = New-Object -ComObject Excel.Application
$ExcelWorkBook = $ExcelObject.Workbooks.Open($SharePointSiteURL)
$ExcelWorkSheet = $ExcelWorkBook.Sheets.Item("VIP List")
$rowMax = $ExcelWorkSheet.UsedRange.Rows.Count
$colMax = $ExcelWorkSheet.UsedRange.Columns.Count
# now, have the loop output objects that will be collected in variable $columns
$columns = for ($col = 1; $col -le $colMax; $col++) {
$name = $ExcelWorkSheet.Cells.Item(1, $col).Value() # assuming the first row has the headers
# if $name is not empty or whitespace only
if ($name -match '\S') {
[PsCustomObject]#{
Number = $col
Version = $name
}
}
}
# output to Out-GridView with -PassThru parameter so you can capture the selected item
$answer = ($columns | Out-GridView -Title 'Please select' -PassThru).Number
# if the user did not cancel
if ($answer) {
# get the Name from the chosen value
$action = $columns[$answer -1].Version
Write-Host "You chose to perform: '$action'" -ForegroundColor Cyan
<# run $action #>
}
Please do not forget to remove the used COM objects from memory when the code is done, otherwise they will linger on..
$null = [System.Runtime.Interopservices.Marshal]::ReleaseComObject($ExcelWorkSheet)
$null = [System.Runtime.Interopservices.Marshal]::ReleaseComObject($ExcelWorkBook)
$null = [System.Runtime.Interopservices.Marshal]::ReleaseComObject($ExcelObject)
[System.GC]::Collect()
[System.GC]::WaitForPendingFinalizers()

How to use powershell to select range and dump that to csv file

Actually, this is a version of question here:
How to use powershell to select and copy columns and rows in which data is present in new workbook.
The goal is to grab certain columns from multiple Excel workbooks and dump everything to one csv file. Columns are always the same.
I'm doing that manually:
$xl = New-Object -ComObject Excel.Application
$xl.Visible = $false
$xl.DisplayAlerts = $false
$counter = 0
$input_folder = "C:\Users\user\Documents\excelfiles"
$output_folder = "C:\Users\user\Documents\csvdump"
Get-ChildItem $input_folder -File |
Foreach-Object {
$counter++
$wb = $xl.Workbooks.Open($_.FullName, 0, 1, 5, "")
try {
$ws = $wb.Worksheets.item('Calls') # => This specific worksheet
$rowMax = ($ws.UsedRange.Rows).count
for ($i=1; $i -le $rowMax-1; $i++) {
$newRow = New-Object -Type PSObject -Property #{
'Type' = $ws.Cells.Item(1+$i,1).text
'Direction' = $ws.Cells.Item(1+$i,2).text
'From' = $ws.Cells.Item(1+$i,3).text
'To' = $ws.Cells.Item(1+$i,4).text
}
$newRow | Export-Csv -Path $("$output_folder\$ESO_Output") -Append -noType -Force
}
}
} catch {
Write-host "No such workbook" -ForegroundColor Red
# Return
}
}
Question:
This works, but is extremely slow because Excel has to select every cell, copy that, then Powershell has to create array and save row by row in output csv file.
Is there a method to select a range in Excel (number of columns times ($ws.UsedRange.Rows).count), cut header line and just append this range (array?) to csv file to make everything much faster?
So that's the final solution
Script is 22 times faster!!! than original solution.
Hope somebody will find that useful :)
PasteSpecial is to filter out empty rows. There is no need to save them into csv
$xl = New-Object -ComObject Excel.Application
$xl.Visible = $false
$xl.DisplayAlerts = $false
$counter = 0
$input_folder = "C:\Users\user\Documents\excelfiles"
$output_folder = "C:\Users\user\Documents\csvdump"
Get-ChildItem $input_folder -File |
Foreach-Object {
$counter++
try {
$new_ws1 = $wb.Worksheets.add()
$ws = $wb.Worksheets.item('Calls')
$rowMax = ($ws.UsedRange.Rows).count
$range = $ws.Range("A1:O$rowMax")
$x = $range.copy()
$y = $new_ws1.Range("A1:O$rowMax").PasteSpecial([System.Type]::Missing,[System.Type]::Missing,$true,$false)
$wb.SaveAs("$($output_folder)\$($_.Basename)",[Microsoft.Office.Interop.Excel.XlFileFormat]::xlCSVWindows)
} catch {
Write-host "No such workbook" -ForegroundColor Red
# Return
}
}
$xl.Quit()
Part above will generate a bunch of csv files.
Part below will read these files in separate loop and combine them together into one.
-exclude is an array of something I want to omit
Remove-Item to remove temporary files
Answer below is based on this post: https://stackoverflow.com/a/27893253/6190661
$getFirstLine = $true
Get-ChildItem "$output_folder\*.csv" -exclude $excluded | foreach {
$filePath = $_
$lines = Get-Content $filePath
$linesToWrite = switch($getFirstLine) {
$true {$lines}
$false {$lines | Select -Skip 1}
}
$getFirstLine = $false
Add-Content "$($output_folder)\MERGED_CSV_FILE.csv" $linesToWrite
Remove-Item $_.FullName
}

Powershell using ImportExcel to delete rows

I am trying to delete rows of data from an Excel file using the ImportExcel module.
I can open the file, find the the data I wish to delete and the DeleteRow command works on a hardcoded value however does not appear to work on a variable...any ideas?
# Gets ImportExcel PowerShell Module
if (-not(Get-Module -ListAvailable -Name ImportExcel)) {
Find-module -Name ImportExcel | Install-Module -Force
}
# Open Excel File
$excel = open-excelpackage 'C:\temp\input.xlsx'
#Set Worksheet
$ws = $excel.Workbook.Worksheets["Sheet1"]
#Get Row Count
$rowcount = $ws.Dimension.Rows
#Delete row if Cell in Column 15 = Yes
for ($i = 2; $i -lt $rowcount; $i++) {
$cell = $ws.Cells[$i, 15]
if ($cell.value -eq "Yes") {
$ws.DeleteRow($i)
}
}
#Save File
Close-ExcelPackage $excel -SaveAs 'C:\Temp\Output.xlsx'
You should reverse the loop and go from bottom to top row. As you have it, by deleting a row, the index of the ones below that is changed and your for ($i = 2; $i -lt $rowcount; $i++) {..} will skip over.
You can also do this without the ImportExcel module if you have Excel installed:
$file = 'C:\Temp\input.xlsx'
$excel = New-Object -ComObject Excel.Application
$excel.Visible = $false
# open the Excel file
$workbook = $excel.Workbooks.Open($file)
$sheet = $workbook.Worksheets.Item(1)
# get the number of rows in the sheet
$rowMax = $sheet.UsedRange.Rows.Count
# loop through the rows to test if the value in column 15 is "Yes"
# do the loop BACKWARDS, otherwise the indices will change on every deletion.
for ($row = $rowMax; $row -ge 2; $row--) {
$cell = $sheet.Cells[$row, 15].Value2
if ($cell -eq 'Yes') {
$null = $sheet.Rows($row).EntireRow.Delete()
}
}
# save and exit
$workbook.SaveAs("C:\Temp\Output.xlsx")
$excel.Quit()
# clean up the COM objects used
$null = [System.Runtime.Interopservices.Marshal]::ReleaseComObject($sheet)
$null = [System.Runtime.Interopservices.Marshal]::ReleaseComObject($workbook)
$null = [System.Runtime.Interopservices.Marshal]::ReleaseComObject($excel)
[System.GC]::Collect()
[System.GC]::WaitForPendingFinalizers()

PowerShell - Open Excel and Unlock Certain Cells Based On Value

I have a report that I am running via SSRS to brings down some data that I need people to give comments on. I am attempting to write a PowerShell script that will open the file and unprotect certain cells based on their value.
This is what the report looks like:
The default for SSRS is that all cells are automatically protected. However, I would like to unprotect all the cells in column A (the column with the yellow text boxes) that do not have the value of "Manager Comments".
I am not really sure how to go about this. I have this so far for my script:
Param(
[string] $FolderPath,
[string] $FileName
)
Write-Output $FolderPath
Write-Output $FileName
$Files = Dir $FolderPath -Recurse | ? {$_.Name -eq $FileName} | Select -ExpandProperty FullName
$excl=New-Object -ComObject "Excel.Application"
foreach ($file in $Files)
{
$wrkb=$excl.Workbooks.Open($file, 0, $false)
$wrkb.Worksheets("Comments").Range("A1:A100").Locked = $False
$wrkb.Save()
$wrkb.Close()
}
$excl.Quit()
But this will just unprotect all of column A. Does anyone know how to have it look at the values and only unprotect those blank values? Bonus points if it can just look for certain colors (the yellow).
And yes, I know that this is a terrible way of doing this...but I work with what I have.
A little progress update...I now can pull the value and cell color index in a loop, but I am not sure how to write the IF statement to lock the cell if the $Color value is equal to "36".
Param(
[string] $FolderPath = "C:\Root\Data\VisualCron\Development\",
[string] $FileName = "Combined Master Report.xlsx"
)
Write-Output $FolderPath
Write-Output $FileName
$Files = Dir $FolderPath -Recurse | ? {$_.Name -eq $FileName} | Select -ExpandProperty FullName
$SheetName = "Comments"
$Excel = New-Object -ComObject "Excel.Application"
foreach ($File in $Files)
{
$Workbook = $Excel.Workbooks.Open($File, 0, $False)
$Sheet = $Workbook.Worksheets.Item("Comments")
$MaxRow = ($Sheet.UsedRange.Rows).Count
$Row,$Column = 1,1
for ($i = 0; $i -le $MaxRow - 1; $i++)
{
$Color = $Sheet.Cells.Item($Row+$i,$Column).Interior.ColorIndex
$Value = $Sheet.Cells.Item($Row+$i,$Column).Text
Write-Host ("Color Value:" + $Color + " " + $i + " " + $Value)
}
#$Workbook.Worksheets("Comments").Range("A3:A50").Locked = $False
#$Workbook.Worksheets("Comments").Protect('Test',1,1,1,1,0,0,0,0,0,0,0,0,0,0,0)
$Workbook.Save()
$Workbook.Close()
}
$excl.Quit()
Another update:
I have gotten it to work, but it is a little strange. When I first go through my loop, the color index appears to be "36", but once I change one value, the yellow index changes to "5". I am not really sure why it does that, but I wrote this script, and hopefully, it is consistent.
Param(
[string] $FolderPath = "C:\Root\Data\VisualCron\Development\",
[string] $FileName = "Combined Master Report.xlsx"
)
Write-Output $FolderPath
Write-Output $FileName
$Files = Dir $FolderPath -Recurse | ? {$_.Name -eq $FileName} | Select -ExpandProperty FullName
$SheetName = "Comments"
$Excel = New-Object -ComObject "Excel.Application"
foreach ($File in $Files)
{
$Workbook = $Excel.Workbooks.Open($File, 0, $False)
$Sheet = $Workbook.Worksheets.Item("Comments")
$MaxRow = ($Sheet.UsedRange.Rows).Count
$Row,$Column = 1,1
for ($i = 1; $i -le $MaxRow - 1; $i++)
{
$Color = $Sheet.Cells.Item($Row+$i,$Column).Interior.ColorIndex
$Value = $Sheet.Cells.Item($Row+$i,$Column).Text
#Write-Output ($Color)
$Range = "A" + ($Row + $i) + ":A" + ($Row + $i)
if ($Color -eq 36 -or $Color -eq 5)
{
$Range = "A" + ($Row + $i) + ":A" + ($Row + $i)
$Workbook.Worksheets("Comments").Range($Range).Locked = $False
#Write-Host $Range
}
}
$Workbook.Worksheets("Comments").Protect('Test',1,1,1,1,0,0,0,0,0,0,0,0,0,0,0)
$Workbook.Save()
$Workbook.Close()
}
$Excel.Quit()
Does anyone have any ideas on why the color index would be changing?

excel/powershell - find multiple cells that match a string not just the last one

Is it possible to find all cell references that contain a specific string in powershell? $Range.Find always just returns the last cell in the sheet, I want all the cells (or do I have to loop through each row? groan).
$Excel = New-Object -ComObject Excel.Application
$Excel.Visible = $true
$Workbook = $Excel.Workbooks.Open("C:\Users\some.xlsx")
$MySheet = $Excel.Worksheets.Item("somesheet")
$Range = $MySheet.UsedRange
$Targets = $Range.Find("Cheese")
Foreach ($Target in $Targets)
{
Write-Host $Target.AddressLocal()
}
I think you should use:
$Target = $Range.Find("Cheese")
$First = $Target
Do
{
Write-Host $Target.AddressLocal()
$Target = $Range.FindNext($Target)
}
While ($Target -ne $NULL -and $Target.AddressLocal() -ne $First.AddressLocal())

Resources