Excel File Reader Script in Powershell Not Working? - excel

I wanted to read the contents of an excel file and then output them to a text file. Currently, my code only outputs the results to the console.
I tried to write code that gets the applicable columns and then loads them into an array to be written to the console later.
$Excel = New-Object -ComObject Excel.Application
$Excel.Visible = $False
$PLACE = "C:\Somepath\somename.xlsx"
$OpenFile = $Excel.Workbooks.Open($PLACE)
$Workbook = $OpenFile.Worksheets
$Worksheet = $Workbook.Item(1)
# Get the values for each column
$MNumber = $Worksheet.Cells | where {$_.value2 -eq "Material-Number"} | select -First 1
$MDescription = $Worksheet.Cells | where {$_.value2 -eq "Material-Description"} | select -First 1
# Get the values for each row in Material Number
$NumValues = #()
$NumValues = for($i=2; $MNumber.Cells.Item($i).Value2 -ne $null; $i++ ){
$MNumber.Cells.Item($i)
}
# Get the values for each row in Material Description
$DescValues = #()
$DescValues = for($i=2; $MDescription.Cells.Item($i).Value2 -ne $null; $i++ ){
$MDescription.Cells.Item($i)
}
$NumValues | ForEach-Object {Write-host $_.value2}
$DescValues | ForEach-Object {Write-Host $_.value2}
Nothing output to the console by the end of the process. Even though those are the exact columns and each column contains data.

Try match instead of eq here:
$MNumber = $Worksheet.Cells | where {$_.value2 -match "Material-Number"} | select -First 1
$MDescription = $Worksheet.Cells | where {$_.value2 -match "Material-Description"} | select -First 1
I'm not sure if your cell has other strings within it on $_.value2 but if it does eq will not work. It's trying to find the ENTIRE string it's conditioning against. Therefore, if there's other strings of data within the cell it will not pull the data.

Related

Select Excel row --> Create Hashtable from Entries

I have an Excel .xlsx-file which looks like this:
Now I'd like to create a PowerShell script, which can do the following:
Ask the User which row he wants to use (for example 4)
Create a hashtable with those entries
The hashtable should look like this:
Name Value
---- -----
Name Jane Doe
Age 67
Street Grace St. 19
Zipcode 12345
Date 03.03.2013
Does someone know how I can achieve this?
Plus: Is this actually achievable with a xlsx-File or do I need to use a CSV-file?
First, it is achievable if we convert the xls to csv:
$excelFilePath = "C:\Temp\abc.xlsx"
$Excel = New-Object -ComObject Excel.Application
$Excel.Visible = $false
$Excel.DisplayAlerts = $false
$Workbook = $excel.Workbooks.Open($excelFilePath)
$($Workbook.Worksheets | where {$_.Name -eq "Sheet1"}).SaveAs("C:\Temp\abcd.csv", 6)
$Excel.Quit()
I would suggest using an ID instead of row number:
$importedAbcd = Import-Csv -Path "C:\Temp\abcd.csv" -Delimiter ";"
[int]$answer = Read-Host "Select ID to work with"
$object = $importedAbcd | Where-Object {$_.ID -eq $answer}
You have the row now converting it to hash:
$hash = #{}
$object.psobject.properties | Foreach { $hash[$_.Name] = $_.Value }
A more intuitive way is to
read the excel file directly with the Import-Excel module
pipe it to Out-Gridview, select a single line
and populate the hashtable
$HashTable = [ordered]#{}
Import-Excel "x:\path\sample.xls" | Out-GridView -OutputMode Single -Title "Select one line" |
ForEach-Object {$_.psobject.properties | ForEach-Object {
$HashTable[$_.Name]=$_.Value
}
}
$HashTable
Sample output:
> $HashTable
Name Value
---- -----
Name Molly Johnson
Agr 35
Street Murraay St. 86
Zipcode 54321
Date 02.02.2009

Reading Excel Spreadsheet and adding information to it

I currently have a spreadsheet with the following display:
+------------+---------+---------+
| Site | UserP |UserName |
+------------+---------+---------+
| Site A | Read | user1 |
| | | |
+------------+---------+---------+
| Site A | Write | user2 |
| | | |
+------------+---------+---------+
The problem is that sometimes the UserName field brings a Group and then I have to open this group and create a new row for each user in this group with the same information.
So if I had this:
Site A | Read | Group 1
It would become this:
Site A | Read | Group 1
Site A | Read | Group 1 User A
Site A | Read | Group 1 User B
Site A | Read | Group 1 User C
I can manually pull the information from the group, the thing is that I don't know how to read a specific column from excel and then create a new row in that excel sheet.
I didn't post any code cause I barely have one. I don't know how to loop to the cells.
This is what I have.
$file = "\Desktop\AccessReviewReportPRE.csv"
$sheetName = "AccessReviewReportPRE"
$objExcel = New-Object -ComObject Excel.Application
$workbook = $objExcel.Workbooks.Open($file)
$sheet = $workbook.Worksheets.Item($sheetName)
Now, I know how to access individual cells, like this:
$worksheet.cells.Item(3, 3).text
Just not sure how to loop trough all rows, check what is in the third column and do a action on that.
Following #BenH tip, I did this:
$file = "\Desktop\AccessReviewReportPRE.csv"
$fileContent = Import-csv $file -header "URL", "Site/List/Folder/Item", "Title/Name", "PermissionType", "Permissions", "LoginName"
$newRow = New-Object PsObject -Property #{"URL" = 'sdfs'; "Site/List/Folder/Item" = 'dsfd'; "Title/Name" = 'sdf'; "PermissionType" = 'dsf'; "Permissions" = 'sdfs'; "LoginName" = 'sdf'}
$fileContent += $newRow
$fileContent | Export-Csv -NoTypeInformation -Path "\Desktop\AccessReviewReportAFTER.csv"
But it is duplicating the header. Any ideas why?
To loop through each Row you need a loop.
I recommend a do-while.
#My Code to find a groupname
$WorkBook=$Excel.Workbooks.Open($strPath)
#Selcet the first Tabelle
$mappe = $workbook.sheets.item(1)
$Target = $mappe.Range("A1").Find($Grouname)
$start = $Target.Row
end, $middle, $counter = 0
do {
#assigning
$middle = $end
#Search Funktion
$nextFind = $mappe.Range("A1:A150000").FindNext($Target)
#Old Range assinging to $find
$Target = $nextFind
#Assign the Rownumber
$end = $nextFind.Row
#Check if the Value is correct
if ($mappe.cells.Item($nextFind.Row,1).Value() -eq $Groupname)
{
$counter++
}else{
break
}
}while($end -gt $start)
#The FindNext function only works when there are at least 2 of the groups
#but in some cases there is only 1 group. To fix the issue a counter counts
#the passages and if it is 1, $middle will be assigned the start row.
if($counter -eq 1)
{
$middle= $start
}
#Because a line must be added after the last one, $row needs be increased by 1
$row = $middle + 1
#Column stays the same, later it will be increased
$column = $Target.Column
# ADD ROW
$eRow = $mappe.cells.item($row,1).entireRow
$active = $eRow.activate()
$active = $eRow.insert()
With this Code, I'm looking for a Group. The Finde function which I use helps me to locate the group. The finde function does not work in a loop, that's why you must use the FindeNext function.
$middle contains the last row number to add a row it must be increased.
In order to add data into multiple columns, the row has to stay the same, but the column must be increased.
$mappe.Cells.Item($row, $column) = Text
$column++
If you wanna check the 3 column and based on its value you wanna make an action, make a if-statment.
I know the Code ain't perfect, but I hope it helped.

PowerShell saving excel sheet in unreadable format

I have the below piece of code that checks for Files to Tapes jobs for a database and gives the output in an excel sheet.
$date = Get-Date
$day = $date.Day
$hour = $date.Hour
$Excel = New-Object -ComObject Excel.Application
$Excel.visible = $true
$Excel.DisplayAlerts = $false
$Workbook = $Excel.Workbooks.Add()
$Sheet = $Excel.Worksheets.Item(1)
#Counter variable for rows and columns
$intRow = 1
$intCol = 1
$Sheet.Cells.Item($intRow,1) = "Tasks/Servers"
$Sheet.Cells.Item($intRow,2) = "DateLastRun"
$Sheet.Cells.Item($intRow,3) = "PRX1CSDB01"
$Sheet.Cells.Item($intRow,4) = "PRX1CSDB02"
$Sheet.Cells.Item($intRow,5) = "PRX1CSDB03"
$Sheet.Cells.Item($intRow,6) = "PRX1CSDB11"
$Sheet.Cells.Item($intRow,7) = "PRX1CSDB12"
$Sheet.Cells.Item($intRow,8) = "PRX1CSDB13"
$Sheet.Cells.Item($intRow+1,1) = "File To Tape weekly Full Backup"
$Sheet.UsedRange.Rows.Item(1).Borders.LineStyle = 1
#FTT.txt contains the path for a list of servers
$path = Get-Content D:\Raghav\DB_Integrated\FTT.txt
foreach ($server in $path)
{
If (Test-Path $server)
{
$BckpWeek = gci -path $server | select-object | where {$_.Name -like "*logw*"} | sort LastWriteTime | select -last 1
$Sheet.Cells.Item($intRow+1,$intCol+1) = $BckpWeek.LastWriteTime.ToString('MMddyyyy')
$Sheet.UsedRange.Rows.Item($intRow).Borders.LineStyle = 1
$x = (get-date) - ([datetime]$BckpWeek.LastWriteTime)
if( $x.days -gt 7){$status_week = "Failed"}
else{$status_week = "Successful"}
$Sheet.Cells.Item($intRow+1,$intCol+2) = $status_week
$intCol++
}
else
{
$Sheet.Cells.Item($intRow+1,$intCol+2) = "Path Not Found"
$intCol++
}
}
$Sheet.UsedRange.EntireColumn.AutoFit()
$workBook.SaveAs("C:\Users\Output.xlsx",51)
$excel.Quit()
However, when I try to import the contents of Output.xlsx into a variable say $cc, I get data in an unreadable format.
$cc = Import-Csv "C:\Users\Output.xlsx"
Attached is the image for what I get on exporting output.xlsx into $cc. I tried to put the output in csv format too. But that also doesnt seem to help.Anybody having any idea on this or having faced any similar situation before?
#ZevSpitz - Looking for the OleDbConnection class, I landed up at https://blogs.technet.microsoft.com/pstips/2014/06/02/get-excel-data-without-excel/ . This is what I was looking for. Thank you for pointing me out in the right direction.
#MikeGaruccio - Unfortunately, I didn't find Import-Excel command in Get-Help menu. I am using Powershell 4.0. Anyways, thank you for the suggestion.

Pulling Columns from Excel

ExampleData
I'm trying to pull from a total of 6 columns from an excel worksheet. I'm using a modified script from another post on stack, it looks like this.
$strPath = "C:\Users\User\Documents\EXEL\fj.xls"
$AssetInv = "C:\Users\User\Documents\EXEL\fj.txt"
$objExcel = New-Object -ComObject Excel.Application
$objExcel.Visible = $false
$WorkBook = $objExcel.Workbooks.Open($strPath)
$worksheet = $workbook.sheets.item("Daily Price Quote - Better Of -")
$intRowMax = ($worksheet.UsedRange.Rows).count
#$StartRow = 2
#$site = 1
#$state = 3
#$retailprice = 18
#$yourprice = 20
#$SavingsTotal = 21
Write "`Site City State retailprice yourprice savingstotal" | Out-File $AssetInv
Write "--------------- ------------------- -------------------- -----------------------------" |
Out-FIle $AssetInv -Append
Write-Host "Processing: " $intRowMax "rows"
for ($intRow = 7 ; $intRow -le $intRowMax ; $intRow++) {
$site = $worksheet.cells.item($intRow, 10).value2
$city = $worksheet.cells.item($intRow, 2).value2
$state = $worksheet.cells.item($intRow, 3).value2
$retailprice = $worksheet.cells.item($intRow, 18).value2
$yourprice = $worksheet.cells.item($intRow, 20).value2
$SavingsTotal = $worksheet.cells.item($intRow, 21).value2
if (($site -ge 1 )) {
"{0, -15} {1, -30} {2, -25} {3, -25}" -f $site, $city, $state, $retailprice, $yourprice, $Savingstotal |
Out-File $AssetInv -Append
}
}
$objexcel.quit()
Currently I am pulling no data, earlier I had it working for a maximum of 3 records. Any insight as to what I am doing wrong?
Ok, we'll start with your code to get the worksheet we want. Then we'll select the UsedRange to get the range of cells that have data. Then we'll run that through a ForEach loop, skipping the first 5 rows since they have garbage/header info. For each row we'll make a new object, with properties set to the associated cell (kind of like what you do with all your variables). All of those objects will be collected in an array, which we output to a CSV file. If you don't like the CSV, you could pipe it to Format-Table, and pipe that to Out-File (might have to pipe to out-string, then out-file... it's not something I do often).
$strPath = "C:\Users\User\Documents\EXEL\fj.xls"
$AssetInv = "C:\Users\User\Documents\EXEL\fj.txt"
$objExcel = New-Object -ComObject Excel.Application
$objExcel.Visible = $false
$WorkBook = $objExcel.Workbooks.Open($strPath)
$worksheet = $workbook.sheets.item("Daily Price Quote - Better Of -")
$UsedRange = $worksheet.usedrange
$Data = ForEach($Row in ($UsedRange.Rows|Select -skip 5)){
New-Object PSObject -Property #{
'Site' = $Row.Cells.Item(1).Value2
'City' = $Row.Cells.Item(2).Value2
'State' = $Row.Cells.Item(3).Value2
'Retail Price' = $Row.Cells.Item(18).Value2
'Your Price' = $Row.Cells.Item(20).Value2
'Total Savings' = $Row.Cells.Item(21).Value2
}
}
$Data | Where{[int]::Parse($_.Site) -ge 1} | Select Site,City,State,'Retail Price','Your Price','Total Savings' | Export-Csv -NoTypeInformation -Path $AssetInv
$objExcel.quit()
The plus side is that you also are left with $Data which is all the data you want to be able to use in case you need to do anything else with it (look for items that you save over X%, or items that cost less than $5 or whatever.

Powershell, Excel Finding string in cell, Color row, delete others

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}
}

Resources