From 9778015039b7a8a0cfc84525566fb2b34e783e72 Mon Sep 17 00:00:00 2001 From: Hans-Joachim Kliemeck Date: Fri, 6 Nov 2015 09:26:49 +0100 Subject: [PATCH] first implementation of win_share module --- windows/win_share.ps1 | 251 ++++++++++++++++++++++++++++++++++++++++++ windows/win_share.py | 113 +++++++++++++++++++ 2 files changed, 364 insertions(+) create mode 100644 windows/win_share.ps1 create mode 100644 windows/win_share.py diff --git a/windows/win_share.ps1 b/windows/win_share.ps1 new file mode 100644 index 00000000000..3d816ac1657 --- /dev/null +++ b/windows/win_share.ps1 @@ -0,0 +1,251 @@ +#!powershell +# This file is part of Ansible + +# Copyright 2015, Hans-Joachim Kliemeck +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see . + +# WANT_JSON +# POWERSHELL_COMMON + +#Functions +Function UserSearch +{ + Param ([string]$AccountName) + #Check if there's a realm specified + if ($AccountName.Split("\").count -gt 1) + { + if ($AccountName.Split("\")[0] -eq $env:COMPUTERNAME) + { + $IsLocalAccount = $true + } + Else + { + $IsDomainAccount = $true + $IsUpn = $false + } + + } + Elseif ($AccountName.contains("@")) + { + $IsDomainAccount = $true + $IsUpn = $true + } + Else + { + #Default to local user account + $accountname = $env:COMPUTERNAME + "\" + $AccountName + $IsLocalAccount = $true + } + + if ($IsLocalAccount -eq $true) + { + # do not use Win32_UserAccount, because e.g. SYSTEM (BUILTIN\SYSTEM or COMPUUTERNAME\SYSTEM) will not be listed. on Win32_Account groups will be listed too + $localaccount = get-wmiobject -class "Win32_Account" -namespace "root\CIMV2" -filter "(LocalAccount = True)" | where {$_.Caption -eq $AccountName} + if ($localaccount) + { + return $localaccount.SID + } + } + ElseIf ($IsDomainAccount -eq $true) + { + #Search by samaccountname + $Searcher = [adsisearcher]"" + + If ($IsUpn -eq $false) { + $Searcher.Filter = "sAMAccountName=$($accountname.split("\")[1])" + } + Else { + $Searcher.Filter = "userPrincipalName=$($accountname)" + } + + $result = $Searcher.FindOne() + if ($result) + { + $user = $result.GetDirectoryEntry() + + # get binary SID from AD account + $binarySID = $user.ObjectSid.Value + + # convert to string SID + return (New-Object System.Security.Principal.SecurityIdentifier($binarySID,0)).Value + } + } +} +Function NormalizeAccounts +{ + param( + [parameter(valuefrompipeline=$true)] + $users + ) + + $users = $users.Trim() + If ($users -eq "") { + $splittedUsers = [Collections.Generic.List[String]] @() + } + Else { + $splittedUsers = [Collections.Generic.List[String]] $users.Split(",") + } + + $normalizedUsers = [Collections.Generic.List[String]] @() + ForEach($splittedUser in $splittedUsers) { + $sid = UserSearch $splittedUser + If (!$sid) { + Fail-Json $result "$splittedUser is not a valid user or group on the host machine or domain" + } + + $normalizedUser = (New-Object System.Security.Principal.SecurityIdentifier($sid)).Translate([System.Security.Principal.NTAccount]) + $normalizedUsers.Add($normalizedUser) + } + + return ,$normalizedUsers +} + +$params = Parse-Args $args; + +$result = New-Object PSObject; +Set-Attr $result "changed" $false; + +$name = Get-Attr $params "name" -failifempty $true +$state = Get-Attr $params "state" "present" -validateSet "present","absent" -resultobj $result + +Try { + $share = Get-SmbShare $name -ErrorAction SilentlyContinue + If ($state -eq "absent") { + If ($share) { + Remove-SmbShare -Force -Name $name + Set-Attr $result "changed" $true; + } + } + Else { + $path = Get-Attr $params "path" -failifempty $true + $description = Get-Attr $params "description" "" + + $permissionList = Get-Attr $params "list" "no" -validateSet "no","yes" -resultobj $result | ConvertTo-Bool + $folderEnum = if ($permissionList) { "Unrestricted" } else { "AccessBased" } + + $permissionRead = Get-Attr $params "read" "" | NormalizeAccounts + $permissionChange = Get-Attr $params "change" "" | NormalizeAccounts + $permissionFull = Get-Attr $params "full" "" | NormalizeAccounts + $permissionDeny = Get-Attr $params "deny" "" | NormalizeAccounts + + If (-Not (Test-Path -Path $path)) { + Fail-Json $result "$path directory does not exist on the host" + } + + # need to (re-)create share + If (!$share) { + New-SmbShare -Name $name -Path $path + $share = Get-SmbShare $name -ErrorAction SilentlyContinue + + Set-Attr $result "changed" $true; + } + If ($share.Path -ne $path) { + Remove-SmbShare -Force -Name $name + + New-SmbShare -Name $name -Path $path + $share = Get-SmbShare $name -ErrorAction SilentlyContinue + + Set-Attr $result "changed" $true; + } + + # updates + If ($share.Description -ne $description) { + Set-SmbShare -Force -Name $name -Description $description + Set-Attr $result "changed" $true; + } + If ($share.FolderEnumerationMode -ne $folderEnum) { + Set-SmbShare -Force -Name $name -FolderEnumerationMode $folderEnum + Set-Attr $result "changed" $true; + } + + # clean permissions that imply others + ForEach ($user in $permissionFull) { + $permissionChange.remove($user) + $permissionRead.remove($user) + } + ForEach ($user in $permissionChange) { + $permissionRead.remove($user) + } + + # remove permissions + $permissions = Get-SmbShareAccess -Name $name + ForEach ($permission in $permissions) { + If ($permission.AccessControlType -eq "Deny") { + If (!$permissionDeny.Contains($permission.AccountName)) { + Unblock-SmbShareAccess -Force -Name $name -AccountName $permission.AccountName + Set-Attr $result "changed" $true; + } + } + ElseIf ($permission.AccessControlType -eq "Allow") { + If ($permission.AccessRight -eq "Full") { + If (!$permissionFull.Contains($permission.AccountName)) { + Revoke-SmbShareAccess -Force -Name $name -AccountName $permission.AccountName + Set-Attr $result "changed" $true; + + Continue + } + + # user got requested permissions + $permissionFull.remove($permission.AccountName) + } + ElseIf ($permission.AccessRight -eq "Change") { + If (!$permissionChange.Contains($permission.AccountName)) { + Revoke-SmbShareAccess -Force -Name $name -AccountName $permission.AccountName + Set-Attr $result "changed" $true; + + Continue + } + + # user got requested permissions + $permissionChange.remove($permission.AccountName) + } + ElseIf ($permission.AccessRight -eq "Read") { + If (!$permissionRead.Contains($permission.AccountName)) { + Revoke-SmbShareAccess -Force -Name $name -AccountName $permission.AccountName + Set-Attr $result "changed" $true; + + Continue + } + + # user got requested permissions + $permissionRead.Remove($permission.AccountName) + } + } + } + + # add missing permissions + ForEach ($user in $permissionRead) { + Grant-SmbShareAccess -Force -Name $name -AccountName $user -AccessRight "Read" + Set-Attr $result "changed" $true; + } + ForEach ($user in $permissionChange) { + Grant-SmbShareAccess -Force -Name $name -AccountName $user -AccessRight "Change" + Set-Attr $result "changed" $true; + } + ForEach ($user in $permissionFull) { + Grant-SmbShareAccess -Force -Name $name -AccountName $user -AccessRight "Full" + Set-Attr $result "changed" $true; + } + ForEach ($user in $permissionDeny) { + Block-SmbShareAccess -Force -Name $name -AccountName $user + Set-Attr $result "changed" $true; + } + } +} +Catch { + Fail-Json $result "an error occured when attempting to create share $name" +} + +Exit-Json $result \ No newline at end of file diff --git a/windows/win_share.py b/windows/win_share.py new file mode 100644 index 00000000000..6a6039bad30 --- /dev/null +++ b/windows/win_share.py @@ -0,0 +1,113 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright 2015, Hans-Joachim Kliemeck +# +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see . + +# this is a windows documentation stub. actual code lives in the .ps1 +# file of the same name + +DOCUMENTATION = ''' +--- +module: win_share +version_added: "2.0" +short_description: Manage Windows shares +description: + - Add, modify or remove Windows share and set share permissions. +requirements: + - Windows 8, Windows 2012 or newer +options: + name: + description: + - Share name + required: yes + path: + description: + - Share directory + required: yes + state: + description: + - Specify whether to add C(present) or remove C(absent) the specified share + required: no + choices: + - present + - absent + default: present + description: + description: + - Share description + required: no + default: none + list: + description: + - Specify whether to allow or deny file listing, in case user got no permission on share + required: no + choices: + - yes + - no + default: none + read: + description: + - Specify user list that should get read access on share, separated by comma. + required: no + default: none + change: + description: + - Specify user list that should get read and write access on share, separated by comma. + required: no + default: none + full: + description: + - Specify user list that should get full access on share, separated by comma. + required: no + default: none + deny: + description: + - Specify user list that should get no access, regardless of implied access on share, separated by comma. + required: no + default: none +Hans-Joachim Kliemeck (@h0nIg) +''' + +EXAMPLES = ''' +# Playbook example +# Add share and set permissions +--- +- name: Add secret share + win_share: + name: internal + description: top secret share + path: C:\\shares\\internal\\ + list: 'no' + full: Administrators,CEO + read: HR-Global + deny: HR-External + +- name: Add public company share + win_share: + name: company + description: top secret share + path: C:\\shares\\company\\ + list: 'yes' + full: Administrators,CEO + read: Global + +# Remove previously added share + win_share: + name: internal + state: absent +''' \ No newline at end of file