|
| 1 | +SQL_2012 = 'SQL_2012' |
| 2 | +SQL_2014 = 'SQL_2014' |
| 3 | + |
| 4 | +module PuppetX |
| 5 | + module Sqlserver |
| 6 | + # https://msdn.microsoft.com/en-us/library/ms143786.aspx basic feature docs |
| 7 | + class Features |
| 8 | + private |
| 9 | + |
| 10 | + SQL_WMI_PATH = { |
| 11 | + SQL_2012 => 'ComputerManagement11', |
| 12 | + SQL_2014 => 'ComputerManagement12', |
| 13 | + } |
| 14 | + |
| 15 | + SQL_REG_ROOT = 'Software\Microsoft\Microsoft SQL Server' |
| 16 | + |
| 17 | + def self.connect(version = SQL_2012) |
| 18 | + require 'win32ole' |
| 19 | + ver = SQL_WMI_PATH[version] |
| 20 | + WIN32OLE.connect("winmgmts://./root/Microsoft/SqlServer/#{ver}") |
| 21 | + end |
| 22 | + |
| 23 | + def self.get_parent_path(key_path) |
| 24 | + # should be the same as SQL_REG_ROOT |
| 25 | + key_path.slice(0, key_path.rindex('\\')) |
| 26 | + end |
| 27 | + |
| 28 | + def self.get_reg_key_val(win32_reg_key, val_name, reg_type) |
| 29 | + win32_reg_key[val_name, reg_type] |
| 30 | + rescue |
| 31 | + nil |
| 32 | + end |
| 33 | + |
| 34 | + def self.get_sql_reg_val_features(key_name, reg_val_feat_hash) |
| 35 | + require 'win32/registry' |
| 36 | + |
| 37 | + Win32::Registry::HKEY_LOCAL_MACHINE.open(key_name) do |key| |
| 38 | + reg_val_feat_hash |
| 39 | + .select { |val_name, _| get_reg_key_val(key, val_name, Win32::Registry::REG_DWORD) == 1 } |
| 40 | + .map { |_, feat_name| feat_name } |
| 41 | + end |
| 42 | + end |
| 43 | + |
| 44 | + def self.get_sql_reg_key_features(key_name, reg_key_feat_hash, instance_name) |
| 45 | + require 'win32/registry' |
| 46 | + |
| 47 | + installed = reg_key_feat_hash.select do |subkey, feat_name| |
| 48 | + begin |
| 49 | + Win32::Registry::HKEY_LOCAL_MACHINE.open("#{key_name}\\#{subkey}") do |feat_key| |
| 50 | + get_reg_key_val(feat_key, instance_name, Win32::Registry::REG_SZ) |
| 51 | + end |
| 52 | + rescue Win32::Registry::Error # subkey doesn't exist |
| 53 | + end |
| 54 | + end |
| 55 | + |
| 56 | + installed.values |
| 57 | + end |
| 58 | + |
| 59 | + def self.get_wmi_property_values(wmi, query, prop_name = 'PropertyStrValue') |
| 60 | + vals = [] |
| 61 | + |
| 62 | + wmi.ExecQuery(query).each do |v| |
| 63 | + vals.push(v.Properties_(prop_name).Value) |
| 64 | + end |
| 65 | + |
| 66 | + vals |
| 67 | + end |
| 68 | + |
| 69 | + def self.get_instance_names_by_ver(version = SQL_2012) |
| 70 | + query = 'SELECT InstanceName FROM ServerSettings' |
| 71 | + get_wmi_property_values(connect(version), query, 'InstanceName') |
| 72 | + rescue WIN32OLERuntimeError => e # version doesn't exist |
| 73 | + # WBEM_E_INVALID_NAMESPACE from wbemcli.h |
| 74 | + return [] if e.message =~ /0x8004100e/m |
| 75 | + raise |
| 76 | + end |
| 77 | + |
| 78 | + def self.get_sql_property_values(version, instance_name, property_name) |
| 79 | + query = <<-END |
| 80 | + SELECT * FROM SqlServiceAdvancedProperty |
| 81 | + WHERE PropertyName='#{property_name}' |
| 82 | + AND SqlServiceType=1 AND ServiceName LIKE '%#{instance_name}' |
| 83 | + END |
| 84 | + # WMI LIKE query to substring match since ServiceName will be of the format |
| 85 | + # MSSQLSERVER (first install) or MSSQL$MSSQLSERVER (second install) |
| 86 | + |
| 87 | + get_wmi_property_values(connect(version), query) |
| 88 | + end |
| 89 | + |
| 90 | + def self.get_wmi_instance_info(version, instance_name) |
| 91 | + { |
| 92 | + :name => instance_name, |
| 93 | + :version_friendly => version, |
| 94 | + :version => get_sql_property_values(version, instance_name, 'VERSION').first, |
| 95 | + # typically Software\Microsoft\Microsoft SQL Server\MSSQL11.MSSQLSERVER |
| 96 | + :reg_root => get_sql_property_values(version, instance_name, 'REGROOT').first, |
| 97 | + } |
| 98 | + end |
| 99 | + |
| 100 | + def self.get_instance_features(reg_root, instance_name) |
| 101 | + instance_features = { |
| 102 | + # also reg Replication/IsInstalled set to 1 |
| 103 | + 'SQL_Replication_Core_Inst' => 'SQL Server Replication', |
| 104 | + # also WMI: SqlService WHERE SQLServiceType = 1 # MSSQLSERVER |
| 105 | + 'SQL_Engine_Core_Inst' => 'Database Engine Services', |
| 106 | + 'SQL_FullText_Adv' => 'Full-Text and Semantic Extractions for Search', |
| 107 | + 'SQL_DQ_Full' => 'Data Quality Services' |
| 108 | + } |
| 109 | + |
| 110 | + feat_root = "#{reg_root}\\ConfigurationState" |
| 111 | + features = get_sql_reg_val_features(feat_root, instance_features) |
| 112 | + |
| 113 | + # https://msdn.microsoft.com/en-us/library/ms179591.aspx |
| 114 | + # WMI equivalents require trickier name parsing |
| 115 | + parent_subkey_features = { |
| 116 | + # also WMI: SqlService WHERE SQLServiceType = 5 # MSSQLServerOLAPService |
| 117 | + 'OLAP' => 'Analysis Services', |
| 118 | + # also WMI: SqlService WHERE SQLServiceType = 6 # ReportServer |
| 119 | + 'RS' => 'Reporting Services - Native' |
| 120 | + } |
| 121 | + |
| 122 | + # instance features found in non-parented reg keys |
| 123 | + feat_root = "#{get_parent_path(reg_root)}\\Instance Names" |
| 124 | + parent_features = get_sql_reg_key_features(feat_root, parent_subkey_features, instance_name) |
| 125 | + |
| 126 | + features + parent_features |
| 127 | + end |
| 128 | + |
| 129 | + def self.get_shared_features(version, reg_root) |
| 130 | + shared_features = { |
| 131 | + 'Connectivity_Full' => 'Client Tools Connectivity', |
| 132 | + 'SDK_Full' => 'Client Tools SDK', |
| 133 | + 'MDSCoreFeature' => 'Master Data Services', |
| 134 | + 'Tools_Legacy_Full' => 'Client Tools Backwards Compatibility', |
| 135 | + 'SQL_SSMS_Full' => 'Management Tools - Complete', |
| 136 | + 'SQL_SSMS_Adv' => 'Management Tools - Basic', # also SQL_PowerShell_Tools_ANS |
| 137 | + # also WMI: SqlService WHERE SQLServiceType = 4 # MsDtsServer |
| 138 | + 'SQL_DTS_Full' => 'Integration Services' |
| 139 | + # currently ignoring Reporting Services Shared |
| 140 | + } |
| 141 | + |
| 142 | + reg_ver = (version == SQL_2014 ? '120' : '110') |
| 143 | + reg_root = "#{reg_root}\\#{reg_ver}\\ConfigurationState" |
| 144 | + |
| 145 | + get_sql_reg_val_features(reg_root, shared_features) |
| 146 | + end |
| 147 | + |
| 148 | + public |
| 149 | + |
| 150 | + # return a hash of version => instance info and shared features |
| 151 | + # |
| 152 | + # { |
| 153 | + # "SQL_2012" => {}, |
| 154 | + # "SQL_2014" => { |
| 155 | + # "MSSQLSERVER" => { |
| 156 | + # :name => "MSSQLSERVER", |
| 157 | + # :version_friendly => "SQL_2014", |
| 158 | + # :version => "12.0.2000.8", |
| 159 | + # :reg_root => "Software\\Microsoft\\Microsoft SQL Server\\MSSQL12.MSSQLSERVER", |
| 160 | + # :features => [ |
| 161 | + # "SQL Server Replication", |
| 162 | + # "Database Engine Services", |
| 163 | + # "Full-Text and Semantic Extractions for Search", |
| 164 | + # "Data Quality Services", |
| 165 | + # "Analysis Services", |
| 166 | + # "Reporting Services - Native" |
| 167 | + # ] |
| 168 | + # }, |
| 169 | + # :features => [ |
| 170 | + # "Client Tools Connectivity", |
| 171 | + # "Client Tools SDK", |
| 172 | + # "Master Data Services", |
| 173 | + # "Client Tools Backwards Compatibility", |
| 174 | + # "Management Tools - Complete", |
| 175 | + # "Management Tools - Basic", |
| 176 | + # "Integration Services" |
| 177 | + # ] |
| 178 | + # } |
| 179 | + # } |
| 180 | + def self.get_installations |
| 181 | + version_instance_map = get_instance_names |
| 182 | + .map do |version, instance_names| |
| 183 | + instances = instance_names |
| 184 | + .map { |name| [ name, get_instance_info(version, name) ] } |
| 185 | + |
| 186 | + instances.push([:features, get_shared_features(version, SQL_REG_ROOT)]) |
| 187 | + |
| 188 | + [ version, Hash[instances] ] |
| 189 | + end |
| 190 | + |
| 191 | + Hash[version_instance_map] |
| 192 | + end |
| 193 | + |
| 194 | + # return a hash of version => instance name array |
| 195 | + # |
| 196 | + # { |
| 197 | + # "SQL_2012" => ["MSSQLSERVER"], |
| 198 | + # "SQL_2014" => ["MSSQLSERVER", "MSSQLSERVER2"], |
| 199 | + # } |
| 200 | + def self.get_instance_names |
| 201 | + { |
| 202 | + SQL_2012 => get_instance_names_by_ver(SQL_2012), |
| 203 | + SQL_2014 => get_instance_names_by_ver(SQL_2014), |
| 204 | + } |
| 205 | + end |
| 206 | + |
| 207 | + # returns a hash containing instance details |
| 208 | + # |
| 209 | + # { |
| 210 | + # :name => "MSSQLSERVER2", |
| 211 | + # :version_friendly => "SQL_2014", |
| 212 | + # :version => "12.0.2000.8", |
| 213 | + # :reg_root => "Software\\Microsoft\\Microsoft SQL Server\\MSSQL12.MSSQLSERVER2", |
| 214 | + # :features => [ |
| 215 | + # "SQLServer Replication", |
| 216 | + # "Database Engine Services", |
| 217 | + # "Full-Text and Semantic Extractions for Search", |
| 218 | + # "Data Quality Services", |
| 219 | + # "Analysis Services", |
| 220 | + # "Reporting Services - Native" |
| 221 | + # ] |
| 222 | + # } |
| 223 | + def self.get_instance_info(version = SQL_2012, instance_name) |
| 224 | + sql_instance = get_wmi_instance_info(version, instance_name) |
| 225 | + feats = get_instance_features(sql_instance[:reg_root], sql_instance[:name]) |
| 226 | + sql_instance.merge({:features => feats}) |
| 227 | + end |
| 228 | + end |
| 229 | + end |
| 230 | +end |
0 commit comments