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 ;)
Related
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
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 looking for a way to read my excel sheet, then;
Find empty cells and give them a name, then mark them yellow.
Find rows containing a certain set of word and mark it red. If the script
If cannot find the words specified in row it should delete the entire row.
Here's my script so far:
Any help will be much appreciated
Updated with help of #TheMadTechician
#If there is no Out-Clipboard, set it
If(!(Get-Command Out-Clipboard -ErrorAction SilentlyContinue)){Set-Alias Out-Clipboard "$env:SYSTEMROOT\System32\clip.exe"}
#Get current date
$Date = get-date -format yyyy-MM-dd
$Company = "company"
$Company2 = "company2"
#Define all files/Paths.
$Path = "C:\$Company2\BlockedIP"
md "$Path\HTML\$Date" -Force |Out-Null
$path2 = "$Path\HTML\$Date"
$PathWeb = "/HTML/$Date"
#Path = C:/$Company2/BlockedIP
#Path2 = C:/$Company2/BlockedIP/HTML/2014-07-09
#Define File's used or created in this script.
$File = "$Path\IP-$Date.txt"
$FileHtml = "$Path2\IP-$Date.htm"
$FileXML = "$Path\IP-$Date.xlsx"
$FileHTMLWeb = "$PathWeb\IP-$date.htm"
#File = C:/$Company2/BlockedIP/IP-2014-07-09.txt
#FileXML = C:/$Company2/BlockedIP/HTML/2014-07-09/IP-2014-07-09.htm
#FileHtml = C:/$Company2/BlockedIP/HTML/2014-07-09/IP-2014-07-09.xlsx
#FileHTMLWeb = PublicIP/HTML/2014-07-09/IP-2014-07-09.htm
#Define error actions.
#$erroractionpreference = "SilentlyContinue"
#Get content from given IP list.
$colComputers = #(get-content $File | Sort -unique)
$count = $colComputers.Count
write-output "$Count IP's detected."
#Get DNS Results
$Progress=1
$DNSResults = $colComputers | %{
Write-Progress -Activity "Creating a usable 'Blocked IP' list ($Progress/$count)" -PercentComplete ($Progress/$Count*100) -Status "Please stand by"
try {
($dnsresult = [System.Net.DNS]::GetHostEntry($_))|out-null
}
catch {
$dnsresult = "Fail"
}
[PSCustomObject][Ordered]#{
Source=$_.ToUpper()
HostName=$dnsresult.HostName
IPAddress=$dnsresult.AddressList[0].ToString()
}
$Progress++
}
$DNSResults | Sort HostName | ConvertTo-Csv -Delimiter "`t" -NoTypeInformation | Select -Skip 1 | Out-Clipboard
#Open Excel.
$a = New-Object -comobject Excel.Application
#Since we want this script to look like it's being used without excel I set it's visibility to false.
$a.visible = $True
#Disable excel confirmations.
$a.DisplayAlerts = $False
<#
# set interactive to false so nothing from excel is shown.
$Excel.DisplayAlerts = $false
$Excel.ScreenUpdating = $false
$Excel.Visible = $false
$Excel.UserControl = $false
$Excel.Interactive = $false
#>
#Create sheets in Excel.
$b = $a.Workbooks.Add()
$c = $b.Worksheets.Item(1)
$c.Activate() | Out-Null
#Create a Title for the first worksheet and adjust the font
$c.Cells.Item(1,1)= "Blocked IP's $Date"
$c.Cells.Item(1,1).Font.ColorIndex = 55
$c.Cells.Item(1,1).Font.Color = 8210719
$range = $c.Range("a1","e1")
$range.Style = 'Title'
$range.Select()
$range.MergeCells = $true
$range.VerticalAlignment = -4108
$CounterRow = $Count+5
#Define subjects.
$c.Name = "Blocked IP's ($Date)"
$c.Cells.Item(2,1) = "Given IP"
$c.Cells.Item(2,2) = "Resolved DNS"
$c.Cells.Item(2,3) = "Returned IP"
$c.Cells.Item(2,5) = "$Company"
$c.Cells.Item($Count+5,1) = "Created by"
$link = "http://www.$Company"
$link2 = "https://www.linkedin.com/profile/view?id=#########"
$r = $c.Range("E2")
[void]$c.Hyperlinks.Add($r, $link)
$r = $c.Range("A$Counterrow")
[void]$c.Hyperlinks.Add($r, $link)
#Define cell formatting from subjects.
$c.Range("A2:E2").Interior.ColorIndex = 6
$c.Range("A2:E2").font.size = 13
$c.Range("A2:E2").Font.ColorIndex = 1
$c.Range("A2:E2").Font.Bold = $True
#Define html code for Excel save to .htm.
$xlExcelHTML = 44
#Define the usedrange, excluding header and footer rows
$e = $c.Range("A3:E$($DNSResults.Count+2)")
#Populate data into spreadsheet
$DNSResults | Sort HostName | ConvertTo-Csv -Delimiter "`t" -NoTypeInformation | Select -Skip 1 | Out-Clipboard
#$c.Cells.Item(3,1).Select()
$c.Paste($e,$false)
$e = $c.Range("A3:C$($DNSResults.Count+2)")
$c.Paste($e,$false)
$Keywords = "Google","thenetworkfactory","HappyTreeFriends"
$Filter = "($(($Keywords|%{[RegEx]::Escape($_)}) -join "|"))"
$DNSResults | Where{$_ -match $filter} | Sort HostName | ConvertTo-Csv -Delimiter "`t" -NoTypeInformation | Select -Skip 1 | Out-Clipboard
ForEach($Cell in $e){
If([String]::IsNullOrWhitespace($Cell.value2)){$Cell.interior.colorindex=6}
}
#Define the usedrange for autofitting.
$d = $c.UsedRange
#Set background color for the IP list.
$E.interior.colorindex = 15
#Define borders here.
$xlOpenXMLWorkbook = 51
$xlAutomatic=-4105
$xlBottom = -4107
$xlCenter = -4108
$xlRight = -4152
$xlContext = -5002
$xlContinuous=1
$xlDiagonalDown=5
$xlDiagonalUp=6
$xlEdgeBottom=9
$xlEdgeLeft=7
$xlEdgeRight=10
$xlEdgeTop=8
$xlInsideHorizontal=12
$xlInsideVertical=11
$xlNone=-4142
$xlThin=2
$selection = $c.range("A3:C$($DNSResults.Count+2)")
$selection.select() |out-null
$selection.HorizontalAlignment = $xlRight
$selection.VerticalAlignment = $xlBottom
$selection.WrapText = $false
$selection.Orientation = 0
$selection.AddIndent = $false
$selection.IndentLevel = 0
$selection.ShrinkToFit = $false
$selection.ReadingOrder = $xlContext
$selection.MergeCells = $false
$selection.Borders.Item($xlInsideHorizontal).Weight = $xlThin
#Make everything fit in it's cell.
$d.EntireColumn.AutoFit() | Out-Null
#Save the file as .xlsx on every placed IP to ensure the file is not lost due to any reason.
$b.SaveAs("$FileXML")
#Clear screen on every checked IP to remove the 'True' statement.
#cls
#
#Save final result as a .htm file
$b.SaveAs("$FileHTML",$xlExcelHTML)
#Close and quit Excel.
$b.Close()
get-process *Excel* | Stop-Process -force
#Move .txt file to the correct HTML folder.
move-item $file $path2 -Force
#Move .xlsx file to the correct HTML folder.
move-item $filexml $path2 -Force
#Declare XLSX file for mail
$MailXML = "$path2\IP-$Date.xlsx"
#Clear screen, again. (Let's keep things tidy.)
#cls
#Variables for public IP
# I am defining website url in a variable
$url = "http://checkip.dyndns.com"
# Creating a new .Net Object names a System.Net.Webclient
$webclient = New-Object System.Net.WebClient
# In this new webdownlader object we are telling $webclient to download the
# url $url
$IpPublic = $webclient.DownloadString($url)
# Just a simple text manuplation to get the ipadress form downloaded URL
# If you want to know what it contain try to see the variable $IpPublic
$IpPublic2 = $IpPublic.ToString()
$ipPublic3 = $IpPublic2.Split(" ")
$ipPublic4 = $ipPublic3[5]
$ipPublic5 = $ipPublic4.replace("</body>","")
$FinalIPAddress = $ipPublic5.replace("</html>","")
$ipLocal = (Get-WmiObject -class win32_NetworkAdapterConfiguration -Filter 'ipenabled = "true"').ipaddress[0]
#Variables e-mail.
$From = "Blocked IP <r.van.tour#$Company>"
$To = "IT Dept <r.van.tour#$Company>"
$CC = "Someone <$Company2#$Company"
$Subject = "Blocked IPs for $date ($Count Total)"
#The href should point to the htm file in your iis/apache folder.
$WebLink = $FinalIPAddress+$FileHtmlWeb
$here = "<a href='http://$Weblink'><b>Here</b></a>"
#Define the body of your e-mail, in this case it displays a message and shows the server it is send from with it's local IP.
#A link to the .htm file, how many IP's were blocked and the date of the message.
$Body = "<!DOCTYPE html><html><head> <title>Blocked IP's $Date</title></head><header><h1>Blocked IP</h1><p><time pubdate datetime='$date'></time></p></header><br>"
$body += "<body>Dear <font color=black>$to</font>,<br><br>"
$body += "This is an automated message generated by server: <font color=red><b>$env:COMPUTERNAME, $IPLocal.</b></font><br><br>"
$body += "Click <font color=red><b>$here</b></font> to see the Blocked IP report for $date containing $count IP's.<br>"
$body += "Or see the attachment to open it in Excel.<br></body></html>"
#Clear screen, again. (Let's keep things tidy.)
#cls
#Send output as e-mail.
$SMTPServer = "smtp.gmail.com"
$SMTPPort = "587"
$Username = "###gmail.com"
$Password = "##"
$message = New-Object System.Net.Mail.MailMessage
$message.IsBodyHTML = $true
$message.ReplyTo = $From
$message.Sender = $From
$message.subject = $subject
$message.body = $body
$message.to.add($to)
$message.from = $From
$message.attachments.add($MailXML)
$smtp = New-Object System.Net.Mail.SmtpClient($SMTPServer, $SMTPPort);
$smtp.EnableSSL = $true
$smtp.Credentials = New-Object System.Net.NetworkCredential($Username, $Password);
$smtp.send($message)
#Create a function to relase Com object at end of script.
function Release-Ref ($ref) {
([System.Runtime.InteropServices.Marshal]::ReleaseComObject(
[System.__ComObject]$ref) -gt 0)
[System.GC]::Collect()
[System.GC]::WaitForPendingFinalizers()
}
#Release COM Object
[System.Runtime.InteropServices.Marshal]::ReleaseComObject([System.__ComObject]$a) | Out-Null
#Clear screen for the final time. (Let's keep things tidy.)
#cls
#Exit powershell
exit
#TheMadTechnician
There seems to be a problem with this part of the script where non-resolved IP adresses fail to be shown in $DNSResults
$Keywords = "Google","Cloudflare","Cloud","Ping","Easy- Voyage","McAfee","Pingdom","Panopta","Scoot","Uniglobe"
$Filter = "($(($Keywords|%{[RegEx]::Escape($_)}) -join "|"))"
#Define error actions.
#$erroractionpreference = "SilentlyContinue"
#Get content from given IP list.
$colComputers = #(get-content $File | Sort -unique)
$SourceCount = $colComputers.Count
write-output "$SourceCount IP's detected."
#Get DNS Results
$Progress=1
$DNSResults = $colComputers | %{
Write-Progress -Activity "Creating a usable 'Blocked IP' list ($Progress/$sourcecount)" -PercentComplete ($Progress/$sourceCount*100) -Status "Please stand by"
try {
($dnsresult = [System.Net.DNS]::GetHostEntry($_))|out-null
}
catch {
$dnsresult = "Fail"
}
[PSCustomObject][Ordered]#{
Source=$_.ToUpper()
HostName=$dnsresult.HostName
IPAddress=$dnsresult.AddressList[0].ToString()
}
$Progress++
}
$count = ($DNSResults|?{$_ -match $filter}).count
Ok, I'm guessing you have borrowed and pieced this together from scripts found all over because the way it is put together seems kind of unplanned. Like things were added and revised, and there wasn't a master plan that put it all together. So, where to start? With the progress bar, since I already addressed that.
The Progress Bar
Move $i=1 above ForEach($strComputer in $colComputers) and add $i++ after $intRow = $intRow + 1 (which could be shortened to $intRow++). But we already knew that. This kind of becomes a moot point, since I've reworked a good bit of your script, but in practice you'll see it with the ForEach loop coming up next.
The ForEach Loop
Next, your ForEach loop. Boy, that's quite a doozy there. You are making it do things over, and over, and over that really just need to be done once after everything is complete. So, what shall we move to after the loop? Let's start with, well, almost everything. Cell formatting? Later. Filling cells? Later. Sorting? Later. Adjusting column width? Later. Saving the file? Later!
So, what does that leave us in the loop? Not much really, all it leaves is the progress bar and checking DNS entries. Why do it this way? Because we can create an array in PowerShell with the data you want, sort the data, select only the fields that you want to use from the array, and then paste all data in at once instead of one record at a time, and do all the formatting afterwards. In fact, what would probably be better for formatting, instead of checking cells for blanks and coloring them red is to just apply Conditional Formatting to them so that if they're blank they show up red, and let Excel do the work for you.
This is going to make the script run a lot faster since you aren't doing the same work several times, and are working with raw data in PowerShell instead of making Excel do it. I ended up reducing your ForEach loop down to just a few lines:
#Get DNS Results
$Progress=1
$DNSResults = $colComputers | %{
Write-Progress -Activity "Creating a usable 'Blocked IP' list ($Progress/$count)" -PercentComplete ($Progress/$Count*100) -Status "Please stand by"
try {
($dnsresult = [System.Net.DNS]::GetHostEntry($_))|out-null
}
catch {
$dnsresult = "Fail"
}
[PSCustomObject][Ordered]#{
Source=$_.ToUpper()
HostName=$dnsresult.HostName
IPAddress=$dnsresult.AddressList[0].ToString()
}
$Progress++
}
That will loop through the entries and create an array of custom objects that have 3 properties that are the 3 cells you wanted in your spreadsheet.
Excel Setup
Ok, you obviously have a grasp of things as far as formatting and injecting simple text, so I'm going to glaze over your title setup for now and get to getting the processed data into the spreadsheet.
For arrays of data (like your Computer/HostName/IP array of data that you were putting in) it is easier to paste it into Excel as a tab delimited CSV object. Out-Clipboard isn't a standard PowerShell cmdlet, even if I think it should be. On the other hand Clip.exe comes standard with windows, so we can just set an alias for it (if you have the PowerShell Community Extensions this is already done for you). I put this at the top of the script to get it out of the way. Normally I would put it right after any functions that I had setup in a script if I needed to setup an alias like this. It checks if you have Out-Clipboard, and if you don't it sets up the alias for Clip.exe to Out-Clipboard.
If(!(Get-Command Out-Clipboard -ErrorAction SilentlyContinue)){Set-Alias Out-Clipboard "$env:SYSTEMROOT\System32\clip.exe"}
Now we can pipe things to the clipboard, and that's real handy for what we want to do next. We are going to take our array and convert it to a tab delimited CSV (with no type info), skip the first entry (the header row), sort what's left by HostName, and pipe it to the clipboard.
$DNSResults | Sort HostName | ConvertTo-Csv -Delimiter "`t" -NoTypeInformation | Select -Skip 1 | Out-Clipboard
Now we just have to paste that into Excel. To do that we need a range object to specify as the target, so I'll set that up, and then we use the WorkSheet object's Paste(Range,link) method. Don't worry about the link part, we are going to use the $false Boolean for that because we are not going to link the pasted data to a datasource for dynamic updating. This looks something like:
$e = $c.Range("A3:E$($DNSResults.Count+2)")
$c.Paste($e,$false)
Then we go on to setting the color for your results, and the rest of your formatting.
So... why'd we change it?
So, my favorite uncle always told me growing up "if it ain't broke, don't fix it". Your script did what you wanted, so why did I revamp it if it wasn't broken? The changes aren't huge, it's really about speeding things up and inserting everything at once instead of one at a time, but the biggest thing is that now we can filter in PowerShell before you insert into Excel, and that brings us to your original questions:
Set blank cells to yellow.
Match rows with key words, delete all other rows.
Seek and destroy!
We'll get to the blanks in a second, but deleting the rows that don't have your certain key words is easy now. Don't delete them, just don't insert them to start with! It's easy enough to setup a list of words to filter for, and then only include records with those words when we go to export to the clipboard.
$Keywords = "Google","Facebook","HappyTreeFriends"
$Filter = "($(($Keywords|%{[RegEx]::Escape($_)}) -join "|"))"
The second line creates a string that you can do a RegEx match against. It takes your keywords, escapes any special characters, joins them up with a pipe separating them, and encloses them in parenthesis. After those two lines $Filter = (Google|Facebook|HappyTreeFriends). Then on the line that you want to send data to the clipboard just add a Where clause:
$DNSResults | Where{$_ -match $filter} | Sort HostName | ConvertTo-Csv -Delimiter "`t" -NoTypeInformation | Select -Skip 1 | Out-Clipboard
That way only records that have one of your blacklisted words will be inserted into Excel, and you don't have to worry about going back and deleting rows. You want rows that do have the key words to be highlighted red? Just color them that way (right now you have them set to color index 15, grey, just change that to 3 and they'll be red) to start with since they should be the only things in there.
What's missing?
Blank cells can be hard to spot, especially once you get some formatting going, and have less defined cell borders. Finding empty cells in this case is really easy though. We already have $e, which is a range object that includes all of the data that we just inserted into Excel, even the blank cells. A range object is basically a collection of cells, each having it's own properties like it's location (row,column), formatting, value, etc. What you can do is simple enough:
#If there is no Out-Clipboard, set it
If(!(Get-Command Out-Clipboard -ErrorAction SilentlyContinue)){Set-Alias Out-Clipboard "$env:SYSTEMROOT\System32\clip.exe"}
#Get current date
$Date = get-date -format yyyy-MM-dd
$Company = "company"
$Company2 = "company2"
#Define all files/Paths.
$Path = "C:\$Company2\BlockedIP"
md "$Path\HTML\$Date" -Force |Out-Null
$path2 = "$Path\HTML\$Date"
$PathWeb = "/HTML/$Date"
#Path = C:/$Company2/BlockedIP
#Path2 = C:/$Company2/BlockedIP/HTML/2014-07-09
#Define File's used or created in this script.
$File = "$Path\IP-$Date.txt"
$FileHtml = "$Path2\IP-$Date.htm"
$FileXML = "$Path\IP-$Date.xlsx"
$FileHTMLWeb = "$PathWeb\IP-$date.htm"
#File = C:/$Company2/BlockedIP/IP-2014-07-09.txt
#FileXML = C:/$Company2/BlockedIP/HTML/2014-07-09/IP-2014-07-09.htm
#FileHtml = C:/$Company2/BlockedIP/HTML/2014-07-09/IP-2014-07-09.xlsx
#FileHTMLWeb = PublicIP/HTML/2014-07-09/IP-2014-07-09.htm
$Keywords = "Google","thenetworkfactory"
$Filter = "($(($Keywords|%{[RegEx]::Escape($_)}) -join "|"))"
#Define error actions.
#$erroractionpreference = "SilentlyContinue"
#Get content from given IP list.
$colComputers = #(get-content $File | Sort -unique)
$SourceCount = $colComputers.Count
write-output "$Count IP's detected."
#Get DNS Results
$Progress=1
$DNSResults = $colComputers | %{
Write-Progress -Activity "Creating a usable 'Blocked IP' list ($Progress/$sourcecount)" -PercentComplete ($Progress/$sourceCount*100) -Status "Please stand by"
try {
($dnsresult = [System.Net.DNS]::GetHostEntry($_))|out-null
}
catch {
$dnsresult = "Fail"
}
[PSCustomObject][Ordered]#{
Source=$_.ToUpper()
HostName=$dnsresult.HostName
IPAddress=$dnsresult.AddressList[0].ToString()
}
$Progress++
}
$count = ($DNSResults|?{$_ -match $filter}).count
#Open Excel.
$a = New-Object -comobject Excel.Application
#Since we want this script to look like it's being used without excel I set it's visibility to false.
$a.visible = $True
#Disable excel confirmations.
$a.DisplayAlerts = $False
<#
# set interactive to false so nothing from excel is shown.
$Excel.DisplayAlerts = $false
$Excel.ScreenUpdating = $false
$Excel.Visible = $false
$Excel.UserControl = $false
$Excel.Interactive = $false
#>
#Create sheets in Excel.
$b = $a.Workbooks.Add()
$c = $b.Worksheets.Item(1)
$c.Activate() | Out-Null
#Create a Title for the first worksheet and adjust the font
$c.Cells.Item(1,1)= "Blocked IP's $Date"
$c.Cells.Item(1,1).Font.ColorIndex = 55
$c.Cells.Item(1,1).Font.Color = 8210719
$range = $c.Range("a1","e1")
$range.Style = 'Title'
$range.Select()
$range.MergeCells = $true
$range.VerticalAlignment = -4108
$CounterRow = $Count+5
#Define subjects.
$c.Name = "Blocked IP's ($Date)"
$c.Cells.Item(2,1) = "Given IP"
$c.Cells.Item(2,2) = "Resolved DNS"
$c.Cells.Item(2,3) = "Returned IP"
$c.Cells.Item(2,5) = "$Company"
$c.Cells.Item($Count+5,1) = "Created by"
$link = "http://www.$Company"
$link2 = "https://www.linkedin.com/profile/view?id=#########"
$r = $c.Range("E2")
[void]$c.Hyperlinks.Add($r, $link)
$r = $c.Range("A$Counterrow")
[void]$c.Hyperlinks.Add($r, $link)
#Define cell formatting from subjects.
$c.Range("A2:E2").Interior.ColorIndex = 6
$c.Range("A2:E2").font.size = 13
$c.Range("A2:E2").Font.ColorIndex = 1
$c.Range("A2:E2").Font.Bold = $True
#Define html code for Excel save to .htm.
$xlExcelHTML = 44
#Define the usedrange, excluding header and footer rows
$e = $c.Range("A3:E$(2+$Count)")
#Populate data into spreadsheet
$DNSResults | Where{$_ -match $filter} | Sort HostName | ConvertTo-Csv -Delimiter "`t" -NoTypeInformation | Select -Skip 1 | Out-Clipboard
#$c.Cells.Item(3,1).Select()
$c.Paste($e,$false)
ForEach($Cell in $e){
If([String]::IsNullOrWhitespace($Cell.value2)){$Cell.interior.colorindex=6}
}
#Define the usedrange for autofitting.
$d = $c.UsedRange
#Set background color for the IP list.
$E.interior.colorindex = 15
#Define borders here.
$xlOpenXMLWorkbook = 51
$xlAutomatic=-4105
$xlBottom = -4107
$xlCenter = -4108
$xlRight = -4152
$xlContext = -5002
$xlContinuous=1
$xlDiagonalDown=5
$xlDiagonalUp=6
$xlEdgeBottom=9
$xlEdgeLeft=7
$xlEdgeRight=10
$xlEdgeTop=8
$xlInsideHorizontal=12
$xlInsideVertical=11
$xlNone=-4142
$xlThin=2
$selection = $c.range("A3:C$($DNSResults.Count+2)")
$selection.select() |out-null
$selection.HorizontalAlignment = $xlRight
$selection.VerticalAlignment = $xlBottom
$selection.WrapText = $false
$selection.Orientation = 0
$selection.AddIndent = $false
$selection.IndentLevel = 0
$selection.ShrinkToFit = $false
$selection.ReadingOrder = $xlContext
$selection.MergeCells = $false
$selection.Borders.Item($xlInsideHorizontal).Weight = $xlThin
#Make everything fit in it's cell.
$d.EntireColumn.AutoFit() | Out-Null
#Save the file as .xlsx on every placed IP to ensure the file is not lost due to any reason.
$b.SaveAs("$FileXML")
#Clear screen on every checked IP to remove the 'True' statement.
#cls
#
#Save final result as a .htm file
$b.SaveAs("$FileHTML",$xlExcelHTML)
#Close and quit Excel.
$b.Close()
get-process *Excel* | Stop-Process -force
#Move .txt file to the correct HTML folder.
move-item $file $path2 -Force
#Move .xlsx file to the correct HTML folder.
move-item $filexml $path2 -Force
#Declare XLSX file for mail
$MailXML = "$path2\IP-$Date.xlsx"
#Clear screen, again. (Let's keep things tidy.)
#cls
#Variables for public IP
# I am defining website url in a variable
$url = "http://checkip.dyndns.com"
# Creating a new .Net Object names a System.Net.Webclient
$webclient = New-Object System.Net.WebClient
# In this new webdownlader object we are telling $webclient to download the
# url $url
$IpPublic = $webclient.DownloadString($url)
# Just a simple text manuplation to get the ipadress form downloaded URL
# If you want to know what it contain try to see the variable $IpPublic
$IpPublic2 = $IpPublic.ToString()
$ipPublic3 = $IpPublic2.Split(" ")
$ipPublic4 = $ipPublic3[5]
$ipPublic5 = $ipPublic4.replace("</body>","")
$FinalIPAddress = $ipPublic5.replace("</html>","")
$ipLocal = (Get-WmiObject -class win32_NetworkAdapterConfiguration -Filter 'ipenabled = "true"').ipaddress[0]
#Variables e-mail.
$From = "Blocked IP <r.van.tour#$Company>"
$To = "IT Dept <r.van.tour#$Company>"
$CC = "Someone <$Company2#$Company"
$Subject = "Blocked IPs for $date ($Count Total)"
#The href should point to the htm file in your iis/apache folder.
$WebLink = $FinalIPAddress+$FileHtmlWeb
$here = "<a href='http://$Weblink'><b>Here</b></a>"
#Define the body of your e-mail, in this case it displays a message and shows the server it is send from with it's local IP.
#A link to the .htm file, how many IP's were blocked and the date of the message.
$Body = "<!DOCTYPE html><html><head> <title>Blocked IP's $Date</title></head><header><h1>Blocked IP</h1><p><time pubdate datetime='$date'></time></p></header><br>"
$body += "<body>Dear <font color=black>$to</font>,<br><br>"
$body += "This is an automated message generated by server: <font color=red><b>$env:COMPUTERNAME, $IPLocal.</b></font><br><br>"
$body += "Click <font color=red><b>$here</b></font> to see the Blocked IP report for $date containing $count IP's.<br>"
$body += "Or see the attachment to open it in Excel.<br></body></html>"
#Clear screen, again. (Let's keep things tidy.)
#cls
#Send output as e-mail.
$SMTPServer = "smtp.gmail.com"
$SMTPPort = "587"
$Username = "###gmail.com"
$Password = "##"
$message = New-Object System.Net.Mail.MailMessage
$message.IsBodyHTML = $true
$message.ReplyTo = $From
$message.Sender = $From
$message.subject = $subject
$message.body = $body
$message.to.add($to)
$message.from = $From
$message.attachments.add($MailXML)
$smtp = New-Object System.Net.Mail.SmtpClient($SMTPServer, $SMTPPort);
$smtp.EnableSSL = $true
$smtp.Credentials = New-Object System.Net.NetworkCredential($Username, $Password);
$smtp.send($message)
#Create a function to relase Com object at end of script.
function Release-Ref ($ref) {
([System.Runtime.InteropServices.Marshal]::ReleaseComObject(
[System.__ComObject]$ref) -gt 0)
[System.GC]::Collect()
[System.GC]::WaitForPendingFinalizers()
}
#Release COM Object
[System.Runtime.InteropServices.Marshal]::ReleaseComObject([System.__ComObject]$a) | Out-Null
#Clear screen for the final time. (Let's keep things tidy.)
#cls
#Exit powershell
exit
Edit: I found the issue with your script. You didn't update the line where you are copying to the clipboard, you just added in lines later in the script, after the script already pasted to Excel, so the code you added effectively did nothing. I've made a couple of updates to your code (to handle record counts better, and filter correctly), and updated the above script.
Edit2: Man this post is getting long. Ok, so to include all entries I've modified a few things. First the ForEach loop that looks up the DNS results, I modified the object creation lines to check if they exist before trying to populate so it stops throwing errors on things that don't have one or both HostName and IP Addresses for DNS Lookups.
[PSCustomObject][Ordered]#{
Source=$_.ToUpper()
HostName=$(if(!([string]::IsNullOrEmpty($dnsresult.HostName))){$dnsresult.HostName})
IPAddress=$(if(!([string]::IsNullOrEmpty($dnsresult.AddressList))){$dnsresult.AddressList[0].ToString()})
}
Then I split the results into three catagories: With Hostname matching a keyword, with Hostname not matching a keyword, and no Hostname (also change $count wince we are including everything).
$DNSWithKeyword = $DNSResults | ?{$_.HostName -match $Filter}
$DNSNoKeyword = $DNSResults | ?{!($_.HostName -match $Filter) -and !([string]::IsNullOrEmpty($_.HostName))}
$DNSLookupFailed = $DNSResults | ?{([string]::IsNullOrEmpty($_.HostName))}
#$count = ($DNSResults|?{$_ -match $filter}).count
$count = $SourceCount
Then down a ways where used range is defined I added one for each category, and instead of just one Copy/Paste I do three so there are those matching keywords first, sorted by hostname, then no keyword sorted by hostname, and then those that have no hostname. Then I colored each by section, and went back after and colored blank cells yellow again.
#Define the usedrange, excluding header and footer rows
$KeyRange = $c.Range("A3:E$(2+$DNSWithKeyword.Count)")
$NoKeyRange = $c.Range("A$(2+$DNSWithKeyword.Count+1):E$(2+$DNSWithKeyword.Count+$DNSNoKeyword.Count)")
$NoDNSRange = $c.Range("A$(2+$DNSWithKeyword.Count+$DNSNoKeyword.Count+1):E$(2+$DNSWithKeyword.Count+$DNSNoKeyword.Count+$DNSLookupFailed.Count)")
$e = $c.Range("A3:E$(2+$Count)")
#Populate data into spreadsheet
$DNSWithKeyword | Sort HostName | ConvertTo-Csv -Delimiter "`t" -NoTypeInformation | Select -Skip 1 | Out-Clipboard
$c.Paste($KeyRange,$false)
$DNSNoKeyword | Sort HostName | ConvertTo-Csv -Delimiter "`t" -NoTypeInformation | Select -Skip 1 | Out-Clipboard
$c.Paste($NoKeyRange,$false)
$DNSLookupFailed | ConvertTo-Csv -Delimiter "`t" -NoTypeInformation | Select -Skip 1 | Out-Clipboard
$c.Paste($NoDNSRange,$false)
#Define the usedrange for autofitting.
$d = $c.UsedRange
#Set background color for the IP list.
$KeyRange.interior.colorindex = 3
$NoKeyRange.interior.colorindex = 15
$NoDNSRange.interior.colorindex = 14
ForEach($Cell in $e){
If([String]::IsNullOrWhitespace($Cell.value2)){$Cell.interior.colorindex=6}
}
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()
}