Summary: SQL Server MVP, Sean McCown, talks about using Windows PowerShell to work with SQL Server instance-level properties.
Microsoft Scripting Guy, Ed Wilson, is here. Today is the first of three blogs written by guest blogger, Sean McCown. You can read more about Sean and his previous guest blogs on the Hey, Scripting Guy! Blog site.
Here’s Sean…
Many DBAs don’t realize how much power they get from the Windows PowerShell provider and how much information is at their fingertips. Here I’m going to walk you through some of the better server instance-level properties, and we’ll even save them to a table so you have a nice inventory of your entire shop. Of course, feel free to add your own properties. Also, I explain a lot of basic things here and that’s because I really don’t like to assume too much knowledge. I think you should be familiar with a little bit of Windows PowerShell for this post, but I try to explain things at a low enough level that you should be OK if you only know the most basic of basics.
Let’s start by getting to the server properties. The easiest way to do it depends on where you are when you start. If you’re in SSMS, right-click the server name in Object Explorer, and then click Start PowerShell.
When you’re there, you’ll be at the instance level. You need to be at the server level though. So to get to the server level, you simply type cd..
Or if you’re already in Windows PowerShell, you can navigate to the server path like this:
>cd sqlserver:\sql\sdlseanm
That will put you in the same location. It’s worth noting that if you don’t see SQLSERVER when you run the command psdrive, you need to load the SQL Server provider. To learn how to do this you can watch my video, Watching Powershell Profiles. The code is different for SQL Server 2012. Type Import-Module SQLPS, but the rest of the video applies.
Now that we’re where we need to be, we can start looking at properties. As with anything else, there are more than one way to accomplish the same task in Windows PowerShell, and I’m going to show you a couple of them. Let’s start by pulling up a listing of the items at this level:
>dir
This gives us a list of the instances on this server. And that makes sense because we were at a specific instance and went a level up. In my case, I’ve got two instances because Visual Studio insists on installing SQL Express no matter what I say.
Now that we see the instances, let’s find out the information we can get for them. We do that by typing:
>dir | gm
This gives us a huge list of properties and methods. Here’s a screenshot of the partial list:
Right off the bat you’ll recognize many of these from the T-SQL function SERVERPROPERTY(). So if you’ve already got this information, why bother messing with it in Windows PowerShell? Well, for starters, you can more easily perform large inventories in Windows PowerShell (as we’ll see later), and you can set the writeable properties for all your boxes, which is something you can’t easily do in T-SQL.
First, let’s set a little bit more of the stage. At the end of each of those lines, you’ll see something that looks like this: {get;} or {get;set;}.
The get means you can read the value, and the set means you can change, or set the value. So you can read all of them, but you can only set some of them. And that makes sense because you can’t set the BuildNumber for an install. Only the install process can do that.
But what values can you set? You can look through this list, but they’re not all lined up nicely on the right so it’s hard to make sure that you don’t miss anything. What we need is an easier way to view if these values are writeable.
Before we can do that, we need to learn how to work with instances. Because although we were able to run Get-Member, we won’t be able to work with any of them until we run it against a specific instance. You can get a specific instance like this:
>$a = dir | ?{$_.name –eq “SDLSEANM”}
And now if you run Get-Member against $a, you’ll come up with the same list, but now you’re set to actually work with these values. Now let’s take a look at the {get;set;} info in an easier way.
>$a.properties | FT Name, Writable, Dirty, Value –auto
And again, here’s a partial list of the results.
See how much easier it is to see the properties that you can change? And with this method we can very easily filter down the list to exactly what we want to see, or even change the sorting. I’ll leave those details to you though. You’ll also notice that I included the Dirty column. We’ll be using that in the next post. And if you want to see all the properties that you have available to you here, just type:
>$a.properties | gm
I think we’ve got enough background to actually start working with these items now. Let’s start by pulling some basic instance info.
>$a | FT Name,Edition, VersionMajor, VersionMinor, ProductLevel, Processors, PhysicalMemory –auto
Here’s what I got back:
This is actually pretty good info without hardly trying. But again, that’s all info you can get from SERVERPROPERTY(). So let’s make this more useful right out of the gate so you can see the real power of what I’m talking about.
First we have to know which servers we want to look at. We can’t just say, “Go get us a list of all the SQL boxes from the ether,” and expect results. We have to actually get it from somewhere. And that “somewhere” is where you’ll have a lot of flexibility. For simplicity I’m going to use a simple array with a couple servers in it, but you can use the results from a query, an Excel file, XML, or even a plain text file. You can really use anything that you can query and get a list of server names back from. I’m going to use an array, and here’s how you could set it by using a query:
>$a = invoke-sqlcmd –ServerInstance MyServer –Database “MyDB” –query “Select ServerName from dbo.Servers”
>$a | ?{$b = $_.ServerName;}
Now we’ll use the array instead, but it’ll be almost exactly the same. First we need a table to hold our info because we’re going take all this info from several boxes and put it into a table in a database. This is why this method is so powerful.
I’m going to create a generic table for this example, but you should make your data types a little tighter.
CREATE TABLE ServerProperties
(
ServerName varchar(255),
Edition varchar(255),
Vmajor tinyint,
Vminor varchar(255),
ProductLevel varchar(255),
Processors tinyint,
PhysicalMemory int
)
That should do what we want.
Now we need to cycle through our servers, collect the info, and save it to the table. Let’s get started.
Import-Module SQLPS -DisableNameChecking
$a = "SDLSEANM", "crp01msddb01"
$a | %{ # Begin $a
$ServerName = $_;
Cd SQLSERVER:\SQL\$ServerName;
$InstanceList = dir;
$InstanceList | %{ # Begin $InstanceList
$Name = $_.Name;
$Edition = $_.Edition;
$VersionMajor = $_.VersionMajor;
$VersionMinor = $_.VersionMinor;
$ProductLevel = $_.ProductLevel;
$Processors = $_.Processors;
$PhysicalMemory = $_.PhysicalMemory;
$ServerName; ## So we can watch it cycle through the boxes.
"Insert ServerProperties Select '$Name', '$Edition', $VersionMajor, '$VersionMinor', '$ProductLevel', $Processors, $PhysicalMemory"
Invoke-sqlcmd –ServerInstance "crp01msddb01" –database "tempdb" –query "Insert ServerProperties Select '$Name', '$Edition', $VersionMajor, '$VersionMinor', '$ProductLevel', $Processors, $PhysicalMemory"
} # End $InstanceList
} # End $a
That’s it. We’re done.
Now, I realize that looks like it might be getting to be a long script, but it’s really not that bad. Most of it is control-type stuff. In fact, the Invoke-SqlCmd line is the only line that does any actual work. Everything else is setting things up and making them look pretty so we can manage the script. I could have easily cut all the variable assignment, but it makes the script a lot easier to read and extend, so I prefer to leave it in. Also, if the Query command is going to be long you might consider putting it into a variable too, and then putting the variable in the database call. Here’s what that would look like:
$InsertQuery = "Insert ServerProperties Select '$Name', '$Edition', $VersionMajor, '$VersionMinor', '$ProductLevel', $Processors, $PhysicalMemory"
Invoke-sqlcmd –ServerInstance "crp01msddb01" –database "tempdb" –query "$InsertQuery"
This makes it easier to manage if you’re going to have a lot of values.
So there you go. I only demonstrated a handful of properties for you, but you can see from Get-Member that there are so many more to be had. Explore them on your own and write your own table to put them in. This is such a powerful technique for documenting your SQL Server environment, and it clearly can’t be done this easily in T-SQL.
Additionally, you don’t have to have the process do the insert. You could save the T-SQL statements to a file and run them manually, if you prefer. That’s easy enough too:
$InsertQuery = "Insert ServerProperties Select '$Name', '$Edition', $VersionMajor, '$VersionMinor', '$ProductLevel', $Processors, $PhysicalMemory"
$InsertQuery | Out-File 'c:\Inserts.sql' -append
That’s it for now. Next time, we’re going to talk about the Dirty column we saw earlier and explore when it can be useful.
~Sean
Awesome job, Sean. Thank you. SQL Server Week will continue tomorrow when we will have another guest blog by Sean.
I invite you to follow me on Twitter and Facebook. If you have any questions, send email to me at scripter@microsoft.com, or post your questions on the Official Scripting Guys Forum. See you tomorrow. Until then, peace.
Ed Wilson, Microsoft Scripting Guy