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