From 19ec2969f5e2eb89a8afec7972d1f96fac1159e5 Mon Sep 17 00:00:00 2001 From: Easyreturns <20781445+jeffotterpohl@users.noreply.github.com> Date: Fri, 26 Jun 2020 14:46:14 -0500 Subject: [PATCH 1/9] Initial Module Creation for AdminUser --- CHANGELOG.md | 2 + README.md | 34 + source/ConfigMgrCBDsc.psd1 | 3 +- .../DSC_CMAdministrativeUser.psm1 | 628 +++++++++++++++++ .../DSC_CMAdministrativeUser.schema.mof | 16 + .../DSC_CMAdministrativeUser.strings.psd1 | 20 + .../CMAdministrativeUser_Absent.ps1 | 18 + .../CMAdministrativeUser_Present.ps1 | 44 ++ .../ConfigMgrCBDsc.ResourceHelper.psd1 | 1 + .../ConfigMgrCBDsc.ResourceHelper.psm1 | 114 ++++ tests/Unit/CMAdministrativeUser.tests.ps1 | 639 ++++++++++++++++++ .../ConfigMgrCBDsc.ResourceHelper.tests.ps1 | 86 +++ 12 files changed, 1604 insertions(+), 1 deletion(-) create mode 100644 source/DSCResources/DSC_CMAdministrativeUser/DSC_CMAdministrativeUser.psm1 create mode 100644 source/DSCResources/DSC_CMAdministrativeUser/DSC_CMAdministrativeUser.schema.mof create mode 100644 source/DSCResources/DSC_CMAdministrativeUser/en-US/DSC_CMAdministrativeUser.strings.psd1 create mode 100644 source/Examples/Resources/CMAdministrativeUser/CMAdministrativeUser_Absent.ps1 create mode 100644 source/Examples/Resources/CMAdministrativeUser/CMAdministrativeUser_Present.ps1 create mode 100644 tests/Unit/CMAdministrativeUser.tests.ps1 diff --git a/CHANGELOG.md b/CHANGELOG.md index 7ca942b..121ece6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -40,6 +40,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Added CMPullDistributionPoint Resource - Added ConvertTo-AnyCimInstance to the ResourceHelper - Added CMSiteMaintenance Resource +- Added CMAdministrativeUser Resource +- Added Compare-MultipleCompares to the ResourceHelper ### Changed diff --git a/README.md b/README.md index b47617c..b4e5127 100644 --- a/README.md +++ b/README.md @@ -91,6 +91,8 @@ Please check out common DSC Community [contributing guidelines](https://dsccommu - **CMPullDistributionPoint**: Provides a resource for modifying a distribution point and making the distribution point a Pull Distribution Point. - **CMSiteMaintenance**: Provides a resource for modifying the Site Maintenance tasks. +- **DSC_CMAdministrativeUser**: Provides a resource for adding, removing, and configuring + administrative users. ### xSccmPreReqs @@ -858,3 +860,35 @@ Please check out common DSC Community [contributing guidelines](https://dsccommu - [CMSiteMaintenance_MaintenanceTask_Enabled](Source\Examples\Resources\CMSiteMaintenance\CMSiteMaintenance_MaintenanceTask_Enabled.ps1) - [CMSiteMaintenance_SummaryTask_Enabled](Source\Examples\Resources\CMSiteMaintenance\CMSiteMaintenance_SummaryTask_Enabled.ps1) - [CMSiteMaintenance_UpdateAppCatTablesTask_Enabled](Source\Examples\Resources\CMSiteMaintenance\CMSiteMaintenance_UpdateAppCatTablesTask_Enabled.ps1) + +### CMAdministrativeUser + +- **[String] AdminName** _(Key)_: Specifies the name of the administrator account. +- **[String] SiteCode** _(Required)_: Specifies the Site Code for the Configuration + Manager site. +- **[String] Roles[]** _(Write)_: Specifies an array of names for the roles + desired to be assigned to an administrative user. +- **[String] RolesToInclude[]** _(Write)_: Specifies an array of names for the + roles desired to be added to an administrative user. +- **[String] RolesToExclude[]** _(Write)_: Specifies an array of names for the + roles desired to be removed from an administrative user. +- **[String] Scopes[]** _(Write)_: Specifies an array of names for the scopes + desired to be assigned to an administrative user. +- **[String] ScopesToInclude[]** _(Write)_: Specifies an array of names for the + scopes desired to be added to an administrative user. +- **[String] ScopesToExclude[]** _(Write)_: Specifies an array of names for the + scopes desired to be removed from an administrative user. +- **[String] Collections[]** _(Write)_: Specifies an array of names for the + collections desired to be assigned to an administrative user. +- **[String] CollectionsToInclude[]** _(Write)_: Specifies an array of names for + the collections desired to be added to an administrative user. +- **[String] CollectionsToExclude[]** _(Write)_: Specifies an array of names for + the collections desired to be removed from an administrative user. +- **[String] Ensure** _(Write)_: Specifies whether the administrative user + is present or absent. + - Values include: { Present | Absent } + +#### CMAdministrativeUser Examples + +- [CMAdministrativeUser_Absent](Source\Examples\Resources\CMAdministrativeUser\CMAdministrativeUser_Absent.ps1) +- [CMAdministrativeUser_Present](Source\Examples\Resources\CMAdministrativeUser\CMAdministrativeUser_Present.ps1) diff --git a/source/ConfigMgrCBDsc.psd1 b/source/ConfigMgrCBDsc.psd1 index 58fd9bf..987610d 100644 --- a/source/ConfigMgrCBDsc.psd1 +++ b/source/ConfigMgrCBDsc.psd1 @@ -66,6 +66,7 @@ 'CMPxeDistributionPoint' 'CMPullDistributionPoint' 'CMSiteMaintenance' + 'CMAdministrativeUser' ) <# @@ -82,7 +83,7 @@ 'SccmSqlSetup','SCCMInstall','CMIniFile','Collections','Boundaries','ForestDiscovery','ClientStatusSettings','BoundaryGroups', 'ManagementPoint','AssetIntelligencePoint','FallbackStatusPoint','SoftwareUpdatePoint','DistrubtionPoint','HeartbeatDiscovery', 'ServiceConnectionPoint','NetworkDiscovery','ReportingServicePoint','SystemDiscovery','PXEDistributionPoint','PullDistributionPoint', - 'SiteMaintenance') + 'SiteMaintenance','DSC_CMAdministrativeUser') # A URL to the license for this module. LicenseUri = 'https://github.com/dsccommunity/ConfigMgrCBDsc/blob/master/LICENSE' diff --git a/source/DSCResources/DSC_CMAdministrativeUser/DSC_CMAdministrativeUser.psm1 b/source/DSCResources/DSC_CMAdministrativeUser/DSC_CMAdministrativeUser.psm1 new file mode 100644 index 0000000..b36abf8 --- /dev/null +++ b/source/DSCResources/DSC_CMAdministrativeUser/DSC_CMAdministrativeUser.psm1 @@ -0,0 +1,628 @@ +$script:dscResourceCommonPath = Join-Path -Path $PSScriptRoot -ChildPath '..\..\Modules\DscResource.Common' +$script:configMgrResourcehelper = Join-Path -Path $PSScriptRoot -ChildPath '..\..\Modules\ConfigMgrCBDsc.ResourceHelper' + +Import-Module -Name $script:dscResourceCommonPath +Import-Module -Name $script:configMgrResourcehelper + +$script:localizedData = Get-LocalizedData -DefaultUICulture 'en-US' + +<# + .SYNOPSIS + This will return a hashtable of results. + + .PARAMETER SiteCode + Specifies the SiteCode for the Configuration Manager site. + + .PARAMETER AdminName + Specifies the name of the administrator account. +#> +function Get-TargetResource +{ + [CmdletBinding()] + [OutputType([System.Collections.Hashtable])] + param + ( + [Parameter(Mandatory = $true)] + [String] + $SiteCode, + + [Parameter(Mandatory = $true)] + [String] + $AdminName + ) + + Write-Verbose -Message $script:localizedData.RetrieveSettingValue + Import-ConfigMgrPowerShellModule -SiteCode $SiteCode + Set-Location -Path "$($SiteCode):\" + + $admin = Get-CMAdministrativeUser -Name $AdminName + + if ($admin) + { + $scope = @() + $collections = @() + + foreach ($item in $admin.Permissions) + { + if ($item.CategoryTypeID -eq 29) + { + $scope += $item.CategoryName + } + elseif ($item.CategoryTypeId -eq 1) + { + $collections += $item.CategoryName + } + } + + $status = 'Present' + } + else + { + $status = 'Absent' + } + + return @{ + SiteCode = $SiteCode + AdminName = $AdminName + Roles = $admin.RoleNames + Collections = $collections + Scopes = $scope + Ensure = $status + } +} + +<# + .SYNOPSIS + This will set the desired state. + + .PARAMETER SiteCode + Specifies the SiteCode for the Configuration Manager site. + + .PARAMETER AdminName + Specifies the name of the administrator account. + + .PARAMETER Roles + Specifies an array of names for the roles desired to be assigned to an administrative user. + + .PARAMETER RolesToInclude + Specifies an array of names for the roles desired to be added to an administrative user. + + .PARAMETER RolesToExclude + Specifies an array of names for the roles desired to be removed from an administrative user. + + .PARAMETER Scopes + Specifies an array of names for the scopes desired to be assigned to an administrative user. + + .PARAMETER ScopesToInclude + Specifies an array of names for the scopes desired to be added to an administrative user. + + .PARAMETER ScopesToExclude + Specifies an array of names for the scopes desired to be removed from an administrative user. + + .PARAMETER Collections + Specifies an array of names for the collections desired to be assigned to an administrative user. + + .PARAMETER CollectionsToInclude + Specifies an array of names for the collections desired to be added to an administrative user. + + .PARAMETER CollectionsToExclude + Specifies an array of names for the collections desired to be removed from an administrative user. + + .PARAMETER Ensure + Specifies if the administrative User is to be present or absent. +#> +function Set-TargetResource +{ + [CmdletBinding()] + param + ( + [Parameter(Mandatory = $true)] + [String] + $SiteCode, + + [Parameter(Mandatory = $true)] + [String] + $AdminName, + + [Parameter()] + [String[]] + $Roles, + + [Parameter()] + [String[]] + $RolesToInclude, + + [Parameter()] + [String[]] + $RolesToExclude, + + [Parameter()] + [String[]] + $Scopes, + + [Parameter()] + [String[]] + $ScopesToInclude, + + [Parameter()] + [String[]] + $ScopesToExclude, + + [Parameter()] + [String[]] + $Collections, + + [Parameter()] + [String[]] + $CollectionsToInclude, + + [Parameter()] + [String[]] + $CollectionsToExclude, + + [Parameter()] + [ValidateSet('Present','Absent')] + [String] + $Ensure = 'Present' + ) + + Import-ConfigMgrPowerShellModule -SiteCode $SiteCode + Set-Location -Path "$($SiteCode):\" + $state = Get-TargetResource -SiteCode $SiteCode -AdminName $AdminName + + try + { + if ($Ensure -eq 'Present') + { + if ($Roles -or $RolesToInclude -or $RolesToExclude) + { + $rolesArray = @{ + Match = $Roles + Include = $RolesToInclude + Exclude = $RolesToExclude + CurrentState = $state.Roles + } + + $roleCompare = Compare-MultipleCompares @rolesArray + + if ($roleCompare.Missing) + { + foreach ($roleCheck in $roleCompare.Missing) + { + if (Get-CMSecurityRole -Name $roleCheck) + { + $rolesAdd += $roleCheck + } + else + { + $errorMsg += ($script:localizedData.ErrorMsg -f $roleCheck, 'role') + } + } + } + } + + if ($Scopes -or $ScopesToInclude -or $ScopesToExclude) + { + $scopesArray = @{ + Match = $Scopes + Include = $ScopesToInclude + Exclude = $ScopesToExclude + CurrentState = $state.Scopes + } + + $scopeCompare = Compare-MultipleCompares @scopesArray + + if ($scopeCompare.Missing) + { + foreach ($scopeCheck in $scopeCompare.Missing) + { + if (Get-CMSecurityScope -Name $scopeCheck) + { + $scopesAdd += $scopeCheck + } + else + { + $errorMsg += ($script:localizedData.ErrorMsg -f $scopeCheck, 'Scope') + } + } + } + } + + if ($Collections -or $CollectionsToInclude -or $CollectionsToExclude) + { + $collectionsArray = @{ + Match = $Collections + Include = $CollectionsToInclude + Exclude = $CollectionsToExclude + CurrentState = $state.Collections + } + + $collectionsCompare = Compare-MultipleCompares @collectionsArray + + if ($collectionsCompare.Missing) + { + foreach ($collectionCheck in $collectionsCompare.Missing) + { + if (Get-CMCollection -Name $collectionCheck) + { + $collectionsAdd += $collectionCheck + } + else + { + $errorMsg += ($script:localizedData.ErrorMsg -f $collectionCheck, 'Collections') + } + } + } + } + + if ($state.Ensure -eq 'Absent') + { + if ($rolesAdd) + { + Write-Verbose -Message ($script:localizedData.RolesMissing -f ($rolesAdd | Out-String)) + + $buildingParams += @{ + RoleName = $rolesAdd + } + } + else + { + throw $script:localizedData.ValidRole + } + + if ($scopesAdd) + { + Write-Verbose -Message ($script:localizedData.ScopesMissing -f ($scopesAdd | Out-String)) + + $buildingParams += @{ + SecurityScopeName = $scopesAdd + } + } + + if ($collectionsAdd) + { + Write-Verbose -Message ($script:localizedData.CollectionsMissing -f ($collectionsAdd | Out-String)) + + $buildingParams += @{ + CollectionName = $collectionsAdd + } + } + + if ($buildingParams) + { + New-CMAdministrativeUser -Name $AdminName @buildingParams + } + } + else + { + if ($rolesAdd) + { + foreach ($role in $rolesAdd) + { + Write-Verbose -Message ($script:localizedData.RolesMissing -f $role) + Add-CMSecurityRoleToAdministrativeUser -RoleName $role -AdministrativeUserName $AdminName + } + } + + if ($roleCompare.Remove) + { + foreach ($roleRemove in $roleCompare.Remove) + { + Write-Verbose -Message ($script:localizedData.RolesRemove -f $roleRemove) + Remove-CMSecurityRoleFromAdministrativeUser -RoleName $roleRemove -AdministrativeUserName $AdminName + } + } + + if ($scopesAdd) + { + if ($scopeCompare.CurrentState.Contains('All')) + { + throw $script:localizedData.ModifyAll + } + + if ($scopesAdd.Contains('All')) + { + throw $script:localizedData.AllParam + } + + foreach ($scope in $scopesAdd) + { + Write-Verbose -Message ($script:localizedData.ScopesMissing -f $scope) + Add-CMSecurityScopeToAdministrativeUser -AdministrativeUserName $AdminName -SecurityScopeName $scope + } + } + + if ($scopeCompare.Remove) + { + if ($scopeCompare.Remove.Contains('All')) + { + throw $script:localizedData.RemoveAll + } + + foreach ($scopeRemove in $scopeCompare.Remove) + { + Write-Verbose -Message ($script:localizedData.ScopesRemove -f $scopeRemove) + Remove-CMSecurityScopeFromAdministrativeUser -AdministrativeUserName $AdminName -SecurityScopeName $scopeRemove + } + } + + if ($collectionsAdd) + { + foreach ($collection in $collectionsAdd) + { + Write-Verbose -Message ($script:localizedData.CollectionsMissing -f $collection) + Add-CMCollectionToAdministrativeUser -UserName $AdminName -CollectionName $collection + } + } + + if ($collectionsCompare.Remove) + { + foreach ($collectionRemove in $collectionsCompare.Remove) + { + Write-Verbose -Message ($script:localizedData.CollectionsRemove -f $collectionRemove) + Remove-CMCollectionFromAdministrativeUser -UserName $AdminName -CollectionName $collectionRemove + } + } + } + } + elseif ($state.Ensure -eq 'Present') + { + Write-Verbose -Message ($script:localizedData.RemoveAdmin -f $AdminName) + Remove-CMAdministrativeUser -Name $AdminName + } + + if ($errorMsg) + { + throw $errorMsg + } + } + catch + { + throw $_ + } + finally + { + Set-Location -Path $env:windir + } +} + +<# + .SYNOPSIS + This will test the desired state. + + .PARAMETER SiteCode + Specifies the SiteCode for the Configuration Manager site. + + .PARAMETER AdminName + Specifies the name of the administrator account. + + .PARAMETER Roles + Specifies an array of names for the roles desired to be assigned to an administrative user. + + .PARAMETER RolesToInclude + Specifies an array of names for the roles desired to be added to an administrative user. + + .PARAMETER RolesToExclude + Specifies an array of names for the roles desired to be removed from an administrative user. + + .PARAMETER Scopes + Specifies an array of names for the scopes desired to be assigned to an administrative user. + + .PARAMETER ScopesToInclude + Specifies an array of names for the scopes desired to be added to an administrative user. + + .PARAMETER ScopesToExclude + Specifies an array of names for the scopes desired to be removed from an administrative user. + + .PARAMETER Collections + Specifies an array of names for the collections desired to be assigned to an administrative user. + + .PARAMETER CollectionsToInclude + Specifies an array of names for the collections desired to be added to an administrative user. + + .PARAMETER CollectionsToExclude + Specifies an array of names for the collections desired to be removed from an administrative user. + + .PARAMETER Ensure + Specifies if the administrative User is to be present or absent. +#> +function Test-TargetResource +{ + [CmdletBinding()] + [OutputType([System.Boolean])] + param + ( + [Parameter(Mandatory = $true)] + [String] + $SiteCode, + + [Parameter(Mandatory = $true)] + [String] + $AdminName, + + [Parameter()] + [String[]] + $Roles, + + [Parameter()] + [String[]] + $RolesToInclude, + + [Parameter()] + [String[]] + $RolesToExclude, + + [Parameter()] + [String[]] + $Scopes, + + [Parameter()] + [String[]] + $ScopesToInclude, + + [Parameter()] + [String[]] + $ScopesToExclude, + + [Parameter()] + [String[]] + $Collections, + + [Parameter()] + [String[]] + $CollectionsToInclude, + + [Parameter()] + [String[]] + $CollectionsToExclude, + + [Parameter()] + [ValidateSet('Present','Absent')] + [String] + $Ensure = 'Present' + ) + + Import-ConfigMgrPowerShellModule -SiteCode $SiteCode + Set-Location -Path "$($SiteCode):\" + $state = Get-TargetResource -SiteCode $SiteCode -AdminName $AdminName + $result = $true + + if ($Ensure -eq 'Present') + { + if ($state.Ensure -eq 'Absent') + { + if ([string]::IsNullOrEmpty($Roles) -and [string]::IsNullOrEmpty($RolesToInclude)) + { + write-Warning -Message 'Administrator account is absent, Roles or RolesToInclude will need to be specified with a valide rolename' + } + + Write-Verbose -Message ($script:localizedData.AddAdmin -f $AdminName) + $result = $false + } + else + { + if ($Roles -or $RolesToInclude -or $RolesToExclude) + { + $rolesArray = @{ + Match = $Roles + Include = $RolesToInclude + Exclude = $RolesToExclude + CurrentState = $state.Roles + } + + $roleCompare = Compare-MultipleCompares @rolesArray + + if ($PSBoundParameters.ContainsKey('Roles')) + { + if ($PSBoundParameters.ContainsKey('RolesToInclude') -or $PSBoundParameters.ContainsKey('RolesToExclude')) + { + Write-Warning -Message $script:localizedData.RolesIgnore + } + } + + if ($roleCompare.Missing) + { + Write-Verbose -Message ($script:localizedData.RolesMissing -f ($roleCompare.Missing | Out-String)) + $result = $false + } + + if ($roleCompare.Remove) + { + Write-Verbose -Message ($script:localizedData.RolesRemove -f ($roleCompare.Remove | Out-String)) + $result = $false + } + } + + if ($Scopes -or $ScopesToInclude -or $ScopesToExclude) + { + $scopesArray = @{ + Match = $Scopes + Include = $ScopesToInclude + Exclude = $ScopesToExclude + CurrentState = $state.Scopes + } + + $scopeCompare = Compare-MultipleCompares @scopesArray + + if ($PSBoundParameters.ContainsKey('Scopes')) + { + if ($PSBoundParameters.ContainsKey('ScopesToInclude') -or $PSBoundParameters.ContainsKey('ScopesToExclude')) + { + Write-Warning -Message $script:localizedData.ScopesIgnore + } + } + + if ($scopeCompare.Missing) + { + if ($scopeCompare.Missing.Contains('All')) + { + Write-Warning -Message $script:localizedData.AllParam + } + + if ($scopeCompare.CurrentState.Contains('All')) + { + Write-Warning -Message $script:localizedData.ModifyAll + } + + Write-Verbose -Message ($script:localizedData.ScopesMissing -f ($scopeCompare.Missing | Out-String)) + $result = $false + } + + if ($scopeCompare.Remove) + { + if ($scopeCompare.Remove.Contains('All')) + { + Write-Warning -Message $script:localizedData.RemoveAll + } + + Write-Verbose -Message ($script:localizedData.ScopesRemove -f ($scopeCompare.Remove | Out-String)) + $result = $false + } + } + + if ($Collections -or $CollectionsToInclude -or $CollectionsToExclude) + { + $collectionsArray = @{ + Match = $Collections + Include = $CollectionsToInclude + Exclude = $CollectionsToExclude + CurrentState = $state.Collections + } + + $collectionsCompare = Compare-MultipleCompares @collectionsArray + + if ($PSBoundParameters.ContainsKey('Collections')) + { + if ($PSBoundParameters.ContainsKey('CollectionsToInclude') -or + $PSBoundParameters.ContainsKey('CollectionsToExclude')) + { + Write-Warning -Message $script:localizedData.CollectionsIgnore + } + } + + if ($collectionsCompare.Missing) + { + Write-Verbose -Message ($script:localizedData.CollectionsMissing -f ($collectionsCompare.Missing | Out-String)) + $result = $false + } + + if ($collectionsCompare.Remove) + { + Write-Verbose -Message ($script:localizedData.CollectionsRemove -f ($collectionsCompare.Remove | Out-String)) + $result = $false + } + } + } + } + elseif ($state.Ensure -eq 'Present') + { + Write-Verbose -Message ($script:localizedData.RemoveAdmin -f $AdminName) + $result = $false + } + + Write-Verbose -Message ($script:localizedData.TestState -f $result) + Set-Location -Path $env:windir + return $result +} + +Export-ModuleMember -Function *-TargetResource diff --git a/source/DSCResources/DSC_CMAdministrativeUser/DSC_CMAdministrativeUser.schema.mof b/source/DSCResources/DSC_CMAdministrativeUser/DSC_CMAdministrativeUser.schema.mof new file mode 100644 index 0000000..cefd105 --- /dev/null +++ b/source/DSCResources/DSC_CMAdministrativeUser/DSC_CMAdministrativeUser.schema.mof @@ -0,0 +1,16 @@ +[ClassVersion("1.0.0"), FriendlyName("CMAdministrativeUser")] +class DSC_CMAdministrativeUser : OMI_BaseResource +{ + [Key, Description("Specifies the name of the administrator account.")] String AdminName; + [Required, Description("Specifies the SiteCode for the Configuration Manager site.")] String SiteCode; + [Write, Description("Specifies an array of names for the roles desired to be assigned to an administrative user.")] String Roles[]; + [Write, Description("Specifies an array of names for the roles desired to be added to an administrative user.")] String RolesToInclude[]; + [Write, Description("Specifies an array of names for the roles desired to be removed from an administrative user.")] String RolesToExclude[]; + [Write, Description("Specifies an array of names for the scopes desired to be assigned to an administrative user.")] String Scopes[]; + [Write, Description("Specifies an array of names for the scopes desired to be added to an administrative user.")] String ScopesToInclude[]; + [Write, Description("Specifies an array of names for the scopes desired to be removed from an administrative user.")] String ScopesToExclude[]; + [Write, Description("Specifies an array of names for the collections desired to be assigned to an administrative user.")] String Collections[]; + [Write, Description("Specifies an array of names for the collections desired to be added to an administrative user.")] String CollectionsToInclude[]; + [Write, Description("Specifies an array of names for the collections desired to be removed from an administrative user.")] String CollectionsToExclude[]; + [Write, Description("Specifies whether the administrative user is present or absent."), ValueMap{"Present","Absent"}, Values{"Present","Absent"}] String Ensure; +}; diff --git a/source/DSCResources/DSC_CMAdministrativeUser/en-US/DSC_CMAdministrativeUser.strings.psd1 b/source/DSCResources/DSC_CMAdministrativeUser/en-US/DSC_CMAdministrativeUser.strings.psd1 new file mode 100644 index 0000000..8c35c23 --- /dev/null +++ b/source/DSCResources/DSC_CMAdministrativeUser/en-US/DSC_CMAdministrativeUser.strings.psd1 @@ -0,0 +1,20 @@ +ConvertFrom-StringData @' + RetrieveSettingValue = Getting results for Configuration Manager administrative user. + TestState = Test-TargetResource compliance check returned: {0}. + AddAdmin = Expected {0} to be present but is absent. + RolesMissing = The following roles are missing {0}. + RolesRemove = The following roles need to be removed {0}. + ScopesMissing = The following scopes are missing {0}. + ScopesRemove = The following scopes need to be removed {0}. + CollectionsMissing = The following collections are missing {0}. + CollectionsRemove = The following collections need to be removed {0}. + RemoveAdmin = Expected {0} to be absent but is present. + RolesIgnore = Roles is set, ignoring RolesToInclude and RolesToExclude settings. + ScopesIgnore = Scopes is set, ignoring ScopesToInclude and ScopesToExclude settings. + CollectionsIgnore = Collections is set, ignoring CollectionsToInclude and CollectionsToExclude settings. + ValidRole = When administrative user does not exist, at least 1 valid role must be specified. + ErrorMsg = {0} was not added as is not a valid {1}. + AllParam = Unable to add the All scopes setting via Desired State Configuration, All can be used for new account only. + RemoveAll = Unable to remove the All scope via Desired State Configuration. + ModifyAll = Unable to modify scope with Desired State Configuration as it is currently set to All. +'@ diff --git a/source/Examples/Resources/CMAdministrativeUser/CMAdministrativeUser_Absent.ps1 b/source/Examples/Resources/CMAdministrativeUser/CMAdministrativeUser_Absent.ps1 new file mode 100644 index 0000000..96f8780 --- /dev/null +++ b/source/Examples/Resources/CMAdministrativeUser/CMAdministrativeUser_Absent.ps1 @@ -0,0 +1,18 @@ +<# + .SYNOPSIS + A DSC configuration script to remove an administrative users in Configuration Manager. +#> +Configuration Example +{ + Import-DscResource -ModuleName ConfigMgrCBDsc + + Node localhost + { + CMAdministrativeUser User1 + { + SiteCode = 'Lab' + AdminName = 'contoso\User1' + Ensure = 'Absent' + } + } +} diff --git a/source/Examples/Resources/CMAdministrativeUser/CMAdministrativeUser_Present.ps1 b/source/Examples/Resources/CMAdministrativeUser/CMAdministrativeUser_Present.ps1 new file mode 100644 index 0000000..d0bcc4e --- /dev/null +++ b/source/Examples/Resources/CMAdministrativeUser/CMAdministrativeUser_Present.ps1 @@ -0,0 +1,44 @@ +<# + .SYNOPSIS + A DSC configuration script to create an administrative users in Configuration Manager. +#> +Configuration Example +{ + Import-DscResource -ModuleName ConfigMgrCBDsc + + Node localhost + { + CMAdministrativeUser User1 + { + SiteCode = 'Lab' + AdminName = 'contoso\User1' + RolesToInclude = 'Security Administrator' + RolesToExclude = 'Remote Tools Operator' + CollectionsToInclude = 'Collection0' + CollectionsToExclude = 'Collection1' + ScopesToInclude = 'Default' + ScopesToExclude = 'Test' + Ensure = 'Present' + } + + CMAdministrativeUser User2 + { + SiteCode = 'Lab' + AdminName = 'contoso\User2' + Roles = 'Security Administrator' + Collections = 'Collection0' + Scopes = 'Default' + Ensure = 'Present' + } + + CMAdministrativeUser User3 + { + SiteCode = 'Lab' + AdminName = 'contoso\User3' + Roles = 'Security Administrator','Full Administrator' + Collections = 'Collection0','Collection1' + Scopes = 'Default','Scope1' + Ensure = 'Present' + } + } +} diff --git a/source/Modules/ConfigMgrCBDsc.ResourceHelper/ConfigMgrCBDsc.ResourceHelper.psd1 b/source/Modules/ConfigMgrCBDsc.ResourceHelper/ConfigMgrCBDsc.ResourceHelper.psd1 index 09b67f4..8f0e798 100644 --- a/source/Modules/ConfigMgrCBDsc.ResourceHelper/ConfigMgrCBDsc.ResourceHelper.psd1 +++ b/source/Modules/ConfigMgrCBDsc.ResourceHelper/ConfigMgrCBDsc.ResourceHelper.psd1 @@ -41,6 +41,7 @@ 'Get-BoundaryInfo' 'ConvertTo-ScheduleInterval' 'ConvertTo-AnyCimInstance' + 'Compare-MultipleCompares' ) # Cmdlets to export from this module diff --git a/source/Modules/ConfigMgrCBDsc.ResourceHelper/ConfigMgrCBDsc.ResourceHelper.psm1 b/source/Modules/ConfigMgrCBDsc.ResourceHelper/ConfigMgrCBDsc.ResourceHelper.psm1 index f8455ce..ca0b8ac 100644 --- a/source/Modules/ConfigMgrCBDsc.ResourceHelper/ConfigMgrCBDsc.ResourceHelper.psm1 +++ b/source/Modules/ConfigMgrCBDsc.ResourceHelper/ConfigMgrCBDsc.ResourceHelper.psm1 @@ -407,6 +407,119 @@ function ConvertTo-AnyCimInstance -Property $property -ClientOnly } +<# + .SYNOPSIS + Returns the boundary ID based on Value and Type of boundary specified. + + .PARAMETER Match + Specifies an array of values to validate if missing or extra settings compared to current state. + + .PARAMETER Include + Specifies an array of values to validate if missing from current state. + + .PARAMETER Exclude + Specifies an array of values to validate if extra compared to current state. + + .PARAMETER CurrentState + Specifies an array to compare against for match, include, or exclude. +#> +function Compare-MultipleCompares +{ + [CmdletBinding()] + param + ( + [Parameter()] + [AllowEmptyString()] + [String[]] + $Match, + + [Parameter()] + [AllowEmptyString()] + [String[]] + $Include, + + [Parameter()] + [AllowEmptyString()] + [String[]] + $Exclude, + + [Parameter()] + [String[]] + $CurrentState + ) + + $missing = @() + $remove = @() + + if (-not [string]::IsNullOrEmpty($Match)) + { + $type = 'Match' + + if ($null -eq $CurrentState) + { + $missing = $Match + } + else + { + $compares = Compare-Object -ReferenceObject $Match -DifferenceObject $CurrentState + + foreach ($compare in $compares) + { + if ($compare.SideIndicator -eq '<=') + { + $missing += $compare.InputObject + } + else + { + $remove += $compare.InputObject + } + } + } + } + else + { + if (-not [string]::IsNullOrEmpty($Include)) + { + $type = 'Include' + + foreach ($item in $Include) + { + if ($CurrentState -notcontains $item) + { + $missing += $item + } + } + } + + if (-not [string]::IsNullOrEmpty($Exclude)) + { + if ($type -eq 'Include') + { + $type = 'Include, Exclude' + } + else + { + $type = 'Exclude' + } + + foreach ($item in $Exclude) + { + if ($CurrentState -contains $item) + { + $remove += ($CurrentState | Where-Object -FilterScript {$_ -eq $item}) + } + } + } + } + + return @{ + Type = $type + Missing = $missing + Remove = $remove + CurrentState = $CurrentState + } +} + Export-ModuleMember -Function @( 'Import-ConfigMgrPowerShellModule' 'Convert-CidrToIP' @@ -416,4 +529,5 @@ Export-ModuleMember -Function @( 'Get-BoundaryInfo' 'ConvertTo-ScheduleInterval' 'ConvertTo-AnyCimInstance' + 'Compare-MultipleCompares' ) diff --git a/tests/Unit/CMAdministrativeUser.tests.ps1 b/tests/Unit/CMAdministrativeUser.tests.ps1 new file mode 100644 index 0000000..cc83327 --- /dev/null +++ b/tests/Unit/CMAdministrativeUser.tests.ps1 @@ -0,0 +1,639 @@ +[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidUsingConvertToSecureStringWithPlainText', '')] +param () + +$script:dscModuleName = 'ConfigMgrCBDsc' +$script:dscResourceName = 'DSC_CMAdministrativeUser' + +function Invoke-TestSetup +{ + try + { + Import-Module -Name DscResource.Test -Force -ErrorAction 'Stop' + } + catch [System.IO.FileNotFoundException] + { + throw 'DscResource.Test module dependency not found. Please run ".\build.ps1 -Tasks build" first.' + } + + $script:testEnvironment = Initialize-TestEnvironment ` + -DSCModuleName $script:dscModuleName ` + -DSCResourceName $script:dscResourceName ` + -ResourceType 'Mof' ` + -TestType 'Unit' + + # Import Stub function + $script:moduleRoot = Split-Path -Parent (Split-Path -Parent $PSScriptRoot) + Import-Module (Join-Path -Path $PSScriptRoot -ChildPath 'Stubs\ConfigMgrCBDscStub.psm1') -Force -WarningAction SilentlyContinue +} + +function Invoke-TestCleanup +{ + Restore-TestEnvironment -TestEnvironment $script:testEnvironment +} + +Invoke-TestSetup + +# Begin Testing +try +{ + InModuleScope $script:dscResourceName { + Describe 'ConfigMgrCBDsc - DSC_CMAdministrativeUser\Get-TargetResource' -Tag 'Get' { + BeforeAll { + Mock -CommandName Import-ConfigMgrPowerShellModule + Mock -CommandName Set-Location + } + + Context 'When retrieving Collection settings' { + BeforeEach { + $getAdminUser = @{ + SourceSite = 'Lab' + LogonName = 'contoso\User' + RoleNames = 'Security Administrator' + Permissions = @( + @{ + CategoryName = 'Default' + CategoryTypeId = 29 + } + @{ + CategoryName = 'Test' + CategoryTypeId = 29 + } + @{ + CategoryName = 'All Systems' + CategoryTypeId = 1 + } + @{ + CategoryName = 'All Users and User Groups' + CategoryTypeId = 1 + } + ) + } + + $getInput = @{ + SiteCode = 'Lab' + AdminName = 'contoso\User' + } + } + + It 'Should return desired result admin user present' { + Mock -CommandName Get-CMAdministrativeUser -MockWith { $getAdminUser } + + $result = Get-TargetResource @getInput + $result | Should -BeOfType System.Collections.HashTable + $result.SiteCode | Should -Be -ExpectedValue 'Lab' + $result.AdminName | Should -Be -ExpectedValue 'contoso\User' + $result.Roles | Should -Be -ExpectedValue 'Security Administrator' + $result.Scopes | Should -Be -ExpectedValue 'Default','Test' + $result.Collections | Should -Be -ExpectedValue 'All Systems','All Users and User Groups' + $result.Ensure | Should -Be -ExpectedValue 'Present' + } + + It 'Should return desired result admin user absent' { + Mock -CommandName Get-CMAdministrativeUser + + $result = Get-TargetResource @getInput + $result | Should -BeOfType System.Collections.HashTable + $result.SiteCode | Should -Be -ExpectedValue 'Lab' + $result.AdminName | Should -Be -ExpectedValue 'contoso\User' + $result.Roles | Should -Be -ExpectedValue $null + $result.Scopes | Should -Be -ExpectedValue $null + $result.Collections | Should -Be -ExpectedValue $null + $result.Ensure | Should -Be -ExpectedValue 'Absent' + } + } + } + + Describe 'ConfigMgrCBDsc - DSC_CMAdministrativeUser\Set-TargetResource' -Tag 'Set' { + BeforeAll { + $inputRolesDif = @{ + SiteCode = 'Lab' + AdminName = 'contoso\User' + Roles = 'Full Administrator' + } + + $inputScopeDif = @{ + SiteCode = 'Lab' + AdminName = 'contoso\User' + Scopes = 'Test' + } + + $inputCollectionDif = @{ + SiteCode = 'Lab' + AdminName = 'contoso\User' + Collections = 'All Systems','All Servers' + } + + $getReturnPresent = @{ + SiteCode = 'Lab' + AdminName = 'contoso\User' + Roles = 'Security Administrator' + Scopes = 'Default' + Collections = 'All Systems','All Users and User Groups' + Ensure = 'Present' + } + + $getReturnAbsent = @{ + SiteCode = 'Lab' + AdminName = 'contoso\User' + Roles = $null + Scopes = $null + Collections = $null + Ensure = 'Absent' + } + + Mock -CommandName Import-ConfigMgrPowerShellModule + Mock -CommandName Set-Location + Mock -CommandName New-CMAdministrativeUser + Mock -CommandName Add-CMSecurityRoleToAdministrativeUser + Mock -CommandName Remove-CMSecurityRoleFromAdministrativeUser + Mock -CommandName Add-CMSecurityScopeToAdministrativeUser + Mock -CommandName Remove-CMSecurityScopeFromAdministrativeUser + Mock -CommandName Add-CMCollectionToAdministrativeUser + Mock -CommandName Remove-CMCollectionFromAdministrativeUser + Mock -CommandName Remove-CMAdministrativeUser + } + + Context 'When Set-TargetResource runs successfully' { + BeforeEach { + $inputMatch = @{ + SiteCode = 'Lab' + AdminName = 'contoso\User' + Roles = 'Security Administrator' + Scopes = 'Default' + Collections = 'All Systems','All Users and User Groups' + Ensure = 'Present' + } + + $inputAbsent = @{ + SiteCode = 'Lab' + AdminName = 'contoso\User' + Ensure = 'Absent' + } + + Mock -CommandName Get-CMSecurityRole -MockWith { $true } + Mock -CommandName Get-CMSecurityScope -MockWith { $true} + Mock -CommandName Get-CMCollection -MockWith { $true } + } + + It 'Should return desired result when deleting an administrator' { + Mock -CommandName Get-TargetResource -MockWith { $getReturnPresent } + + Set-TargetResource @inputAbsent + Assert-MockCalled Import-ConfigMgrPowerShellModule -Exactly -Times 1 -Scope It + Assert-MockCalled Set-Location -Exactly -Times 2 -Scope It + Assert-MockCalled Get-TargetResource -Exactly -Times 1 -Scope It + Assert-MockCalled New-CMAdministrativeUser -Exactly -Times 0 -Scope It + Assert-MockCalled Get-CMSecurityRole -Exactly -Times 0 -Scope It + Assert-MockCalled Add-CMSecurityRoleToAdministrativeUser -Exactly -Times 0 -Scope It + Assert-MockCalled Remove-CMSecurityRoleFromAdministrativeUser -Exactly -Times 0 -Scope It + Assert-MockCalled Get-CMSecurityScope -Exactly -Times 0 -Scope It + Assert-MockCalled Add-CMSecurityScopeToAdministrativeUser -Exactly -Times 0 -Scope It + Assert-MockCalled Remove-CMSecurityScopeFromAdministrativeUser -Exactly -Times 0 -Scope It + Assert-MockCalled Get-CMCollection -Exactly -Times 0 -Scope It + Assert-MockCalled Add-CMCollectionToAdministrativeUser -Exactly -Times 0 -Scope It + Assert-MockCalled Remove-CMCollectionFromAdministrativeUser -Exactly -Times 0 -Scope It + Assert-MockCalled Remove-CMAdministrativeUser -Exactly -Times 1 -Scope It + } + + It 'Should return desired result when creating a new administrator' { + Mock -CommandName Get-TargetResource -MockWith { $getReturnAbsent } + + Set-TargetResource @inputMatch + Assert-MockCalled Import-ConfigMgrPowerShellModule -Exactly -Times 1 -Scope It + Assert-MockCalled Set-Location -Exactly -Times 2 -Scope It + Assert-MockCalled Get-TargetResource -Exactly -Times 1 -Scope It + Assert-MockCalled New-CMAdministrativeUser -Exactly -Times 1 -Scope It + Assert-MockCalled Get-CMSecurityRole -Exactly -Times 1 -Scope It + Assert-MockCalled Add-CMSecurityRoleToAdministrativeUser -Exactly -Times 0 -Scope It + Assert-MockCalled Remove-CMSecurityRoleFromAdministrativeUser -Exactly -Times 0 -Scope It + Assert-MockCalled Get-CMSecurityScope -Exactly -Times 1 -Scope It + Assert-MockCalled Add-CMSecurityScopeToAdministrativeUser -Exactly -Times 0 -Scope It + Assert-MockCalled Remove-CMSecurityScopeFromAdministrativeUser -Exactly -Times 0 -Scope It + Assert-MockCalled Get-CMCollection -Exactly -Times 2 -Scope It + Assert-MockCalled Add-CMCollectionToAdministrativeUser -Exactly -Times 0 -Scope It + Assert-MockCalled Remove-CMCollectionFromAdministrativeUser -Exactly -Times 0 -Scope It + Assert-MockCalled Remove-CMAdministrativeUser -Exactly -Times 0 -Scope It + } + + It 'Should return desired result when changing roles for the administrator' { + Mock -CommandName Get-TargetResource -MockWith { $getReturnPresent } + + Set-TargetResource @inputRolesDif + Assert-MockCalled Import-ConfigMgrPowerShellModule -Exactly -Times 1 -Scope It + Assert-MockCalled Set-Location -Exactly -Times 2 -Scope It + Assert-MockCalled Get-TargetResource -Exactly -Times 1 -Scope It + Assert-MockCalled New-CMAdministrativeUser -Exactly -Times 0 -Scope It + Assert-MockCalled Get-CMSecurityRole -Exactly -Times 1 -Scope It + Assert-MockCalled Add-CMSecurityRoleToAdministrativeUser -Exactly -Times 1 -Scope It + Assert-MockCalled Remove-CMSecurityRoleFromAdministrativeUser -Exactly -Times 1 -Scope It + Assert-MockCalled Get-CMSecurityScope -Exactly -Times 0 -Scope It + Assert-MockCalled Add-CMSecurityScopeToAdministrativeUser -Exactly -Times 0 -Scope It + Assert-MockCalled Remove-CMSecurityScopeFromAdministrativeUser -Exactly -Times 0 -Scope It + Assert-MockCalled Get-CMCollection -Exactly -Times 0 -Scope It + Assert-MockCalled Add-CMCollectionToAdministrativeUser -Exactly -Times 0 -Scope It + Assert-MockCalled Remove-CMCollectionFromAdministrativeUser -Exactly -Times 0 -Scope It + Assert-MockCalled Remove-CMAdministrativeUser -Exactly -Times 0 -Scope It + } + + It 'Should return desired result when changing scopes for the administrator' { + Mock -CommandName Get-TargetResource -MockWith { $getReturnPresent } + + Set-TargetResource @inputScopeDif + Assert-MockCalled Import-ConfigMgrPowerShellModule -Exactly -Times 1 -Scope It + Assert-MockCalled Set-Location -Exactly -Times 2 -Scope It + Assert-MockCalled Get-TargetResource -Exactly -Times 1 -Scope It + Assert-MockCalled New-CMAdministrativeUser -Exactly -Times 0 -Scope It + Assert-MockCalled Get-CMSecurityRole -Exactly -Times 0 -Scope It + Assert-MockCalled Add-CMSecurityRoleToAdministrativeUser -Exactly -Times 0 -Scope It + Assert-MockCalled Remove-CMSecurityRoleFromAdministrativeUser -Exactly -Times 0 -Scope It + Assert-MockCalled Get-CMSecurityScope -Exactly -Times 1 -Scope It + Assert-MockCalled Add-CMSecurityScopeToAdministrativeUser -Exactly -Times 1 -Scope It + Assert-MockCalled Remove-CMSecurityScopeFromAdministrativeUser -Exactly -Times 1 -Scope It + Assert-MockCalled Get-CMCollection -Exactly -Times 0 -Scope It + Assert-MockCalled Add-CMCollectionToAdministrativeUser -Exactly -Times 0 -Scope It + Assert-MockCalled Remove-CMCollectionFromAdministrativeUser -Exactly -Times 0 -Scope It + Assert-MockCalled Remove-CMAdministrativeUser -Exactly -Times 0 -Scope It + } + + It 'Should return desired result when changing collections for the administrator' { + Mock -CommandName Get-TargetResource -MockWith { $getReturnPresent } + + Set-TargetResource @inputCollectionDif + Assert-MockCalled Import-ConfigMgrPowerShellModule -Exactly -Times 1 -Scope It + Assert-MockCalled Set-Location -Exactly -Times 2 -Scope It + Assert-MockCalled Get-TargetResource -Exactly -Times 1 -Scope It + Assert-MockCalled New-CMAdministrativeUser -Exactly -Times 0 -Scope It + Assert-MockCalled Get-CMSecurityRole -Exactly -Times 0 -Scope It + Assert-MockCalled Add-CMSecurityRoleToAdministrativeUser -Exactly -Times 0 -Scope It + Assert-MockCalled Remove-CMSecurityRoleFromAdministrativeUser -Exactly -Times 0 -Scope It + Assert-MockCalled Get-CMSecurityScope -Exactly -Times 0 -Scope It + Assert-MockCalled Add-CMSecurityScopeToAdministrativeUser -Exactly -Times 0 -Scope It + Assert-MockCalled Remove-CMSecurityScopeFromAdministrativeUser -Exactly -Times 0 -Scope It + Assert-MockCalled Get-CMCollection -Exactly -Times 1 -Scope It + Assert-MockCalled Add-CMCollectionToAdministrativeUser -Exactly -Times 1 -Scope It + Assert-MockCalled Remove-CMCollectionFromAdministrativeUser -Exactly -Times 1 -Scope It + Assert-MockCalled Remove-CMAdministrativeUser -Exactly -Times 0 -Scope It + } + } + + Context 'When Set-TargetResource throws' { + BeforeEach { + $getReturnPresentAll = @{ + SiteCode = 'Lab' + AdminName = 'contoso\User' + Roles = 'Security Administrator' + Scopes = 'All' + Collections = 'All Systems','All Users and User Groups' + Ensure = 'Present' + } + + $newUserNoRole = @{ + SiteCode = 'Lab' + AdminName = 'contoso\test' + Ensure = 'Present' + } + + $inputChangeAllScope = @{ + SiteCode = 'Lab' + AdminName = 'contoso\User' + ScopesToInclude = 'All' + } + + $inputRemoveAllScope = @{ + SiteCode = 'Lab' + AdminName = 'contoso\User' + ScopesToExclude = 'All' + } + + $modifyAllScope = 'Unable to modify scope with Desired State Configuration as it is currently set to All' + $newUserNoRoleMsg = 'When administrative user does not exist, at least 1 valid role must be specified.' + $addAllScopes = 'Unable to add the All scopes setting via Desired State Configuration, All can be used for new account only.' + $removeAllScopes = 'Unable to remove the All scope via Desired State Configuration.' + + Mock -CommandName Get-CMSecurityRole + Mock -CommandName Get-CMSecurityScope + Mock -CommandName Get-CMCollection + } + + It 'Should return throw when no valid security role is specified with a new user' { + Mock -CommandName Get-TargetResource -MockWith { $getReturnAbsent } + + { Set-TargetResource @newUserNoRole } | Should -Throw -ExpectedMessage $newUserNoRoleMsg + Assert-MockCalled Import-ConfigMgrPowerShellModule -Exactly -Times 1 -Scope It + Assert-MockCalled Set-Location -Exactly -Times 2 -Scope It + Assert-MockCalled Get-TargetResource -Exactly -Times 1 -Scope It + Assert-MockCalled New-CMAdministrativeUser -Exactly -Times 0 -Scope It + Assert-MockCalled Get-CMSecurityRole -Exactly -Times 0 -Scope It + Assert-MockCalled Add-CMSecurityRoleToAdministrativeUser -Exactly -Times 0 -Scope It + Assert-MockCalled Remove-CMSecurityRoleFromAdministrativeUser -Exactly -Times 0 -Scope It + Assert-MockCalled Get-CMSecurityScope -Exactly -Times 0 -Scope It + Assert-MockCalled Add-CMSecurityScopeToAdministrativeUser -Exactly -Times 0 -Scope It + Assert-MockCalled Remove-CMSecurityScopeFromAdministrativeUser -Exactly -Times 0 -Scope It + Assert-MockCalled Get-CMCollection -Exactly -Times 0 -Scope It + Assert-MockCalled Add-CMCollectionToAdministrativeUser -Exactly -Times 0 -Scope It + Assert-MockCalled Remove-CMCollectionFromAdministrativeUser -Exactly -Times 0 -Scope It + Assert-MockCalled Remove-CMAdministrativeUser -Exactly -Times 0 -Scope It + } + + It 'Should return throw when the security role does not exist' { + Mock -CommandName Get-TargetResource -MockWith { $getReturnPresent } + + { Set-TargetResource @inputRolesDif } | Should -Throw + Assert-MockCalled Import-ConfigMgrPowerShellModule -Exactly -Times 1 -Scope It + Assert-MockCalled Set-Location -Exactly -Times 2 -Scope It + Assert-MockCalled Get-TargetResource -Exactly -Times 1 -Scope It + Assert-MockCalled New-CMAdministrativeUser -Exactly -Times 0 -Scope It + Assert-MockCalled Get-CMSecurityRole -Exactly -Times 1 -Scope It + Assert-MockCalled Add-CMSecurityRoleToAdministrativeUser -Exactly -Times 0 -Scope It + Assert-MockCalled Remove-CMSecurityRoleFromAdministrativeUser -Exactly -Times 1 -Scope It + Assert-MockCalled Get-CMSecurityScope -Exactly -Times 0 -Scope It + Assert-MockCalled Add-CMSecurityScopeToAdministrativeUser -Exactly -Times 0 -Scope It + Assert-MockCalled Remove-CMSecurityScopeFromAdministrativeUser -Exactly -Times 0 -Scope It + Assert-MockCalled Get-CMCollection -Exactly -Times 0 -Scope It + Assert-MockCalled Add-CMCollectionToAdministrativeUser -Exactly -Times 0 -Scope It + Assert-MockCalled Remove-CMCollectionFromAdministrativeUser -Exactly -Times 0 -Scope It + Assert-MockCalled Remove-CMAdministrativeUser -Exactly -Times 0 -Scope It + } + + It 'Should return throw when scope does not exist' { + Mock -CommandName Get-TargetResource -MockWith { $getReturnPresent } + + { Set-TargetResource @inputScopeDif } | Should -Throw + Assert-MockCalled Import-ConfigMgrPowerShellModule -Exactly -Times 1 -Scope It + Assert-MockCalled Set-Location -Exactly -Times 2 -Scope It + Assert-MockCalled Get-TargetResource -Exactly -Times 1 -Scope It + Assert-MockCalled New-CMAdministrativeUser -Exactly -Times 0 -Scope It + Assert-MockCalled Get-CMSecurityRole -Exactly -Times 0 -Scope It + Assert-MockCalled Add-CMSecurityRoleToAdministrativeUser -Exactly -Times 0 -Scope It + Assert-MockCalled Remove-CMSecurityRoleFromAdministrativeUser -Exactly -Times 0 -Scope It + Assert-MockCalled Get-CMSecurityScope -Exactly -Times 1 -Scope It + Assert-MockCalled Add-CMSecurityScopeToAdministrativeUser -Exactly -Times 0 -Scope It + Assert-MockCalled Remove-CMSecurityScopeFromAdministrativeUser -Exactly -Times 1 -Scope It + Assert-MockCalled Get-CMCollection -Exactly -Times 0 -Scope It + Assert-MockCalled Add-CMCollectionToAdministrativeUser -Exactly -Times 0 -Scope It + Assert-MockCalled Remove-CMCollectionFromAdministrativeUser -Exactly -Times 0 -Scope It + Assert-MockCalled Remove-CMAdministrativeUser -Exactly -Times 0 -Scope It + } + + It 'Should throw when collection does not exist' { + Mock -CommandName Get-TargetResource -MockWith { $getReturnPresent } + + { Set-TargetResource @inputCollectionDif } | Should -Throw + Assert-MockCalled Import-ConfigMgrPowerShellModule -Exactly -Times 1 -Scope It + Assert-MockCalled Set-Location -Exactly -Times 2 -Scope It + Assert-MockCalled Get-TargetResource -Exactly -Times 1 -Scope It + Assert-MockCalled New-CMAdministrativeUser -Exactly -Times 0 -Scope It + Assert-MockCalled Get-CMSecurityRole -Exactly -Times 0 -Scope It + Assert-MockCalled Add-CMSecurityRoleToAdministrativeUser -Exactly -Times 0 -Scope It + Assert-MockCalled Remove-CMSecurityRoleFromAdministrativeUser -Exactly -Times 0 -Scope It + Assert-MockCalled Get-CMSecurityScope -Exactly -Times 0 -Scope It + Assert-MockCalled Add-CMSecurityScopeToAdministrativeUser -Exactly -Times 0 -Scope It + Assert-MockCalled Remove-CMSecurityScopeFromAdministrativeUser -Exactly -Times 0 -Scope It + Assert-MockCalled Get-CMCollection -Exactly -Times 1 -Scope It + Assert-MockCalled Add-CMCollectionToAdministrativeUser -Exactly -Times 0 -Scope It + Assert-MockCalled Remove-CMCollectionFromAdministrativeUser -Exactly -Times 1 -Scope It + Assert-MockCalled Remove-CMAdministrativeUser -Exactly -Times 0 -Scope It + } + + It 'Should throw when trying to change the All Scope' { + Mock -CommandName Get-CMSecurityScope -MockWith { $true } + Mock -CommandName Get-TargetResource -MockWith { $getReturnPresentAll } + + { Set-TargetResource @inputScopeDif } | Should -Throw -ExpectedMessage $modifyAllScope + Assert-MockCalled Import-ConfigMgrPowerShellModule -Exactly -Times 1 -Scope It + Assert-MockCalled Set-Location -Exactly -Times 2 -Scope It + Assert-MockCalled Get-TargetResource -Exactly -Times 1 -Scope It + Assert-MockCalled New-CMAdministrativeUser -Exactly -Times 0 -Scope It + Assert-MockCalled Get-CMSecurityRole -Exactly -Times 0 -Scope It + Assert-MockCalled Add-CMSecurityRoleToAdministrativeUser -Exactly -Times 0 -Scope It + Assert-MockCalled Remove-CMSecurityRoleFromAdministrativeUser -Exactly -Times 0 -Scope It + Assert-MockCalled Get-CMSecurityScope -Exactly -Times 1 -Scope It + Assert-MockCalled Add-CMSecurityScopeToAdministrativeUser -Exactly -Times 0 -Scope It + Assert-MockCalled Remove-CMSecurityScopeFromAdministrativeUser -Exactly -Times 0 -Scope It + Assert-MockCalled Get-CMCollection -Exactly -Times 0 -Scope It + Assert-MockCalled Add-CMCollectionToAdministrativeUser -Exactly -Times 0 -Scope It + Assert-MockCalled Remove-CMCollectionFromAdministrativeUser -Exactly -Times 0 -Scope It + Assert-MockCalled Remove-CMAdministrativeUser -Exactly -Times 0 -Scope It + } + + It 'Should throw when trying to remove the All Scope' { + Mock -CommandName Get-CMSecurityScope -MockWith { $true } + Mock -CommandName Get-TargetResource -MockWith { $getReturnPresentAll } + + { Set-TargetResource @inputRemoveAllScope } | Should -Throw -ExpectedMessage $removeAllScopes + Assert-MockCalled Import-ConfigMgrPowerShellModule -Exactly -Times 1 -Scope It + Assert-MockCalled Set-Location -Exactly -Times 2 -Scope It + Assert-MockCalled Get-TargetResource -Exactly -Times 1 -Scope It + Assert-MockCalled New-CMAdministrativeUser -Exactly -Times 0 -Scope It + Assert-MockCalled Get-CMSecurityRole -Exactly -Times 0 -Scope It + Assert-MockCalled Add-CMSecurityRoleToAdministrativeUser -Exactly -Times 0 -Scope It + Assert-MockCalled Remove-CMSecurityRoleFromAdministrativeUser -Exactly -Times 0 -Scope It + Assert-MockCalled Get-CMSecurityScope -Exactly -Times 0 -Scope It + Assert-MockCalled Add-CMSecurityScopeToAdministrativeUser -Exactly -Times 0 -Scope It + Assert-MockCalled Remove-CMSecurityScopeFromAdministrativeUser -Exactly -Times 0 -Scope It + Assert-MockCalled Get-CMCollection -Exactly -Times 0 -Scope It + Assert-MockCalled Add-CMCollectionToAdministrativeUser -Exactly -Times 0 -Scope It + Assert-MockCalled Remove-CMCollectionFromAdministrativeUser -Exactly -Times 0 -Scope It + Assert-MockCalled Remove-CMAdministrativeUser -Exactly -Times 0 -Scope It + } + + It 'Should throw when trying to add the All Scope' { + Mock -CommandName Get-CMSecurityScope -MockWith { $true } + Mock -CommandName Get-TargetResource -MockWith { $getReturnPresent } + + { Set-TargetResource @inputChangeAllScope } | Should -Throw -ExpectedMessage $addAllScopes + Assert-MockCalled Import-ConfigMgrPowerShellModule -Exactly -Times 1 -Scope It + Assert-MockCalled Set-Location -Exactly -Times 2 -Scope It + Assert-MockCalled Get-TargetResource -Exactly -Times 1 -Scope It + Assert-MockCalled New-CMAdministrativeUser -Exactly -Times 0 -Scope It + Assert-MockCalled Get-CMSecurityRole -Exactly -Times 0 -Scope It + Assert-MockCalled Add-CMSecurityRoleToAdministrativeUser -Exactly -Times 0 -Scope It + Assert-MockCalled Remove-CMSecurityRoleFromAdministrativeUser -Exactly -Times 0 -Scope It + Assert-MockCalled Get-CMSecurityScope -Exactly -Times 1 -Scope It + Assert-MockCalled Add-CMSecurityScopeToAdministrativeUser -Exactly -Times 0 -Scope It + Assert-MockCalled Remove-CMSecurityScopeFromAdministrativeUser -Exactly -Times 0 -Scope It + Assert-MockCalled Get-CMCollection -Exactly -Times 0 -Scope It + Assert-MockCalled Add-CMCollectionToAdministrativeUser -Exactly -Times 0 -Scope It + Assert-MockCalled Remove-CMCollectionFromAdministrativeUser -Exactly -Times 0 -Scope It + Assert-MockCalled Remove-CMAdministrativeUser -Exactly -Times 0 -Scope It + } + } + } + + Describe 'ConfigMgrCBDsc - DSC_CMPullDistributionPoint\Test-TargetResource' -Tag 'Test' { + BeforeAll { + $getReturnPresent = @{ + SiteCode = 'Lab' + AdminName = 'contoso\User' + Roles = 'Security Administrator' + Scopes = 'Default' + Collections = 'All Systems','All Users and User Groups' + Ensure = 'Present' + } + + $getReturnPresentAll = @{ + SiteCode = 'Lab' + AdminName = 'contoso\User' + Roles = 'Security Administrator' + Scopes = 'All' + Collections = 'All Systems','All Users and User Groups' + Ensure = 'Present' + } + + $getReturnAbsent = @{ + SiteCode = 'Lab' + AdminName = 'contoso\User' + Roles = $null + Scopes = $null + Collections = $null + Ensure = 'Absent' + } + + $inputAbsent = @{ + SiteCode = 'Lab' + AdminName = 'contoso\User' + Ensure = 'Absent' + } + + Mock -CommandName Set-Location + Mock -CommandName Import-ConfigMgrPowerShellModule + } + + Context 'When running Test-TargetResource when get returns present' { + BeforeEach { + $inputMatch = @{ + SiteCode = 'Lab' + AdminName = 'contoso\User' + Roles = 'Security Administrator' + Scopes = 'Default' + Collections = 'All Systems','All Users and User Groups' + Ensure = 'Present' + } + + $inputAllOptions = @{ + SiteCode = 'Lab' + AdminName = 'contoso\User' + Roles = 'Security Administrator' + RolesToInclude = 'Full Administrator' + RolesToExclude = 'Default Administrator' + Scopes = 'Default' + ScopesToInclude = 'Test' + ScopesToExclude = 'Scope1' + Collections = 'All Systems','All Users and User Groups' + CollectionsToInclude = 'All Servers' + CollectionsToExclude = 'All Workstations' + Ensure = 'Present' + } + + $inputRolesDif = @{ + SiteCode = 'Lab' + AdminName = 'contoso\User' + Roles = 'Full Administrator' + } + + $inputScopeDif = @{ + SiteCode = 'Lab' + AdminName = 'contoso\User' + Scopes = 'Test' + } + + $inputCollectionDif = @{ + SiteCode = 'Lab' + AdminName = 'contoso\User' + Collections = 'All Systems','All Servers' + } + + $inputChangeAllScope = @{ + SiteCode = 'Lab' + AdminName = 'contoso\User' + ScopesToInclude = 'All' + } + + Mock -CommandName Get-TargetResource -MockWith { $getReturnPresent } + } + + It 'Should return desired result true settings match' { + Test-TargetResource @inputMatch | Should -Be $true + } + + It 'Should return desired result false when roles do not match' { + Test-TargetResource @inputRolesDif | Should -Be $false + } + + It 'Should return desired result false when scopes do not match' { + Test-TargetResource @inputScopeDif | Should -Be $false + } + + It 'Should return desired result false when collections do not match' { + Test-TargetResource @inputCollectionDif | Should -Be $false + } + + It 'Should return desired result tue when specifying the match parameter but the include and exclude do not match' { + Test-TargetResource @inputAllOptions | Should -Be $true + } + + It 'Should return desired result false when present and expected absent' { + Test-TargetResource @inputAbsent | Should -Be $false + } + + It 'Should return desired result false when trying to add All Scope with warning' { + Test-TargetResource @inputChangeAllScope | Should -Be $false + } + } + + Context 'When running Test-TargetResource when get returns absent' { + BeforeEach { + $inputNewUser = @{ + SiteCode = 'Lab' + AdminName = 'contoso\User' + Roles = 'Security Administrator' + Scopes = 'All' + Collections = 'All Systems','All Users and User Groups' + Ensure = 'Present' + } + + $inputNewUserNoRolesInvalid = @{ + SiteCode = 'Lab' + AdminName = 'contoso\User' + Ensure = 'Present' + } + + Mock -CommandName Get-TargetResource -MockWith { $getReturnAbsent } + } + + It 'Should return desired result true when absent and expected absent' { + Test-TargetResource @inputAbsent | Should -Be $true + } + + It 'Should return desired result false when absent and expected present' { + Test-TargetResource @inputNewUser | Should -Be $false + } + + It 'Should return desired result false when absent and not specifying a role' { + Test-TargetResource @inputNewUserNoRolesInvalid | Should -Be $false + } + } + + Context 'All Setting results' { + BeforeEach { + $inputRemoveAll = @{ + SiteCode = 'Lab' + AdminName = 'contoso\User' + ScopesToInclude = 'Default' + ScopesToExclude = 'All' + } + + Mock -CommandName Get-TargetResource -MockWith { $getReturnPresentAll } + } + + It 'Should return desired result false when all scope present and removing the scope' { + Test-TargetResource @inputRemoveAll | Should -Be $false + } + } + } + } +} +finally +{ + Invoke-TestCleanup +} diff --git a/tests/Unit/ConfigMgrCBDsc.ResourceHelper.tests.ps1 b/tests/Unit/ConfigMgrCBDsc.ResourceHelper.tests.ps1 index ccb1792..14604f8 100644 --- a/tests/Unit/ConfigMgrCBDsc.ResourceHelper.tests.ps1 +++ b/tests/Unit/ConfigMgrCBDsc.ResourceHelper.tests.ps1 @@ -483,4 +483,90 @@ InModuleScope $script:subModuleName { } } } + + Describe "$moduleResourceName\Compare-MultipleCompares" { + BeforeAll { + $inputParamMatch = @{ + CurrentState = 'Device1','Device2' + Match = 'Device2','Device3' + Include = $null + Exclude = $null + } + + $inputParamCurrentNull = @{ + CurrentState = $null + Match = 'Device2','Device3' + Include = $null + Exclude = $null + } + + $inputParamInclude = @{ + CurrentState = 'Device1','Device2' + Match = $null + Include = 'Device2','Device3' + Exclude = $null + } + + $inputParamExclude = @{ + CurrentState = 'Device1','Device2' + Match = $null + Include = $null + Exclude = 'Device2','Device3' + } + + $inputParamExcludeInclude = @{ + CurrentState = 'Device1','Device2' + Match = $null + Include = 'Device4' + Exclude = 'Device2','Device3' + } + } + + Context 'When return is as expected' { + It 'Should return desired result for desired results with match' { + $result = Compare-MultipleCompares @inputParamMatch + $result | Should -BeOfType System.Collections.HashTable + $result.Type | Should -Be -ExpectedValue 'Match' + $result.Missing | Should -Be -ExpectedValue 'Device3' + $result.Remove | Should -Be -ExpectedValue 'Device1' + $result.CurrentState | Should -Be -ExpectedValue 'Device1','Device2' + } + + It 'Should return desired result for desired missing settings with match when current state is null' { + $result = Compare-MultipleCompares @inputParamCurrentNull + $result | Should -BeOfType System.Collections.HashTable + $result.Type | Should -Be -ExpectedValue 'Match' + $result.Missing | Should -Be -ExpectedValue 'Device2','Device3' + $result.Remove | Should -Be -ExpectedValue $null + $result.CurrentState | Should -Be -ExpectedValue $null + } + + It 'Should return desired result for desired missing settings with include' { + $result = Compare-MultipleCompares @inputParamInclude + $result | Should -BeOfType System.Collections.HashTable + $result.Type | Should -Be -ExpectedValue 'Include' + $result.Missing | Should -Be -ExpectedValue 'Device3' + $result.Remove | Should -Be -ExpectedValue $null + $result.CurrentState | Should -Be -ExpectedValue 'Device1','Device2' + } + + It 'Should return desired result for desired exclude settings with exclude' { + $result = Compare-MultipleCompares @inputParamExclude + $result | Should -BeOfType System.Collections.HashTable + $result.Type | Should -Be -ExpectedValue 'Exclude' + $result.Missing | Should -Be -ExpectedValue $null + $result.Remove | Should -Be -ExpectedValue 'Device2' + $result.CurrentState | Should -Be -ExpectedValue 'Device1','Device2' + } + + It 'Should return desired result for desired exclude settings with include and exclude' { + $result = Compare-MultipleCompares @inputParamExcludeInclude + $result | Should -BeOfType System.Collections.HashTable + $result.Type | Should -Be -ExpectedValue 'Include, Exclude' + $result.Missing | Should -Be -ExpectedValue 'Device4' + $result.Remove | Should -Be -ExpectedValue 'Device2' + $result.CurrentState | Should -Be -ExpectedValue 'Device1','Device2' + } + } + } } From ff1468dd5863316f949db6e41b0ba05c8a5b1e6c Mon Sep 17 00:00:00 2001 From: Easyreturns <20781445+jeffotterpohl@users.noreply.github.com> Date: Mon, 29 Jun 2020 06:37:50 -0500 Subject: [PATCH 2/9] Updated Readme --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index b4e5127..56162c6 100644 --- a/README.md +++ b/README.md @@ -91,7 +91,7 @@ Please check out common DSC Community [contributing guidelines](https://dsccommu - **CMPullDistributionPoint**: Provides a resource for modifying a distribution point and making the distribution point a Pull Distribution Point. - **CMSiteMaintenance**: Provides a resource for modifying the Site Maintenance tasks. -- **DSC_CMAdministrativeUser**: Provides a resource for adding, removing, and configuring +- **CMAdministrativeUser**: Provides a resource for adding, removing, and configuring administrative users. ### xSccmPreReqs From 1ddb3df23c6f17218c823e88557729d3886f293f Mon Sep 17 00:00:00 2001 From: Easyreturns <20781445+jeffotterpohl@users.noreply.github.com> Date: Mon, 29 Jun 2020 07:45:49 -0500 Subject: [PATCH 3/9] Version PSDepend --- RequiredModules.psd1 | 1 + 1 file changed, 1 insertion(+) diff --git a/RequiredModules.psd1 b/RequiredModules.psd1 index 3e0a3f0..90c7a2e 100644 --- a/RequiredModules.psd1 +++ b/RequiredModules.psd1 @@ -19,6 +19,7 @@ 'DscResource.AnalyzerRules' = 'latest' xDscResourceDesigner = 'latest' 'DscResource.DocGenerator' = 'latest' + PSDepend = '0.3.2' # Build dependent modules 'DscResource.Common' = 'latest' From e7e8f4073f422c37b98522a704b31c0f79d55edc Mon Sep 17 00:00:00 2001 From: Easyreturns <20781445+jeffotterpohl@users.noreply.github.com> Date: Mon, 29 Jun 2020 07:57:37 -0500 Subject: [PATCH 4/9] Removed Required Module --- RequiredModules.psd1 | 1 - 1 file changed, 1 deletion(-) diff --git a/RequiredModules.psd1 b/RequiredModules.psd1 index 90c7a2e..3e0a3f0 100644 --- a/RequiredModules.psd1 +++ b/RequiredModules.psd1 @@ -19,7 +19,6 @@ 'DscResource.AnalyzerRules' = 'latest' xDscResourceDesigner = 'latest' 'DscResource.DocGenerator' = 'latest' - PSDepend = '0.3.2' # Build dependent modules 'DscResource.Common' = 'latest' From 526ffe8649b4c0e77d7b01b18cd134508b833402 Mon Sep 17 00:00:00 2001 From: Easyreturns <20781445+jeffotterpohl@users.noreply.github.com> Date: Mon, 29 Jun 2020 10:02:54 -0500 Subject: [PATCH 5/9] Uupdated ConfigMgrCBDsc.psd1 --- source/ConfigMgrCBDsc.psd1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/ConfigMgrCBDsc.psd1 b/source/ConfigMgrCBDsc.psd1 index 987610d..caa6157 100644 --- a/source/ConfigMgrCBDsc.psd1 +++ b/source/ConfigMgrCBDsc.psd1 @@ -83,7 +83,7 @@ 'SccmSqlSetup','SCCMInstall','CMIniFile','Collections','Boundaries','ForestDiscovery','ClientStatusSettings','BoundaryGroups', 'ManagementPoint','AssetIntelligencePoint','FallbackStatusPoint','SoftwareUpdatePoint','DistrubtionPoint','HeartbeatDiscovery', 'ServiceConnectionPoint','NetworkDiscovery','ReportingServicePoint','SystemDiscovery','PXEDistributionPoint','PullDistributionPoint', - 'SiteMaintenance','DSC_CMAdministrativeUser') + 'SiteMaintenance','AdministrativeUser') # A URL to the license for this module. LicenseUri = 'https://github.com/dsccommunity/ConfigMgrCBDsc/blob/master/LICENSE' From 17e45057c8cd1d3e779a5e3d9f9fc5b336da17a3 Mon Sep 17 00:00:00 2001 From: Easyreturns <20781445+jeffotterpohl@users.noreply.github.com> Date: Wed, 1 Jul 2020 07:30:15 -0500 Subject: [PATCH 6/9] Updates per PR Comments --- .../DSC_CMAdministrativeUser.psm1 | 85 ++++++++++++++- .../DSC_CMAdministrativeUser.strings.psd1 | 3 + tests/Unit/CMAdministrativeUser.tests.ps1 | 102 ++++++++++++++++++ 3 files changed, 185 insertions(+), 5 deletions(-) diff --git a/source/DSCResources/DSC_CMAdministrativeUser/DSC_CMAdministrativeUser.psm1 b/source/DSCResources/DSC_CMAdministrativeUser/DSC_CMAdministrativeUser.psm1 index b36abf8..fa58adb 100644 --- a/source/DSCResources/DSC_CMAdministrativeUser/DSC_CMAdministrativeUser.psm1 +++ b/source/DSCResources/DSC_CMAdministrativeUser/DSC_CMAdministrativeUser.psm1 @@ -46,11 +46,17 @@ function Get-TargetResource { if ($item.CategoryTypeID -eq 29) { - $scope += $item.CategoryName + if ($scope -notcontains $item.CategoryName) + { + $scope += $item.CategoryName + } } elseif ($item.CategoryTypeId -eq 1) { - $collections += $item.CategoryName + if ($collections -notcontains $item.CategoryName) + { + $collections += $item.CategoryName + } } } @@ -176,6 +182,17 @@ function Set-TargetResource { if ($Roles -or $RolesToInclude -or $RolesToExclude) { + if ($RolesToInclude -and $RolesToExclude) + { + foreach ($item in $RolesToInclude) + { + if ($RolesToExclude -contains $item) + { + throw ($script:localizedData.RolesInEx -f $item) + } + } + } + $rolesArray = @{ Match = $Roles Include = $RolesToInclude @@ -191,7 +208,7 @@ function Set-TargetResource { if (Get-CMSecurityRole -Name $roleCheck) { - $rolesAdd += $roleCheck + [array]$rolesAdd += $roleCheck } else { @@ -203,6 +220,17 @@ function Set-TargetResource if ($Scopes -or $ScopesToInclude -or $ScopesToExclude) { + if ($ScopesToInclude -and $ScopesToExclude) + { + foreach ($item in $ScopesToInclude) + { + if ($ScopesToExclude -contains $item) + { + throw ($script:localizedData.ScopesInEx -f $item) + } + } + } + $scopesArray = @{ Match = $Scopes Include = $ScopesToInclude @@ -218,7 +246,7 @@ function Set-TargetResource { if (Get-CMSecurityScope -Name $scopeCheck) { - $scopesAdd += $scopeCheck + [array]$scopesAdd += $scopeCheck } else { @@ -230,6 +258,17 @@ function Set-TargetResource if ($Collections -or $CollectionsToInclude -or $CollectionsToExclude) { + if ($CollectionsToInclude -and $CollectionsToExclude) + { + foreach ($item in $CollectionsToInclude) + { + if ($CollectionsToExclude -contains $item) + { + throw ($script:localizedData.CollectionsInEx -f $item) + } + } + } + $collectionsArray = @{ Match = $Collections Include = $CollectionsToInclude @@ -245,7 +284,7 @@ function Set-TargetResource { if (Get-CMCollection -Name $collectionCheck) { - $collectionsAdd += $collectionCheck + [array]$collectionsAdd += $collectionCheck } else { @@ -503,6 +542,18 @@ function Test-TargetResource { if ($Roles -or $RolesToInclude -or $RolesToExclude) { + if ($RolesToInclude -and $RolesToExclude) + { + foreach ($item in $RolesToInclude) + { + if ($RolesToExclude -contains $item) + { + Write-Warning -Message ($script:localizedData.RolesInEx -f $item) + $result = $false + } + } + } + $rolesArray = @{ Match = $Roles Include = $RolesToInclude @@ -535,6 +586,18 @@ function Test-TargetResource if ($Scopes -or $ScopesToInclude -or $ScopesToExclude) { + if ($ScopesToInclude -and $ScopesToExclude) + { + foreach ($item in $ScopesToInclude) + { + if ($ScopesToExclude -contains $item) + { + Write-Warning -Message ($script:localizedData.ScopesInEx -f $item) + $result = $false + } + } + } + $scopesArray = @{ Match = $Scopes Include = $ScopesToInclude @@ -582,6 +645,18 @@ function Test-TargetResource if ($Collections -or $CollectionsToInclude -or $CollectionsToExclude) { + if ($CollectionsToInclude -and $CollectionsToExclude) + { + foreach ($item in $CollectionsToInclude) + { + if ($CollectionsToExclude -contains $item) + { + Write-Warning -Message ($script:localizedData.CollectionsInEx -f $item) + $result = $false + } + } + } + $collectionsArray = @{ Match = $Collections Include = $CollectionsToInclude diff --git a/source/DSCResources/DSC_CMAdministrativeUser/en-US/DSC_CMAdministrativeUser.strings.psd1 b/source/DSCResources/DSC_CMAdministrativeUser/en-US/DSC_CMAdministrativeUser.strings.psd1 index 8c35c23..98b2156 100644 --- a/source/DSCResources/DSC_CMAdministrativeUser/en-US/DSC_CMAdministrativeUser.strings.psd1 +++ b/source/DSCResources/DSC_CMAdministrativeUser/en-US/DSC_CMAdministrativeUser.strings.psd1 @@ -17,4 +17,7 @@ ConvertFrom-StringData @' AllParam = Unable to add the All scopes setting via Desired State Configuration, All can be used for new account only. RemoveAll = Unable to remove the All scope via Desired State Configuration. ModifyAll = Unable to modify scope with Desired State Configuration as it is currently set to All. + RolesInEx = RolesToExclude and RolesToInclude contain to same entry {0}, remove from one of the arrays. + ScopesInEx = ScopesToExclude and ScopesToInclude contain to same entry {0}, remove from one of the arrays. + CollectionsInEx = CollectionsToExclude and CollectionsToInclude contain to same {0}, remove from one of the arrays. '@ diff --git a/tests/Unit/CMAdministrativeUser.tests.ps1 b/tests/Unit/CMAdministrativeUser.tests.ps1 index cc83327..3c6f3d2 100644 --- a/tests/Unit/CMAdministrativeUser.tests.ps1 +++ b/tests/Unit/CMAdministrativeUser.tests.ps1 @@ -305,10 +305,34 @@ try ScopesToExclude = 'All' } + $dupRoles = @{ + SiteCode = 'Lab' + AdminName = 'contoso\User' + RolesToInclude = 'Full Administrator' + RolesToExclude = 'Full Administrator' + } + + $dupScopes = @{ + SiteCode = 'Lab' + AdminName = 'contoso\User' + ScopesToInclude = 'Default' + ScopesToExclude = 'Default' + } + + $dupCollections = @{ + SiteCode = 'Lab' + AdminName = 'contoso\User' + CollectionsToInclude = 'Test1' + CollectionsToExclude = 'Test1' + } + $modifyAllScope = 'Unable to modify scope with Desired State Configuration as it is currently set to All' $newUserNoRoleMsg = 'When administrative user does not exist, at least 1 valid role must be specified.' $addAllScopes = 'Unable to add the All scopes setting via Desired State Configuration, All can be used for new account only.' $removeAllScopes = 'Unable to remove the All scope via Desired State Configuration.' + $rolesInEx = 'RolesToExclude and RolesToInclude contain to same entry Full Administrator, remove from one of the arrays.' + $scopesInEx = 'ScopesToExclude and ScopesToInclude contain to same entry Default, remove from one of the arrays.' + $collInEx = 'CollectionsToExclude and CollectionsToInclude contain to same Test1, remove from one of the arrays.' Mock -CommandName Get-CMSecurityRole Mock -CommandName Get-CMSecurityScope @@ -457,6 +481,69 @@ try Assert-MockCalled Remove-CMCollectionFromAdministrativeUser -Exactly -Times 0 -Scope It Assert-MockCalled Remove-CMAdministrativeUser -Exactly -Times 0 -Scope It } + + It 'Should throw when RolesToInclude and RolesToExclude have duplicate settings' { + Mock -CommandName Get-CMSecurityScope -MockWith { $true } + Mock -CommandName Get-TargetResource -MockWith { $getReturnPresent } + + { Set-TargetResource @dupRoles } | Should -Throw -ExpectedMessage $rolesInEx + Assert-MockCalled Import-ConfigMgrPowerShellModule -Exactly -Times 1 -Scope It + Assert-MockCalled Set-Location -Exactly -Times 2 -Scope It + Assert-MockCalled Get-TargetResource -Exactly -Times 1 -Scope It + Assert-MockCalled New-CMAdministrativeUser -Exactly -Times 0 -Scope It + Assert-MockCalled Get-CMSecurityRole -Exactly -Times 0 -Scope It + Assert-MockCalled Add-CMSecurityRoleToAdministrativeUser -Exactly -Times 0 -Scope It + Assert-MockCalled Remove-CMSecurityRoleFromAdministrativeUser -Exactly -Times 0 -Scope It + Assert-MockCalled Get-CMSecurityScope -Exactly -Times 0 -Scope It + Assert-MockCalled Add-CMSecurityScopeToAdministrativeUser -Exactly -Times 0 -Scope It + Assert-MockCalled Remove-CMSecurityScopeFromAdministrativeUser -Exactly -Times 0 -Scope It + Assert-MockCalled Get-CMCollection -Exactly -Times 0 -Scope It + Assert-MockCalled Add-CMCollectionToAdministrativeUser -Exactly -Times 0 -Scope It + Assert-MockCalled Remove-CMCollectionFromAdministrativeUser -Exactly -Times 0 -Scope It + Assert-MockCalled Remove-CMAdministrativeUser -Exactly -Times 0 -Scope It + } + + It 'Should throw when ScopesToInclude and ScopesToExclude have duplicate settings' { + Mock -CommandName Get-CMSecurityScope -MockWith { $true } + Mock -CommandName Get-TargetResource -MockWith { $getReturnPresent } + + { Set-TargetResource @dupScopes } | Should -Throw -ExpectedMessage $scopesInEx + Assert-MockCalled Import-ConfigMgrPowerShellModule -Exactly -Times 1 -Scope It + Assert-MockCalled Set-Location -Exactly -Times 2 -Scope It + Assert-MockCalled Get-TargetResource -Exactly -Times 1 -Scope It + Assert-MockCalled New-CMAdministrativeUser -Exactly -Times 0 -Scope It + Assert-MockCalled Get-CMSecurityRole -Exactly -Times 0 -Scope It + Assert-MockCalled Add-CMSecurityRoleToAdministrativeUser -Exactly -Times 0 -Scope It + Assert-MockCalled Remove-CMSecurityRoleFromAdministrativeUser -Exactly -Times 0 -Scope It + Assert-MockCalled Get-CMSecurityScope -Exactly -Times 0 -Scope It + Assert-MockCalled Add-CMSecurityScopeToAdministrativeUser -Exactly -Times 0 -Scope It + Assert-MockCalled Remove-CMSecurityScopeFromAdministrativeUser -Exactly -Times 0 -Scope It + Assert-MockCalled Get-CMCollection -Exactly -Times 0 -Scope It + Assert-MockCalled Add-CMCollectionToAdministrativeUser -Exactly -Times 0 -Scope It + Assert-MockCalled Remove-CMCollectionFromAdministrativeUser -Exactly -Times 0 -Scope It + Assert-MockCalled Remove-CMAdministrativeUser -Exactly -Times 0 -Scope It + } + + It 'Should throw when CollectionsToInclude and CollectionsToExclude have duplicate settings' { + Mock -CommandName Get-CMSecurityScope -MockWith { $true } + Mock -CommandName Get-TargetResource -MockWith { $getReturnPresent } + + { Set-TargetResource @dupCollections } | Should -Throw -ExpectedMessage $collInEx + Assert-MockCalled Import-ConfigMgrPowerShellModule -Exactly -Times 1 -Scope It + Assert-MockCalled Set-Location -Exactly -Times 2 -Scope It + Assert-MockCalled Get-TargetResource -Exactly -Times 1 -Scope It + Assert-MockCalled New-CMAdministrativeUser -Exactly -Times 0 -Scope It + Assert-MockCalled Get-CMSecurityRole -Exactly -Times 0 -Scope It + Assert-MockCalled Add-CMSecurityRoleToAdministrativeUser -Exactly -Times 0 -Scope It + Assert-MockCalled Remove-CMSecurityRoleFromAdministrativeUser -Exactly -Times 0 -Scope It + Assert-MockCalled Get-CMSecurityScope -Exactly -Times 0 -Scope It + Assert-MockCalled Add-CMSecurityScopeToAdministrativeUser -Exactly -Times 0 -Scope It + Assert-MockCalled Remove-CMSecurityScopeFromAdministrativeUser -Exactly -Times 0 -Scope It + Assert-MockCalled Get-CMCollection -Exactly -Times 0 -Scope It + Assert-MockCalled Add-CMCollectionToAdministrativeUser -Exactly -Times 0 -Scope It + Assert-MockCalled Remove-CMCollectionFromAdministrativeUser -Exactly -Times 0 -Scope It + Assert-MockCalled Remove-CMAdministrativeUser -Exactly -Times 0 -Scope It + } } } @@ -549,6 +636,17 @@ try ScopesToInclude = 'All' } + $inputDupIncludeExclude = @{ + SiteCode = 'Lab' + AdminName = 'contoso\User' + RolesToInclude = 'Full Administrator' + RolesToExclude = 'Full Administrator' + ScopesToInclude = 'Default' + ScopesToExclude = 'Default' + CollectionsToInclude = 'Test1' + CollectionsToExclude = 'Test1' + } + Mock -CommandName Get-TargetResource -MockWith { $getReturnPresent } } @@ -579,6 +677,10 @@ try It 'Should return desired result false when trying to add All Scope with warning' { Test-TargetResource @inputChangeAllScope | Should -Be $false } + + It 'Should return desired result false when include and exclude contain duplicate settings' { + Test-TargetResource @inputDupIncludeExclude | Should -Be $false + } } Context 'When running Test-TargetResource when get returns absent' { From 0fc5c9ece0ef283bc7ff6b174c80341cc98eed92 Mon Sep 17 00:00:00 2001 From: Easyreturns <20781445+jeffotterpohl@users.noreply.github.com> Date: Wed, 1 Jul 2020 08:56:24 -0500 Subject: [PATCH 7/9] Moved the Include Exclude warning messages. --- .../DSC_CMAdministrativeUser.psm1 | 71 ++++++++++--------- 1 file changed, 36 insertions(+), 35 deletions(-) diff --git a/source/DSCResources/DSC_CMAdministrativeUser/DSC_CMAdministrativeUser.psm1 b/source/DSCResources/DSC_CMAdministrativeUser/DSC_CMAdministrativeUser.psm1 index fa58adb..1e980cd 100644 --- a/source/DSCResources/DSC_CMAdministrativeUser/DSC_CMAdministrativeUser.psm1 +++ b/source/DSCResources/DSC_CMAdministrativeUser/DSC_CMAdministrativeUser.psm1 @@ -528,6 +528,42 @@ function Test-TargetResource if ($Ensure -eq 'Present') { + if ($RolesToInclude -and $RolesToExclude) + { + foreach ($item in $RolesToInclude) + { + if ($RolesToExclude -contains $item) + { + Write-Warning -Message ($script:localizedData.RolesInEx -f $item) + $result = $false + } + } + } + + if ($ScopesToInclude -and $ScopesToExclude) + { + foreach ($item in $ScopesToInclude) + { + if ($ScopesToExclude -contains $item) + { + Write-Warning -Message ($script:localizedData.ScopesInEx -f $item) + $result = $false + } + } + } + + if ($CollectionsToInclude -and $CollectionsToExclude) + { + foreach ($item in $CollectionsToInclude) + { + if ($CollectionsToExclude -contains $item) + { + Write-Warning -Message ($script:localizedData.CollectionsInEx -f $item) + $result = $false + } + } + } + if ($state.Ensure -eq 'Absent') { if ([string]::IsNullOrEmpty($Roles) -and [string]::IsNullOrEmpty($RolesToInclude)) @@ -542,17 +578,6 @@ function Test-TargetResource { if ($Roles -or $RolesToInclude -or $RolesToExclude) { - if ($RolesToInclude -and $RolesToExclude) - { - foreach ($item in $RolesToInclude) - { - if ($RolesToExclude -contains $item) - { - Write-Warning -Message ($script:localizedData.RolesInEx -f $item) - $result = $false - } - } - } $rolesArray = @{ Match = $Roles @@ -586,18 +611,6 @@ function Test-TargetResource if ($Scopes -or $ScopesToInclude -or $ScopesToExclude) { - if ($ScopesToInclude -and $ScopesToExclude) - { - foreach ($item in $ScopesToInclude) - { - if ($ScopesToExclude -contains $item) - { - Write-Warning -Message ($script:localizedData.ScopesInEx -f $item) - $result = $false - } - } - } - $scopesArray = @{ Match = $Scopes Include = $ScopesToInclude @@ -645,18 +658,6 @@ function Test-TargetResource if ($Collections -or $CollectionsToInclude -or $CollectionsToExclude) { - if ($CollectionsToInclude -and $CollectionsToExclude) - { - foreach ($item in $CollectionsToInclude) - { - if ($CollectionsToExclude -contains $item) - { - Write-Warning -Message ($script:localizedData.CollectionsInEx -f $item) - $result = $false - } - } - } - $collectionsArray = @{ Match = $Collections Include = $CollectionsToInclude From 19afafaa75c8b945701e88b2ceb8474cd1cf398b Mon Sep 17 00:00:00 2001 From: Easyreturns <20781445+jeffotterpohl@users.noreply.github.com> Date: Wed, 1 Jul 2020 09:20:17 -0500 Subject: [PATCH 8/9] Removed Empty line --- .../DSC_CMAdministrativeUser/DSC_CMAdministrativeUser.psm1 | 1 - 1 file changed, 1 deletion(-) diff --git a/source/DSCResources/DSC_CMAdministrativeUser/DSC_CMAdministrativeUser.psm1 b/source/DSCResources/DSC_CMAdministrativeUser/DSC_CMAdministrativeUser.psm1 index 1e980cd..9639de0 100644 --- a/source/DSCResources/DSC_CMAdministrativeUser/DSC_CMAdministrativeUser.psm1 +++ b/source/DSCResources/DSC_CMAdministrativeUser/DSC_CMAdministrativeUser.psm1 @@ -578,7 +578,6 @@ function Test-TargetResource { if ($Roles -or $RolesToInclude -or $RolesToExclude) { - $rolesArray = @{ Match = $Roles Include = $RolesToInclude From 33de0a994a06c5b805d4d79c7883a64d9a20cf93 Mon Sep 17 00:00:00 2001 From: Easyreturns <20781445+jeffotterpohl@users.noreply.github.com> Date: Wed, 1 Jul 2020 10:15:41 -0500 Subject: [PATCH 9/9] Updates per PR comments --- .../DSC_CMAdministrativeUser/DSC_CMAdministrativeUser.psm1 | 4 ++-- tests/Unit/CMAdministrativeUser.tests.ps1 | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/source/DSCResources/DSC_CMAdministrativeUser/DSC_CMAdministrativeUser.psm1 b/source/DSCResources/DSC_CMAdministrativeUser/DSC_CMAdministrativeUser.psm1 index 9639de0..87805d5 100644 --- a/source/DSCResources/DSC_CMAdministrativeUser/DSC_CMAdministrativeUser.psm1 +++ b/source/DSCResources/DSC_CMAdministrativeUser/DSC_CMAdministrativeUser.psm1 @@ -115,7 +115,7 @@ function Get-TargetResource Specifies an array of names for the collections desired to be removed from an administrative user. .PARAMETER Ensure - Specifies if the administrative User is to be present or absent. + Specifies if the administrative user is to be present or absent. #> function Set-TargetResource { @@ -463,7 +463,7 @@ function Set-TargetResource Specifies an array of names for the collections desired to be removed from an administrative user. .PARAMETER Ensure - Specifies if the administrative User is to be present or absent. + Specifies if the administrative user is to be present or absent. #> function Test-TargetResource { diff --git a/tests/Unit/CMAdministrativeUser.tests.ps1 b/tests/Unit/CMAdministrativeUser.tests.ps1 index 3c6f3d2..acec85e 100644 --- a/tests/Unit/CMAdministrativeUser.tests.ps1 +++ b/tests/Unit/CMAdministrativeUser.tests.ps1 @@ -171,7 +171,7 @@ try } Mock -CommandName Get-CMSecurityRole -MockWith { $true } - Mock -CommandName Get-CMSecurityScope -MockWith { $true} + Mock -CommandName Get-CMSecurityScope -MockWith { $true } Mock -CommandName Get-CMCollection -MockWith { $true } }