DSCResources/cLocalFileShare/cLocalFileShare.psm1
<#
Author : Serge Nikalaichyk (https://www.linkedin.com/in/nikalaichyk) Version : 1.0.1 Date : 2015-10-15 #> function Get-TargetResource { [CmdletBinding()] [OutputType([Hashtable])] param ( [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] [String] $Name, [Parameter(Mandatory = $true)] [ValidateScript({Test-Path -Path $_ -PathType Container})] [String] $Path ) if ($Path.EndsWith('\')) { $Path = $Path.TrimEnd('\') } $Share = Get-WmiObject -Class Win32_Share -Filter "Name = '$Name' AND Type = 0" if ($Share) { if ($Share.Path -eq $Path) { Write-Verbose -Message "File share '$Name' with path '$Path' was found." $EnsureResult = 'Present' $ShareAccessSplit = Get-cLocalFileShareAccess -Name $Name | ConvertFrom-cLocalFileShareAccess } else { throw "File share '$Name' already exists and is targeting to path '$($Share.Path)'." } } else { Write-Verbose -Message "File share '$Name' with path '$Path' could not be found." $EnsureResult = 'Absent' } $ReturnValue = @{ Ensure = $EnsureResult Name = $Name Path = $Path ConcurrentUserLimit = [UInt32]$Share.MaximumAllowed Description = $Share.Description FullAccess = [String[]]@($ShareAccessSplit.FullAccess) ChangeAccess = [String[]]@($ShareAccessSplit.ChangeAccess) ReadAccess = [String[]]@($ShareAccessSplit.ReadAccess) NoAccess = [String[]]@($ShareAccessSplit.NoAccess) } return $ReturnValue } function Test-TargetResource { [CmdletBinding()] [OutputType([Boolean])] param ( [Parameter(Mandatory = $false)] [ValidateSet('Absent', 'Present')] [String] $Ensure = 'Present', [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] [String] $Name, [Parameter(Mandatory = $true)] [ValidateScript({Test-Path -Path $_ -PathType Container})] [String] $Path, [Parameter(Mandatory = $false)] [UInt32] $ConcurrentUserLimit, [Parameter(Mandatory = $false)] [String] $Description, [Parameter(Mandatory = $false)] [ValidateNotNullOrEmpty()] [String[]] $FullAccess, [Parameter(Mandatory = $false)] [ValidateNotNullOrEmpty()] [String[]] $ChangeAccess, [Parameter(Mandatory = $false)] [ValidateNotNullOrEmpty()] [String[]] $ReadAccess, [Parameter(Mandatory = $false)] [ValidateNotNullOrEmpty()] [String[]] $NoAccess ) if ($Path.EndsWith('\')) { $Path = $Path.TrimEnd('\') } $TargetResource = Get-TargetResource -Name $Name -Path $Path if ($Ensure -eq 'Absent') { if ($TargetResource.Ensure -eq 'Absent') { $InDesiredState = $true } else { $InDesiredState = $false } } elseif ($Ensure -eq 'Present') { if ($TargetResource.Ensure -eq 'Absent') { $InDesiredState = $false } else { $InDesiredState = $true $PSBoundParameters.Keys.Where({$_ -in @('Name', 'Path', 'ConcurrentUserLimit', 'Description', 'Ensure')}).ForEach( { if (Compare-Object -ReferenceObject $PSBoundParameters.Item($_) -DifferenceObject $TargetResource.Item($_)) { "Property '{0}': Current value: '{1}'; Desired value: '{2}'." -f $_, ($TargetResource.Item($_) -join ', '), ($PSBoundParameters.Item($_) -join ', ') | Write-Verbose $InDesiredState = $false } } ) # Normalize and test access-related property values if ($PSBoundParameters.Keys.Where({$_ -in @('FullAccess', 'ChangeAccess', 'ReadAccess', 'NoAccess')})) { Write-Verbose -Message "Testing access-related property values." $ReferenceAccessSplit = ConvertTo-cLocalFileShareAccess -FullAccess $FullAccess -ChangeAccess $ChangeAccess -ReadAccess $ReadAccess -NoAccess $NoAccess | ConvertFrom-cLocalFileShareAccess if (Compare-Object -ReferenceObject $ReferenceAccessSplit.FullAccess -DifferenceObject $TargetResource.FullAccess) { "Property '{0}': Current value: '{1}'; Desired value: '{2}'." -f 'FullAccess', ($TargetResource.FullAccess -join ', '), ($ReferenceAccessSplit.FullAccess -join ', ') | Write-Verbose $InDesiredState = $false } if (Compare-Object -ReferenceObject $ReferenceAccessSplit.ChangeAccess -DifferenceObject $TargetResource.ChangeAccess) { "Property '{0}': Current value: '{1}'; Desired value: '{2}'." -f 'ChangeAccess', ($TargetResource.ChangeAccess -join ', '), ($ReferenceAccessSplit.ChangeAccess -join ', ') | Write-Verbose $InDesiredState = $false } if (Compare-Object -ReferenceObject $ReferenceAccessSplit.ReadAccess -DifferenceObject $TargetResource.ReadAccess) { "Property '{0}': Current value: '{1}'; Desired value: '{2}'." -f 'ReadAccess', ($TargetResource.ReadAccess -join ', '), ($ReferenceAccessSplit.ReadAccess -join ', ') | Write-Verbose $InDesiredState = $false } if (Compare-Object -ReferenceObject $ReferenceAccessSplit.NoAccess -DifferenceObject $TargetResource.NoAccess) { "Property '{0}': Current value: '{1}'; Desired value: '{2}'." -f 'NoAccess', ($TargetResource.NoAccess -join ', ') , ($ReferenceAccessSplit.NoAccess -join ', ') | Write-Verbose $InDesiredState = $false } } } } if ($InDesiredState -eq $true) { Write-Verbose -Message "The target resource is already in the desired state. No action is required." } else { Write-Verbose -Message "The target resource is not in the desired state." } return $InDesiredState } function Set-TargetResource { [CmdletBinding(SupportsShouldProcess = $true)] param ( [Parameter(Mandatory = $false)] [ValidateSet('Absent', 'Present')] [String] $Ensure = 'Present', [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] [String] $Name, [Parameter(Mandatory = $true)] [ValidateScript({Test-Path -Path $_ -PathType Container})] [String] $Path, [Parameter(Mandatory = $false)] [UInt32] $ConcurrentUserLimit, [Parameter(Mandatory = $false)] [String] $Description, [Parameter(Mandatory = $false)] [ValidateNotNullOrEmpty()] [String[]] $FullAccess, [Parameter(Mandatory = $false)] [ValidateNotNullOrEmpty()] [String[]] $ChangeAccess, [Parameter(Mandatory = $false)] [ValidateNotNullOrEmpty()] [String[]] $ReadAccess, [Parameter(Mandatory = $false)] [ValidateNotNullOrEmpty()] [String[]] $NoAccess ) if (-not $PSCmdlet.ShouldProcess($Name)) { return } if ($Path.EndsWith('\')) { $Path = $Path.TrimEnd('\') } $TargetResource = Get-TargetResource -Name $Name -Path $Path if ($Ensure -eq 'Absent') { if ($TargetResource.Ensure -eq 'Present') { Write-Verbose -Message "Removing file share '$Name'." Remove-cLocalFileShare -Name $Name -Confirm:$false } } elseif ($Ensure -eq 'Present') { if ($TargetResource.Ensure -eq 'Absent') { Write-Verbose -Message "Creating file share '$Name' with path '$Path'." New-cLocalFileShare -Name $Name -Path $Path -ErrorAction Stop $TargetResource = Get-TargetResource -Name $Name -Path $Path } # Compare permissions $ReferenceAccess = ConvertTo-cLocalFileShareAccess -FullAccess $FullAccess -ChangeAccess $ChangeAccess -ReadAccess $ReadAccess -NoAccess $NoAccess if ($ReferenceAccess) { $ReferenceAccessSplit = $ReferenceAccess | ConvertFrom-cLocalFileShareAccess if ( (Compare-Object -ReferenceObject $ReferenceAccessSplit.FullAccess -DifferenceObject $TargetResource.FullAccess) -or (Compare-Object -ReferenceObject $ReferenceAccessSplit.ChangeAccess -DifferenceObject $TargetResource.ChangeAccess) -or (Compare-Object -ReferenceObject $ReferenceAccessSplit.ReadAccess -DifferenceObject $TargetResource.ReadAccess) -or (Compare-Object -ReferenceObject $ReferenceAccessSplit.NoAccess -DifferenceObject $TargetResource.NoAccess) ) { Write-Verbose -Message "Setting file share permissions." Set-cLocalFileShareAccess -Name $Name -AccessRuleCollection $ReferenceAccess } } else { Write-Verbose -Message "File share permissions will not be modified." } $PSBoundParameters.GetEnumerator() | Where-Object {$_.Key -in (Get-Command -Name Set-cLocalFileShare).Parameters.Keys} | ForEach-Object -Begin {$SetParameters = @{}} -Process {$SetParameters.Add($_.Key, $_.Value)} if ($SetParameters.Count -ne 0) { Set-cLocalFileShare @SetParameters } } } Export-ModuleMember -Function Get-TargetResource, Set-TargetResource, Test-TargetResource #region Helper Functions function Initialize-cLocalFileShareType { $TypeDefinition = @' namespace cLocalFileShare { public enum AccessMask { Read = 1179817, Change = 1245631, Full = 2032127 } public enum AceType { Allow = 0, Deny = 1 } public class AccessRule { public string AccountName {get; set;} public AceType AccessControlType {get; set;} public AccessMask AccessRight {get; set;} public AccessRule(string Principal, AceType Type, AccessMask Access) { AccountName = Principal; AccessControlType = Type; AccessRight = Access; } } } '@ if (-not ('cLocalFileShare.AccessRule' -as [Type])) { Add-Type -TypeDefinition $TypeDefinition } } Initialize-cLocalFileShareType function New-cLocalFileShare { [CmdletBinding(ConfirmImpact = 'Medium', SupportsShouldProcess = $true)] param ( [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] [String] $Name, [Parameter(Mandatory = $true)] [ValidateScript({Test-Path -Path $_ -PathType Container})] [String] $Path ) process { if ($Path.EndsWith('\')) { $Path = $Path.TrimEnd('\') } if ($PSCmdlet.ShouldProcess($Name, 'Create File Share')) { $Result = ([WmiClass]'Win32_Share').Create($Path, $Name, 0) if ($Result) { if ($Result.ReturnValue -eq 0) { Write-Verbose -Message "File share '$Name' was created." } else { Write-Error -Message "Unable to create file share '$Name'. Return code: '$($Result.ReturnValue)'." return } } } } } function Remove-cLocalFileShare { [CmdletBinding(ConfirmImpact = 'High', SupportsShouldProcess = $true)] param ( [Parameter(Mandatory = $true, ValueFromPipeline = $true)] [ValidateNotNullOrEmpty()] [String] $Name ) process { $Share = Get-WmiObject -Class Win32_Share -Filter "Name = '$Name' AND Type = 0" if (-not $Share) { Write-Error -Message "File share '$Name' could not be found." return } if ($PSCmdlet.ShouldProcess($Name, 'Remove File Share')) { $Result = $Share.Delete() if ($Result) { if ($Result.ReturnValue -eq 0) { Write-Verbose -Message "File share '$Name' was removed." } else { Write-Error -Message "Unable to remove file share '$Name'. Return code: '$($Result.ReturnValue)'." return } } else { Write-Verbose -Message "File share '$Name' was not removed." } } } } function Set-cLocalFileShare { [CmdletBinding(ConfirmImpact = 'Medium', SupportsShouldProcess = $true)] param ( [Parameter(Mandatory = $true, ValueFromPipeline = $true)] [ValidateNotNullOrEmpty()] [String] $Name, [Parameter(Mandatory = $false)] [UInt32] $ConcurrentUserLimit = 0, [Parameter(Mandatory = $false)] [String] $Description = $null ) process { $Share = Get-WmiObject -Class Win32_Share -Filter "Name = '$Name' AND Type = 0" if (-not $Share) { Write-Error -Message "File share '$Name' could not be found." return } if (-not $PSCmdlet.ShouldProcess($Name, 'Set File Share')) { return } if ($PSBoundParameters.Keys.Where({$_ -in 'ConcurrentUserLimit', 'Description'})) { if ($PSBoundParameters.ContainsKey('ConcurrentUserLimit')) { if ($ConcurrentUserLimit -eq 0) { # The SetShareInfo method of the Win32_Share class cannot set the AllowMaximum property to True Invoke-Expression -Command "$Env:SystemRoot\System32\net.exe Share '$Name' /Unlimited" | Out-Null } } else { $ConcurrentUserLimit = $Share.MaximumAllowed } if (-not $PSBoundParameters.ContainsKey('Description')) { $Description = $Share.Description } $Result = $Share.SetShareInfo($ConcurrentUserLimit, $Description, $null) if ($Result) { if ($Result.ReturnValue -eq 0) { Write-Verbose -Message "File share '$Name' was set." } else { Write-Error -Message "Unable to set file share '$Name'. Return code: '$($Result.ReturnValue)'." return } } else { Write-Verbose -Message "File share '$Name' was not set." } } } } function Get-cLocalFileShareAccess { [CmdletBinding()] param ( [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] [String] $Name ) begin { Initialize-cLocalFileShareType $OutputEntries = New-Object -TypeName 'System.Collections.ObjectModel.Collection`1[cLocalFileShare.AccessRule]' } process { $ShareSecurity = Get-WmiObject -Class Win32_LogicalShareSecuritySetting -Filter "Name = '$Name'" if (-not $ShareSecurity) { Write-Error -Message "The Win32_LogicalShareSecuritySetting object could not be found. Please ensure file share '$Name' exists." return } $SecurityDescriptor = $ShareSecurity.GetSecurityDescriptor().Descriptor $SecurityDescriptor.DACL | ForEach-Object { $Identity = Resolve-IdentityReference -Identity $_.Trustee.SIDString -Verbose:$false $OutputEntries.Add((New-Object -TypeName cLocalFileShare.AccessRule -ArgumentList $Identity.Name, $_.AceType, $_.AccessMask)) } } end { return $OutputEntries } } function Set-cLocalFileShareAccess { [CmdletBinding(SupportsShouldProcess = $true)] param ( [Parameter(Mandatory = $true, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true)] [ValidateNotNullOrEmpty()] [String] $Name, [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] [Object[]] $AccessRuleCollection ) begin { Initialize-cLocalFileShareType [cLocalFileShare.AccessRule[]]$AccessRuleCollection = $AccessRuleCollection } process { $ShareSecurity = Get-WmiObject -Class Win32_LogicalShareSecuritySetting -Filter "Name = '$Name'" if (-not $ShareSecurity) { Write-Error -Message "The Win32_LogicalShareSecuritySetting object could not be found. Please ensure file share '$Name' exists." return } if ($PSCmdlet.ShouldProcess($Name, 'Set Share Permissions')) { $AccessRuleCollection | Select-Object -PipelineVariable AccessRule | ForEach-Object -Begin { $SecurityDescriptor = ([WmiClass]'Win32_SecurityDescriptor').CreateInstance() $SecurityDescriptor.ControlFlags = 32772 } -Process { "Adding '{0}' '{1}' access permission for '{2}'." -f $AccessRule.AccessControlType, $AccessRule.AccessRight, $AccessRule.AccountName | Write-Verbose $Trustee = ([WmiClass]'Win32_Trustee').CreateInstance() $Trustee.SIDString = (Resolve-IdentityReference -Identity $AccessRule.AccountName -Verbose:$false).SID $Ace = ([WmiClass]'Win32_ACE').CreateInstance() $Ace.AccessMask = $AccessRule.AccessRight $Ace.AceFlags = 0 $Ace.AceType = $AccessRule.AccessControlType $Ace.Trustee = $Trustee $SecurityDescriptor.DACL += $Ace } -End { if ($SecurityDescriptor.DACL.Count -ne 0) { $Result = $ShareSecurity.SetSecurityDescriptor($SecurityDescriptor) if ($Result.ReturnValue -eq 0) { "Permissions were set on file share '{0}'." -f $ShareSecurity.Name | Write-Verbose } else { "Failed to set permissions on file share '{0}'. Return code: '{1}'." -f $ShareSecurity.Name, $Result.ReturnValue | Write-Error return } } else { "Permissions were not set on file share '{0}'." -f $ShareSecurity.Name | Write-Verbose } } } } } function ConvertFrom-cLocalFileShareAccess { [CmdletBinding()] param ( [Parameter(Mandatory = $true, ValueFromPipeline = $true)] [Object[]] $InputObject ) begin { $ReturnValue = [PSCustomObject]@{ FullAccess = @() ChangeAccess = @() ReadAccess = @() NoAccess = @() } } process { foreach ($Item in $InputObject) { if ($Item.AccessRight -eq 'Full' -and $Item.AccessControlType -eq 'Allow') { $ReturnValue.FullAccess += $Item.AccountName } elseif ($Item.AccessRight -eq 'Change' -and $Item.AccessControlType -eq 'Allow') { $ReturnValue.ChangeAccess += $Item.AccountName } elseif ($Item.AccessRight -eq 'Read' -and $Item.AccessControlType -eq 'Allow') { $ReturnValue.ReadAccess += $Item.AccountName } elseif ($Item.AccessControlType -eq 'Deny') { $ReturnValue.NoAccess += $Item.AccountName } } } end { return $ReturnValue } } function ConvertTo-cLocalFileShareAccess { [CmdletBinding()] param ( [Parameter(Mandatory = $false, ValueFromPipelineByPropertyName = $true)] [String[]] $FullAccess = $null, [Parameter(Mandatory = $false, ValueFromPipelineByPropertyName = $true)] [String[]] $ChangeAccess = $null, [Parameter(Mandatory = $false, ValueFromPipelineByPropertyName = $true)] [String[]] $ReadAccess = $null, [Parameter(Mandatory = $false, ValueFromPipelineByPropertyName = $true)] [String[]] $NoAccess = $null ) begin { Initialize-cLocalFileShareType $InputEntries = New-Object -TypeName 'System.Collections.ObjectModel.Collection`1[cLocalFileShare.AccessRule]' $OutputEntries = New-Object -TypeName 'System.Collections.ObjectModel.Collection`1[cLocalFileShare.AccessRule]' } process { if ($PSBoundParameters.ContainsKey('FullAccess') -and $FullAccess.Count -ne 0) { $FullAccess | Resolve-IdentityReference | ForEach-Object {$InputEntries.Add((New-Object -TypeName cLocalFileShare.AccessRule -ArgumentList $_.Name, 'Allow', 'Full'))} } if ($PSBoundParameters.ContainsKey('ChangeAccess') -and $ChangeAccess.Count -ne 0) { $ChangeAccess | Resolve-IdentityReference | ForEach-Object {$InputEntries.Add((New-Object -TypeName cLocalFileShare.AccessRule -ArgumentList $_.Name, 'Allow', 'Change'))} } if ($PSBoundParameters.ContainsKey('ReadAccess') -and $ReadAccess.Count -ne 0) { $ReadAccess | Resolve-IdentityReference | ForEach-Object {$InputEntries.Add((New-Object -TypeName cLocalFileShare.AccessRule -ArgumentList $_.Name, 'Allow', 'Read'))} } if ($PSBoundParameters.ContainsKey('NoAccess') -and $NoAccess.Count -ne 0) { $NoAccess | Resolve-IdentityReference | ForEach-Object {$InputEntries.Add((New-Object -TypeName cLocalFileShare.AccessRule -ArgumentList $_.Name, 'Deny', 'Full'))} } } end { $InputEntries | Group-Object -Property AccountName -PipelineVariable EntryGroup | ForEach-Object { $EntryGroup.Group | ForEach-Object -Begin { $OutputEntry = New-Object -TypeName cLocalFileShare.AccessRule -ArgumentList $EntryGroup.Name, 'Allow', 'Read' } -Process { $OutputEntry.AccessControlType = $OutputEntry.AccessControlType -bor $_.AccessControlType $OutputEntry.AccessRight = $OutputEntry.AccessRight -bor $_.AccessRight } -End { $OutputEntries += $OutputEntry } } return $OutputEntries } } function Resolve-IdentityReference { [CmdletBinding()] param ( [Parameter(Mandatory = $true, ValueFromPipeline = $true)] [String] $Identity ) process { try { Write-Verbose -Message "Resolving identity reference '$Identity'." if ($Identity -match '^S-\d-(\d+-){1,14}\d+$') { [System.Security.Principal.SecurityIdentifier]$Identity = $Identity } else { [System.Security.Principal.NTAccount]$Identity = $Identity } $SID = $Identity.Translate([System.Security.Principal.SecurityIdentifier]) $NTAccount = $SID.Translate([System.Security.Principal.NTAccount]) $OutputObject = [PSCustomObject]@{Name = $NTAccount.Value; SID = $SID.Value} return $OutputObject } catch { "Unable to resolve identity reference '{0}'. Error: '{1}'" -f $Identity, $_.Exception.Message | Write-Error return } } } #endregion |