<?xml version="1.0" encoding="utf-8"?>
<rss xmlns:atom="https://www.w3.org/2005/Atom" xmlns:sy="https://purl.org/rss/1.0/modules/syndication/" xmlns:dc="https://purl.org/dc/elements/1.1/" xmlns:content="https://purl.org/rss/1.0/modules/content/" xmlns:wfw="https://wellformedweb.org/CommentAPI/" xmlns:slash="https://purl.org/rss/1.0/modules/slash/"
  version="2.0">
  <channel>
    <title>Josh's SQL Blog</title>
    <atom:link
      href="https://docs.microsoft.com/archive/blogs/joshs_sql_blog/feed.xml"
      rel="self"
      type="application/rss+xml" />
    <link>https://docs.microsoft.com/archive/blogs/joshs_sql_blog/feed.xml</link>
    <description>Random SQL bits for your consumption</description>
    <lastBuildDate>Wed, 17 Apr 2019 04:06:51 GMT</lastBuildDate>
    <language>en-US</language>
    <sy:updatePeriod>hourly</sy:updatePeriod>
    <sy:updateFrequency>1</sy:updateFrequency>
    <item>
      <title>Export SQL Query Results to Excel and Email to Multiple Recipients</title>
      <link>https://docs.microsoft.com/archive/blogs/joshs_sql_blog/export-sql-query-results-to-excel-and-email-to-multiple-recipients</link>
      <pubDate>Tue, 19 Mar 2013 10:55:00 GMT</pubDate>
      <dc:creator><![CDATA[Binary Creations]]></dc:creator>
      <guid
        isPermaLink="false">https://blogs.technet.microsoft.com/joshs_sql_blog/2013/03/19/export-sql-query-results-to-excel-and-email-to-multiple-recipients/</guid>
      <description><![CDATA[I was recently tasked with creating a custom solution that could query a table (specifically 6...]]></description>
      <content:encoded><![CDATA[<p><span style="font-size: medium;">I was recently tasked with creating a custom solution that could query a table (specifically 6 columns) of which one column contained a query that needed to be executed. The results of this execution needed to be placed in Excel, formatted and sent to a list of recipients (one of the other columns). The request was a little more involved than this but the under-pinning&rsquo;s I thought might be useful to others so I&rsquo;m posting my solution here.</span></p>
<p><span style="font-size: medium;">To begin with, I know there are many different possibilities for creating this type of solution. I opted for a solution that I felt was simplistic (to me) and something I knew I could build within a reasonable amount of time (disclaimer: I am not a programmer so forgive my coding practices). I choose to design the solution in PowerShell (2.0 specifically) and tap into some existing cmdlets I was aware of to help speed things along.</span></p>
<p><span style="font-size: medium;">To get started I conceptually laid out the steps I needed to take to get to the end result.&nbsp;</span></p>
<ul>
<li><span style="font-size: medium;">I had a table called [Alerts] that contained 6 columns I needed to query.</span></li>
<li><span style="font-size: medium;">I needed to execute an initial query to query the [Alerts] table to extract the results from the 6 columns:</span></li>
<ul>
<li><span style="font-size: medium;">[Query]</span></li>
<li><span style="font-size: medium;">[Recipients]</span></li>
<li><span style="font-size: medium;">[Subject]</span></li>
<li><span style="font-size: medium;">[Body]</span></li>
<li><span style="font-size: medium;">[From]</span></li>
<li><span style="font-size: medium;">[FileLocation]</span></li>
</ul>
</ul>
<p><span style="font-size: medium;">I think these column names are pretty self-explanatory as to what they are for.&nbsp; The [Query] column is the column that contains the query I needed to execute and then save the results to Excel.&nbsp; The [FileLocation] column is the file location that the Excel file should be saved to.</span></p>
<ul>
<li><span style="font-size: medium;">For each row in the [Alerts] table I needed to execute the query from the [Query] column.</span></li>
<li><span style="font-size: medium;">Save the results to Excel (formatted nicely)</span></li>
<li><span style="font-size: medium;">Email the file as an attachment to individuals in the [Recipients] column with [Subject], [Body], and [From] from their respective columns.</span></li>
</ul>
<p><span style="font-size: medium;">One of the other requirements was that all of this needed to be called from a stored procedure.&nbsp; But for this posting I&rsquo;m just going to show you what I did during testing and validation to call a script with hard coded values in it.&nbsp; If I get time I&rsquo;ll do another post on how I transplanted the rest into a sproc I could call and pass in dynamic values.</span></p>
<p><span style="font-size: medium;">Exporting query results directly into Excel isn&rsquo;t a native capability of SQL Server, so I went through what I knew I could leverage and settled on PowerShell to assist to me.&nbsp; I also was unaware of an easy way to go straight to Excel so I came up with a workaround that works quiet well for my situation. &nbsp;So without further ado, here</span><br /><span style="font-size: medium;">is my solution.</span></p>
<p><span style="font-size: medium;">First I created this Powershell code, note; I&rsquo;ve changed some of the hard-coded values for security. I&rsquo;ve commented a lot of the code chunks for clarity and will expand areas that I feel warrant further explanation.</span></p>
<pre class="scroll"><span style="font-size: medium;"><code class="mysql"> #---------------<br /> # Function: Main<br /> #---------------<br /> #Server we are connecting to<br /> $ServerName = 'MySQLServer'<br /> <br /> #DB we are connecting to<br /> $DatabaseName = 'TestDB'<br /> <br /> #Initial query to extract data from [Alerts] table<br /> $Query = 'select TSQL, FilePath, Recipient, Subject, Body, [From] from [dbo].[Alerts]'</code></span></pre>
<p><span style="font-size: medium;">The following variables are the hard-coded column names from the [Alerts] table.&nbsp; While not desirable at the time this is how I constructed this to get the results I wanted.</span></p>
<pre class="scroll"><span style="font-size: medium;"><code class="mysql"> #Column names from [Alerts] table<br /> $RecipientsColName = 'Recipient'<br /> $TSQLColName = 'TSQL'<br /> $FilePathColName = 'FilePath'<br /> $SubjectColName = 'Subject'<br /> $BodyColName = 'Body'<br /> $FromColName = 'From'<br /> $smtpServer = 'mysmtphost.somewhere.mydomain.com'</code></span></pre>
<p><span style="font-size: medium;">I spin up an Excel COM object initially to prevent load time later (as you&rsquo;ll see in a bit).</span></p>
<pre class="scroll"><span style="font-size: medium;"><code class="mysql"> #Create Excel COM object<br /> $Global:excel = New-Object -ComObject excel.application<br /> $Global:excel.visible = $False<br /> $Global:excel.displayalerts=$False<br /> <br /> #Pass in query from above to query [Alerts] table #SQL Query to execute<br /> $sqlCMD = $Query<br /> <br /> #Create a SQL connection string<br /> $sqlConStr = "Server=$ServerName;Database=$DatabaseName;Integrated Security=True" <br /> <br /> #Open the connection<br /> $sqlCon = New-Object System.Data.SQLClient.SqlConnection ($sqlConStr)<br /> <br /> #Create a Data adapter, pass in query and connection params<br /> $sqlDA = New-Object System.Data.SqlClient.SqlDataAdapter ($sqlCMD, $sqlCon) <br /> <br /> #Create a Data Table<br /> $sqlDT = new-object System.Data.DataTable</code></span></pre>
<p><span style="font-size: medium;">In the error handling I have a function called CloseExcel (detailed later on).&nbsp; This function will dereference the Excel COM object should a failure occur during the connection attempt.</span></p>
<pre class="scroll"><span style="font-size: medium;"><code class="mysql"> #Error handling should the connection fail <br /> Try<br /> {<br /> #Fill table, catch connection error<br /> $sqlDA.fill($sqlDT) | out-null<br /> }<br /> Catch [Exception]<br /> {<br /> CloseExcel<br /> Return $Error<br /> }<br /> Finally<br /> {<br /> #Close connection<br /> $sqlCon.Close() <br /> } </code></span></pre>
<p><span style="font-size: medium;">This variable is used to hold the position of the files I create.&nbsp; Since each row will create files this is the position of each file in an array.</span></p>
<pre class="scroll"><span style="font-size: medium;"><code class="mysql"> #xls file slot in array for attachment to send<br /> [int] $Slot = 0</code></span></pre>
<p><span style="font-size: medium;">At this point I have all the results in a data table and I want to loop through each row pulling out the values from each column.&nbsp;This is where the hard coded column values come in handy. There is probably a better or different way of doing this using column position, but this worked for me.</span></p>
<pre class="scroll"><span style="font-size: medium;"><code class="mysql"> #Loop through each row in the table<br /> $sqlDT | ForEach-Object { <br /> [string] $Query = $_.$TSQLColName<br /> [string] $FilePath = $_.$FilePathColName<br /> [string] $Recipient = $_.$RecipientsColName<br /> [string] $Subject = $_.$SubjectColName<br /> [string] $Body = $_.$BodyColName<br /> [string] $From = $_.$FromColName</code></span></pre>
<p><span style="font-size: medium;">For each row I stuff the column values into their respective variables and then execute the respective query. &nbsp;I&rsquo;m using the built-in Powershell cmdlet Export-Csv to automatically convert the results into a csv file (to the location and name specified in the table columns). There is nothing dynamically being created here as the requirement was to put the file in a location specified in the table.&nbsp;One could easily change this to dynamically create a filename.</span></p>
<pre class="scroll"><span style="font-size: medium;"><code class="mysql"> Try<br /> {<br /> #Execute $query and Output results to .csv file<br /> Invoke-Sqlcmd -query $Query -ServerInstance $ServerName -Database <br /> $DatabaseName | Export-Csv -Path $FilePath".csv" -NoTypeInformation<br /> }<br /> Catch [Exception]<br /> { <br /> CloseExcel<br /> Return $Error<br /> }</code></span></pre>
<p><span style="font-size: medium;">What I&rsquo;m doing here is building an array of csv files (for each queries row output). I will explain more on why I&rsquo;m doing it this way later.</span></p>
<pre class="scroll"><span style="font-size: medium;"><code class="mysql">#Build csv file list for cleanup<br />$Global:csvFileLists += $Global:FilePath + '.csv'</code></span></pre>
<p><span style="font-size: medium;">Now here comes the magic.&nbsp; What I do is take the csv file and open it in Excel silently. Notice the use of the file list array and slot to determine the file based on what iteration of the loop I&rsquo;m in.</span></p>
<pre class="scroll"><span style="font-size: medium;"><code class="mysql"> #Format the file to .xls<br /> $workBook = $excel.Workbooks.Open($Global:csvFileLists[$Slot])<br /> $workSheet = $workBook.workSheets.Item(1)</code></span></pre>
<p><span style="font-size: medium;">$RowCount holds the number of rows in the file, which I use later to determine what to subject to email. If you expect more rows than what an integer can hold, you should change this type to a different type.</span></p>
<pre class="scroll"><span style="font-size: medium;"><code class="mysql"> #Determine how many rows<br /> [int] $RowCount = $workSheet.UsedRange.Rows.Count</code></span></pre>
<p><span style="font-size: medium;">Here I'm using Excel's autofit function to resize the columns for me:</span></p>
<pre class="scroll"><span style="font-size: medium;"><code class="mysql"> #Autofit the text to the column<br /> $resize.EntireColumn.AutoFit() | Out-Null</code></span></pre>
<p><span style="font-size: medium;">Now I&rsquo;m just formatting the entire first row since the csv file will contain headers to make it pretty and stand out.</span></p>
<pre class="scroll"><span style="font-size: medium;"><code class="mysql"> #Select the entire first row, bold, underline, center and shade it.<br /> $range = $workSheet.Cells.Item(1).EntireRow <br /> $range.Font.Bold = $true<br /> $range.Font.Underline = $true<br /> $range.HorizontalAlignment = 3<br /> <br /> #light blue - https://msdn.microsoft.com/en-us/library/cc296089.aspx#xlDiscoveringColorIndex_ColorIndexProperty<br /> $range.Interior.ColorIndex = 37<br /> <br /> # Possible values from: https://msdn.microsoft.com/en-us/library/microsoft.office.interop.excel.xlfileformat.aspx<br /> # 51 = xlOpenXMLWorkbook (without macro's in 2007-2010, xlsx)<br /> # 56 = xlExcel8 (97-2003 format in Excel 2007-2010, xls)<br /> # 52 = xlOpenXMLWorkbookMacroEnabled (with or without macro's in 2007-2010, xlsm)<br /> # 50 = xlExcel12 (Excel Binary Workbook in 2007-2010 with or without macro's, xlsb)<br /> $xlExcel8 = 56</code></span></pre>
<p><span style="font-size: medium;">To change this from a csv to an Excel file I drop the&nbsp; .csv file extension and then save the file as a native .xls file.</span></p>
<pre class="scroll"><span style="font-size: medium;"><code class="mysql"> #Drop .csv on file path for saving<br /> $Global:FilePath = $Global:FilePath.Replace(".csv", "")<br /> $workbook.SaveAs($Global:FilePath,$xlExcel8)</code></span></pre>
<p><span style="font-size: medium;">I also track in a different global array all of the Excel files I&rsquo;ve create so I can be sure to delete them at the end.</span></p>
<pre class="scroll"><span style="font-size: medium;"><code class="mysql"> #Add file to list for later cleanup<br /> $Global:xlsFileLists += $workBook.FullName<br /> <br /> #Close workbook<br /> $workBook.Close() <br /> <br /> #deref sheets<br /> $workSheet = $null<br /> $workBook = $null</code></span></pre>
<p><span style="font-size: medium;">Now, to send an email I&rsquo;m using PowerShell&rsquo;s built in Send-MailMessage cmdlet. One of the tricky thing about using this is if you have multiple recipients. If you have multiple recipients entered in the table like this:</span></p>
<p style="padding-left: 30px;"><span style="font-size: medium;"><a href="mailto:Someone1@outlook.com">Someone1@outlook.com</a>,<a href="mailto:Someone2@outlook.com">Someone2@outlook.com</a></span></p>
<p><span style="font-size: medium;">You can&rsquo;t just stuff the single string $Recipient into the Send-MailMessage parameter &ndash;To like this:&nbsp;</span></p>
<p style="padding-left: 30px;"><span style="font-size: medium;">$Recipient = <a href="mailto:Someone1@outlook.com">Someone1@outlook.com</a>,<a href="mailto:Someone2@outlook.com">Someone2@outlook.com</a></span></p>
<p style="padding-left: 30px;"><span style="font-size: medium;">Send-MailMessage -To $Recipient</span></p>
<p><span style="font-size: medium;">It doesn&rsquo;t work because Send-MailMessage expects either a string, or a string array. Thus I built an el-cheapo parse should there be more than 1 recipient in the column.&nbsp; The function is called . . . can you guess ... ParseRecipient and I pass the variable $Recipient. I admit I went a little cheeky and created a global variable, well if I&rsquo;m being</span><br /><span style="font-size: medium;">honest a few globals, simply to make things easier to work with.&nbsp;</span></p>
<p><span style="font-size: medium;">The logic is, if $RowCount from the Excel file is greater than 1, I know there is data in the file since the first row is the headers.&nbsp; If it&rsquo;s less than 1, I know there wasn&rsquo;t any data in the file and my logic changes. In both cases I call the function ParseRecipient and pass $Recipient for parsing.</span></p>
<pre class="scroll"><span style="font-size: medium;"><code class="mysql"> If ($RowCount -gt 1 )<br /> {<br /> Try<br /> {<br /> #Parase Recipient<br /> ParseRecipient $Recipient<br /> <br /> #Send Email <br /> Send-MailMessage -To $Global:Recipients -Subject $Subject -Body $Body -Attachment $Global:xlsFileLists[$Slot] -From $From -SmtpServer $smtpServer <br /> }<br /> Catch [Exception]<br /> { <br /> CloseExcel<br /> Return $Error<br /> } <br /> }<br /> ELSE<br /> { <br /> $Subject = "No Reports Today - $Subject"<br /> $Body = "Reports were empty today."<br /> <br /> Try<br /> {<br /> #Parase Recipient<br /> ParseRecipient $Recipient<br /> <br /> #Send Email<br /> Send-MailMessage -To $Recipients -Subject $Subject -Body $Body -From $From -SmtpServer $smtpServer<br /> }<br /> Catch [Exception]<br /> { <br /> CloseExcel<br /> Return $Error<br /> } <br /> }<br /> <br /> #increment for next attachment file <br /> $Slot += 1<br /> }<br /> #end of $sqlDT | ForEach-Object {</code></span></pre>
<p><span style="font-size: medium;">This point marks the end of the looping from $sqlDT | ForEach-Object. At this point for every row in the table, we&rsquo;ve executed a query, pumped the results into an excel file (saving the file name and location in an array), converted the file from .csv to .xls (again saving the file name and location in a different array) and sent out an email for each file to the respective recipients. Now we need to do a little housekeeping and delete all the files.&nbsp;</span></p>
<p><span style="font-size: medium;">Here I just loop through the arrays I built and delete all the .csv and .xls files.</span></p>
<pre class="scroll"><span style="font-size: medium;"><code class="mysql"> #Delete csv files<br /> ForEach ($csvFile in $Global:CSVFileLists)<br /> {<br /> Remove-Item $csvFile<br /> }<br /> <br /> #Delete xls files<br /> ForEach ($xlsFile in $Global:xlsFileLists) <br /> {<br /> Remove-Item $xlsFile<br /> } </code></span></pre>
<p><span style="font-size: medium;">Finally I make sure to call the CloseExcel function to clean up the Excel COM object references.</span></p>
<pre class="scroll"><span style="font-size: medium;"><code class="mysql">CloseExcel </code></span></pre>
<p><span style="font-size: medium;">And there you have it.&nbsp; This is how I exported SQL query results with column headers to Excel and emailed them to intended recipients. Here is the CloseExcel Function (nothing special and perhaps a little overkill).</span></p>
<pre class="scroll"><span style="font-size: medium;"><code class="mysql">#----------------------------------<br /> # Function: CloseExcel<br /> # Dereferences the Excel COM object<br /> #----------------------------------<br /> Function CloseExcel<br /> {<br /> #Close Excel and cleanup<br /> $Global:excel.quit() <br /> $Global:excel = $null<br /> [GC]::Collect() <br /> } </code></span></pre>
<p><span style="font-size: medium;">And here is the recipient parsing logic:</span></p>
<pre class="scroll"><span style="font-size: medium;"><code class="mysql"></code><code class="mysql"> <br /> #---------------------------------------<br /> # Function: ParseRecipient<br /> # Parses Recipient into Recipients Array<br /> #---------------------------------------<br /> Function ParseRecipient<br /> {<br /> Param([string]$Recipient)<br /> <br /> #Clear out array<br /> $Global:Recipients = $null<br /> <br /> $commaIndex = 0<br /> DO<br /> {<br /> #First comma loc (0 based)<br /> $commaIndex = $Recipient.Trim().IndexOf(",") <br /> <br /> IF ($commaIndex -eq -1)<br /> {<br /> #Build Recipients Array<br /> $Global:Recipients += $Recipient.Trim()<br /> }<br /> ELSE<br /> {<br /> #Build Recipients Array from 0 to commaIndex<br /> $Global:Recipients += $Recipient.Trim().Substring(0,$commaIndex)<br /> <br /> #Rewrite Recipient list, start from commaIndex+1<br /> $Recipient = $Recipient.Substring(($commaIndex+1)).Trim()<br /> }<br /> }<br /> UNTIL ($commaIndex -eq -1)<br /> } </code></span></pre>
<p><span style="font-size: medium;">I forgot to mention here are my global variables I declared:</span></p>
<pre class="scroll"><span style="font-size: medium;"><code class="mysql"> #-------<br /> #Globals <br /> #-------<br /> #Collection of files to cleanup<br /> [string[]] $Global:xlsFileLists = @()<br /> [string[]] $Global:csvFileLists = @()<br /> [object] $Global:excel<br /> [string[]] $Global:Recipients = @() <br /> </code></span></pre>
<p><span style="font-size: medium;">To complete this&nbsp;I wired it up so I could call the script from SQL Server by usig xp_cmdshell by doing this:</span></p>
<pre class="scroll"><span style="font-size: medium;"><code class="mysql"> DECLARE @pscmd varchar (200)<br /> DECLARE @result int<br /> SET @pscmd = 'C:\WINDOWS\system32\WindowsPowerShell\v1.0\powershell.exe &ndash;file "C:\ExportToExcel.ps1"' <br /> EXEC @result = xp_cmdshell @pscmd, NO_OUTPUT<br /> If (@result=0)<br /> Print 'Sucess'<br /> ELSE<br /> Print 'Failure' </code></span></pre>
<p><span style="font-size: medium;">I've attached the complete code below (ExportToExcel.zip) for you to enjoy.&nbsp;Remember none of this code is production ready. Use it at your own risk as it is merely meant to be sample code for you to recycle and reform into your own needs.</span></p>
<p><span style="font-size: medium;">Thanks for stopping by and I hope this little morsel has been helpful to you!!</span></p>
<p>&nbsp;</p>
<p><a href="https://msdnshared.blob.core.windows.net/media/TNBlogsFS/prod.evol.blogs.technet.com/telligent.evolution.components.attachments/01/9791/00/00/03/55/96/41/ExportToExcel.zip" original-url="https://blogs.technet.com/cfs-filesystemfile.ashx/__key/telligent-evolution-components-attachments/01-9791-00-00-03-55-96-41/ExportToExcel.zip">ExportToExcel.zip</a></p>]]></content:encoded>
    </item>
  </channel>
</rss>