SQL Server backup status Report with PowerShell - sql

I got a PowerShell script for reporting SQL backup status on multiple servers, it works fine but it had no function to send mail. I added that part and now I am able to get the mail with the attachment.
The only concern is, I want the report to show "NA" and not a default date where the satabase is in Simple Recovery Model or if backup has not happened. Can someone please advise?
Here is the code, just in case someone needs it unlike my requirement:
$ServerList = Get-Content "Serverlist location"
$OutputFile = "to save the report location"
$titleDate = Get-Date -UFormat "%m-%d-%Y - %A"
$HTML = '<style type="text/css">
#Header{font-family:"Trebuchet MS", Arial, Helvetica, sans-serif;width:100%;border-collapse:collapse;}
#Header td, #Header th {font-size:14px;border:1px solid #98bf21;padding:3px 7px 2px 7px;}
#Header th {font-size:14px;text-align:left;padding-top:5px;padding-bottom:4px;background-color:#A7C942;color:#fff;}
#Header tr.alt td {color:#000;background-color:#EAF2D3;}
</Style>'
$HTML += "<HTML><BODY><Table border=1 cellpadding=0 cellspacing=0 width=100% id=Header>
<TR>
<TH><B>Database Name</B></TH>
<TH><B>RecoveryModel</B></TD>
<TH><B>Last Full Backup Date</B></TH>
<TH><B>Last Differential Backup Date</B></TH>
<TH><B>Last Log Backup Date</B></TH>
</TR>"
[System.Reflection.Assembly]::LoadWithPartialName('Microsoft.SqlServer.SMO') | Out-Null
foreach ($ServerName in $ServerList)
{
$HTML += "<TR bgColor='#ccff66'><TD colspan=5 align=center><B>$ServerName</B></TD></TR>"
$SQLServer = New-Object ('Microsoft.SqlServer.Management.Smo.Server') $ServerName
foreach ($Database in $SQLServer.Databases)
{
$HTML += "<TR>
<TD>$($Database.Name)</TD>
<TD>$($Database.RecoveryModel)</TD>
<TD>$($Database.LastBackupDate)</TD>
<TD>$($Database.LastDifferentialBackupDate)</TD>
<TD>$($Database.LastLogBackupDate)</TD>
</TR>"
}
}
$HTML += "</Table></BODY></HTML>"
$HTML | Out-File $OutputFile
$emailFrom = "send email address"
$emailTo = "recipient email address"
$subject = "Xyz Report"
$body = "your words "
$smtpServer = "Smptp server"
$filePath = "location of the file you want to attach"
function sendEmail([string]$emailFrom, [string]$emailTo, [string]$subject,[string]$body,[string]$smtpServer,[string]$filePath)
{
$email = New-Object System.Net.Mail.MailMessage
$email.From = $emailFrom
$email.To.Add($emailTo)
$email.Subject = $subject
$email.Body = $body
$emailAttach = New-Object System.Net.Mail.Attachment $filePath
$email.Attachments.Add($emailAttach)
$smtp = New-Object Net.Mail.SmtpClient($smtpServer)
$smtp.Send($email)
}
sendEmail $emailFrom $emailTo $subject $body $smtpServer $filePath

Replace
<TD>$($Database.LastBackupDate)</TD>
with something like
<TD>$(if ($Database.RecoveryModel -eq 'Simple' -or $Database.LastBackupDate -eq '01/01/0001 00:00:00') {'NA'} else {$Database.LastBackupDate})</TD>
Do the same for LastDifferentialBackupDate and LastLogBackupDate.
With that said, I strongly recommend looking into calculated properties, ConvertTo-Html, and Send-MailMessage, which would allow you to greatly simplify your code:
[Reflection.Assembly]::LoadWithPartialName('Microsoft.SqlServer.SMO') | Out-Null
$emailFrom = 'sender#example.com'
$emailTo = 'recipient#example.com'
$subject = 'Xyz Report'
$smtpServer = 'mail.example.com'
$style = #'
<style type="text/css">
...
</style>
'#
$msg = Get-Content 'C:\path\to\serverlist.txt' |
ForEach-Object {New-Object 'Microsoft.SqlServer.Management.Smo.Server' $_} |
Select-Object -Expand Databases |
Select-Object Name, RecoveryModel,
#{n='LastBackupDate';e={if ($_.RecoveryModel -eq 'Simple' -or $_.LastBackupDate -eq '01/01/0001 00:00:00') {'NA'} else {$_.LastBackupDate}}},
#{n='LastDifferentialBackupDate';e={if ($_.RecoveryModel -eq 'Simple' -or $_.LastDifferentialBackupDate -eq '01/01/0001 00:00:00') {'NA'} else {$_.LastDifferentialBackupDate}}},
#{n='LastLogBackupDate';e={if ($_.RecoveryModel -eq 'Simple' -or $_.LastLogBackupDate -eq '01/01/0001 00:00:00') {'NA'} else {$_.LastLogBackupDate}}} |
ConvertTo-Html -Head $style | Out-String
Send-MailMessage -From $emailFrom -To $emailTo -Subject $subject -Body $msg -BodyAsHtml -SmtpServer $smtpServer
See also.

Related

Powershell mail every user from SQL query

Unfortunately, I could not find my answer from all the examples I came across.
I have a SQL query that contains the following values
First name
Suffix
Last Name
Email address
Now I want to send a mail per record and in the mail mention the name.
Below is the code that doesn't do the loop right now and puts the name in the mail.
<# Variabelen #>
$PlaceDate = "Amsterdam, " + (Get-Date -f dd-MM-yyyy)
$MailSubject = "My subject"
$sqlinstance = "MSSQLSRV1"
<# SQL data #>
$query = "SELECT [Firstname]
,[Suffix]
,[Lastname]
,[Emailaddress]
,[DateVisit]
FROM [DBname].[dbo].[tbl_EventVisitors]
where CONVERT(DATE, [DateVisit]) = CAST( GETDATE()-1 AS Date )"
$results = Invoke-Sqlcmd -Query $query -ServerInstance $sqlinstance
<# Create mail with SQL fields #>
$CoryReportHtml = ConvertTo-Html -PreContent #"
<body>
<br />
$PlaceDate<br /><br />
Dear $Firstname $Suffix $Lastname,<br />
MyMessage
</body>
"# | Out-String
<# Send the mail #>
$mailParams = #{
SmtpServer = 'localhost'
to = $Emailaddress
from = "from#example.com"
Subject = $MailSubject
Body = $CoryReportHtml
BodyAsHtml = $true
}
Send-MailMessage #mailParams
$results = Invoke-Sqlcmd -Query $query -ServerInstance $sqlinstance
foreach ($row in $results) {
$body_value = $null
$body_value = "Dear $($row.Firstname) $($row.$Lastname)"
$body_value += "my message"
$Emailaddress = $row.Name
$MailSubject = "Subject here"
$mailParams = #{
SmtpServer = 'localhost'
to = $Emailaddress
from = "from#example.com"
Subject = $MailSubject
Body = $body_valu
BodyAsHtml = $true
}
Send-MailMessage #mailParams
Write-Host "email sent to $row.name"
}

Multi-level json to SQL outputs per element instead of per row

I'm using the Microsoft 365 Defender API to receive all recent events/incidents.
I get a json file as following: link to example json
And use following script to try and convert this for easy import to an SQL server:
(Echoes only as test)
# Send the request and get the results.
$response = Invoke-WebRequest -UseBasicParsing -Method Get -Uri $url -Headers $headers -ErrorAction Stop
# Extract the incidents from the results.
$alerts = ($response | ConvertFrom-Json)
$devices = ($response | ConvertFrom-Json ).value.alerts.devices
$entities = ($response | ConvertFrom-Json ).value.alerts.entities
Foreach($row in $alerts){
$IncidentID = $alerts.value.incidentID
$Createdtime = $alerts.value.creationTime
$Status = $alerts.value.status
$Severity = $alerts.value.severity
$Classification = $alerts.value.classification
$IncidentName = $alerts.value.incidentName
$URL = $alerts.incidentUri
$Klant = $afkorting
$Username = $entities.accountname
$device = $devices.deviceDnsName
echo $IncidentID
echo $Createdtime
echo $Status
echo $Severity
echo $Classification
echo $IncidentName
echo $URL
echo $Klant
echo $Username
echo $device
Invoke-Sqlcmd -ServerInstance "SQL.domain.local\MSQL2016" -Database "private" -Username private -Password 'private' -Query "INSERT Into dbo.private ( [IncidentID], [Createdtime], [Status], [Severity], [Classification], [IncidentName], [URL], [Klant], [Username], [device]) VALUES ('$IncidentID', '$Createdtime', '$Status', '$Severity', '$Classification', '$IncidentName', '$URL', '$Klant', '$Username', '$device')"
}
However, the output in case of 3 incidents looks like:
IncidentID
IncidentID
IncidentID
Createdtime
Createdtime
Createdtime
Status
Status
Status
So grouped by element instead of grouped by IncidentID.
I can't find a way to get the output like:
IncidentID
Createdtime
Status
Severity
Classification
IncidentName
URL
Klant
Username
device
I "solved" this with an intermediary step exporting to CSV's and merging them and piping those to SQL for now, but that's too inefficient.
Move resolution of $devices and $entities into the loop, then use the iterator variable $row instead of referencing all $alerts inside the loop body:
# Send the request and get the results.
$response = Invoke-WebRequest -UseBasicParsing -Method Get -Uri $url -Headers $headers -ErrorAction Stop
# Extract the incidents from the results.
$alerts = ($response | ConvertFrom-Json)
foreach($row in $alerts){
$IncidentID = $row.value.incidentID
$Createdtime = $row.value.creationTime
$Status = $row.value.status
$Severity = $row.value.severity
$Classification = $row.value.classification
$IncidentName = $row.value.incidentName
$URL = $row.incidentUri
$Klant = $afkorting # where does `$afkorting` come from?
$Username = $row.value.entities.accountname
$device = $row.value.devices.deviceDnsName
# Insert into SQL Server here
}

Powershell, invoke-sqlcmd, export-csv fails to show data if there is more than one result

I have the below code to get data from a SQL DB and export it into a CSV file:
#Server and Database names
$SQLServer = "Servername"
$DB = "DatabaseName"
#SQL Command
$FullScriptSQL="Select * from MyTable WHERE Column = 'TestData'"
#Invoke the command, rename the column headers and export to CSV file
$FullScriptCallLinked = Invoke-Sqlcmd -ServerInstance $SQLServer -Database $DB -Query $FullScriptSQL | select-object #{ expression={$_.Column1};label='Column1 Name'},#{ expression={$_.Column2};label='Column2 Name'},#{ expression={$_.Column3}; label='Column3 Name' },#{ expression={$_.Column4} ; label='Column4 Name' }
Export-CSV -Path ".\ResultFile\FullScript.csv" -inputobject $FullScriptCallLinked -Append -NoTypeInformation
This works perfectly if there is one result. But if there is more than one result, it will show the below in the csv file
I am at my wits end as to why it is doing this. It's obviously the DB parameter data or something to that effect. Been googling for a few days with no luck. Anyone smarter than I able to assist please?
Instead of using Select-Object to rename your columns, which is quite inefficient, you could give the alias to your columns on the query itself:
$SQLServer = "Servername"
$DB = "DatabaseName"
$query = #'
SELECT Column1 AS "Column1 Name",
Column2 AS "Column2 Name",
Column3 AS "Column3 Name",
Column4 AS "Column4 Name"
FROM MyTable
WHERE ColumnX = 'TestData'
'#
Invoke-Sqlcmd -ServerInstance $SQLServer -Database $DB -Query $query |
Export-CSV -Path ".\ResultFile\FullScript.csv" -NoTypeInformation
Also, as in my comment, the code you have on your question is fine and should work, the only problem was using -InputObject instead of piping the results to Export-Csv:
$FullScriptCallLinked | Export-Csv -Path ".\ResultFile\FullScript.csv" -NoTypeInformation
Figured it out. I knew I was close!
#Server and Database names
$SQLServer = "Servername"
$DB = "DatabaseName"
#SQL Command
$FullScriptSQL="Select * from MyTable WHERE Column = 'TestData'"
#Invoke the command, rename the column headers and export to CSV file
$FullScriptCallLinked = Invoke-Sqlcmd -ServerInstance $SQLServer -Database $DB -Query $FullScriptSQL
foreach($i in $FullScriptCallLinked){
select-object #{ expression={$_.Column1};label='Column1 Name'},#{ expression={$_.Column2};label='Column2 Name'},#{ expression={$_.Column3}; label='Column3 Name' },#{ expression={$_.Column4} ; label='Column4 Name' }
Export-CSV -Path ".\ResultFile\FullScript.csv" -inputobject $i -Append -NoTypeInformation
}

Powershell script to get output through Sql query in table format attached in email?

The below script is resulting in the error below when attempt to send mail is made.
New-Object : A positional parameter cannot be found that accepts argument '='.
At line:22 char:18
+ ... onnection = New-Object System.Data.SqlClient.SqlConnection $SqlCon ...
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : InvalidArgument: (:) [New-Object], ParameterBindingException
+ FullyQualifiedErrorId : PositionalParameterNotFound,Microsoft.PowerShell.Commands.NewObjectCommand
Exception calling "Fill" with "1" argument(s): "Login failed for user ''."
At line:29 char:1
+ $SqlAdapter.Fill($DataSet)
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : NotSpecified: (:) [], MethodInvocationException
+ FullyQualifiedErrorId : SqlException
Send-MailMessage : Cannot convert 'System.Object[]' to the type 'System.String' required by parameter 'Body'. Specified method is not supported.
At line:44 char:17
+ -BodyAsHtml $html_table `
+ ~~~~~~~~~~~
+ CategoryInfo : InvalidArgument: (:) [Send-MailMessage], ParameterBindingException
+ FullyQualifiedErrorId : CannotConvertArgument,Microsoft.PowerShell.Commands.SendMailMessage
$Servers = (Import-Csv -Path "D:\Scripts\input.csv").ComputerName
$SQLDBName = "ReportServer"
$SQLQuery = #"
SELECT Distinct
RL.RoleName,
USR.UserName
FROM
Catalog C
INNER JOIN Policies PL
ON C.PolicyID = PL.PolicyID
INNER JOIN PolicyUserRole PUR
ON PUR.PolicyID = PL.PolicyID
INNER JOIN Users USR
ON PUR.UserID = USR.UserID
INNER JOIN dbo.Roles RL
ON RL.RoleID = PUR.RoleID
WHERE RoleName = 'Content Manager'
ORDER BY USR.UserName
"#
# This code connects to the SQL server and retrieves the data
$SQLConnection = New-Object System.Data.SqlClient.SqlConnection $SqlConnection.ConnectionString = "Server = $Servers; Database = $SQLDBName;"
$SqlCmd = New-Object System.Data.SqlClient.SqlCommand
$SqlCmd.CommandText = $SqlQuery
$SqlCmd.Connection = $SqlConnection
$SqlAdapter = New-Object System.Data.SqlClient.SqlDataAdapter
$SqlAdapter.SelectCommand = $SqlCmd
$DataSet = New-Object System.Data.DataSet
$SqlAdapter.Fill($DataSet)
$SqlConnection.Close()
# This code outputs the retrieved data
$html = $DataSet.Tables[0] | ConvertTo-Html -fragment
$results = $DataSet.Tables | format-table -autosize | out-string
$mail_body = $results
# Send the email
$html_table = $dt | sort-object "Status" | ConvertTo-Html -Fragment
Send-MailMessage `
-From "Reporting.Services#accenture.com" `
-To 'aditi.m.singh#accenture.com' `
-Subject 'Sending the Attachment' `
-BodyAsHtml $html_table `
-SmtpServer 'AMRINT.SMTP.ACCENTURE.COM'
This should work for you. One issue you had is that the variable $dt was never initialized in your script.
param(
$emailFrom = 'Reporting.Services#accenture.com',
$emailTo = 'aditi.m.singh#accenture.com',
$emailSubject = 'Sending the Attachment',
$smtp = 'AMRINT.SMTP.ACCENTURE.COM',
$Server = "$Env:ComputerName\MSSQLSERVER01",
$SQLDBName = 'Master',
$SQLQuery = #"
SELECT Distinct
RL.RoleName,
USR.UserName
FROM
Catalog C
INNER JOIN Policies PL
ON C.PolicyID = PL.PolicyID
INNER JOIN PolicyUserRole PUR
ON PUR.PolicyID = PL.PolicyID
INNER JOIN Users USR
ON PUR.UserID = USR.UserID
INNER JOIN dbo.Roles RL
ON RL.RoleID = PUR.RoleID
WHERE RoleName = 'Content Manager'
ORDER BY USR.UserName
"#
)
# This code connects to the SQL server and retrieves the data
$SQLConnection = New-Object System.Data.SqlClient.SqlConnection
$SqlConnection.ConnectionString = "Server = $Server; Database = $SQLDBName; Integrated Security=true;"
$SqlCmd = New-Object System.Data.SqlClient.SqlCommand
$SqlCmd.CommandText = $SqlQuery
$SqlCmd.Connection = $SqlConnection
$SqlAdapter = New-Object System.Data.SqlClient.SqlDataAdapter
$SqlAdapter.SelectCommand = $SqlCmd
$DataSet = New-Object System.Data.DataSet
$SqlAdapter.Fill($DataSet)
$SqlConnection.Close()
$data = $DataSet.Tables[0]
$html = $data `
| Select-Object -Property RoleName, UserName `
| ConvertTo-Html -fragment `
| Out-String
Send-MailMessage `
-From $emailFrom `
-To $emailTo `
-Subject $emailSubject `
-BodyAsHtml $html `
-SmtpServer $smtp

update sql table for Active Directory createdon and disabled on information

I have a user table in the database that i am trying to update with Createdon date and disabled on date with the data from Active Directory. So far this is what I have:
$SearchRoot = "OU=NonAIQ,OU=FrontOffice,DC=dev,DC=local"
$serverName = "localhost"
#$SearchRoot = "OU=NonAIQ,OU=FrontOffice,DC=dmz,DC=local"
#$serverName = "spoproddb3.dmz.local"
try {
Import-Module "sqlps" -DisableNameChecking
if ((Get-PSSnapin -Name "Quest.ActiveRoles.ADManagement" -ErrorAction SilentlyContinue) -eq $null ) {
Add-PsSnapin "Quest.ActiveRoles.ADManagement"
}
$externalUsers = Get-QADUser -SizeLimit 0 -SearchRoot $SearchRoot | Select-Object whencreated, whenchanged
$externalUsers | % {
$query = #"
Update tbl_EdgeUsers Set CompanyName = '$_.CreationDate'
Where UserUPN = $_.UserPrincipalName;
"#
Write-Host "The query is $query"
Invoke-SqlCmd -ServerInstance $serverName -Query $query -Database "EdgeDW"
}
} finally {
Remove-Module "sqlps" -ErrorAction SilentlyContinue
Remove-PsSnapin "Quest.ActiveRoles.ADManagement"
}
Now for when created, we just grab all the values.
But since AD does not track the Disabled in date, I am using the when changed date since we dont make changes to an account once it is changed.
The part that I am stuck on is about the logic for when changed date. For this I have to check if an account is disabled. If it is the update the table with that date. If an account is not disabled, then ignore that value and set the value in the sql table as '1/1/9999'.
can you guys please help with this logic?
Thank you in advance for any help.
of top of my head maybe something such as this, although thinking about it now, its a nasty way having the invoke-sql inside the foreach loop if the dataset is large, probably better to output the results of the if statement to csv or somewhere then run the invoke-sql against that.
$users = Get-ADUser -Filter * -Properties whenchanged | Select-Object -Property UserPrincipalName, whenchanged, enabled
$disabledsql = #"
update tbl_EdgeUsers Set date = '$user.whenchanged'
Where UserUPN = '$user.UserPrincipalName';
"#
$activesql = #"
update tbl_EdgeUsers Set date = '1/1/9999
Where UserUPN = '$user.UserPrincipalName';
"#
foreach ($user in $users)
{
if ($user.enabled -eq 'False')
{
Invoke-Sqlcmd -ServerInstance $serverName -Query $disabledsql -Database 'EdgeDW'
}
else
{
Invoke-Sqlcmd -ServerInstance $serverName -Query $activesql -Database 'EdgeDW'
}
}