From 9b335c1e75cd15374172955881c7a570eb2b9b4f Mon Sep 17 00:00:00 2001 From: Mariah Breakey Date: Thu, 15 Dec 2016 16:04:17 -0800 Subject: [PATCH] adding in all group changes --- DscResources/CommonResourceHelper.psm1 | 6 +- .../MSFT_GroupResource.psm1 | 1 + .../en-US/MSFT_GroupResource.schema.mfl | Bin 1690 -> 1692 bytes Examples/Sample_Group_Members.ps1 | 36 - .../Sample_Group_MembersToIncludeExclude.ps1 | 44 - Examples/Sample_Group_RemoveMembers.ps1 | 21 + Examples/Sample_Group_SetMembers.ps1 | 23 + README.md | 12 +- .../MSFT_GroupResource.Integration.Tests.ps1 | 109 ++- Tests/TestHelpers/CommonTestHelper.psm1 | 406 --------- .../MSFT_GroupResource.TestHelper.psm1 | 783 +++++++++++++----- Tests/Unit/MSFT_GroupResource.Tests.ps1 | 10 +- 12 files changed, 691 insertions(+), 760 deletions(-) delete mode 100644 Examples/Sample_Group_Members.ps1 delete mode 100644 Examples/Sample_Group_MembersToIncludeExclude.ps1 create mode 100644 Examples/Sample_Group_RemoveMembers.ps1 create mode 100644 Examples/Sample_Group_SetMembers.ps1 diff --git a/DscResources/CommonResourceHelper.psm1 b/DscResources/CommonResourceHelper.psm1 index cc433f6..e2e5835 100644 --- a/DscResources/CommonResourceHelper.psm1 +++ b/DscResources/CommonResourceHelper.psm1 @@ -105,9 +105,9 @@ function New-InvalidOperationException .PARAMETER ResourceName The name of the resource as it appears before '.strings.psd1' of the localized string file. For example: - WindowsOptionalFeature: MSFT_WindowsOptionalFeature - Service: MSFT_ServiceResource - Registry: MSFT_RegistryResource + For WindowsOptionalFeature: MSFT_WindowsOptionalFeature + For Service: MSFT_ServiceResource + For Registry: MSFT_RegistryResource #> function Get-LocalizedData { diff --git a/DscResources/MSFT_GroupResource/MSFT_GroupResource.psm1 b/DscResources/MSFT_GroupResource/MSFT_GroupResource.psm1 index 463e0c5..e4e0081 100644 --- a/DscResources/MSFT_GroupResource/MSFT_GroupResource.psm1 +++ b/DscResources/MSFT_GroupResource/MSFT_GroupResource.psm1 @@ -60,6 +60,7 @@ with the associated SID of the member that cannot be resolved then continues the operation. #> +$errorActionPreference = 'Stop' Set-StrictMode -Version 'Latest' Import-Module -Name (Join-Path -Path (Split-Path -Path $PSScriptRoot -Parent) ` diff --git a/DscResources/MSFT_GroupResource/en-US/MSFT_GroupResource.schema.mfl b/DscResources/MSFT_GroupResource/en-US/MSFT_GroupResource.schema.mfl index 6122b8d8959adeed4659c7dda7c28072572f750a..42c2d71e4b558a53bc55e2d0db2cc4d11c032953 100644 GIT binary patch delta 18 acmbQmJBN2e8zZB_ -Configuration Sample_Group -{ - param - ( - [Parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [String] - $GroupName, - - [String[]] - $Members = @() - ) - - Import-DscResource -ModuleName 'PSDscResources' - - Group Group1 - { - GroupName = $GroupName - Ensure = 'Present' - Members = $Members - } -} - -Sample_Group diff --git a/Examples/Sample_Group_MembersToIncludeExclude.ps1 b/Examples/Sample_Group_MembersToIncludeExclude.ps1 deleted file mode 100644 index f21e981..0000000 --- a/Examples/Sample_Group_MembersToIncludeExclude.ps1 +++ /dev/null @@ -1,44 +0,0 @@ -<# - .SYNOPSIS - Creates a group with the specified name and members included or modifies the members of a group if - the named group already exists. - - .PARAMETER GroupName - The name of the group to create or modify. - - .PARAMETER MembersToInclude - The list of members the group should contain. - The default value is an empty list. - - .PARAMETER MembersToExclude - The list of members the group should not contain. - The default value is an empty list. -#> -Configuration Sample_Group -{ - param - ( - [Parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [String] - $GroupName, - - [String[]] - $MembersToInclude = @(), - - [String[]] - $MembersToExclude = @() - ) - - Import-DscResource -ModuleName 'PSDscResources' - - Group Group1 - { - GroupName = $GroupName - Ensure = 'Present' - MembersToInclude = $MembersToInclude - MembersToExclude = $MembersToExclude - } -} - -Sample_Group diff --git a/Examples/Sample_Group_RemoveMembers.ps1 b/Examples/Sample_Group_RemoveMembers.ps1 new file mode 100644 index 0000000..f75beee --- /dev/null +++ b/Examples/Sample_Group_RemoveMembers.ps1 @@ -0,0 +1,21 @@ +<# + .SYNOPSIS + If the group named GroupName1 does not exist, creates a group named GroupName1. + + If the group named GroupName1 already exists removes the users that have the usernames + Username1 or Username2 from the group. +#> +Configuration Sample_Group_RemoveMembers +{ + [CmdletBinding()] + param () + + Import-DscResource -ModuleName 'PSDscResources' + + Group Group1 + { + GroupName = 'GroupName1' + Ensure = 'Present' + MembersToExclude = @( 'Username1', 'Username2' ) + } +} diff --git a/Examples/Sample_Group_SetMembers.ps1 b/Examples/Sample_Group_SetMembers.ps1 new file mode 100644 index 0000000..a050a3b --- /dev/null +++ b/Examples/Sample_Group_SetMembers.ps1 @@ -0,0 +1,23 @@ +<# + .SYNOPSIS + If the group named GroupName1 does not exist, creates a group named GroupName1 and adds the + users with the usernames Username1 and Username2 to the group. + + If the group named GroupName1 already exists, removes any users that do not have the + usernames Username1 or Username2 from the group and adds the users that have the usernames + Username1 and Username2 to the group. +#> +Configuration Sample_Group_SetMembers +{ + [CmdletBinding()] + param () + + Import-DscResource -ModuleName 'PSDscResources' + + Group Group1 + { + GroupName = 'GroupName1' + Ensure = 'Present' + Members = @( 'Username1', 'Username2' ) + } +} diff --git a/README.md b/README.md index 082533e..59ce68b 100644 --- a/README.md +++ b/README.md @@ -267,15 +267,17 @@ None * Updated unit tests. * Added WindowsProcess * CommonTestHelper: - * Added Get-AppVeyorAdministratorCredential - * Added Set-StrictMode -'Latest' and $errorActionPreference -'Stop' + * Added Get-AppVeyorAdministratorCredential. + * Added Set-StrictMode -'Latest' and $errorActionPreference -'Stop'. * Service: - * Updated resource module, tests, and examples to reflect the changes made in xPSDesiredStateConfiguration + * Updated resource module, unit tests, integration tests, and examples to reflect the changes made in xPSDesiredStateConfiguration. +* Group: + * Updated resource module, examples, and integration tests to reflect the changes made in xPSDesiredStateConfiguration. ### 2.1.0.0 -* Added WindowsFeature +* Added WindowsFeature. ### 2.0.0.0 -* Initial release of PSDscResources +* Initial release of PSDscResources. diff --git a/Tests/Integration/MSFT_GroupResource.Integration.Tests.ps1 b/Tests/Integration/MSFT_GroupResource.Integration.Tests.ps1 index bac6c2b..86d7b8e 100644 --- a/Tests/Integration/MSFT_GroupResource.Integration.Tests.ps1 +++ b/Tests/Integration/MSFT_GroupResource.Integration.Tests.ps1 @@ -1,6 +1,9 @@ [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidUsingConvertToSecureStringWithPlainText', '')] param () +$errorActionPreference = 'Stop' +Set-StrictMode -Version 'Latest' + if ($PSVersionTable.PSVersion.Major -lt 5 -or $PSVersionTable.PSVersion.Minor -lt 1) { Write-Warning -Message 'Cannot run PSDscResources integration tests on PowerShell versions lower than 5.1' @@ -28,6 +31,27 @@ try -ChildPath 'MSFT_GroupResource_Members.config.ps1' $script:confgurationWithMembersToIncludeExcludeFilePath = Join-Path -Path $PSScriptRoot ` -ChildPath 'MSFT_GroupResource_MembersToIncludeExclude.config.ps1' + # Fake users for testing + $testUsername1 = 'TestUser1' + $testUsername2 = 'TestUser2' + + $testUsernames = @( $testUsername1, $testUsername2 ) + + $testPassword = 'T3stPassw0rd#' + $secureTestPassword = ConvertTo-SecureString -String $testPassword -AsPlainText -Force + + foreach ($username in $testUsernames) + { + $testUserCredential = New-Object -TypeName 'System.Management.Automation.PSCredential' -ArgumentList @( $username, $secureTestPassword ) + $null = New-User -Credential $testUserCredential + } + } + + AfterAll { + foreach ($username in $testUsernames) + { + Remove-User -UserName $username + } } It 'Should use Group from PSDscResources' { @@ -63,7 +87,7 @@ try Start-DscConfiguration -Path $TestDrive -ErrorAction 'Stop' -Wait -Force } | Should Not Throw - Test-GroupExists -GroupName $testGroupName | Should Be $true + Test-GroupExists -GroupName $testGroupName -Members @() | Should Be $true } finally { @@ -78,22 +102,12 @@ try $configurationName = 'CreateGroupWithTwoMembers' $testGroupName = 'TestGroupWithMembers2' - $username1 = 'TestUser1' - $username2 = 'TestUser2' - - $testPassword = 'T3stPassw0rd#' - $secureTestPassword = ConvertTo-SecureString -String $testPassword -AsPlainText -Force - - $testUserCredential1 = New-Object -TypeName 'System.Management.Automation.PSCredential' -ArgumentList @( $username1, $secureTestPassword ) - $testUserCredential2 = New-Object -TypeName 'System.Management.Automation.PSCredential' -ArgumentList @( $username2, $secureTestPassword ) - - $user1 = New-User -Credential $testUserCredential1 - $user2 = New-User -Credential $testUserCredential2 + $groupMembers = $testUsernames $resourceParameters = @{ Ensure = 'Present' GroupName = $testGroupName - Members = @( $username1, $username2 ) + Members = $groupMembers } Test-GroupExists -GroupName $testGroupName | Should Be $false @@ -106,7 +120,7 @@ try Start-DscConfiguration -Path $TestDrive -ErrorAction 'Stop' -Wait -Force } | Should Not Throw - Test-GroupExists -GroupName $testGroupName | Should Be $true + Test-GroupExists -GroupName $testGroupName -Members $groupMembers | Should Be $true } finally { @@ -114,9 +128,6 @@ try { Remove-Group -GroupName $testGroupName } - - Remove-User -UserName $username1 - Remove-User -UserName $username2 } } @@ -124,19 +135,12 @@ try $configurationName = 'CreateGroupWithTwoMembers' $testGroupName = 'TestGroupWithMembersToInclude3' - $username1 = 'TestUser1' - - $testPassword = 'T3stPassw0rd#' - $secureTestPassword = ConvertTo-SecureString -String $testPassword -AsPlainText -Force - - $testUserCredential1 = New-Object -TypeName 'System.Management.Automation.PSCredential' -ArgumentList @( $username1, $secureTestPassword ) - - $user1 = New-User -Credential $testUserCredential1 + $groupMembers = @( $testUsername1 ) $resourceParameters = @{ Ensure = 'Present' GroupName = $testGroupName - MembersToInclude = @( $username1 ) + MembersToInclude = $groupMembers } Test-GroupExists -GroupName $testGroupName | Should Be $false @@ -153,7 +157,7 @@ try Start-DscConfiguration -Path $TestDrive -ErrorAction 'Stop' -Wait -Force } | Should Not Throw - Test-GroupExists -GroupName $testGroupName | Should Be $true + Test-GroupExists -GroupName $testGroupName -MembersToInclude $groupMembers | Should Be $true } finally { @@ -161,8 +165,6 @@ try { Remove-Group -GroupName $testGroupName } - - Remove-User -UserName $username1 } } @@ -170,24 +172,17 @@ try $configurationName = 'CreateGroupWithTwoMembers' $testGroupName = 'TestGroupWithMembersToInclude3' - $username1 = 'TestUser1' - - $testPassword = 'T3stPassw0rd#' - $secureTestPassword = ConvertTo-SecureString -String $testPassword -AsPlainText -Force - - $testUserCredential1 = New-Object -TypeName 'System.Management.Automation.PSCredential' -ArgumentList @( $username1, $secureTestPassword ) - - $user1 = New-User -Credential $testUserCredential1 + $groupMembersToExclude = @( $testUsername1 ) $resourceParameters = @{ Ensure = 'Present' GroupName = $testGroupName - MembersToExclude = @( $username1 ) + MembersToExclude = $groupMembersToExclude } Test-GroupExists -GroupName $testGroupName | Should Be $false - New-Group -GroupName $testGroupName -MemberUserNames @( $username1 ) + New-Group -GroupName $testGroupName -Members $groupMembersToExclude Test-GroupExists -GroupName $testGroupName | Should Be $true @@ -199,7 +194,7 @@ try Start-DscConfiguration -Path $TestDrive -ErrorAction 'Stop' -Wait -Force } | Should Not Throw - Test-GroupExists -GroupName $testGroupName | Should Be $true + Test-GroupExists -GroupName $testGroupName -MembersToExclude $groupMembersToExclude | Should Be $true } finally { @@ -207,8 +202,40 @@ try { Remove-Group -GroupName $testGroupName } + } + } - Remove-User -UserName $username1 + It 'Should remove a group' { + $configurationName = 'RemoveGroup' + $testGroupName = 'TestRemoveGroup1' + + $resourceParameters = @{ + Ensure = 'Absent' + GroupName = $testGroupName + } + + Test-GroupExists -GroupName $testGroupName | Should Be $false + + New-Group -GroupName $testGroupName + + Test-GroupExists -GroupName $testGroupName | Should Be $true + + try + { + { + . $script:confgurationWithMembersFilePath -ConfigurationName $configurationName + & $configurationName -OutputPath $TestDrive @resourceParameters + Start-DscConfiguration -Path $TestDrive -ErrorAction 'Stop' -Wait -Force + } | Should Not Throw + + Test-GroupExists -GroupName $testGroupName | Should Be $false + } + finally + { + if (Test-GroupExists -GroupName $testGroupName) + { + Remove-Group -GroupName $testGroupName + } } } } diff --git a/Tests/TestHelpers/CommonTestHelper.psm1 b/Tests/TestHelpers/CommonTestHelper.psm1 index 4ba611b..7cf6d24 100644 --- a/Tests/TestHelpers/CommonTestHelper.psm1 +++ b/Tests/TestHelpers/CommonTestHelper.psm1 @@ -106,409 +106,6 @@ function Test-IsLocalMachine return $false } -<# - .SYNOPSIS - Creates a user account. - - .DESCRIPTION - This function creates a user on the local or remote machine. - - .PARAMETER Credential - The credential containing the username and password to use to create the account. - - .PARAMETER Description - The optional description to set on the user account. - - .PARAMETER ComputerName - The optional name of the computer to update. Omit to create a user on the local machine. - - .NOTES - For remote machines, the currently logged on user must have rights to create a user. -#> -function New-User -{ - [CmdletBinding()] - param - ( - [Parameter(Mandatory = $true)] - [System.Management.Automation.PSCredential] - [System.Management.Automation.Credential()] - $Credential, - - [String] - $Description, - - [String] - $ComputerName = $env:COMPUTERNAME - ) - - if (Test-IsNanoServer) - { - New-UserOnNanoServer @PSBoundParameters - } - else - { - New-UserOnFullSKU @PSBoundParameters - } -} - -<# - .SYNOPSIS - Creates a user account on a full server. - - .DESCRIPTION - This function creates a user on the local or remote machine running a full server. - - .PARAMETER Credential - The credential containing the username and password to use to create the account. - - .PARAMETER Description - The optional description to set on the user account. - - .PARAMETER ComputerName - The optional name of the computer to update. Omit to create a user on the local machine. - - .NOTES - For remote machines, the currently logged on user must have rights to create a user. -#> -function New-UserOnFullSKU -{ - [CmdletBinding()] - param - ( - [Parameter(Mandatory = $true)] - [System.Management.Automation.PSCredential] - [System.Management.Automation.Credential()] - $Credential, - - [String] - $Description, - - [String] - $ComputerName = $env:COMPUTERNAME - ) - - Set-StrictMode -Version Latest - - $userName = $Credential.UserName - $password = $Credential.GetNetworkCredential().Password - - # Remove user if it already exists. - Remove-User $userName $ComputerName - - $adComputerEntry = [ADSI] "WinNT://$ComputerName" - $adUserEntry = $adComputerEntry.Create('User', $userName) - $null = $adUserEntry.SetPassword($password) - - if ($PSBoundParameters.ContainsKey('Description')) - { - $null = $adUserEntry.Put('Description', $Description) - } - - $null = $adUserEntry.SetInfo() -} - -<# - .SYNOPSIS - Creates a user account on a Nano server. - - .DESCRIPTION - This function creates a user on the local machine running a Nano server. - - .PARAMETER Credential - The credential containing the username and password to use to create the account. - - .PARAMETER Description - The optional description to set on the user account. - - .PARAMETER ComputerName - This parameter should not be used on NanoServer. -#> -function New-UserOnNanoServer -{ - [CmdletBinding()] - param - ( - [Parameter(Mandatory = $true)] - [System.Management.Automation.PSCredential] - [System.Management.Automation.Credential()] - $Credential, - - [String] - $Description, - - [String] - $ComputerName = $env:COMPUTERNAME - ) - - Set-StrictMode -Version Latest - - if ($PSBoundParameters.ContainsKey('ComputerName')) - { - if (-not (Test-IsLocalMachine -Scope $ComputerName)) - { - throw 'Do not specify the ComputerName arguments when running on NanoServer unless it is a local machine.' - } - } - - $userName = $Credential.UserName - $securePassword = $Credential.GetNetworkCredential().SecurePassword - - # Remove user if it already exists. - Remove-LocalUser -Name $userName -ErrorAction SilentlyContinue - - New-LocalUser -Name $userName -Password $securePassword - - if ($PSBoundParameters.ContainsKey('Description')) - { - Set-LocalUser -Name $userName -Description $Description - } -} - -<# - .SYNOPSIS - Removes a user account. - - .DESCRIPTION - This function removes a local user from the local or remote machine. - - .PARAMETER UserName - The name of the user to remove. - - .PARAMETER ComputerName - The optional name of the computer to update. Omit to remove the user on the local machine. - - .NOTES - For remote machines, the currently logged on user must have rights to remove a user. -#> -function Remove-User -{ - [CmdletBinding()] - param - ( - [Parameter(Mandatory = $true)] - [String] - $UserName, - - [String] - $ComputerName = $env:COMPUTERNAME - ) - - if (Test-IsNanoServer) - { - Remove-UserOnNanoServer @PSBoundParameters - } - else - { - Remove-UserOnFullSKU @PSBoundParameters - } -} - -<# - .SYNOPSIS - Removes a user account on a full server. - - .DESCRIPTION - This function removes a local user from the local or remote machine running a full server. - - .PARAMETER UserName - The name of the user to remove. - - .PARAMETER ComputerName - The optional name of the computer to update. Omit to remove the user on the local machine. - - .NOTES - For remote machines, the currently logged on user must have rights to remove a user. -#> -function Remove-UserOnFullSKU -{ - [CmdletBinding()] - param - ( - [Parameter(Mandatory = $true)] - [String] - $UserName, - - [String] - $ComputerName = $env:COMPUTERNAME - ) - - Set-StrictMode -Version Latest - - $adComputerEntry = [ADSI] "WinNT://$ComputerName" - - if ($adComputerEntry.Children | Where-Object Path -like "WinNT://*$ComputerName/$UserName") - { - $null = $adComputerEntry.Delete('user', $UserName) - } -} - -<# - .SYNOPSIS - Removes a local user account on a Nano server. - - .DESCRIPTION - This function removes a local user from the local machine running a Nano Server. - - .PARAMETER UserName - The name of the user to remove. - - .PARAMETER ComputerName - This parameter should not be used on NanoServer. -#> -function Remove-UserOnNanoServer -{ - [CmdletBinding()] - param - ( - [Parameter(Mandatory = $true)] - [String] - $UserName, - - [String] - $ComputerName = $env:COMPUTERNAME - ) - - Set-StrictMode -Version Latest - - if ($PSBoundParameters.ContainsKey('ComputerName')) - { - if (-not (Test-IsLocalMachine -Scope $ComputerName)) - { - throw 'Do not specify the ComputerName arguments when running on NanoServer unless it is a local machine.' - } - } - - Remove-LocalUser -Name $UserName -} - -<# - .SYNOPSIS - Determines if a user exists. - - .DESCRIPTION - This function determines if a user exists on a local or remote machine. - - .PARAMETER UserName - The name of the user to test. - - .PARAMETER ComputerName - The optional name of the computer to update. -#> -function Test-User -{ - [CmdletBinding()] - param - ( - [Parameter(Mandatory = $true)] - [String] - $UserName, - - [String] - $ComputerName = $env:COMPUTERNAME - ) - - if (Test-IsNanoServer) - { - Test-UserOnNanoServer @PSBoundParameters - } - else - { - Test-UserOnFullSKU @PSBoundParameters - } -} - -<# - .SYNOPSIS - Determines if a user exists on a full server. - - .DESCRIPTION - This function determines if a user exists on a local or remote machine running a full server. - - .PARAMETER UserName - The name of the user to test. - - .PARAMETER ComputerName - The optional name of the computer to update. -#> -function Test-UserOnFullSKU -{ - [CmdletBinding()] - param - ( - [Parameter(Mandatory = $true)] - [String] - $UserName, - - [String] - $ComputerName = $env:COMPUTERNAME - ) - - Set-StrictMode -Version Latest - - $adComputerEntry = [ADSI] "WinNT://$ComputerName" - - if ($adComputerEntry.Children | Where-Object Path -like "WinNT://*$ComputerName/$UserName") - { - return $true - } - - return $false -} - -<# - .SYNOPSIS - Determines if a user exists on a Nano server. - - .DESCRIPTION - This function determines if a user exists on a local or remote machine running a Nano server. - - .PARAMETER UserName - The name of the user to test. - - .PARAMETER ComputerName - This parameter should not be used on NanoServer. -#> -function Test-UserOnNanoServer -{ - [CmdletBinding()] - param - ( - [Parameter(Mandatory = $true)] - [String] - $UserName, - - [String] - $ComputerName = $env:COMPUTERNAME - ) - - if ($PSBoundParameters.ContainsKey('ComputerName')) - { - if (-not (Test-IsLocalMachine -Scope $ComputerName)) - { - throw 'Do not specify the ComputerName arguments when running on NanoServer unless it is a local machine.' - } - } - - # Try to find a user by name. - try - { - $null = Get-LocalUser -Name $UserName -ErrorAction Stop - return $true - } - catch [System.Exception] - { - if ($_.CategoryInfo.ToString().Contains('UserNotFoundException')) - { - # A user with the provided name does not exist. - return $false - } - throw $_.Exception - } - - return $false -} - <# .SYNOPSIS Waits a certain amount of time for a script block to return true. @@ -815,9 +412,6 @@ function Exit-DscResourceTestEnvironment Export-ModuleMember -Function @( 'Test-GetTargetResourceResult', ` - 'New-User', ` - 'Remove-User', ` - 'Test-User', ` 'Wait-ScriptBlockReturnTrue', ` 'Test-IsFileLocked', ` 'Test-SetTargetResourceWithWhatIf', ` diff --git a/Tests/TestHelpers/MSFT_GroupResource.TestHelper.psm1 b/Tests/TestHelpers/MSFT_GroupResource.TestHelper.psm1 index 95b01e9..4ba1916 100644 --- a/Tests/TestHelpers/MSFT_GroupResource.TestHelper.psm1 +++ b/Tests/TestHelpers/MSFT_GroupResource.TestHelper.psm1 @@ -1,21 +1,28 @@ -Import-Module -Name (Join-Path -Path (Join-Path -Path (Split-Path -Path (Split-Path -Path $PSScriptRoot -Parent) -Parent) -ChildPath 'DSCResources') -ChildPath 'CommonResourceHelper.psm1') + +$errorActionPreference = 'Stop' +Set-StrictMode -Version 'Latest' + +#Import CommonResourceHelper for Test-IsNanoServer +$moduleRootFilePath = Split-Path -Path (Split-Path -Path $PSScriptRoot -Parent) -Parent +$dscResourcesFolderFilePath = Join-Path -Path $moduleRootFilePath -ChildPath 'DSCResources' +$commonResourceHelperFilePath = Join-Path -Path $dscResourcesFolderFilePath -ChildPath 'CommonResourceHelper.psm1' +Import-Module -Name $commonResourceHelperFilePath <# .SYNOPSIS - Determines if a Windows group exists. - - .DESCRIPTION - This function determines if a Windows group exists on a local or remote machine. + Tests if a Windows group with the given name and members exists. .PARAMETER GroupName - The name of the group to test. + The name of the group. - .PARAMETER ComputerName - The optional name of the computer to check. - The default value is the local machine. + .PARAMETER Members + The usernames of the members of the group. - .NOTES - For remote machines, the currently logged on user must have rights to enumerate groups. + .PARAMETER MembersToInclude + The usernames of the members that should be included in the group. + + .PARAMETER MembersToExclude + The usernames of the members that should be excluded from the group. #> function Test-GroupExists { @@ -27,9 +34,17 @@ function Test-GroupExists [String] $GroupName, - [ValidateNotNullOrEmpty()] - [String] - $ComputerName = $env:computerName + [Parameter()] + [String[]] + $Members, + + [Parameter()] + [String[]] + $MembersToInclude, + + [Parameter()] + [String[]] + $MembersToExclude ) if (Test-IsNanoServer) @@ -40,46 +55,23 @@ function Test-GroupExists { return Test-GroupExistsOnFullSKU @PSBoundParameters } - - if (Test-IsNanoServer) - { - # Try to find a group by its name. - try - { - $null = Get-LocalGroup -Name $GroupName -ErrorAction Stop - return $true - } - catch [System.Exception] - { - if ($_.CategoryInfo.ToString().Contains('GroupNotFoundException')) - { - # A group with the provided name does not exist. - return $false - } - throw $_.Exception - } - } - else - { - return [ADSI]::Exists("WinNT://$env:ComputerName/$GroupName,group") - } } <# .SYNOPSIS - Determines if a Windows group exists. - - .DESCRIPTION - This function determines if a Windows group exists on a local or remote machine. + Tests if a Windows group with the given name and members exists. .PARAMETER GroupName - The name of the group to test. + The name of the group. - .PARAMETER ComputerName - The optional name of the computer to check. Omit to check for the group on the local machine. + .PARAMETER Members + The usernames of the members of the group. - .NOTES - For remote machines, the currently logged on user must have rights to enumerate groups. + .PARAMETER MembersToInclude + The usernames of the members that should be included in the group. + + .PARAMETER MembersToExclude + The usernames of the members that should be excluded from the group. #> function Test-GroupExistsOnFullSKU { @@ -92,38 +84,131 @@ function Test-GroupExistsOnFullSKU [String] $GroupName, - [ValidateNotNullOrEmpty()] - [String] - $ComputerName = $env:computerName + [Parameter()] + [String[]] + $Members, + + [Parameter()] + [String[]] + $MembersToInclude, + + [Parameter()] + [String[]] + $MembersToExclude ) - Set-StrictMode -Version 'Latest' + $principalContext = New-Object -TypeName 'System.DirectoryServices.AccountManagement.PrincipalContext' ` + -ArgumentList @( [System.DirectoryServices.AccountManagement.ContextType]::Machine ) - $adsiComputerEntry = [ADSI] "WinNT://$ComputerName" + $group = [System.DirectoryServices.AccountManagement.GroupPrincipal]::FindByIdentity($principalContext, $GroupName) + $groupExists = $null -ne $group - foreach ($adsiComputerEntryChild in $adsiComputerEntry.Children) + if ($groupExists) { - if ($adsiComputerEntryChild.Path -like "WinNT://*$ComputerName/$GroupName") + if ($PSBoundParameters.ContainsKey('Members')) { - return $true + $noActualMembers = ($null -eq $group.Members) -or ($group.Members.Count -eq 0) + $noExpectedMembers = ($null -eq $Members) -or ($Members.Count -eq 0) + + if ($noActualMembers -and $noExpectedMembers) + { + $membersMatch = $true + } + elseif ($noActualMembers -xor $noExpectedMembers) + { + $membersMatch = $false + } + else + { + $membersMatch = $null -eq (Compare-Object -ReferenceObject $Members -DifferenceObject $group.Members.Name) + } + + $groupExists = $groupExists -and $membersMatch + } + + if ($PSBoundParameters.ContainsKey('MembersToInclude')) + { + $noActualMembers = ($null -eq $group.Members) -or ($group.Members.Count -eq 0) + $noExpectedMembers = ($null -eq $MembersToInclude) -or ($MembersToInclude.Count -eq 0) + + if ($noExpectedMembers) + { + $membersToIncludeMatch = $true + } + elseif ($noActualMembers) + { + $membersToIncludeMatch = $false + } + else + { + $membersToIncludeMatch = $true + + foreach ($expectedMemberName in $MembersToInclude) + { + if ($group.Members.Name -inotcontains $expectedMemberName) + { + $membersToIncludeMatch = $false + break + } + } + } + + $groupExists = $groupExists -and $membersToIncludeMatch + } + + if ($PSBoundParameters.ContainsKey('MembersToExclude')) + { + $noActualMembers = ($null -eq $group.Members) -or ($group.Members.Count -eq 0) + $noExcludedMembers = ($null -eq $MembersToExclude) -or ($MembersToExclude.Count -eq 0) + + if ($noExcludedMembers) + { + $membersToExcludeMatch = $true + } + elseif ($noActualMembers) + { + $membersToExcludeMatch = $true + } + else + { + $groupMemberNames = $group.Members.Name | ForEach-Object { ($_ -split '/')[-1] } + + $membersToExcludeMatch = $true + + foreach ($excludedMemberName in $MembersToExclude) + { + if ($group.Members.Name -icontains $excludedMemberName) + { + $membersToExcludeMatch = $false + break + } + } + } + + $groupExists = $groupExists -and $membersToExcludeMatch } } - return $false + $null = $principalContext.Dispose() + + return $groupExists } <# .SYNOPSIS - Determines if a Windows group exists. - - .DESCRIPTION - This function determines if a Windows group exists on a local or remote machine. + Tests if a Windows group with the given name and members exists. .PARAMETER GroupName - The name of the group to test. + The name of the group. - .PARAMETER ComputerName - This parameter should not be used on NanoServer. + .PARAMETER Members + The usernames of the members of the group. + + .PARAMETER MembersToInclude + The usernames of the members that should be included in the group. + + .PARAMETER MembersToExclude + The usernames of the members that should be excluded from the group. #> function Test-GroupExistsOnNanoServer { @@ -136,31 +221,30 @@ function Test-GroupExistsOnNanoServer [String] $GroupName, - [ValidateNotNullOrEmpty()] - [String] - $ComputerName = $env:computerName + [Parameter()] + [String[]] + $Members, + + [Parameter()] + [String[]] + $MembersToInclude, + + [Parameter()] + [String[]] + $MembersToExclude ) - Set-StrictMode -Version 'Latest' - - if ($PSBoundParameters.ContainsKey('ComputerName')) - { - if (-not (Test-IsLocalMachine -Scope $ComputerName)) - { - throw 'Do not specify ComputerName when running on NanoServer unless it is the local machine.' - } - } + $groupExists = $true try { - Get-LocalGroup -Name $GroupName -ErrorAction Stop | Out-Null - return $true + $null = Get-LocalGroup -Name $GroupName } catch [System.Exception] { if ($_.CategoryInfo.ToString().Contains('GroupNotFoundException')) { - return $false + $groupExists = $false } else { @@ -168,30 +252,116 @@ function Test-GroupExistsOnNanoServer } } - return $false + if ($groupExists) + { + if ($PSBoundParameters.ContainsKey('Members')) + { + $groupMembers = Get-LocalGroupMember -Group $Group + + $noActualMembers = ($null -eq $groupMembers) -or ($groupMembers.Count -eq 0) + $noExpectedMembers = ($null -eq $Members) -or ($Members.Count -eq 0) + + if ($noActualMembers -and $noExpectedMembers) + { + $membersMatch = $true + } + elseif ($noActualMembers -xor $noExpectedMembers) + { + $membersMatch = $false + } + else + { + $groupMemberNames = $groupMembers.Name | ForEach-Object { ($_ -split '/')[-1] } + $membersMatch = $null -eq (Compare-Object -ReferenceObject $Members -DifferenceObject $groupMemberNames) + } + + $groupExists = $groupExists -and $membersMatch + } + + if ($PSBoundParameters.ContainsKey('MembersToInclude')) + { + $groupMembers = Get-LocalGroupMember -Group $Group + + $noActualMembers = ($null -eq $groupMembers) -or ($groupMembers.Count -eq 0) + $noExpectedMembers = ($null -eq $MembersToInclude) -or ($MembersToInclude.Count -eq 0) + + if ($noExpectedMembers) + { + $membersToIncludeMatch = $true + } + elseif ($noActualMembers) + { + $membersToIncludeMatch = $false + } + else + { + $groupMemberNames = $groupMembers.Name | ForEach-Object { ($_ -split '/')[-1] } + + $membersToIncludeMatch = $true + + foreach ($expectedMemberName in $MembersToInclude) + { + if ($groupMemberNames -inotcontains $expectedMemberName) + { + $membersToIncludeMatch = $false + break + } + } + } + + $groupExists = $groupExists -and $membersToIncludeMatch + } + + if ($PSBoundParameters.ContainsKey('MembersToExclude')) + { + $groupMembers = Get-LocalGroupMember -Group $Group + + $noActualMembers = ($null -eq $groupMembers) -or ($groupMembers.Count -eq 0) + $noExcludedMembers = ($null -eq $MembersToExclude) -or ($MembersToExclude.Count -eq 0) + + if ($noExcludedMembers) + { + $membersToExcludeMatch = $true + } + elseif ($noActualMembers) + { + $membersToExcludeMatch = $true + } + else + { + $groupMemberNames = $groupMembers.Name | ForEach-Object { ($_ -split '/')[-1] } + + $membersToExcludeMatch = $true + + foreach ($excludedMemberName in $MembersToExclude) + { + if ($groupMemberNames -icontains $excludedMemberName) + { + $membersToExcludeMatch = $false + break + } + } + } + + $groupExists = $groupExists -and $membersToExcludeMatch + } + } + + return $groupExists } <# .SYNOPSIS - Creates a Windows group - - .DESCRIPTION - This function creates a Windows group on the local or remote machine. + Creates a Windows group. .PARAMETER GroupName - The name of the group to create + The name of the group. .PARAMETER Description - The optional description to set for the group. + The description of the group. - .PARAMETER MemberUserNames - The usernames of the optional members to add to the group. - - .PARAMETER ComputerName - The optional name of the computer to update. Omit to create the group on the local machine. - - .NOTES - For remote machines, the currently logged on user must have rights to create a group. + .PARAMETER Members + The usernames of the members to add to the group. #> function New-Group { @@ -203,18 +373,19 @@ function New-Group [String] $GroupName, + [Parameter()] [String] $Description, + [Parameter()] [String[]] - $MemberUserNames, - - [ValidateNotNullOrEmpty()] - [String] - $ComputerName = $env:computerName + $Members ) - Set-StrictMode -Version 'Latest' + if (Test-GroupExists -GroupName $GroupName) + { + throw "Group $GroupName already exists." + } if (Test-IsNanoServer) { @@ -228,25 +399,16 @@ function New-Group <# .SYNOPSIS - Creates a Windows group on a full server - - .DESCRIPTION - This function creates a Windows group on the local or remote full server machine. + Creates a Windows group on a full server. .PARAMETER GroupName - The name of the group to create + The name of the group. .PARAMETER Description - The optional description to set for the group. + The description of the group. - .PARAMETER MemberUserNames - The usernames of the optional members to add to the group. - - .PARAMETER ComputerName - The optional name of the computer to update. Omit to create the group on the local machine. - - .NOTES - For remote machines, the currently logged on user must have rights to create a group. + .PARAMETER Members + The usernames of the members to add to the group. #> function New-GroupOnFullSKU { @@ -258,64 +420,48 @@ function New-GroupOnFullSKU [String] $GroupName, + [Parameter()] [String] $Description, + [Parameter()] [String[]] - $MemberUserNames, - - [ValidateNotNullOrEmpty()] - [String] - $ComputerName = $env:computerName + $Members ) - Set-StrictMode -Version 'Latest' - - $adsiComputerEntry = [ADSI] "WinNT://$ComputerName" - - if (Test-GroupExists -GroupName $GroupName) - { - Remove-Group -GroupName $GroupName -ComputerName $ComputerName - } - + $adsiComputerEntry = [ADSI] "WinNT://$env:computerName" $adsiGroupEntry = $adsiComputerEntry.Create('Group', $GroupName) if ($PSBoundParameters.ContainsKey('Description')) { - $adsiGroupEntry.Put('Description', $Description) | Out-Null + $null = $adsiGroupEntry.Put('Description', $Description) } - $adsiGroupEntry.SetInfo() | Out-Null + $null = $adsiGroupEntry.SetInfo() - if ($PSBoundParameters.ContainsKey("MemberUserNames")) + if ($PSBoundParameters.ContainsKey("Members")) { - $adsiGroupEntry = [ADSI]"WinNT://$ComputerName/$GroupName,group" + $adsiGroupEntry = [ADSI]"WinNT://$env:computerName/$GroupName,group" - foreach ($memberUserName in $MemberUserNames) + foreach ($memberUserName in $Members) { - $adsiGroupEntry.Add("WinNT://$ComputerName/$memberUserName") | Out-Null + $null = $adsiGroupEntry.Add("WinNT://$env:computerName/$memberUserName") } } } <# .SYNOPSIS - Creates a Windows group on a Nano server - - .DESCRIPTION - This function creates a Windows group on the local Nano server machine. + Creates a Windows group on a Nano server. .PARAMETER GroupName - The name of the group to create + The name of the group. .PARAMETER Description - The optional description to set for the group. + The description of the group. - .PARAMETER MemberUserNames - The usernames of the optional members to add to the group. - - .PARAMETER ComputerName - This parameter should not be used on a Nano server. + .PARAMETER Members + The usernames of the members to add to the group. #> function New-GroupOnNanoServer { @@ -327,55 +473,34 @@ function New-GroupOnNanoServer [String] $GroupName, + [Parameter()] [String] $Description, + [Parameter()] [String[]] - $MemberUserNames, - - [ValidateNotNullOrEmpty()] - [String] - $ComputerName = $env:computerName + $Members ) - Set-StrictMode -Version 'Latest' - - if ($PSBoundParameters.ContainsKey('ComputerName')) - { - if (-not (Test-IsLocalMachine -Scope $ComputerName)) - { - throw 'Do not specify ComputerName when running on NanoServer unless it is the local machine.' - } - } - - if (Test-GroupExists -GroupName $GroupName) - { - Remove-LocalGroup -Name $GroupName -ErrorAction SilentlyContinue - } - - New-LocalGroup -Name $GroupName + $null = New-LocalGroup -Name $GroupName if ($PSBoundParameters.ContainsKey('Description')) { - Set-LocalGroup -Name $GroupName -Description $Description + $null = Set-LocalGroup -Name $GroupName -Description $Description } - if ($PSBoundParameters.ContainsKey('MemberUserNames')) + if ($PSBoundParameters.ContainsKey('Members')) { - Add-LocalGroupMember -Name $GroupName -Member $Members + $null = Add-LocalGroupMember -Name $GroupName -Member $Members } } <# .SYNOPSIS - Deletes a user group. + Deletes a Windows group. .PARAMETER GroupName - The name of the user group to delete. - - .PARAMETER ComputerName - The optional name of the computer to update. - The default value is the local machine. + The name of the group. #> function Remove-Group { @@ -385,13 +510,14 @@ function Remove-Group [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] [String] - $GroupName, - - [ValidateNotNullOrEmpty()] - [String] - $ComputerName = $env:computerName + $GroupName ) + if (-not (Test-GroupExists -GroupName $GroupName)) + { + throw "Group $GroupName does not exist to remove." + } + if (Test-IsNanoServer) { Remove-GroupOnNanoServer @PSBoundParameters @@ -404,14 +530,10 @@ function Remove-Group <# .SYNOPSIS - Deletes a local user group on a full server. + Deletes a Windows group on a full server. .PARAMETER GroupName - The name of the local user group to delete. - - .PARAMETER ComputerName - The optional name of the computer to update. - The default value is the local machine. + The name of the group. #> function Remove-GroupOnFullSKU { @@ -421,33 +543,19 @@ function Remove-GroupOnFullSKU [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] [String] - $GroupName, - - [ValidateNotNullOrEmpty()] - [String] - $ComputerName = $env:computerName + $GroupName ) - Set-StrictMode -Version 'Latest' - - $adsiComputerEntry = [ADSI]"WinNT://$ComputerName" - - if (Test-GroupExists -GroupName $GroupName) - { - $adsiComputerEntry.Delete('Group', $GroupName) | Out-Null - } + $adsiComputerEntry = [ADSI]("WinNT://$env:computerName") + $null = $adsiComputerEntry.Delete('Group', $GroupName) } <# .SYNOPSIS - Deletes a local user group on a Nano server. + Deletes a Windows group on a Nano server. .PARAMETER GroupName - The name of the local user group to delete. - - .PARAMETER ComputerName - This parameter should not be used on NanoServer. - The default value is the local machine. + The name of the group. #> function Remove-GroupOnNanoServer { @@ -457,30 +565,263 @@ function Remove-GroupOnNanoServer [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] [String] - $GroupName, - - [ValidateNotNullOrEmpty()] - [String] - $ComputerName = $env:computerName + $GroupName ) - Set-StrictMode -Version 'Latest' + Remove-LocalGroup -Name $GroupName +} - if ($PSBoundParameters.ContainsKey('ComputerName')) +<# + .SYNOPSIS + Tests if a user with the given username exists. + + .PARAMETER Username + The username of the user. +#> +function Test-UserExists +{ + [OutputType([Boolean])] + [CmdletBinding()] + param + ( + [Parameter(Mandatory = $true)] + [String] + $Username + ) + + if (Test-IsNanoServer) { - if (-not (Test-IsLocalMachine -Scope $ComputerName)) - { - throw 'Do not specify ComputerName when running on NanoServer unless it is the local machine.' - } + return Test-UserExistsOnNanoServer @PSBoundParameters } - - if (Test-GroupExists -GroupName $GroupName) + else { - Remove-LocalGroup -Name $GroupName + return Test-UserExistsOnFullSKU @PSBoundParameters } } -Export-ModuleMember -Function ` - New-Group, ` - Remove-Group, ` - Test-GroupExists +<# + .SYNOPSIS + Tests if a user with the given username exists on a full server. + + .PARAMETER Username + The username of the user. +#> +function Test-UserExistsOnFullSKU +{ + [OutputType([Boolean])] + [CmdletBinding()] + param + ( + [Parameter(Mandatory = $true)] + [String] + $Username + ) + + $principalContext = New-Object -TypeName 'System.DirectoryServices.AccountManagement.PrincipalContext' ` + -ArgumentList @( [System.DirectoryServices.AccountManagement.ContextType]::Machine ) + + $user = [System.DirectoryServices.AccountManagement.UserPrincipal]::FindByIdentity($principalContext, $Username) + $userExists = $null -ne $user + + $null = $principalContext.Dispose() + + return $userExists +} + +<# + .SYNOPSIS + Tests if a user with the given username exists on a Nano server. + + .PARAMETER Username + The username of the user. +#> +function Test-UserExistsOnNanoServer +{ + [OutputType([Boolean])] + [CmdletBinding()] + param + ( + [Parameter(Mandatory = $true)] + [String] + $Username + ) + + $userExists = $true + + try + { + $null = Get-LocalUser -Name $Username + } + catch [System.Exception] + { + if ($_.CategoryInfo.ToString().Contains('UserNotFoundException')) + { + $userExists = $false + } + else + { + $_.Exception + } + } + + return $userExists +} + +<# + .SYNOPSIS + Creates a user account. + + .PARAMETER Credential + The credential containing the user's username and password. +#> +function New-User +{ + [CmdletBinding()] + param + ( + [Parameter(Mandatory = $true)] + [System.Management.Automation.PSCredential] + [System.Management.Automation.Credential()] + $Credential + ) + + if (Test-UserExists -Username $Credential.UserName) + { + throw "User $($Credential.UserName) already exists." + } + + if (Test-IsNanoServer) + { + New-UserOnNanoServer @PSBoundParameters + } + else + { + New-UserOnFullSKU @PSBoundParameters + } +} + +<# + .SYNOPSIS + Creates a user on a full server. + + .PARAMETER Credential + The credential containing the user's username and password. +#> +function New-UserOnFullSKU +{ + [CmdletBinding()] + param + ( + [Parameter(Mandatory = $true)] + [System.Management.Automation.PSCredential] + [System.Management.Automation.Credential()] + $Credential + ) + + $userName = $Credential.UserName + $password = $Credential.GetNetworkCredential().Password + + $adsiComputerEntry = [ADSI]("WinNT://$env:computerName") + $adsiUserEntry = $adsiComputerEntry.Create('User', $userName) + $null = $adsiUserEntry.SetPassword($password) + $null = $adsiUserEntry.SetInfo() +} + +<# + .SYNOPSIS + Creates a user on a Nano server. + + .PARAMETER Credential + The credential containing the user's username and password. +#> +function New-UserOnNanoServer +{ + [CmdletBinding()] + param + ( + [Parameter(Mandatory = $true)] + [System.Management.Automation.PSCredential] + [System.Management.Automation.Credential()] + $Credential + ) + + $userName = $Credential.UserName + $securePassword = $Credential.GetNetworkCredential().Password + + New-LocalUser -Name $userName -Password $securePassword +} + +<# + .SYNOPSIS + Removes a user. + + .PARAMETER UserName + The name of the user. +#> +function Remove-User +{ + [CmdletBinding()] + param + ( + [Parameter(Mandatory = $true)] + [String] + $UserName + ) + + if (-not (Test-UserExists -Username $Username)) + { + throw "User $Username does not exist to remove." + } + + if (Test-IsNanoServer) + { + Remove-UserOnNanoServer @PSBoundParameters + } + else + { + Remove-UserOnFullSKU @PSBoundParameters + } +} + +<# + .SYNOPSIS + Removes a user on a full server. + + .PARAMETER UserName + The name of the user. +#> +function Remove-UserOnFullSKU +{ + [CmdletBinding()] + param + ( + [Parameter(Mandatory = $true)] + [String] + $UserName + ) + + $adsiComputerEntry = [ADSI]("WinNT://$env:computerName") + $null = $adsiComputerEntry.Delete('User', $UserName) +} + +<# + .SYNOPSIS + Removes a user on a Nano server. + + .PARAMETER UserName + The name of the user. +#> +function Remove-UserOnNanoServer +{ + [CmdletBinding()] + param + ( + [Parameter(Mandatory = $true)] + [String] + $UserName + ) + + $null = Remove-LocalUser -Name $UserName +} + +Export-ModuleMember -Function @( 'New-Group', 'Remove-Group', 'Test-GroupExists', 'New-User', 'Remove-User' ) diff --git a/Tests/Unit/MSFT_GroupResource.Tests.ps1 b/Tests/Unit/MSFT_GroupResource.Tests.ps1 index f44e684..d76db53 100644 --- a/Tests/Unit/MSFT_GroupResource.Tests.ps1 +++ b/Tests/Unit/MSFT_GroupResource.Tests.ps1 @@ -1,6 +1,9 @@ [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidUsingConvertToSecureStringWithPlainText', '')] param () +$errorActionPreference = 'Stop' +Set-StrictMode -Version 'Latest' + Import-Module -Name (Join-Path -Path (Split-Path -Path $PSScriptRoot -Parent) ` -ChildPath (Join-Path -Path 'TestHelpers' ` -ChildPath 'CommonTestHelper.psm1')) @@ -1664,8 +1667,9 @@ try Mock 'Get-GroupMembersFromDirectoryEntry' { return @( $memberDirectoryEntry3 ) } - It 'Should ignore stale members - Expected to write a warning' { - $getMembersResult = Get-MembersAsPrincipalsList -Group $script:testGroup -PrincipalContextCache $principalContextCache -Disposables $disposables | Should Be $null + It 'Should ignore stale members' { + $getMembersResult = Get-MembersAsPrincipalsList -Group $script:testGroup -PrincipalContextCache $principalContextCache -Disposables $disposables -WarningAction 'SilentlyContinue' + $getMembersResult | Should Be $null Assert-MockCalled -CommandName 'Get-GroupMembersFromDirectoryEntry' -ParameterFilter { $Group.Name -eq $script:testGroupName } Assert-MockCalled -CommandName 'New-Object' -ParameterFilter { $TypeName -eq 'System.DirectoryServices.DirectoryEntry' } @@ -1998,8 +2002,6 @@ try } } } - - } } }