Click here to monitor SSC
Av rating:
Total votes: 47
Total comments: 26


Laerte Junior
Exceptional PowerShell DBA Pt 2 - The Morning Checklist
21 January 2010

Laerte Junior takes us further into the world of an Exceptional PowerShell DBA, showing us how he uses PowerShell 2.0 to take all the headaches out of even more of his daily checklist. What could be better than having your morning checklist run itself?

I confess that when I started working with PowerShell, I envisioned functions as behaving the same way as Visual Basic or C#, i.e. everything being done within the single function. Thus it was big and full of conditions. Yet one of the great features of PowerShell is consistency; Everything in PowerShell is an object, and the output from one cmdlet can be easily “piped" into the input of another. This means your functions are generic, and over just two or three lines of code you can filter, apply summations, dictate output format, manage inserts into a SQL Server table etc...

As a result, PowerShell is an incredibly versatile scripting language which makes proactive database administration potentially pain-free. In my previous article, I demonstrated how to deal with orphaned users using PowerShell and SMO. This time around, I’ll take you through some more of my daily checklists and management tasks which have been ‘PowerShelled’ to make my life infinitely easier! Bear in mind that, as with my last article, my scripts require PowerShell 2.0, and all I’ll be giving you today are my scripts (which you can download from the top of this article) and examples on how to use them. I think they’re incredibly useful, and if you have any questions about any of them, feel free to leave a comment. So, without further ado:

Recording log information

Let's start by talking a little more about Save-MSSQLMsg. It is one of most important functions here, because it will generate a log of all messages, be they error messages or simply information. As a result, I will always use this function in conjunction with others to ensure I know exactly what is going on in my servers. It takes 6 parameters, and only the first two (file and server names) are mandatory. The other 4 are not informed of the default values, as they are used by the script to insert information into the log files.

Function Save-MSSQLMsg () 
{
#requires -Version 2

<#
.SYNOPSIS
Save log in file
.PARAMETER NamePS1
File Name
.PARAMETER Server
Server name
.PARAMETER databasename
DatabaseName
.PARAMETER Message default 
Message To Log
.PARAMETER PathFileLog default 
Path to generate file
Default = C:\temp
.PARAMETER TodayDate
Date to Log default getdate
#>
[CmdletBinding()] Param (
[Parameter(position
=1,Mandatory = $true )][String] $NamePS1,
[Parameter(position
=2,Mandatory = $true )][String] $Server,
[Parameter(position
=3,Mandatory = $false )][String] $DatabaseName = ",
[Parameter(position
=4,Mandatory = $false )][String] $Message = " ,
[Parameter(position
=5,Mandatory = $false )][String] $PathFileLog =
"C:\temp",
[Parameter(position
=6,Mandatory = $false )][String] $TodayDate =
(
Get-Date -Format "yyyyMMddhhmmss")
)
process
{

#Test if path wich will contains the error file exists. If not,
# create

if (!(Test-Path -path $PathFileLog))
{
try {
New-Item $PathFileLog -itemtype directory -ErrorAction
Stop | Out-Null } catch {
Write-Host "Can not create log file path" break;
}
}


$NameFileFull = Join-Path $PathFileLog "$NamePS1$TodayDate.log"

$TDate = $TodayDate.Substring(0,4) + "-" + $TodayDate.Substring(4,2)
+ "-" + $TodayDate.Substring(6,2)

"Server : " + $Server + " Database : " + $DatabaseName + " Date : "
+ $TDate + " Message: " + $Message | Out-file $NameFileFull -append
}
}

So, for generating an error message or simply information, we use :

Save-MSSQLMsg "ScriptName1" "Server1" "Database1" "MessageToDisplay1" 
"C:\TEMP"

But if we want only one file to be generated, we must constantly pass the same script name and date to the function, i.e., we need to set this in the parameters at the beginning of the function, and then we can use the function like this:

$ScriptName = "ScriptName1" 
$TodaydateMSG = Get-Date -Format "yyyyMMddhhmmss" Save-MSSQLMsg $ScriptName "Server1" "Database1" "MessageToDisplay1"
"C:\TEMP" $TodaydateMSG Save-MSSQLMsg $ScriptName "Server2" "Database2" "MessageToDisplay2"
"C:\TEMP" $TodaydateMSG Save-MSSQLMsg $ScriptName "Server3" "Database3" "MessageToDisplay3"
"C:\TEMP" $TodaydateMSG

Returning Backup Information

“Laerte, I need all backup information about backups in our databases, and if we had some of them without backup”

“Yes boss. Right away. How do you want that information? Email ? Snail Mail ? Smoke Signal ? Drums ?”

Not a problem; You’re an Exceptional DBA who has everything ready for your checks, and PowerShell under your command. As the heading suggests, this function returns information about your database backups. The properties return by the object are :

LineNumber

(Int)

Date

(DateTime)

ServerName

(string)

DatabaseName

(string)

LastBackupDate

(DateTime)

LastDifferentialBackupDate

(DateTime)

LastLogBackupDate

(DateTime)

Bear in mind that you can control which servers this script checks by supplying a list of targets in a flat text file – C:\TEMP\Servers.txt. If you don’t supply this file, the script will only check the default server. Take a look at the script below, and I’ll give some examples of how to use it after the jump>

Function Get-MSSQLBackup () 
{
#requires -Version 2
<#
.SYNOPSIS
Returns information about backups
.DESCRIPTION
Write to Application LocalComputer Eventlog
.PARAMETER TxtServerList
List with servers, if not pass default server is used
.LINK
www.laertejuniordba.spaces.live.com

#>
[CmdletBinding()] PARAM (
[Parameter(position
=1,Mandatory=$false,
ValueFromPipelineByPropertyName
=$true,HelpMessage="File Servers
List"
)]
[Alias(
"FullNameTXT")]
[
String] $TXTServersList = $env:COMPUTERNAME ) begin { [reflection.assembly]::LoadWithPartialName("Microsoft.SqlServer.Smo")
|
out-null } Process { $verbosePreference="continue" [datetime] $TodayDate = get-date -Format "yyyy/MM/dd hh:mm:ss" if ($TXTServersList.substring($TXTServersList.length -4,4) -eq ".TXT")
{
try { $ServersList = get-content $TXTServersList
}
catch {
$msg = $error[0]
Write-Warning $msg break;
}
}
else { $ServersList = $TXTServersList } $LineNumber = 1
$FinalResult = @()

foreach ($svr in $ServersList )
{
try
{
$DatabaseName = " $Server=New-Object "Microsoft.SqlServer.Management.Smo.Server"
"$svr" $Data = $Server.Databases| where-object {$_.IsAccessible } |
foreach {

[
datetime] $LastBackupDate = "{0:yyyy/MM/dd hh:mm:ss}"
-f
[datetime] $_.LastBackupDate
[
datetime]$LastDifferentialBackupDate = "{0:yyyy/MM/dd hh:mm:ss}"
-f [datetime] $_.LastDifferentialBackupDate
[
datetime]$LastLogBackupDate = "{0:yyyy/MM/dd hh:mm:ss}"
-f [datetime] $_.LastLogBackupDate
[
String] $DatabaseName = $_.name

$Object = New-Object PSObject -Property @{
LineNumber
= $LineNumber
Date
= $TodayDate
ServerName
= $svr DatabaseName = $DatabaseName
LastBackupDate
= $LastBackupDate LastDifferentialBackupDate = $LastDifferentialBackupDate LastLogBackupDate = $LastLogBackupDate } $FinalResult += $Object $LineNumber ++ } } catch {
$msg = $error[0]
Write-Warning $msg Save-MSSQLMsg "Get-MSSQLBackup" "$svr" "$DatabaseName" "$msg" continue } } Write-Output $FinalResult
}
}


So, now that you've seen the function, here are some examples on how to use it :

  • List all backup information on the default server:
Get-MSSQLBackup 
  • List all backup information on target server[s] (supplied in the text file):
Get-MSSQLBackup "C:\TEMP\Servers.txt"
  • List all databases which haven't been backed up for one day:
Get-MSSQLBackup "C:\TEMP\servers1.txt" | where-Object {( (get-Date) 
- ($_.LastBackupDate)).days -gt 1} | Select LineNumber,Date,ServerName,
DatabaseName,LastBackupDate,LastDifferentialBackupDate,LastLogBackupDate
|
Format-Table
  • List backup information for all databases, and store it in a SQL Server table:
create table BackupsXML (XMLCOL XML)
(Get-MSSQLBackup
"C:\TEMP\servers.txt" | Select LineNumber,Date,ServerName,
DatabaseName,LastBackupDate,LastDifferentialBackupDate,LastLogBackupDate
|
ConvertTo-Xml -NoTypeInformation).save("c:\temp\Backups.xml")
$XML = (Get-Content "C:\Temp\Backups.xml" ) -replace "'", "''" $SQL = "insert into BackupsXML (XMLCol) values ('$XML')"
invoke-sqlcmd -ServerInstance
$env:COMPUTERNAME -database "tempdb"
-query
$sql
  • Generate TSQL to list all data from the "BackupsXML" table, where the ServerName condition = "Jupiter":
Select t2.Col1.value('(Property[@Name="LineNumber"]/text())[1]', 'int') 
LineNumber,
t2.Col1.value(
'(Property[@Name="Date"]/text())[1]', 'nvarchar(255)')
Date,
t2.Col1.value(
'(Property[@Name="ServerName"]/text())[1]',
'nvarchar(max)') ServerName,
t2.Col1.value(
'(Property[@Name="DatabaseName"]/text())[1]', 'nvarchar(255)') DatabaseName,
t2.Col1.value(
'(Property[@Name="LastBackupDate"]/text())[1]',
'nvarchar(255)') LastBackupDate
FROM dbo.BackupsXML
CROSS APPLY xmlcol.nodes(
'/Objects/Object') As t2(Col1)
where t2.Col1.value('(Property[@Name="ServerName"]/text())[1]',
'nvarchar(max)') = 'Jupiter'

Now that we’ve got this script working, let's generate a CSV file and email it to our beloved leader:

Get-MSSQLBackup "C:\TEMP\Servers.txt" | Select Date,ServerName,
DatabaseName,LastBackupDate |
Export-Csv -Path "C:\temp\Backups.csv" -NoTypeInformation –Force Send-MailMessage -To "YourBelovedBoss@contoso.com" -Subject "Database Without Backups My Beloved Boss"
-from "ExceptionalPowershellDBA@contoso.com" -Attachments "C:\TEMP\backups.csv" -SmtpServer "10.0.0.10"

Returns Linked Servers Information and Connectivity

“Laerteeeee (yes..he loves my name) , Some of our remote procedures are returning errors. What is happening?”

“I will check the linked servers again, my lord”

I know..I know.. Powershell is my middle name.

This function returns an object with information about all your linked servers and your connectivity. As with the others functions, you can pass a file to the script with all the servers you want to test, or else leave it empty if you want the current server. Likewise, this script will also generate a log file if some server is off or another problem occurs.The properties returned are :

LineNumber

(int)

Date

(datetime)

ServerName

(string)

LinkedServerName

(string)

DataSource

(string)

DateLastModified

(datetime)

CollationCompatible

(boolean)

DataAccess

(boolean)

RPC

(boolean)

RpcOut

(boolean)

UseRemoteCollation

(boolean)

CollationName

(string)

ConnectionTimeOut

(int)

QueryTimeOut

(int)

Distributor

(boolean)

Publisher

(boolean)

Subscriber

(boolean)

LazySchemaValidation

(boolean)

EnablePromotionofDistributedTransactionsForRPC

(boolean)

ProviderName

(string)

ProductName

(string)

Connectivity

(boolean)

Function Get-MSSQLLinkedServer()
<#
#requires -Version 2

<#
.SYNOPSIS
Returns information about linkedservers. Also test connectivity too.
.DESCRIPTION
Returns information about linkedservers. Also test connectivity too.
.PARAMETER TxtServerList
List with servers, if not pass default server is used
.LINK
www.laertejuniordba.spaces.live.com

#>
#>
{
[CmdletBinding()]

PARAM (
[Parameter(position
=1,Mandatory=$false,
ValueFromPipelineByPropertyName
=$true,
HelpMessage
="File Servers List")]
[Alias(
"FullNameTXT")]
[
String] $TXTServersList = $env:COMPUTERNAME ) begin
{
[
reflection.assembly]::LoadWithPartialName("Microsoft.SqlServer.Smo")
|
out-null
}
Process { $verbosePreference="continue"
[
datetime] $TodayDate = get-date -Format "yyyy/MM/dd hh:mm:ss" if ($TXTServersList.substring($TXTServersList.length -4,4) -eq ".TXT")
{
try { $ServersList = get-content $TXTServersList
}
catch {
$msg = $error[0]
Write-Warning $msg break;
}
}
else { $ServersList = $TXTServersList } $LineNumber = 1
$FinalResult = @()

foreach ($svr in $ServersList )
{
try
{
$Server=New-Object "Microsoft.SqlServer.Management.Smo.Server" "$svr" $Data = $Server.linkedservers| where-object {$_.State -eq "Existing"} | foreach {



[
datetime]$DateLastModified = "{0:yyyy-MM-dd hh:mm:ss}" -f [datetime] $_.DateLastModified

try { $_.testconnection()
$Connectivity = $true } catch {
$Connectivity = $false } $Object = New-Object PSObject -Property @{

LineNumber
= $LineNumber
Date
= $TodayDate
ServerName
= $svr LinkedServerName = $_.Name
DataSource
= $_.DataSource
DateLastModified
= $DateLastModified CollationCompatible = $_.CollationCompatible
DataAccess
= $_.DataAccess
RPC
= $_.RPC
RpcOut
= $_.RPCOut
UseRemoteCollation
= $_.UseRemoteCollation
CollationName
= $_.CollationName
ConnectionTimeOut
= $_.ConnectTimeOut
QueryTimeOut
= $_.QueryTimeOut
Distributor
= $_.Distributor
Publisher
= $_.Publisher
Subscriber
= $_.Subscriber
LazySchemaValidation
= $_.LazySchemaValidation
EnablePromotionofDistributedTransactionsForRPC
= $_.IsPromotionofDistributedTransactionsForRPCEnable
ProviderName
= $_.ProviderName
ProductName
= $_.ProductName
Connectivity
= $Connectivity }

$FinalResult += $Object $LineNumber ++
}


}
catch {
$msg = $error[0]
Write-Warning $msg Save-MSSQLMsg "Get-MSSQLLinkedServer" "$svr" " "$msg"
continue } } Write-Output $FinalResult } }

… And just as before, some examples on how to use it:

  • List All the LinkedServers information for the default Server:
Get-SQLLinkedServer 
  • >List All the LinkedServers information for All target servers:
Get-SQLLinkedServer "c:\temp\servers.txt" 
  • List All the LinkedServers information for All target servers, and filter the data down to just the name and connectivity test data:
Get-SQLLinkedServer "c:\temp\servers.txt" | select LineNumber,Date,
ServerName,LinkedServerName,Connectivity |
Format-Table
  • List All the LinkedServers information for All target servers, and filter the data down to just the name and connectivity test data for results where Connectivity = false):
Get-SQLLinkedServer "c:\temp\servers.txt" | where-object {!$_.Connectivity}
|
select LineNumber,Date,ServerName,LinkedServerName,Connectivity
|
Format-Table
  • List All the LinkedServers information for All target servers, filter the data down to just the name and connectivity test results where Connectivity = false), and store the data in a ‘LinkerServers’ SQL Server Table:
TSQL create table LinkedServers (XMLCOL XML)
(Get-SQLLinkedServer
"c:\temp\servers.txt" | where-object
{
!$_.Connectivity} | select LineNumber,Date,ServerName,LinkedServerName,
Connectivity |
ConvertTo-Xml -NoTypeInformation)
.save(
"c:\temp\LinkedServers.xml")
$XML = (Get-Content "C:\Temp\LinkedServers.xml" ) -replace "'", "''" $SQL = "insert into LinkedServers (XMLCol) values ('$XML')"
invoke-sqlcmd -ServerInstance
$env:COMPUTERNAME -database "tempdb"
-query
$sql
  • Generate TSQL to list all the data from the ‘LinkerServers’ table where the ServerName condition = "Jupiter"
Select t2.Col1.value('(Property[@Name="LineNumber"]/text())[1]', 'int') 
LineNumber,
t2.Col1.value(
'(Property[@Name="Date"]/text())[1]', 'nvarchar(255)')
Date,
t2.Col1.value(
'(Property[@Name="ServerName"]/text())[1]',
'nvarchar(max)') ServerName,
t2.Col1.value(
'(Property[@Name="LinkedServerName"]/text())[1]', 'nvarchar(255)') LinkedServerName,
t2.Col1.value(
'(Property[@Name="Connectivity"]/text())[1]', 'nvarchar(255)') Connectivity
FROM dbo.LinkedServers
CROSS APPLY xmlCOL.nodes(
'/Objects/Object') As t2(Col1)
where t2.Col1.value('(Property[@Name="ServerName"]/text())[1]', 'nvarchar(max)') = 'Jupiter'

Return Logins information

“Laerte, my Padawan, we need to know all the logins created in our servers during last week”

“Yes my Master. I´m an Exceptional DBA and The PowerShell is with me.“

This function returns an object containing:

LineNumber

(int)

Date

(datetime)

ServerName

(string)

LoginName

(string)

CreateDate

(Datetime)

DateLastModified

(Datetime)

LoginType

(String)

AsymmetricKeys

(String)

Certificate

(String)

DefaultDatabase

(String)

DenyWindowsLogin

(Boolean)

HasAccess

(Boolean)

IsDisable

(Boolean)

IsLocked

(Boolean)

IsPassowordExpired

(Boolean)

IsSystemObject

(Boolean)

Language

(String)

LanguageAlias

(String)

MustChangePassword

(Boolean)

PasswordExpirationEnabled

(Boolean)

PasswordPolicyEnforced

(Boolean)

... And the script itself looks like this:

Function Get-MSSQLLogin()
#requires -Version 2
<#
.SYNOPSIS
Returns information about Logins
.DESCRIPTION
Returns information about Logins
.PARAMETER TxtServerList
List with servers, if not pass default server is used
.LINK
www.laertejuniordba.spaces.live.com

#>
{ [CmdletBinding()] PARAM (
[Parameter(position
=1,Mandatory=$false,
ValueFromPipelineByPropertyName
=$true,
HelpMessage
="File Servers List")]
[Alias(
"FullNameTXT")]
[
String] $TXTServersList = $env:COMPUTERNAME ) begin
{
[
reflection.assembly]::LoadWithPartialName("Microsoft.SqlServer.Smo")
|
out-null
}
Process { $verbosePreference="continue"
[
datetime] $TodayDate = get-date -Format "yyyy/MM/dd hh:mm:ss" if ($TXTServersList.substring($TXTServersList.length -4,4) -eq ".TXT")
{
try { $ServersList = get-content $TXTServersList
}
catch {
$msg = $error[0]
Write-Warning $msg break;
}
}
else { $ServersList = $TXTServersList } $LineNumber = 1
$FinalResult = @()



foreach ($svr in $ServersList )
{
try
{
$Server=New-Object "Microsoft.SqlServer.Management.Smo.Server"
"$svr" $Data = $Server.logins | where-object {$_.State -eq "Existing"}
|
foreach {



[
datetime] $CreateDate = "{0:yyyy/MM/dd hh:mm:ss}" -f [datetime] $_.CreateDate
[
datetime] $DateLastModified = "{0:yyyy/MM/dd hh:mm:ss}"
-f [datetime] $_.DateLastModified

$Object = New-Object PSObject -Property @{

LineNumber
=$LineNumber
Date
=$TodayDate
ServerName
=$svr LoginName =$_.Name
CreateDate
=$CreateDate
DateLastModified
=$DateLastModified
LoginType
=$_.LoginType
AsymmetricKeys
=$_.AsymmetricKeys
Certificate
=$_.Certificate
DefaultDatabase
=$_.DefaultDatabase
DenyWindowsLogin
=$_.DenyWindowsLogin
HasAccess
=$_.HasAccess
IsDisable
=$_.IsDisable
IsLocked
=$_.IsLocked
IsPassowordExpired
=$_.IsPassowordExpired
IsSystemObject
=$_.IsSystemObject
Language
=$_.Language
LanguageAlias
=$_.LanguageAlias
MustChangePassword
=$_.MustChangePassword
PasswordExpirationEnabled
=$_.PasswordExpirationEnabled
PasswordPolicyEnforced
=$_.PasswordPolicyEnforced }

$FinalResult += $Object $LineNumber ++
}

}
catch {
$msg = $error[0]
Write-Warning $msg Save-MSSQLMsg "Get-MSSQLLogin" "$svr" " "$msg"
continue } } Write-Output $FinalResult
}
}

As before you can easily apply all the filters you want , this function works with logs, and the usage patterns are basically the same:

  • List All Logins information on the default server:
Get-MSSQLLogin 
  • List All Logins information on All target servers:
Get-MSSQLLogin "c:\temp\servers.txt" 
  • List All Logins information on All target servers, filter the data down to just the name and CreateDate:
Get-MSSQLLogin "c:\temp\servers.txt" | select LineNumber,Date,ServerName,
LoginName,CreateDate |
Format-Table
  • List All Logins information on All target servers filter the data down to just the name and where the CreateDate is less than 7 days ago, and store the results in a SQL Server Table:
Get-MSSQLLogin | where-Object {((get-date) - ($_.createdate)).days -lt 7} 
|
select LineNumber,Date,ServerName,LoginName,CreateDate |Format-Table TSQL create table Logins (XMLCOL XML): (Get-MSSQLLogin "c:\temp\servers.txt" | where-Object {((get-date) - ($_.createdate)).days -lt 7} | select LineNumber,Date,ServerName,
LoginName,CreateDate |
ConvertTo-Xml -NoTypeInformation)
.save(
"c:\temp\Logins.xml")
$XML = (Get-Content c:\temp\Logins ) -replace "'", "''" $SQL = "insert into Logins (XML) values ('$XML')"
invoke-sqlcmd -ServerInstance
$env:COMPUTERNAME -database "tempdb"
-query
$sql
  • Generate TSQL to list the data where the ServerName condition = "Jupiter":
Select t2.Col1.value('(Property[@Name="LineNumber"]/text())[1]', 'int') 
LineNumber,
t2.Col1.value(
'(Property[@Name="Date"]/text())[1]', 'nvarchar(255)')
Date,
t2.Col1.value(
'(Property[@Name="ServerName"]/text())[1]',
'nvarchar(max)') ServerName,
t2.Col1.value(
'(Property[@Name="LoginName"]/text())[1]',
'nvarchar(255)') LoginName,
t2.Col1.value(
'(Property[@Name="CreateDate"]/text())[1]', 'nvarchar(255)') CreateDate
FROM dbo.Logins
CROSS APPLY xmlCOL.nodes(
'/Objects/Object') As t2(Col1)
where t2.Col1.value('(Property[@Name="ServerName"]/text())[1]',
'nvarchar(max)') = 'Jupiter'

Returning Jobs information

“Laerte, I see that every morning when you get to work, you have an excel list in your inbox listing the problematic backups on all the servers in the environment. Out of curiosity, how do you do that? I couldn’t find Database Mail configured, checking procedures or any other mechanism in place to audit jobs in servers.”

From my machine, with a few lines of code, I access all the servers’ lists of the jobs that failed, in XLSX format, and send that information to my email. I don’t have any procedures or mechanism on the servers;. the function is uniform, repetitive and centralized; it returns the following information:

LineNumber

(int)

Date

(DateTime)

ServerName

(String)

JobName

(String)

Lastrunoutcome

(String)

Isenabled

(Boolean)

lastrundate

(DateTime)

Function Get-MSSQLJob()
#requires -Version 2 <# .SYNOPSIS Returns information about Jobs .DESCRIPTION Returns information about Jobs .PARAMETER TxtServerList List with servers, if not pass default server is used .LINK www.laertejuniordba.spaces.live.com #> { [CmdletBinding()] PARAM (
[Parameter(position
=1,Mandatory=$false,
ValueFromPipelineByPropertyName
=$true,
HelpMessage
="File Servers List")]
[Alias(
"FullNameTXT")]
[
String] $TXTServersList = $env:COMPUTERNAME ) begin
{
[
reflection.assembly]::LoadWithPartialName("Microsoft.SqlServer.Smo")
|
out-null
}
Process { $verbosePreference="continue"
[
datetime] $TodayDate = get-date -Format "yyyy/MM/dd hh:mm:ss" if ($TXTServersList.substring($TXTServersList.length -4,4) -eq ".TXT")
{
try { $ServersList = get-content $TXTServersList
}
catch {
$msg = $error[0]
Write-Warning $msg break;
}
}
else { $ServersList = $TXTServersList } $LineNumber = 1 $FinalResult = @()



foreach ($svr in $ServersList )
{
try
{
$Server=New-Object "Microsoft.SqlServer.Management.Smo.Server" "$svr" $Data = $Server.jobserver.jobs | foreach {

$Object = New-Object PSObject -Property @{
LineNumber
= $LineNumber
Date
= $TodayDate
ServerName
= $svr JobName = $_.name
lastrunoutcome
= $_.lastrunoutcome
isenabled
= $_.isenabled
lastrundate
= $_.lastrundate
}
$FinalResult += $Object $LineNumber ++
}


}
catch {
$msg = $error[0]
Write-Warning $msg Save-MSSQLMsg "Get-MSSQLJob" "$svr" " "$msg"
continue } } Write-Output $FinalResult
}
}

And now, once we’ve collected all that iinformation, we can create a XLSX and send to our email:

$excel = New-Object -ComObject Excel.Application
$excel.Visible = $false
$workbook = $excel.Workbooks.add()
$workbook.WorkSheets.item(1).Name = "Jobs" $sheet = $workbook.WorkSheets.Item("Jobs ")

$sheet.cells.item(1,1) = "Date" $sheet.cells.item(1,2) = "ServerName" $sheet.cells.item(1,3) = "JobName" $sheet.cells.item(1,4) = "LastRunDate" $Line = 2 foreach ($Jobs in Get-MSSQLJob "C:\TEMP\servers.txt"

where-Object {$_.lastrunoutcome –eq “Failed” –and  $_.isenabled} )


{
$sheet.cells.item($line, 1) = $Jobs.Date -f "{0:yyyy-MM-dd hh:mm:ss}"
-f [datetime]
$sheet.cells.item($line, 2) = $Jobs.ServerName
$sheet.cells.item($line, 3) = $Jobs.JobName
$sheet.cells.item($line, 4) = $Jobs.LastRunDate
$Line++ } $excel.Visible = $true $workbook.SaveAs("C:\TEMP\Jobs.xlsx")
$workbook.Close()
$excel.Quit

Send-MailMessage -To " ExceptionalPowershellDBA@contoso.com "
-Subject "Jobs With Problems"
-from "AuditDBA@contoso.com" -Attachments "C:\TEMP\Jobs.xlsx" -SmtpServer "10.0.0.10"

If you want to see more about PowerShell exporting data to Excel, be sure to read this excellent content on TechNet:

Now it only remains for you to schedule this script for a job in SQL Server, or even Windows Task Scheduler. Keep in mind that these functions are just examples, and do not return all the properties of the SMO objects used. If you want a complete list of properties, I suggest using MSDN and studying the classes in question.

So, in this second part of my Exceptional PowerShell DBA posts you can see some of my daily checklist, and, more importantly, how PowerShell can help reduce the overhead of managing that checklist Please feel free to download my sample scripts from the top of this article and experiment with them. In the third and final part of these posts, we will see some more set ups we can do, like changing collation columns and rebuilding indexes. As always: don’t forget, you are an Exceptional DBA who loves PowerShell!



This article has been viewed 13706 times.
Laerte Junior

Author profile: Laerte Junior

Laerte Junior is a SQL Server MVP and, through his technology blog and simple-talk articles, an active member of the Microsoft community in Brasil. He is a skilled Principal Database Architect, Developer, and Administrator, specializing in SQL Server and Powershell Programming with over 8 years of hands-on experience. He holds a degree in Computer Science, has been awarded a number of certifications (including MCDBA), and is an expert in SQL Server 2000 / SQL Server 2005 / SQL Server 2008 technologies. He also organizes, and is a speaker at microsoft community events, attracting hundreds of attendees. Laerte has also recently become a Friend of Redgate in Brasil, has taught classes at universities, and produced webcasts for the community.

You should follow him on Twitter as @LaerteSQLDBA

Search for other articles by Laerte Junior

Rate this article:   Avg rating: from a total of 47 votes.


Poor

OK

Good

Great

Must read
 
Have Your Say
Do you have an opinion on this article? Then add your comment below:
You must be logged in to post to this forum

Click here to log in.


Subject: Very cool
Posted by: Evilazaro (view profile)
Posted on: Thursday, January 21, 2010 at 10:45 AM
Message: Excellent article laertes, you will be very useful to DBAs:))


Att.
Evilázaro Alves-MVP Developer Connected Systems

Subject: Congratulations!
Posted by: gcardoso (view profile)
Posted on: Thursday, January 21, 2010 at 10:47 AM
Message: I was not even a fan of Power Shell (yet), but in my opinion the article is very good and the scripts are very useful




Subject: Excellent
Posted by: Demetrio Silva (view profile)
Posted on: Thursday, January 21, 2010 at 10:49 AM
Message: My great friend Laertes. Excellent article. It is a showcase of how we can use the PS to perform routine tasks and to worry about other things

Subject: Very Good!
Posted by: Denison (not signed in)
Posted on: Thursday, January 21, 2010 at 10:52 AM
Message: Larte, The Forever Master!!!

Take Regards,

Denison

Subject: Wonderfull
Posted by: Math (not signed in)
Posted on: Thursday, January 21, 2010 at 1:22 PM
Message: Excellent article, I was waiting for the second part and did not disappoint. I loved the part of the "Yes my Master" remember my company. I was expecting for "Laerte, I am your father" hahahaha
Congratulations Laerte

Subject: Thanks !!!
Posted by: laerte (view profile)
Posted on: Thursday, January 21, 2010 at 1:39 PM
Message: Thank you all, hope you enjoyed the content. Math, hahahaha .. no .. no ... I believe I am well liked by the men, but not to this point.
It seemed that I am a big fan of Star Wars?. If I tell you the name of my VMs (R2D2, C3PO, Jabba, Chewie)

Subject: Excelente!!
Posted by: Sérgio Duarte (not signed in)
Posted on: Thursday, January 21, 2010 at 5:38 PM
Message: Valeu Laerte!!

Mais um grande artigo de powershell!!

Att.
Sergio Duarte

Subject: Really Exceptional!
Posted by: Paulo R. Pereira (view profile)
Posted on: Thursday, January 21, 2010 at 6:05 PM
Message: Laerte, you are doing Exceptional job writing about the relation between PS and SQL Server! I hope to see more articles about PS!

Subject: Very good ...
Posted by: Weber (not signed in)
Posted on: Friday, January 22, 2010 at 6:02 AM
Message: Good already know this guy ha a good time, and he always manages to surprise expectations, still goes like my friend.

Subject: Excelent
Posted by: Diogo Munarin (not signed in)
Posted on: Friday, January 22, 2010 at 6:04 AM
Message: Great article...keep up with the excellent work

Subject: qualidade!
Posted by: Kleber Rafael (not signed in)
Posted on: Friday, January 22, 2010 at 6:10 AM
Message: valeu Laerte,

é bom poder contar com sua larga experiencia para nos alimentar de conhecimento!!!

excelente artigo Amigão.

[]'s

Subject: Show de Bola
Posted by: Anonymous (not signed in)
Posted on: Friday, January 22, 2010 at 6:11 AM
Message: Com certeza Laerte, mais um para entrar na biblioteca de grandes soluções.
Obrigado!!

Subject: Excelente
Posted by: Ricardo Frias (not signed in)
Posted on: Friday, January 22, 2010 at 6:12 AM
Message: Grande artigo!

Subject: Great Article
Posted by: Thiago Caserta (not signed in)
Posted on: Friday, January 22, 2010 at 6:20 AM
Message: It's a great article that helps to learn more about PowerShell.
Keep doing it, man!

Subject: As usual
Posted by: mcflyamorim (view profile)
Posted on: Friday, January 22, 2010 at 7:25 AM
Message: Weel done my red-gate friend.

Cheers

Subject: Muito Bom
Posted by: Junior (view profile)
Posted on: Friday, January 22, 2010 at 12:09 PM
Message: Grande Laerte sempre com otimas soluções nos mais variados assuntos!

Parabéns...

Subject: Amazing article - Great knonwledge and humor!
Posted by: Luciano Palma (not signed in)
Posted on: Friday, January 22, 2010 at 7:16 PM
Message: Congratulations, Laerte!
Your article makes it very clear why the word "Power" is in "PowerShell", and why your beloved boss should be happy in increasing your paycheck! ;)
Cheers,
Luciano
http://lpalma.com


Subject: “Failedâ€
Posted by: Lucas (not signed in)
Posted on: Saturday, January 23, 2010 at 1:53 PM
Message: Hey, Whats it?

{$_.lastrunoutcome –eq “Failed” –and $_isenabled) )

Subject: Re: “Failedâ€
Posted by: Andrew Clarke (view profile)
Posted on: Saturday, January 23, 2010 at 2:31 PM
Message: Fixed!

Subject: re:
Posted by: Anonymous (not signed in)
Posted on: Monday, January 25, 2010 at 1:56 PM
Message: “Laerte, I see that every morning when you get to work, you have an excel list in your inbox listing the problematic backups on all the servers in the environment. Out of curiosity, how do you do that? I couldn’t find Database Mail configured, checking procedures or any other mechanism in place to audit jobs in servers.”

"From my machine, with a few lines of code, I access all the servers’ lists of the jobs that failed,"


Personally I would see as being a risk rather than a clever solution. Surely, from your employer's perspective, the aim should be to make processes like this as visible as possible so that others can take over in your absence?

Subject: Re
Posted by: laerte (view profile)
Posted on: Monday, January 25, 2010 at 2:15 PM
Message: You're completly right. Everything has to be done with highly visible, with the approval of the team and manage, especially when it comes to administration of the database. I used a figurative sense, to show that we do not need any other mechanism on the servers to do this monitoring, only a few lines of code powershell.
There are a number of security requirements that must surely be followed.
Great point of view. I hope you enjoyed the content.

Subject: Congrats Laerte
Posted by: Nilton Pinheiro (not signed in)
Posted on: Monday, January 25, 2010 at 7:29 PM
Message: Hi Laerte.. good job! Your articles are cool and very usefull for people that are starting in SQLPS.

Congrats for your work and thanks to show how SQLPS is simple and usefull.

Nilton Pinheiro
SQL Server MVP

Subject: Greate article
Posted by: RFernandes (view profile)
Posted on: Tuesday, January 26, 2010 at 6:25 AM
Message: Laerte's, this article proves your commitment to help SQL Server technical community, congratulations!

Regards,

Rodrigo Souza
Microsoft
LATAM - SQL Server PFE

Subject: Laerte broke the mouth of the balloon
Posted by: Sir Fabio Cotrim (not signed in)
Posted on: Wednesday, January 27, 2010 at 10:28 AM
Message: You are simply the Best (or de beast HAHAHAHAHA).

Good article my friend.

Soon, you will be worthy of belonging to my company's select group of geniuses

regards

Subject: Re
Posted by: laerte (view profile)
Posted on: Wednesday, January 27, 2010 at 10:49 AM
Message: Nilton and Rodrigo. Thanks my friends. Good to see you guys in here. I hope you enjoyed the article :-)
Cotrim hahahaha..you are unique.
This is why our turn in the datacenter was more a pleasure than a job. You encouraged our nights working, migrating servers (remember?)
Thank you my friend! :-)

Subject: Nice article!
Posted by: Thiago Zavaschi (view profile)
Posted on: Thursday, January 28, 2010 at 4:53 PM
Message: Laerte "Powershell" Jr. Nice article!
Congratz my friend!
:)

 










Phil Factor
Automated Script-generation with Powershell and SMO
 In the first of a series of articles on automating the process of building, modifying and copying SQL Server... Read more...



 View the blog
What's the Point of Using VARCHAR(n) Anymore?
 The arrival of the (MAX) data types in SQL Server 2005 were one of the most popular feature for the... Read more...

SQL Source Control: The Development Story
 Often, there is a huge difference between software being easy to use, and easy to develop. When your... Read more...

How to Import Data from HTML pages
 It turns out that there are plenty of ways to get data into SQL Server from websites, whether the data... Read more...

SQL Scripts Manager: An Appreciation
 SQL Scripts Manager is Simple-Talk's present to its readers. William Brewer was an enthusiastic... Read more...

Hosted Team Foundation Server 2010 Review
 Team Foundation Server (TFS) has expanded its remit to support the whole software development process,... Read more...

Beginning SQL Server 2005 Reporting Services Part 1
 Steve Joubert begins an in-depth tour of SQL Server 2005 Reporting Services with a step-by-step guide... Read more...

Ten Common Database Design Mistakes
 Database design and implementation is the cornerstone of any data centric project (read 99.9% of... Read more...

Reading and Writing Files in SQL Server using T-SQL
 SQL Server provides several "standard" techniques by which to read and write to files but, just... Read more...

Beginning SQL Server 2005 Reporting Services Part 2
 Continuing his in-depth tour of SQL Server 2005 Reporting Services, Steve Joubert demonstrates the most... Read more...

Creating CSV Files Using BCP and Stored Procedures
 Nigel Rivett demonstrates some core techniques for extracting SQL Server data into CSV files, focussing... Read more...

Over 400,000 Microsoft professionals subscribe to the Simple-Talk technical journal. Join today, it's fast, simple, free and secure.

Join Simple Talk