Skip to content

Commit 83a1091

Browse files
committed
Merge pull request #116 from Iristyle/ticket/master/FM-2303-feature-hash
(FM-2303, FM-2790, FM-2445) WMI / Registry impl of Discovery Report
2 parents 36a45a8 + 24d7d8a commit 83a1091

File tree

11 files changed

+310
-110
lines changed

11 files changed

+310
-110
lines changed

lib/facter/sqlserver_features.rb

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
require File.expand_path(File.join(File.dirname(__FILE__), '..', 'puppet_x/sqlserver/features'))
2+
3+
Facter.add(:sqlserver_features) do
4+
confine :osfamily => :windows
5+
6+
setcode do
7+
PuppetX::Sqlserver::Features.get_features
8+
end
9+
end

lib/facter/sqlserver_instances.rb

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
require File.expand_path(File.join(File.dirname(__FILE__), '..', 'puppet_x/sqlserver/features'))
2+
3+
Facter.add(:sqlserver_instances) do
4+
confine :osfamily => :windows
5+
6+
setcode do
7+
PuppetX::Sqlserver::Features.get_instances
8+
end
9+
end

lib/puppet/provider/sqlserver.rb

Lines changed: 1 addition & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
require File.expand_path(File.join(File.dirname(__FILE__), '..', '..', '..', 'lib/puppet_x/sqlserver/server_helper'))
2+
require File.expand_path(File.join(File.dirname(__FILE__), '..', '..', '..', 'lib/puppet_x/sqlserver/features'))
23
require File.expand_path(File.join(File.dirname(__FILE__), 'sqlserver'))
34
require 'tempfile'
45

@@ -38,47 +39,6 @@ def not_nil_and_not_empty?(obj)
3839
!obj.nil? and !obj.empty?
3940
end
4041

41-
def self.run_discovery_script
42-
discovery = <<-DISCOVERY
43-
if(Test-Path 'C:\\Program Files\\Microsoft SQL Server\\120\\Setup Bootstrap\\SQLServer2014\\setup.exe'){
44-
pushd 'C:\\Program Files\\Microsoft SQL Server\\120\\Setup Bootstrap\\SQLServer2014\\'
45-
Start-Process -FilePath .\\setup.exe -ArgumentList @("/Action=RunDiscovery","/q") -Wait -WindowStyle Hidden
46-
popd
47-
}elseif(Test-Path 'C:\\Program Files\\Microsoft SQL Server\\110\\Setup Bootstrap\\SQLServer2012\\setup.exe'){
48-
pushd 'C:\\Program Files\\Microsoft SQL Server\\110\\Setup Bootstrap\\SQLServer2012\\'
49-
Start-Process -FilePath .\\setup.exe -ArgumentList @("/Action=RunDiscovery","/q") -Wait -WindowStyle Hidden
50-
popd
51-
}
52-
53-
$file = gci 'C:\\Program Files\\Microsoft SQL Server\\*\\Setup Bootstrap\\Log\\*\\SqlDiscoveryReport.xml' -ErrorAction Ignore | sort -Descending | select -First 1
54-
if($file -ne $null) {
55-
[xml] $xml = cat $file
56-
$json = $xml.ArrayOfDiscoveryInformation.DiscoveryInformation
57-
$hash = @{"instances" = @();"TimeStamp"= ("{0:yyyy-MM-dd HH:mm:ss}" -f $file.CreationTime)}
58-
foreach($instance in ($json | % { $_.Instance } | Get-Unique )){
59-
$features = @()
60-
$json | %{
61-
if($_.instance -eq $instance){
62-
$features += $_.feature
63-
}
64-
}
65-
if($instance -eq "" ){
66-
$hash.Add("Generic Features",$features)
67-
}else{
68-
$hash["instances"] += $instance
69-
$hash.Add($instance,@{"features"=$features})
70-
}
71-
}
72-
$file.Directory.Delete($true)
73-
Write-Host (ConvertTo-Json $hash)
74-
}else{
75-
Write-host ("{}")
76-
}
77-
DISCOVERY
78-
result = powershell([discovery])
79-
JSON.parse(result)
80-
end
81-
8242
def self.run_install_dot_net
8343
install_dot_net = <<-DOTNET
8444
Install-WindowsFeature NET-Framework-Core

lib/puppet/provider/sqlserver_features/mssql.rb

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8,14 +8,13 @@
88
Puppet::Type::type(:sqlserver_features).provide(:mssql, :parent => Puppet::Provider::Sqlserver) do
99
def self.instances
1010
instances = []
11-
jsonResult = Puppet::Provider::Sqlserver.run_discovery_script
12-
debug "Parsing json result #{jsonResult}"
13-
if jsonResult.has_key?('Generic Features')
11+
result = Facter.value(:sqlserver_features)
12+
debug "Parsing result #{result}"
13+
result = !result[SQL_2014].empty? ? result[SQL_2014] : result[SQL_2012]
14+
if !result.empty?
1415
existing_instance = {:name => "Generic Features",
1516
:ensure => :present,
16-
:features =>
17-
PuppetX::Sqlserver::ServerHelper.translate_features(
18-
jsonResult['Generic Features']).sort!
17+
:features => result.sort
1918
}
2019
debug "Parsed features = #{existing_instance[:features]}"
2120

lib/puppet/provider/sqlserver_instance/mssql.rb

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -9,19 +9,16 @@
99
Puppet::Type::type(:sqlserver_instance).provide(:mssql, :parent => Puppet::Provider::Sqlserver) do
1010
def self.instances
1111
instances = []
12-
jsonResult = Puppet::Provider::Sqlserver.run_discovery_script
13-
debug "Parsing json result #{jsonResult}"
14-
if jsonResult.has_key?('instances')
15-
jsonResult['instances'].each do |instance_name|
12+
result = Facter.value(:sqlserver_instances)
13+
debug "Parsing result #{result}"
14+
result = result.values.inject(:merge)
15+
result.keys.each do |instance_name|
1616
existing_instance = {:name => instance_name,
1717
:ensure => :present,
18-
:features =>
19-
PuppetX::Sqlserver::ServerHelper.translate_features(
20-
jsonResult[instance_name]['features']).sort!
18+
:features => result[instance_name]['features'].sort
2119
}
2220
instance = new(existing_instance)
2321
instances << instance
24-
end
2522
end
2623
instances
2724
end

lib/puppet_x/sqlserver/features.rb

Lines changed: 235 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,235 @@
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

lib/puppet_x/sqlserver/server_helper.rb

Lines changed: 1 addition & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,37 +1,11 @@
11
module PuppetX
22
module Sqlserver
33
class ServerHelper
4-
@features_hash = {
5-
:AS => 'Analysis Services',
6-
:RS => 'Reporting Services - Native',
7-
:SQLEngine => 'Database Engine Services',
8-
:Replication => 'SQL Server Replication',
9-
:FullText => 'Full-Text and Semantic Extractions for Search',
10-
:DQ => 'Data Quality Services',
11-
:BC => 'Client Tools Backwards Compatibility',
12-
:SSMS => 'Management Tools - Basic',
13-
:ADV_SSMS => 'Management Tools - Complete',
14-
:Conn => 'Client Tools Connectivity',
15-
:SDK => 'Client Tools SDK',
16-
:IS => 'Integration Services',
17-
:MDS => 'Master Data Services',
18-
}
194
@super_feature_hash = {
205
:SQL => [:DQ, :FullText, :Replication, :SQLEngine],
21-
:Tools => [:SSMS, :ADV_SSMS, :Conn]
6+
:Tools => [:BC, :SSMS, :ADV_SSMS, :Conn, :SDK]
227
}
238

24-
25-
def self.translate_features(features)
26-
translated = []
27-
Array.new(features).each do |feature|
28-
if @features_hash.has_value?(feature)
29-
translated << @features_hash.key(feature).to_s
30-
end
31-
end
32-
translated
33-
end
34-
359
def self.get_sub_features(super_feature)
3610
@super_feature_hash[super_feature.to_sym]
3711
end

0 commit comments

Comments
 (0)