Skip to content

Commit 5dcef02

Browse files
authored
Merge pull request #241 from michaeltlombardi/ticket/master/MODULES-5566-rewrite-instance-discovery
(MODULES-5566) Rewrite Instance Discovery
2 parents 4711aaa + 5f7caec commit 5dcef02

File tree

2 files changed

+54
-86
lines changed

2 files changed

+54
-86
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

spec/acceptance/z_last_sqlserver_features_spec.rb

+2-11
Original file line numberDiff line numberDiff line change
@@ -204,17 +204,8 @@ def bind_and_apply_failing_manifest(features, ensure_val = 'present')
204204

205205
before(:all) do
206206
puppet_version = (on host, puppet('--version')).stdout.chomp
207-
208-
if puppet_version =~ /^4\.\d+\.\d+/
209-
json_result = JSON.parse((on host, puppet('facts --render-as json')).raw_output)["values"]["sqlserver_instances"]
210-
names = json_result.collect { |k, v| json_result[k].keys }.flatten
211-
else
212-
# use agents fact to get instance names
213-
distmoduledir = on(host, "echo #{host['distmoduledir']}").raw_output.chomp
214-
facter_opts = {:environment => {'FACTERLIB' => "#{distmoduledir}/sqlserver/lib/facter"}}
215-
216-
names = eval(fact_on(host, 'sqlserver_instances', facter_opts)).values.inject(:merge).keys
217-
end
207+
json_result = JSON.parse((on host, puppet('facts --render-as json')).raw_output)["values"]["sqlserver_instances"]
208+
names = json_result.collect { |k, v| json_result[k].keys }.flatten
218209
remove_sql_instances(host, {:version => sql_version, :instance_names => names})
219210
end
220211

0 commit comments

Comments
 (0)