Null argument on variable - html

I'm trying to use PowerShell to run a MySQL script and convert the output to an HTML file or e-mail the output.
However I'm having a trouble with the variable which stores the output.
The code works fine, as I'm able to output the results, it just fails at moving to HTML or e-mail.
#The dataset must be created before it can be used in the script:
$dataSet = New-Object System.Data.DataSet
#MYSQL query
$command = $myconnection.CreateCommand()
$command.CommandText = "
SELECT ID, Date_Close, Time_Close FROM systemcontrol.database_close
WHERE Date_Close >= CONCAT(YEAR(NOW()), '-', MONTH(NOW()), '-01')
AND Database_Close_ID = 1
ORDER BY Date_Close DESC
";
Write-Host "4B - Sales Reports Month End Database"
$reader = $command.ExecuteReader()
#The data reader will now contain the results from the database query.
#Processing the Contents of a Data Reader
#The contents of a data reader is processes row by row:
while ($reader.Read()) {
#And then field by field:
for ($i= 0; $i -lt $reader.FieldCount; $i++) {
Write-Output $reader.GetValue($i).ToString()
}
}
ConvertTo-Html -Body "$reader" -Title "4B - Sales Reports Month End Database" | Out-File C:\************.HTML
Send-MailMessage -From " Daily Check <server#company.com>" -To "Admin <admin#admin>" -Subject "Daily Check: Server Times" -Body "$reader" -Priority High -Dno onSuccess, onFailure -SmtpServer 1.xx.xx.xx
$myconnection.Close()
This is the error I'm getting:
Cannot validate argument on parameter 'Body'. The argument is null or empty. Provide an argument that is not null or empty, and then try the command again.
It doesn't seem to recognise the $reader variable. Where am I going wrong here?

You are passing a SqlDataReader object to the -body parameter which is expecting a string[]. Just collect the values in your while loop in a $result array and pass that to the body:
#The dataset must be created before it can be used in the script:
$dataSet = New-Object System.Data.DataSet
#MYSQL query
$command = $myconnection.CreateCommand()
$command.CommandText = "
SELECT ID, Date_Close, Time_Close FROM systemcontrol.database_close
WHERE Date_Close >= CONCAT(YEAR(NOW()), '-', MONTH(NOW()), '-01')
AND Database_Close_ID = 1
ORDER BY Date_Close DESC
";
Write-Host "4B - Sales Reports Month End Database"
$reader = $command.ExecuteReader()
#The data reader will now contain the results from the database query.
$result = #()
#Processing the Contents of a Data Reader
#The contents of a data reader is processes row by row:
while ($reader.Read()) {
#And then field by field:
for ($i= 0; $i -lt $reader.FieldCount; $i++) {
$value = $reader.GetValue($i).ToString()
Write-Output $value
$result += $value
}
}
ConvertTo-Html -Body $result -Title "4B - Sales Reports Month End Database" | Out-File C:\************.HTML
Send-MailMessage -From " Daily Check <server#company.com>" -To "Admin <admin#admin>" -Subject "Daily Check: Server Times" -Body "$reader" -Priority High -Dno onSuccess, onFailure -SmtpServer 1.xx.xx.xx
$myconnection.Close()

Related

URL Date Pull using Powershell

So I'm trying to generate a streaming dataset in Power BI, so that I can have a tab in teams that constantly updates with data from my companies database. The way I pull the data is through our platform API that generates a JSON and a URL based on the query. As far as I can find, the only way to really do this is through Powershell. I want to pull out the hours and numbers fields from the JSON and then push that data to the dashboard in Power Bi, but I have absolutely no experience with any of this, so I'm completely baffled as to where to start.
Here's the powershell code I have so far, but it throws an error in regard to pulling the URL info.
$request = 'http://wya.works/rta_develop/xmlServlet?&command=retrieve&sql=select%20%5B%24Hours%5D%2C%20%5B%24Date%20Worked%5D%20from%20%5B%21HOURS%5D%20&attributesOnly=Date%20Worked%2C%20Hours&contentType=JSON&referer=&time=1595271424377&key=696a666d'
Invoke-WebRequest $request
$json.RECORD | % {
$hours = 0
$date = ""
$_.Field | % {
if ($_ -match '\d{1,2}/\d{1,2}\/d{4}'){
$date = "$_"
}
else {
$hours = $_
}
}
New-Object -TypeName psobject -Property (#{
Date = $date
Hours = $hours
})
}
$endpoint = "https://api.powerbi.com/beta/d6cdaa23-930e-49c1-9d2a-0fbe648551b2/datasets/34eaea1e-73b6-4759-ac8b-aaae51708654/rows?noSignUpCheck=1&key=Ur9E0GDrhkp4EwJOF4bCbg7EO7aIve54urjB8M%2BHevG1%2F6pDgRJ47Fvkmx4b%2FcMowlhV18ZYyVtF9pfG%2BM1EQA%3D%3D"
$payload = #{
"Hours" =$hours
"Date Worked" =$date
}
Invoke-RestMethod -Method Post -Uri "$endpoint" -Body (ConvertTo-Json #($paylojnjlkhad))
This is a snippet from the JSON file and all of the fields I need (numeric: Hours) (date: Date Worked) are labeled the same, which makes this a lot more difficult.
{"COUNT":"332","DISPLAY_LIST_START":"1","DISPLAY_LIST_STOP":"332","STOP":"332","RECORD":[{"SESSION_ID":"HxNI-Zuc1B2EFAzTS8hx6w7Ek_dbrNCMhYVNI3Ta","FIELD":["6",["04/23/2018"]]},{"SESSION_ID":"HxNI-Zuc1B2EFAzTS8hx6w7Ek_dbrNCMhYVNI3Ta","FIELD":["6",["04/24/2018"]]},{"SESSION_ID":"HxNI-Zuc1B2EFAzTS8hx6w7Ek_dbrNCMhYVNI3Ta","FIELD":["6",["04/26/2018"]]},{"SESSION_ID":"HxNI-Zuc1B2EFAzTS8hx6w7Ek_dbrNCMhYVNI3Ta","FIELD":["6",["04/30/2018"]]},{"SESSION_ID":"HxNI-Zuc1B2EFAzTS8hx6w7Ek_dbrNCMhYVNI3Ta","FIELD":["6",["05/01/2018"]]},{"SESSION_ID":"HxNI-Zuc1B2EFAzTS8hx6w7Ek_dbrNCMhYVNI3Ta","FIELD":["4",["05/02/2018"]]}
I need to use the URL instead of the actual file, because my company's platform runs on blockchain and is constantly updated.
If you want to make an API call per pair of $date and $hours, you can do the following. This will make one call per RECORD value.
$endpoint = "https://api.powerbi.com/beta/d6cdaa23-930e-49c1-9d2a-0fbe648551b2/datasets/34eaea1e-73b6-4759-ac8b-aaae51708654/rows?noSignUpCheck=1&key=Ur9E0GDrhkp4EwJOF4bCbg7EO7aIve54urjB8M%2BHevG1%2F6pDgRJ47Fvkmx4b%2FcMowlhV18ZYyVtF9pfG%2BM1EQA%3D%3D"
$json.RECORD | Foreach-Object {
$hours = 0
$date = ""
$_.Field | Foreach-Object {
if ($_ -match '\d{1,2}/\d{1,2}/\d{4}'){
$date = $_
}
else {
$hours = $_
}
}
$payload = #{
"Hours" = $hours
"Date Worked" = $date
}
Invoke-RestMethod -Method Post -Uri $endpoint -Body (ConvertTo-Json $payload)
}
If you want to make one API call after creating an array of $date and $hours pairs, you can do the following. This will only make one API call.
$endpoint = "https://api.powerbi.com/beta/d6cdaa23-930e-49c1-9d2a-0fbe648551b2/datasets/34eaea1e-73b6-4759-ac8b-aaae51708654/rows?noSignUpCheck=1&key=Ur9E0GDrhkp4EwJOF4bCbg7EO7aIve54urjB8M%2BHevG1%2F6pDgRJ47Fvkmx4b%2FcMowlhV18ZYyVtF9pfG%2BM1EQA%3D%3D"
$payloadArray = $json.RECORD | Foreach-Object {
$hours = 0
$date = ""
$_.Field | Foreach-Object {
if ($_ -match '\d{1,2}/\d{1,2}/\d{4}'){
$date = $_
}
else {
$hours = $_
}
}
$payload = #{
"Hours" = $hours
"Date Worked" = $date
}
$payload
}
Invoke-RestMethod -Method Post -Uri $endpoint -Body (ConvertTo-Json $payloadArray)
Exlanation:
If you want to have a payload per field pair, you will need to make an API call after $date and $hours are both set. Then repeat that process for each pair. If your payload supports an array structure, then you can create a collection of those pairs.
You are only doing the API call after all fields have been processed without creating a collection, which means you only get the last pair of $date and $hours values. As you iterate through your RECORD items, you are overwriting $date and $hours before the API call is ever made.

importing HTML and outputting with variables

I'm looking for a method to import a template HTML file in PowerShell and being able to populate it with variables, which in turn sends an e-mail in HTML format containing user data.
I already know how to send the HTML e-mail. And, currently have HTML in a 'here' string embedded in the code. I want to take it a step further, by being able to grab an HTML template based on country code. So, if the user is in the US, it'll get a English HTML data filled e-mail, if they're dutch, they'll get it in Dutch, etc.
function SendMessage {
Param(
[Parameter(Position=0,Mandatory=$true)]
[string]$Identity,
[Parameter(Position=1,Mandatory=$true)]
[string]$Body
)
$Subject = "Important information - Do not delete this email. Welcome to Voicemail"
$SmtpClient = New-object system.net.mail.smtpClient
$MailMessage = New-Object system.net.mail.mailmessage
$CredentialFile = ".\UMcloud-creds.txt"
$password = Get-Content $CredentialFile| ConvertTo-SecureString -Force
$UMCloudAdmin = ""
$SmtpClient.Credentials = New-Object System.Net.NetworkCredential($UMCloudAdmin, $Password)
$smtpclient.Host = "smtp-in.net"
$MailMessage.From = "Voicemail <P#domain.net>"
$MailMessage.To.clear()
$MailMessage.To.Add($Identity)
$MailMessage.Subject = $Subject
$Mailmessage.Body = $body
$MailMessage.IsBodyHtml = 1
$Logofilepath = ((Resolve-Path .\).Path) + "\logo.jpg"
$attachment = New-Object System.Net.Mail.Attachment -ArgumentList $LogoFilePath
$attachment.ContentDisposition.Inline = $True
$attachment.ContentDisposition.DispositionType = "Inline"
$attachment.ContentType.MediaType = "image/jpg"
$attachment.ContentId = "logo.jpg"
$MailMessage.Attachments.Add($attachment)
do {
$Continue = $false
try {
$smtpclient.Send($MailMessage)
Write-LogFile $OutputLogFile ("[SUCCESS] {0} {1}" -f $identity, $UMExtension)
Write-LogFile $customemaillog ("[SUCCESS] {0} {1}" -f $identity, $UMExtension) | out-null
$Continue = $true
} catch {
sleep -s 10
Write-LogFile $OutputLogFile "[ERROR] $Identity $_.Exception.Message"
Write-LogFile $CustomEmailLog "[ERROR] $Identity $UMExtension"
Write-Error $_.Exception.Message
}
} until($Continue -eq $true)
} # End send message
$WelcomeText = Get-Content -Path ".\$CountryID.txt"
$Body = #"
<html>
...
</html>
"#
The here string is part of the script, I'd like to be able to import it from TXT file as not to clutter the script.
Instead of using a text file, create a powershell file (ps1) for each html language format you want. Within those files, you can set a single variable as the html text (file EN_US.ps1):
$bodyENUS = #"Dear <b><font color=red>user</b></font> <br>
This is a test <b>HTML</b> email for your language preference<br>
Sincerely,<br> PdeRover<br>"
You can then pass the variable into the main ps file using two ways: Dot Sourcing or using a Global Variable.
Dot Sourcing: calling the variable by providing the file name.
In the main file:
..\EN_US.ps1
..\PT_PT.ps1
$smtp = "Exchange-Server"
$to = $Identity
$from = "Voicemail <P#domain.net>"
$subject = "This is a Test of HTML Email"
if (user is English speaking){
$bodyByLang = $bodyENUS}
elseif (user is Portuguese speaking) {
$bodyByLang = $bodyPTPT}
send-MailMessage -SmtpServer $smtp -To $to -From $from -Subject $subject -Body $bodyByLang -BodyAsHtml -Priority high
Global Variable: prefixing a variable with $Global:and calling the file during runtime. $Global: bodyENUS Then calling it using $bodyENUS
I asked my own SO question about the difference/best practice of using them. May be worth a read.
This should be enough to point you in the right direction.

RunSpacePool output CSV contains blank rows

I am using this amazing answer and got RunSpacePools to output a CSV file but my CSV file has blank rows and I just cannot figure out where the blank rows are coming from.
The blank lines are shown in Notepad as ,,,
IF(Get-Command Get-SCOMAlert -ErrorAction SilentlyContinue){}ELSE{Import-Module OperationsManager}
"Get Pend reboot servers from prod"
New-SCOMManagementGroupConnection -ComputerName ProdServer1
$AlertData = get-SCOMAlert -Criteria "Severity = 1 AND ResolutionState < 254 AND Name = 'Pending Reboot'" | Select NetbiosComputerName
"Get Pend reboot servers from test"
#For test information
New-SCOMManagementGroupConnection -ComputerName TestServer1
$AlertData += Get-SCOMAlert -Criteria "Severity = 1 AND ResolutionState < 254 AND Name = 'Pending Reboot'" | Select NetbiosComputerName
"Remove duplicates"
$AlertDataNoDupe = $AlertData | Sort NetbiosComputerName -Unique
$scriptblock = {
Param([string]$server)
$csv = Import-Csv D:\Scripts\MaintenanceWindow2.csv
$window = $csv | where {$_.Computername -eq "$server"} | % CollectionName
$SCCMWindow = IF ($window){$window}ELSE{"NoDeadline"}
$PingCheck = Test-Connection -Count 1 $server -Quiet -ErrorAction SilentlyContinue
IF($PingCheck){$PingResults = "Alive"}
ELSE{$PingResults = "Dead"}
Try{$operatingSystem = Get-WmiObject Win32_OperatingSystem -ComputerName $server -ErrorAction Stop
$LastReboot = [Management.ManagementDateTimeConverter]::ToDateTime($operatingSystem.LastBootUpTime)
$LastReboot.DateTime}
Catch{$LastReboot = "Access Denied!"}
#create custom object as output for CSV.
[PSCustomObject]#{
Server=$server
MaintenanceWindow=$SCCMWindow
Ping=$PingResults
LastReboot=$LastReboot
}#end custom object
}#script block end
$RunspacePool = [RunspaceFactory]::CreateRunspacePool(100,100)
$RunspacePool.Open()
$Jobs =
foreach ( $item in $AlertDataNoDupe )
{
$Job = [powershell]::Create().
AddScript($ScriptBlock).
AddArgument($item.NetbiosComputerName)
$Job.RunspacePool = $RunspacePool
[PSCustomObject]#{
Pipe = $Job
Result = $Job.BeginInvoke()
}
}
Write-Host 'Working..' -NoNewline
Do {
Write-Host '.' -NoNewline
Start-Sleep -Milliseconds 500
} While ( $Jobs.Result.IsCompleted -contains $false)
Write-Host ' Done! Writing output file.'
Write-host "Output file is d:\scripts\runspacetest4.csv"
$(ForEach ($Job in $Jobs)
{ $Job.Pipe.EndInvoke($Job.Result) }) |
Export-Csv d:\scripts\runspacetest4.csv -NoTypeInformation
$RunspacePool.Close()
$RunspacePool.Dispose()
After trial and error, I ended up working with this method of run space pools to get close. Looking closer, I found the output was polluted by WMI's extra whitespaces.
To solve this, I ended up using the following within the ScriptBlock's Try statement.
$LastReboot = [Management.ManagementDateTimeConverter]::ToDateTime `
($operatingSystem.LastBootUpTime).ToString().Trim()
Now the data returned is all single line as desired.
-Edit to comment on WMI's extra whitespaces in output. See this question for more details.
Consider the following method to return a computer's last reboot timestamp. Note you can format the string as needed, see this library page for more info.
$os = (gwmi -Class win32_operatingsystem).LastBootUpTime
[Management.ManagementDateTimeConverter]::ToDateTime($os)
Observe the whitespaces, which can be removed by converting the output to a string then using Trim() to remove the whitespaces.

Getting no query results in email

This is my powershell script below. I am trying to export the query's results into email body. However, the email contains nothing except the table headers. Could anyone help out what could be wrong / incomplete?
# Create a DataTable
$table = New-Object system.Data.DataTable "bugs"
$col1 = New-Object system.Data.DataColumn bug_id,([string])
$col2 = New-Object system.Data.DataColumn bug_status,([string])
$col3 = New-Object system.Data.DataColumn resolution,([string])
$col4 = New-Object system.Data.DataColumn short_desc,([string])
$col5 = New-Object system.Data.DataColumn deadline,([string])
$table.columns.add($col1)
$table.columns.add($col2)
$table.columns.add($col3)
$table.columns.add($col4)
$table.columns.add($col5)
# This code defines the search string in the database table
$SQLQuery = "SELECT bug_id,
bug_status,
resolution,
short_desc,
deadline
FROM bugs
WHERE ( bug_status IN ( 'RESOLVED', 'VERIFIED', 'INTEST' )
AND deadline BETWEEN CURDATE() AND DATE_ADD(CURDATE(), INTERVAL 30 DAY)
)
OR ( bug_status IN ( 'RESOLVED', 'VERIFIED', 'INTEST' )
AND deadline BETWEEN DATE_SUB(CURDATE(), INTERVAL 30 DAY) AND
CURDATE() )
ORDER BY deadline ASC
"
# This code connects to the SQL server and retrieves the data
$MySQLAdminUserName = 'user_name'
$MySQLAdminPassword = 'password'
$MySQLDatabase = 'mantis'
$MySQLHost = '<HOSTNAME>'
$ConnectionString = "server=" + $MySQLHost + ";port=3306;uid=" + $MySQLAdminUserName + ";pwd=" + $MySQLAdminPassword + ";database="+$MySQLDatabase
[void][system.reflection.Assembly]::LoadFrom("C:\Program Files (x86)\Devolutions\Remote Desktop Manager Free\MySQL.Data.dll")
$Connection = New-Object MySql.Data.MySqlClient.MySqlConnection
$Connection.ConnectionString = $ConnectionString
$Connection.Open()
$Command = New-Object MySql.Data.MySqlClient.MySqlCommand($SQLQuery, $Connection)
$DataAdapter = New-Object MySql.Data.MySqlClient.MySqlDataAdapter($Command)
$DataSet = New-Object System.Data.DataSet
$RecordCount = $dataAdapter.Fill($dataSet, "data")
$DataSet.Tables[0]
# Create an HTML version of the DataTable
$html = "<table><tr><td>bug_id</td><td>bug_status</td><td>resolution</td><td>short_desc</td><td>deadline</td></tr>"
foreach ($row in $table.Rows)
{
$html += "<tr><td>" + $row[0] + "</td><td>" + $row[1] + "</td></tr>" + "</td></tr>" + $row[2] + "</td></tr>"
}
$html += "</table>"
# Send the email
$smtpserver = "<SMTPSERVER>"
$from = "test#test.com"
$to = "test2#test.com"
$subject = "Hello"
$body = "Hi there,<br />Here is a table:<br /><br />" + $html
Send-MailMessage -smtpserver $smtpserver -from $from -to $to -subject $subject -body $body -bodyashtml
You may have other problems, but the date arithmetic is definitely one issue.
MySQL has very strange rules about date arithmetic. The value of CURDATE() is not a date. It is either a string or a number. In a numeric context (CURDATE() + 9 is a numeric context), then it returns a number.
So, the date 2015-01-25 is returned as the integer 20150125. You can see this phenomenon on SQL Fiddle here. This value plus nine is 20150134. Not a valid date and not what you expect.
The easiest fix is to use date_add():
SELECT bug_id, bug_status, resolution, short_desc, deadline
FROM bugs
WHERE bug_status IN ('RESOLVED') AND
deadline BETWEEN CURDATE() AND date_add(CURDATE(), interval 9 day)
I was able to put this together from multiple sources that seems to work with any mySQL query you plug in accordingly. I figured I'd share in case anyone else finds it helpful. Seems to be reliable from what I tested at least. You don't need to build the HTML table in with the SQL logic this way.
## -- This will download the needed PS module and load it accordingly and then prompt you to save the mySQL credential to make the connection to the data source
## -- Run PowerShell as administrator
## https://mcpmag.com/articles/2016/03/02/querying-mysql-databases.aspx
Invoke-WebRequest -Uri https://github.com/adbertram/MySQL/archive/master.zip -OutFile 'C:\Users\user\desktop\MySQL.zip'
$modulesFolder = 'C:\Program Files\WindowsPowerShell\Modules'
Expand-Archive -Path C:\Users\user\desktop\MySQL.zip -DestinationPath $modulesFolder
Rename-Item -Path "$modulesFolder\MySql-master" -NewName MySQL
$dbCred = Get-Credential
Connect-MySqlServer -Credential $dbcred -ComputerName 'localhost' -Database sakila
## Enter the mySQL username and password when prompted
Invoke-MySqlQuery -Query 'SELECT * FROM actor'
## --This will run automated after the PS module used to make the mySQL is already loaded and not prompt for credential to allow non-interactive runs
## --Embedded credential used here
$secpasswd = ConvertTo-SecureString “password” -AsPlainText -Force
$dbCred = New-Object System.Management.Automation.PSCredential (“root”, $secpasswd)
Connect-MySqlServer -Credential $dbcred -ComputerName 'localhost' -Database sakila
Invoke-MySqlQuery -Query 'SELECT * FROM actor LIMIT 5'
## This runs the Function to do the alternating row colors in the HTML table
Function Set-AlternatingRows {
[CmdletBinding()]
Param(
[Parameter(Mandatory,ValueFromPipeline)]
[string]$Line,
[Parameter(Mandatory)]
[string]$CSSEvenClass,
[Parameter(Mandatory)]
[string]$CSSOddClass
)
Begin {
$ClassName = $CSSEvenClass
}
Process {
If ($Line.Contains("<tr><td>"))
{ $Line = $Line.Replace("<tr>","<tr class=""$ClassName"">")
If ($ClassName -eq $CSSEvenClass)
{ $ClassName = $CSSOddClass
}
Else
{ $ClassName = $CSSEvenClass
}
}
Return $Line
}
}
## -- This portion builds the HTML table based on the SQL query which you can change accordingly for your needs
## https://community.spiceworks.com/scripts/show/1745-set-alternatingrows-function-modify-your-html-table-to-have-alternating-row-colors
## https://philerb.com/2011/11/sending-mail-with-powershell/
## https://thesurlyadmin.com/2013/01/21/how-to-create-html-reports/
$Title = "My Report Title"
$Header = #"
<style>
TABLE {border-width: 1px;border-style: solid;border-color: black;border-collapse: collapse;}
TH {border-width: 1px;padding: 3px;border-style: solid;border-color: black;background-color: #6495ED;}
TD {border-width: 1px;padding: 3px;border-style: solid;border-color: black;}
.odd { background-color:#ffffff; }
.even { background-color:#dddddd; }
</style>
<title>
$Title
</title>
"#
$Pre = "<b>Table Title"
$Post = "<b>Table Footer"
$html = Invoke-MySqlQuery -Query 'SELECT * FROM actor LIMIT 5' |
Select * -ExcludeProperty RowError, RowState, HasErrors, Name, Table, ItemArray |
ConvertTo-HTML -Head $Header -PreContent $Pre -PostContent $Post |
Set-AlternatingRows -CSSEvenClass even -CSSOddClass odd
$emailSmtpServer = "smtp.gmail.com"
$emailSmtpServerPort = "587"
$emailSmtpUser = "Username"
$emailSmtpPass = "Password"
$emailMessage = New-Object System.Net.Mail.MailMessage
$emailMessage.From = "mailbox#gmail.com"
$emailMessage.To.Add( "mailbox#gmail.com" )
$emailMessage.Subject = "Test email from PowerShell"
$emailMessage.IsBodyHtml = $true
$emailMessage.Body = #"
This is the body of the message.<br /><br /> $html
"#
$SMTPClient = New-Object System.Net.Mail.SmtpClient( $emailSmtpServer , $emailSmtpServerPort )
$SMTPClient.EnableSsl = $true
$SMTPClient.Credentials = New-Object System.Net.NetworkCredential( $emailSmtpUser , $emailSmtpPass );
$SMTPClient.Send( $emailMessage )
Sources
Modify your HTML table to have alternating row colors
Sending mail with PowerShell
How to Create HTML Reports
Querying MySQL Databases with PowerShell

Resolve DNS, export to Excel and HTML, then send mail

I was almost done with my script and did some late night editing and written over my old version so I cannot go back.
The script was running fine, still needed some tweaks but now it ha come to a complete halt.
The idea is to GC a list of IP's. Resolve the IP's and place them in an excel sheet. Then save the sheet to htm and xlsx. And finally mailing those to me.
Now it gets stuck on sorting the sheet, saving AND mailing...
Can someone give me some insight on what I did wrong here?
it gets stuck on sorting the sheet, saving AND mailing.
It no longer sorts B3:B$Count:
Exception calling "Sort" with "1" argument(s): "The sort reference is not valid. Make sure that it's within the data you want to sort, and the first Sort By box isn't the same or blank."
At C:\Folder\Scripts\Get-IP.ps1:137 char:5
+ [void] $objRange.Sort($objRange2)
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : NotSpecified: (:) [], MethodInvocationException
+ FullyQualifiedErrorId : ComMethodTargetInvocation
It no longer saves the xlsx file, but does save the HTM file. It is clearly not overwriting something. I even restarted to make sure.
Exception calling "SaveAs" with "1" argument(s): "Microsoft Excel cannot access the file 'C://Folder/BlockedIP/HTML/2014-07-08/0BCEF810'. workbook."
At C:\Folder\Scripts\Get-IP.ps1:160 char:5
+ $b.SaveAs("$FileXML")
+ ~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : NotSpecified: (:) [], MethodInvocationException
+ FullyQualifiedErrorId : ComMethodTargetInvocation
And finally, it will no longer send me e-mails:
New-Object : Exception calling ".ctor" with "2" argument(s): "The specified string is not in the form required for an e-mail address."
At C:\Folder\Scripts\Get-IP.ps1:217 char:13
+ $SMTP = New-Object System.Net.Mail.MailMessage($SMTP, 587)
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : InvalidOperation: (:) [New-Object], MethodInvocationException
+ FullyQualifiedErrorId : ConstructorInvokedThrowException,Microsoft.PowerShell.Commands.NewObjectCommand
The script:
#Get current date
$Date = get-date -format yyyy-MM-dd
#Define all files/Paths.
$Path = "C:/Folder/BlockedIP"
md "$Path/HTML/$Date" -Force
$path2 = "$Path/HTML/$Date"
$PathWeb = "/HTML/$Date"
#Define File's used or created in this script.
$File = "$Path/IP-$Date.txt"
$FileHtml = "$Path2/IP-$Date.htm"
$FileXML = "$Path2/IP-$Date.xlsx"
$FileHTMLWeb = "$PathWeb/IP-$date.htm"
#Get content from given IP list.
$colComputers = #(get-content $File | Sort -unique)
$count = $colComputers.Count
write-output "$Count IP's detected."
#Define error actions.
#$erroractionpreference = "SilentlyContinue"
#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
#Create sheets in Excel.
$b = $a.Workbooks.Add()
$c = $b.Worksheets.Item(1)
#Create a Title for the first worksheet and adjust the font
$row = 1
$Column = 1
target="_parent">Creator'
$c.Cells.Item($row,$column)= "Blocked IP's $Date"
$c.Cells.Item($row,$column).Font.Size = 18
$c.Cells.Item($row,$column).Font.Bold=$True
$c.Cells.Item($row,$column).Font.Name = "Cambria"
$c.Cells.Item($row,$column).Font.ThemeFont = 1
$c.Cells.Item($row,$column).Font.ThemeColor = 4
$c.Cells.Item($row,$column).Font.ColorIndex = 55
$c.Cells.Item($row,$column).Font.Color = 8210719
$range = $c.Range("a1","e1")
$range.Merge() | Out-Null
$range.VerticalAlignment = -4160
#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 name"
#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 the usedrange for autofitting.
$d = $c.UsedRange
#Make everything fit in it's cell
$D.EntireColumn.AutoFit() | Out-Null
#Define html code for Excel save to .htm.
$xlExcelHTML = 44
#Define rows to alter in excel.
$iRow = 3
$intRow = 3
#Time to run the script.
foreach ($strComputer in $colComputers)
{
#Place IP's from text in the excel sheet
$c.Cells.Item($intRow, 1) = $strComputer.ToUpper()
$d.EntireColumn.AutoFit() | Out-Null
#Create a status bar for the script
$i = 1
Write-Progress -Activity `
"Creating a usable 'Blocked IP' list ($i/$count)" `
-PercentComplete ($i/$colComputers.Count*100) `
-Status "Please stand by"
try {
$dnsresult = [System.Net.DNS]::GetHostEntry($strComputer)
}
catch {
$dnsresult = "$null"
}
#Clear screen on every checked IP to remove the 'True' statement.
#cls
#Do something with $dnsresults.
#Display information about host
#Give hostname Entry in Cell2
$c.Cells.Item($intRow,2) = $dnsresult.HostName
#IP listed in Cell 3
$c.Cells.Item($intRow,3) = $dnsresult.AddressList[0].IpAddressToString
#Make everything fit in it's cell.
$d.EntireColumn.AutoFit() | Out-Null
#Define row for the IP list.
$intRow = $intRow + 1
#Set background color for the IP list.
$d.Range("A$($iRow):E$($intRow)").interior.colorindex = 15
#Sort all IP's on resolved name.
$objWorksheet = $b.Worksheets.Item(1)
$objRange = $objWorksheet.UsedRange
$objRange2 = $objworksheet.Range("B3:B($Count)")
[void] $objRange.Sort($objRange2)
#Define borders here.
<# Insert script :D #>
#Define Filters here. (Picking out blank DNS and giving those a name)
<# Insert script :D #>
#Define Filters here. (Picking out specific DNS name and give them color code)
<# Insert script :D #>
#Make everything fit in it's cell.
$d.EntireColumn.AutoFit() | Out-Null
#Clear screen on every checked IP to remove the 'True' statement.
#cls
}
#Save the file as .xlsx on every placed IP to ensure the file is not lost due to any reason.
$b.SaveAs("$FileXML")
#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
#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>","")
#Variables e-mail.
$From = "Blocked IP <###g##.com>"
$To = "IT Dept <#####.nl>"
$CC = "Someone <##r###.nl"
$SMTP = "smtp.gmail.com"
$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 = "This is an automated message generated by server: $env:COMPUTERNAME, $IP</br></br>
Please see the attachment or click $here to get the $Count blocked IP's of $date. </br> </br></br>"
#Variables e-mail user.
$username = "#####.com"
$password = "##"
$secstr = New-Object -TypeName System.Security.SecureString
$password.ToCharArray() | ForEach-Object {$secstr.AppendChar($_)}
$Cred = new-object -typename System.Management.Automation.PSCredential -argumentlist $username, $secstr
$ip = (Get-WmiObject -class win32_NetworkAdapterConfiguration -Filter 'ipenabled = "true"').ipaddress[0]
#Clear screen, again. (Let's keep things tidy.)
#cls
#Send output as e-mail.
$SMTP = New-Object System.Net.Mail.MailMessage($SMTP, 587)
$SMTP.EnableSsl = $true
$SMTP.Credentials = New-Object System.Net.NetworkCredential("$username", "$password");
$SMTP.isbodyhtml= $true
$SMTP.Send($From, $To, $Subject, $FileXML, $Body)
send-mailmessage -BodyAsHtml -from $From -to $To -cc $CC -subject $Subject -Attachments $FileXML -body $Body -priority High -smtpServer $SMTP -credential ($cred) -usessl
#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
Any help will be greatly appreciated!
Exception calling "Sort" with "1" argument(s): "The sort reference is not valid. Make sure that it's within the data you want to sort, and the first Sort By box isn't the same or blank."
Double-check that $objRange and $objRange2 reference the correct ranges:
$objRange.Address()
$objRange2.Address()
Not much else I can tell you here without seeing your actual data.
Exception calling "SaveAs" with "1" argument(s): "Microsoft Excel cannot access the file 'C://Folder/BlockedIP/HTML/2014-07-08/0BCEF810'. workbook."
If the path really were C://Folder/... you'd be getting a different exception. Please do not fabricate error messages.
New-Object : Exception calling ".ctor" with "2" argument(s): "The specified string is not in the form required for an e-mail address."
You're confusing MailMessage and SmtpClient class. Not to mention that you don't even need either of them, since you're using Send-MailMessage anyway. Just remove the following 5 lines:
$SMTP = New-Object System.Net.Mail.MailMessage($SMTP, 587)
$SMTP.EnableSsl = $true
$SMTP.Credentials = New-Object System.Net.NetworkCredential("$username", "$password");
$SMTP.isbodyhtml= $true
$SMTP.Send($From, $To, $Subject, $FileXML, $Body)
Solved the sorting problem by altering the code to:
$objRange = $c.Range("A$($iRow):E$($intRow)")
$objRange2 = $c.Range("B$($iRow):B$($intRow)")
[void] $objRange.Sort($objRange2)
Turns out it got stuck on the header in the xml file
Replaced mailing with:
$From = "Blocked IP <#####.##>"
$To = "IT Dept <#####.##>"
$CC = "Someone <#####.##"
$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 = "Dear <font color=black>$to</font>,<br><br>"
$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 = $username
$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)
The saving of the file turned out to be a misguided path.
Since the excel sheet was created from folder1 it wouldnt save instandly to folder 2 since its temp save file would remain in folder1.