Skip to content

Commit 377a0bb

Browse files
(MODULES-5566) Discover instances via registry
Without this commit, the logic to discover instances and features relies on WMI calls and fails to discover instances unless the SQL Engine is installed; this meant that Puppet would never discover instances with only analytics services or reporting services installed, causing each puppet run to attempt to reinstall them, even though they exist. This commit refactors the logic for discovering SQL instances to use registry instead of WMI. In order to do so, we use the Puppet Util Windows Registry module and ensure that SQL instances with valid names outside the current code page can be discovered. This ensures that we are able to discover SQL instances regardless of which features they have installed. This commit also removes code supporting calls to WMI to discover SQL instances.
1 parent 4711aaa commit 377a0bb

File tree

1 file changed

+52
-75
lines changed

1 file changed

+52
-75
lines changed

lib/puppet_x/sqlserver/features.rb

+52-75
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
require 'puppet/util/windows'
2+
13
SQL_2012 ||= 'SQL_2012'
24
SQL_2014 ||= 'SQL_2014'
35
SQL_2016 ||= 'SQL_2016'
@@ -10,39 +12,26 @@ module Sqlserver
1012
class Features
1113
private
1214

15+
include Puppet::Util::Windows::Registry
16+
extend Puppet::Util::Windows::Registry
17+
1318
SQL_CONFIGURATION ||= {
1419
SQL_2012 => {
1520
:major_version => 11,
16-
:wmi_path => 'ComputerManagement11',
1721
:registry_path => '110',
1822
},
1923
SQL_2014 => {
2024
:major_version => 12,
21-
:wmi_path => 'ComputerManagement12',
2225
:registry_path => '120',
2326
},
2427
SQL_2016 => {
2528
:major_version => 13,
26-
:wmi_path => 'ComputerManagement13',
2729
:registry_path => '130',
2830
}
2931
}
3032

3133
SQL_REG_ROOT ||= 'Software\Microsoft\Microsoft SQL Server'
32-
33-
# http://msdn.microsoft.com/en-us/library/windows/desktop/aa384129(v=vs.85).aspx
34-
KEY_WOW64_64KEY ||= 0x100
35-
KEY_READ ||= 0x20019
36-
37-
def self.connect(version)
38-
require 'win32ole'
39-
ver = SQL_CONFIGURATION[version][:wmi_path]
40-
context = WIN32OLE.new('WbemScripting.SWbemNamedValueSet')
41-
context.Add("__ProviderArchitecture", 64)
42-
locator = WIN32OLE.new('WbemScripting.SWbemLocator')
43-
# WMI Path must use backslashes. Ruby 2.3 can cause crashes with forward slashes
44-
locator.ConnectServer(nil, "root\\Microsoft\\SqlServer\\#{ver}", nil, nil, nil, nil, nil, context)
45-
end
34+
HKLM ||= 'HKEY_LOCAL_MACHINE'
4635

4736
def self.get_parent_path(key_path)
4837
# should be the same as SQL_REG_ROOT
@@ -55,81 +44,66 @@ def self.get_reg_key_val(win32_reg_key, val_name, reg_type)
5544
nil
5645
end
5746

58-
def self.get_sql_reg_val_features(key_name, reg_val_feat_hash)
59-
require 'win32/registry'
47+
def self.key_exists?(path)
48+
begin
49+
open(HKLM, path, KEY_READ | KEY64) {}
50+
return true
51+
rescue
52+
return false
53+
end
54+
end
6055

56+
def self.get_sql_reg_val_features(key_name, reg_val_feat_hash)
6157
vals = []
62-
6358
begin
64-
hklm = Win32::Registry::HKEY_LOCAL_MACHINE
65-
vals = hklm.open(key_name, KEY_READ | KEY_WOW64_64KEY) do |key|
59+
vals = open(HKLM, key_name, KEY_READ | KEY64) do |key|
6660
reg_val_feat_hash
6761
.select { |val_name, _| get_reg_key_val(key, val_name, Win32::Registry::REG_DWORD) == 1 }
6862
.map { |_, feat_name| feat_name }
6963
end
70-
rescue Win32::Registry::Error # subkey doesn't exist
64+
rescue Puppet::Util::Windows::Error # subkey doesn't exist
7165
end
7266

7367
vals
7468
end
7569

76-
def self.get_sql_reg_key_features(key_name, reg_key_feat_hash, instance_name)
77-
require 'win32/registry'
70+
def self.get_reg_instance_info(friendly_version)
71+
instance_root = 'SOFTWARE\Microsoft\Microsoft SQL Server\Instance Names'
72+
return [] unless key_exists?(instance_root)
73+
discovered = {}
74+
open(HKLM, "#{instance_root}", KEY_READ | KEY64) do |registry|
75+
each_key(registry) do |instance_type, _|
76+
open(HKLM, "#{instance_root}\\#{instance_type}", KEY_READ | KEY64) do |instance|
77+
each_value(instance) do |short_name, _, long_name|
78+
root = "Software\\Microsoft\\Microsoft SQL Server\\#{long_name}"
79+
discovered[short_name] ||= {
80+
'name' => short_name,
81+
'reg_root' => [],
82+
'version' => open(HKLM, "#{root}\\MSSQLServer\\CurrentVersion", KEY_READ | KEY64) { |r| values(r)['CurrentVersion'] },
83+
'version_friendly' => friendly_version
84+
}
85+
86+
discovered[short_name]['reg_root'].push(root)
87+
end
88+
end
89+
end
90+
end
91+
discovered.values
92+
end
7893

94+
def self.get_sql_reg_key_features(key_name, reg_key_feat_hash, instance_name)
7995
installed = reg_key_feat_hash.select do |subkey, feat_name|
8096
begin
81-
hklm = Win32::Registry::HKEY_LOCAL_MACHINE
82-
hklm.open("#{key_name}\\#{subkey}", KEY_READ | KEY_WOW64_64KEY) do |feat_key|
97+
open(HKLM, "#{key_name}\\#{subkey}", KEY_READ | KEY64) do |feat_key|
8398
get_reg_key_val(feat_key, instance_name, Win32::Registry::REG_SZ)
8499
end
85-
rescue Win32::Registry::Error # subkey doesn't exist
100+
rescue Puppet::Util::Windows::Error # subkey doesn't exist
86101
end
87102
end
88103

89104
installed.values
90105
end
91106

92-
def self.get_wmi_property_values(wmi, query, prop_name = 'PropertyStrValue')
93-
vals = []
94-
95-
wmi.ExecQuery(query).each do |v|
96-
vals.push(v.Properties_(prop_name).Value)
97-
end
98-
99-
vals
100-
end
101-
102-
def self.get_instance_names_by_ver(version)
103-
query = 'SELECT InstanceName FROM ServerSettings'
104-
get_wmi_property_values(connect(version), query, 'InstanceName')
105-
rescue WIN32OLERuntimeError => e # version doesn't exist
106-
# WBEM_E_INVALID_NAMESPACE from wbemcli.h
107-
return [] if e.message =~ /8004100e/im
108-
raise
109-
end
110-
111-
def self.get_sql_property_values(version, instance_name, property_name)
112-
query = <<-END
113-
SELECT * FROM SqlServiceAdvancedProperty
114-
WHERE PropertyName='#{property_name}'
115-
AND SqlServiceType=1 AND ServiceName LIKE '%#{instance_name}'
116-
END
117-
# WMI LIKE query to substring match since ServiceName will be of the format
118-
# MSSQLSERVER (first install) or MSSQL$MSSQLSERVER (second install)
119-
120-
get_wmi_property_values(connect(version), query)
121-
end
122-
123-
def self.get_wmi_instance_info(version, instance_name)
124-
{
125-
'name' => instance_name,
126-
'version_friendly' => version,
127-
'version' => get_sql_property_values(version, instance_name, 'VERSION').first,
128-
# typically Software\Microsoft\Microsoft SQL Server\MSSQL11.MSSQLSERVER
129-
'reg_root' => get_sql_property_values(version, instance_name, 'REGROOT').first,
130-
}
131-
end
132-
133107
def self.get_instance_features(reg_root, instance_name)
134108
instance_features = {
135109
# also reg Replication/IsInstalled set to 1
@@ -215,8 +189,9 @@ def self.get_instances
215189
.map do |version|
216190
major_version = SQL_CONFIGURATION[version][:major_version]
217191

218-
instances = get_instance_names_by_ver(version)
219-
.map { |name| [ name, get_instance_info(version, name) ] }
192+
instances = get_reg_instance_info(version).map do |instance|
193+
[instance['name'], get_instance_info(version,instance)]
194+
end
220195

221196
# Instance names are unique on a single host, but not for a particular SQL Server version therefore
222197
# it's possible to request information for a valid instance_name but not for version. In this case
@@ -266,15 +241,17 @@ def self.get_features
266241
# "RS"
267242
# ]
268243
# }
269-
def self.get_instance_info(version, instance_name)
244+
def self.get_instance_info(version, sql_instance)
270245
return nil if version.nil?
271-
sql_instance = get_wmi_instance_info(version, instance_name)
272246
# Instance names are unique on a single host, but not for a particular SQL Server version therefore
273247
# it's possible to request information for a valid instance_name but not for version. In this case
274248
# we just return nil.
275249
return nil if sql_instance['reg_root'].nil?
276-
feats = get_instance_features(sql_instance['reg_root'], sql_instance['name'])
277-
sql_instance.merge({'features' => feats})
250+
251+
feats = sql_instance['reg_root'].map do |reg_root|
252+
get_instance_features(reg_root, sql_instance['name'])
253+
end
254+
sql_instance.merge({'features' => feats.uniq})
278255
end
279256
end
280257
end

0 commit comments

Comments
 (0)