Search Excel with PowerShell and return cell address - excel

I have a PowerShell script which searches an Excel file for a server and then outputs details of the server. My script works but it is inefficient (see below).
What I would like to do is use the Excel Range.Find method for speed, but I can't get the right syntax for this to work in PowerShell.
In addition, is it possible to use PowerShell to open the Excel document, find the value being searched for and set the focus? It's the setting focus part that I struggled with.
Thanks in advance
Function SearchExcel{
#Specify the path of the excel file
$FilePath = "c:\temp\Servers.xlsx"
$ServerName="DC1"
#Specify the Sheet name
$SheetName = "VMs","Physical Servers"
$Row = 1
$Column = 1
$Found = $False
# Create an Object Excel.Application using Com interface
$objExcel = New-Object -ComObject Excel.Application
# Disable the 'visible' property so the document won't open in excel
#$objExcel.Visible = $true
# Open the Excel file and save it in $WorkBook
$WorkBook = $objExcel.Workbooks.Open($FilePath)
# Load the WorkSheet 'BuildSpecs'
$WorkSheet = $WorkBook.sheets.item($SheetName[0])
$range=$WorkSheet.Range("a1:a500")
#Search Hyper-V sheet
while (($WorkSheet.Cells.Item($Row, $Column).Value() -ne $Null) -and ($Found -eq $False)) {
#^-- looping though the excel list, updating the row. Stop if Cell is Empty or Value is Found
If (($WorkSheet.Cells.Item($Row, $Column).Value()) -eq $ServerName) {
#^-- Cell value equals $Arg
Write-Host $WorkSheet.Cells.Item($Row, $Column).Value() $WorkSheet.Cells.Item($Row, $Column+1).Value(), $WorkSheet.Cells.Item($Row, $Column+2).Value(), $WorkSheet.Cells.Item($Row, $Column+3).Value(),$WorkSheet.Cells.Item($Row, $Column+4).Value(),"Cluster:" $WorkSheet.Cells.Item($Row, $Column+6).Value(), "Backed up by:" $WorkSheet.Cells.Item($Row, $Column+7).Value(),$WorkSheet.Cells.Item($Row, $Column+8).Value(),$WorkSheet.Cells.Item($Row, $Column+10).Value(),"vCPUs:" $WorkSheet.Cells.Item($Row, $Column+13).Value(),"RAM (GB):" $WorkSheet.Cells.Item($Row, $Column+14).Value(),"C: (GB):" $WorkSheet.Cells.Item($Row, $Column+15).Value(),"Page (GB):" $WorkSheet.Cells.Item($Row, $Column+16).Value(), "Total Disk (GB):" $WorkSheet.Cells.Item($Row, $Column+26).value().ToString(),$WorkSheet.Cells.Item($Row, $Column+24).Value()
# write-host "processing"
$Found = $True
}
$Row += 1 #Continue to the next row
}#while
write-host "Not found, searching second sheet"
#If not found, search physical servers
If ($found -eq $False)
{
#Search Physical VMs sheet
$WorkSheet = $WorkBook.sheets.item($SheetName[1])
$range=$WorkSheet.Range("a1:a200")
while (($WorkSheet.Cells.Item($Row, $Column).Value() -ne $Null) -and ($Found -eq $False)) {
#^-- looping though the excel list, updating the row. Stop if Cell is Empty or Value is Found
If (($WorkSheet.Cells.Item($Row, $Column).Value()) -eq $ServerName) {
#^-- Cell value equals $Arg
Write-Host $WorkSheet.Cells.Item($Row, $Column).Value() $WorkSheet.Cells.Item($Row, $Column+1).Value(), $WorkSheet.Cells.Item($Row, $Column+21).Value(), $WorkSheet.Cells.Item($Row, $Column+22).Value(),$WorkSheet.Cells.Item($Row, $Column+23).Value(),"Cluster:" $WorkSheet.Cells.Item($Row, $Column+29).Value()
# write-host "processing"
$Found = $True
}
$Row += 1 #Continue to the next row
}
}#if
#Close Excel
$workbook.close()
$objExcel.Quit()
}#Function
SearchExcel

Never mind, I significantly sped this up by changing the line:
(($WorkSheet.Cells.Item($Row, $Column).Value() -ne $Null) -and ($Found -eq $False))
to
($row -le 500) -and ($Found -eq $False))

You don't need search text cell by cell by PowerShell script. You can make use of the Range.Find method of Excel VBA object.
Below is the code maybe helpful to you.
Function Search-Sheet
{
[CmdletBinding()]
Param
(
[Parameter(Mandatory=$true)]$Sheet,
[Parameter(Mandatory=$true)][String]$SearchText
)
$firstResult = $result = $Sheet.UsedRange.Find($SearchText)
$allResults = New-Object System.Collections.ArrayList
If ($firstResult -eq $null) {
Return $allResults
}
$processedResult = Compose-SearchResult -Result $result
$allResults.Add($processedResult) | Out-Null
$isSearchEnd = $false
Do {
$result = $Sheet.UsedRange.FindNext($result)
$isSearchEnd = Test-SearchResultSame -Result1 $result -Result2 $firstResult
If (-not $isSearchEnd) {
$processedResult = Compose-SearchResult -Result $result
$allResults.Add($processedResult) | Out-Null
}
} While (-not $isSearchEnd)
Return $allResults
}
You can also download the complete sample script here How to search text in Excel by PowerShell script

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()

Find a value in Excel via PowerShell

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
}

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?

is it possible to read an Excel through powershell like this?

I have this excel
every row is an automation script I need to execute with certain parameters, the excel is because every script receives different parameters, and I need to do a powershell script that reads the excel file and for each row, execute that process id(script) and send those parameters
is there a way to do that? is it doable?
so far I have this
$file = "C:\Users\MX02689\Documents\Parametros.xlsx"
$sheetName = "Sheet1"
$objExcel = New-Object -ComObject Excel.Application
$workbook = $objExcel.Workbooks.Open($file)
$sheet = $workbook.Worksheets.Item($sheetName)
$objExcel.Visible=$false
$rowMax = ($sheet.UsedRange.Rows).count
$colMax = ($sheet.UsedRange.Columns).count
$rowName,$colName = 1,1
#the idea here is that for each row that has values do this
for($i=1;$i-le $colMax-1; $i++)
#The idea here is that if (parameter 1 -eq 1 ){
execute the command we use to send the scripts process id; "parameter2 parameter 3 parameter 4"
}else{
skip the row and go to the next one
}
{
Write-Output("" + $sheet.Cells.Item($rowName,$colName+$i).text)
}
am I in the right direction? thank you for the help :)
am I in the right direction? is it doable what Im trying to do? is there a optimized way to achieve this? thank you for your help :)
Greetings
Using Excel is not the fastest or easiest way of doing this with PowerShell.
It can be done like this:
$file = "D:\Parametros.xlsx"
$objExcel = New-Object -ComObject Excel.Application
$workbook = $objExcel.Workbooks.Open($file)
$sheet = $workbook.Worksheets.Item(1)
$objExcel.Visible = $false
$rowMax = ($sheet.UsedRange.Rows).count
$colMax = ($sheet.UsedRange.Columns).count
for ($row = 2; $row -le $rowMax; $row++) { # skip the header row
$params = #()
for ($col = 1; $col -le $colMax; $col++) {
$params += $sheet.Cells.Item($row, $col).Value()
}
# execute the command. For demo, just show the parameters used
'Invoke-Command parameters: {0}' -f ($params -join ', ')
}
$objExcel.Quit()
# clean-up used Com objects
[System.Runtime.Interopservices.Marshal]::ReleaseComObject($sheet) | Out-Null
[System.Runtime.Interopservices.Marshal]::ReleaseComObject($workbook) | Out-Null
[System.Runtime.Interopservices.Marshal]::ReleaseComObject($objExcel) | Out-Null
[System.GC]::Collect()
[System.GC]::WaitForPendingFinalizers()
Far more convenient would be to save your Excel file as CSV and use that:
Import-Csv -Path 'D:\Parametros.csv' | ForEach-Object {
# execute the command. For demo, just show the parameters used
'Invoke-Command parameters: {0}, {1}, {2}, {3}' -f $_.'process id', $_.parameter1, $_.parameter2, $_.parameter3, $_.parameter4
}
Demo output for both methods:
Invoke-Command parameters: 235522, 1, testinguser3, Mko12345, something
Invoke-Command parameters: 235266, 0, testinguser4, Mko12346, something
Invoke-Command parameters: 235266, 1, testinguser5, Mko12347, something
From your comment, I now understand what the "1" or "0" means in parameter1.
Below find the adjusted codes for Excel aswell as the CSV method:
Method for Excel:
$file = "D:\Parametros.xlsx"
$objExcel = New-Object -ComObject Excel.Application
$workbook = $objExcel.Workbooks.Open($file)
$sheet = $workbook.Worksheets.Item(1)
$objExcel.Visible = $false
$rowMax = ($sheet.UsedRange.Rows).count
$colMax = ($sheet.UsedRange.Columns).count
for ($row = 2; $row -le $rowMax; $row++) { # skip the header row
$params = #()
for ($col = 1; $col -le $colMax; $col++) {
$params += $sheet.Cells.Item($row, $col).Value()
}
# if the second parameter value converted to int = 1, proceed; if 0 skip the line
if ([int]$param[1] -ne 0) {
# execute the command. For demo, just show the parameters used
'Invoke-Command parameters: {0}' -f ($params -join ', ').TrimEnd(", ")
}
}
$objExcel.Quit()
# clean-up used Com objects
[System.Runtime.Interopservices.Marshal]::ReleaseComObject($sheet) | Out-Null
[System.Runtime.Interopservices.Marshal]::ReleaseComObject($workbook) | Out-Null
[System.Runtime.Interopservices.Marshal]::ReleaseComObject($objExcel) | Out-Null
[System.GC]::Collect()
[System.GC]::WaitForPendingFinalizers()
Method for CSV file:
Import-Csv -Path 'D:\Parametros.csv' | ForEach-Object {
# get the field values from the row in array $params (not a fixed number of fields)
$params = #($_.PsObject.Properties).Value
# if the second parameter value converted to int = 1, proceed; if 0 skip the line
if ([int]$params[1] -ne 0) {
# execute the command. For demo, just show the parameters used
'Invoke-Command parameters: {0}' -f ($params -join ', ').TrimEnd(", ")
}
}

Resources