Search results matching tags 'PowerShell' and 'T-SQL Tuesday'http://sqlblog.com/search/SearchResults.aspx?o=DateDescending&tag=PowerShell,T-SQL+Tuesday&orTags=0Search results matching tags 'PowerShell' and 'T-SQL Tuesday'en-USCommunityServer 2.1 SP2 (Build: 61129.1)T-SQL Tuesday #39: Managing your SQL Server Services with PowerShellhttp://sqlblog.com/blogs/allen_white/archive/2013/02/12/t-sql-tuesday-39-managing-your-sql-server-services-with-powershell.aspxTue, 12 Feb 2013 18:09:00 GMT21093a07-8b3d-42db-8cbf-3350fcbf5496:47673AllenMWhite
<p><a href="http://blog.waynesheffield.com/wayne/archive/2013/02/invitation-for-t-sql-tuesday-39-can-you-shell-what-the-posh-is-cooking/"><img src="http://blog.waynesheffield.com/wayne/wp-content/uploads/2012/04/TSQL2sDay150x150.jpg" alt="T-SQL Tuesday">This T-SQL Tuesday</a> is about using PowerShell to do something with SQL Server. Now, if you've read any of my blog posts you probably know I've been using PowerShell to do things with SQL Server for a while now, but I'm glad Wayne decided on this topic for his T-SQL Tuesday topic, because everyone has different ways to use PowerShell, and you can learn from all of them, as I do.</p>
<p>(When I started to write this post I'd intended to share how I convert a PerfMon binary log file into SQL Server data for baseline analysis, but found I'd already done that <a href="http://sqlblog.com/blogs/allen_white/archive/2012/03/03/load-perfmon-log-data-into-sql-server-with-powershell.aspx">here</a>. Then, I thought I'd share how I save SQL Agent jobs and move them to another server, but did <a href="http://sqlblog.com/blogs/allen_white/archive/2012/05/02/script-and-migrate-agent-jobs-between-servers-using-powershell.aspx">that one</a>, too!)</p>
<p>One of the interesting aspects of SMO (Server Management Objects) is the Managed Computer object. It doesn't get a lot of attention because, well, that goes to the SQL Server instance and the various database objects. Administrators, though, need to pay attention to managing the instance itself. SQL Server 2008 introduced the Configuration Manager, a GUI application that allows administrators to view the SQL Server services installed, including their current state, the service account they use, etc. It also allows them to manage the external access to the instances via the network protocols supported, the TCP/IP ports, etc.</p>
<p>Here's a diagram of the Managed Computer object:</p>
<p><img src="https://cache.nebula.phx3.secureserver.net/obj/NTc2MjgwQzY3MDEwQzkxM0JBMDQ6NmY4YWVlZjU5MDRkNTUyZjg0YmM5MDE0Njc5ZTI2MmM=?u=446abbd5-9565-4e48-9bde-723335ef117f" alt="Managed Computer Object"></p>
<p>Now, if you're familiar with the Configuration Manager you should see some parallels there, and that makes sense, because these objects are the ones Configuration Manager is working with.</p>
<p>Let's say it's time for you to change the service account and password for your SQL Server instance. Using this model, we have our guide. We need to create a new ManagedComputer object, connect to the server, connect to the service, use the SetServiceAccount() method to set the new values, then restart the service. In this example I'll also restart the Agent service since I'm resetting the SQL Server instance service account.</p>
<pre>[System.Reflection.Assembly]::LoadWithPartialName("Microsoft.SqlServer.SqlWmiManagement") | Out-Null
$mc = new-object Microsoft.SqlServer.Management.Smo.Wmi.ManagedComputer localhost
$sqlinst = $mc.Services['MSSQLSERVER']
$sqlagnt = $mc.Services['SQLSERVERAGENT']
$sqlinst.SetServiceAccount('TESTDOMAIN\AlternateAcct','L44HhRMeF25UDvQeJTj5UqyE')
$sqlinst.Alter()
$sqlinst.Stop()
start-sleep -s 10
$sqlinst.Start()
$sqlagnt.Start()
</pre>
<p>Let's say I just want to see the services on my local instance, like I do in Configuration Manager. Again, that's pretty easy.</p>
<pre>[System.Reflection.Assembly]::LoadWithPartialName("Microsoft.SqlServer.SqlWmiManagement") | Out-Null
$mc = new-object Microsoft.SqlServer.Management.Smo.Wmi.ManagedComputer localhost
$mc.Services | select Name, ServiceState, DisplayName, ServiceAccount | format-table
</pre>
<p>There are times you'll need to work with the network protocols, and I've actually used the following code to change the IP port for an instance after an install, because it HAD to match a particular value.</p>
<pre>$mc = new-object ('Microsoft.SqlServer.Management.Smo.WMI.ManagedComputer') localhost
$i=$mc.ServerInstances['MSSQLSERVER']
$p=$i.ServerProtocols['Tcp']
$ip=$p.IPAddresses['IPAll']
$ip.IPAddressProperties['TcpDynamicPorts'].Value = '1099'
$p.Alter()
</pre>
<p>Now, why is this important, if I can do all this in Configuration Manager? Well, SQL Server 2012 supports installation on Windows Server Core, and there's no GUI on a Core server, so this becomes the best way to access the objects you normally manage with Configuration Manager. More importantly, it allows you to build a set of scripts to "just handle" any number of problems, without trying to remember what submenu gives you access to what property to change to solve your problem.</p>
<p>Good luck, and thanks, Wayne, for hosting this month's T-SQL Tuesday!</p>
<p>Allen</p>Behind the scenes of PowerShell and SQLhttp://sqlblog.com/blogs/rob_farley/archive/2013/02/11/behind-the-scenes-of-powershell-and-sql.aspxTue, 12 Feb 2013 00:11:46 GMT21093a07-8b3d-42db-8cbf-3350fcbf5496:47649rob_farley<p>Every year, PowerShell increases its stranglehold on the Windows Server system and the applications that run upon it – with good reason too. Its consistent mechanisms for interaction between its scripting interface and the underlying systems make it easy for people to feel comfortable, and there is a discoverability that has been lacking in many other scripting environments.</p> <p>Of course, SQL Server hasn’t been overlooked at all, and it’s coming up to five years since the <a href="https://msmvps.com/blogs/robfarley/archive/2008/03/04/sql-server-2008-powershell-snapin.aspx" target="_blank">SnapIns were made available</a> (even longer since people started to <a href="http://msmvps.com/blogs/robfarley/archive/2007/01/05/powershell-and-sql.aspx" target="_blank">dabble with SQL using PowerShell</a>).</p> <p>But what’s going on behind the scenes? Does PowerShell present a threat to those amongst us who will always prefer T-SQL? Does PowerShell give us new options that are not available any other way? Well, let’s have a bit of a look, especially since this month’s T-SQL Tuesday (hosted by <a href="http://blog.waynesheffield.com/wayne/archive/2013/02/invitation-for-t-sql-tuesday-39-can-you-shell-what-the-posh-is-cooking/" target="_blank">Wayne Sheffield</a> who tweets as <a href="http://twitter.com/DBAWayne" target="_blank">@DBAWayne</a>) is on the topic of PowerShell.</p> <p><a href="http://blog.waynesheffield.com/wayne/archive/2013/02/invitation-for-t-sql-tuesday-39-can-you-shell-what-the-posh-is-cooking/" target="_blank"><img style="background-image:none;border-bottom:0px;border-left:0px;margin:5px;padding-left:0px;padding-right:0px;display:inline;float:right;border-top:0px;border-right:0px;padding-top:0px;" title="TSQL2sDay150x150" border="0" alt="TSQL2sDay150x150" align="right" src="http://sqlblog.com/blogs/rob_farley/TSQL2sDay150x150_41BF631A.jpg" width="170" height="170" /></a></p> <p>So we know PowerShell is useful. However we spin it up, we can quickly jump into writing commands, whether it be interacting with WMI, hooking into some .Net assembly we’ve loaded up, or simply browsing the file system. I’ve developed a tendency to use it to start whichever SQL instances I’m needing for the day – by default I have all of them turned off, since I don’t know which one I’ll be wanting most.</p> <p>If we’re going to be interacting with SQL, then it’s easiest to either load up the SQLPS environment directly (there’s a PowerShell shortcut within Management Studio), or else (as I do), start a PowerShell window with the Snapin loaded. I prefer this later option, as the SQLPS environment is a slightly cut-back version of PowerShell. But either way – the stuff I’ll continue on with is essentially the same whichever environment you use.</p> <p>If you’ve talked about SQL with me long enough, you’ll have come across the fact that I often use SQL Profiler when I’m curious about where to find information. My former colleague <a href="http://www.jimmcleod.net" target="_blank">Jim McLeod</a> (<a href="http://twitter.com/jim_mcleod" target="_blank">@Jim_McLeod</a>) blogged <a href="http://www.jimmcleod.net/blog/index.php/2012/08/14/t-sql-tuesday-33-trick-shots/" target="_blank">about this a few months ago</a>, with an example that I remember looking through with him four or five years ago. It’s a great technique that works on all kinds of things, even across different versions of SQL Server. It also adds as a terrific reminder that Management Studio is not a special application, it simply knows how to ask for the pieces of information that it shows.</p> <p>But PowerShell (or SMO, for that matter), that’s in the .Net world. Surely that would be able to bypass the clunky T-SQL stuff that Management Studio does... I mean, Management Studio has to be flexible enough to work across remote servers, talking through firewalls that only allow T-SQL interfaces. Surely PowerShell has access to a deeper magic.</p> <p>Well, no. PowerShell still lets you talk to remote servers, and ends up using the same methods.</p> <p>Let’s prove it.</p> <p>Spin up Profiler, and start a trace against your favourite instance. I like to watch for SQL:BatchCompleted, SP:StmtCompleted and RPC:Completed events when doing this kind of thing. I’m using an instance that isn’t doing anything else, but you could apply a ColumnFilter to filter the events to things with an ApplicationName starting with SQLPS if you prefer.</p> <p>With that running, I jump into PowerShell and do something like:</p> <blockquote> <p><font face="Consolas">PS SQLSERVER:\sql\localhost\sql2008r2&gt; dir Databases | ft name</font></p> </blockquote> <p>This lists the names of the databases on my SQL2008R2 instances. You don’t need to see the results, you can imagine them for yourself.</p> <p>If PowerShell were using some secret interface, it’s unlikely we’d see something in Profiler. But it’s not, and we see a bunch of stuff.</p> <p><img style="background-image:none;border-bottom:0px;border-left:0px;padding-left:0px;padding-right:0px;display:inline;border-top:0px;border-right:0px;padding-top:0px;" title="image" border="0" alt="image" src="http://sqlblog.com/blogs/rob_farley/image_56ED68C2.png" width="672" height="468" /></p> <p>We see a bunch of Remote Procedure Calls, each with a Stored Procedure Statement Completed event showing the same information. And look – we see queries against master.sys.databases, asking for the name of each of the databases, passing in the name as a parameter. Brilliant! Notice just a bit earlier though, there’s a SQL:BatchCompleted call. This means that a query has been passed in directly. It’s this:</p> <blockquote> <p><font face="Consolas">SELECT <br />CAST( <br />&#160;&#160;&#160;&#160;&#160;&#160;&#160; serverproperty(N'Servername') <br />&#160;&#160;&#160;&#160;&#160;&#160; AS sysname) AS [Server_Name], <br />dtb.name AS [Name] <br />FROM <br />master.sys.databases AS dtb <br />WHERE <br />(CAST(case when dtb.name in ('master','model','msdb','tempdb') then 1 else dtb.is_distributor end AS bit)=0) <br />ORDER BY <br />[Name] ASC</font></p> </blockquote> <p><img style="background-image:none;border-bottom:0px;border-left:0px;padding-left:0px;padding-right:0px;display:inline;border-top:0px;border-right:0px;padding-top:0px;" title="image" border="0" alt="image" src="http://sqlblog.com/blogs/rob_farley/image_3EB9D89A.png" width="646" height="432" /></p> <p>So it grabs the list of database names first, and then makes extra calls to be able to fetch the list of names again, one by one.</p> <p>The reason why it’s grabbing the list of names one by one isn’t because it’s stupid and is asking to be ridiculed. It’s because we've asked to see that property, and I guess the PowerShell people figured that no matter what property you ask for, it’ll go and fetch it to show you.</p> <p>When I asked for the CompatibilityLevel property instead, I got some different rows thrown in. Interestingly though, it still asked for the name each time.</p> <p><img style="background-image:none;border-bottom:0px;border-left:0px;padding-left:0px;padding-right:0px;display:inline;border-top:0px;border-right:0px;padding-top:0px;" title="image" border="0" alt="image" src="http://sqlblog.com/blogs/rob_farley/image_3862DC3F.png" width="635" height="437" /></p> <p>Also interestingly, when I asked for the CompatibilityLevel a subsequent time, the calls for “SELECT dtb.compatibility_level…” weren’t in there. They’d been cached by the PowerShell environment – important to note if you ever come across PowerShell giving you old values.</p> <p>So what about asking something more interesting? Let’s try asking about the IndexSpaceUsage in AdventureWorks.</p> <blockquote> <p><font face="Consolas">PS SQLSERVER:\sql\localhost\sql2008r2&gt; gi Databases\AdventureWorks | ft IndexSpaceUsage</font></p> </blockquote> <p>The result tells me it’s 62576. Yeah, but today I’m not interested in that, just what happened in the trace.</p> <p>Four entries. An SP:StmtCompleted with matching RPC:Completed, and two SQL:BatchCompleted.</p> <p>The SP:StmtCompleted and RPC:Completed were this statement, passing in the parameter value ‘AdventureWorks’. Clearly incredibly informative.</p> <blockquote> <p><font face="Consolas">SELECT <br />CAST(0 AS float) AS [IndexSpaceUsage], <br />dtb.name AS [DatabaseName] <br />FROM <br />master.sys.databases AS dtb <br />WHERE <br />(dtb.name=@_msparam_0)</font></p> </blockquote> <p>This is getting the value zero. Wow. Brilliant stuff.</p> <p>The last entry – the second of the two SQL:BatchCompleted events is:</p> <blockquote> <p><font face="Consolas">select convert(float,low/1024.) from master.dbo.spt_values where number = 1 and type = 'E'</font></p> </blockquote> <p>If you run this in Management Studio, you’ll discover it gives the value 8. Ok.</p> <p>The other entry is more interesting.</p> <blockquote> <p><font face="Consolas">use [AdventureWorks] <br />SELECT <br />SUM(CASE WHEN a.type &lt;&gt; 1 THEN a.used_pages WHEN p.index_id &lt; 2 THEN a.data_pages ELSE 0 END) AS [DataSpaceUsage], <br />SUM(a.used_pages) AS [IndexSpaceTotal] <br />FROM <br />sys.allocation_units AS a INNER JOIN sys.partitions AS p ON (a.type = 2 AND p.partition_id = a.container_id) OR (a.type IN (1,3) AND p.hobt_id = a.container_id)</font></p> </blockquote> <p>This is more like it! We run this in Management Studio, and we see two values. DataSpaceUsage is 13682, IndexSpaceTotal is 21504. Neither are our value 62576. But we do have clues in the column names, and in that value 8 that came back too. We can easily deduce that it’s actually (IndexSpaceTotal-DataSpaceUsage)*8, and we have ourselves a nice little method for working out the IndexSpaceUsage ourselves now if we need it.</p> <p>Or we can just ask PowerShell next time as well.</p> <p>Incidentally – if you’re considering doing the MCM Lab exam some time, then you might find that a familiarity with PowerShell comes in really handy. I’m not saying there are PowerShell questions on the exam at all – I’m just suggesting that you may find that PowerShell becomes a really useful way of getting at some of the information that you’re looking for. If you’re stumbling around the list of DMVs trying to remember which one it is that stores some particular thing, remember that you might be able to get the data out more easily if you use PowerShell instead.</p> <p>So can we discover secret things about SQL from PowerShell? Are there things we can do in PowerShell that are impossible through other mechanisms? Hooks that let us break the rules even?</p> <p>Recently, Kendal van Dyke asked a question about this kind of thing on Twitter. He was wondering if you could have a default constraint on a column in a view. The reason for his wondering was that he saw a property on a view column in PowerShell that made him wonder. The answer is no though, and there’s a simple reason.</p> <p>PowerShell is a programmatic interface. It involves classes and property and methods. It does things row by row, which is why much of what you see in that trace feels amazingly pedantic – asking about things which shouldn’t have to be that complicated. The implication of this though, is that PowerShell reuses the concept of a column, regardless of whether this is a column in a table, a view, or anywhere else it decides to need a column. The fact that columns in tables have some extra properties isn’t enough to make this class re-use pointless. If we try to set a Default constraint for a column in a view though, we get an error, just like if we tried to do it any other way.</p> <p>The PowerShell I used was:</p> <blockquote> <p><font face="Consolas">$db = Get-Item SQLSERVER:\sql\localhost\sql2008r2\Databases\AdventureWorks <br /></font><font face="Consolas">$def = New-Object -TypeName Microsoft.SqlServer.Management.SMO.Default –ArgumentList $db, &quot;ViewDefault&quot; <br /></font><font face="Consolas">$def.TextHeader = &quot;CREATE DEFAULT ViewDefault AS&quot; <br />$def.TextBody = &quot;'ABC'&quot; <br /></font><font face="Consolas">$def.Create() <br /></font><font face="Consolas">$def.BindToColumn(&quot;vStateProvinceCountryRegion&quot;,&quot;StateProvinceCode&quot;,&quot;Person&quot;)</font></p> </blockquote> <p>The code that ended up getting called was to the stored procedure <em>sp_bindefault</em> (despite it being deprecated). Naturally, trying to execute this against a view column gives an error regardless of what wrappers you have put around it – PowerShell or not.</p> <p><img style="background-image:none;border-bottom:0px;border-left:0px;padding-left:0px;padding-right:0px;display:inline;border-top:0px;border-right:0px;padding-top:0px;" title="image" border="0" alt="image" src="http://sqlblog.com/blogs/rob_farley/image_125D061C.png" width="813" height="132" /></p> <p>So PowerShell is very useful, and it provides a handy way of getting to a lot of things that could be otherwise hard. But looking below the surface, it isn’t able to circumvent the basic restrictions of SQL Server, because it still ends up doing its work using T-SQL.</p> <p><a href="http://twitter.com/rob_farley" target="_blank">@rob_farley</a></p>T-SQL Tuesday #15 : Running T-SQL workloads remotely on multiple servershttp://sqlblog.com/blogs/aaron_bertrand/archive/2011/02/08/t-sql-tuesday-15-running-t-sql-workloads-remotely-on-multiple-servers.aspxTue, 08 Feb 2011 19:10:00 GMT21093a07-8b3d-42db-8cbf-3350fcbf5496:33316AaronBertrand<p>&nbsp;</p><table cellpadding="10" cellspacing="0">
<tr><td><a href="http://sqlasylum.wordpress.com/2011/02/01/invitation-to-t-sql-tuesday-15-automation-in-sql-server/" title="http://sqlasylum.wordpress.com/2011/02/01/invitation-to-t-sql-tuesday-15-automation-in-sql-server/" target="_blank"><img src="http://sqlblog.com/files/folders/30073/download.aspx" align="left" border="0" height="154" hspace="22" width="154"></a></td><td>This month's installment of T-SQL Tuesday is hosted by Pat Wright (<a href="http://sqlasylum.wordpress.com/" title="http://sqlasylum.wordpress.com/" target="_blank">blog</a> | <a href="http://twitter.com/SQLAsylum" title="http://twitter.com/SQLAsylum" target="_blank">twitter</a>). Pat says: <span style="font-style:italic;">"So the topic I have chosen for this month is Automation! It can be Automation</span><span style="font-style:italic;">
with T-SQL or with Powershell or a mix of both. Give us your best
tips/tricks and ideas for making our lives easier through Automation."</span>
<p>In a recent project, we've had a need to run concurrent workloads on as many as 100 instances of SQL Server in a test environment. A goal, obviously, is to accomplish this without having to go and manually run the load tests on each of the 100 servers. Another goal, since this is for the purposes of measuring overhead (including network load), is to not pollute the tests with thousands of RPC and batch calls going over the network. This means trying very hard to keep as much of the work "local" as possible.<br></p><br></td></tr>
</table><p>Now, we could set up SQL Server Agent jobs on each server that run the
local workloads. That would eliminate the impact of network overhead
that would be caused by running the loads remotely, but one of the goals
is to minimize not only network overhead but also the overhead on the
machines themselves - Agent would add overhead just by running the jobs,
never mind adding all that history and cleanup for msdb. We wanted the
impact on the local machine, compared to sitting idle, to represent only
the stress caused by the commands themselves, to isolate that from the
overhead caused by how we chose to run the commands.</p>
So, we turned to PowerShell remoting, which allows you to run
PowerShell commands from a different server just as if you were running
locally. <p>&nbsp;<font size="4"><br>Getting the Environment Ready </font><br>
</p><font size="4"></font><p>As I described earlier, we've had several occasions where we need to run workloads against 100 instances of SQL Server.&nbsp; We build these VMs using PowerShell, and to ease scripting and other automation around them, give each VM a name in a numerical sequence: VM-SQL-001, VM-SQL-002, ..., VM-SQL-099, VM-SQL-100. Building out the VMs using a template, with a default SQL Server instance already installed, is relatively straightforward in Hyper-V; the only tricky part is that we had to write a script to change @@SERVERNAME, using sp_dropserver/sp_addserver, on all of the SQL Server instances (they all have unique Windows names, but SQL Server itself starts with the server name of the original template).<br></p>
<p>After building the 100 VMs with PowerShell, coming up with a .sql script that would run a suitable workload, and then using PowerShell to blast those .sql files to all 100 VMs (we could have pulled a single script over the network at runtime, but that would add overhead), we were ready to go.&nbsp; But first, in order to use remoting, you have to do the following things:</p>
<blockquote><b><br>Configure WinRM</b> <br></blockquote>
<blockquote>
<p>Luckily, we are standardized on Windows Server 2008 R2, so we did not have to install WinRM.&nbsp; If you are on Windows XP or Vista, or Windows Server 2003, 2003 R2, or 2008, you will need to install WinRM before proceeding:</p>
<blockquote>
<p><a href="http://support.microsoft.com/kb/968930%20" title="http://support.microsoft.com/kb/968930 " target="_blank">KB #968930 : Windows Management Framework Core package (Windows PowerShell 2.0 and WinRM 2.0)</a><br></p>
</blockquote>
<p>The install is simple, though you'll likely need to reboot.&nbsp; Configuring the service to start automatically is part of the next step.<br></p>
<p><br><b>Enable remoting</b></p>
<p>In PowerShell, remoting is not enabled by default.&nbsp; In order to turn WinRM on, keep it on, and set up an automatic firewall extension, you need to run the following command from a PowerShell prompt:</p>
<blockquote>
<table bgcolor="#eeeeee" cellpadding="0" cellspacing="0">
<tr>
<td>
<pre style="padding:10px 20px;font-size:12px;font-family:consolas,lucida console,courier new,courier;-moz-background-inline-policy:continuous;"><font color="#0000ff">Enable-PSRemoting</font><font><font color="#000000">;</font></font>
</pre></td>
</tr>
</table>
</blockquote>
<p><b><br>Set TrustedHosts</b></p>
<p>So that other servers can connect and run commands, you need to "let them in" ... this works kind of like a firewall exception.&nbsp; You can of course be more restrictive than using * (which lets all systems in) but this will depend on the existing security in your environment.<br></p>
<blockquote>
<table bgcolor="#eeeeee" cellpadding="0" cellspacing="0">
<tr>
<td>
<pre style="padding:10px 20px;font-size:12px;font-family:consolas,lucida console,courier new,courier;-moz-background-inline-policy:continuous;"><font color="#0000ff">Set-Item</font> <font color="#8a2be2">WSMan:\localhost\Client\TrustedHosts</font> <font color="#8a2be2">*</font>
<font color="#0000ff">Restart-Service</font> <font color="#8a2be2">WinRM</font><font><font color="#000000">;</font></font>
</pre></td>
</tr>
</table>
</blockquote>
<p>For the virtual machines we created, this step had already been performed
in the VM that served as the Hyper-V template, so the process did not need to be repeated 100 times.&nbsp; If you
need to monitor servers that already exist, you'll need to enable
remoting and set TrustedHosts on each of them.&nbsp; Thankfully, you'll only ever have to do this once; from then on, you can accomplish just about anything using PowerShell remotely.</p>
</blockquote>
<p>&nbsp;<br><font size="4">The Script(s) to Rule Them All</font></p>
<p>Once the environment was configured, we would need a total of three scripts - and one command line call - to run the workload against anywhere from 1 to 100 SQL Server instances.&nbsp; The three scripts are:</p>
<ul>
<li>A .sql script with the actual commands to run; actually, 100 copies of this, located in C:\scripts\ on each VM. Make sure that all T-SQL commands have proper statement terminators (;).<br></li>
<li>A .ps1 script that will be run remotely and load each local .sql script to execute. Again, this needs to be located in C:\scripts\ or a similar local folder on each VM.*<br></li>
<li>A single .psm1 script on the local machine that will load as a module and start the sessions / jobs to fire each .ps1 script on each VM in turn.<br></li>
</ul>
<blockquote><p><i>* This is probably not a strict requirement, but I had a hard time getting it to work without distributing both files. The .ps1 script can certainly be local if the SQL commands you're sending are defined within the file, as opposed to being an external resource. But having bulky T-SQL inside a function can make for a very unmanageable and unreadable script.</i><br></p></blockquote><p>The .sql script has a whole bunch of commands to generate a variety of activity on the SQL Server instances on the remote VMs - from blocks and deadlocks, to significant durations, to compiles and recompiles.&nbsp; I'm not going to show the script here because that's not the important part.&nbsp; </p>
<p>On its own, the script is only capable of generating so much load - however, when you can run multiple copies of the script in parallel, you can really make it look like an interesting and real-world workload. We accomplished this by establishing a session on each remote VM, then creating a separate job in each session, for each "thread" that we wanted to run the script.&nbsp; This is where the coolness of automation comes in.</p>
<p style="font-weight:bold;margin-left:40px;"><br>The .ps1 script</p>
<p style="margin-left:40px;">This script (RunWorkLoads.ps1) accepts two arguments: $cycles and $delay.&nbsp; $cycles represents the number of times we want to run the workload. If I wanted to run the workload for a specific amount of time, rather than provide additional timing parameters, I was content with experimenting with the workload script itself and the number of cycles in order to run for at least that duration.&nbsp; $delay is the number of milliseconds I wanted to wait in between running the command... this provides the ability to tailor the activity against the servers according to realistic application behavior.
</p>
<blockquote style="margin-left:40px;">
<table bgcolor="#eeeeee" cellpadding="0" cellspacing="0">
<tr>
<td>
<pre style="padding:10px 20px;font-size:12px;font-family:consolas,lucida console,courier new,courier;-moz-background-inline-policy:continuous;"><font color="#00008b">param</font><font color="#000000">(</font><font color="#008080">[int]</font><font color="#ff4500">$cycles</font><font color="#a9a9a9">,</font> <font color="#008080">[int]</font><font color="#ff4500">$delay</font><font color="#000000">)</font>
<font color="#00008b">function</font> <font color="#8a2be2">RunWorkLoads</font> <font color="#000000"><br>{</font>
<font color="#00008b"> param</font><font color="#000000"><br> (<br> </font><font color="#008080">[int]</font><font color="#ff4500">$cycles</font><font color="#a9a9a9">,</font> <font color="#008080"><br> [int]</font><font color="#ff4500">$delay</font><font color="#000000"><br> )</font>
<font color="#ff4500">$query</font> <font color="#a9a9a9">=</font> <font color="#0000ff">Get-Content</font> <font color="#8a2be2">C:\scripts\MyWorkload.sql</font><font color="#000000">;</font>
<font color="#ff4500">$query</font> <font color="#a9a9a9">=</font> <font color="#008080">[string]</font><font color="#a9a9a9">::</font><font color="#000000">join</font><font color="#000000">(</font><font color="#8b0000">" "</font><font color="#a9a9a9">,</font> <font color="#ff4500">$query</font><font color="#000000">)</font><font color="#000000">;</font>
<font color="#00008b"> for</font> <font color="#000000">(</font><font color="#ff4500">$i</font> <font color="#a9a9a9">=</font> <font color="#800080">1</font><font color="#000000">;</font> <font color="#ff4500">$i</font> <font color="#a9a9a9">-le</font> <font color="#ff4500">$cycles</font><font color="#000000">;</font> <font color="#ff4500">$i</font><font color="#a9a9a9">++</font><font color="#000000">)</font>
<font color="#000000"> {</font>
<font color="#0000ff">Start-Sleep</font> <font color="#000080">-Milliseconds</font> <font color="#ff4500">$delay</font><font color="#000000">;</font>
<font color="#0000ff"> Invoke-Sqlcmd</font> <font color="#000080">-Query</font> <font color="#ff4500">$query</font> <font color="#000080">-ServerInstance</font> <font color="#8a2be2">localhost</font> <font color="#000080">-Database</font> <font color="#8a2be2">AdventureWorks</font><font color="#000000">;</font>
<font color="#000000">}</font>
<font color="#000000">}</font>
<font color="#0000ff">LoadSQLSnapin.ps1</font>
<font color="#0000ff">RunWorkLoads</font> <font color="#000080">-cycles</font> <font color="#ff4500">$cycles</font> <font color="#000080">-delay</font> <font color="#ff4500">$delay</font><font color="#000000">;</font></pre></td>
</tr>
</table>
</blockquote>
<p style="margin-left:40px;"><br><span style="font-weight:bold;">The .psm1 script </span><br></p>
<p style="margin-left:40px;">This module (RunWorkLoadsOnVMs) loads with the PowerShell command prompt (or with the ISE), so that we can just call the function within the module without having to point PowerShell at a folder.&nbsp; This is all about reducing our work, right?&nbsp; The tricky part is getting the path information right.&nbsp; As mentioned above, the .sql and .ps1 scripts need to be on the target machines (and again, I didn't fight too hard to try to make this work without distributing the files - though I'm sure a solution exists).&nbsp; Coming back to the automation argument, how easy is it to copy these scripts over to the target machines?&nbsp; With the naming scheme we chose (VM-SQL-001 -&gt; VM-SQL-100), it is quite easy:</p>
<blockquote style="margin-left:40px;">
<table bgcolor="#eeeeee" cellpadding="0" cellspacing="0">
<tr>
<td>
<pre style="padding:10px 20px;font-size:12px;font-family:consolas,lucida console,courier new,courier;-moz-background-inline-policy:continuous;"><font color="#00008b">for</font> <font color="#000000">(</font><font color="#ff4500">$i</font> <font color="#a9a9a9">=</font> <font color="#800080">1</font><font color="#000000">;</font> <font color="#ff4500">$i</font> <font color="#a9a9a9">-le</font> <font color="#800080">1</font><font color="#000000">;</font> <font color="#ff4500">$i</font><font color="#a9a9a9">++</font><font color="#000000">)</font>
<font color="#000000">{</font>
<font color="#ff4500">$vm</font> <font color="#a9a9a9">=</font> <font color="#8b0000">"VM-SQL-"</font><font color="#000000">;</font>
<font color="#ff4500">$x</font> <font color="#a9a9a9">=</font> <font color="#8b0000">"000"</font> <font color="#a9a9a9">+</font> <font color="#000000">(</font><font color="#ff4500">$i</font> <font color="#a9a9a9">+</font> <font color="#800080">1</font><font color="#000000">)</font><font color="#a9a9a9">.</font><font color="#000000">ToString</font><font color="#000000">(</font><font color="#000000">)</font><font color="#000000">;</font>
<font color="#ff4500">$vm</font> <font color="#a9a9a9">+=</font> <font color="#ff4500">$x</font><font color="#a9a9a9">.</font><font color="#000000">Substring</font><font color="#000000">(</font><font color="#ff4500">$x</font><font color="#a9a9a9">.</font><font color="#000000">Length</font> <font color="#a9a9a9">-</font> <font color="#800080">3</font><font color="#a9a9a9">,</font> <font color="#800080">3</font><font color="#000000">)</font><font color="#000000">;</font>
<font color="#0000ff">Copy-Item</font> <font color="#8a2be2">C:\Scripts\RunWorkLoads.ps1</font> <font color="#8b0000">"\\$vm\C$\Scripts\"</font><font color="#000000">;</font>
<font color="#000000">}</font></pre></td>
</tr>
</table>
</blockquote>
<p style="margin-left:40px;">One other tricky part you may come across is the need to use <a href="http://download.microsoft.com/download/9/5/E/95EF66AF-9026-4BB0-A41D-A4F81802D92C/%5BMS-CSSP%5D.pdf" title="http://download.microsoft.com/download/9/5/E/95EF66AF-9026-4BB0-A41D-A4F81802D92C/%5BMS-CSSP%5D.pdf" target="_blank">CredSSP</a> to pass the credentials of a domain user with the rights to run commands against the remote servers.&nbsp; In our case, this meant that when starting the command we would need to enter the password for the domain user once (but, hey, not 100 times - PowerShell++).</p>
<p style="margin-left:40px;">The module takes these four parameters: $vmCount (the number of VMs you want to run load tests against), $threads (the number of threads you want on each machine), $cycles and $delay (both described above).&nbsp; It uses multi-dimensional arrays to keep track of the job and session for each thread on each machine.&nbsp; Note that I would normally use much more descriptive variable names, but was trying to make sure the script fit on this page horizontally.&nbsp; I am thankful that I didn't have to learn about these session and job nuances the hard way; this was actually a substantial amount of work on the part of Greg Gonzalez (<a href="http://greg.blogs.sqlsentry.net/" title="http://greg.blogs.sqlsentry.net/" target="_blank">blog</a> | <a href="http://twitter.com/SQLsensei" title="http://twitter.com/SQLsensei" target="_blank">twitter</a>):</p>
<blockquote style="margin-left:40px;">
<table bgcolor="#eeeeee" cellpadding="0" cellspacing="0">
<tr>
<td>
<pre style="padding:10px 20px;font-size:12px;font-family:consolas,lucida console,courier new,courier;-moz-background-inline-policy:continuous;"><font color="#00008b">function</font> <font color="#8a2be2">RunWorkLoadsOnVMs</font>
<font color="#000000">{</font>
<font color="#00008b">param</font>
<font color="#000000">(</font>
<font color="#008080"> [int]</font><font color="#ff4500">$vmCount</font> <font color="#a9a9a9">=</font> <font color="#800080">1</font><font color="#a9a9a9">,</font>
<font color="#008080"> [int]</font><font color="#ff4500">$threads</font> <font color="#a9a9a9">=</font> <font color="#800080">1</font><font color="#a9a9a9">,</font>
<font color="#008080"> [int]</font><font color="#ff4500">$cycles</font> <font color="#a9a9a9">=</font> <font color="#800080">150</font><font color="#a9a9a9">,</font>
<font color="#008080"> [int]</font><font color="#ff4500">$delay</font> <font color="#a9a9a9">=</font> <font color="#800080">1000</font>
<font color="#000000">)</font>
<font color="#008080">[int]</font><font color="#ff4500">$vmIdMax</font> <font color="#a9a9a9">=</font> <font color="#000000">(</font><font color="#ff4500">$vmCount</font> <font color="#a9a9a9">-</font> <font color="#800080">1</font><font color="#000000">)</font><font color="#000000">;</font>
<font color="#008080">[int]</font><font color="#ff4500">$SIdMax</font> <font color="#a9a9a9">=</font> <font color="#000000">(</font><font color="#ff4500">$threads</font> <font color="#a9a9a9">-</font> <font color="#800080">1</font><font color="#000000">)</font><font color="#000000">;</font>
<font color="#ff4500">$S</font> <font color="#a9a9a9">=</font> <font color="#0000ff">New-Object</font> <font color="#8b0000">'object[,]'</font> <font color="#ff4500">$vmCount</font><font color="#a9a9a9">,</font> <font color="#ff4500">$threads</font><font color="#000000">;</font>
<font color="#ff4500">$J</font> <font color="#a9a9a9">=</font> <font color="#0000ff">New-Object</font> <font color="#8b0000">'object[,]'</font> <font color="#ff4500">$vmCount</font><font color="#a9a9a9">,</font> <font color="#ff4500">$threads</font><font color="#000000">;</font>
<font color="#ff4500">$C</font> <font color="#a9a9a9">=</font> <font color="#0000ff">Get-Credential</font> <font color="#000080">-credential</font> <font color="#8a2be2">domain\user</font><font color="#000000">;</font> <font color="#006400"># will raise credential prompt</font>
<font color="#ff4500">$P</font> <font color="#a9a9a9">=</font> <font color="#8b0000">"C:\Scripts\RunWorkLoads.ps1"</font><font color="#000000">;</font>
<font color="#00008b">for</font> <font color="#000000">(</font><font color="#008080">[int]</font><font color="#ff4500">$vmId</font> <font color="#a9a9a9">=</font> <font color="#800080">0</font><font color="#000000">;</font> <font color="#ff4500">$vmId</font> <font color="#a9a9a9">-le</font> <font color="#ff4500">$vmIdMax</font><font color="#000000">;</font> <font color="#ff4500">$vmId</font><font color="#a9a9a9">++</font><font color="#000000">)</font>
<font color="#000000">{</font>
<font color="#ff4500">$vm</font> <font color="#a9a9a9">=</font> <font color="#8b0000">"VM-SQL-"</font><font color="#000000">;</font>
<font color="#ff4500">$x</font> <font color="#a9a9a9">=</font> <font color="#8b0000">"000"</font> <font color="#a9a9a9">+</font> <font color="#000000">(</font><font color="#ff4500">$vmId</font> <font color="#a9a9a9">+</font> <font color="#800080">1</font><font color="#000000">)</font><font color="#a9a9a9">.</font><font color="#000000">ToString</font><font color="#000000">(</font><font color="#000000">)</font><font color="#000000">;</font>
<font color="#ff4500">$vm</font> <font color="#a9a9a9">+=</font> <font color="#ff4500">$x</font><font color="#a9a9a9">.</font><font color="#000000">Substring</font><font color="#000000">(</font><font color="#ff4500">$x</font><font color="#a9a9a9">.</font><font color="#000000">Length</font> <font color="#a9a9a9">-</font> <font color="#800080">3</font><font color="#a9a9a9">,</font> <font color="#800080">3</font><font color="#000000">)</font><font color="#000000">;</font>
<font color="#00008b">for</font> <font color="#000000">(</font><font color="#008080">[int]</font><font color="#ff4500">$SId</font> <font color="#a9a9a9">=</font> <font color="#800080">0</font><font color="#000000">;</font> <font color="#ff4500">$SId</font> <font color="#a9a9a9">-le</font> <font color="#ff4500">$SIdMax</font><font color="#000000">;</font> <font color="#ff4500">$SId</font><font color="#a9a9a9">++</font><font color="#000000">)</font>
<font color="#000000"> {</font>
<font color="#0000ff"> Write-Host</font> <font color="#8b0000">"Starting job $SId on $vm..."</font><font color="#000000">;</font>
<font color="#ff4500"> $S</font><font color="#a9a9a9">[</font><font color="#ff4500">$vmId</font><font color="#a9a9a9">,</font> <font color="#ff4500">$SId</font><font color="#a9a9a9">]</font> <font color="#a9a9a9">=</font> <font color="#0000ff">New-PSSession</font> <font color="#000080">-ComputerName</font> <font color="#ff4500">$vm</font> <font color="#000080">-Name</font> <font color="#8b0000">"S$vmId-$sessId"</font> <font color="#000080">-Credential</font> <font color="#ff4500">$C</font> <font color="#000080">-Authentication</font> <font color="#8a2be2">CredSSP</font><font color="#000000">;</font>
<font color="#00008b"> if</font> <font color="#000000">(</font><font color="#ff4500">$S</font><font color="#a9a9a9">[</font><font color="#ff4500">$vmId</font><font color="#a9a9a9">,</font> <font color="#ff4500">$SId</font><font color="#a9a9a9">]</font> <font color="#a9a9a9">-ne</font> <font color="#ff4500">$null</font><font color="#000000">)</font>
<font color="#000000"> {</font> <br><font color="#ff4500"> $jn</font> <font color="#a9a9a9">= </font><font color="#8b0000">"J$vmId-$SId"</font><font><font color="#000000">;</font></font>
<font color="#ff4500"> $J</font><font color="#a9a9a9">[</font><font color="#ff4500">$vmId</font><font color="#a9a9a9">,</font> <font color="#ff4500">$SId</font><font color="#a9a9a9">]</font> <font color="#a9a9a9">=</font> <font color="#0000ff">Invoke-Command</font> <font color="#000080">-Session</font> <font color="#ff4500">$S</font><font color="#a9a9a9">[</font><font color="#ff4500">$vmId</font><font color="#a9a9a9">,</font> <font color="#ff4500">$SId</font><font color="#a9a9a9">]</font> <font color="#000080">-FilePath</font> <font color="#ff4500">$P</font> <font color="#000080">-AsJob</font> <font color="#000080">-JobName</font> <font color="#ff4500">$jn</font> <font color="#000080">-ArgumentList</font> <font color="#ff4500">$cycles</font><font color="#a9a9a9">,</font> <font color="#ff4500">$delay</font><font color="#000000">;</font>
<font color="#000000"> }</font>
<font color="#000000"> }</font>
<font color="#000000">}</font>
<font color="#0000ff">Write-Host</font> <font color="#8b0000">"Waiting for jobs to complete..."</font><font color="#000000">;</font>
<font color="#0000ff">Get-Job</font> <font color="#a9a9a9">|</font> <font color="#0000ff">Wait-Job</font><font color="#000000">;</font>
<font color="#00008b">for</font> <font color="#000000">(</font><font color="#008080">[int]</font><font color="#ff4500">$vmId</font> <font color="#a9a9a9">=</font> <font color="#800080">0</font><font color="#000000">;</font> <font color="#ff4500">$vmId</font> <font color="#a9a9a9">-le</font> <font color="#ff4500">$vmIdMax</font><font color="#000000">;</font> <font color="#ff4500">$vmId</font><font color="#a9a9a9">++</font><font color="#000000">)</font>
<font color="#000000">{</font>
<font color="#00008b">for</font> <font color="#000000">(</font><font color="#008080">[int]</font><font color="#ff4500">$SId</font> <font color="#a9a9a9">=</font> <font color="#800080">0</font><font color="#000000">;</font> <font color="#ff4500">$SId</font> <font color="#a9a9a9">-le</font> <font color="#ff4500">$SIdMax</font><font color="#000000">;</font> <font color="#ff4500">$SId</font><font color="#a9a9a9">++</font><font color="#000000">)</font>
<font color="#000000">{</font>
<font color="#00008b">if</font> <font color="#000000">(</font><font color="#ff4500">$J</font><font color="#a9a9a9">[</font><font color="#ff4500">$vmId</font><font color="#a9a9a9">,</font> <font color="#ff4500">$SId</font><font color="#a9a9a9">]</font><font color="#a9a9a9">.</font><font color="#000000">State</font> <font color="#a9a9a9">-eq</font> <font color="#8b0000">"Failed"</font> <font color="#000000">)</font>
<font color="#000000">{</font>
<font color="#0000ff">Get-Job</font> <font color="#000080">-Name</font> <font color="#ff4500">$J</font><font color="#a9a9a9">[</font><font color="#ff4500">$vmId</font><font color="#a9a9a9">,</font> <font color="#ff4500">$SId</font><font color="#a9a9a9">]</font><font color="#a9a9a9">.</font><font color="#000000">Name</font><font color="#000000">;</font>
<font color="#000000">}</font>
<font color="#000000">}</font>
<font color="#000000">}</font>
<font color="#ff4500">$ErrorActionPreference</font> <font color="#a9a9a9">=</font> <font color="#8b0000">"Continue"</font><font color="#000000">;</font>
<font color="#0000ff">Write-Host</font> <font color="#8b0000">"Cleaning up sessions..."</font><font color="#000000">;</font>
<font color="#0000ff">Get-PSSession</font> <font color="#a9a9a9">|</font> <font color="#0000ff">Remove-PSSession</font><font color="#000000">;</font>
<font color="#0000ff">Write-Host</font> <font color="#8b0000">"Cleaning up jobs..."</font><font color="#000000">;</font>
<font color="#0000ff">Get-Job</font> <font color="#a9a9a9">|</font> <font color="#0000ff">Remove-Job</font><font color="#000000">;</font>
<font color="#000000">}</font>
<font color="#0000ff">Write-Host</font> <font color="#8b0000">"RunWorkLoadsOnVMs module loaded."</font><font color="#000000">;</font></pre></td>
</tr>
</table>
</blockquote>
<p style="margin-left:40px;">And here is a sample call with the resulting credential prompt:</p>
<blockquote>
</blockquote>
<p style="margin-left:40px;"><img src="http://sqlblog.com/files/folders/33312/download.aspx" border="0" height="420" width="776">&nbsp;</p>
<blockquote>
</blockquote>
<p>A word of caution: always let the script finish and perform its own cleanup.&nbsp; If you hit Ctrl+C while these jobs are running, you will have to go back and clean up the jobs and sessions yourself.&nbsp; PowerShell may make this a bit easier than it could be, but it's still going to be a pain. <br></p>
<p><br><font size="4">Conclusion</font> <br></p>
<p>Are there 3rd party tools to allow for some of this functonality? Sure. There are all kinds of stress testing tools available (even several free ones), and we could have chosen one of dozens of existing methods to accomplish our goals. But we did not come across any that provided all of the flexibility we needed to facilitate the type of testing we wanted - never mind in a single command line.</p><p>For load testing in a simulated environment, our naming scheme made it very easy to scale my tests to any number of the 100 VMs.&nbsp; If you have no control over the server names, there wouldn't be much you'd have to change - instead of looping through a simple counter, you'd have to pull the list of server names from a source somewhere, such as a text file.<br></p>
<p>Many thanks again to Greg Gonzalez for doing much of the legwork and pointing me in the right direction many times throughout this project. <br></p>
<p>&nbsp; <br></p>T-SQL Tuesday #14: Resolutionshttp://sqlblog.com/blogs/aaron_bertrand/archive/2011/01/10/t-sql-tuesday-14-resolutions.aspxTue, 11 Jan 2011 02:18:00 GMT21093a07-8b3d-42db-8cbf-3350fcbf5496:32499AaronBertrand<img src="http://sqlblog.com/files/folders/30073/download.aspx" align="left" border="0" height="150" hspace="12" width="150">
<p>This month, T-SQL Tuesday is being hosted by freshly minted MVP Jen McCown (<a href="http://www.midnightdba.com/Jen/" title="http://www.midnightdba.com/Jen/" target="_blank">blog</a> | <a href="http://twitter.com/MidnightDBA" title="http://twitter.com/MidnightDBA" target="_blank">twitter</a>), and her topic is "<a href="http://www.midnightdba.com/Jen/2011/01/tsql-tuesday-014/" title="http://www.midnightdba.com/Jen/2011/01/tsql-tuesday-014/" target="_blank">Resolutions!</a>"&nbsp; I already gave a rough sort of overview on <a href="http://sqlblog.com/blogs/aaron_bertrand/archive/2010/12/30/goals-for-2011.aspx" title="http://sqlblog.com/blogs/aaron_bertrand/archive/2010/12/30/goals-for-2011.aspx" target="_blank">my goals for 2011</a>, but I thought I would be able to dig a little deeper with enough relevance to participate.&nbsp; So with that in mind, and with a goal of not setting the bar too high, here are a few of the resolutions I hope to achieve in 2011:<br> <br></p><p><font size="4">To become better at PowerShell</font></p><p>Not just because all the cool kids are doing it, but because I truly see the value in automation and scripting.&nbsp; I am currently working on an intensive project which involves running workloads, gathering performance counters, measuring wait stats, and bulk loading all of the output into a database for analysis... previously I was using a hodge-podge of PowerShell, LogMan, VBScript and SQL Server Agent, and I am begrudgingly consolidating everything to PowerShell.&nbsp; I like the results, but I am not all that comfortable with the learning curve.&nbsp; And I am finding some frustrating things missing along the way. For example, Export-CSV can only export to a new file - <a href="http://connect.microsoft.com/PowerShell/feedback/details/525407/" title="http://connect.microsoft.com/PowerShell/feedback/details/525407/" target="_blank">they forgot to support append</a>.&nbsp; There are workarounds, sure, and the bug is closed as fixed (albeit, with no comment, so I'm not sure exactly what that means).&nbsp; Still, even if we get an enhanced Export-CSV function in PowerShell 2.5 or 3.0, this seems like core functionality that should have been there from the beginning.<br><br></p>
<p><font size="4">To have faith in Connect again</font></p><p>I go through cycles... I love Connect, then I hate it, then I love it, then I hate it again.&nbsp; I'm in the not-so-positive camp right now, for a couple of reasons: (1) I'm seeing way too many easy fixes being thrown out the window for Denali, because it's too late in the cycle or because they aren't considered important enough, and (2) the quality of the communication from the product team is at an all-time low IMHO.&nbsp; Several folks have tried to encourage me to keep at it, and even to push Connect harder because now is the time that it will count... but I'm finding it difficult to come around.&nbsp; I am contemplating resurrecting last year's Connect Digest blog series, even after some MVPs kicked me in the junk about it, but I think that will come down to both time and motivation.&nbsp; For the motivation side I think I need to see a slightly better effort on Microsoft's part - no more form responses, no more closures without a comment, and no more ignoring items for three years.<br></p><p><br><font size="4">To practice my presentations more</font></p><p>Brent Ozar (<a href="http://brentozar.com/" title="http://brentozar.com/" target="_blank">blog</a> | <a href="http://twitter.com/BrentO" title="http://twitter.com/BrentO" target="_blank">twitter</a>) had a great post today, entitled "<a href="http://www.brentozar.com/archive/2011/01/how-rehearse-presentation/" title="http://www.brentozar.com/archive/2011/01/how-rehearse-presentation/" target="_blank">How to Rehearse a Presentation</a>."&nbsp; He has some great thoughts in there, and while - with all due respect - there's nothing truly rocket science about it, it is very useful to see it all in one cohesive place like that: it makes it very easy to identify which steps I'm currently NOT performing when preparing for speaking events.&nbsp; The point that best hit home for me was to consider each audience member's time at an hourly rate, and that anything less than stellar is a disservice to them.&nbsp; I have several engagements on tap in 2011, and I'm going to strive to make each presentation better than the last - and not to treat them like my own personal Toastmasters meetings.&nbsp; In a conversation today I identified some of the key points I need to work on to improve my presentation skills, and that's as good a start as any.</p><p>&nbsp; <br></p>