Update the mediawiki cookbook and upstream cookbooks
Compatibility with Chef 14
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
30
cookbooks/windows/resources/dns.rb
Normal file
30
cookbooks/windows/resources/dns.rb
Normal 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
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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?
|
||||
|
||||
@@ -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
|
||||
40
cookbooks/windows/resources/user_privilege.rb
Normal file
40
cookbooks/windows/resources/user_privilege.rb
Normal 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
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user