Reading Excel Spreadsheet and adding information to it - excel

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.

Related

Excel File Reader Script in Powershell Not Working?

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.

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

CSV Not Exporting after merge

I asked a similar question here and it got me on a very good track, however while trying to 'all in one' it, I ended up with an issue. I'm very close, but after testing numerous theories I'm starting to reverse what I need.
I have a multiple sheet Excel - I need to pull the first sheet, CSV-ify it, then remove the headers, then only get the first 3 digits from the first line.("List Name")
My PS is:
#Create and get my Excel Obj
$excel = New-Object -comobject Excel.Application
$excel.visible=$false
$excel.DisplayAlerts=$false
$UserWorkBook = $excel.Workbooks.Open("C:\path\to\file\CompleteTest.xls")
$UserWorksheet = $UserWorkBook.Worksheets.Item(1)
$hitlistCSV = "C:\path\to\file\BroadcastReport.csv"
$xlCSV = 6 #changed-grav
#Save, close, and clean up
$UserWorksheet.SaveAs($hitlistCSV,$xlCSV)
$UserWorkBook.close()
$excel.quit()
$excel = $null
$expandfile = "C:\path\to\file\BroadcastReport.csv"
(gc $expandfile | select -Skip 1) | sc $expandfile
Import-Csv $expandfile | ForEach-Object {
$_."List Name" = $_."List Name".SubString(0,3)
$_
}
$expandfile | Export-Csv "C:\path\to\file\BroadcastReport.csv" -NoTypeInformation
I've modified it a lot, and tried to make it single runnable to avoid schedule multiple BATs. Everything seems to work - It pulls the first sheet, renames it, and replaces the top line EVERY time I run the 2nd portion. However, the SubString portion isn't taking effect, nor am I getting a 'FileExport' file.
My last modification now makes it so everytime it replies that I need to 'supply an Input Object' so I feel like it has to do with function-izing it. For reference the line I need to split is double quoted because it has commas. Ex:
"123|ST,City"
I only need the 123. When I just import the file and run my ForEach function, the output is what I expect. Just saving it to the file isn't.
Thanks.
Welcome any input.
XLS Source
This is the header line I need to skip
List Name Broadcast Name ColumC ColumD Colum E ColumF Colum G ColumJ
401|ST, City ST, City - More Text(LSM) (16803) 1 854 73 854 233 27.28%
402|ST, City ST, City - October (LSM) (16807) 1 851 57 851 186 21.86%
CSV Source after XLS -> CSV
One thing that is weird is that once it becomes a CSV, I'm left with ~6 fields of just commas. Example:
List Name,Broadcast Name,ColumC,ColumD,Colum E ,Colum,Colum F,ColumG,,,,,,
"402|ST,City","ST, City - More Text(ACR) (16803)",1,854,73,854,233,27.28%,,,,,,
"402|ST,City","City, ST - Text (LSM) (16807)",1,851,57,851,186,21.86%,,,,,,
UPDATE:
Added example, updated source - Column names keep a space where a space exists.
Broadcast Name column -does- have a comma in it, but I assume it's being exported with double quotes.
RESOLUTION
#Create and get my Excel Obj
$excel = New-Object -comobject Excel.Application
$excel.visible=$false
$excel.DisplayAlerts=$false
$UserWorkBook = $excel.Workbooks.Open("C:\path\to\file\ExcelBook.xls")
$UserWorksheet = $UserWorkBook.Worksheets.Item(1)
$hitlistCSV = "C:\path\to\file\Output.csv"
$xlCSV = 6 #changed-grav
#Save, close, and clean up
$UserWorksheet.SaveAs($hitlistCSV,$xlCSV)
$UserWorkBook.close()
$excel.quit()
$excel = $null
$expandfile = "C:\path\to\file\Output.csv"
$report = get-content $ExpandFile | select -skip 1 | convertfrom-csv | ForEach-Object {
$_."List Name" = $_."List Name".SubString(0,3)
$_
}
$report | Export-Csv ""C:\path\to\file\Output.csv" -force -NoTypeInformation
Issue was a slight modification to the export function being separated functions. I assume due to some sort of write lock.
The problem is on your last line, you are setting $expandfile to the return result of export-csv, it is prompting you to provide an input because it is trying to perform the export but is only provided with a path. Just change the last line to
$expandfile | Export-Csv "C:\Users\Donavin\Desktop\TXRH\FileExport.csv" -NoTypeInformation
EDIT
Ok need to make a few more changes for things to work correctly, valid import/export code is below
$expandfile = "C:\path\to\file\BroadcastReport.csv"
(gc $expandfile | select -Skip 1) | sc $expandfile
Import-Csv $expandfile | ForEach-Object {
$_."List Name" = $_."List Name".SubString(0,3)
$_
} | Export-Csv "C:\path\to\file\BroadcastReport.csv" -force -NoTypeInformation

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

PowerShell - paste data into Excel

Today I have just thrown together this PowerShell script which
takes a tab-delimited text file,
reads it into memory,
makes a variable number of filter queries based on distinct values of a certain column
creates a new empty Excel workbook
adds each of the subsets of filtered data to
a new Excel worksheet
The last step is where I am stuck. Currently my code puts a few lines of data into a range in the worksheet, in the form of unrolled/transposed "key: value" entries, resulting in a horizontal data layout. The same range of data is always overwritten.
I want data in the form of a vertical layout, i.e., data in columns, just the same way as if the CSV file was imported with the import-file-wizard of MS Excel.
Is there a simpler way to do it than below?
I admit, some of the PowerShell features are pasted in here in a cargo-cult mode of programming. Please note that I have no PowerShell experience whatsoever. I did some batchfile, VBScript, and VBA coding a few years back. So, other criticisms are also welcome.
PARAM (
[Parameter(ValueFromPipeline = $true)]
$infile = ".\04-2011\110404-13.txt"
)
PROCESS {
echo " $infile"
Write-Host "Num Args:" $args.Length;
$xl = New-Object -comobject Excel.Application;
$xl.Visible = $true;
$Workbook = $xl.Workbooks.Add();
$content = Import-Csv -delimiter "`t" $infile;
$ports = $content | Select-Object Port# | Sort-Object Port# -Unique -Descending;
$ports | ForEach-Object {
$p = $_;
Write-Host $p.{Port#};
$Worksheet = $Workbook.Worksheets.Add();
$workSheet.Name = [string]::Format("{0} {1}", "PortNo", $p.{Port#});
$filtered = $content | Where-Object {$_.{Port#} -eq $p.{Port#} };
$filtered | ForEach-Object {
Write-Host $_.{ObsDateTime}, $_.{Port#}
}
$filtered | clip.exe;
$range = $Workbook.ActiveSheet.Range("a2", "a$($filtered.count)");
$Workbook.ActiveSheet.Paste($range, $false);
}
$xl.Quit()
}
Data Output Example
Wrong
Port# : 1
Obs# : 1
Exp_Flux : 0,99
IV Cdry : 406.96
IV Tcham : 16.19
IV Pressure : 100.7
IV H2O : 9.748
IV V3 : 11.395
IV V4 : 0.759
IV RH : 53.12
Right
Port# Obs# Exp_Flux IV Cdry IV Tcham IV Pressure IV H2O IV V3 IV V4 IV RH
1 1 0,99 406.96 16.19 100.7 9.748 11.395 0.759 53.12
Try Export-Xls, it looks very nice. Never had the chance to use it, but (virtually) knowing the person who worked on it, I'm sure you will be very happy to use it. If you'll go with it, please provide a feedback here will be appreciated.
POSSIBLE WORKAROUND FOR UNORDERED PROPERTIES IN Export-Xls
The function Add-Array2Clipboard could be changed so that it accepts a new input parameter: an array providing the name of the properties ordered as required.
Then the you can change the section where get-member is used. Silly example:
"z", "a", "c" | %{ get-member -name $_ -inputobject $thecurrentobject }
This is just an example on how you can achieve ordered properties from get-member.
I've used the $Workbook.ActiveSheet.Cells.Item($row, $col).Value2 function to more be able to pinpoint more precisely where to put the data when exporting to Excel.
Something like
$row = 1
Get-Content $file | Foreach-Object {
$cols = $_.split("`t")
for ($i = 0; $i < $cols.count; $i++)
{
$Workbook.ActiveSheet.Cells.Item($row, $i+1).Value2 = $cols[$i]
}
$row++
}
Warning: dry-coded! You'll probably need some try..catch as well.
I used a modified Export-Xls function, a bit different as User empo suggested.
This is my call to it
Export-Xls $filtered -Path $outfile -WorksheetName "$wn" -SheetPosition "end" | Out-Null # -SheetPosition "end";
However, the current release of Export-Xls re-orders the columns of the in-memory representation of the csv-text -file. I want the data columns of the text file in their original order, so I had to hack and simplify the original code as follows:
function Add-Array2Clipboard {
param (
[PSObject[]]$ConvertObject,
[switch]$Header
)
process{
$array = #();
$line =""
if ($Header) {
$line = #()
$row = $ConvertObject | Select -First 1
$row.psobject.properties | Foreach {$line += "$($_.Name)" }
$array += [String]::Join("`t", $line)
}
else {
foreach($row in $ConvertObject){
$line =""
$vals = #()
$row.psobject.properties | Foreach {$vals += $_.Value}
$array += [String]::Join("`t", $vals)
}
}
$array | clip.exe;
}
}

Resources