Update the mediawiki cookbook and upstream cookbooks

Compatibility with Chef 14
This commit is contained in:
Greg Karékinian
2019-04-08 11:20:12 +02:00
parent 6e3e8cde1b
commit 777b85c2ab
312 changed files with 5603 additions and 14219 deletions

View File

@@ -3,8 +3,8 @@
# Cookbook:: windows
# Resource:: auto_run
#
# Copyright:: 2011-2017, Business Intelligence Associates, Inc.
# Copyright:: 2017, Chef Software, Inc.
# Copyright:: 2011-2018, Business Intelligence Associates, Inc.
# Copyright:: 2017-2018, Chef Software Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@@ -19,28 +19,48 @@
# limitations under the License.
#
property :program, String
property :name, String, name_property: true
chef_version_for_provides '< 14.0' if respond_to?(:chef_version_for_provides)
resource_name :windows_auto_run
property :program_name, String, name_property: true
property :path, String, coerce: proc { |x| x.tr('/', '\\') }
property :args, String
property :root, Symbol,
equal_to: %i(machine user),
default: :machine
alias_method :program, :path
action :create do
registry_key 'HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Run' do
data = "\"#{new_resource.path}\""
data << " #{new_resource.args}" if new_resource.args
registry_key registry_path do
values [{
name: new_resource.name,
name: new_resource.program_name,
type: :string,
data: "\"#{new_resource.program}\" #{new_resource.args}",
data: data,
}]
action :create
end
end
action :remove do
registry_key 'HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Run' do
registry_key registry_path do
values [{
name: new_resource.name,
name: new_resource.program_name,
type: :string,
data: '',
}]
action :delete
end
end
action_class do
# determine the full registry path based on the root property
# @return [String]
def registry_path
{ machine: 'HKLM', user: 'HKCU' }[new_resource.root] + \
'\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Run'
end
end

View File

@@ -4,6 +4,7 @@
# Resource:: certificate
#
# Copyright:: 2015-2017, Calastone Ltd.
# Copyright:: 2018-2019, Chef Software, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@@ -18,29 +19,38 @@
# limitations under the License.
#
include Windows::Helper
require 'chef/util/path_helper'
property :source, String, name_property: true, required: true
chef_version_for_provides '< 14.7' if respond_to?(:chef_version_for_provides)
resource_name :windows_certificate
property :source, String, name_property: true
property :pfx_password, String
property :private_key_acl, Array
property :store_name, String, default: 'MY', regex: /^(?:MY|CA|ROOT|TrustedPublisher|TRUSTEDPEOPLE)$/
property :user_store, [true, false], default: false
property :store_name, String, default: 'MY', equal_to: ['TRUSTEDPUBLISHER', 'TrustedPublisher', 'CLIENTAUTHISSUER', 'REMOTE DESKTOP', 'ROOT', 'TRUSTEDDEVICES', 'WEBHOSTING', 'CA', 'AUTHROOT', 'TRUSTEDPEOPLE', 'MY', 'SMARTCARDROOT', 'TRUST', 'DISALLOWED']
property :user_store, [TrueClass, FalseClass], default: false
property :cert_path, String
property :sensitive, [ TrueClass, FalseClass ], default: lazy { |r| r.pfx_password ? true : false }
action :create do
hash = '$cert.GetCertHashString()'
code_script = cert_script(true) <<
within_store_script { |store| store + '.Add($cert)' } <<
acl_script(hash)
load_gem
guard_script = cert_script(false) <<
cert_exists_script(hash)
# Extension of the certificate
ext = ::File.extname(new_resource.source)
cert_obj = fetch_cert_object(ext) # Fetch OpenSSL::X509::Certificate object
thumbprint = OpenSSL::Digest::SHA1.new(cert_obj.to_der).to_s # Fetch its thumbprint
converge_by("adding certificate #{new_resource.source} into #{new_resource.store_name} to #{cert_location}\\#{new_resource.store_name}") do
powershell_script new_resource.name do
guard_interpreter :powershell_script
convert_boolean_return true
code code_script
not_if guard_script
# Need to check if return value is Boolean:true
# If not then the given certificate should be added in certstore
if verify_cert(thumbprint) == true
Chef::Log.debug('Certificate is already present')
else
converge_by("Adding certificate #{new_resource.source} into Store #{new_resource.store_name}") do
if ext == '.pfx'
add_pfx_cert
else
add_cert(cert_obj)
end
end
end
end
@@ -60,59 +70,146 @@ action :acl_add do
code_script << acl_script(hash)
guard_script << cert_exists_script(hash)
converge_by("setting the acls on #{new_resource.source} in #{cert_location}\\#{new_resource.store_name}") do
powershell_script new_resource.name do
guard_interpreter :powershell_script
convert_boolean_return true
code code_script
only_if guard_script
end
powershell_script "setting the acls on #{new_resource.source} in #{cert_location}\\#{new_resource.store_name}" do
guard_interpreter :powershell_script
convert_boolean_return true
code code_script
only_if guard_script
sensitive if new_resource.sensitive
end
end
action :delete do
# do we have a hash or a subject?
# TODO: It's a bit annoying to know the thumbprint of a cert you want to remove when you already
# have the file. Support reading the hash directly from the file if provided.
search = if new_resource.source =~ /^[a-fA-F0-9]{40}$/
"Thumbprint -eq '#{new_resource.source}'"
else
"Subject -like '*#{new_resource.source.sub(/\*/, '`*')}*'" # escape any * in the source
end
cert_command = "Get-ChildItem Cert:\\#{cert_location}\\#{new_resource.store_name} | where { $_.#{search} }"
load_gem
code_script = within_store_script do |store|
<<-EOH
foreach ($c in #{cert_command})
{
#{store}.Remove($c)
}
EOH
end
guard_script = "@(#{cert_command}).Count -gt 0\n"
converge_by("Removing certificate #{new_resource.source} from #{cert_location}\\#{new_resource.store_name}") do
powershell_script new_resource.name do
guard_interpreter :powershell_script
convert_boolean_return true
code code_script
only_if guard_script
cert_obj = fetch_cert
if cert_obj
converge_by("Deleting certificate #{new_resource.source} from Store #{new_resource.store_name}") do
delete_cert
end
else
Chef::Log.debug('Certificate not found')
end
end
action :fetch do
load_gem
cert_obj = fetch_cert
if cert_obj
show_or_store_cert(cert_obj)
else
Chef::Log.debug('Certificate not found')
end
end
action :verify do
load_gem
out = verify_cert
if !!out == out
out = out ? 'Certificate is valid' : 'Certificate not valid'
end
Chef::Log.info(out.to_s)
end
action_class do
require 'openssl'
# load the gem and rescue a gem install if it fails to load
def load_gem
gem 'win32-certstore', '>= 0.2.4'
require 'win32-certstore' # until this is in core chef
rescue LoadError
Chef::Log.debug('Did not find win32-certstore >= 0.2.4 gem installed. Installing now')
chef_gem 'win32-certstore' do
compile_time true
action :upgrade
end
require 'win32-certstore'
end
def add_cert(cert_obj)
store = ::Win32::Certstore.open(new_resource.store_name)
store.add(cert_obj)
end
def add_pfx_cert
store = ::Win32::Certstore.open(new_resource.store_name)
store.add_pfx(new_resource.source, new_resource.pfx_password)
end
def delete_cert
store = ::Win32::Certstore.open(new_resource.store_name)
store.delete(new_resource.source)
end
def fetch_cert
store = ::Win32::Certstore.open(new_resource.store_name)
store.get(new_resource.source)
end
# Checks whether a certificate with the given thumbprint
# is already present and valid in certificate store
# If the certificate is not present, verify_cert returns a String: "Certificate not found"
# But if it is present but expired, it returns a Boolean: false
# Otherwise, it returns a Boolean: true
def verify_cert(thumbprint = new_resource.source)
store = ::Win32::Certstore.open(new_resource.store_name)
store.valid?(thumbprint)
end
def show_or_store_cert(cert_obj)
if new_resource.cert_path
export_cert(cert_obj, new_resource.cert_path)
if ::File.size(new_resource.cert_path) > 0
Chef::Log.info("Certificate export in #{new_resource.cert_path}")
else
::File.delete(new_resource.cert_path)
end
else
Chef::Log.info(cert_obj.display)
end
end
def export_cert(cert_obj, cert_path)
out_file = ::File.new(cert_path, 'w+')
case ::File.extname(cert_path)
when '.pem'
out_file.puts(cert_obj.to_pem)
when '.der'
out_file.puts(cert_obj.to_der)
when '.cer'
cert_out = powershell_out("openssl x509 -text -inform DER -in #{cert_obj.to_pem} -outform CER").stdout
out_file.puts(cert_out)
when '.crt'
cert_out = powershell_out("openssl x509 -text -inform DER -in #{cert_obj.to_pem} -outform CRT").stdout
out_file.puts(cert_out)
when '.pfx'
cert_out = powershell_out("openssl pkcs12 -export -nokeys -in #{cert_obj.to_pem} -outform PFX").stdout
out_file.puts(cert_out)
when '.p7b'
cert_out = powershell_out("openssl pkcs7 -export -nokeys -in #{cert_obj.to_pem} -outform P7B").stdout
out_file.puts(cert_out)
else
Chef::Log.info('Supported certificate format .pem, .der, .cer, .crt, .pfx and .p7b')
end
out_file.close
end
def cert_location
@location ||= new_resource.user_store ? 'CurrentUser' : 'LocalMachine'
end
def cert_script(persist)
cert_script = '$cert = New-Object System.Security.Cryptography.X509Certificates.X509Certificate2'
file = win_friendly_path(new_resource.source)
file = Chef::Util::PathHelper.cleanpath(new_resource.source)
cert_script << " \"#{file}\""
if ::File.extname(file.downcase) == '.pfx'
cert_script << ", \"#{new_resource.pfx_password}\""
if persist && new_resource.user_store
cert_script << ', [System.Security.Cryptography.X509Certificates.X509KeyStorageFlags]::PersistKeySet'
cert_script << ', ([System.Security.Cryptography.X509Certificates.X509KeyStorageFlags]::PersistKeySet)'
elsif persist
cert_script << ', ([System.Security.Cryptography.X509Certificates.X509KeyStorageFlags]::PersistKeySet -bor [System.Security.Cryptography.X509Certificates.X509KeyStorageFlags]::MachineKeyset)'
end
@@ -122,45 +219,83 @@ action_class do
def cert_exists_script(hash)
<<-EOH
$hash = #{hash}
Test-Path "Cert:\\#{cert_location}\\#{new_resource.store_name}\\$hash"
EOH
$hash = #{hash}
Test-Path "Cert:\\#{cert_location}\\#{new_resource.store_name}\\$hash"
EOH
end
def within_store_script
inner_script = yield '$store'
<<-EOH
$store = New-Object System.Security.Cryptography.X509Certificates.X509Store "#{new_resource.store_name}", ([System.Security.Cryptography.X509Certificates.StoreLocation]::#{cert_location})
$store.Open([System.Security.Cryptography.X509Certificates.OpenFlags]::ReadWrite)
#{inner_script}
$store.Close()
EOH
$store = New-Object System.Security.Cryptography.X509Certificates.X509Store "#{new_resource.store_name}", ([System.Security.Cryptography.X509Certificates.StoreLocation]::#{cert_location})
$store.Open([System.Security.Cryptography.X509Certificates.OpenFlags]::ReadWrite)
#{inner_script}
$store.Close()
EOH
end
def acl_script(hash)
return '' if new_resource.private_key_acl.nil? || new_resource.private_key_acl.empty?
# this PS came from http://blogs.technet.com/b/operationsguy/archive/2010/11/29/provide-access-to-private-keys-commandline-vs-powershell.aspx
# and from https://msdn.microsoft.com/en-us/library/windows/desktop/bb204778(v=vs.85).aspx
set_acl_script = <<-EOH
$hash = #{hash}
$storeCert = Get-ChildItem "cert:\\#{cert_location}\\#{new_resource.store_name}\\$hash"
if ($storeCert -eq $null) { throw 'no key exists.' }
$keyname = $storeCert.PrivateKey.CspKeyContainerInfo.UniqueKeyContainerName
if ($keyname -eq $null) { throw 'no private key exists.' }
if ($storeCert.PrivateKey.CspKeyContainerInfo.MachineKeyStore)
{
$fullpath = "$Env:ProgramData\\Microsoft\\Crypto\\RSA\\MachineKeys\\$keyname"
}
else
{
$currentUser = New-Object System.Security.Principal.NTAccount($Env:UserDomain, $Env:UserName)
$userSID = $currentUser.Translate([System.Security.Principal.SecurityIdentifier]).Value
$fullpath = "$Env:ProgramData\\Microsoft\\Crypto\\RSA\\$userSID\\$keyname"
}
EOH
$hash = #{hash}
$storeCert = Get-ChildItem "cert:\\#{cert_location}\\#{new_resource.store_name}\\$hash"
if ($storeCert -eq $null) { throw 'no key exists.' }
$keyname = $storeCert.PrivateKey.CspKeyContainerInfo.UniqueKeyContainerName
if ($keyname -eq $null) { throw 'no private key exists.' }
if ($storeCert.PrivateKey.CspKeyContainerInfo.MachineKeyStore)
{
$fullpath = "$Env:ProgramData\\Microsoft\\Crypto\\RSA\\MachineKeys\\$keyname"
}
else
{
$currentUser = New-Object System.Security.Principal.NTAccount($Env:UserDomain, $Env:UserName)
$userSID = $currentUser.Translate([System.Security.Principal.SecurityIdentifier]).Value
$fullpath = "$Env:ProgramData\\Microsoft\\Crypto\\RSA\\$userSID\\$keyname"
}
EOH
new_resource.private_key_acl.each do |name|
set_acl_script << "$uname='#{name}'; icacls $fullpath /grant $uname`:RX\n"
end
set_acl_script
end
# Method returns an OpenSSL::X509::Certificate object
#
# Based on its extension, the certificate contents are used to initialize
# PKCS12 (PFX), PKCS7 (P7B) objects which contains OpenSSL::X509::Certificate.
#
# @note Other then PEM, all the certificates are usually in binary format, and hence
# their contents are loaded by using File.binread
#
# @param ext [String] Extension of the certificate
#
# @return [OpenSSL::X509::Certificate] Object containing certificate's attributes
#
# @raise [OpenSSL::PKCS12::PKCS12Error] When incorrect password is provided for PFX certificate
#
def fetch_cert_object(ext)
contents = if binary_cert?
::File.binread(new_resource.source)
else
::File.read(new_resource.source)
end
case ext
when '.pfx'
OpenSSL::PKCS12.new(contents, new_resource.pfx_password).certificate
when '.p7b'
OpenSSL::PKCS7.new(contents).certificates.first
else
OpenSSL::X509::Certificate.new(contents)
end
end
# @return [Boolean] Whether the certificate file is binary encoded or not
#
def binary_cert?
powershell_out!("file -b --mime-encoding #{new_resource.source}").stdout.strip == 'binary'
end
end

View File

@@ -4,6 +4,7 @@
# Resource:: certificate_binding
#
# Copyright:: 2015-2017, Calastone Ltd.
# Copyright:: 2018, Chef Software, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@@ -18,20 +19,20 @@
# limitations under the License.
#
include Chef::Mixin::ShellOut
include Chef::Mixin::PowershellOut
include Windows::Helper
property :cert_name, String, name_property: true, required: true
property :cert_name, String, name_property: true
property :name_kind, Symbol, equal_to: [:hash, :subject], default: :subject
property :address, String, default: '0.0.0.0'
property :port, Integer, default: 443
property :app_id, String, default: '{4dc3e181-e14b-4a21-b022-59fc669b0914}'
property :store_name, String, default: 'MY', regex: /^(?:MY|CA|ROOT)$/
property :store_name, String, default: 'MY', equal_to: ['TRUSTEDPUBLISHER', 'CLIENTAUTHISSUER', 'REMOTE DESKTOP', 'ROOT', 'TRUSTEDDEVICES', 'WEBHOSTING', 'CA', 'AUTHROOT', 'TRUSTEDPEOPLE', 'MY', 'SMARTCARDROOT', 'TRUST']
property :exists, [true, false], desired_state: true
load_current_value do |desired|
cmd = shell_out("#{locate_sysnative_cmd('netsh.exe')} http show sslcert ipport=#{desired.address}:#{desired.port}")
mode = desired.address.match(/(\d+\.){3}\d+|\[.+\]/).nil? ? 'hostnameport' : 'ipport'
cmd = shell_out("#{locate_sysnative_cmd('netsh.exe')} http show sslcert #{mode}=#{desired.address}:#{desired.port}")
Chef::Log.debug "netsh reports: #{cmd.stdout}"
address desired.address
@@ -88,7 +89,8 @@ action_class do
def add_binding(hash)
cmd = "#{netsh_command} http add sslcert"
cmd << " ipport=#{current_resource.address}:#{current_resource.port}"
mode = address_mode(current_resource.address)
cmd << " #{mode}=#{current_resource.address}:#{current_resource.port}"
cmd << " certhash=#{hash}"
cmd << " appid=#{current_resource.app_id}"
cmd << " certstorename=#{current_resource.store_name}"
@@ -98,7 +100,8 @@ action_class do
end
def delete_binding
shell_out!("#{netsh_command} http delete sslcert ipport=#{current_resource.address}:#{current_resource.port}")
mode = address_mode(current_resource.address)
shell_out!("#{netsh_command} http delete sslcert #{mode}=#{current_resource.address}:#{current_resource.port}")
end
def check_hash(hash)
@@ -125,4 +128,8 @@ action_class do
hash = p.stdout.strip
hash[0].ord == 239 ? hash.force_encoding('UTF-8').delete!("\xEF\xBB\xBF".force_encoding('UTF-8')) : hash
end
def address_mode(address)
address.match(/(\d+\.){3}\d+|\[.+\]/).nil? ? 'hostnameport' : 'ipport'
end
end

View File

@@ -0,0 +1,30 @@
#
# Author:: Richard Lavey (richard.lavey@calastone.com)
# Cookbook Name:: windows
# Resource:: dns
#
# Copyright:: 2015, Calastone Ltd.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
actions :create, :delete
default_action :create
attribute :host_name, kind_of: String, name_property: true, required: true
attribute :record_type, kind_of: String, default: 'A', regex: /^(?:A|CNAME)$/
attribute :dns_server, kind_of: String, default: '.'
attribute :target, kind_of: [Array, String], required: true
attribute :ttl, kind_of: Integer, required: false, default: 0
attr_accessor :exists

View File

@@ -3,7 +3,7 @@
# Cookbook:: windows
# Resource:: feature
#
# Copyright:: 2011-2017, Chef Software, Inc.
# Copyright:: 2011-2018, Chef Software Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@@ -18,65 +18,42 @@
# limitations under the License.
#
chef_version_for_provides '< 14.0' if respond_to?(:chef_version_for_provides)
resource_name :windows_feature
property :feature_name, [Array, String], name_property: true
property :source, String
property :all, [true, false], default: false
property :management_tools, [true, false], default: false
property :install_method, Symbol, equal_to: [:windows_feature_dism, :windows_feature_powershell, :windows_feature_servermanagercmd]
include Windows::Helper
def whyrun_supported?
true
end
property :timeout, Integer, default: 600
action :install do
run_default_provider :install
run_default_subresource :install
end
action :remove do
run_default_provider :remove
run_default_subresource :remove
end
action :delete do
run_default_provider :delete
run_default_subresource :delete
end
action_class do
def locate_default_provider
if new_resource.install_method
new_resource.install_method
elsif ::File.exist?(locate_sysnative_cmd('dism.exe'))
:windows_feature_dism
elsif ::File.exist?(locate_sysnative_cmd('servermanagercmd.exe'))
:windows_feature_servermanagercmd
else
:windows_feature_powershell
end
end
# call the appropriate windows_feature resource based on the specified subresource
# @return [void]
def run_default_subresource(desired_action)
raise 'Support for Windows feature installation via servermanagercmd.exe has been removed as this support is no longer needed in Windows 2008 R2 and above. You will need to update your cookbook to install either via dism or powershell (preferred).' if new_resource.install_method == :windows_feature_servermanagercmd
def run_default_provider(desired_action)
case locate_default_provider
when :windows_feature_dism
windows_feature_dism new_resource.name do
action desired_action
feature_name new_resource.feature_name
source new_resource.source if new_resource.source
all new_resource.all
end
when :windows_feature_servermanagercmd
windows_feature_servermanagercmd new_resource.name do
action desired_action
feature_name new_resource.feature_name
source new_resource.source if new_resource.source
all new_resource.all
end
when :windows_feature_powershell
windows_feature_powershell new_resource.name do
action desired_action
feature_name new_resource.feature_name
source new_resource.source if new_resource.source
all new_resource.all
end
subresource = new_resource.install_method || :windows_feature_dism
declare_resource(subresource, new_resource.name) do
action desired_action
feature_name new_resource.feature_name
source new_resource.source if new_resource.source
all new_resource.all
timeout new_resource.timeout
management_tools new_resource.management_tools if subresource == :windows_feature_powershell
end
end
end

View File

@@ -1,9 +1,9 @@
#
# Author:: Seth Chisamore (<schisamo@chef.io>)
# Cookbook:: windows
# Provider:: feature_dism
# Resource:: feature_dism
#
# Copyright:: 2011-2017, Chef Software, Inc.
# Copyright:: 2011-2018, Chef Software Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@@ -18,87 +18,191 @@
# limitations under the License.
#
property :feature_name, [Array, String], name_property: true
chef_version_for_provides '< 14.0' if respond_to?(:chef_version_for_provides)
resource_name :windows_feature_dism
property :feature_name, [Array, String], coerce: proc { |x| to_formatted_array(x) }, name_property: true
property :source, String
property :all, [true, false], default: false
property :timeout, Integer, default: 600
# @return [Array] lowercase the array unless we're on < Windows 2012
def to_formatted_array(x)
x = x.split(/\s*,\s*/) if x.is_a?(String) # split multiple forms of a comma separated list
# feature installs on windows < 2012 are case sensitive so only downcase when on 2012+
# @todo when we're really ready to remove support for Windows 2008 R2 this check can go away
older_than_2012_or_8? ? x : x.map(&:downcase)
end
# a simple helper to determine if we're on a windows release pre-2012 / 8
# @return [Boolean] Is the system older than Windows 8 / 2012
def older_than_2012_or_8?
node['platform_version'].to_f < 6.2
end
include Chef::Mixin::ShellOut
include Windows::Helper
action :install do
Chef::Log.warn("Requested feature #{new_resource.feature_name} is not available on this system.") unless available?
unless !available? || installed?
converge_by("install Windows feature #{new_resource.feature_name}") do
addsource = new_resource.source ? "/LimitAccess /Source:\"#{new_resource.source}\"" : ''
addall = new_resource.all ? '/All' : ''
shell_out!("#{dism} /online /enable-feature #{to_array(new_resource.feature_name).map { |feature| "/featurename:#{feature}" }.join(' ')} /norestart #{addsource} #{addall}", returns: [0, 42, 127, 3010])
# Reload ohai data
reload_ohai_features_plugin(new_resource.action, new_resource.feature_name)
reload_cached_dism_data unless node['dism_features_cache']
fail_if_unavailable # fail if the features don't exist
Chef::Log.debug("Windows features needing installation: #{features_to_install.empty? ? 'none' : features_to_install.join(',')}")
unless features_to_install.empty?
message = "install Windows feature#{'s' if features_to_install.count > 1} #{features_to_install.join(',')}"
converge_by(message) do
install_command = "#{dism} /online /enable-feature #{features_to_install.map { |f| "/featurename:#{f}" }.join(' ')} /norestart"
install_command << " /LimitAccess /Source:\"#{new_resource.source}\"" if new_resource.source
install_command << ' /All' if new_resource.all
begin
shell_out!(install_command, returns: [0, 42, 127, 3010], timeout: new_resource.timeout)
rescue Mixlib::ShellOut::ShellCommandFailed => e
raise "Error 50 returned by DISM related to parent features, try setting the 'all' property to 'true' on the 'windows_feature_dism' resource." if required_parent_feature?(e.inspect)
raise e.message
end
reload_cached_dism_data # Reload cached dism feature state
end
end
end
action :remove do
if installed?
converge_by("removing Windows feature #{new_resource.feature_name}") do
shell_out!("#{dism} /online /disable-feature #{to_array(new_resource.feature_name).map { |feature| "/featurename:#{feature}" }.join(' ')} /norestart", returns: [0, 42, 127, 3010])
# Reload ohai data
reload_ohai_features_plugin(new_resource.action, new_resource.feature_name)
reload_cached_dism_data unless node['dism_features_cache']
Chef::Log.debug("Windows features needing removal: #{features_to_remove.empty? ? 'none' : features_to_remove.join(',')}")
unless features_to_remove.empty?
message = "remove Windows feature#{'s' if features_to_remove.count > 1} #{features_to_remove.join(',')}"
converge_by(message) do
shell_out!("#{dism} /online /disable-feature #{features_to_remove.map { |f| "/featurename:#{f}" }.join(' ')} /norestart", returns: [0, 42, 127, 3010], timeout: new_resource.timeout)
reload_cached_dism_data # Reload cached dism feature state
end
end
end
action :delete do
raise Chef::Exceptions::UnsupportedAction, "#{self} :delete action not support on #{win_version.sku}" unless supports_feature_delete?
if available?
converge_by("deleting Windows feature #{new_resource.feature_name} from the image") do
shell_out!("#{dism} /online /disable-feature #{to_array(new_resource.feature_name).map { |feature| "/featurename:#{feature}" }.join(' ')} /Remove /norestart", returns: [0, 42, 127, 3010])
# Reload ohai data
reload_ohai_features_plugin(new_resource.action, new_resource.feature_name)
raise_if_delete_unsupported
reload_cached_dism_data unless node['dism_features_cache']
fail_if_unavailable # fail if the features don't exist
Chef::Log.debug("Windows features needing deletion: #{features_to_delete.empty? ? 'none' : features_to_delete.join(',')}")
unless features_to_delete.empty?
message = "delete Windows feature#{'s' if features_to_delete.count > 1} #{features_to_delete.join(',')} from the image"
converge_by(message) do
shell_out!("#{dism} /online /disable-feature #{features_to_delete.map { |f| "/featurename:#{f}" }.join(' ')} /Remove /norestart", returns: [0, 42, 127, 3010], timeout: new_resource.timeout)
reload_cached_dism_data # Reload cached dism feature state
end
end
end
action_class do
def installed?
@installed ||= begin
install_ohai_plugin unless node['dism_features']
# @return [Array] features the user has requested to install which need installation
def features_to_install
@install ||= begin
# disabled features are always available to install
available_for_install = node['dism_features_cache']['disabled'].dup
# Compare against ohai plugin instead of costly dism run
node['dism_features'].key?(new_resource.feature_name) && node['dism_features'][new_resource.feature_name] =~ /Enable/
# removed features are also available for installation
available_for_install.concat(node['dism_features_cache']['removed'])
# the intersection of the features to install & disabled/removed features are what needs installing
new_resource.feature_name & available_for_install
end
end
def available?
@available ||= begin
install_ohai_plugin unless node['dism_features']
# @return [Array] features the user has requested to remove which need removing
def features_to_remove
# the intersection of the features to remove & enabled features are what needs removing
@remove ||= new_resource.feature_name & node['dism_features_cache']['enabled']
end
# Compare against ohai plugin instead of costly dism run
node['dism_features'].key?(new_resource.feature_name) && node['dism_features'][new_resource.feature_name] !~ /with payload removed/
# @return [Array] features the user has requested to delete which need deleting
def features_to_delete
# the intersection of the features to remove & enabled/disabled features are what needs removing
@remove ||= begin
all_available = node['dism_features_cache']['enabled'] +
node['dism_features_cache']['disabled']
new_resource.feature_name & all_available
end
end
def reload_ohai_features_plugin(take_action, feature_name)
ohai "Reloading Dism_Features Plugin - Action #{take_action} of feature #{feature_name}" do
action :reload
plugin 'dism_features'
# if any features are not supported on this release of Windows or
# have been deleted raise with a friendly message. At one point in time
# we just warned, but this goes against the behavior of ever other package
# provider in Chef and it isn't clear what you'd want if you passed an array
# and some features were available and others were not.
# @return [void]
def fail_if_unavailable
all_available = node['dism_features_cache']['enabled'] +
node['dism_features_cache']['disabled'] +
node['dism_features_cache']['removed']
# the difference of desired features to install to all features is what's not available
unavailable = (new_resource.feature_name - all_available)
raise "The Windows feature#{'s' if unavailable.count > 1} #{unavailable.join(',')} #{unavailable.count > 1 ? 'are' : 'is'} not available on this version of Windows. Run 'dism /online /Get-Features' to see the list of available feature names." unless unavailable.empty?
end
# run dism.exe to get a list of all available features and their state
# and save that to the node at node.override level.
# We do this because getting a list of features in dism takes at least a second
# and this data will be persisted across multiple resource runs which gives us
# a much faster run when no features actually need to be installed / removed.
# @return [void]
def reload_cached_dism_data
Chef::Log.debug('Caching Windows features available via dism.exe.')
node.override['dism_features_cache'] = Mash.new
node.override['dism_features_cache']['enabled'] = []
node.override['dism_features_cache']['disabled'] = []
node.override['dism_features_cache']['removed'] = []
# Grab raw feature information from dism command line
raw_list_of_features = shell_out("#{dism} /Get-Features /Online /Format:Table /English").stdout
# Split stdout into an array by windows line ending
features_list = raw_list_of_features.split("\r\n")
features_list.each do |feature_details_raw|
case feature_details_raw
when /Payload Removed/ # matches 'Disabled with Payload Removed'
add_to_feature_mash('removed', feature_details_raw)
when /Enable/ # matches 'Enabled' and 'Enable Pending' aka after reboot
add_to_feature_mash('enabled', feature_details_raw)
when /Disable/ # matches 'Disabled' and 'Disable Pending' aka after reboot
add_to_feature_mash('disabled', feature_details_raw)
end
end
Chef::Log.debug("The dism cache contains\n#{node['dism_features_cache']}")
end
def install_ohai_plugin
Chef::Log.info("node['dism_features'] data missing. Installing the dism_features Ohai plugin")
# parse the feature string and add the values to the appropriate array
# in the
# strips trailing whitespace characters then split on n number of spaces
# + | + n number of spaces
# @return [void]
def add_to_feature_mash(feature_type, feature_string)
feature_details = feature_string.strip.split(/\s+[|]\s+/).first
ohai_plugin 'dism_features' do
compile_time true
cookbook 'windows'
end
# dism on windows 2012+ isn't case sensitive so it's best to compare
# lowercase lists so the user input doesn't need to be case sensitive
# @todo when we're ready to remove windows 2008R2 the gating here can go away
feature_details.downcase! unless older_than_2012_or_8?
node.override['dism_features_cache'][feature_type] << feature_details
end
def supports_feature_delete?
win_version.major_version >= 6 && win_version.minor_version >= 2
# Fail unless we're on windows 8+ / 2012+ where deleting a feature is supported
# @return [void]
def raise_if_delete_unsupported
raise Chef::Exceptions::UnsupportedAction, "#{self} :delete action not support on Windows releases before Windows 8/2012. Cannot continue!" if older_than_2012_or_8?
end
# account for File System Redirector
def required_parent_feature?(error_message)
error_message.include?('Error: 50') && error_message.include?('required parent feature')
end
# find dism accounting for File System Redirector
# http://msdn.microsoft.com/en-us/library/aa384187(v=vs.85).aspx
def dism
@dism ||= begin

View File

@@ -1,70 +1,242 @@
#
# Author:: Greg Zapp (<greg.zapp@gmail.com>)
# Cookbook:: windows
# Provider:: feature_powershell
# Resource:: feature_powershell
#
# Copyright:: 2015-2018, Chef Software, Inc
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
property :feature_name, [Array, String], name_attribute: true
require 'chef/json_compat'
chef_version_for_provides '< 14.0' if respond_to?(:chef_version_for_provides)
resource_name :windows_feature_powershell
property :feature_name, [Array, String], coerce: proc { |x| to_formatted_array(x) }, name_property: true
property :source, String
property :all, [true, false], default: false
property :all, [TrueClass, FalseClass], default: false
property :timeout, Integer, default: 600
property :management_tools, [TrueClass, FalseClass], default: false
# a simple helper to determine if we're on a windows release pre-2012 / 8
# @return [Boolean] Is the system older than Windows 8 / 2012
def older_than_2012_or_8?
node['platform_version'].to_f < 6.2
end
def to_formatted_array(x)
x = x.split(/\s*,\s*/) if x.is_a?(String) # split multiple forms of a comma separated list
# feature installs on windows < 8/2012 are case sensitive so only downcase when on 2012+
older_than_2012_or_8? ? x : x.map(&:downcase)
end
include Chef::Mixin::PowershellOut
include Windows::Helper
action :install do
raise_on_old_powershell
reload_cached_powershell_data unless node['powershell_features_cache']
fail_if_unavailable # fail if the features don't exist
fail_if_removed # fail if the features are in removed state
Chef::Log.debug("Windows features needing installation: #{features_to_install.empty? ? 'none' : features_to_install.join(',')}")
unless features_to_install.empty?
converge_by("install Windows feature#{'s' if features_to_install.count > 1} #{features_to_install.join(',')}") do
install_command = "#{install_feature_cmdlet} #{features_to_install.join(',')}"
install_command << ' -IncludeAllSubFeature' if new_resource.all
if older_than_2012_or_8? && (new_resource.source || new_resource.management_tools)
Chef::Log.warn("The 'source' and 'management_tools' properties are only available on Windows 8/2012 or greater. Skipping these properties!")
else
install_command << " -Source \"#{new_resource.source}\"" if new_resource.source
install_command << ' -IncludeManagementTools' if new_resource.management_tools
end
cmd = powershell_out!(install_command, timeout: new_resource.timeout)
Chef::Log.info(cmd.stdout)
reload_cached_powershell_data # Reload cached powershell feature state
end
end
end
action :remove do
if installed?
converge_by("remove Windows feature #{new_resource.feature_name}") do
cmd = powershell_out!("#{remove_feature_cmdlet} #{to_array(new_resource.feature_name).join(',')}")
raise_on_old_powershell
reload_cached_powershell_data unless node['powershell_features_cache']
Chef::Log.debug("Windows features needing removal: #{features_to_remove.empty? ? 'none' : features_to_remove.join(',')}")
unless features_to_remove.empty?
converge_by("remove Windows feature#{'s' if features_to_remove.count > 1} #{features_to_remove.join(',')}") do
cmd = powershell_out!("#{remove_feature_cmdlet} #{features_to_remove.join(',')}", timeout: new_resource.timeout)
Chef::Log.info(cmd.stdout)
reload_cached_powershell_data # Reload cached powershell feature state
end
end
end
action :delete do
if available?
converge_by("delete Windows feature #{new_resource.feature_name} from the image") do
cmd = powershell_out!("Uninstall-WindowsFeature #{to_array(new_resource.feature_name).join(',')} -Remove")
raise_on_old_powershell
raise_if_delete_unsupported
reload_cached_powershell_data unless node['powershell_features_cache']
fail_if_unavailable # fail if the features don't exist
Chef::Log.debug("Windows features needing deletion: #{features_to_delete.empty? ? 'none' : features_to_delete.join(',')}")
unless features_to_delete.empty?
converge_by("delete Windows feature#{'s' if features_to_delete.count > 1} #{features_to_delete.join(',')} from the image") do
cmd = powershell_out!("Uninstall-WindowsFeature #{features_to_delete.join(',')} -Remove", timeout: new_resource.timeout)
Chef::Log.info(cmd.stdout)
reload_cached_powershell_data # Reload cached powershell feature state
end
end
end
action_class do
# shellout to determine the actively installed version of powershell
# we have this same data in ohai, but it doesn't get updated if powershell is installed mid run
# @return [Integer] the powershell version or 0 for nothing
def powershell_version
cmd = powershell_out('$PSVersionTable.psversion.major')
return 1 if cmd.stdout.empty? # PowerShell 1.0 doesn't have a $PSVersionTable
Regexp.last_match(1).to_i if cmd.stdout =~ /^(\d+)/
rescue Errno::ENOENT
0 # zero as in nothing is installed
end
# raise if we're running powershell less than 3.0 since we need convertto-json
# check the powershell version via ohai data and if we're < 3.0 also shellout to make sure as
# a newer version could be installed post ohai run. Yes we're double checking. It's fine.
# @todo this can go away when we fully remove support for Windows 2008 R2
# @raise [RuntimeError] Raise if powershell is < 3.0
def raise_on_old_powershell
# be super defensive about the powershell lang plugin not being there
return if node['languages'] && node['languages']['powershell'] && node['languages']['powershell']['version'].to_i >= 3
raise 'The windows_feature_powershell resource requires PowerShell 3.0 or later. Please install PowerShell 3.0+ before running this resource.' if powershell_version < 3
end
# The appropirate cmdlet to install a windows feature based on windows release
# @return [String]
def install_feature_cmdlet
node['os_version'].to_f < 6.2 ? 'Import-Module ServerManager; Add-WindowsFeature' : 'Install-WindowsFeature'
older_than_2012_or_8? ? 'Add-WindowsFeature' : 'Install-WindowsFeature'
end
# The appropirate cmdlet to remove a windows feature based on windows release
# @return [String]
def remove_feature_cmdlet
node['os_version'].to_f < 6.2 ? 'Import-Module ServerManager; Remove-WindowsFeature' : 'Uninstall-WindowsFeature'
older_than_2012_or_8? ? 'Remove-WindowsFeature' : 'Uninstall-WindowsFeature'
end
def installed?
@installed ||= begin
cmd = powershell_out("(Get-WindowsFeature #{to_array(new_resource.feature_name).join(',')} | ?{$_.InstallState -ne \'Installed\'}).count")
cmd.stderr.empty? && cmd.stdout.chomp.to_i == 0
# @return [Array] features the user has requested to install which need installation
def features_to_install
# the intersection of the features to install & disabled features are what needs installing
@install ||= new_resource.feature_name & node['powershell_features_cache']['disabled']
end
# @return [Array] features the user has requested to remove which need removing
def features_to_remove
# the intersection of the features to remove & enabled features are what needs removing
@remove ||= new_resource.feature_name & node['powershell_features_cache']['enabled']
end
# @return [Array] features the user has requested to delete which need deleting
def features_to_delete
# the intersection of the features to remove & enabled/disabled features are what needs removing
@remove ||= begin
all_available = node['powershell_features_cache']['enabled'] +
node['powershell_features_cache']['disabled']
new_resource.feature_name & all_available
end
end
def available?
@available ||= begin
cmd = powershell_out("(Get-WindowsFeature #{to_array(new_resource.feature_name).join(',')} | ?{$_.InstallState -ne \'Removed\'}).count")
cmd.stderr.empty? && cmd.stdout.chomp.to_i > 0
end
end
end
action :install do
Chef::Log.warn("Requested feature #{new_resource.feature_name} is not available on this system.") unless available?
unless !available? || installed?
converge_by("install Windows feature #{new_resource.feature_name}") do
addsource = new_resource.source ? "-Source \"#{new_resource.source}\"" : ''
addall = new_resource.all ? '-IncludeAllSubFeature' : ''
cmd = if node['os_version'].to_f < 6.2
powershell_out!("#{install_feature_cmdlet} #{to_array(new_resource.feature_name).join(',')} #{addall}")
else
powershell_out!("#{install_feature_cmdlet} #{to_array(new_resource.feature_name).join(',')} #{addsource} #{addall}")
end
Chef::Log.info(cmd.stdout)
# if any features are not supported on this release of Windows or
# have been deleted raise with a friendly message. At one point in time
# we just warned, but this goes against the behavior of ever other package
# provider in Chef and it isn't clear what you'd want if you passed an array
# and some features were available and others were not.
# @return [void]
def fail_if_unavailable
all_available = node['powershell_features_cache']['enabled'] +
node['powershell_features_cache']['disabled'] +
node['powershell_features_cache']['removed']
# the difference of desired features to install to all features is what's not available
unavailable = (new_resource.feature_name - all_available)
raise "The Windows feature#{'s' if unavailable.count > 1} #{unavailable.join(',')} #{unavailable.count > 1 ? 'are' : 'is'} not available on this version of Windows. Run 'Get-WindowsFeature' to see the list of available feature names." unless unavailable.empty?
end
# run Get-WindowsFeature to get a list of all available features and their state
# and save that to the node at node.override level.
# @return [void]
def reload_cached_powershell_data
Chef::Log.debug('Caching Windows features available via Get-WindowsFeature.')
node.override['powershell_features_cache'] = Mash.new
node.override['powershell_features_cache']['enabled'] = []
node.override['powershell_features_cache']['disabled'] = []
node.override['powershell_features_cache']['removed'] = []
parsed_feature_list.each do |feature_details_raw|
case feature_details_raw['InstallState']
when 5 # matches 'Removed' InstallState
add_to_feature_mash('removed', feature_details_raw['Name'])
when 1, 3 # matches 'Installed' or 'InstallPending' states
add_to_feature_mash('enabled', feature_details_raw['Name'])
when 0, 2 # matches 'Available' or 'UninstallPending' states
add_to_feature_mash('disabled', feature_details_raw['Name'])
end
end
Chef::Log.debug("The powershell cache contains\n#{node['powershell_features_cache']}")
end
# fetch the list of available feature names and state in JSON and parse the JSON
def parsed_feature_list
# Grab raw feature information from dism command line
# Windows < 2012 doesn't present a state value so we have to check if the feature is installed or not
raw_list_of_features = if older_than_2012_or_8? # make the older format look like the new format, warts and all
powershell_out!('Get-WindowsFeature | Select-Object -Property Name, @{Name=\"InstallState\"; Expression = {If ($_.Installed) { 1 } Else { 0 }}} | ConvertTo-Json -Compress', timeout: new_resource.timeout).stdout
else
powershell_out!('Get-WindowsFeature | Select-Object -Property Name,InstallState | ConvertTo-Json -Compress', timeout: new_resource.timeout).stdout
end
Chef::JSONCompat.from_json(raw_list_of_features)
end
# add the features values to the appropriate array
# @return [void]
def add_to_feature_mash(feature_type, feature_details)
# add the lowercase feature name to the mash unless we're on < 2012 where they're case sensitive
node.override['powershell_features_cache'][feature_type] << (older_than_2012_or_8? ? feature_details : feature_details.downcase)
end
# Fail if any of the packages are in a removed state
# @return [void]
def fail_if_removed
return if new_resource.source # if someone provides a source then all is well
if node['platform_version'].to_f > 6.2 # 2012R2 or later
return if registry_key_exists?('HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\Servicing') && registry_value_exists?('HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\Servicing', name: 'LocalSourcePath') # if source is defined in the registry, still fine
end
removed = new_resource.feature_name & node['powershell_features_cache']['removed']
raise "The Windows feature#{'s' if removed.count > 1} #{removed.join(',')} #{removed.count > 1 ? 'are' : 'is'} have been removed from the host and cannot be installed." unless removed.empty?
end
# Fail unless we're on windows 8+ / 2012+ where deleting a feature is supported
def raise_if_delete_unsupported
raise Chef::Exceptions::UnsupportedAction, "#{self} :delete action not supported on Windows releases before Windows 8/2012. Cannot continue!" if older_than_2012_or_8?
end
end

View File

@@ -1,76 +0,0 @@
#
# Author:: Seth Chisamore (<schisamo@chef.io>)
# Cookbook:: windows
# Provider:: feature_servermanagercmd
#
# Copyright:: 2011-2017, Chef Software, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
property :feature_name, [Array, String], name_attribute: true
property :source, String
property :all, [true, false], default: false
include Chef::Mixin::ShellOut
include Windows::Helper
action :install do
unless installed?
converge_by("install Windows feature #{new_resource.feature_name}") do
check_reboot(shell_out("#{servermanagercmd} -install #{to_array(new_resource.feature_name).join(' ')}", returns: [0, 42, 127, 1003, 3010]), new_resource.feature_name)
end
end
end
action :remove do
if installed?
converge_by("removing Windows feature #{new_resource.feature_name}") do
check_reboot(shell_out("#{servermanagercmd} -remove #{to_array(new_resource.feature_name).join(' ')}", returns: [0, 42, 127, 1003, 3010]), new_resource.feature_name)
end
end
end
action :delete do
Chef::Log.warn('servermanagercmd does not support removing a feature from the image.')
end
# Exit codes are listed at http://technet.microsoft.com/en-us/library/cc749128(v=ws.10).aspx
action_class do
def check_reboot(result, feature)
if result.exitstatus == 3010 # successful, but needs reboot
node.run_state['reboot_requested'] = true
Chef::Log.warn("Require reboot to install #{feature}")
elsif result.exitstatus == 1001 # failure, but needs reboot before we can do anything else
node.run_state['reboot_requested'] = true
Chef::Log.warn("Failed installing #{feature} and need to reboot")
end
result.error! # throw for any other bad results. The above results will also get raised, and should cause a reboot via the handler.
end
def installed?
@installed ||= begin
cmd = shell_out("#{servermanagercmd} -query", returns: [0, 42, 127, 1003])
cmd.stderr.empty? && (cmd.stdout =~ /^\s*?\[X\]\s.+?\s\[#{new_resource.feature_name}\]\s*$/i)
end
end
# account for File System Redirector
# http://msdn.microsoft.com/en-us/library/aa384187(v=vs.85).aspx
def servermanagercmd
@servermanagercmd ||= begin
locate_sysnative_cmd('servermanagercmd.exe')
end
end
end

View File

@@ -3,8 +3,8 @@
# Cookbook:: windows
# Resource:: font
#
# Copyright:: 2014-2017, Schuberg Philis BV.
# Copyright:: 2017, Chef Software, Inc.
# Copyright:: 2014-2018, Schuberg Philis BV.
# Copyright:: 2017-2018, Chef Software Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@@ -19,14 +19,17 @@
# limitations under the License.
#
property :name, String, name_property: true
property :source, String, required: false
require 'chef/util/path_helper'
include Windows::Helper
chef_version_for_provides '< 14.0' if respond_to?(:chef_version_for_provides)
resource_name :windows_font
property :font_name, String, name_property: true
property :source, String, required: false, coerce: proc { |x| x =~ /^.:.*/ ? x.tr('\\', '/').gsub('//', '/') : x }
action :install do
if font_exists?
Chef::Log.debug("Not installing font: #{new_resource.name}, font already installed.")
Chef::Log.debug("Not installing font: #{new_resource.font_name} as font already installed.")
else
retrieve_cookbook_font
install_font
@@ -35,46 +38,74 @@ action :install do
end
action_class do
# if a source is specified fetch using remote_file. If not use cookbook_file
def retrieve_cookbook_font
font_file = new_resource.name
font_file = new_resource.font_name
if new_resource.source
remote_file font_file do
action :nothing
source "file://#{new_resource.source}"
path win_friendly_path(::File.join(ENV['TEMP'], font_file))
action :nothing
source source_uri
path Chef::Util::PathHelper.join(ENV['TEMP'], font_file)
end.run_action(:create)
else
cookbook_file font_file do
action :nothing
cookbook cookbook_name.to_s unless cookbook_name.nil?
path win_friendly_path(::File.join(ENV['TEMP'], font_file))
path Chef::Util::PathHelper.join(ENV['TEMP'], font_file)
end.run_action(:create)
end
end
# delete the temp cookbook file
def del_cookbook_font
file ::File.join(ENV['TEMP'], new_resource.name) do
file Chef::Util::PathHelper.join(ENV['TEMP'], new_resource.font_name) do
action :delete
end
end
# install the font into the appropriate fonts directory
def install_font
require 'win32ole' if RUBY_PLATFORM =~ /mswin|mingw32|windows/
fonts_dir = WIN32OLE.new('WScript.Shell').SpecialFolders('Fonts')
folder = WIN32OLE.new('Shell.Application').Namespace(fonts_dir)
converge_by("install font #{new_resource.name}") do
folder.CopyHere(win_friendly_path(::File.join(ENV['TEMP'], new_resource.name)))
converge_by("install font #{new_resource.font_name} to #{fonts_dir}") do
folder.CopyHere(Chef::Util::PathHelper.join(ENV['TEMP'], new_resource.font_name))
end
end
# Check to see if the font is installed
# Check to see if the font is installed in the fonts dir
#
# === Returns
# <true>:: If the font is installed
# <false>:: If the font is not instaled
# @return [Boolean] Is the font is installed?
def font_exists?
require 'win32ole' if RUBY_PLATFORM =~ /mswin|mingw32|windows/
fonts_dir = WIN32OLE.new('WScript.Shell').SpecialFolders('Fonts')
::File.exist?(win_friendly_path(::File.join(fonts_dir, new_resource.name)))
fonts_dir = Chef::Util::PathHelper.join(ENV['windir'], 'fonts')
Chef::Log.debug("Seeing if the font at #{Chef::Util::PathHelper.join(fonts_dir, new_resource.font_name)} exists")
::File.exist?(Chef::Util::PathHelper.join(fonts_dir, new_resource.font_name))
end
# Parse out the schema provided to us to see if it's one we support via remote_file.
# We do this because URI will parse C:/foo as schema 'c', which won't work with remote_file
#
# @return [Boolean]
def remote_file_schema?(schema)
return true if %w(http https ftp).include?(schema)
end
# return new_resource.source if we have a proper URI specified
# if it's a local file listed as a source return it in file:// format
#
# @return [String] path to the font
def source_uri
begin
require 'uri'
if remote_file_schema?(URI.parse(new_resource.source).scheme)
Chef::Log.debug('source property starts with ftp/http. Using source property unmodified')
return new_resource.source
end
rescue URI::InvalidURIError
Chef::Log.warn("source property of #{new_resource.source} could not be processed as a URI. Check the format you provided.")
end
Chef::Log.debug('source property does not start with ftp/http. Prepending with file:// as it appears to be a local file.')
"file://#{new_resource.source}"
end
end

View File

@@ -18,10 +18,9 @@
# limitations under the License.
#
include Chef::Mixin::ShellOut
include Windows::Helper
property :url, String, name_property: true, required: true
property :url, String, name_property: true
property :user, String
property :sddl, String
property :exists, [true, false], desired_state: true
@@ -36,7 +35,7 @@ load_current_value do |desired|
exists true
url desired.url
# Checks first for sddl, because it generates user(s)
sddl_match = cmd_out.match(/SDDL:\s*(?<sddl>.+)/)
sddl_match = cmd_out.match(/SDDL:\s*(?<sddl>\S+)/)
if sddl_match
sddl sddl_match['sddl']
else

View File

@@ -3,8 +3,8 @@
# Cookbook:: windows
# Resource:: pagefile
#
# Copyright:: 2012-2017, Nordstrom, Inc.
# Copyright:: 2017, Chef Software, Inc.
# Copyright:: 2012-2018, Nordstrom, Inc.
# Copyright:: 2017-2018, Chef Software Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@@ -19,17 +19,19 @@
# limitations under the License.
#
property :name, String, name_property: true
chef_version_for_provides '< 14.0' if respond_to?(:chef_version_for_provides)
resource_name :windows_pagefile
property :path, String, coerce: proc { |x| x.tr('/', '\\') }, name_property: true
property :system_managed, [true, false]
property :automatic_managed, [true, false], default: false
property :initial_size, Integer
property :maximum_size, Integer
include Chef::Mixin::ShellOut
include Windows::Helper
action :set do
pagefile = new_resource.name
pagefile = new_resource.path
initial_size = new_resource.initial_size
maximum_size = new_resource.maximum_size
system_managed = new_resource.system_managed
@@ -58,16 +60,22 @@ end
action :delete do
validate_name
pagefile = new_resource.name
delete(pagefile) if exists?(pagefile)
delete(new_resource.path) if exists?(new_resource.path)
end
action_class do
# make sure the provided name property matches the appropriate format
# we do this here and not in the property itself because if automatic_managed
# is set then this validation is not necessary / doesn't make sense at all
def validate_name
return if /^.:.*.sys/ =~ new_resource.name
raise "#{new_resource.name} does not match the format DRIVE:\\path\\file.sys for pagefiles. Example: C:\\pagefile.sys"
return if /^.:.*.sys/ =~ new_resource.path
raise "#{new_resource.path} does not match the format DRIVE:\\path\\file.sys for pagefiles. Example: C:\\pagefile.sys"
end
# See if the pagefile exists
#
# @param [String] pagefile path to the pagefile
# @return [Boolean]
def exists?(pagefile)
@exists ||= begin
Chef::Log.debug("Checking if #{pagefile} exists by runing: #{wmic} pagefileset where SettingID=\"#{get_setting_id(pagefile)}\" list /format:list")
@@ -76,6 +84,12 @@ action_class do
end
end
# is the max/min pagefile size set?
#
# @param [String] pagefile path to the pagefile
# @param [String] min the minimum size of the pagefile
# @param [String] max the minimum size of the pagefile
# @return [Boolean]
def max_and_min_set?(pagefile, min, max)
@max_and_min_set ||= begin
Chef::Log.debug("Checking if #{pagefile} min: #{min} and max #{max} are set")
@@ -84,14 +98,20 @@ action_class do
end
end
# create a pagefile
#
# @param [String] pagefile path to the pagefile
def create(pagefile)
converge_by("create pagefile #{pagefile}") do
Chef::Log.debug("Running #{wmic} pagefileset create name=\"#{win_friendly_path(pagefile)}\"")
cmd = shell_out("#{wmic} pagefileset create name=\"#{win_friendly_path(pagefile)}\"")
Chef::Log.debug("Running #{wmic} pagefileset create name=\"#{pagefile}\"")
cmd = shell_out("#{wmic} pagefileset create name=\"#{pagefile}\"")
check_for_errors(cmd.stderr)
end
end
# delete a pagefile
#
# @param [String] pagefile path to the pagefile
def delete(pagefile)
converge_by("remove pagefile #{pagefile}") do
Chef::Log.debug("Running #{wmic} pagefileset where SettingID=\"#{get_setting_id(pagefile)}\" delete")
@@ -100,6 +120,9 @@ action_class do
end
end
# see if the pagefile is automatically managed by Windows
#
# @return [Boolean]
def automatic_managed?
@automatic_managed ||= begin
Chef::Log.debug('Checking if pagefiles are automatically managed')
@@ -108,6 +131,7 @@ action_class do
end
end
# turn on automatic management of all pagefiles by Windows
def set_automatic_managed
converge_by('set pagefile to Automatic Managed') do
Chef::Log.debug("Running #{wmic} computersystem where name=\"%computername%\" set AutomaticManagedPagefile=True")
@@ -116,6 +140,7 @@ action_class do
end
end
# turn off automatic management of all pagefiles by Windows
def unset_automatic_managed
converge_by('set pagefile to User Managed') do
Chef::Log.debug("Running #{wmic} computersystem where name=\"%computername%\" set AutomaticManagedPagefile=False")
@@ -124,6 +149,11 @@ action_class do
end
end
# set a custom size for the pagefile (vs the defaults)
#
# @param [String] pagefile path to the pagefile
# @param [String] min the minimum size of the pagefile
# @param [String] max the minimum size of the pagefile
def set_custom_size(pagefile, min, max)
converge_by("set #{pagefile} to InitialSize=#{min} & MaximumSize=#{max}") do
Chef::Log.debug("Running #{wmic} pagefileset where SettingID=\"#{get_setting_id(pagefile)}\" set InitialSize=#{min},MaximumSize=#{max}")
@@ -132,7 +162,10 @@ action_class do
end
end
def set_system_managed(pagefile) # rubocop: disable Style/AccessorMethodName
# set a pagefile size to be system managed
#
# @param [String] pagefile path to the pagefile
def set_system_managed(pagefile) # rubocop: disable Naming/AccessorMethodName
converge_by("set #{pagefile} to System Managed") do
Chef::Log.debug("Running #{wmic} pagefileset where SettingID=\"#{get_setting_id(pagefile)}\" set InitialSize=0,MaximumSize=0")
cmd = shell_out("#{wmic} pagefileset where SettingID=\"#{get_setting_id(pagefile)}\" set InitialSize=0,MaximumSize=0", returns: [0])
@@ -141,11 +174,11 @@ action_class do
end
def get_setting_id(pagefile)
pagefile = win_friendly_path(pagefile)
pagefile = pagefile.split('\\')
"#{pagefile[1]} @ #{pagefile[0]}"
split_path = pagefile.split('\\')
"#{split_path[1]} @ #{split_path[0]}"
end
# raise if there's an error on stderr on a shellout
def check_for_errors(stderr)
raise stderr.chomp unless stderr.empty?
end

View File

@@ -1,54 +0,0 @@
#
# Author:: Paul Morton (<pmorton@biaprotect.com>)
# Cookbook:: windows
# Resource:: path
#
# Copyright:: 2011-2017, Business Intelligence Associates, Inc
# Copyright:: 2017, Chef Software, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
property :path, String, name_property: true
include Windows::Helper
action :add do
env 'path' do
action :modify
delim ::File::PATH_SEPARATOR
value new_resource.path.tr('/', '\\')
notifies :run, "ruby_block[fix ruby ENV['PATH']]", :immediately
end
# The windows Env provider does not correctly expand variables in
# the PATH environment variable. Ruby expects these to be expanded.
# This is a temporary fix for that.
#
# Follow at https://github.com/chef/chef/pull/1876
#
ruby_block "fix ruby ENV['PATH']" do
block do
ENV['PATH'] = expand_env_vars(ENV['PATH'])
end
action :nothing
end
end
action :remove do
env 'path' do
action :delete
delim ::File::PATH_SEPARATOR
value new_resource.path.tr('/', '\\')
end
end

View File

@@ -22,7 +22,10 @@
require 'resolv'
property :device_id, String, name_property: true, required: true
chef_version_for_provides '< 14.0' if respond_to?(:chef_version_for_provides)
resource_name :windows_printer
property :device_id, String, name_property: true
property :comment, String
property :default, [true, false], default: false
property :driver_name, String, required: true

View File

@@ -3,7 +3,7 @@
# Cookbook:: windows
# Resource:: printer_port
#
# Copyright:: 2012-2017, Nordstrom, Inc.
# Copyright:: 2012-2018, Nordstrom, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@@ -22,7 +22,10 @@
require 'resolv'
property :ipv4_address, String, name_attribute: true, required: true, regex: Resolv::IPv4::Regex
chef_version_for_provides '< 14.0' if respond_to?(:chef_version_for_provides)
resource_name :windows_printer_port
property :ipv4_address, String, name_property: true, regex: Resolv::IPv4::Regex
property :port_name, String
property :port_number, Integer, default: 9100
property :port_description, String
@@ -36,15 +39,15 @@ def port_exists?(name)
port_reg_key = PORTS_REG_KEY + name
Chef::Log.debug "Checking to see if this reg key exists: '#{port_reg_key}'"
Registry.key_exists?(port_reg_key)
registry_key_exists?(port_reg_key)
end
# @todo Set @current_resource port properties from registry
load_current_value do |desired|
name desired.name
ipv4_address desired.ipv4_address
port_name desired.port_name || "IP_#{@new_resource.ipv4_address}"
exists port_exists?(desired.port_name)
# TODO: Set @current_resource port properties from registry
port_name desired.port_name || "IP_#{desired.ipv4_address}"
exists port_exists?(desired.port_name || "IP_#{desired.ipv4_address}")
end
action :create do

View File

@@ -1,10 +1,12 @@
# -*- coding: utf-8 -*-
#
# Author:: Sölvi Páll Ásgeirsson (<solvip@gmail.com>), Richard Lavey (richard.lavey@calastone.com)
# Author:: Sölvi Páll Ásgeirsson (<solvip@gmail.com>)
# Author:: Richard Lavey (richard.lavey@calastone.com)
# Author:: Tim Smith (tsmith@chef.io)
# Cookbook:: windows
# Resource:: share
#
# Copyright:: 2014-2017, Sölvi Páll Ásgeirsson.
# Copyright:: 2018, Chef Software, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@@ -19,271 +21,268 @@
# limitations under the License.
#
property :share_name, String, name_property: true
property :path, String, required: true
property :description, String, default: ''
property :full_users, Array, default: []
property :change_users, Array, default: []
property :read_users, Array, default: []
chef_version_for_provides '< 14.7' if respond_to?(:chef_version_for_provides)
resource_name :windows_share
require 'chef/json_compat'
require 'chef/util/path_helper'
# Specifies a name for the SMB share. The name may be composed of any valid file name characters, but must be less than 80 characters long. The names pipe and mailslot are reserved for use by the computer.
property :share_name, String, name_property: true
# Specifies the path of the location of the folder to share. The path must be fully qualified. Relative paths or paths that contain wildcard characters are not permitted.
property :path, String
# Specifies an optional description of the SMB share. A description of the share is displayed by running the Get-SmbShare cmdlet. The description may not contain more than 256 characters.
property :description, String, default: ''
# Specifies which accounts are granted full permission to access the share. Use a comma-separated list to specify multiple accounts. An account may not be specified more than once in the FullAccess, ChangeAccess, or ReadAccess parameter lists, but may be specified once in the FullAccess, ChangeAccess, or ReadAccess parameter list and once in the NoAccess parameter list.
property :full_users, Array, default: [], coerce: proc { |u| u.sort }
# Specifies which users are granted modify permission to access the share
property :change_users, Array, default: [], coerce: proc { |u| u.sort }
# Specifies which users are granted read permission to access the share. Multiple users can be specified by supplying a comma-separated list.
property :read_users, Array, default: [], coerce: proc { |u| u.sort }
# Specifies the lifetime of the new SMB share. A temporary share does not persist beyond the next restart of the computer. By default, new SMB shares are persistent, and non-temporary.
property :temporary, [true, false], default: false
# Specifies the scope name of the share.
property :scope_name, String, default: '*'
# Specifies the continuous availability time-out for the share.
property :ca_timeout, Integer, default: 0
# Indicates that the share is continuously available.
property :continuously_available, [true, false], default: false
# Specifies the caching mode of the offline files for the SMB share.
# property :caching_mode, String, equal_to: %w(None Manual Documents Programs BranchCache)
# Specifies the maximum number of concurrently connected users that the new SMB share may accommodate. If this parameter is set to zero (0), then the number of users is unlimited.
property :concurrent_user_limit, Integer, default: 0
# Indicates that the share is encrypted.
property :encrypt_data, [true, false], default: false
# Specifies which files and folders in the SMB share are visible to users. AccessBased: SMB does not the display the files and folders for a share to a user unless that user has rights to access the files and folders. By default, access-based enumeration is disabled for new SMB shares. Unrestricted: SMB displays files and folders to a user even when the user does not have permission to access the items.
# property :folder_enumeration_mode, String, equal_to: %(AccessBased Unrestricted)
include Windows::Helper
include Chef::Mixin::PowershellOut
require 'win32ole' if RUBY_PLATFORM =~ /mswin|mingw32|windows/
load_current_value do |desired|
# this command selects individual objects because EncryptData & CachingMode have underlying
# types that get converted to their Integer values by ConvertTo-Json & we need to make sure
# those get written out as strings
share_state_cmd = "Get-SmbShare -Name '#{desired.share_name}' | Select-Object Name,Path, Description, Temporary, CATimeout, ContinuouslyAvailable, ConcurrentUserLimit, EncryptData | ConvertTo-Json"
ACCESS_FULL = 2_032_127
ACCESS_CHANGE = 1_245_631
ACCESS_READ = 1_179_817
Chef::Log.debug("Running '#{share_state_cmd}' to determine share state'")
ps_results = powershell_out(share_state_cmd)
action :create do
if different_path?
unless current_resource.path.nil? || current_resource.path.empty?
converge_by('Removing previous share') do
delete_share
end
end
converge_by("Creating share #{current_resource.share_name}") do
create_share
end
# detect a failure without raising and then set current_resource to nil
if ps_results.error?
Chef::Log.debug("Error fetching share state: #{ps_results.stderr}")
current_value_does_not_exist!
end
if different_members?(:full_users) ||
different_members?(:change_users) ||
different_members?(:read_users) ||
different_description?
converge_by("Setting permissions and description for #{new_resource.share_name}") do
set_share_permissions
Chef::Log.debug("The Get-SmbShare results were #{ps_results.stdout}")
results = Chef::JSONCompat.from_json(ps_results.stdout)
path results['Path']
description results['Description']
temporary results['Temporary']
ca_timeout results['CATimeout']
continuously_available results['ContinuouslyAvailable']
# caching_mode results['CachingMode']
concurrent_user_limit results['ConcurrentUserLimit']
encrypt_data results['EncryptData']
# folder_enumeration_mode results['FolderEnumerationMode']
perm_state_cmd = %(Get-SmbShareAccess -Name "#{desired.share_name}" | Select-Object AccountName,AccessControlType,AccessRight | ConvertTo-Json)
Chef::Log.debug("Running '#{perm_state_cmd}' to determine share permissions state'")
ps_perm_results = powershell_out(perm_state_cmd)
# we raise here instead of warning like above because we'd only get here if the above Get-SmbShare
# command was successful and that continuing would leave us with 1/2 known state
raise "Could not determine #{desired.share_name} share permissions by running '#{perm_state_cmd}'" if ps_perm_results.error?
Chef::Log.debug("The Get-SmbShareAccess results were #{ps_perm_results.stdout}")
f_users, c_users, r_users = parse_permissions(ps_perm_results.stdout)
full_users f_users
change_users c_users
read_users r_users
end
def after_created
raise 'The windows_share resource relies on PowerShell cmdlets not present in Windows releases prior to 8/2012. Cannot continue!' if node['platform_version'].to_f < 6.3
end
# given the string output of Get-SmbShareAccess parse out
# arrays of full access users, change users, and read only users
def parse_permissions(results_string)
json_results = Chef::JSONCompat.from_json(results_string)
json_results = [json_results] unless json_results.is_a?(Array) # single result is not an array
f_users = []
c_users = []
r_users = []
json_results.each do |perm|
next unless perm['AccessControlType'] == 0 # allow
case perm['AccessRight']
when 0 then f_users << stripped_account(perm['AccountName']) # 0 full control
when 1 then c_users << stripped_account(perm['AccountName']) # 1 == change
when 2 then r_users << stripped_account(perm['AccountName']) # 2 == read
end
end
[f_users, c_users, r_users]
end
# local names are returned from Get-SmbShareAccess in the full format MACHINE\\NAME
# but users of this resource would simply say NAME so we need to strip the values for comparison
def stripped_account(name)
name.slice!("#{node['hostname']}\\")
name
end
action :create do
# we do this here instead of requiring the property because :delete doesn't need path set
raise 'No path property set' unless new_resource.path
converge_if_changed do
# you can't actually change the path so you have to delete the old share first
if different_path?
Chef::Log.debug('The path has changed so we will delete and recreate share')
delete_share
create_share
elsif current_resource.nil?
# powershell cmdlet for create is different than updates
Chef::Log.debug('The current resource is nil so we will create a new share')
create_share
else
Chef::Log.debug('The current resource was not nil so we will update an existing share')
update_share
end
# creating the share does not set permissions so we need to update
update_permissions
end
end
action :delete do
if !current_resource.path.nil? && !current_resource.path.empty?
converge_by("Deleting #{current_resource.share_name}") do
if current_resource.nil?
Chef::Log.debug("#{new_resource.share_name} does not exist - nothing to do")
else
converge_by("delete #{new_resource.share_name}") do
delete_share
end
else
Chef::Log.debug("#{current_resource.share_name} does not exist - nothing to do")
end
end
load_current_value do |desired|
wmi = WIN32OLE.connect('winmgmts://')
shares = wmi.ExecQuery("SELECT * FROM Win32_Share WHERE name = '#{desired.share_name}'")
existing_share = shares.Count == 0 ? nil : shares.ItemIndex(0)
description ''
unless existing_share.nil?
path existing_share.Path
description existing_share.Description
action_class do
def different_path?
return false if current_resource.nil? # going from nil to something isn't different for our concerns
return false if current_resource.path == Chef::Util::PathHelper.cleanpath(new_resource.path)
true
end
perms = share_permissions name
unless perms.nil?
full_users perms[:full_users]
change_users perms[:change_users]
read_users perms[:read_users]
end
end
def delete_share
delete_command = "Remove-SmbShare -Name '#{new_resource.share_name}' -Force"
def share_permissions(name)
wmi = WIN32OLE.connect('winmgmts://')
shares = wmi.ExecQuery("SELECT * FROM Win32_LogicalShareSecuritySetting WHERE name = '#{name}'")
# The security descriptor is an output parameter
sd = nil
begin
shares.ItemIndex(0).GetSecurityDescriptor(sd)
sd = WIN32OLE::ARGV[0]
rescue WIN32OLERuntimeError
Chef::Log.warn('Failed to retrieve any security information about the share.')
Chef::Log.debug("Running '#{delete_command}' to remove the share")
powershell_out!(delete_command)
end
read = []
change = []
full = []
def update_share
update_command = "Set-SmbShare -Name '#{new_resource.share_name}' -Description '#{new_resource.description}' -Force"
unless sd.nil?
sd.DACL.each do |dacl|
trustee = "#{dacl.Trustee.Domain}\\#{dacl.Trustee.Name}".downcase
case dacl.AccessMask
when ACCESS_FULL
full.push(trustee)
when ACCESS_CHANGE
change.push(trustee)
when ACCESS_READ
read.push(trustee)
else
Chef::Log.warn "Unknown access mask #{dacl.AccessMask} for user #{trustee}. This will be lost if permissions are updated"
Chef::Log.debug("Running '#{update_command}' to update the share")
powershell_out!(update_command)
end
def create_share
raise "#{new_resource.path} is missing or not a directory. Shares cannot be created if the path doesn't first exist." unless ::File.directory? new_resource.path
share_cmd = "New-SmbShare -Name '#{new_resource.share_name}' -Path '#{Chef::Util::PathHelper.cleanpath(new_resource.path)}' -Description '#{new_resource.description}' -ConcurrentUserLimit #{new_resource.concurrent_user_limit} -CATimeout #{new_resource.ca_timeout} -EncryptData:#{bool_string(new_resource.encrypt_data)} -ContinuouslyAvailable:#{bool_string(new_resource.continuously_available)}"
share_cmd << " -ScopeName #{new_resource.scope_name}" unless new_resource.scope_name == '*' # passing * causes the command to fail
share_cmd << " -Temporary:#{bool_string(new_resource.temporary)}" if new_resource.temporary # only set true
Chef::Log.debug("Running '#{share_cmd}' to create the share")
powershell_out!(share_cmd)
# New-SmbShare adds the "Everyone" user with read access no matter what so we need to remove it
# before we add our permissions
revoke_user_permissions(['Everyone'])
end
# determine what users in the current state don't exist in the desired state
# users/groups will have their permissions updated with the same command that
# sets it, but removes must be performed with Revoke-SmbShareAccess
def users_to_revoke
@users_to_revoke ||= begin
# if the resource doesn't exist then nothing needs to be revoked
if current_resource.nil?
[]
else # if it exists then calculate the current to new resource diffs
(current_resource.full_users + current_resource.change_users + current_resource.read_users) - (new_resource.full_users + new_resource.change_users + new_resource.read_users)
end
end
end
{
full_users: full,
change_users: change,
read_users: read,
}
end
# update existing permissions on a share
def update_permissions
# revoke any users that had something, but now has nothing
revoke_user_permissions(users_to_revoke) unless users_to_revoke.empty?
action_class do
def description_exists?(resource)
!resource.description.nil?
end
# set permissions for each of the permission types
%w(full read change).each do |perm_type|
# set permissions for a brand new share OR
# update permissions if the current state and desired state differ
next unless permissions_need_update?(perm_type)
grant_command = "Grant-SmbShareAccess -Name '#{new_resource.share_name}' -AccountName \"#{new_resource.send("#{perm_type}_users").join('","')}\" -Force -AccessRight #{perm_type}"
def different_description?
if description_exists?(new_resource) && description_exists?(current_resource)
new_resource.description.casecmp(current_resource.description) != 0
else
description_exists?(new_resource) || description_exists?(current_resource)
Chef::Log.debug("Running '#{grant_command}' to update the share permissions")
powershell_out!(grant_command)
end
end
def different_path?
return true if current_resource.path.nil?
win_friendly_path(new_resource.path).casecmp(win_friendly_path(current_resource.path)) != 0
# determine if permissions need to be updated.
# Brand new share with no permissions defined: no
# Brand new share with permissions defined: yes
# Existing share with differing permissions: yes
#
# @param [String] type the permissions type (Full, Read, or Change)
def permissions_need_update?(type)
property_name = "#{type}_users"
# brand new share, but nothing to set
return false if current_resource.nil? && new_resource.send(property_name).empty?
# brand new share with new permissions to set
return true if current_resource.nil? && !new_resource.send(property_name).empty?
# there's a difference between the current and desired state
return true unless (new_resource.send(property_name) - current_resource.send(property_name)).empty?
# anything else
false
end
def different_members?(permission_type)
!(current_resource.send(permission_type.to_sym) - new_resource.send(permission_type.to_sym).map(&:downcase)).empty? &&
!(new_resource.send(permission_type.to_sym).map(&:downcase) - current_resource.send(permission_type.to_sym)).empty?
# revoke user permissions from a share
# @param [Array] users
def revoke_user_permissions(users)
revoke_command = "Revoke-SmbShareAccess -Name '#{new_resource.share_name}' -AccountName \"#{users.join('","')}\" -Force"
Chef::Log.debug("Running '#{revoke_command}' to revoke share permissions")
powershell_out!(revoke_command)
end
def find_share_by_name(name)
wmi = WIN32OLE.connect('winmgmts://')
shares = wmi.ExecQuery("SELECT * FROM Win32_Share WHERE name = '#{name}'")
shares.Count == 0 ? nil : shares.ItemIndex(0)
end
def delete_share
find_share_by_name(new_resource.share_name).delete
end
def create_share
raise "#{new_resource.path} is missing or not a directory" unless ::File.directory? new_resource.path
new_share_script = <<-EOH
$share = [wmiclass]"\\\\#{ENV['COMPUTERNAME']}\\root\\CimV2:Win32_Share"
$result=$share.Create('#{new_resource.path}',
'#{new_resource.share_name}',
0,
16777216,
'#{new_resource.description}',
$null,
$null)
exit $result.returnValue
EOH
r = powershell_out new_share_script
message = case r.exitstatus
when 2
'2 : Access Denied'
when 8
'8 : Unknown Failure'
when 9
'9 : Invalid Name'
when 10
'10 : Invalid Level'
when 21
'21 : Invalid Parameter'
when 22
'22 : Duplicate Share'
when 23
'23 : Redirected Path'
when 24
'24 : Unknown Device or Directory'
when 25
'25 : Net Name Not Found'
else
r.exitstatus.to_s
end
raise "Could not create share. Win32_Share.create returned #{message}" if r.error?
end
# set_share_permissions - Enforce the share permissions as dictated by the resource attributes
def set_share_permissions
share_permissions_script = <<-EOH
Function New-SecurityDescriptor
{
param (
[array]$ACEs
)
#Create SeCDesc object
$SecDesc = ([WMIClass] "\\\\$env:ComputerName\\root\\cimv2:Win32_SecurityDescriptor").CreateInstance()
foreach ($ACE in $ACEs )
{
$SecDesc.DACL += $ACE.psobject.baseobject
}
#Return the security Descriptor
return $SecDesc
}
Function New-ACE
{
param (
[string] $Name,
[string] $Domain,
[string] $Permission = "Read"
)
#Create the Trusteee Object
$Trustee = ([WMIClass] "\\\\$env:computername\\root\\cimv2:Win32_Trustee").CreateInstance()
$account = get-wmiobject Win32_Account -filter "Name like '$Name' and Domain like '$Domain'"
$accountSID = [WMI] "\\\\$env:ComputerName\\root\\cimv2:Win32_SID.SID='$($account.sid)'"
$Trustee.Domain = $Domain
$Trustee.Name = $Name
$Trustee.SID = $accountSID.BinaryRepresentation
#Create ACE (Access Control List) object.
$ACE = ([WMIClass] "\\\\$env:ComputerName\\root\\cimv2:Win32_ACE").CreateInstance()
switch ($Permission)
{
"Read" { $ACE.AccessMask = 1179817 }
"Change" { $ACE.AccessMask = 1245631 }
"Full" { $ACE.AccessMask = 2032127 }
default { throw "$Permission is not a supported permission value. Possible values are 'Read','Change','Full'" }
}
$ACE.AceFlags = 3
$ACE.AceType = 0
$ACE.Trustee = $Trustee
$ACE
}
$dacl_array = @()
EOH
new_resource.full_users.each do |user|
share_permissions_script += user_to_ace(user, 'Full')
end
new_resource.change_users.each do |user|
share_permissions_script += user_to_ace(user, 'Change')
end
new_resource.read_users.each do |user|
share_permissions_script += user_to_ace(user, 'Read')
end
share_permissions_script += <<-EOH
$dacl = New-SecurityDescriptor -Aces $dacl_array
$share = get-wmiobject win32_share -filter 'Name like "#{new_resource.share_name}"'
$return = $share.SetShareInfo($null, '#{new_resource.description}', $dacl)
exit $return.returnValue
EOH
r = powershell_out(share_permissions_script)
raise "Could not set share permissions. Win32_Share.SedtShareInfo returned #{r.exitstatus}" if r.error?
end
def user_to_ace(fully_qualified_user_name, access)
domain, user = fully_qualified_user_name.split('\\')
unless domain && user
raise "Invalid user entry #{fully_qualified_user_name}. The user names must be specified as 'DOMAIN\\user'"
end
"\n$dacl_array += new-ace -Name '#{user}' -domain '#{domain}' -permission '#{access}'"
# convert True/False into "$True" & "$False"
def bool_string(bool)
# bool ? 1 : 0
bool ? '$true' : '$false'
end
end

View File

@@ -3,7 +3,8 @@
# Cookbook:: windows
# Resource:: shortcut
#
# Copyright:: 2010-2017, VMware, Inc.
# Copyright:: 2010-2018, VMware, Inc.
# Copyright:: 2017-2018, Chef Software Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@@ -18,7 +19,10 @@
# limitations under the License.
#
property :name, String
chef_version_for_provides '< 14.0' if respond_to?(:chef_version_for_provides)
resource_name :windows_shortcut
property :shortcut_name, String, name_property: true
property :target, String
property :arguments, String
property :description, String
@@ -28,8 +32,8 @@ property :iconlocation, String
load_current_value do |desired|
require 'win32ole' if RUBY_PLATFORM =~ /mswin|mingw32|windows/
link = WIN32OLE.new('WScript.Shell').CreateShortcut(desired.name)
name desired.name
link = WIN32OLE.new('WScript.Shell').CreateShortcut(desired.shortcut_name)
name desired.shortcut_name
target(link.TargetPath)
arguments(link.Arguments)
description(link.Description)
@@ -39,8 +43,8 @@ end
action :create do
converge_if_changed do
converge_by "creating shortcut #{new_resource.name}" do
link = WIN32OLE.new('WScript.Shell').CreateShortcut(new_resource.name)
converge_by "creating shortcut #{new_resource.shortcut_name}" do
link = WIN32OLE.new('WScript.Shell').CreateShortcut(new_resource.shortcut_name)
link.TargetPath = new_resource.target unless new_resource.target.nil?
link.Arguments = new_resource.arguments unless new_resource.arguments.nil?
link.Description = new_resource.description unless new_resource.description.nil?

View File

@@ -1,384 +0,0 @@
#
# Author:: Paul Mooring (<paul@chef.io>)
# Cookbook:: windows
# Resource:: task
#
# Copyright:: 2012-2017, Chef Software, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
# Passwords can't be loaded for existing tasks, making :modify both confusing
# and not very useful
require 'chef/mixin/shell_out'
require 'rexml/document'
include Chef::Mixin::ShellOut
include Chef::Mixin::PowershellOut
property :task_name, String, name_property: true, regex: [/\A[^\/\:\*\?\<\>\|]+\z/]
property :command, String
property :cwd, String
property :user, String, default: 'SYSTEM'
property :password, String
property :run_level, equal_to: [:highest, :limited], default: :limited
property :force, [true, false], default: false
property :interactive_enabled, [true, false], default: false
property :frequency_modifier, [Integer, String], default: 1
property :frequency, equal_to: [:minute,
:hourly,
:daily,
:weekly,
:monthly,
:once,
:on_logon,
:onstart,
:on_idle], default: :hourly
property :start_day, String
property :start_time, String
property :day, [String, Integer]
property :months, String
property :idle_time, Integer
property :exists, [true, false], desired_state: true
property :status, Symbol, desired_state: true
property :enabled, [true, false], desired_state: true
def load_task_hash(task_name)
Chef::Log.debug 'Looking for existing tasks'
# we use powershell_out here instead of powershell_out! because a failure implies that the task does not exist
task_script = <<-EOH
[Console]::OutputEncoding = [Text.UTF8Encoding]::UTF8
schtasks /Query /FO LIST /V /TN \"#{task_name}\"
EOH
output = powershell_out(task_script).stdout.force_encoding('UTF-8')
if output.empty?
task = false
else
task = {}
output.split("\n").map! { |line| line.split(':', 2).map!(&:strip) }.each do |field|
if field.is_a?(Array) && field[0].respond_to?(:to_sym)
task[field[0].gsub(/\s+/, '').to_sym] = field[1]
end
end
end
task
end
load_current_value do |desired|
pathed_task_name = desired.task_name.start_with?('\\') ? desired.task_name : "\\#{desired.task_name}"
task_hash = load_task_hash pathed_task_name
task_name pathed_task_name
if task_hash.respond_to?(:[]) && task_hash[:TaskName] == pathed_task_name
exists true
status :running if task_hash[:Status] == 'Running'
enabled task_hash[:ScheduledTaskState] == 'Enabled' ? true : false
cwd task_hash[:StartIn] unless task_hash[:StartIn] == 'N/A'
command task_hash[:TaskToRun]
user task_hash[:RunAsUser]
else
exists false
end
end
action :create do
if current_resource.exists && !(task_need_update? || new_resource.force)
Chef::Log.info "#{new_resource} task already exists - nothing to do"
else
converge_by("creating a new scheduled task #{new_resource.task_name}") do
validate_user_and_password
validate_interactive_setting
validate_create_frequency_modifier
validate_create_day
validate_create_months
validate_idle_time
options = {}
options['F'] = '' if new_resource.force || task_need_update?
options['SC'] = schedule
options['MO'] = new_resource.frequency_modifier if frequency_modifier_allowed
options['I'] = new_resource.idle_time unless new_resource.idle_time.nil?
options['SD'] = new_resource.start_day unless new_resource.start_day.nil?
options['ST'] = new_resource.start_time unless new_resource.start_time.nil?
options['TR'] = new_resource.command
options['RU'] = new_resource.user
options['RP'] = new_resource.password if use_password?
options['RL'] = 'HIGHEST' if new_resource.run_level == :highest
options['IT'] = '' if new_resource.interactive_enabled
options['D'] = new_resource.day if new_resource.day
options['M'] = new_resource.months unless new_resource.months.nil?
run_schtasks 'CREATE', options
cwd(new_resource.cwd) if new_resource.cwd
end
end
end
action :run do
if current_resource.exists
if current_resource.status == :running
Chef::Log.info "#{new_resource} task is currently running, skipping run"
else
converge_by("running scheduled task #{new_resource.task_name}") do
run_schtasks 'RUN'
new_resource.updated_by_last_action true
end
end
else
Chef::Log.debug "#{new_resource} task doesn't exists - nothing to do"
end
end
action :change do
if current_resource.exists
converge_by("changing scheduled task #{new_resource.task_name}") do
validate_user_and_password
validate_interactive_setting
options = {}
options['TR'] = new_resource.command if new_resource.command
options['RU'] = new_resource.user if new_resource.user
options['RP'] = new_resource.password if new_resource.password
options['SD'] = new_resource.start_day unless new_resource.start_day.nil?
options['ST'] = new_resource.start_time unless new_resource.start_time.nil?
options['IT'] = '' if new_resource.interactive_enabled
run_schtasks 'CHANGE', options
cwd(new_resource.cwd) if new_resource.cwd != current_resource.cwd
end
else
Chef::Log.debug "#{new_resource} task doesn't exists - nothing to do"
end
end
action :delete do
if current_resource.exists
converge_by("deleting scheduled task #{new_resource.task_name}") do
# always need to force deletion
run_schtasks 'DELETE', 'F' => ''
end
else
Chef::Log.debug "#{new_resource} task doesn't exists - nothing to do"
end
end
action :end do
if current_resource.exists
if current_resource.status != :running
Chef::Log.debug "#{new_resource} is not running - nothing to do"
else
converge_by("stopping scheduled task #{new_resource.task_name}") do
run_schtasks 'END'
end
end
else
Chef::Log.fatal "#{new_resource} task doesn't exist - nothing to do"
raise Errno::ENOENT, "#{new_resource}: task does not exist, cannot end"
end
end
action :enable do
if current_resource.exists
if current_resource.enabled
Chef::Log.debug "#{new_resource} already enabled - nothing to do"
else
converge_by("enabling scheduled task #{new_resource.task_name}") do
run_schtasks 'CHANGE', 'ENABLE' => ''
end
end
else
Chef::Log.fatal "#{new_resource} task doesn't exist - nothing to do"
raise Errno::ENOENT, "#{new_resource}: task does not exist, cannot enable"
end
end
action :disable do
if current_resource.exists
if current_resource.enabled
converge_by("disabling scheduled task #{new_resource.task_name}") do
run_schtasks 'CHANGE', 'DISABLE' => ''
end
else
Chef::Log.debug "#{new_resource} already disabled - nothing to do"
end
else
Chef::Log.debug "#{new_resource} task doesn't exist - nothing to do"
end
end
action_class do
# rubocop:disable Style/StringLiteralsInInterpolation
def run_schtasks(task_action, options = {})
cmd = "schtasks /#{task_action} /TN \"#{new_resource.task_name}\" "
options.keys.each do |option|
cmd += "/#{option} "
cmd += "\"#{options[option].to_s.gsub('"', "\\\"")}\" " unless options[option] == ''
end
Chef::Log.debug('running: ')
Chef::Log.debug(" #{cmd}")
shell_out!(cmd, returns: [0])
end
# rubocop:enable Style/StringLiteralsInInterpolation
def task_need_update?
# gsub needed as schtasks converts single quotes to double quotes on creation
current_resource.command != new_resource.command.tr("'", '"') ||
current_resource.user != new_resource.user
end
def cwd(folder)
Chef::Log.debug 'looking for existing tasks'
# we use shell_out here instead of shell_out! because a failure implies that the task does not exist
xml_cmd = shell_out("schtasks /Query /TN \"#{new_resource.task_name}\" /XML")
return if xml_cmd.exitstatus != 0
doc = REXML::Document.new(xml_cmd.stdout)
Chef::Log.debug 'Removing former CWD if any'
doc.root.elements.delete('Actions/Exec/WorkingDirectory')
unless folder.nil?
Chef::Log.debug 'Setting CWD as #folder'
cwd_element = REXML::Element.new('WorkingDirectory')
cwd_element.add_text(folder)
exec_element = doc.root.elements['Actions/Exec']
exec_element.add_element(cwd_element)
end
temp_task_file = ::File.join(ENV['TEMP'], 'windows_task.xml')
begin
::File.open(temp_task_file, 'w:UTF-16LE') do |f|
doc.write(f)
end
options = {}
options['RU'] = new_resource.user if new_resource.user
options['RP'] = new_resource.password if new_resource.password
options['IT'] = '' if new_resource.interactive_enabled
options['XML'] = temp_task_file
run_schtasks('DELETE', 'F' => '')
run_schtasks('CREATE', options)
ensure
::File.delete(temp_task_file)
end
end
SYSTEM_USERS = ['NT AUTHORITY\SYSTEM', 'SYSTEM', 'NT AUTHORITY\LOCALSERVICE', 'NT AUTHORITY\NETWORKSERVICE'].freeze
def validate_user_and_password
return unless new_resource.user && use_password?
return unless new_resource.password.nil?
Chef::Log.fatal "#{new_resource.task_name}: Can't specify a non-system user without a password!"
end
def validate_interactive_setting
return unless new_resource.interactive_enabled && new_resource.password.nil?
Chef::Log.fatal "#{new_resource} did not provide a password when attempting to set interactive/non-interactive."
end
def validate_create_day
return unless new_resource.day
unless [:weekly, :monthly].include?(new_resource.frequency)
raise 'day attribute is only valid for tasks that run weekly or monthly'
end
return unless new_resource.day.is_a?(String) && new_resource.day.to_i.to_s != new_resource.day
days = new_resource.day.split(',')
days.each do |day|
unless ['mon', 'tue', 'wed', 'thu', 'fri', 'sat', 'sun', '*'].include?(day.strip.downcase)
raise 'day attribute invalid. Only valid values are: MON, TUE, WED, THU, FRI, SAT, SUN and *. Multiple values must be separated by a comma.'
end
end
end
def validate_create_months
return unless new_resource.months
unless [:monthly].include?(new_resource.frequency)
raise 'months attribute is only valid for tasks that run monthly'
end
return unless new_resource.months.is_a? String
months = new_resource.months.split(',')
months.each do |month|
unless ['JAN', 'FEB', 'MAR', 'APR', 'MAY', 'JUN', 'JUL', 'AUG', 'SEP', 'OCT', 'NOV', 'DEC', '*'].include?(month.strip.upcase)
raise 'months attribute invalid. Only valid values are: JAN, FEB, MAR, APR, MAY, JUN, JUL, AUG, SEP, OCT, NOV, DEC and *. Multiple values must be separated by a comma.'
end
end
end
def validate_idle_time
return unless new_resource.frequency == :on_idle
return if new_resource.idle_time.to_i > 0 && new_resource.idle_time.to_i <= 999
raise "idle_time value #{new_resource.idle_time} is invalid. Valid values for :on_idle frequency are 1 - 999."
end
def validate_create_frequency_modifier
# Currently is handled in create action 'frequency_modifier_allowed' line. Does not allow for frequency_modifier for once,onstart,onlogon,onidle
# Note that 'OnEvent' is not a supported frequency.
return if new_resource.frequency.nil? || new_resource.frequency_modifier.nil?
case new_resource.frequency
when :minute
unless new_resource.frequency_modifier.to_i > 0 && new_resource.frequency_modifier.to_i <= 1439
raise "frequency_modifier value #{new_resource.frequency_modifier} is invalid. Valid values for :minute frequency are 1 - 1439."
end
when :hourly
unless new_resource.frequency_modifier.to_i > 0 && new_resource.frequency_modifier.to_i <= 23
raise "frequency_modifier value #{new_resource.frequency_modifier} is invalid. Valid values for :hourly frequency are 1 - 23."
end
when :daily
unless new_resource.frequency_modifier.to_i > 0 && new_resource.frequency_modifier.to_i <= 365
raise "frequency_modifier value #{new_resource.frequency_modifier} is invalid. Valid values for :daily frequency are 1 - 365."
end
when :weekly
unless new_resource.frequency_modifier.to_i > 0 && new_resource.frequency_modifier.to_i <= 52
raise "frequency_modifier value #{new_resource.frequency_modifier} is invalid. Valid values for :weekly frequency are 1 - 52."
end
when :monthly
unless ('1'..'12').to_a.push('FIRST', 'SECOND', 'THIRD', 'FOURTH', 'LAST', 'LASTDAY').include?(new_resource.frequency_modifier.to_s.upcase)
raise "frequency_modifier value #{new_resource.frequency_modifier} is invalid. Valid values for :monthly frequency are 1 - 12, 'FIRST', 'SECOND', 'THIRD', 'FOURTH', 'LAST', 'LASTDAY'."
end
end
end
def use_password?
@use_password ||= !SYSTEM_USERS.include?(new_resource.user.upcase)
end
def schedule
case new_resource.frequency
when :on_logon
'ONLOGON'
when :on_idle
'ONIDLE'
else
new_resource.frequency
end
end
def frequency_modifier_allowed
case new_resource.frequency
when :minute, :hourly, :daily, :weekly
true
when :monthly
new_resource.months.nil? || %w(FIRST SECOND THIRD FOURTH LAST LASTDAY).include?(new_resource.frequency_modifier)
else
false
end
end
end

View File

@@ -0,0 +1,40 @@
#
# Author:: Jared Kauppila (<jared@kauppi.la>)
# Cookbook:: windows
# Resource:: user_privilege
#
property :principal, String, name_property: true
property :privilege, [Array, String], required: true, coerce: proc { |v| [*v].sort }
action :add do
([*new_resource.privilege] - [*current_resource.privilege]).each do |user_right|
converge_by("adding user privilege #{user_right}") do
Chef::ReservedNames::Win32::Security.add_account_right(new_resource.principal, user_right)
end
end
end
action :remove do
if Gem::Version.new(Chef::VERSION) < Gem::Version.new('14.4.10')
Chef::Log.warn('Chef 14.4.10 is required to use windows_privilege remove action')
else
curr_res_privilege = current_resource.privilege
new_res_privilege = new_resource.privilege
missing_res_privileges = (new_res_privilege - curr_res_privilege)
if missing_res_privileges
Chef::Log.info("Privilege: #{missing_res_privileges.join(', ')} not present. Unable to delete")
end
(new_res_privilege - missing_res_privileges).each do |user_right|
converge_by("removing user privilege #{user_right}") do
Chef::ReservedNames::Win32::Security.remove_account_right(new_resource.principal, user_right)
end
end
end
end
load_current_value do |desired|
privilege Chef::ReservedNames::Win32::Security.get_account_right(desired.principal)
end

View File

@@ -1,11 +1,12 @@
#
# Author:: Doug MacEachern (<dougm@vmware.com>)
# Author:: Seth Chisamore (<schisamo@chef.io>)
# Author:: Wade Peacock (<wade.peacock@visioncritical.com>)
# Cookbook:: windows
# Resource:: zipfile
#
# Copyright:: 2010-2017, VMware, Inc.
# Copyright:: 2011-2017, Chef Software, Inc.
# Copyright:: 2011-2018, Chef Software, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@@ -20,20 +21,19 @@
# limitations under the License.
#
require 'chef/util/path_helper'
property :path, String, name_property: true
property :source, String
property :overwrite, [true, false], default: false
property :checksum, String
include Windows::Helper
require 'find'
action :unzip do
ensure_rubyzip_gem_installed
Chef::Log.debug("unzip #{new_resource.source} => #{new_resource.path} (overwrite=#{new_resource.overwrite})")
cache_file_path = if new_resource.source =~ %r{^(file|ftp|http|https):\/\/} # http://rubular.com/r/DGoIWjLfGI
uri = as_uri(source)
uri = as_uri(new_resource.source)
local_cache_path = "#{Chef::Config[:file_cache_path]}/#{::File.basename(::URI.unescape(uri.path))}"
Chef::Log.debug("Caching a copy of file #{new_resource.source} at #{cache_file_path}")
@@ -48,7 +48,7 @@ action :unzip do
new_resource.source
end
cache_file_path = win_friendly_path(cache_file_path)
cache_file_path = Chef::Util::PathHelper.cleanpath(cache_file_path)
converge_by("unzip #{new_resource.source}") do
ruby_block 'Unzipping' do
@@ -111,12 +111,14 @@ action :zip do
end
action_class do
include Windows::Helper
require 'find'
def ensure_rubyzip_gem_installed
require 'zip'
rescue LoadError
Chef::Log.info("Missing gem 'rubyzip'...installing now.")
chef_gem 'rubyzip' do
version node['windows']['rubyzipversion']
action :install
compile_time true
end