I'm really new to Powershell and I feel like I've looked all over and can't quite figure out what is wrong with my code.
My goal is a powershell script that can run against an Excel workbook and delete rows with a specific string in the cell (in this case it is local admin accounts).
Currently my script launches the excel sheet opens, but no rows are deleted. The code exits without error. Any help would be greatly appreciated
$ObjExcelCellTypeLastCell = 11
$ObjExcel = New-Object -ComObject Excel.Application
$ObjExcel.Visible = $True
$ObjExcel.DisplayAlerts = $True
$Workbook = $ObjExcel.Workbooks.Open("File\Path\")
$Worksheet = $Workbook.Worksheets.Item(1)
$used = $Worksheet.usedRange
$lastCell = $used.SpecialCells($ObjExcelCellTypeLastCell)
$row = $lastCell.row
for ($i = $Worksheet.usedrange.rows.count; $i -gt 0; $i--)
{
If ($Worksheet.Cells.Item($i, 1) = "Local Admin") {
$Range = $Worksheet.Cells.Item($i, 1).EntireRow
$Range.Delete()
$i = $i + 1
Else
Break
}
Exit
}
I don't know much about powershell but i think your if statement $Worksheet.Cells.Item($i, 1) = "Local Admin" is wrong, you should use -eq
also maybe you need to call the Close method on the workbook object that you just Open'd
I am not sure if it's solved, but my code is like below. It's not exactly same to mine, but I think this would work.
#get last row
$rowLast = $WorkSheet.UsedRange.Rows.Count
#for loop
for ($row = $rowLast; $row -gt 0; $row--) {
if($WorkSheet.Cells.Item($row, 1).Text -eq "Local Admin"){
#delete the row. Without "[void]", you will get message "True" when successfully deleted the row.
[void]$WorkSheet.Rows($row).Delete()
}
}
I think you need ".Text" after "$Worksheet.Cells.Item($i, 1)".
Also, I think following codes should be removed.
$i = $i + 1
Else
Break
Exit
Related
I have below csv file, I want to import into excel and add the row grouping for the child items using powershell. I was able open the file and format the cell. Not sure how to add row grouping.
Data
name,,
one,,
,value1,value2
,value3 ,value4
two,,
,value4,sevalue4
,value5,sevalue5
,value6,sevalue6
,value7,sevalue7
three,,
,value8,sevalue8
,value9,sevalue9
,value10,sevalue10
,value11,sevalue11
I want to convert like this in excel.
Here is the code I have it to open it in excel.
$a = New-Object -comobject Excel.Application
$a.visible = $True
$b = $a.Workbooks.Open("C:\shared\c1.csv")
$c = $b.Worksheets.Item(1)
$d = $c.Cells(1,1)
$d.Interior.ColorIndex = 19
$d.Font.ColorIndex = 11
$d.Font.Bold = $True
$b.Save("C:\shared\c1.xlsx")
How do I add row grouping for this data?
Thanks
SR
Logic Applied:
Group all the consecutive rows for which the value in column A is blank
In the following code, I have opened a CSV file, made the required grouping as per the data shared by you and saved it. While saving it, because of the row grouping, I was not able to save it in csv format. So, I had to change the format to a normal workbook. But, it works.
Code
$objExl = New-Object -ComObject Excel.Application
$objExl.visible = $true
$objExl.DisplayAlerts = $false
$strPath = "C:\Users\gurmansingh\Documents\a.csv" #Enter the path of csv
$objBook = $objExl.Workbooks.open($strPath)
$objSheet = $objBook.Worksheets.item(1)
$intRowCount = $objSheet.usedRange.Rows.Count
for($i=1; $i -le $intRowCount; $i++)
{
if($objSheet.Cells.Item($i,1).text -like "")
{
$startRow = $i
for($j=$i+1; $j -le $intRowCount; $j++)
{
if($objSheet.cells.Item($j,1).text -ne "" -or $j -eq $intRowCount)
{
$endRow = $j-1
if($j -eq $intRowCount)
{
$endRow = $j
}
break
}
}
$str = "A"+$startRow+":A"+$endRow
$objSheet.Range($str).Rows.Group()
$i=$j
}
}
$objBook.SaveAs("C:\Users\gurmansingh\Documents\b",51) #saving in a different format.
$objBook.Close()
$objExl.Quit()
Before:
a.csv
Output after running the code:
b.xlsx
Also, check out how easy it is to do using my Excel PowerShell module.
Install-Module ImportExcel
https://github.com/dfinke/ImportExcel/issues/556#issuecomment-469897886
I am novice programmer of powershell, I am trying to do excel search and change of format and font option. Here is the snippet were I am trying to search for the word "PASSED" and change the color to green and bold, currently the code does exits out without changing as expected what is wrong in this which I could not figure out, need help in this regards.
$excel = New-Object -ComObject Excel.Application
$excel.Visible = $false
$excel.DisplayAlerts = $False
$workbook = $excel.Workbooks.Open("C:\test.xlsx")
$sheet = $workbook.ActiveSheet
$xlCellTypeLastCell = 11
$used = $sheet.usedRange
$lastCell = $used.SpecialCells($xlCellTypeLastCell)
$row = $lastCell.row # goes to the last used row in the worksheet
for ($i = 1; $i -lt $row.length; $i++) {
If ($sheet.cells.Item(1,2).Value() = "PASSED") {
$sheet.Cells.Item(1,$i+1).Font.ColorIndex = 10
$sheet.Cells.Item(1,$i+1).Font.Bold = $true
}
}
$workbook.SaveAs("C:\output.xlsx")
$workbook.Close()
Input(test.xlsx) file has the following
Module | test | Status
ABC a PASSED
Its quiet a huge file with different status of each unit test.
$row is a string containing the last row number, comparing to it's Length property in the for loop will land you in trouble since it'll give you the length of the string itself.
Change it to:
for ($i = 1; $i -lt $row; $i++) {
In the if statement inside the loop, there's another problem: =
In order to compare two values for equality, use the -eq operator instead of = (= is only for assignment):
if ($sheet.cells.Item($i,2).Value() -eq "PASSED") {
$sheet.Cells.Item(1,$i+1).Font.ColorIndex = 10
$sheet.Cells.Item(1,$i+1).Font.Bold = $true
}
Lastly, Excel cell references are not zero-based, so Item(1,2) will refer to the cell that in your example has the value "test" (notice how it takes a row as the first parameter, and a column as the second). Change it to Item(2,3) to test against the correct cell, and transpose the cell coordinates inside the if block as well.
You may want to update the for loop to reflect this as well:
for ($i = 2; $i -le $row; $i++) {
if ($sheet.cells.Item($i,3).Value() = "PASSED") {
$sheet.Cells.Item($i,3).Font.ColorIndex = 10
$sheet.Cells.Item($i,3).Font.Bold = $true
}
}
I have a Excel file which which looks like this:
Visted Domains Comments
yahoo.com
google.com
hotmail.com
All of columns are already populated.
I am trying to read the domain from Excel file, open in IE. Once it's visited, write 'yes' under 'Visited' column.
So far, this current script read from the Excel file, and opens in IE. Once I close the current IE Window, it opens the next URL.
$ExcelObject = New-Object -comobject Excel.Application
$ExcelObject.Visible = $true
$ExcelObject.DisplayAlerts = $False
$excelFile = "C:\Users\muafzal\Documents\Files\EMIE\Analyzing\list.xlsx"
$Workbook = $ExcelObject.workbooks.open($excelFile)
$Sheet = $Workbook.Worksheets.Item(1)
$row = [int]2
$domain = #() # beginnt bei 2,1... 3,1... 4,1
Do {
$domain += $Sheet.Cells.Item($row,2).Text ; $row = $row + [int]1
} until (!$Sheet.Cells.Item($row,1).Text)
ForEach($url in $domain){
#Start IE and make it visible
$ie = new-object -com "InternetExplorer.Application"
$ie.Visible = $true
#Navigate to the URL
$ie.Navigate($url)
# Output URL that have been visited to a text file.
$url | Out-File $done -Append
#Sleep while IE is running
while($ie.visible){
start-sleep -s 1
}
}
I would like the Excel document to be writable, so I can enter comments about the website.
I guess by "I would like the excel document to be writeable" you mean, the PS script should do the job for you.
For that, we have to solve 2 problems:
How do we write into excel cells:
First of all, you probably don't want to use the Text property of the Range object returned by $Sheet.Cells.Item($row, 1) because it always returns what would be displayed if you had the excel sheet open (and that includes the hashes you could get if the text doesn't fit in the cell). For more information, see this question.
My guess is, that Value will be ok instead of Text - and because it's a property, you can also use it to write information.
My suggestion for your script would be to move the logic from your ForEach loop into your Do loop as you could use the $row index to also address the Visited and Comment column.
To set a Column to visited you could write for example:
Do {
$domain += $Sheet.Cells.Item($row,2).Text
# (browser magic here!)
# edit exel sheet:
$Sheet.Cells.Item($row, 1).Value = 'yes'
$row = $row + [int]1
} until (!$Sheet.Cells.Item($row,1).Text)
How do we ask the user for the comment?
You can use the InputBox method from the Microsoft.VisualBasic.Interaction class:
# this will import the Microsoft.VisualBasic assembly and make
# the Interaction class available in $vbi
# add those two lines to the top of your script
[void][System.Reflection.Assembly]::LoadWithPartialName('Microsoft.VisualBasic')
$vbi = [Microsoft.VisualBasic.Interaction] # convenience variable
# ask the user for a comment
$comment = $vbi::InputBox("Write a comment about that website:", "Comment")
if ($comment -eq "") {
echo "input box was cancelled!"
}
else {
echo "comment:`t$comment"
}
At the end, your code might look like this:
[void][System.Reflection.Assembly]::LoadWithPartialName('Microsoft.VisualBasic')
$vbi = [Microsoft.VisualBasic.Interaction] # convenience variable
$ExcelObject = New-Object -comobject Excel.Application
$ExcelObject.Visible = $true
$ExcelObject.DisplayAlerts = $False
$excelFile = "C:\Users\muafzal\Documents\Files\EMIE\Analyzing\list.xlsx"
$Workbook = $ExcelObject.workbooks.open($excelFile)
$Sheet = $Workbook.Worksheets.Item(1)
$row = [int]2
$domain = #() # beginnt bei 2,1... 3,1... 4,1
Do {
$domain += $Sheet.Cells.Item($row,2).Text
# browser magic here:
#Start IE and make it visible
$ie = new-object -com "InternetExplorer.Application"
$ie.Visible = $true
#Navigate to the URL
$ie.Navigate($url)
# Output URL that have been visited to a text file.
$url | Out-File $done -Append
#Sleep while IE is running
while($ie.visible){
start-sleep -s 1
}
# ask the user for a comment
$comment = $vbi::InputBox("Write a comment about that website:", "Comment")
if ($comment -eq "") {
# cancel was pressed, so maybe revisit later?
$Sheet.Cells.Item($row, 1).Value = 'no'
}
else {
# edit exel sheet:
$Sheet.Cells.Item($row, 1).Value = 'yes'
$sheet.Cells.Item($row, 3).Value = $comment
}
# next row...
$row = $row + [int]1
} until (!$Sheet.Cells.Item($row,1).Text)
PS: I don't have an excel installed to test the code but I think it should work right away. I hope that is what you really wanted to know ;)
I'm not to great with powershell, but I am writing a script that opens an excel file, reads content from a .txt file, and throws that data into the excel sheet. The data entered in will give values to a chart on another worksheet. I want to ask the user if they would like to create a copy of this chart or not (not always required) If the user would like to then it will copy only the chart worksheet, and saveas its own workbook (..copyedChart.xls).
Now, I know when I use the .Copy() function it will take the current active sheet and already open up a new instance of excel. My problem is actually being able "control" this new instance of excel, I am having trouble understanding how to actually call that sheet and save it.
Here is what I have..
#Create an instance of Excel
$excel = New-Object -comobject Excel.Application
Write-Host "Initializing applications.." #all Write-Host are for the users..
#declaring sheet names..
$sheetName = "Sheet1"
$sheetName2 = "Sheet2"
$excel.displayAlerts = $false
Try{
#open Excel file
[string]$file = "C:\Users\Desktop\test.xls"
#create a reference to the specified excel workbook
$workBook = $excel.Workbooks.open($file)
#activates sheet
$sheet = $workBook.WorkSheets.Item($sheetName).activate()
$excel.Visible = $true
$data = Get-Content C:\Users\Desktop\input.txt
}#end try
Catch{
Write-Host "An error has occured."
}#end catch
Write-Host "Inputting new data.."
$i = 0
$rowNumber = 2
#Im just parsing the input.txt and spitting it out into excel
foreach($row in $data){
if($row){
[Array] $index = $row.Split(" ")
$i++
$column = 1
if($i -ge 1){
foreach($item in $index){
$excel.Cells.Item($rowNumber, $column) = "$item"
$column++
}#end foreach
$rowNumber++
}#end if $i
}#end if $row
}#end foreach
$date = $excel.Cells.Item(2, 1).Value().toString("MMMM-dd-yyyy") #row, column
#changes the active sheet
$sheet2 = $workBook.Sheets.Item($sheetName2).Select()
#references the active chart on the active page
$chart = $workBook.ActiveChart
Write-Host "Updating charts.."
#changes the title of the chart to include the current date
$chart.ChartTitle.Text = "Some title - $date"
#saves the files to these locations
$save = 'C:\Users\Desktop\'+$date+'-text.xls'
$saveChartCopy = 'C:\Users\Desktop\'+$date+'-CHARTCOPY.xls'
Write-Host "Saving new file.."
#save this workbook
$workBook.SaveAs($save)
#asks the user if they would like to create a copy of the chart
$makeCopy = Read-Host ("Would you like to create a copy of the chart? (y/n)")
#-----------------------------------------------------------STUCK HERE
#if yes, copy and save the chart as a new workbook.
if($makeCopy -eq "y" -or $makeCopy -eq "Y"){
$copiedChart = $chart.Copy() #Copies the chart and opens into a new instance of excel...
$copiedChart.SaveAs($saveChartCopy) #My sad attempt at trying to save the copied chart...
}
#if no, than close excel
elseif($makeCopy -eq "n" -or $makeCopy -eq "N"){
#close excel
Write-Host "Closing Excel.."
$excel.Quit()
Write-Host "Complete!"
}
else{
Read-Host "Please enter a valid option!"
}
If there is any confusion as to what I am asking please ask and I will try to further explain.
Also, because I am new to powershell and I am kind of a noob programmer.. I am open to all other input in regards to my code.
You have to create a new workbook object with $Excel.Workbooks.add():
$NewWorkBook = $Excel.Workbooks.Add()
# Copy and paste your sheet
$NewWorkBook.SaveAs($FileName)
$NewWorkBook.Close()
If I understand your problem correctly(and please tell me if I'm not), all you have to do is catch the handle to the new workbook in another variable. I was curious about this myself and decided to throw something quick and dirty to play around with it. Try something like this:
$Excel = New-Object -ComObject "Excel.Application"
$Workbook = $Excel.Workbooks.Add()
$Sheet = $Workbook.Worksheets.Item(1)
$Excel.Visible = $true
$Cells = $Sheet.Cells
$Workbook2 = $Excel.Workbooks.Add()
$Sheet2 = $Workbook2.Worksheets.Item(1)
$Cells2 = $Sheet2.Cells
$Cells.Item(1,1) = "Book 1"
$Cells2.Item(1,1) = "Book 2"
$Workbook.Close()
$Workbook2.Close()
$Excel.Quit()
[System.Runtime.Interopservices.Marshal]::ReleaseComObject($Excel)
Also, don't forget to cleanup that ComObject!
Using PowerShell I would like to capture user input, compare the input to data in an Excel spreadsheet and write the data in corresponding cells to a variable. I am fairly new to PowerShell and can't seem to figure this out. Example would be: A user is prompted for a Store Number, they enter "123". The input is then compared to the data in Column A. The data in the corresponding cells is captured and written to a variable, say $GoLiveDate.
Any help would be greatly appreciated.
User input can be read like this:
$num = Read-Host "Store number"
Excel can be handled like this:
$xl = New-Object -COM "Excel.Application"
$xl.Visible = $true
$wb = $xl.Workbooks.Open("C:\path\to\your.xlsx")
$ws = $wb.Sheets.Item(1)
Looking up a value in one column and assigning the corresponding value from another column to a variable could be done like this:
for ($i = 1; $i -le 3; $i++) {
if ( $ws.Cells.Item($i, 1).Value -eq $num ) {
$GoLiveDate = $ws.Cells.Item($i, 2).Value
break
}
}
Don't forget to clean up after you're done:
$wb.Close()
$xl.Quit()
[System.Runtime.Interopservices.Marshal]::ReleaseComObject($xl)
I find it preferable to use an OleDB connection to interact with Excel. It's faster than COM interop and less error prone than import-csv. You can prepare a collection of psobjects (one psobject is one row, each property corresponding to a column) to match your desired target grid and insert it into the Excel file. Similarly, you can insert a DataTable instead of a PSObject collection, but unless you start by retrieving data from some data source, PSObject collection way is usually easier.
Here's a function i use for writing a psobject collection to Excel:
function insert-OLEDBData ($file,$sheet,$ocol) {
{
"xlsb$"
{"Provider=Microsoft.ACE.OLEDB.12.0;Data Source=`"$File`";Extended Properties=`"Excel 12.0;HDR=YES;IMEX=1`";"}
"xlsx$"
{"Provider=Microsoft.ACE.OLEDB.12.0;Data Source=`"$File`";Extended Properties=`"Excel 12.0 Xml;HDR=YES;IMEX=1`";"}
}
$OLEDBCon = New-Object System.Data.OleDb.OleDbConnection($cs)
$hdr = $oCol|gm -MemberType NoteProperty|%{$_.name}
$names = '[' + ($hdr-join"],[") + ']'
$vals = (#("?")*([array]$hdr).length)-join','
$sql = "insert into [$sheet`$] ($names) values ($vals)"
$sqlCmd = New-Object system.Data.OleDb.OleDbCommand($sql)
$sqlCmd.connection = $oledbcon
$cpary = #($null)*([array]$hdr).length
$i=0
[array]$hdr|%{([array]$cpary)[$i] = $sqlCmd.parameters.add($_,"VarChar",255);$i++}
$oledbcon.open()
for ($i=0;$i-lt([array]$ocol).length;$i++)
{
for ($k=0;$k-lt([array]$hdr).length;$k++)
{
([array]$cpary)[$k].value = ([array]$oCol)[$i].(([array]$hdr)[$k])
}
$res = $sqlCmd.ExecuteNonQuery()
}
$OLEDBCon.close()
}
This does not seem to work anymore. I swear it used to, but maybe an update to O365 killed it? or I last used it on Win 7, and have long since moved to Win 10:
$GoLiveDate = $ws.Cells.Item($i, 2).Value
I can still use .Value for writing to a cell, but not for reading it into a variable. instead of the contents of the cell, It returns: "Variant Value (Variant) {get} {set}"
But after some digging, I found this does work to read a cell into a variable:
$GoLiveDate = $ws.Cells.Item($i, 2).Text
In regards to the next question / comment squishy79 asks about slowness, and subsequent
OleDB solutions, I can't seem to get those to work in modern OS' either, but my own performance trick is to have all my Excel PowerShell scripts write to a tab delimited .txt file like so:
Add-Content -Path "C:\FileName.txt" -Value $Header1`t$Header2`t$Header3...
Add-Content -Path "C:\FileName.txt" -Value $Data1`t$Data2`t$Data3...
Add-Content -Path "C:\FileName.txt" -Value $Data4`t$Data5`t$Data6...
then when done writing all the data, open the .txt file using the very slow Com "Excel.Application" just to do formatting then SaveAs .xlsx (See comment by SaveAs):
Function OpenInExcelFormatSaveAsXlsx
{
Param ($FilePath)
If (Test-Path $FilePath)
{
$Excel = New-Object -ComObject Excel.Application
$Excel.Visible = $true
$Workbook = $Excel.Workbooks.Open($FilePath)
$Sheet = $Workbook.ActiveSheet
$UsedRange = $Sheet.UsedRange
$RowMax = ($Sheet.UsedRange.Rows).count
$ColMax = ($Sheet.UsedRange.Columns).count
# This code gets the Alpha character for Columns, even for AA AB, etc.
For ($Col = 1; $Col -le $ColMax; $Col++)
{
$Asc = ""
$Asc1 = ""
$Asc2 = ""
If ($Col -lt 27)
{
$Asc = ([char]($Col + 64))
Write-Host "Asc: $Asc"
}
Else
{
$First = [math]::truncate($Col / 26)
$Second = $Col - ($First * 26)
If ($Second -eq 0)
{
$First = ($First - 1)
$Second = 26
}
$Asc1 = ([char][int]($First + 64))
$Asc2 = ([char][int]($Second + 64))
$Asc = "$Asc1$Asc2"
}
}
Write-Host "Col: $Col"
Write-Host "Asc + 1: $Asc" + "1"
$Range = $Sheet.Range("a1", "$Asc" + "1")
$Range.Select() | Out-Null
$Range.Font.Bold = $true
$Range.Borders.Item(9).LineStyle = 1
$Range.Borders.Item(9).Weight = 2
$UsedRange = $Sheet.UsedRange
$UsedRange.EntireColumn.AutoFit() | Out-Null
$SavePath = $FilePath.Replace(".txt", ".xlsx")
# I found scant documentation, but you need a file format 51 to save a .txt file as .xlsx
$Workbook.SaveAs($SavePath, 51)
$Workbook.Close
$Excel.Quit()
}
Else
{
Write-Host "File Not Found: $FilePath"
}
}
$TextFilePath = "C:\ITUtilities\MyTabDelimitedTextFile.txt"
OpenInExcelFormatSaveAsXlsx -FilePath $TextFilePath
If you don't care about formatting, you can just open the tab delimited .txt files as-is in Excel.
Of course, this is not very good for inserting data into an existing Excel spreadsheet unless you are OK with having the script rewrite the whole sheet it each time an insert is made. It will still run much faster than using COM in most cases.
I found this, and Yevgeniy's answer. I had to do a few minor changes to the above function in order for it to work. Most notably the handeling of NULL or empty valued values in the input array. Here is Yevgeniy's code with a few minor changes:
function insert-OLEDBData {
PARAM (
[Parameter(Mandatory=$True,Position=1)]
[string]$file,
[Parameter(Mandatory=$True,Position=2)]
[string]$sheet,
[Parameter(Mandatory=$True,Position=3)]
[array]$ocol
)
$cs = Switch -regex ($file)
{
"xlsb$"
{"Provider=Microsoft.ACE.OLEDB.12.0;Data Source=`"$File`";Extended Properties=`"Excel 12.0;HDR=YES`";"}
"xlsx$"
{"Provider=Microsoft.ACE.OLEDB.12.0;Data Source=`"$File`";Extended Properties=`"Excel 12.0 Xml;HDR=YES`";"}
}
$OLEDBCon = New-Object System.Data.OleDb.OleDbConnection($cs)
$hdr = $oCol | Get-Member -MemberType NoteProperty,Property | ForEach-Object {$_.name}
$names = '[' + ($hdr -join "],[") + ']'
$vals = (#("?")*([array]$hdr).length) -join ','
$sql = "insert into [$sheet`$] ($names) values ($vals)"
$sqlCmd = New-Object system.Data.OleDb.OleDbCommand($sql)
$sqlCmd.connection = $oledbcon
$cpary = #($null)*([array]$hdr).length
$i=0
[array]$hdr|%{([array]$cpary)[$i] = $sqlCmd.parameters.add($_,"VarChar",255);$i++}
$oledbcon.open()
for ($i=0;$i -lt ([array]$ocol).length;$i++)
{
for ($k=0;$k -lt ([array]$hdr).length;$k++)
{
IF (([array]$oCol)[$i].(([array]$hdr)[$k]) -notlike "") {
([array]$cpary)[$k].value = ([array]$oCol)[$i].(([array]$hdr)[$k])
} ELSE {
([array]$cpary)[$k].value = ""
}
}
$res = $sqlCmd.ExecuteNonQuery()
}
$OLEDBCon.close()
}