summaryrefslogtreecommitdiff
path: root/lib/chef/resource/windows_share.rb
diff options
context:
space:
mode:
Diffstat (limited to 'lib/chef/resource/windows_share.rb')
-rw-r--r--lib/chef/resource/windows_share.rb299
1 files changed, 299 insertions, 0 deletions
diff --git a/lib/chef/resource/windows_share.rb b/lib/chef/resource/windows_share.rb
new file mode 100644
index 0000000000..ee75041012
--- /dev/null
+++ b/lib/chef/resource/windows_share.rb
@@ -0,0 +1,299 @@
+#
+# Copyright:: 2014-2017, Sölvi Páll Ásgeirsson.
+#
+# 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.
+#
+
+require "chef/resource"
+
+class Chef
+ class Resource
+ # @author Sölvi Páll Ásgeirsson <solvip@gmail.com>
+ # @author Richard Lavey <richard.lavey@calastone.com>
+ class WindowsShare < Chef::Resource
+ resource_name :windows_share
+ provides :windows_share, os: "windows"
+
+ property :share_name, String, name_property: true
+ property :path, String
+ property :description, String, default: ""
+ property :full_users, Array, default: []
+ property :change_users, Array, default: []
+ property :read_users, Array, default: []
+
+ include Windows::Helper
+ include Chef::Mixin::PowershellOut
+
+ require "win32ole" if RUBY_PLATFORM =~ /mswin|mingw32|windows/
+
+ ACCESS_FULL = 2_032_127
+ ACCESS_CHANGE = 1_245_631
+ ACCESS_READ = 1_179_817
+
+ action :create do
+ raise "No path property set" unless new_resource.path
+
+ 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
+ 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
+ end
+ end
+ end
+
+ action :delete do
+ if !current_resource.path.nil? && !current_resource.path.empty?
+ converge_by("Deleting #{current_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
+ 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 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.")
+ end
+
+ read = []
+ change = []
+ full = []
+
+ 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"
+ end
+ end
+ end
+
+ {
+ full_users: full,
+ change_users: change,
+ read_users: read,
+ }
+ end
+
+ action_class do
+ def description_exists?(resource)
+ !resource.description.nil?
+ end
+
+ 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)
+ 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
+ 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?
+ 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 = '$Name' and Domain = '$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}'"
+ end
+ end
+ end
+ end
+end