diff --git a/CHANGELOG.md b/CHANGELOG.md index 8ed15d55..4d3f2588 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,9 +2,16 @@ ## Unreleased -- xComputer: +- Computer: - Fix for 'directory service is busy' error when joining a domain and renaming a computer when JoinOU is specified - Fixes [Issue #221](https://github.com/PowerShell/ComputerManagementDsc/issues/221). +- Added new resource SmbShare + - Moved and improved from deprecated module xSmbShare. +- Changes to ComputerManagementDsc.Common + - Updated Test-DscParameterState so it now can compare zero item + collections (arrays). +- Changes to WindowsEventLog + - Minor style guideline cleanup. ## 6.4.0.0 diff --git a/DSCResources/MSFT_SmbShare/MSFT_SmbShare.psm1 b/DSCResources/MSFT_SmbShare/MSFT_SmbShare.psm1 new file mode 100644 index 00000000..105ffa71 --- /dev/null +++ b/DSCResources/MSFT_SmbShare/MSFT_SmbShare.psm1 @@ -0,0 +1,841 @@ +$modulePath = Join-Path -Path (Split-Path -Path (Split-Path -Path $PSScriptRoot -Parent) -Parent) -ChildPath 'Modules' + +# Import the ComputerManagementDsc Common Modules +Import-Module -Name (Join-Path -Path $modulePath ` + -ChildPath (Join-Path -Path 'ComputerManagementDsc.Common' ` + -ChildPath 'ComputerManagementDsc.Common.psm1')) -Force + +# Import the ComputerManagementDsc Resource Helper Module +Import-Module -Name (Join-Path -Path $modulePath ` + -ChildPath (Join-Path -Path 'ComputerManagementDsc.ResourceHelper' ` + -ChildPath 'ComputerManagementDsc.ResourceHelper.psm1')) + +# Import Localization Strings +$script:localizedData = Get-LocalizedData ` + -ResourceName 'MSFT_SmbShare' ` + -ResourcePath (Split-Path -Parent $Script:MyInvocation.MyCommand.Path) + +<# + .SYNOPSIS + Returns the current state of the SMB share. + + .PARAMETER Name + Specifies the name of the SMB share. + + .PARAMETER Path + Specifies the path of the SMB share. + + Not used in Get-TargetResource. +#> +function Get-TargetResource +{ + [CmdletBinding()] + [OutputType([System.Collections.Hashtable])] + param + ( + [Parameter(Mandatory = $true)] + [System.String] + $Name, + + [Parameter(Mandatory = $true)] + [System.String] + $Path + ) + + Write-Verbose -Message ($script:localizedData.GetTargetResourceMessage -f $Name) + + $returnValue = @{ + Ensure = 'Absent' + Name = $Name + Path = [System.String] $null + Description = [System.String] $null + ConcurrentUserLimit = 0 + EncryptData = $false + FolderEnumerationMode = [System.String] $null + CachingMode = [System.String] $null + ContinuouslyAvailable = $false + ShareState = [System.String] $null + ShareType = [System.String] $null + ShadowCopy = $false + Special = $false + } + + $accountsFullAccess = [system.string[]] @() + $accountsChangeAccess = [system.string[]] @() + $accountsReadAccess = [system.string[]] @() + $accountsNoAccess = [system.string[]] @() + + $smbShare = Get-SmbShare -Name $Name -ErrorAction 'SilentlyContinue' + + if ($smbShare) + { + $returnValue['Ensure'] = 'Present' + $returnValue['Name'] = $smbShare.Name + $returnValue['Path'] = $smbShare.Path + $returnValue['Description'] = $smbShare.Description + $returnValue['ConcurrentUserLimit'] = $smbShare.ConcurrentUserLimit + $returnValue['EncryptData'] = $smbShare.EncryptData + $returnValue['FolderEnumerationMode'] = $smbShare.FolderEnumerationMode.ToString() + $returnValue['CachingMode'] = $smbShare.CachingMode.ToString() + $returnValue['ContinuouslyAvailable'] = $smbShare.ContinuouslyAvailable + $returnValue['ShareState'] = $smbShare.ShareState.ToString() + $returnValue['ShareType'] = $smbShare.ShareType.ToString() + $returnValue['ShadowCopy'] = $smbShare.ShadowCopy + $returnValue['Special'] = $smbShare.Special + + $currentSmbShareAccessPermissions = Get-SmbShareAccess -Name $Name + + foreach ($access in $currentSmbShareAccessPermissions) + { + switch ($access.AccessRight) + { + 'Change' + { + if ($access.AccessControlType -eq 'Allow') + { + $accountsChangeAccess += @($access.AccountName) + } + } + + 'Read' + { + if ($access.AccessControlType -eq 'Allow') + { + $accountsReadAccess += @($access.AccountName) + } + } + + 'Full' + { + if ($access.AccessControlType -eq 'Allow') + { + $accountsFullAccess += @($access.AccountName) + } + + if ($access.AccessControlType -eq 'Deny') + { + $accountsNoAccess += @($access.AccountName) + } + } + } + } + } + else + { + Write-Verbose -Message ($script:localizedData.ShareNotFound -f $Name) + } + + <# + This adds either an empty array, or a populated array depending + if accounts with the respectively access was found. + #> + $returnValue['FullAccess'] = [System.String[]] $accountsFullAccess + $returnValue['ChangeAccess'] = [System.String[]] $accountsChangeAccess + $returnValue['ReadAccess'] = [System.String[]] $accountsReadAccess + $returnValue['NoAccess'] = [System.String[]] $accountsNoAccess + + return $returnValue +} + +<# + .SYNOPSIS + Creates or removes the SMB share. + + .PARAMETER Name + Specifies the name of the SMB share. + + .PARAMETER Path + Specifies the path of the SMB share. + + .PARAMETER Description + Specifies the description of the SMB share. + + .PARAMETER ConcurrentUserLimit + 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. The default value is zero (0). + + .PARAMETER EncryptData + Indicates that the SMB share is encrypted. + + .PARAMETER FolderEnumerationMode + Specifies which files and folders in the new SMB share are visible to + users. { AccessBased | Unrestricted } + + .PARAMETER CachingMode + Specifies the caching mode of the offline files for the SMB share. + { 'None' | 'Manual' | 'Programs' | 'Documents' | 'BranchCache' } + + .PARAMETER ContinuouslyAvailable + Specifies whether the SMB share should be continuously available. + + .PARAMETER FullAccess + Specifies which accounts are granted full permission to access the + SMB share. + + .PARAMETER ChangeAccess + Specifies which accounts will be granted modify permission to access the + SMB share. + + .PARAMETER ReadAccess + Specifies which accounts is granted read permission to access the SMB share. + + .PARAMETER NoAccess + Specifies which accounts are denied access to the SMB share. + + .PARAMETER Ensure + Specifies if the SMB share should be added or removed. +#> +function Set-TargetResource +{ + [CmdletBinding()] + param + ( + [Parameter(Mandatory = $true)] + [System.String] + $Name, + + [Parameter(Mandatory = $true)] + [System.String] + $Path, + + [Parameter()] + [System.String] + $Description, + + [Parameter()] + [System.UInt32] + $ConcurrentUserLimit, + + [Parameter()] + [System.Boolean] + $EncryptData, + + [Parameter()] + [ValidateSet('AccessBased', 'Unrestricted')] + [System.String] + $FolderEnumerationMode, + + [Parameter()] + [ValidateSet('None', 'Manual', 'Programs', 'Documents', 'BranchCache')] + [System.String] + $CachingMode, + + [Parameter()] + [System.Boolean] + $ContinuouslyAvailable, + + [Parameter()] + [System.String[]] + $FullAccess, + + [Parameter()] + [System.String[]] + $ChangeAccess, + + [Parameter()] + [System.String[]] + $ReadAccess, + + [Parameter()] + [System.String[]] + $NoAccess, + + [Parameter()] + [ValidateSet('Present', 'Absent')] + [System.String] + $Ensure = 'Present' + ) + + Assert-AccessPermissionParameters @PSBoundParameters + + <# + Copy the $PSBoundParameters to a new hash table, so we have the + original intact. + #> + $smbShareParameters = @{} + $PSBoundParameters + + $currentSmbShareConfiguration = Get-TargetResource -Name $Name -Path $Path + + if ($currentSmbShareConfiguration.Ensure -eq 'Present') + { + Write-Verbose -Message ($script:localizedData.IsPresent -f $Name) + + if ($Ensure -eq 'Present') + { + Write-Verbose -Message $script:localizedData.UpdatingProperties + + $parametersToRemove = $smbShareParameters.Keys | + Where-Object -FilterScript { + $_ -in ('ChangeAccess','ReadAccess','FullAccess','NoAccess','Ensure','Path') + } + + $parametersToRemove | ForEach-Object -Process { + $smbShareParameters.Remove($_) + } + + # Use Set-SmbShare for performing operations other than changing access + Set-SmbShare @smbShareParameters -Force -ErrorAction 'Stop' + + $smbShareAccessPermissionParameters = @{ + Name = $Name + } + + if ($PSBoundParameters.ContainsKey('FullAccess')) + { + $smbShareAccessPermissionParameters['FullAccess'] = $FullAccess + } + + if ($PSBoundParameters.ContainsKey('ChangeAccess')) + { + $smbShareAccessPermissionParameters['ChangeAccess'] = $ChangeAccess + } + + if ($PSBoundParameters.ContainsKey('ReadAccess')) + { + $smbShareAccessPermissionParameters['ReadAccess'] = $ReadAccess + } + + if ($PSBoundParameters.ContainsKey('NoAccess')) + { + $smbShareAccessPermissionParameters['NoAccess'] = $NoAccess + } + + # We should only pass the access collections that the user wants to enforce. + Remove-SmbShareAccessPermission @smbShareAccessPermissionParameters + + Add-SmbShareAccessPermission @smbShareAccessPermissionParameters + } + else + { + Write-Verbose -Message ($script:localizedData.RemoveShare -f $Name) + + Remove-SmbShare -name $Name -Force -ErrorAction 'Stop' + } + } + else + { + if ($Ensure -eq 'Present') + { + $smbShareParameters.Remove('Ensure') + + Write-Verbose -Message ($script:localizedData.CreateShare -f $Name) + + <# + Remove access collections that are empty, since empty + collections are not allowed to be provided to the cmdlet + New-SmbShare. + #> + foreach ($accessProperty in ('ChangeAccess','ReadAccess','FullAccess','NoAccess')) + { + if ($smbShareParameters.ContainsKey($accessProperty) -and -not $smbShareParameters[$accessProperty]) + { + $smbShareParameters.Remove($accessProperty) + } + } + + New-SmbShare @smbShareParameters -ErrorAction 'Stop' + } + } +} + +<# + .SYNOPSIS + Determines if the SMB share is in the desired state. + + .PARAMETER Name + Specifies the name of the SMB share. + + .PARAMETER Path + Specifies the path of the SMB share. + + .PARAMETER Description + Specifies the description of the SMB share. + + .PARAMETER ConcurrentUserLimit + 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. The default value is zero (0). + + .PARAMETER EncryptData + Indicates that the SMB share is encrypted. + + .PARAMETER FolderEnumerationMode + Specifies which files and folders in the new SMB share are visible to + users. { AccessBased | Unrestricted } + + .PARAMETER CachingMode + Specifies the caching mode of the offline files for the SMB share. + { 'None' | 'Manual' | 'Programs' | 'Documents' | 'BranchCache' } + + .PARAMETER ContinuouslyAvailable + Specifies whether the SMB share should be continuously available. + + .PARAMETER FullAccess + Specifies which accounts are granted full permission to access the + SMB share. + + .PARAMETER ChangeAccess + Specifies which accounts will be granted modify permission to access the + SMB share. + + .PARAMETER ReadAccess + Specifies which accounts is granted read permission to access the SMB share. + + .PARAMETER NoAccess + Specifies which accounts are denied access to the SMB share. + + .PARAMETER Ensure + Specifies if the SMB share should be added or removed. +#> +function Test-TargetResource +{ + [CmdletBinding()] + [OutputType([System.Boolean])] + param + ( + [Parameter(Mandatory = $true)] + [System.String] + $Name, + + [Parameter(Mandatory = $true)] + [System.String] + $Path, + + [Parameter()] + [System.String] + $Description, + + [Parameter()] + [System.UInt32] + $ConcurrentUserLimit, + + [Parameter()] + [System.Boolean] + $EncryptData, + + [Parameter()] + [ValidateSet('AccessBased', 'Unrestricted')] + [System.String] + $FolderEnumerationMode, + + [Parameter()] + [ValidateSet('None', 'Manual', 'Programs', 'Documents', 'BranchCache')] + [System.String] + $CachingMode, + + [Parameter()] + [System.Boolean] + $ContinuouslyAvailable, + + [Parameter()] + [System.String[]] + $FullAccess, + + [Parameter()] + [System.String[]] + $ChangeAccess, + + [Parameter()] + [System.String[]] + $ReadAccess, + + [Parameter()] + [System.String[]] + $NoAccess, + + [Parameter()] + [ValidateSet('Present', 'Absent')] + [System.String] + $Ensure = 'Present' + ) + + Assert-AccessPermissionParameters @PSBoundParameters + + Write-Verbose -Message ($script:localizedData.TestTargetResourceMessage -f $Name) + + $resourceRequiresUpdate = $false + + $currentSmbShareConfiguration = Get-TargetResource -Name $Name -Path $Path + + if ($currentSmbShareConfiguration.Ensure -eq $Ensure) + { + if ($Ensure -eq 'Present') + { + Write-Verbose -Message ( + '{0} {1}' -f ` + ($script:localizedData.IsPresent -f $Name), + $script:localizedData.EvaluatingProperties + ) + + <# + Using $VerbosePreference so that the verbose messages in + Test-DscParameterState is outputted, if the user requested + verbose messages. + #> + $resourceRequiresUpdate = Test-DscParameterState ` + -CurrentValues $currentSmbShareConfiguration ` + -DesiredValues $PSBoundParameters ` + -Verbose:$VerbosePreference + } + else + { + Write-Verbose -Message ($script:localizedData.IsAbsent -f $Name) + + $resourceRequiresUpdate = $true + } + } + + return $resourceRequiresUpdate +} + +<# + .SYNOPSIS + Removes the access permission for accounts that are no longer part + of the respectively access collections (FullAccess, ChangeAccess, + ReadAccess, and NoAccess). + + .PARAMETER Name + The name of the SMB share for which to remove access permission. + + .PARAMETER FullAccess + A string collection of account names that _should have_ full access + permission. The accounts not in this collection will be removed from + the SMB share. + + .PARAMETER ChangeAccess + A string collection of account names that _should have_ change access + permission. The accounts not in this collection will be removed from + the SMB share. + + .PARAMETER ReadAccess + A string collection of account names that _should have_ read access + permission. The accounts not in this collection will be removed from + the SMB share. + + .PARAMETER NoAccess + A string collection of account names that _should be_ denied access + to the SMB share. The accounts not in this collection will be removed + from the SMB share. + + .NOTES + The access permission is only removed if the parameter was passed + into the function. +#> +function Remove-SmbShareAccessPermission +{ + param + ( + [Parameter(Mandatory = $true)] + [System.String] + $Name, + + [Parameter()] + [System.String[]] + $FullAccess, + + [Parameter()] + [System.String[]] + $ChangeAccess, + + [Parameter()] + [System.String[]] + $ReadAccess, + + [Parameter()] + [System.String[]] + $NoAccess + ) + + $currentSmbShareAccessPermissions = Get-SmbShareAccess -Name $Name + + <# + First all access must be removed for accounts that should not + have permission, or should be unblocked (those that was denied + access). After that we can add new accounts using the function + Add-SmbShareAccessPermission. + #> + foreach ($smbShareAccess in $currentSmbShareAccessPermissions) + { + switch ($smbShareAccess.AccessControlType) + { + 'Allow' + { + $shouldRevokeAccess = $false + + foreach ($accessRight in 'Change','Read','Full') + { + $accessRightVariableName = '{0}Access' -f $accessRight + $shouldRevokeAccess = $shouldRevokeAccess ` + -or ( + $smbShareAccess.AccessRight -eq $accessRight ` + -and $PSBoundParameters.ContainsKey($accessRightVariableName) ` + -and $smbShareAccess.AccountName -notin $PSBoundParameters[$accessRightVariableName] + ) + } + + if ($shouldRevokeAccess) + { + Write-Verbose -Message ($script:localizedData.RevokeAccess -f $smbShareAccess.AccountName, $Name) + + Revoke-SmbShareAccess -Name $Name -AccountName $smbShareAccess.AccountName -Force -ErrorAction 'Stop' + } + } + + 'Deny' + { + if ($smbShareAccess.AccessRight -eq 'Full') + { + if ($PSBoundParameters.ContainsKey('NoAccess') -and $smbShareAccess.AccountName -notin $NoAccess) + { + Write-Verbose -Message ($script:localizedData.UnblockAccess -f $smbShareAccess.AccountName, $Name) + + Unblock-SmbShareAccess -Name $Name -AccountName $smbShareAccess.AccountName -Force -ErrorAction 'Stop' + } + } + } + } + } +} + +<# + .SYNOPSIS + Add the access permission to the SMB share for accounts, in the + respectively access collections (FullAccess, ChangeAccess, + ReadAccess, and NoAccess), that do not yet have access. + + .PARAMETER Name + The name of the SMB share to add access permission to. + + .PARAMETER FullAccess + A string collection of account names that should have full access + permission. The accounts in this collection will be added to the + SMB share. + + .PARAMETER ChangeAccess + A string collection of account names that should have change access + permission. The accounts in this collection will be added to the + SMB share. + + .PARAMETER ReadAccess + A string collection of account names that should have read access + permission. The accounts in this collection will be added to the + SMB share. + + .PARAMETER NoAccess + A string collection of account names that should be denied access + to the SMB share. The accounts in this collection will be added to + the SMB share. +#> +function Add-SmbShareAccessPermission +{ + param + ( + [Parameter(Mandatory = $true)] + [System.String] + $Name, + + [Parameter()] + [System.String[]] + $FullAccess, + + [Parameter()] + [System.String[]] + $ChangeAccess, + + [Parameter()] + [System.String[]] + $ReadAccess, + + [Parameter()] + [System.String[]] + $NoAccess + ) + + $currentSmbShareAccessPermissions = Get-SmbShareAccess -Name $Name + + if ($PSBoundParameters.ContainsKey('ChangeAccess')) + { + # Get already added account names. + $smbShareChangeAccessObjects = $currentSmbShareAccessPermissions | Where-Object -FilterScript { + $_.AccessControlType -eq 'Allow' ` + -and $_.AccessRight -eq 'Change' + } + + # Get a collection of just the account names. + $changeAccessAccountNames = @($smbShareChangeAccessObjects.AccountName) + + $newAccountsToHaveChangeAccess = $ChangeAccess | Where-Object -FilterScript { + $_ -notin $changeAccessAccountNames + } + + $accessRight = 'Change' + + # Add new accounts that should have change permission. + $newAccountsToHaveChangeAccess | ForEach-Object -Process { + Write-Verbose -Message ($script:localizedData.GrantAccess -f $accessRight, $_, $Name) + + Grant-SmbShareAccess -Name $Name -AccountName $_ -AccessRight $accessRight -Force -ErrorAction 'Stop' + } + } + + if ($PSBoundParameters.ContainsKey('ReadAccess')) + { + # Get already added account names. + $smbShareReadAccessObjects = $currentSmbShareAccessPermissions | Where-Object -FilterScript { + $_.AccessControlType -eq 'Allow' ` + -and $_.AccessRight -eq 'Read' + } + + # Get a collection of just the account names. + $readAccessAccountNames = @($smbShareReadAccessObjects.AccountName) + + $newAccountsToHaveReadAccess = $ReadAccess | Where-Object -FilterScript { + $_ -notin $readAccessAccountNames + } + + $accessRight = 'Read' + + # Add new accounts that should have read permission. + $newAccountsToHaveReadAccess | ForEach-Object -Process { + Write-Verbose -Message ($script:localizedData.GrantAccess -f $accessRight, $_, $Name) + + Grant-SmbShareAccess -Name $Name -AccountName $_ -AccessRight $accessRight -Force -ErrorAction 'Stop' + } + } + + if ($PSBoundParameters.ContainsKey('FullAccess')) + { + # Get already added account names. + $smbShareFullAccessObjects = $currentSmbShareAccessPermissions | Where-Object -FilterScript { + $_.AccessControlType -eq 'Allow' ` + -and $_.AccessRight -eq 'Full' + } + + # Get a collection of just the account names. + $fullAccessAccountNames = @($smbShareFullAccessObjects.AccountName) + + $newAccountsToHaveFullAccess = $FullAccess | Where-Object -FilterScript { + $_ -notin $fullAccessAccountNames + } + + $accessRight = 'Full' + + # Add new accounts that should have full permission. + $newAccountsToHaveFullAccess | ForEach-Object -Process { + Write-Verbose -Message ($script:localizedData.GrantAccess -f $accessRight, $_, $Name) + + Grant-SmbShareAccess -Name $Name -AccountName $_ -AccessRight $accessRight -Force -ErrorAction 'Stop' + } + } + + if ($PSBoundParameters.ContainsKey('NoAccess')) + { + # Get already added account names. + $smbShareNoAccessObjects = $currentSmbShareAccessPermissions | Where-Object -FilterScript { + $_.AccessControlType -eq 'Deny' ` + -and $_.AccessRight -eq 'Full' + } + + # Get a collection of just the account names. + $noAccessAccountNames = @($smbShareNoAccessObjects.AccountName) + + $newAccountsToHaveNoAccess = $NoAccess | Where-Object -FilterScript { + $_ -notin $noAccessAccountNames + } + + # Add new accounts that should be denied permission. + $newAccountsToHaveNoAccess | ForEach-Object -Process { + Write-Verbose -Message ($script:localizedData.DenyAccess -f $_, $Name) + + Block-SmbShareAccess -Name $Name -AccountName $_ -Force -ErrorAction 'Stop' + } + } +} + +<# + .SYNOPSIS + Assert that not only empty collections are passed in the + respectively access permission collections (FullAccess, + ChangeAccess, ReadAccess, and NoAccess). + + .PARAMETER Name + The name of the SMB share to add access permission to. + + .PARAMETER FullAccess + A string collection of account names that should have full access + permission. The accounts in this collection will be added to the + SMB share. + + .PARAMETER ChangeAccess + A string collection of account names that should have change access + permission. The accounts in this collection will be added to the + SMB share. + + .PARAMETER ReadAccess + A string collection of account names that should have read access + permission. The accounts in this collection will be added to the + SMB share. + + .PARAMETER NoAccess + A string collection of account names that should be denied access + to the SMB share. The accounts in this collection will be added to + the SMB share. + + .PARAMETER RemainingParameters + Container for the rest of the potentially splatted parameters from + the $PSBoundParameters object. + + .NOTES + The group 'Everyone' is automatically given read access by + the cmdlet New-SmbShare if all access permission parameters + (FullAccess, ChangeAccess, ReadAccess, NoAccess) is set to @(). + For that reason we need neither of the parameters, or at least + one to specify an account. +#> +function Assert-AccessPermissionParameters +{ + param + ( + [Parameter()] + [System.String[]] + $FullAccess, + + [Parameter()] + [System.String[]] + $ChangeAccess, + + [Parameter()] + [System.String[]] + $ReadAccess, + + [Parameter()] + [System.String[]] + $NoAccess, + + [Parameter(ValueFromRemainingArguments)] + [System.Collections.Generic.List`1[System.Object]] + $RemainingParameters + ) + + <# + First check if ReadAccess is monitored (part of the configuration). + If it is not monitored, then we don't need to worry if Everyone is + added. + #> + if ($PSBoundParameters.ContainsKey('ReadAccess') -and -not $ReadAccess) + { + $fullAccessIsEmpty = $PSBoundParameters.ContainsKey('FullAccess') -and -not $FullAccess + $changeAccessIsEmpty = $PSBoundParameters.ContainsKey('ChangeAccess') -and -not $ChangeAccess + $noAccessIsEmpty = $PSBoundParameters.ContainsKey('NoAccess') -and -not $NoAccess + + <# + If ReadAccess should have no members, then we need at least one + member in one of the other access permission collections. + #> + if ($fullAccessIsEmpty -and $changeAccessIsEmpty -and $noAccessIsEmpty) + { + New-InvalidArgumentException -Message $script:localizedData.InvalidAccessParametersCombination -ArgumentName 'FullAccess, ChangeAccess, ReadAccess, NoAccess' + } + } +} diff --git a/DSCResources/MSFT_SmbShare/MSFT_SmbShare.schema.mof b/DSCResources/MSFT_SmbShare/MSFT_SmbShare.schema.mof new file mode 100644 index 00000000..965d6c91 --- /dev/null +++ b/DSCResources/MSFT_SmbShare/MSFT_SmbShare.schema.mof @@ -0,0 +1,25 @@ + +[ClassVersion("1.0.0.0"), FriendlyName("SmbShare")] +class MSFT_SmbShare : OMI_BaseResource +{ + [Key, Description("Specifies the name of the SMB share.")] String Name; + [Required, Description("Specifies the path of the SMB share.")] String Path; + [Write, Description("Specifies the description of the SMB share.")] String Description; + [Write, Description("Specifies which accounts will be granted modify permission to access the SMB share.")] String ChangeAccess[]; + [Write, Description("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. The default value is zero (0).")] Uint32 ConcurrentUserLimit; + [Write, Description("Indicates that the SMB share is encrypted.")] Boolean EncryptData; + [Write, Description("Specifies which files and folders in the new SMB share are visible to users."), ValueMap{"AccessBased","Unrestricted"}, Values{"AccessBased","Unrestricted"}] String FolderEnumerationMode; + [Write, Description("Specifies the caching mode of the offline files for the SMB share."), ValueMap{"None","Manual","Programs","Documents","BranchCache"}, Values{"None","Manual","Programs","Documents","BranchCache"}] String CachingMode; + [Write, Description("Specifies whether the SMB share should be continuously available.")] Boolean ContinuouslyAvailable; + [Write, Description("Specifies which accounts are granted full permission to access the SMB share.")] String FullAccess[]; + [Write, Description("Specifies which accounts are denied access to the SMB share.")] String NoAccess[]; + [Write, Description("Specifies which accounts is granted read permission to access the SMB share.")] String ReadAccess[]; + [Write, Description("Specifies if the SMB share should be added or removed."), ValueMap{"Present","Absent"}, Values{"Present","Absent"}] String Ensure; + [Read, Description("Specifies the state of the SMB share.")] String ShareState; + [Read, Description("Specifies the type of the SMB share.")] String ShareType; + [Read, Description("Specifies if this SMB share is a ShadowCopy.")] Boolean ShadowCopy; + [Read, Description("Specifies if this SMB share is a special share. E.g. an admin share, default shares, or IPC$ share.")] Boolean Special; +}; + + + diff --git a/DSCResources/MSFT_SmbShare/README.md b/DSCResources/MSFT_SmbShare/README.md new file mode 100644 index 00000000..064cc216 --- /dev/null +++ b/DSCResources/MSFT_SmbShare/README.md @@ -0,0 +1,40 @@ +# Description + +The resource is used to manage SMB shares, and access permissions to +SMB shares. + +## Requirements + +### Cluster Shares + +The property `ContinuouslyAvailable` can only be set to `$true` when +the SMB share is a cluster share in a failover cluster. Also in the blog +[SMB Transparent Failover – making file shares continuously available](https://blogs.technet.microsoft.com/filecab/2016/03/25/smb-transparent-failover-making-file-shares-continuously-available-2) +by [Claus Joergensen](https://github.com/clausjor) it is mentioned that +SMB Transparent Failover does not support cluster disks with 8.3 name +generation enabled. + +### Access permissions + +It is not allowed to provide empty collections in the configuration for +the access permissions parameters. The configuration below will cause an +exception to be thrown. + +```powershell +SmbShare 'Integration_Test' +{ + Name = 'TestShare' + Path = 'C:\Temp' + FullAccess = @() + ChangeAccess = @() + ReadAccess = @() + NoAccess = @() +} +``` + +The access permission parameters must either be all removed to manage +the access permission manually, or add at least one member to one of +the access permission parameters. If all the access permission parameters +are removed, then by design, the cmdlet New-SmbShare will add +the *Everyone* group with read access permission to the SMB share. +To prevent that, add a member to either access permission parameters. diff --git a/DSCResources/MSFT_SmbShare/en-US/MSFT_SmbShare.schema.mfl b/DSCResources/MSFT_SmbShare/en-US/MSFT_SmbShare.schema.mfl new file mode 100644 index 00000000..9d2fd5ec --- /dev/null +++ b/DSCResources/MSFT_SmbShare/en-US/MSFT_SmbShare.schema.mfl @@ -0,0 +1,21 @@ +[Description("This resource is used to configure SMB shares.") : Amended,AMENDMENT, LOCALE("MS_409")] +class MSFT_SmbShare : OMI_BaseResource +{ + [Key, Description("Specifies the name of the SMB share.") : Amended] String Name; + [Description("Specifies the path of the SMB share.") : Amended] String Path; + [Description("Specifies the description of the SMB share.") : Amended] String Description; + [Description("Specifies which accounts will be granted modify permission to access the SMB share.") : Amended] String ChangeAccess[]; + [Description("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. The default value is zero (0).") : Amended] Uint32 ConcurrentUserLimit; + [Description("Indicates that the SMB share is encrypted.") : Amended] Boolean EncryptData; + [Description("Specifies which files and folders in the new SMB share are visible to users.") : Amended] String FolderEnumerationMode; + [Description("Specifies the caching mode of the offline files for the SMB share.") : Amended] String CachingMode; + [Description("Specifies whether the SMB share should be continuously available.") : Amended] Boolean ContinuouslyAvailable; + [Description("Specifies which accounts are granted full permission to access the SMB share.") : Amended] String FullAccess[]; + [Description("Specifies which accounts are denied access to the SMB share.") : Amended] String NoAccess[]; + [Description("Specifies which accounts is granted read permission to access the SMB share.") : Amended] String ReadAccess[]; + [Description("Specifies if the SMB share should be added or removed.") : Amended] String Ensure; + [Description("Specifies the state of the SMB share.") : Amended] String ShareState; + [Description("Specifies the type of the SMB share.") : Amended] String ShareType; + [Description("Specifies if this SMB share is a ShadowCopy.") : Amended] String ShadowCopy; + [Description("Specifies if this SMB share is a special share. E.g. an admin share, default shares, or IPC$ share.") : Amended] String Special; +}; diff --git a/DSCResources/MSFT_SmbShare/en-US/MSFT_SmbShare.strings.psd1 b/DSCResources/MSFT_SmbShare/en-US/MSFT_SmbShare.strings.psd1 new file mode 100644 index 00000000..6311a490 --- /dev/null +++ b/DSCResources/MSFT_SmbShare/en-US/MSFT_SmbShare.strings.psd1 @@ -0,0 +1,18 @@ +# Localized resources for WindowsOptionalFeature + +ConvertFrom-StringData @' + GetTargetResourceMessage = Getting the current state of the SMB share '{0}'. + TestTargetResourceMessage = Determining if the SMB share '{0}' is in the desired state. + ShareNotFound = Unable to find a SMB share with the name '{0}'. + IsPresent = The SMB share with the name '{0}' exist. + IsAbsent = The SMB share with the name '{0}' does not exist. + EvaluatingProperties = Evaluating the properties of the SMB share. + UpdatingProperties = Updating properties on the SMB share that are not in desired state. + RemoveShare = Removing the SMB share with the name '{0}'. + CreateShare = Creating a SMB share with the name '{0}'. + RevokeAccess = Revoking granted permission for account '{0}' on the SMB share with the name '{1}'. + UnblockAccess = Revoking denied permission for account '{0}' on the SMB share with the name '{1}'. + GrantAccess = Granting '{0}' permission for account '{1}' on the SMB share with the name '{2}'. + DenyAccess = Denying permission for account '{0}' on the SMB share with the name '{1}'. + InvalidAccessParametersCombination = Not allowed to have all access permission parameters set to empty collections. Must either remove the access permission parameters completely, or add at least one member to one of the access permission parameters. +'@ diff --git a/DSCResources/MSFT_WindowsEventLog/MSFT_WindowsEventLog.psm1 b/DSCResources/MSFT_WindowsEventLog/MSFT_WindowsEventLog.psm1 index e5d254bc..18e543ca 100644 --- a/DSCResources/MSFT_WindowsEventLog/MSFT_WindowsEventLog.psm1 +++ b/DSCResources/MSFT_WindowsEventLog/MSFT_WindowsEventLog.psm1 @@ -177,7 +177,7 @@ function Set-TargetResource if ($PSBoundParameters.ContainsKey('LogRetentionDays')) { - if ($LogMode -eq 'AutoBackup' -and (Get-EventLog -List | Where-Object {$_.Log -like $LogName})) + if ($LogMode -eq 'AutoBackup' -and (Get-EventLog -List | Where-Object -FilterScript {$_.Log -like $LogName})) { $matchingEventLog = Get-EventLog -List | Where-Object -FilterScript { $_.Log -eq $LogName diff --git a/Examples/Resources/SmbShare/1-SmbShare_CreateShare_Config.ps1 b/Examples/Resources/SmbShare/1-SmbShare_CreateShare_Config.ps1 new file mode 100644 index 00000000..47154bb7 --- /dev/null +++ b/Examples/Resources/SmbShare/1-SmbShare_CreateShare_Config.ps1 @@ -0,0 +1,41 @@ +<#PSScriptInfo +.VERSION 1.0.0 +.GUID d0847694-6a83-4f5b-bf6f-30cb078033bc +.AUTHOR Microsoft Corporation +.COMPANYNAME Microsoft Corporation +.COPYRIGHT (c) Microsoft Corporation. All rights reserved. +.TAGS DSCConfiguration +.LICENSEURI https://github.com/PowerShell/ComputerManagementDsc/blob/master/LICENSE +.PROJECTURI https://github.com/PowerShell/ComputerManagementDsc +.ICONURI +.EXTERNALMODULEDEPENDENCIES +.REQUIREDSCRIPTS +.EXTERNALSCRIPTDEPENDENCIES +.RELEASENOTES First version. +.PRIVATEDATA 2016-Datacenter,2016-Datacenter-Server-Core +#> + +#Requires -module ComputerManagementDsc + +<# + .DESCRIPTION + This example creates an SMB share named 'Temp' for the path 'C:\Temp', + using the default values of the cmdlet `New-SmbShare`. + + .NOTES + To know the default values, see the documentation for the cmdlet + `New-SmbShare`. +#> +Configuration SmbShare_CreateShare_Config +{ + Import-DscResource -ModuleName ComputerManagementDsc + + Node localhost + { + SmbShare 'TempShare' + { + Name = 'Temp' + Path = 'C:\Temp' + } + } +} diff --git a/Examples/Resources/SmbShare/2-SmbShare_CreateShareAllProperties_Config.ps1 b/Examples/Resources/SmbShare/2-SmbShare_CreateShareAllProperties_Config.ps1 new file mode 100644 index 00000000..3afe5d23 --- /dev/null +++ b/Examples/Resources/SmbShare/2-SmbShare_CreateShareAllProperties_Config.ps1 @@ -0,0 +1,52 @@ +<#PSScriptInfo +.VERSION 1.0.0 +.GUID 27cc4f2a-e366-49cb-93d6-2f094567ebf3 +.AUTHOR Microsoft Corporation +.COMPANYNAME Microsoft Corporation +.COPYRIGHT (c) Microsoft Corporation. All rights reserved. +.TAGS DSCConfiguration +.LICENSEURI https://github.com/PowerShell/ComputerManagementDsc/blob/master/LICENSE +.PROJECTURI https://github.com/PowerShell/ComputerManagementDsc +.ICONURI +.EXTERNALMODULEDEPENDENCIES +.REQUIREDSCRIPTS +.EXTERNALSCRIPTDEPENDENCIES +.RELEASENOTES First version. +.PRIVATEDATA 2016-Datacenter,2016-Datacenter-Server-Core +#> + +#Requires -module ComputerManagementDsc + +<# + .DESCRIPTION + This example creates an SMB share named 'Temp' for the path 'C:\Temp', + using specific values for each supported property. + + .NOTES + Any other property not yet súpported will use the default values of the + cmdlet `New-SmbShare`.To know the default values, see the documentation + for the cmdlet `New-SmbShare`. +#> +Configuration SmbShare_CreateShareAllProperties_Config +{ + Import-DscResource -ModuleName ComputerManagementDsc + + Node localhost + { + SmbShare 'TempShare' + { + Name = 'Temp' + Path = 'C:\Temp' + Description = 'Some description' + ConcurrentUserLimit = 20 + EncryptData = $false + FolderEnumerationMode = 'AccessBased' + CachingMode = 'Manual' + ContinuouslyAvailable = $false + FullAccess = @() + ChangeAccess = @('AdminUser1') + ReadAccess = @('Everyone') + NoAccess = @('DeniedUser1') + } + } +} diff --git a/Examples/Resources/SmbShare/3-SmbShare_RemoveShare_Config.ps1 b/Examples/Resources/SmbShare/3-SmbShare_RemoveShare_Config.ps1 new file mode 100644 index 00000000..a2e26eb3 --- /dev/null +++ b/Examples/Resources/SmbShare/3-SmbShare_RemoveShare_Config.ps1 @@ -0,0 +1,41 @@ +<#PSScriptInfo +.VERSION 1.0.0 +.GUID f11d7558-0748-4a72-b743-34424cbf4407 +.AUTHOR Microsoft Corporation +.COMPANYNAME Microsoft Corporation +.COPYRIGHT (c) Microsoft Corporation. All rights reserved. +.TAGS DSCConfiguration +.LICENSEURI https://github.com/PowerShell/ComputerManagementDsc/blob/master/LICENSE +.PROJECTURI https://github.com/PowerShell/ComputerManagementDsc +.ICONURI +.EXTERNALMODULEDEPENDENCIES +.REQUIREDSCRIPTS +.EXTERNALSCRIPTDEPENDENCIES +.RELEASENOTES First version. +.PRIVATEDATA 2016-Datacenter,2016-Datacenter-Server-Core +#> + +#Requires -module ComputerManagementDsc + +<# + .DESCRIPTION + This example removes a SMB share named 'Temp'. + + .NOTES + Path must be specified because it is a mandatory parameter, + but it can be set to any value. +#> +Configuration SmbShare_RemoveShare_Config +{ + Import-DscResource -ModuleName ComputerManagementDsc + + Node localhost + { + SmbShare 'TempShare' + { + Ensure = 'Absent' + Name = 'Temp' + Path = 'NotUsed' + } + } +} diff --git a/Modules/ComputerManagementDsc.Common/ComputerManagementDsc.Common.psm1 b/Modules/ComputerManagementDsc.Common/ComputerManagementDsc.Common.psm1 index 5a47be09..162b27a2 100644 --- a/Modules/ComputerManagementDsc.Common/ComputerManagementDsc.Common.psm1 +++ b/Modules/ComputerManagementDsc.Common/ComputerManagementDsc.Common.psm1 @@ -33,7 +33,7 @@ function Remove-CommonParameter $commonParameters = [System.Management.Automation.PSCmdlet]::CommonParameters $commonParameters += [System.Management.Automation.PSCmdlet]::OptionalCommonParameters - $Hashtable.Keys | Where-Object { $_ -in $commonParameters } | ForEach-Object { + $Hashtable.Keys | Where-Object -FilterScript { $_ -in $commonParameters } | ForEach-Object -Process { $inputClone.Remove($_) } @@ -118,7 +118,7 @@ function Test-DscParameterState } else { - $desiredType = [psobject] @{ + $desiredType = [PSObject] @{ Name = 'Unknown' } } @@ -129,7 +129,7 @@ function Test-DscParameterState } else { - $currentType = [psobject] @{ + $currentType = [PSObject] @{ Name = 'Unknown' } } @@ -196,18 +196,29 @@ function Test-DscParameterState { Write-Verbose -Message ($script:localizedData.TestDscParameterCompareMessage -f $key) - if (-not $CurrentValues.ContainsKey($key) -or -not $CurrentValues.$key) + if ($CurrentValues.$key.Count -eq 0 -and $DesiredValues.$key.Count -eq 0) { - Write-Verbose -Message ($script:localizedData.NoMatchValueMessage -f $desiredType.Name, $key, $CurrentValues.$key, $desiredValuesClean.$key) - $returnValue = $false + Write-Verbose -Message ($script:localizedData.MatchEmptyCollectionMessage -f $desiredType.Name, $key) continue } + <# + This evaluation needs to be performed before the next evaluation, + because we need to be able to handle when the current value + is a zero item collection, meaning `-not $CurrentValues.$key` + would otherwise return $true in the next evaluation. + #> elseif ($CurrentValues.$key.Count -ne $DesiredValues.$key.Count) { Write-Verbose -Message ($script:localizedData.NoMatchValueDifferentCountMessage -f $desiredType.Name, $key, $CurrentValues.$key.Count, $desiredValuesClean.$key.Count) $returnValue = $false continue } + elseif (-not $CurrentValues.ContainsKey($key) -or -not $CurrentValues.$key) + { + Write-Verbose -Message ($script:localizedData.NoMatchValueMessage -f $desiredType.Name, $key, $CurrentValues.$key, $desiredValuesClean.$key) + $returnValue = $false + continue + } else { $desiredArrayValues = $DesiredValues.$key @@ -221,7 +232,7 @@ function Test-DscParameterState } else { - $desiredType = [psobject]@{ + $desiredType = [PSObject]@{ Name = 'Unknown' } } @@ -232,7 +243,7 @@ function Test-DscParameterState } else { - $currentType = [psobject]@{ + $currentType = [PSObject]@{ Name = 'Unknown' } } diff --git a/Modules/ComputerManagementDsc.Common/en-us/ComputerManagementDsc.Common.strings.psd1 b/Modules/ComputerManagementDsc.Common/en-us/ComputerManagementDsc.Common.strings.psd1 index 9ba00267..56a31211 100644 --- a/Modules/ComputerManagementDsc.Common/en-us/ComputerManagementDsc.Common.strings.psd1 +++ b/Modules/ComputerManagementDsc.Common/en-us/ComputerManagementDsc.Common.strings.psd1 @@ -6,6 +6,7 @@ ConvertFrom-StringData @' NoMatchPsCredentialUsernameMessage = NOTMATCH: PSCredential username mismatch. Current state is '{0}' and desired state is '{1}'. NoMatchTypeMismatchMessage = NOTMATCH: Type mismatch for property '{0}' Current state type is '{1}' and desired type is '{2}'. MatchValueMessage = MATCH: Value (type '{0}') for property '{1}' does match. Current state is '{2}' and desired state is '{3}'. + MatchEmptyCollectionMessage = MATCH: Value (type '{0}') for property '{1}' does match. Current state and desired state both have zero items in the collection. NoMatchValueMessage = NOTMATCH: Value (type '{0}') for property '{1}' does not match. Current state is '{2}' and desired state is '{3}'. NoMatchValueDifferentCountMessage = NOTMATCH: Value (type '{0}') for property '{1}' does have a different count. Current state count is '{2}' and desired state count is '{3}'. NoMatchElementTypeMismatchMessage = NOTMATCH: Type mismatch for property '{0}' Current state type of element [{1}] is '{2}' and desired type is '{3}'. diff --git a/README.md b/README.md index 4b477cc7..aaf33cfa 100644 --- a/README.md +++ b/README.md @@ -16,6 +16,7 @@ The **ComputerManagementDsc** module contains the following resources: which is only available on Windows Server 2012/Windows 8 and above. DSC configurations containing this resource may be compiled on Windows Server 2008 R2/Windows 7 but can not be applied._ +- **SmbShare**: this resource is use to manage SMB shares on a machine. - **TimeZone**: this resource is used for setting the time zone on a machine. - **VirtualMemory**: allows configuration of properties of the paging file on the local computer. diff --git a/Tests/Integration/MSFT_ScheduledTask.Integration.Tests.ps1 b/Tests/Integration/MSFT_ScheduledTask.Integration.Tests.ps1 index 831913c8..9b11b8fb 100644 --- a/Tests/Integration/MSFT_ScheduledTask.Integration.Tests.ps1 +++ b/Tests/Integration/MSFT_ScheduledTask.Integration.Tests.ps1 @@ -177,7 +177,7 @@ try } It 'Should have set the resource and all the parameters should match' { - $current = Get-DscConfiguration | Where-Object {$_.ConfigurationName -eq $currentConfig} + $current = Get-DscConfiguration | Where-Object -FilterScript {$_.ConfigurationName -eq $currentConfig} $current.TaskName | Should -Be 'Test task once cross timezone' $current.TaskPath | Should -Be '\ComputerManagementDsc\' $current.ActionExecutable | Should -Be 'C:\windows\system32\WindowsPowerShell\v1.0\powershell.exe' @@ -229,7 +229,7 @@ try $expectedStartTime = '2018-10-01T01:00:00' It 'Should have set the resource and all the parameters should match' { - $current = Get-DscConfiguration | Where-Object {$_.ConfigurationName -eq $currentConfig} + $current = Get-DscConfiguration | Where-Object -FilterScript {$_.ConfigurationName -eq $currentConfig} $current.TaskName | Should -Be 'Test task sync across time zone disabled' $current.TaskPath | Should -Be '\ComputerManagementDsc\' $current.ActionExecutable | Should -Be 'C:\windows\system32\WindowsPowerShell\v1.0\powershell.exe' @@ -276,7 +276,7 @@ try $expectedStartTime = '2018-10-01T01:00:00' + (Get-Date -Format 'zzz') It 'Should have set the resource and all the parameters should match' { - $current = Get-DscConfiguration | Where-Object {$_.ConfigurationName -eq $currentConfig} + $current = Get-DscConfiguration | Where-Object -FilterScript {$_.ConfigurationName -eq $currentConfig} $current.TaskName | Should -Be 'Test task sync across time zone enabled' $current.TaskPath | Should -Be '\ComputerManagementDsc\' $current.ActionExecutable | Should -Be 'C:\windows\system32\WindowsPowerShell\v1.0\powershell.exe' diff --git a/Tests/Integration/MSFT_SmbShare.Integration.Tests.ps1 b/Tests/Integration/MSFT_SmbShare.Integration.Tests.ps1 new file mode 100644 index 00000000..707ace10 --- /dev/null +++ b/Tests/Integration/MSFT_SmbShare.Integration.Tests.ps1 @@ -0,0 +1,436 @@ +<# + .SYNOPSIS + Integration tests for DSC resource SmbShare. +#> + +#region HEADER +$script:dscModuleName = 'ComputerManagementDsc' +$script:dscResourceFriendlyName = 'SmbShare' +$script:dscResourceName = "MSFT_$($script:dscResourceFriendlyName)" + +# Integration Test Template Version: 1.3.3 +$script:moduleRoot = Split-Path -Parent (Split-Path -Parent $PSScriptRoot) +if ( (-not (Test-Path -Path (Join-Path -Path $script:moduleRoot -ChildPath 'DSCResource.Tests'))) -or ` + (-not (Test-Path -Path (Join-Path -Path $script:moduleRoot -ChildPath 'DSCResource.Tests\TestHelper.psm1'))) ) +{ + & git @('clone', 'https://github.com/PowerShell/DscResource.Tests.git', (Join-Path -Path $script:moduleRoot -ChildPath 'DscResource.Tests')) +} + +Import-Module -Name (Join-Path -Path $script:moduleRoot -ChildPath (Join-Path -Path 'DSCResource.Tests' -ChildPath 'TestHelper.psm1')) -Force +$TestEnvironment = Initialize-TestEnvironment ` + -DSCModuleName $script:dscModuleName ` + -DSCResourceName $script:dscResourceName ` + -TestType Integration +#endregion + +#region HEADER + +$script:dscResourceFriendlyName = 'SmbShare' +$script:dcsResourceName = "MSFT_$($script:dscResourceFriendlyName)" + +#region Integration Tests +$configurationFile = Join-Path -Path $PSScriptRoot -ChildPath "$($script:dcsResourceName).config.ps1" +. $configurationFile + +Describe "$($script:dcsResourceName)_Integration" { + $configurationName = "$($script:dcsResourceName)_Prerequisites_Config" + + Context ('When using configuration {0}' -f $configurationName) { + It 'Should compile and apply the MOF without throwing' { + { + $configurationParameters = @{ + OutputPath = $TestDrive + ConfigurationData = $ConfigurationData + } + + & $configurationName @configurationParameters + + $startDscConfigurationParameters = @{ + Path = $TestDrive + ComputerName = 'localhost' + Wait = $true + Verbose = $true + Force = $true + ErrorAction = 'Stop' + } + + Start-DscConfiguration @startDscConfigurationParameters + } | Should -Not -Throw + } + } + + $configurationName = "$($script:dcsResourceName)_CreateShare1_Config" + + Context ('When using configuration {0}' -f $configurationName) { + It 'Should compile and apply the MOF without throwing' { + { + $configurationParameters = @{ + OutputPath = $TestDrive + ConfigurationData = $ConfigurationData + } + + & $configurationName @configurationParameters + + $startDscConfigurationParameters = @{ + Path = $TestDrive + ComputerName = 'localhost' + Wait = $true + Verbose = $true + Force = $true + ErrorAction = 'Stop' + } + + Start-DscConfiguration @startDscConfigurationParameters + } | Should -Not -Throw + } + + It 'Should be able to call Get-DscConfiguration without throwing' { + { + $script:currentConfiguration = Get-DscConfiguration -Verbose -ErrorAction Stop + } | Should -Not -Throw + } + + It 'Should have set the resource and all the parameters should match' { + $resourceCurrentState = $script:currentConfiguration | Where-Object -FilterScript { + $_.ConfigurationName -eq $configurationName ` + -and $_.ResourceId -eq "[$($script:dscResourceFriendlyName)]Integration_Test" + } + + $resourceCurrentState.Ensure | Should -Be 'Present' + $resourceCurrentState.Name | Should -Be $ConfigurationData.AllNodes.ShareName1 + $resourceCurrentState.Path | Should -Be $ConfigurationData.AllNodes.SharePath1 + $resourceCurrentState.Description | Should -BeNullOrEmpty + $resourceCurrentState.EncryptData | Should -BeFalse + $resourceCurrentState.ConcurrentUserLimit | Should -Be 0 + $resourceCurrentState.Description | Should -BeNullOrEmpty + $resourceCurrentState.CachingMode | Should -Be 'Manual' + $resourceCurrentState.ContinuouslyAvailable | Should -BeFalse + $resourceCurrentState.ShareState | Should -Be 'Online' + $resourceCurrentState.ShareType | Should -Be 'FileSystemDirectory' + $resourceCurrentState.ShadowCopy | Should -BeFalse + $resourceCurrentState.Special | Should -BeFalse + $resourceCurrentState.FullAccess | Should -BeNullOrEmpty + $resourceCurrentState.ChangeAccess | Should -BeNullOrEmpty + $resourceCurrentState.NoAccess | Should -BeNullOrEmpty + + <# + By design of the cmdlet `New-SmbShare`, the Everyone group is + always added when not providing any access permission members + in the configuration. + #> + $resourceCurrentState.ReadAccess | Should -HaveCount 1 + $resourceCurrentState.ReadAccess | Should -Contain 'Everyone' + } + + It 'Should return $true when Test-DscConfiguration is run' { + Test-DscConfiguration -Verbose | Should -Be 'True' + } + } + + $configurationName = "$($script:dcsResourceName)_CreateShare2_Config" + + Context ('When using configuration {0}' -f $configurationName) { + It 'Should compile and apply the MOF without throwing' { + { + $configurationParameters = @{ + OutputPath = $TestDrive + ConfigurationData = $ConfigurationData + } + + & $configurationName @configurationParameters + + $startDscConfigurationParameters = @{ + Path = $TestDrive + ComputerName = 'localhost' + Wait = $true + Verbose = $true + Force = $true + ErrorAction = 'Stop' + } + + Start-DscConfiguration @startDscConfigurationParameters + } | Should -Not -Throw + } + + It 'Should be able to call Get-DscConfiguration without throwing' { + { + $script:currentConfiguration = Get-DscConfiguration -Verbose -ErrorAction Stop + } | Should -Not -Throw + } + + It 'Should have set the resource and all the parameters should match' { + $resourceCurrentState = $script:currentConfiguration | Where-Object -FilterScript { + $_.ConfigurationName -eq $configurationName ` + -and $_.ResourceId -eq "[$($script:dscResourceFriendlyName)]Integration_Test" + } + + $resourceCurrentState.Ensure | Should -Be 'Present' + $resourceCurrentState.Name | Should -Be $ConfigurationData.AllNodes.ShareName2 + $resourceCurrentState.Path | Should -Be $ConfigurationData.AllNodes.SharePath2 + $resourceCurrentState.Description | Should -BeNullOrEmpty + $resourceCurrentState.EncryptData | Should -BeFalse + $resourceCurrentState.ConcurrentUserLimit | Should -Be 0 + $resourceCurrentState.Description | Should -BeNullOrEmpty + $resourceCurrentState.CachingMode | Should -Be 'Manual' + $resourceCurrentState.ContinuouslyAvailable | Should -BeFalse + $resourceCurrentState.ShareState | Should -Be 'Online' + $resourceCurrentState.ShareType | Should -Be 'FileSystemDirectory' + $resourceCurrentState.ShadowCopy | Should -BeFalse + $resourceCurrentState.Special | Should -BeFalse + $resourceCurrentState.FullAccess | Should -BeNullOrEmpty + $resourceCurrentState.ReadAccess | Should -BeNullOrEmpty + $resourceCurrentState.NoAccess | Should -BeNullOrEmpty + + <# + By design of the cmdlet `New-SmbShare`, the Everyone group is + always added when using `ReadAccess = @()` in the configuration. + #> + $resourceCurrentState.ChangeAccess | Should -HaveCount 1 + $resourceCurrentState.ChangeAccess | Should -Contain $ConfigurationData.AllNodes.UserName1 + } + + It 'Should return $true when Test-DscConfiguration is run' { + Test-DscConfiguration -Verbose | Should -Be 'True' + } + } + + $configurationName = "$($script:dcsResourceName)_UpdateProperties_Config" + + Context ('When using configuration {0}' -f $configurationName) { + It 'Should compile and apply the MOF without throwing' { + { + $configurationParameters = @{ + OutputPath = $TestDrive + ConfigurationData = $ConfigurationData + } + + & $configurationName @configurationParameters + + $startDscConfigurationParameters = @{ + Path = $TestDrive + ComputerName = 'localhost' + Wait = $true + Verbose = $true + Force = $true + ErrorAction = 'Stop' + } + + Start-DscConfiguration @startDscConfigurationParameters + } | Should -Not -Throw + } + + It 'Should be able to call Get-DscConfiguration without throwing' { + { + $script:currentConfiguration = Get-DscConfiguration -Verbose -ErrorAction Stop + } | Should -Not -Throw + } + + It 'Should have set the resource and all the parameters should match' { + $resourceCurrentState = $script:currentConfiguration | Where-Object -FilterScript { + $_.ConfigurationName -eq $configurationName ` + -and $_.ResourceId -eq "[$($script:dscResourceFriendlyName)]Integration_Test" + } + + $resourceCurrentState.Ensure | Should -Be 'Present' + $resourceCurrentState.Name | Should -Be $ConfigurationData.AllNodes.ShareName1 + $resourceCurrentState.Path | Should -Be $ConfigurationData.AllNodes.SharePath1 + $resourceCurrentState.Description | Should -Be 'A new description' + #$resourceCurrentState.EncryptData | Should -BeTrue + $resourceCurrentState.ConcurrentUserLimit | Should -Be 20 + #$resourceCurrentState.FolderEnumerationMode | Should -Be 'AccessBased' + #$resourceCurrentState.CachingMode | Should -Be 'None' + #$resourceCurrentState.ContinuouslyAvailable | Should -BeTrue + $resourceCurrentState.ShareState | Should -Be 'Online' + $resourceCurrentState.ShareType | Should -Be 'FileSystemDirectory' + $resourceCurrentState.ShadowCopy | Should -BeFalse + $resourceCurrentState.Special | Should -BeFalse + + $resourceCurrentState.FullAccess | Should -HaveCount 1 + $resourceCurrentState.FullAccess | Should -Contain $ConfigurationData.AllNodes.UserName1 + + $resourceCurrentState.ChangeAccess | Should -HaveCount 1 + $resourceCurrentState.ChangeAccess | Should -Contain $ConfigurationData.AllNodes.UserName2 + + $resourceCurrentState.ReadAccess | Should -HaveCount 1 + $resourceCurrentState.ReadAccess | Should -Contain $ConfigurationData.AllNodes.UserName3 + + $resourceCurrentState.NoAccess | Should -HaveCount 1 + $resourceCurrentState.NoAccess | Should -Contain $ConfigurationData.AllNodes.UserName4 + } + + It 'Should return $true when Test-DscConfiguration is run' { + Test-DscConfiguration -Verbose | Should -Be 'True' + } + } + + $configurationName = "$($script:dcsResourceName)_RemovePermission_Config" + + Context ('When using configuration {0}' -f $configurationName) { + It 'Should compile and apply the MOF without throwing' { + { + $configurationParameters = @{ + OutputPath = $TestDrive + ConfigurationData = $ConfigurationData + } + + & $configurationName @configurationParameters + + $startDscConfigurationParameters = @{ + Path = $TestDrive + ComputerName = 'localhost' + Wait = $true + Verbose = $true + Force = $true + ErrorAction = 'Stop' + } + + Start-DscConfiguration @startDscConfigurationParameters + } | Should -Not -Throw + } + + It 'Should be able to call Get-DscConfiguration without throwing' { + { + $script:currentConfiguration = Get-DscConfiguration -Verbose -ErrorAction Stop + } | Should -Not -Throw + } + + It 'Should have set the resource and all the parameters should match' { + $resourceCurrentState = $script:currentConfiguration | Where-Object -FilterScript { + $_.ConfigurationName -eq $configurationName ` + -and $_.ResourceId -eq "[$($script:dscResourceFriendlyName)]Integration_Test" + } + + $resourceCurrentState.Ensure | Should -Be 'Present' + $resourceCurrentState.Name | Should -Be $ConfigurationData.AllNodes.ShareName1 + $resourceCurrentState.FullAccess | Should -BeNullOrEmpty + $resourceCurrentState.ChangeAccess | Should -BeNullOrEmpty + $resourceCurrentState.NoAccess | Should -BeNullOrEmpty + + $resourceCurrentState.ReadAccess | Should -HaveCount 1 + $resourceCurrentState.ReadAccess | Should -Contain 'Everyone' + } + + It 'Should return $true when Test-DscConfiguration is run' { + Test-DscConfiguration -Verbose | Should -Be 'True' + } + } + + $configurationName = "$($script:dcsResourceName)_RemoveShare1_Config" + + Context ('When using configuration {0}' -f $configurationName) { + It 'Should compile and apply the MOF without throwing' { + { + $configurationParameters = @{ + OutputPath = $TestDrive + ConfigurationData = $ConfigurationData + } + + & $configurationName @configurationParameters + + $startDscConfigurationParameters = @{ + Path = $TestDrive + ComputerName = 'localhost' + Wait = $true + Verbose = $true + Force = $true + ErrorAction = 'Stop' + } + + Start-DscConfiguration @startDscConfigurationParameters + } | Should -Not -Throw + } + + It 'Should be able to call Get-DscConfiguration without throwing' { + { + $script:currentConfiguration = Get-DscConfiguration -Verbose -ErrorAction Stop + } | Should -Not -Throw + } + + It 'Should have set the resource and all the parameters should match' { + $resourceCurrentState = $script:currentConfiguration | Where-Object -FilterScript { + $_.ConfigurationName -eq $configurationName ` + -and $_.ResourceId -eq "[$($script:dscResourceFriendlyName)]Integration_Test" + } + + $resourceCurrentState.Ensure | Should -Be 'Absent' + $resourceCurrentState.Name | Should -Be $ConfigurationData.AllNodes.ShareName1 + } + + It 'Should return $true when Test-DscConfiguration is run' { + Test-DscConfiguration -Verbose | Should -Be 'True' + } + } + + $configurationName = "$($script:dcsResourceName)_RemoveShare2_Config" + + Context ('When using configuration {0}' -f $configurationName) { + It 'Should compile and apply the MOF without throwing' { + { + $configurationParameters = @{ + OutputPath = $TestDrive + ConfigurationData = $ConfigurationData + } + + & $configurationName @configurationParameters + + $startDscConfigurationParameters = @{ + Path = $TestDrive + ComputerName = 'localhost' + Wait = $true + Verbose = $true + Force = $true + ErrorAction = 'Stop' + } + + Start-DscConfiguration @startDscConfigurationParameters + } | Should -Not -Throw + } + + It 'Should be able to call Get-DscConfiguration without throwing' { + { + $script:currentConfiguration = Get-DscConfiguration -Verbose -ErrorAction Stop + } | Should -Not -Throw + } + + It 'Should have set the resource and all the parameters should match' { + $resourceCurrentState = $script:currentConfiguration | Where-Object -FilterScript { + $_.ConfigurationName -eq $configurationName ` + -and $_.ResourceId -eq "[$($script:dscResourceFriendlyName)]Integration_Test" + } + + $resourceCurrentState.Ensure | Should -Be 'Absent' + $resourceCurrentState.Name | Should -Be $ConfigurationData.AllNodes.ShareName2 + } + + It 'Should return $true when Test-DscConfiguration is run' { + Test-DscConfiguration -Verbose | Should -Be 'True' + } + } + + $configurationName = "$($script:dcsResourceName)_Cleanup_Config" + + Context ('When using configuration {0}' -f $configurationName) { + It 'Should compile and apply the MOF without throwing' { + { + $configurationParameters = @{ + OutputPath = $TestDrive + ConfigurationData = $ConfigurationData + } + + & $configurationName @configurationParameters + + $startDscConfigurationParameters = @{ + Path = $TestDrive + ComputerName = 'localhost' + Wait = $true + Verbose = $true + Force = $true + ErrorAction = 'Stop' + } + + Start-DscConfiguration @startDscConfigurationParameters + } | Should -Not -Throw + } + } +} +#endregion diff --git a/Tests/Integration/MSFT_SmbShare.config.ps1 b/Tests/Integration/MSFT_SmbShare.config.ps1 new file mode 100644 index 00000000..de1e0bf0 --- /dev/null +++ b/Tests/Integration/MSFT_SmbShare.config.ps1 @@ -0,0 +1,302 @@ +#region HEADER +# Integration Test Config Template Version: 1.2.0 +#endregion + +$configFile = [System.IO.Path]::ChangeExtension($MyInvocation.MyCommand.Path, 'json') +if (Test-Path -Path $configFile) +{ + <# + Allows reading the configuration data from a JSON file + for real testing scenarios outside of the CI. + #> + $ConfigurationData = Get-Content -Path $configFile | ConvertFrom-Json +} +else +{ + $ConfigurationData = @{ + AllNodes = @( + @{ + NodeName = 'localhost' + CertificateFile = $env:DscPublicCertificatePath + + ShareName1 = 'DscTestShare1' + SharePath1 = 'C:\DscTestShare1' + + ShareName2 = 'DscTestShare2' + SharePath2 = 'C:\DscTestShare2' + + UserName1 = ('{0}\SmbUser1' -f $env:COMPUTERNAME) + UserName2 = ('{0}\SmbUser2' -f $env:COMPUTERNAME) + UserName3 = ('{0}\SmbUser3' -f $env:COMPUTERNAME) + UserName4 = ('{0}\SmbUser4' -f $env:COMPUTERNAME) + Password = 'P@ssw0rd1' + } + ) + } +} + +<# + .SYNOPSIS + Creates the prerequisites for the other tests. + This creates a folder that will be shared. +#> +Configuration MSFT_SmbShare_Prerequisites_Config +{ + Import-DscResource -ModuleName 'PSDesiredStateConfiguration' + + node $AllNodes.NodeName + { + File 'CreateFolderToShare1' + { + Ensure = 'Present' + Type = 'Directory' + DestinationPath = $Node.SharePath1 + } + + File 'CreateFolderToShare2' + { + Ensure = 'Present' + Type = 'Directory' + DestinationPath = $Node.SharePath2 + } + + User 'CreateAccountUser1' + { + Ensure = 'Present' + UserName = Split-Path -Path $Node.UserName1 -Leaf + Password = New-Object ` + -TypeName System.Management.Automation.PSCredential ` + -ArgumentList @( + (Split-Path -Path $Node.UserName1 -Leaf), + (ConvertTo-SecureString -String $Node.Password -AsPlainText -Force) + ) + } + + User 'CreateAccountUser2' + { + Ensure = 'Present' + UserName = Split-Path -Path $Node.UserName2 -Leaf + Password = New-Object ` + -TypeName System.Management.Automation.PSCredential ` + -ArgumentList @( + (Split-Path -Path $Node.UserName2 -Leaf), + (ConvertTo-SecureString -String $Node.Password -AsPlainText -Force) + ) + } + + User 'CreateAccountUser3' + { + Ensure = 'Present' + UserName = Split-Path -Path $Node.UserName3 -Leaf + Password = New-Object ` + -TypeName System.Management.Automation.PSCredential ` + -ArgumentList @( + (Split-Path -Path $Node.UserName3 -Leaf), + (ConvertTo-SecureString -String $Node.Password -AsPlainText -Force) + ) + } + + User 'CreateAccountUser4' + { + Ensure = 'Present' + UserName = Split-Path -Path $Node.UserName4 -Leaf + Password = New-Object ` + -TypeName System.Management.Automation.PSCredential ` + -ArgumentList @( + (Split-Path -Path $Node.UserName1 -Leaf), + (ConvertTo-SecureString -String $Node.Password -AsPlainText -Force) + ) + } + } +} + +<# + .SYNOPSIS + Create the SMB share with default values, and no permissions. +#> +Configuration MSFT_SmbShare_CreateShare1_Config +{ + Import-DscResource -ModuleName 'ComputerManagementDsc' + + node $AllNodes.NodeName + { + SmbShare 'Integration_Test' + { + Name = $Node.ShareName1 + Path = $Node.SharePath1 + } + } +} + +<# + .SYNOPSIS + Create the SMB share with default values, and no permissions. +#> +Configuration MSFT_SmbShare_CreateShare2_Config +{ + Import-DscResource -ModuleName 'ComputerManagementDsc' + + node $AllNodes.NodeName + { + SmbShare 'Integration_Test' + { + Name = $Node.ShareName2 + Path = $Node.SharePath2 + FullAccess = @() + ChangeAccess = @($Node.UserName1) + ReadAccess = @() + NoAccess = @() + } + } +} + +<# + .SYNOPSIS + Update all properties of the SMB share. + + .NOTES + The property ContinuouslyAvailable cannot be set to $true because that + property requires the share to be a cluster share in a Failover Cluster. + + Log Name: Microsoft-Windows-SMBServer/Operational + Event ID: 1800 + Level: Error + Description: + CA failure - Failed to set continuously available property on a new or + existing file share as the file share is not a cluster share. + +#> +Configuration MSFT_SmbShare_UpdateProperties_Config +{ + Import-DscResource -ModuleName 'ComputerManagementDsc' + + node $AllNodes.NodeName + { + SmbShare 'Integration_Test' + { + Name = $Node.ShareName1 + Path = $Node.SharePath1 + FolderEnumerationMode = 'AccessBased' + CachingMode = 'None' + ConcurrentUserLimit = 20 + ContinuouslyAvailable = $false + Description = 'A new description' + EncryptData = $true + FullAccess = @($Node.UserName1) + ChangeAccess = @($Node.UserName2) + ReadAccess = @($Node.UserName3) + NoAccess = @($Node.UserName4) + } + } +} + +<# + .SYNOPSIS + Remove permission, and no other properties should be changed. +#> +Configuration MSFT_SmbShare_RemovePermission_Config +{ + Import-DscResource -ModuleName 'ComputerManagementDsc' + + node $AllNodes.NodeName + { + SmbShare 'Integration_Test' + { + Name = $Node.ShareName1 + Path = $Node.SharePath1 + FullAccess = @() + ChangeAccess = @() + ReadAccess = @('Everyone') + NoAccess = @() + } + } +} + + +<# + .SYNOPSIS + Remove the share 1. +#> +Configuration MSFT_SmbShare_RemoveShare1_Config +{ + Import-DscResource -ModuleName 'ComputerManagementDsc' + + node $AllNodes.NodeName + { + SmbShare 'Integration_Test' + { + Ensure = 'Absent' + Name = $Node.ShareName1 + Path = 'NotUsed_CanBeAnyValue' + } + } +} + +<# + .SYNOPSIS + Remove the share 2. +#> +Configuration MSFT_SmbShare_RemoveShare2_Config +{ + Import-DscResource -ModuleName 'ComputerManagementDsc' + + node $AllNodes.NodeName + { + SmbShare 'Integration_Test' + { + Ensure = 'Absent' + Name = $Node.ShareName2 + Path = 'NotUsed_CanBeAnyValue' + } + } +} + +<# + .SYNOPSIS + Clean up the prerequisites. +#> +Configuration MSFT_SmbShare_Cleanup_Config +{ + Import-DscResource -ModuleName 'PSDesiredStateConfiguration' + + node $AllNodes.NodeName + { + File 'RemoveShareFolder1' + { + Ensure = 'Absent' + Type = 'Directory' + DestinationPath = $Node.SharePath1 + } + + File 'RemoveShareFolder2s' + { + Ensure = 'Absent' + Type = 'Directory' + DestinationPath = $Node.SharePath2 + } + + User 'RemoveAccountUser1' + { + Ensure = 'Absent' + UserName = Split-Path -Path $Node.UserName1 -Leaf + } + + User 'RemoveAccountUser2' + { + Ensure = 'Absent' + UserName = Split-Path -Path $Node.UserName2 -Leaf + } + + User 'RemoveAccountUser3' + { + Ensure = 'Absent' + UserName = Split-Path -Path $Node.UserName3 -Leaf + } + + User 'RemoveAccountUser4' + { + Ensure = 'Absent' + UserName = Split-Path -Path $Node.UserName4 -Leaf + } + } +} diff --git a/Tests/Unit/MSFT_SmbShare.Tests.ps1 b/Tests/Unit/MSFT_SmbShare.Tests.ps1 new file mode 100644 index 00000000..ac582fcd --- /dev/null +++ b/Tests/Unit/MSFT_SmbShare.Tests.ps1 @@ -0,0 +1,1111 @@ +#region HEADER +$script:dscModuleName = 'ComputerManagementDsc' +$script:dscResourceName = 'MSFT_SmbShare' + +Import-Module -Name (Join-Path -Path (Join-Path -Path (Split-Path $PSScriptRoot -Parent) -ChildPath 'TestHelpers') -ChildPath 'CommonTestHelper.psm1') -Global + +# Unit Test Template Version: 1.2.4 +$script:moduleRoot = Split-Path -Parent (Split-Path -Parent $PSScriptRoot) +if ( (-not (Test-Path -Path (Join-Path -Path $script:moduleRoot -ChildPath 'DSCResource.Tests'))) -or ` + (-not (Test-Path -Path (Join-Path -Path $script:moduleRoot -ChildPath 'DSCResource.Tests\TestHelper.psm1'))) ) +{ + & git @('clone', 'https://github.com/PowerShell/DscResource.Tests.git', (Join-Path -Path $script:moduleRoot -ChildPath 'DscResource.Tests')) +} + +Import-Module -Name (Join-Path -Path $script:moduleRoot -ChildPath (Join-Path -Path 'DSCResource.Tests' -ChildPath 'TestHelper.psm1')) -Force + +$TestEnvironment = Initialize-TestEnvironment ` + -DSCModuleName $script:dscModuleName ` + -DSCResourceName $script:dscResourceName ` + -ResourceType 'Mof' ` + -TestType Unit +#endregion HEADER + +function Invoke-TestSetup +{ +} + +function Invoke-TestCleanup +{ + Restore-TestEnvironment -TestEnvironment $TestEnvironment +} + +try +{ + Invoke-TestSetup + + InModuleScope $script:DSCResourceName { + $mockShareName = 'TestShare' + $mockChangePermissionUserName = @('User1') + $mockReadPermissionUserName = @('User2') + $mockFullPermissionUserName = @('User3', 'User4') + $mockNoPermissionUserName = @('DeniedUser1') + + $mockSmbShare = ( + New-Object -TypeName Object | + Add-Member -MemberType NoteProperty -Name 'Name' -Value $mockShareName -PassThru | + Add-Member -MemberType NoteProperty -Name 'Path' -Value 'c:\temp' -PassThru | + Add-Member -MemberType NoteProperty -Name 'Description' 'Dummy share for unit testing' -PassThru | + Add-Member -MemberType NoteProperty -Name 'ConcurrentUserLimit' -Value 10 -PassThru | + Add-Member -MemberType NoteProperty -Name 'EncryptData' -Value $false -PassThru | + # 0 AccessBased | 1 Unrestricted + Add-Member -MemberType NoteProperty -Name 'FolderEnumerationMode' -Value 'AccessBased' -PassThru | + # 0 Pending | 1 Online | 2 Offline + Add-Member -MemberType NoteProperty -Name 'ShareState' -Value 'Online' -PassThru | + Add-Member -MemberType NoteProperty -Name 'ShareType' -Value 'FileSystemDirectory' -PassThru | + Add-Member -MemberType NoteProperty -Name 'ShadowCopy' -Value $false -PassThru | + Add-Member -MemberType NoteProperty -Name 'CachingMode' -Value 'Manual' -PassThru | + Add-Member -MemberType NoteProperty -Name 'ContinuouslyAvailable' -Value $true -PassThru | + Add-Member -MemberType NoteProperty -Name 'Special' -Value $false -PassThru -Force + ) + + $mockSmbShareAccess = @( + ( + New-Object -TypeName Object | + Add-Member -MemberType NoteProperty -Name 'Name' -Value $mockShareName -PassThru | + Add-Member -MemberType NoteProperty -Name 'ScopName' -Value '*' -PassThru | + Add-Member -MemberType NoteProperty -Name 'AccountName' -Value $mockFullPermissionUserName[0] -PassThru | + Add-Member -MemberType NoteProperty -Name 'AccessControlType' -Value 'Allow' -PassThru | + Add-Member -MemberType NoteProperty -Name 'AccessRight' -Value 'Full' -PassThru -Force + ), + ( + New-Object -TypeName Object | + Add-Member -MemberType NoteProperty -Name 'Name' -Value $mockShareName -PassThru | + Add-Member -MemberType NoteProperty -Name 'ScopName' -Value '*' -PassThru | + Add-Member -MemberType NoteProperty -Name 'AccountName' -Value $mockFullPermissionUserName[1] -PassThru | + Add-Member -MemberType NoteProperty -Name 'AccessControlType' -Value 'Allow' -PassThru | + Add-Member -MemberType NoteProperty -Name 'AccessRight' -Value 'Full' -PassThru -Force + ), + ( + New-Object -TypeName Object | + Add-Member -MemberType NoteProperty -Name 'Name' -Value $mockShareName -PassThru | + Add-Member -MemberType NoteProperty -Name 'ScopName' -Value '*' -PassThru | + Add-Member -MemberType NoteProperty -Name 'AccountName' -Value $mockChangePermissionUserName[0] -PassThru | + Add-Member -MemberType NoteProperty -Name 'AccessControlType' -Value 'Allow' -PassThru | + Add-Member -MemberType NoteProperty -Name 'AccessRight' -Value 'Change' -PassThru -Force + ), + ( + New-Object -TypeName Object | + Add-Member -MemberType NoteProperty -Name 'Name' -Value $mockShareName -PassThru | + Add-Member -MemberType NoteProperty -Name 'ScopName' -Value '*' -PassThru | + Add-Member -MemberType NoteProperty -Name 'AccountName' -Value $mockReadPermissionUserName[0] -PassThru | + Add-Member -MemberType NoteProperty -Name 'AccessControlType' -Value 'Allow' -PassThru | + Add-Member -MemberType NoteProperty -Name 'AccessRight' -Value 'Read' -PassThru -Force + ), + ( + New-Object -TypeName Object | + Add-Member -MemberType NoteProperty -Name 'Name' -Value $mockShareName -PassThru | + Add-Member -MemberType NoteProperty -Name 'ScopName' -Value '*' -PassThru | + Add-Member -MemberType NoteProperty -Name 'AccountName' -Value $mockNoPermissionUserName[0] -PassThru | + Add-Member -MemberType NoteProperty -Name 'AccessControlType' -Value 'Deny' -PassThru | + Add-Member -MemberType NoteProperty -Name 'AccessRight' -Value 'Full' -PassThru -Force + ) + ) + + Describe 'MSFT_SmbShare\Get-TargetResource' -Tag 'Get' { + Context 'When the system is in the desired state' { + BeforeAll { + Mock -CommandName Get-SmbShare -MockWith { + return $mockSmbShare + } + + Mock -CommandName Get-SmbShareAccess -MockWith { + return $mockSmbShareAccess + } + + $testParameters = @{ + Name = $mockSmbShare.Name + Path = $mockSmbShare.Path + Verbose = $true + } + } + + It 'Should return the correct access memberships' { + $getTargetResourceResult = Get-TargetResource @testParameters + + $getTargetResourceResult.ChangeAccess | Should -HaveCount 1 + $getTargetResourceResult.ChangeAccess[0] | Should -BeIn $mockChangePermissionUserName + + $getTargetResourceResult.ReadAccess | Should -HaveCount 1 + $getTargetResourceResult.ReadAccess[0] | Should -BeIn $mockReadPermissionUserName + + $getTargetResourceResult.FullAccess | Should -HaveCount 2 + $getTargetResourceResult.FullAccess[0] | Should -BeIn $mockFullPermissionUserName + $getTargetResourceResult.FullAccess[1] | Should -BeIn $mockFullPermissionUserName + + $getTargetResourceResult.NoAccess | Should -HaveCount 1 + $getTargetResourceResult.NoAccess[0] | Should -BeIn $mockNoPermissionUserName + + Assert-MockCalled Get-SmbShare -Exactly -Times 1 -Scope It + Assert-MockCalled Get-SmbShareAccess -Exactly -Times 1 -Scope It + } + } + + Context 'When the system is not in the desired state' { + BeforeAll { + Mock -CommandName Get-SmbShare + + $testParameters = @{ + Name = $mockSmbShare.Name + Path = $mockSmbShare.Path + Verbose = $true + } + } + + It 'Should return the correct values' { + $getTargetResourceResult = Get-TargetResource @testParameters + + $getTargetResourceResult.Ensure | Should -Be 'Absent' + $getTargetResourceResult.Name | Should -Be $testParameters.Name + $getTargetResourceResult.Path | Should -BeNullOrEmpty + $getTargetResourceResult.Description | Should -BeNullOrEmpty + $getTargetResourceResult.ConcurrentUserLimit | Should -Be 0 + $getTargetResourceResult.EncryptData | Should -BeFalse + $getTargetResourceResult.FolderEnumerationMode | Should -BeNullOrEmpty + $getTargetResourceResult.CachingMode | Should -BeNullOrEmpty + $getTargetResourceResult.ContinuouslyAvailable | Should -BeFalse + $getTargetResourceResult.ShareState | Should -BeNullOrEmpty + $getTargetResourceResult.ShareType | Should -BeNullOrEmpty + $getTargetResourceResult.ShadowCopy | Should -BeFalse + $getTargetResourceResult.Special | Should -BeFalse + $getTargetResourceResult.ChangeAccess | Should -HaveCount 0 + $getTargetResourceResult.ReadAccess | Should -HaveCount 0 + $getTargetResourceResult.FullAccess | Should -HaveCount 0 + $getTargetResourceResult.NoAccess | Should -HaveCount 0 + + Assert-MockCalled Get-SmbShare -Exactly -Times 1 -Scope It + } + } + } + + Describe 'MSFT_SmbShare\Set-TargetResource' -Tag 'Set' { + Context 'When the system is not in the desired state' { + BeforeAll { + Mock -CommandName New-SmbShare + Mock -CommandName Set-SmbShare + Mock -CommandName Remove-SmbShareAccessPermission + Mock -CommandName Add-SmbShareAccessPermission + Mock -CommandName Remove-SmbShare + } + + Context 'When the configuration should be present' { + BeforeAll { + Mock -CommandName Get-TargetResource -MockWith { + return @{ + Name = $mockShareName + Path = $null + Description = $null + ConcurrentUserLimit = [System.UInt32] 0 + EncryptData = $false + FolderEnumerationMode = $null + CachingMode = $null + ContinuouslyAvailable = $false + ShareState = $null + ShareType = $null + ShadowCopy = $false + Special = $false + FullAccess = [System.String[]] @() + ChangeAccess = [System.String[]] @() + ReadAccess = [System.String[]] @() + NoAccess = [System.String[]] @() + Ensure = 'Absent' + } + } + } + + Context 'When no access permission is given' { + It 'Should throw the correct error' { + $setTargetResourceParameters = @{ + Name = $mockShareName + Path = 'TestDrive:\Temp' + Description = 'Some description' + ConcurrentUserLimit = 2 + EncryptData = $false + FolderEnumerationMode = 'AccessBased' + CachingMode = 'Manual' + ContinuouslyAvailable = $true + ChangeAccess = @() + ReadAccess = @() + FullAccess = @() + NoAccess = @() + Verbose = $true + } + + { Set-TargetResource @setTargetResourceParameters } | Should -Throw $script:localizedData.WrongAccessParameters + } + } + + Context 'When access permissions are given' { + It 'Should call the correct mocks' { + $setTargetResourceParameters = @{ + Name = $mockShareName + Path = 'TestDrive:\Temp' + Description = 'Some description' + ConcurrentUserLimit = 2 + EncryptData = $false + FolderEnumerationMode = 'AccessBased' + CachingMode = 'Manual' + ContinuouslyAvailable = $true + ChangeAccess = $mockChangePermissionUserName + ReadAccess = $mockReadPermissionUserName + FullAccess = $mockFullPermissionUserName + NoAccess = $mockNoPermissionUserName + Verbose = $true + } + + { Set-TargetResource @setTargetResourceParameters } | Should -Not -Throw + + Assert-MockCalled New-SmbShare -Exactly -Times 1 -Scope It + Assert-MockCalled Set-SmbShare -Exactly -Times 0 -Scope It + Assert-MockCalled Remove-SmbShare -Exactly -Times 0 -Scope It + Assert-MockCalled Remove-SmbShareAccessPermission -Exactly -Times 0 -Scope It + } + } + } + + Context 'When the configuration should be absent' { + BeforeAll { + Mock -CommandName Get-TargetResource -MockWith { + return @{ + Name = $mockShareName + Ensure = 'Present' + } + } + } + + It 'Should call the correct mocks' { + $setTargetResourceParameters = @{ + Name = $mockShareName + Path = 'AnyValue' + Ensure = 'Absent' + Verbose = $true + } + + { Set-TargetResource @setTargetResourceParameters } | Should -Not -Throw + + Assert-MockCalled New-SmbShare -Exactly -Times 0 -Scope It + Assert-MockCalled Set-SmbShare -Exactly -Times 0 -Scope It + Assert-MockCalled Remove-SmbShare -Exactly -Times 1 -Scope It + } + } + + Context 'When the configuration has a property that is not in desired state' { + BeforeAll { + Mock -CommandName Get-TargetResource -MockWith { + return @{ + Name = $mockSmbShare.Name + Path = $mockSmbShare.Path + Description = $mockSmbShare.Description + ConcurrentUserLimit = [System.UInt32] $mockSmbShare.ConcurrentUserLimit + EncryptData = $mockSmbShare.EncryptData + FolderEnumerationMode = $mockSmbShare.FolderEnumerationMode + CachingMode = $mockSmbShare.CachingMode + # Property that is not in desired state. + ContinuouslyAvailable = $false + ShareState = $mockSmbShare.ShareState + ShareType = $mockSmbShare.ShareType + ShadowCopy = $mockSmbShare.ShadowCopy + Special = $mockSmbShare.Special + FullAccess = [System.String[]] $mockFullPermissionUserName + ChangeAccess = [System.String[]] $mockChangePermissionUserName + ReadAccess = [System.String[]] $mockReadPermissionUserName + NoAccess = [System.String[]] $mockNoPermissionUserName + Ensure = 'Present' + } + } + } + + It 'Should call the correct mocks' { + $setTargetResourceParameters = @{ + Name = $mockShareName + Path = 'TestDrive:\Temp' + Description = 'Some description' + ConcurrentUserLimit = 2 + EncryptData = $false + FolderEnumerationMode = 'AccessBased' + CachingMode = 'Manual' + ContinuouslyAvailable = $true + ChangeAccess = $mockChangePermissionUserName + ReadAccess = $mockReadPermissionUserName + FullAccess = $mockFullPermissionUserName + NoAccess = $mockNoPermissionUserName + Verbose = $true + } + + { Set-TargetResource @setTargetResourceParameters } | Should -Not -Throw + + Assert-MockCalled Set-SmbShare -Exactly -Times 1 -Scope It + Assert-MockCalled Remove-SmbShareAccessPermission -Exactly -Times 1 -Scope It + Assert-MockCalled Add-SmbShareAccessPermission -Exactly -Times 1 -Scope It + Assert-MockCalled New-SmbShare -Exactly -Times 0 -Scope It + Assert-MockCalled Remove-SmbShare -Exactly -Times 0 -Scope It + } + } + } + } + + Describe 'MSFT_SmbShare\Test-TargetResource' -Tag 'Test' { + Context 'When the system is not in the desired state' { + Context 'When no member are provided in any of the access permission collections' { + BeforeAll { + $testTargetResourceParameters = @{ + Name = $mockShareName + Path = 'TestDrive:\Temp' + FullAccess = @() + ChangeAccess = @() + ReadAccess = @() + NoAccess = @() + Ensure = 'Present' + Verbose = $true + } + } + + It 'Should throw the correct error' { + { + Test-TargetResource @testTargetResourceParameters + } | Should -Throw $script:localizedData.WrongAccessParameters + } + } + + Context 'When there is a configured SMB share' { + BeforeAll { + $mockDefaultTestCaseValues = @{ + TestCase = '' + Name = $mockSmbShare.Name + Path = $mockSmbShare.Path + Description = $mockSmbShare.Description + ConcurrentUserLimit = $mockSmbShare.ConcurrentUserLimit + EncryptData = $mockSmbShare.EncryptData + FolderEnumerationMode = $mockSmbShare.FolderEnumerationMode + CachingMode = $mockSmbShare.CachingMode + ContinuouslyAvailable = $mockSmbShare.ContinuouslyAvailable + FullAccess = @() + ChangeAccess = @() + ReadAccess = @($mockReadPermissionUserName) + NoAccess = @() + Ensure = 'Present' + } + + $mockTestCase1 = $mockDefaultTestCaseValues.Clone() + $testProperty = 'Path' + $mockTestCase1['TestCase'] = $testProperty + $mockTestCase1[$testProperty] = 'TestDrive:\NewFolder' + + $mockTestCase2 = $mockDefaultTestCaseValues.Clone() + $testProperty = 'Description' + $mockTestCase2['TestCase'] = $testProperty + $mockTestCase2[$testProperty] = 'New description' + + $mockTestCase3 = $mockDefaultTestCaseValues.Clone() + $testProperty = 'ConcurrentUserLimit' + $mockTestCase3['TestCase'] = $testProperty + $mockTestCase3[$testProperty] = 2 + + $mockTestCase4 = $mockDefaultTestCaseValues.Clone() + $testProperty = 'EncryptData' + $mockTestCase4['TestCase'] = $testProperty + $mockTestCase4[$testProperty] = $true + + $mockTestCase5 = $mockDefaultTestCaseValues.Clone() + $testProperty = 'FolderEnumerationMode' + $mockTestCase5['TestCase'] = $testProperty + $mockTestCase5[$testProperty] = 'Unrestricted' + + $mockTestCase6 = $mockDefaultTestCaseValues.Clone() + $testProperty = 'CachingMode' + $mockTestCase6['TestCase'] = $testProperty + $mockTestCase6[$testProperty] = 'Documents' + + $mockTestCase7 = $mockDefaultTestCaseValues.Clone() + $testProperty = 'ContinuouslyAvailable' + $mockTestCase7['TestCase'] = $testProperty + $mockTestCase7[$testProperty] = $false + + $mockTestCase8 = $mockDefaultTestCaseValues.Clone() + $testProperty = 'FullAccess' + $mockTestCase8['TestCase'] = $testProperty + $mockTestCase8[$testProperty] = @('NewUser') + + $mockTestCase9 = $mockDefaultTestCaseValues.Clone() + $testProperty = 'ChangeAccess' + $mockTestCase9['TestCase'] = $testProperty + $mockTestCase9[$testProperty] = @('NewUser') + + $mockTestCase10 = $mockDefaultTestCaseValues.Clone() + $testProperty = 'ReadAccess' + $mockTestCase10['TestCase'] = $testProperty + $mockTestCase10[$testProperty] = @('NewUser') + + $mockTestCase11 = $mockDefaultTestCaseValues.Clone() + $testProperty = 'NoAccess' + $mockTestCase11['TestCase'] = $testProperty + $mockTestCase11[$testProperty] = @('NewUser') + + $testCases = @( + $mockTestCase1 + $mockTestCase2 + $mockTestCase3 + $mockTestCase4 + $mockTestCase5 + $mockTestCase6 + $mockTestCase7 + $mockTestCase8 + $mockTestCase9 + $mockTestCase10 + $mockTestCase11 + ) + + Mock -CommandName Get-TargetResource -MockWith { + return @{ + Name = $mockSmbShare.Name + Path = $mockSmbShare.Path + Description = $mockSmbShare.Description + ConcurrentUserLimit = [System.UInt32] $mockSmbShare.ConcurrentUserLimit + EncryptData = $mockSmbShare.EncryptData + FolderEnumerationMode = $mockSmbShare.FolderEnumerationMode + CachingMode = $mockSmbShare.CachingMode + ContinuouslyAvailable = $mockSmbShare.ContinuouslyAvailable + ShareState = $mockSmbShare.ShareState + ShareType = $mockSmbShare.ShareType + ShadowCopy = $mockSmbShare.ShadowCopy + Special = $mockSmbShare.Special + FullAccess = [System.String[]] @() + ChangeAccess = [System.String[]] @() + ReadAccess = [System.String[]] @() + NoAccess = [System.String[]] @() + Ensure = 'Present' + } + } + } + + It 'Should return $false when property has the wrong value' -TestCases $testCases { + param + ( + $Name, + $Path, + $Description, + $ConcurrentUserLimit, + $EncryptData, + $FolderEnumerationMode, + $CachingMode, + $ContinuouslyAvailable, + $FullAccess, + $ChangeAccess, + $ReadAccess, + $NoAccess, + $Ensure + ) + + $testTargetResourceParameters = @{ + Name = $Name + Path = $Path + Description = $Description + ConcurrentUserLimit = $ConcurrentUserLimit + EncryptData = $EncryptData + FolderEnumerationMode = $FolderEnumerationMode + CachingMode = $CachingMode + ContinuouslyAvailable = $ContinuouslyAvailable + FullAccess = $FullAccess + ChangeAccess = $ChangeAccess + ReadAccess = $ReadAccess + NoAccess = $NoAccess + Ensure = 'Present' + Verbose = $true + } + + $testTargetResourceResult = Test-TargetResource @testTargetResourceParameters + $testTargetResourceResult | Should -BeFalse + } + + It 'Should return $false when the desired state should be ''Absent''' { + $testTargetResourceParameters = @{ + Ensure = 'Absent' + Name = $mockSmbShare.Name + Path = $mockSmbShare.Path + Description = $mockSmbShare.Description + ConcurrentUserLimit = [System.UInt32] $mockSmbShare.ConcurrentUserLimit + EncryptData = $mockSmbShare.EncryptData + FolderEnumerationMode = $mockSmbShare.FolderEnumerationMode + CachingMode = $mockSmbShare.CachingMode + ContinuouslyAvailable = $mockSmbShare.ContinuouslyAvailable + FullAccess = @() + ChangeAccess = @() + ReadAccess = @($mockReadPermissionUserName) + NoAccess = @() + Verbose = $true + } + + $testTargetResourceResult = Test-TargetResource @testTargetResourceParameters + $testTargetResourceResult | Should -BeFalse + } + } + + Context 'When there are no configured SMB share' { + BeforeAll { + Mock -CommandName Get-TargetResource -MockWith { + return @{ + Ensure = 'Absent' + } + } + } + + It 'Should return $false when the desired state should ''Present''' { + $testTargetResourceParameters = @{ + Ensure = 'Present' + Name = $mockSmbShare.Name + Path = $mockSmbShare.Path + Verbose = $true + } + + $testTargetResourceResult = Test-TargetResource @testTargetResourceParameters + $testTargetResourceResult | Should -BeFalse + } + } + } + + Context 'When the system is in the desired state' { + Context 'When there is a configured SMB share' { + BeforeAll { + Mock -CommandName Get-TargetResource -MockWith { + return @{ + Name = $mockSmbShare.Name + Path = $mockSmbShare.Path + Description = $mockSmbShare.Description + ConcurrentUserLimit = [System.UInt32] $mockSmbShare.ConcurrentUserLimit + EncryptData = $mockSmbShare.EncryptData + FolderEnumerationMode = $mockSmbShare.FolderEnumerationMode + CachingMode = $mockSmbShare.CachingMode + ContinuouslyAvailable = $mockSmbShare.ContinuouslyAvailable + ShareState = $mockSmbShare.ShareState + ShareType = $mockSmbShare.ShareType + ShadowCopy = $mockSmbShare.ShadowCopy + Special = $mockSmbShare.Special + FullAccess = [System.String[]] @() + ChangeAccess = [System.String[]] @() + ReadAccess = [System.String[]] @($mockReadPermissionUserName) + NoAccess = [System.String[]] @() + Ensure = 'Present' + } + } + } + + It 'Should return $true when the desired state should be ''Present''' { + $testTargetResourceParameters = @{ + Ensure = 'Present' + Name = $mockSmbShare.Name + Path = $mockSmbShare.Path + Description = $mockSmbShare.Description + ConcurrentUserLimit = [System.UInt32] $mockSmbShare.ConcurrentUserLimit + EncryptData = $mockSmbShare.EncryptData + FolderEnumerationMode = $mockSmbShare.FolderEnumerationMode + CachingMode = $mockSmbShare.CachingMode + ContinuouslyAvailable = $mockSmbShare.ContinuouslyAvailable + FullAccess = @() + ChangeAccess = @() + ReadAccess = @($mockReadPermissionUserName) + NoAccess = @() + Verbose = $true + } + + $testTargetResourceResult = Test-TargetResource @testTargetResourceParameters + $testTargetResourceResult | Should -BeTrue + } + } + + Context 'When there are no configured SMB share' { + BeforeAll { + Mock -CommandName Get-TargetResource -MockWith { + return @{ + Ensure = 'Absent' + } + } + } + + It 'Should return $true when the desired state should ''Absent''' { + $testTargetResourceParameters = @{ + Ensure = 'Absent' + Name = $mockSmbShare.Name + Path = $mockSmbShare.Path + Verbose = $true + } + + $testTargetResourceResult = Test-TargetResource @testTargetResourceParameters + $testTargetResourceResult | Should -BeTrue + } + } + } + } + + Describe 'MSFT_SmbShare\Add-SmbShareAccessPermission' -Tag 'Helper' { + BeforeAll { + Mock -CommandName Grant-SmbShareAccess + Mock -CommandName Block-SmbShareAccess + + Mock -CommandName Get-SmbShareAccess -MockWith { + <# + Mocked permission: + + Full = @('User3', 'User4') + Change = @('User1') + Read = @('User2') + Denied = @('DeniedUser1') + #> + return $mockSmbShareAccess + } + } + + Context 'When adding granted permissions to an SMB share' { + BeforeAll { + $mockExpectedAccountToBeAdded = 'NewUser' + } + + AfterEach { + Assert-MockCalled -CommandName Block-SmbShareAccess -Exactly -Times 0 -Scope 'It' + } + + Context 'When an account with full access should be added' { + BeforeAll { + $addSmbShareAccessPermissionParameters = @{ + Name = $mockShareName + # User3 is an already present account. It should not be added. + FullAccess = @('User3', $mockExpectedAccountToBeAdded) + Verbose = $true + } + } + + It 'Should not throw an error and call the correct mocks' { + { Add-SmbShareAccessPermission @addSmbShareAccessPermissionParameters } | Should -Not -Throw + + <# + Assert that Grant-SmbShareAccess is only called once, and + that only time was with the correct parameters. + #> + Assert-MockCalled -CommandName Grant-SmbShareAccess -Exactly -Times 1 -Scope 'It' + Assert-MockCalled -CommandName Grant-SmbShareAccess -ParameterFilter { + $Name -eq $mockShareName ` + -and $AccessRight -eq 'Full' ` + -and $AccountName -eq $mockExpectedAccountToBeAdded + } -Exactly -Times 1 -Scope 'It' + } + } + + Context 'When an account with change access should be added' { + BeforeAll { + $addSmbShareAccessPermissionParameters = @{ + Name = $mockShareName + # User1 is an already present account. It should not be added. + ChangeAccess = @('User1', $mockExpectedAccountToBeAdded) + Verbose = $true + } + } + + It 'Should not throw an error and call the correct mocks' { + { Add-SmbShareAccessPermission @addSmbShareAccessPermissionParameters } | Should -Not -Throw + + <# + Assert that Grant-SmbShareAccess is only called once, and + that only time was with the correct parameters. + #> + Assert-MockCalled -CommandName Grant-SmbShareAccess -Exactly -Times 1 -Scope 'It' + Assert-MockCalled -CommandName Grant-SmbShareAccess -ParameterFilter { + $Name -eq $mockShareName ` + -and $AccessRight -eq 'Change' ` + -and $AccountName -eq $mockExpectedAccountToBeAdded + } -Exactly -Times 1 -Scope 'It' + } + } + + Context 'When an account with read access should be added' { + BeforeAll { + $addSmbShareAccessPermissionParameters = @{ + Name = $mockShareName + # User2 is an already present account. It should not be added. + ReadAccess = @('User2', $mockExpectedAccountToBeAdded) + Verbose = $true + } + } + + It 'Should not throw an error and call the correct mocks' { + { Add-SmbShareAccessPermission @addSmbShareAccessPermissionParameters } | Should -Not -Throw + + <# + Assert that Grant-SmbShareAccess is only called once, and + that only time was with the correct parameters. + #> + Assert-MockCalled -CommandName Grant-SmbShareAccess -Exactly -Times 1 -Scope 'It' + Assert-MockCalled -CommandName Grant-SmbShareAccess -ParameterFilter { + $Name -eq $mockShareName ` + -and $AccessRight -eq 'Read' ` + -and $AccountName -eq $mockExpectedAccountToBeAdded + } -Exactly -Times 1 -Scope 'It' + } + } + + Context 'When accounts with different access should be added' { + BeforeAll { + $mockExpectedAccountToBeAdded1 = 'NweUser1' + $mockExpectedAccountToBeAdded2 = 'NweUser2' + $mockExpectedAccountToBeAdded3 = 'NweUser3' + + $addSmbShareAccessPermissionParameters = @{ + Name = $mockShareName + # User1, User2, and User3 is an already present account. It should not be added. + FullAccess = @('User3', $mockExpectedAccountToBeAdded1) + ReadAccess = @('User2', $mockExpectedAccountToBeAdded2) + ChangeAccess = @('User1', $mockExpectedAccountToBeAdded3) + Verbose = $true + } + } + + It 'Should not throw an error and call the correct mocks' { + { Add-SmbShareAccessPermission @addSmbShareAccessPermissionParameters } | Should -Not -Throw + + <# + Assert that Grant-SmbShareAccess is only called once, and + that only time was with the correct parameters. + #> + Assert-MockCalled -CommandName Grant-SmbShareAccess -Exactly -Times 3 -Scope 'It' + Assert-MockCalled -CommandName Grant-SmbShareAccess -ParameterFilter { + $Name -eq $mockShareName ` + -and $AccessRight -eq 'Change' ` + -and $AccountName -eq $mockExpectedAccountToBeAdded3 + } -Exactly -Times 1 -Scope 'It' + + Assert-MockCalled -CommandName Grant-SmbShareAccess -ParameterFilter { + $Name -eq $mockShareName ` + -and $AccessRight -eq 'Full' ` + -and $AccountName -eq $mockExpectedAccountToBeAdded1 + } -Exactly -Times 1 -Scope 'It' + + Assert-MockCalled -CommandName Grant-SmbShareAccess -ParameterFilter { + $Name -eq $mockShareName ` + -and $AccessRight -eq 'Read' ` + -and $AccountName -eq $mockExpectedAccountToBeAdded2 + } -Exactly -Times 1 -Scope 'It' + } + } + } + + Context 'When denying permissions on an SMB share' { + AfterEach { + Assert-MockCalled -CommandName Grant-SmbShareAccess -Exactly -Times 0 -Scope 'It' + } + + Context 'When an account with denied access should be revoked' { + BeforeAll { + $mockExpectedAccountToBeBlocked = 'NewDeniedUser' + + $removeSmbShareAccessPermissionParameters = @{ + Name = $mockShareName + NoAccess = @('DeniedUser1', $mockExpectedAccountToBeBlocked) + Verbose = $true + } + } + + It 'Should not throw an error and call the correct mocks' { + { Add-SmbShareAccessPermission @removeSmbShareAccessPermissionParameters } | Should -Not -Throw + + <# + Assert that Block-SmbShareAccess is only called once, and + that only time was with the correct parameters. + #> + Assert-MockCalled -CommandName Block-SmbShareAccess -Exactly -Times 1 -Scope 'It' + Assert-MockCalled -CommandName Block-SmbShareAccess -ParameterFilter { + $Name -eq $mockShareName ` + -and $AccountName -eq $mockExpectedAccountToBeBlocked + } -Exactly -Times 1 -Scope 'It' + } + } + } + } + + Describe 'MSFT_SmbShare\Remove-SmbShareAccessPermission' -Tag 'Helper' { + BeforeAll { + Mock -CommandName Revoke-SmbShareAccess + Mock -CommandName Unblock-SmbShareAccess + + Mock -CommandName Get-SmbShareAccess -MockWith { + <# + Mocked permission: + + Full = @('User3', 'User4') + Change = @('User1') + Read = @('User2') + Denied = @('DeniedUser1') + #> + return $mockSmbShareAccess + } + } + + Context 'When revoking granted permissions from an SMB share' { + AfterEach { + Assert-MockCalled -CommandName Unblock-SmbShareAccess -Exactly -Times 0 -Scope 'It' + } + + Context 'When an account with full access should be removed' { + BeforeAll { + $mockExpectedAccountToBeRemoved = 'User4' + + $removeSmbShareAccessPermissionParameters = @{ + Name = $mockShareName + FullAccess = @('User3') + Verbose = $true + } + } + + It 'Should not throw an error and call the correct mocks' { + { Remove-SmbShareAccessPermission @removeSmbShareAccessPermissionParameters } | Should -Not -Throw + + <# + Assert that Revoke-SmbShareAccess is only called once, and + that only time was with the correct parameters. + #> + Assert-MockCalled -CommandName Revoke-SmbShareAccess -Exactly -Times 1 -Scope 'It' + Assert-MockCalled -CommandName Revoke-SmbShareAccess -ParameterFilter { + $Name -eq $mockShareName ` + -and $AccountName -eq $mockExpectedAccountToBeRemoved + } -Exactly -Times 1 -Scope 'It' + } + } + + Context 'When an all accounts with full access should be removed' { + BeforeAll { + $mockExpectedAccountToBeRemoved1 = 'User3' + $mockExpectedAccountToBeRemoved2 = 'User4' + + $removeSmbShareAccessPermissionParameters = @{ + Name = $mockShareName + FullAccess = @() + Verbose = $true + } + } + + It 'Should not throw an error and call the correct mocks' { + { Remove-SmbShareAccessPermission @removeSmbShareAccessPermissionParameters } | Should -Not -Throw + + <# + Assert that Revoke-SmbShareAccess is called twice, and + that both times it is called with the correct parameters. + #> + Assert-MockCalled -CommandName Revoke-SmbShareAccess -Exactly -Times 2 -Scope 'It' + Assert-MockCalled -CommandName Revoke-SmbShareAccess -ParameterFilter { + $Name -eq $mockShareName ` + -and $AccountName -eq $mockExpectedAccountToBeRemoved1 + } -Exactly -Times 1 -Scope 'It' + + Assert-MockCalled -CommandName Revoke-SmbShareAccess -ParameterFilter { + $Name -eq $mockShareName ` + -and $AccountName -eq $mockExpectedAccountToBeRemoved2 + } -Exactly -Times 1 -Scope 'It' + } + } + + Context 'When an account with change access should be removed' { + BeforeAll { + $mockExpectedAccountToBeRemoved = 'User1' + + $removeSmbShareAccessPermissionParameters = @{ + Name = $mockShareName + ChangeAccess = @() + Verbose = $true + } + } + + It 'Should not throw an error and call the correct mocks' { + { Remove-SmbShareAccessPermission @removeSmbShareAccessPermissionParameters } | Should -Not -Throw + + <# + Assert that Revoke-SmbShareAccess is only called once, and + that only time was with the correct parameters. + #> + Assert-MockCalled -CommandName Revoke-SmbShareAccess -Exactly -Times 1 -Scope 'It' + Assert-MockCalled -CommandName Revoke-SmbShareAccess -ParameterFilter { + $Name -eq $mockShareName ` + -and $AccountName -eq $mockExpectedAccountToBeRemoved + } -Exactly -Times 1 -Scope 'It' + } + } + + Context 'When an account with read access should be removed' { + BeforeAll { + $mockExpectedAccountToBeRemoved = 'User2' + + $removeSmbShareAccessPermissionParameters = @{ + Name = $mockShareName + ReadAccess = @() + Verbose = $true + } + } + + It 'Should not throw an error and call the correct mocks' { + { Remove-SmbShareAccessPermission @removeSmbShareAccessPermissionParameters } | Should -Not -Throw + + <# + Assert that Revoke-SmbShareAccess is only called for each account, + and each time with the correct parameters. + #> + Assert-MockCalled -CommandName Revoke-SmbShareAccess -Exactly -Times 1 -Scope 'It' + Assert-MockCalled -CommandName Revoke-SmbShareAccess -ParameterFilter { + $Name -eq $mockShareName ` + -and $AccountName -eq $mockExpectedAccountToBeRemoved + } -Exactly -Times 1 -Scope 'It' + + + } + } + + Context 'When an all granted access should be removed' { + BeforeAll { + $removeSmbShareAccessPermissionParameters = @{ + Name = $mockShareName + FullAccess = @() + ChangeAccess = @() + ReadAccess = @() + Verbose = $true + } + } + + It 'Should not throw an error and call the correct mocks' { + { Remove-SmbShareAccessPermission @removeSmbShareAccessPermissionParameters } | Should -Not -Throw + + Assert-MockCalled -CommandName Revoke-SmbShareAccess -Exactly -Times 4 -Scope 'It' + Assert-MockCalled -CommandName Revoke-SmbShareAccess -ParameterFilter { + $Name -eq $mockShareName ` + -and $AccountName -eq 'User1' + } -Exactly -Times 1 -Scope 'It' + + Assert-MockCalled -CommandName Revoke-SmbShareAccess -ParameterFilter { + $Name -eq $mockShareName ` + -and $AccountName -eq 'User2' + } -Exactly -Times 1 -Scope 'It' + + Assert-MockCalled -CommandName Revoke-SmbShareAccess -ParameterFilter { + $Name -eq $mockShareName ` + -and $AccountName -eq 'User3' + } -Exactly -Times 1 -Scope 'It' + + Assert-MockCalled -CommandName Revoke-SmbShareAccess -ParameterFilter { + $Name -eq $mockShareName ` + -and $AccountName -eq 'User4' + } -Exactly -Times 1 -Scope 'It' + } + } + } + + Context 'When revoking denied permissions from an SMB share' { + AfterEach { + Assert-MockCalled -CommandName Revoke-SmbShareAccess -Exactly -Times 0 -Scope 'It' + } + + Context 'When an account with denied access should be revoked' { + BeforeAll { + $mockExpectedAccountToBeUnblocked = 'DeniedUser1' + + $removeSmbShareAccessPermissionParameters = @{ + Name = $mockShareName + NoAccess = @() + Verbose = $true + } + } + + It 'Should not throw an error and call the correct mocks' { + { Remove-SmbShareAccessPermission @removeSmbShareAccessPermissionParameters } | Should -Not -Throw + + <# + Assert that Block-SmbShareAccess is only called once, and + that only time was with the correct parameters. + #> + Assert-MockCalled -CommandName Unblock-SmbShareAccess -Exactly -Times 1 -Scope 'It' + Assert-MockCalled -CommandName Unblock-SmbShareAccess -ParameterFilter { + $Name -eq $mockShareName ` + -and $AccountName -eq $mockExpectedAccountToBeUnblocked + } -Exactly -Times 1 -Scope 'It' + } + } + } + } + + Describe 'MSFT_SmbShare\Assert-AccessPermissionParameters' -Tag 'Helper' { + Context 'When asserting correct provided access permissions parameters' { + Context 'When providing at least one member in one of the access permission collections' { + BeforeAll { + $testCases = @( + @{ + TestCase = 'FullAccess' + FullAccess = @('Member1') + ChangeAccess = @() + ReadAccess = @() + NoAccess = @() + }, + @{ + TestCase = 'ChangeAccess' + FullAccess = @() + ChangeAccess = @('Member1') + ReadAccess = @() + NoAccess = @() + }, + @{ + TestCase = 'ReadAccess' + FullAccess = @() + ChangeAccess = @() + ReadAccess = @('Member1') + NoAccess = @() + }, + @{ + TestCase = 'NoAccess' + FullAccess = @() + ChangeAccess = @() + ReadAccess = @('Member1') + NoAccess = @() + } + ) + } + + It 'Should not throw an error when testing a member in ' -TestCases $testCases { + param + ( + $FullAccess, + $ChangeAccess, + $ReadAccess, + $NoAccess + ) + + # We must using splatting to test 'ValueFromRemainingArguments' parameter. + $assertAccessPermissionParameters = @{ + FullAccess = $FullAccess + ChangeAccess = $ChangeAccess + ReadAccess = $ReadAccess + NoAccess = $NoAccess + DummyParameter = 'Testing ValueFromRemainingArguments' + } + + { + Assert-AccessPermissionParameters @assertAccessPermissionParameters + } | Should -Not -Throw + } + } + + Context 'When not providing any members in any of the access permission collections' { + It 'Should throw the correct error' { + # We must using splatting to test 'ValueFromRemainingArguments' parameter. + $assertAccessPermissionParameters = @{ + FullAccess = @() + ChangeAccess = @() + ReadAccess = @() + NoAccess = @() + DummyParameter = 'Testing ValueFromRemainingArguments' + } + + { + Assert-AccessPermissionParameters @assertAccessPermissionParameters + } | Should -Throw $script:localizedData.WrongAccessParameters + } + } + } + } + } +} +finally +{ + Invoke-TestCleanup +}