From 9778015039b7a8a0cfc84525566fb2b34e783e72 Mon Sep 17 00:00:00 2001 From: Hans-Joachim Kliemeck Date: Fri, 6 Nov 2015 09:26:49 +0100 Subject: [PATCH 1/6] 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 From 9d8b6d470dc419f3d153821a196e25e1f25e96a5 Mon Sep 17 00:00:00 2001 From: Hans-Joachim Kliemeck Date: Fri, 6 Nov 2015 14:29:11 +0100 Subject: [PATCH 2/6] fixxed problems related to path input --- windows/win_share.ps1 | 3 +++ windows/win_share.py | 4 ++-- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/windows/win_share.ps1 b/windows/win_share.ps1 index 3d816ac1657..86970f88d39 100644 --- a/windows/win_share.ps1 +++ b/windows/win_share.ps1 @@ -144,6 +144,9 @@ Try { Fail-Json $result "$path directory does not exist on the host" } + # normalize path and remove slash at the end + $path = (Get-Item $path).FullName -replace ".$" + # need to (re-)create share If (!$share) { New-SmbShare -Name $name -Path $path diff --git a/windows/win_share.py b/windows/win_share.py index 6a6039bad30..9e54185b64b 100644 --- a/windows/win_share.py +++ b/windows/win_share.py @@ -91,7 +91,7 @@ EXAMPLES = ''' win_share: name: internal description: top secret share - path: C:\\shares\\internal\\ + path: C:/shares/internal list: 'no' full: Administrators,CEO read: HR-Global @@ -101,7 +101,7 @@ EXAMPLES = ''' win_share: name: company description: top secret share - path: C:\\shares\\company\\ + path: C:/shares/company list: 'yes' full: Administrators,CEO read: Global From 55f64daee3f1bb1a6d1a24ea106542356315c821 Mon Sep 17 00:00:00 2001 From: Hans-Joachim Kliemeck Date: Fri, 6 Nov 2015 14:36:00 +0100 Subject: [PATCH 3/6] corrected requirements --- windows/win_share.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/windows/win_share.py b/windows/win_share.py index 9e54185b64b..e7c87ccf8a4 100644 --- a/windows/win_share.py +++ b/windows/win_share.py @@ -29,7 +29,7 @@ short_description: Manage Windows shares description: - Add, modify or remove Windows share and set share permissions. requirements: - - Windows 8, Windows 2012 or newer + - Windows 8.1 / Windows 2012 or newer options: name: description: From e4d9034fbc2ab7f3a0132dccdcf44a751fb8a048 Mon Sep 17 00:00:00 2001 From: Hans-Joachim Kliemeck Date: Wed, 11 Nov 2015 12:51:24 +0100 Subject: [PATCH 4/6] corrected replacement of last backslash --- windows/win_share.ps1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/windows/win_share.ps1 b/windows/win_share.ps1 index 86970f88d39..f409281711e 100644 --- a/windows/win_share.ps1 +++ b/windows/win_share.ps1 @@ -145,7 +145,7 @@ Try { } # normalize path and remove slash at the end - $path = (Get-Item $path).FullName -replace ".$" + $path = (Get-Item $path).FullName -replace "\\$" # need to (re-)create share If (!$share) { From c239ee31ac0f4230fbb7533b8a15ab10bbfc99b3 Mon Sep 17 00:00:00 2001 From: Hans-Joachim Kliemeck Date: Tue, 12 Jan 2016 11:39:19 +0100 Subject: [PATCH 5/6] fixed problems related to userpricincipalname (user@domain) and undefined variables fixed variable capitalization --- windows/win_share.ps1 | 39 ++++++++++++++++++--------------------- 1 file changed, 18 insertions(+), 21 deletions(-) diff --git a/windows/win_share.ps1 b/windows/win_share.ps1 index f409281711e..59e4e8ab810 100644 --- a/windows/win_share.ps1 +++ b/windows/win_share.ps1 @@ -22,52 +22,49 @@ #Functions Function UserSearch { - Param ([string]$AccountName) + Param ([string]$accountName) #Check if there's a realm specified - if ($AccountName.Split("\").count -gt 1) + + $searchDomain = $false + $searchDomainUPN = $false + if ($accountName.Split("\").count -gt 1) { - if ($AccountName.Split("\")[0] -eq $env:COMPUTERNAME) - { - $IsLocalAccount = $true - } - Else + if ($accountName.Split("\")[0] -ne $env:COMPUTERNAME) { - $IsDomainAccount = $true - $IsUpn = $false + $searchDomain = $true + $accountName = $accountName.split("\")[1] } - } - Elseif ($AccountName.contains("@")) + Elseif ($accountName.contains("@")) { - $IsDomainAccount = $true - $IsUpn = $true + $searchDomain = $true + $searchDomainUPN = $true } Else { #Default to local user account - $accountname = $env:COMPUTERNAME + "\" + $AccountName - $IsLocalAccount = $true + $accountName = $env:COMPUTERNAME + "\" + $accountName } - if ($IsLocalAccount -eq $true) + if ($searchDomain -eq $false) { # 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} + $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) + Else { #Search by samaccountname $Searcher = [adsisearcher]"" - If ($IsUpn -eq $false) { - $Searcher.Filter = "sAMAccountName=$($accountname.split("\")[1])" + If ($searchDomainUPN -eq $false) { + $Searcher.Filter = "sAMAccountName=$($accountName)" } Else { - $Searcher.Filter = "userPrincipalName=$($accountname)" + $Searcher.Filter = "userPrincipalName=$($accountName)" } $result = $Searcher.FindOne() From 39aab8fe06cb6643dcb082c5a192c9974d9219bb Mon Sep 17 00:00:00 2001 From: Hans-Joachim Kliemeck Date: Tue, 12 Jan 2016 12:41:41 +0100 Subject: [PATCH 6/6] fixxed tests --- windows/win_share.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/windows/win_share.py b/windows/win_share.py index e7c87ccf8a4..14608e6e17f 100644 --- a/windows/win_share.py +++ b/windows/win_share.py @@ -24,7 +24,7 @@ DOCUMENTATION = ''' --- module: win_share -version_added: "2.0" +version_added: "2.1" short_description: Manage Windows shares description: - Add, modify or remove Windows share and set share permissions. @@ -80,7 +80,7 @@ options: - 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) +author: Hans-Joachim Kliemeck (@h0nIg) ''' EXAMPLES = ''' @@ -110,4 +110,8 @@ EXAMPLES = ''' win_share: name: internal state: absent +''' + +RETURN = ''' + ''' \ No newline at end of file