Tuesday, January 30, 2018

SQL Version - Let's Go Further Beyond @@version

When DBAs need to check SQL Server edition, version, build or patch level, most of them use @@version to get the info. Too often I found it's insufficient. Let me give you a few examples:

1. @@version does not provide info on cumulative update or other special security patches. For instance, on a system with build no 12.0.5557.0, it only shows SQL Server 2014 SP2, but more accurately it's SP2 cumulative update package 8 (CU8). The CU details sometimes are critical to compare between SQL instances in order to bring them to consistency or t-shoot a problem due to inconsistency.

2. SQL version closely links to Windows version. So it's a common desire to not only find info at SQL Server level but also at an OS level. Before SQL Server 2016 the OS info is vague to say the least. Let's say, on a Windows Server 2012 system, you might find @@version returns "Windows NT 6.2 <x64> (Build 9200)". I hear ya, we all hate that! Fortunately beginning on SQL 2016, they put more welcome message like "Windows Server 2016 Datacenter Evaluation 10.0 <X64> (Build 14393: ) (Hypervisor)" - much better, huh?

3. The built-in function @@version lacks visibility of tracking down the source of a patch, which usually is a Microsoft Knowledge Base article. It also lacks a concise yet detailed enough description of what a patch is targeted for.  For example, build no 13.0.4199 is an important update for SQL Server 2016 SP1 Reporting Service. If you search for the build number you'll find great details from KB3207512.

4. The official release dates. Don't get confused by the build date returned by @@version. The release dates are more important to end users as they can have a broad view of the life cycles of the products they purchase. The dates also help SQL guys determine how far their servers fall behind of current level.

5. An indicator of hardware model, whether a physical server, a virtual machine, or a Hyper-V, is definitely welcome. In addition, any critical info such as SQL Server's processors, memory, min/max target server memory, etc makes a DBA's life easier.

All the above mentioned shortcomings can be fixed when you use this blog post:

http://sqlserverbuilds.blogspot.com/

For the past decade it has been my de facto web site for SQL Server version checking. Just to disclaim that I have no affiliation with the blogger. I have no knowledge who created and is maintaining this site. Whoever did it it's a job well done. It is probably the most complete and detailed list of all SQL Server patches you can find on the web. It covers the release of version 7.0 all the way up to SQL 2017.

How nice would it be if we could combine the merits from this web site with the good stuff returned by @@version? Well, that's why I'm blogging in this post to show you my 90% automated solution. Fortunately the 10% manual work is as easy as a breeze. Here we go.

My testing prerequisites:
- Any SQL versions between 2005 and 2017, with xp_cmdshell enabled
- Excel 2013/2016
- SQL Server Data Tool (Visual Studio 2015)

Step 1.   At your own leisure copy version contents from the blog site into an Excel spreadsheet. To do so first find the beginning line of your interest, such as "Microsoft SQL Server 2017 Builds". Hold down the shift key while highlighting sections. You can scroll the right bar up and down to quickly get to desired spot, e.g. the end of chart for "Microsoft SQL Server 2000 Builds". Copy and paste it to an blank Excel workbook. Save it as "CSV (Comma delimited) file. When prompted if some special info in the Excel needs to be saved, click yes. The file location is C:\Temp\SQLServerBuild.


Step 2.   In the same folder mentioned in Step 1, create two .sql files for header and tail. They will be merged with a body text to form an intact stored procedure.

Header T-SQL:

USE master;
 GO

IF OBJECT_ID ( 'dbo.sp_SQLServerBuildInfo', 'P' ) IS NOT NULL
   BEGIN
     DROP PROCEDURE dbo.sp_SQLServerBuildInfo;
     IF OBJECT_ID('dbo.sp_SQLServerBuildInfo', 'P' ) IS NOT NULL
         PRINT '*** FAILED DROPPING PROCEDURE dbo.sp_SQLServerBuildInfo ***';
     ELSE
         PRINT '*** DROPPED PROCEDURE dbo.sp_SQLServerBuildInfo ****';
   END;
 GO

CREATE PROCEDURE dbo.sp_SQLServerBuildInfo 
AS

-- *********************************************************************************
 -- Procedure Name:  sp_SQLServerBuildInfo
 -- Purpose:         List SQL Server build no, etc for SQL Server 2005 -> 2017
 -- Note:            Refer to this site: http://sqlserverbuilds.blogspot.com/
 -- Author:          Richard Ding
 -- Date Created:    01/28/2018
 -- Date Modified:   
 --**********************************************************************************

 SET NOCOUNT ON;

 IF EXISTS (SELECT * FROM sys.configurations where name = 'xp_cmdshell' and value_in_use = 1)
   BEGIN
     if object_id('tempdb.dbo.#SystemInfo', 'U') is not null
   drop table #SystemInfo
     create table #SystemInfo (ItemValue VARCHAR(5000));
     INSERT INTO #SystemInfo EXEC ('xp_cmdshell ''systeminfo /FO LIST''');
   END
 ELSE
BEGIN
RAISERROR ('Stored procedure sp_SQLServerBuildInfo requries xp_cmdshell to be turn on. Please check.', 16, 1)
RETURN (0);
END;

DECLARE @OSName varchar(100), @OSVersion varchar(100), @SystemManufacturer varchar(60), @SystemModel varchar(60), @Model varchar(100);
 SELECT @OSName = LTRIM(RTRIM(SUBSTRING(ItemValue, CHARINDEX(':', ItemValue)+1, LEN(ItemValue) - CHARINDEX(':', ItemValue))))
                  FROM #SystemInfo WHERE ItemValue LIKE 'OS Name%';
 SELECT @OSVersion = LTRIM(RTRIM(SUBSTRING(ItemValue, CHARINDEX(':', ItemValue)+1, LEN(ItemValue) - CHARINDEX(':', ItemValue))))
                     FROM #SystemInfo WHERE ItemValue LIKE 'OS Version%';
 SELECT @SystemManufacturer = ItemValue FROM #SystemInfo WHERE ItemValue LIKE 'System Manufacturer%';
 SELECT @SystemModel = ItemValue FROM #SystemInfo WHERE ItemValue LIKE 'System Model%';
 SELECT @Model = '(' + LTRIM(RTRIM(SUBSTRING(@SystemManufacturer, CHARINDEX(':', @SystemManufacturer)+1, LEN(@SystemManufacturer) - CHARINDEX(':',
                 @SystemManufacturer)))) + ') ' + LTRIM(RTRIM(SUBSTRING(@SystemModel, CHARINDEX(':', @SystemModel)+1, LEN(@SystemModel) - CHARINDEX(':', @SystemModel))));


IF object_id('tempdb.dbo.#versioninfo', 'U') IS NOT NULL
DROP TABLE #versioninfo
CREATE TABLE #versioninfo ([Index] varchar(5), [Name] varchar(20), Internal_Value varchar(10), Character_Value varchar(120));   
INSERT INTO #versioninfo EXEC ('master.dbo.xp_msver');
   

DECLARE @ProductVersion varchar(50), @cpuspeedcount varchar(3), @Memory varchar(5);
 SELECT @ProductVersion = CONVERT(VARCHAR(50), SERVERPROPERTY('ProductVersion'));
 SET @cpuspeedcount = (SELECT Internal_Value FROM #versioninfo WHERE Name = 'ProcessorCount');
 SET @Memory = (SELECT Internal_Value FROM #versioninfo WHERE Name = 'PhysicalMemory');

 DECLARE @cpuidentifier varchar(100), @CPUNameString varchar(100);

/*  Find CPU Name String such as "Intel(R) Xeon(TM) CPU 1.60GHz"  */
 EXEC master.dbo.xp_regread
   @rootkey='HKEY_LOCAL_MACHINE',
   @key='HARDWARE\DESCRIPTION\system\Centralprocessor\0',
   @value_name='ProcessorNameString',
   @value = @CPUNameString OUTPUT,
   @no_output = 'no_output';

DECLARE @MaxMemory varchar(5), @MinMemory varchar(5)
SELECT @MaxMemory = convert(varchar(5), cast(value_in_use as int)/1024)  FROM sys.configurations where name = 'max server memory (MB)'
SELECT @MinMemory = convert(varchar(5), cast(value_in_use as int)/1024)  FROM sys.configurations where name = 'min server memory (MB)'

--  Big select statement:
 SELECT @OSName as 'Windows OS Edition'
 , @OSVersion AS 'Windows OS Service pack'
 , @Model AS 'Hardware Model'
 , @Memory + ' MB (SQL Min:' + @MinMemory + ' GB - Max:' + @MaxMemory + ' GB)' AS 'Memory'
 , REPLACE(REPLACE(REPLACE(left(LTRIM(@CPUNameString), 50), '    ', ' '), '   ', ' '), '  ', '') +' (x' + @cpuspeedcount + ') ' AS 'Processor'
 , CONVERT(VARCHAR(50), SERVERPROPERTY('ServerName')) AS 'SQL Server Name'
 , CASE
     WHEN @ProductVersion LIKE '8.0%' THEN 'SQL Server 2000'
     WHEN @ProductVersion LIKE '9.0%' THEN 'SQL Server 2005'
     WHEN @ProductVersion LIKE '10.0%' THEN 'SQL Server 2008'
     WHEN @ProductVersion LIKE '10.50%' THEN 'SQL Server 2008 R2'
     WHEN @ProductVersion LIKE '11.0%' THEN 'SQL Server 2012'
     WHEN @ProductVersion LIKE '12.0%' THEN 'SQL Server 2014'    
  WHEN @ProductVersion LIKE '13.0%' THEN 'SQL Server 2016'
WHEN @ProductVersion LIKE '14.0%' THEN 'SQL Server 2017'
   ELSE '' END + ' ' + CONVERT(VARCHAR(50), SERVERPROPERTY('Edition')) as 'SQL Server Edition', 
   CASE
   --  End of header

Tail T-SQL:

--  Begining of tail:
   ELSE '<' + @ProductVersion + '> Unknown' END AS 'SQL Server Build & Product Version'
--  End of tail 

RETURN 0;   
GO

GRANT EXEC ON sp_SQLServerBuildInfo TO public;
GO

Step 3.   Create an Integration Service solution/project in SQL Server Data Tool. Items are described in the order they are created.


(1)  SQL Server Connection Manager Data Source "(local)". Use Windows Authentication to login in local default instance. Select tempdb as target environment.


(2) Flat File Connection Manager Data Source "SQLServerBuilds.csv". Each detailed windows are shown below.





(3) Execute SQL Control Flow Task "Prep temp table". All settings are default. The T-SQL statement is below

USE [tempdb];

if object_id ('tempdb.dbo.SQLServerBuilds', 'U') is not null
drop table [SQLServerBuilds];

CREATE TABLE [dbo].[SQLServerBuilds](
--ID int identity primary key clustered,
[Build] [nvarchar](500) NULL,
[File version] [nvarchar](500) NULL,
[KB / Description] [nvarchar](4000) NULL,
[Release Date] [nvarchar](500) NULL
);


(4) Data Flow Task "Import from CSV". Note not all columns are selected. 








(5) Execute SQL Task Control Flow "Cleaning garbage". Its DML commands are in the following:

UPDATE tempdb.dbo.[SQLServerBuilds] 
SET [KB / Description] = CONCAT([KB / Description], ', ', substring([Release Date], 1, charindex('",', [Release Date])))
WHERE [Release Date] LIKE '%",%'

UPDATE tempdb.dbo.[SQLServerBuilds] 
SET [Release Date] = STUFF ([Release Date], 1, charindex('",', [Release Date]) + 1, '')
WHERE [Release Date] LIKE '%",%'

DELETE FROM tempdb.dbo.[SQLServerBuilds] WHERE Build IS NULL OR LEFT (Build, 1) LIKE '%[A-Z]%'

UPDATE tempdb.dbo.[SQLServerBuilds] 
SET [KB / Description] = STUFF ([KB / Description], 1, 1, '')
WHERE [KB / Description] LIKE '"%'    --  181 rows affected

UPDATE tempdb.dbo.[SQLServerBuilds] 
SET [KB / Description] = STUFF ([KB / Description], 1, 1, '')
WHERE [KB / Description] LIKE '"%'    --  181 rows affected

UPDATE tempdb.dbo.[SQLServerBuilds] 
SET [KB / Description] = STUFF ([KB / Description], LEN([KB / Description]), 1, '')
WHERE [KB / Description] LIKE '%"'    --  181 rows affected

UPDATE tempdb.dbo.[SQLServerBuilds] SET [Release Date] = REPLACE(REPLACE ([release date], '*new"', ''), '"', '')

UPDATE tempdb.dbo.[SQLServerBuilds] SET Build = REPLACE(Build, '.00.', '.0.') where Build like '1[0-2].00.%'    --  251 rows 


(6) Execute SQL Control Flow Task "Create sp_SQLServerBuildInfo". Its DML commands are in the following:

exec master.dbo.xp_cmdshell 'sqlcmd -E -i "C:\Temp\SQLServerBuild\sp_SQLServerBuild.sql"'

(7) Execute SQL Control Flow Task "Combine into sproc". Its DML commands are in the following:

exec master.dbo.xp_cmdshell 'copy C:\Temp\SQLServerBuild\*.sql C:\Temp\SQLServerBuild\sp_SQLServerBuildInfo.sql /y'

(8) Execute SQL Control Flow Task "Generate body syntax". Its DML commands are in the following:

declare @string varchar (8000)
select @string = 'set nocount on select ''WHEN @ProductVersion LIKE '''''' + Build + ''%'''' THEN ''''<'''' + @ProductVersion + ''''> '' + replace([KB / Description], '''''''', '''''''''''') +  '' ('' + ISNULL([Release Date], '''') +  '')'''''' from tempdb.dbo.[SQLServerBuilds] order by [File version] asc'
select @string = 'sqlcmd -E -d tempdb -Q "' + @string + '" -o "C:\Temp\SQLServerBuild\2. body.sql"'
print @string
exec master.dbo.xp_cmdshell @string

(9) Execute the whole package (may need to execute the first control flow task separately to resolve object reference). Check the result file at C:\Temp\SQLServerBuild\sp_SQLServerBuildInfo.sql. The body part contains about 1000 lines of code so I won't list it here. Below i compared the results of sp_SQLServerBuildInfo and @@version just to see the fine differences.


No comments:

Post a Comment