2015-09-20 15:42:09 +00:00
|
|
|
# encoding: utf-8
|
|
|
|
|
2016-03-08 18:06:55 +00:00
|
|
|
module Inspec::Resources
|
|
|
|
class WindowsFeature < Inspec.resource(1)
|
|
|
|
name 'windows_feature'
|
2018-02-19 14:26:49 +00:00
|
|
|
supports platform: 'windows'
|
2016-03-08 18:06:55 +00:00
|
|
|
desc 'Use the windows_feature InSpec audit resource to test features on Microsoft Windows.'
|
2018-07-25 20:00:06 +00:00
|
|
|
example <<-EOX
|
|
|
|
# By default this resource will use Get-WindowsFeature.
|
|
|
|
# Failing that, it will use DISM.
|
|
|
|
|
|
|
|
# Get-WindowsFeature Example
|
|
|
|
describe windows_feature('Web-WebServer', :powershell) do
|
|
|
|
it { should be_installed }
|
|
|
|
end
|
|
|
|
|
|
|
|
# DISM Example
|
|
|
|
describe windows_feature('IIS-WebServer', :dism) do
|
|
|
|
it { should be_installed }
|
|
|
|
end
|
|
|
|
|
|
|
|
# Try PowerShell then DISM Example
|
|
|
|
describe windows_feature('IIS-WebServer') do
|
2016-03-08 18:06:55 +00:00
|
|
|
it { should be_installed }
|
|
|
|
end
|
2018-07-25 20:00:06 +00:00
|
|
|
EOX
|
2015-09-20 15:42:09 +00:00
|
|
|
|
2018-07-25 20:00:06 +00:00
|
|
|
def initialize(feature, method = nil)
|
2016-03-08 18:06:55 +00:00
|
|
|
@feature = feature
|
2018-07-25 20:00:06 +00:00
|
|
|
@method = method
|
2016-03-08 18:06:55 +00:00
|
|
|
@cache = nil
|
|
|
|
end
|
2015-09-20 15:42:09 +00:00
|
|
|
|
2016-03-08 18:06:55 +00:00
|
|
|
# returns true if the package is installed
|
2018-07-25 20:00:06 +00:00
|
|
|
def installed?
|
2016-03-08 18:06:55 +00:00
|
|
|
info[:installed] == true
|
|
|
|
end
|
2015-09-20 15:42:09 +00:00
|
|
|
|
2016-03-08 18:06:55 +00:00
|
|
|
# returns the package description
|
|
|
|
def info
|
|
|
|
return @cache if !@cache.nil?
|
2015-09-20 15:42:09 +00:00
|
|
|
|
2018-07-25 20:00:06 +00:00
|
|
|
case @method
|
|
|
|
when :powershell
|
|
|
|
@cache = info_via_powershell(@feature)
|
|
|
|
if @cache[:error]
|
|
|
|
# TODO: Allow handling `Inspec::Exception` outside of initialize
|
|
|
|
# See: https://github.com/inspec/inspec/issues/3237
|
|
|
|
# The below will fail the resource regardless of what is raised
|
|
|
|
raise Inspec::Exceptions::ResourceFailed, @cache[:error]
|
|
|
|
end
|
|
|
|
when :dism
|
|
|
|
@cache = info_via_dism(@feature)
|
|
|
|
else
|
|
|
|
@cache = info_via_powershell(@feature)
|
|
|
|
@cache = info_via_dism(@feature) if @cache[:error]
|
2016-03-08 18:06:55 +00:00
|
|
|
end
|
2015-09-20 15:42:09 +00:00
|
|
|
|
2018-07-25 20:00:06 +00:00
|
|
|
@cache
|
2016-03-08 18:06:55 +00:00
|
|
|
end
|
2015-09-20 15:42:09 +00:00
|
|
|
|
2016-03-08 18:06:55 +00:00
|
|
|
def to_s
|
|
|
|
"Windows Feature '#{@feature}'"
|
|
|
|
end
|
2018-07-25 20:00:06 +00:00
|
|
|
|
|
|
|
private
|
|
|
|
|
|
|
|
def info_via_dism(feature)
|
|
|
|
dism_command = "dism /online /get-featureinfo /featurename:#{feature}"
|
|
|
|
cmd = inspec.command(dism_command)
|
|
|
|
|
|
|
|
if cmd.exit_status != 0
|
|
|
|
feature_info = {
|
|
|
|
name: feature,
|
|
|
|
description: 'N/A',
|
|
|
|
installed: false,
|
|
|
|
}
|
|
|
|
else
|
|
|
|
result = cmd.stdout
|
|
|
|
feature_name_regex = /Feature Name : (.*)(\r\n|\n)/
|
|
|
|
description_regex = /Description : (.*)(\r\n|\n)/
|
|
|
|
feature_info = {
|
|
|
|
name: result.match(feature_name_regex).captures[0].chomp,
|
|
|
|
description: result.match(description_regex).captures[0].chomp,
|
|
|
|
installed: true,
|
|
|
|
}
|
|
|
|
end
|
|
|
|
|
|
|
|
feature_info[:method] = :dism
|
|
|
|
feature_info
|
|
|
|
end
|
|
|
|
|
|
|
|
def info_via_powershell(feature)
|
|
|
|
features_cmd = "Get-WindowsFeature | Where-Object {$_.Name -eq '#{feature}' -or $_.DisplayName -eq '#{feature}'} | Select-Object -Property Name,DisplayName,Description,Installed,InstallState | ConvertTo-Json"
|
|
|
|
cmd = inspec.command(features_cmd)
|
|
|
|
|
|
|
|
feature_info = {}
|
|
|
|
|
|
|
|
# The `Get-WindowsFeature` command is not available on the Windows
|
|
|
|
# non-server OS. This attempts to use the `dism` command to get the info.
|
|
|
|
if cmd.stderr =~ /The term 'Get-WindowsFeature' is not recognized/
|
|
|
|
feature_info[:name] = feature
|
|
|
|
feature_info[:error] = 'Could not find `Get-WindowsFeature`'
|
|
|
|
else
|
|
|
|
# We cannot rely on `cmd.exit_status != 0` because by default the
|
|
|
|
# command will exit 1 even on success. So, if we cannot parse the JSON
|
|
|
|
# we know that the feature is not installed.
|
|
|
|
begin
|
|
|
|
result = JSON.parse(cmd.stdout)
|
|
|
|
|
|
|
|
feature_info = {
|
|
|
|
name: result['Name'],
|
|
|
|
description: result['Description'],
|
|
|
|
installed: result['Installed'],
|
|
|
|
}
|
|
|
|
rescue JSON::ParserError => _e
|
|
|
|
feature_info[:name] = feature
|
|
|
|
feature_info[:installed] = false
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
feature_info[:method] = :powershell
|
|
|
|
feature_info
|
|
|
|
end
|
2015-09-20 15:42:09 +00:00
|
|
|
end
|
|
|
|
end
|