diff --git a/CHANGELOG.md b/CHANGELOG.md index e793d19..47aca88 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -48,6 +48,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Added CMCMCollectionMembershipEvaluationComponent Resource - Added CMDistributionPointGroupMembers Resource - Added CMSecurityScopes Resource +- Added CMUserDiscovery Resource ### Changed @@ -69,6 +70,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Added Security Scopes to CMDistributionGroup Resource - Added SiteSystems, SiteSystemsToInclude, and SiteSystemsToExclude and SecurityScopes, SecurityScopesToInclude, SecurityScopesToExclude to CMBoundaryGroup resource. +- Updated CMSystemDiscovery Resource to add needed throw and warn messages. ### Removed diff --git a/README.md b/README.md index cacaaa6..636c8d0 100644 --- a/README.md +++ b/README.md @@ -107,6 +107,8 @@ Please check out common DSC Community [contributing guidelines](https://dsccommu - **CMSecurityScopes**: Provides a resource for adding and removing Security Scopes. Note: If the Security Scope is currently in use and assigned, DSC will not remove the Security Scope. +- **CMUserDiscovery**: Provides a resource to manage the Configuration Manager + User Discovery method. ### xSccmPreReqs @@ -1056,3 +1058,37 @@ Please check out common DSC Community [contributing guidelines](https://dsccommu - [CMSecurityScopes_Present](Source\Examples\Resources\CMSecurityScopes\CMSecurityScopes_Present.ps1) - [CMSecurityScopes_Absent](Source\Examples\Resources\CMSecurityScopes\CMSecurityScopes_Absent.ps1) + +### CMUserDiscovery + +- **[String] SiteCode** _(Key)_: Specifies the Site Code for the Configuration + Manager site. +- **[Boolean] Enabled** _(Key)_: Specifies the enablement of the User + Discovery method. If settings is set to $false no other value provided will be + evaluated for compliance. +- **[Boolean] EnableDeltaDiscovery** _(Write)_: Indicates whether Configuration + Manager discovers resources created or modified in AD DS since the last + discovery cycle. +- **[UInt32] DeltaDiscoveryMins** _(Write)_: Specifies the number of minutes for + the delta discovery. +- **[String] ADContainers[]** _(Write)_: Specifies an array of names of Active Directory + containers to match to the discovery. +- **[String] ADContainersToInclude[]** _(Write)_: Specifies an array of names of + Active Directory containers to add to the discovery. +- **[String] ADContainersToExclude[]** _(Write)_: Specifies an array of names of + Active Directory containers to exclude to the discovery. +- **[String] ScheduleInterval** _(Write)_: Specifies the time when the scheduled + event recurs in hours and days. + - Values include: { None| Days| Hours | Minutes } +- **[UInt32] ScheduleCount** _(Write)_: Specifies how often the recur interval + is run. If hours are specified the max value is 23. Anything over 23 will result + in 23 to be set. If days are specified the max value is 31. Anything over 31 will + result in 31 being set. + +#### CMUserDiscovery Examples + +- [CMUserDiscovery_Disabled](Source\Examples\Resources\CMUserDiscovery\CMUserDiscovery_Disabled.ps1) +- [CMUserDiscovery_Enabled](Source\Examples\Resources\CMUserDiscovery\CMUserDiscovery_Enabled.ps1) +- [CMUserDiscovery_Exclude](Source\Examples\Resources\CMUserDiscovery\CMUserDiscovery_Exclude.ps1) +- [CMUserDiscovery_Include](Source\Examples\Resources\CMUserDiscovery\CMUserDiscovery_Include.ps1) +- [CMUserDiscovery_ScheduleNone](Source\Examples\Resources\CMUserDiscovery\CMUserDiscovery_ScheduleNone.ps1) diff --git a/source/ConfigMgrCBDsc.psd1 b/source/ConfigMgrCBDsc.psd1 index d43fa05..9fce0c8 100644 --- a/source/ConfigMgrCBDsc.psd1 +++ b/source/ConfigMgrCBDsc.psd1 @@ -73,6 +73,7 @@ 'CMCollectionMembershipEvaluationComponent' 'CMDistributionPointGroupMembers' 'CMSecurityScopes' + 'CMUserDiscovery' ) <# @@ -90,7 +91,7 @@ 'ManagementPoint','AssetIntelligencePoint','FallbackStatusPoint','SoftwareUpdatePoint','DistrubtionPoint','HeartbeatDiscovery', 'ServiceConnectionPoint','NetworkDiscovery','ReportingServicePoint','SystemDiscovery','PXEDistributionPoint','PullDistributionPoint', 'SiteMaintenance','AdministrativeUser','DistributionGroup','SiteSystemServer','StatusReportingComponent','CollectionMembershipEvaluationComponent', - 'DistributionPointGroupMembers','SecurityScopes') + 'DistributionPointGroupMembers','SecurityScopes','UserDiscovery') # A URL to the license for this module. LicenseUri = 'https://github.com/dsccommunity/ConfigMgrCBDsc/blob/master/LICENSE' diff --git a/source/DSCResources/DSC_CMSystemDiscovery/DSC_CMSystemDiscovery.psm1 b/source/DSCResources/DSC_CMSystemDiscovery/DSC_CMSystemDiscovery.psm1 index bac6358..8b6441f 100644 --- a/source/DSCResources/DSC_CMSystemDiscovery/DSC_CMSystemDiscovery.psm1 +++ b/source/DSCResources/DSC_CMSystemDiscovery/DSC_CMSystemDiscovery.psm1 @@ -223,11 +223,29 @@ function Set-TargetResource throw $script:localizedData.MissingDeltaDiscovery } - if (($PSBoundParameters.ContainsKey('ScheduleInterval') -and $PSBoundParameters.ScheduleInterval -ne 'None') -and (-not $PSBoundParameters.ContainsKey('ScheduleCount'))) + if (($PSBoundParameters.ContainsKey('ScheduleInterval') -and $PSBoundParameters.ScheduleInterval -ne 'None') -and + (-not $PSBoundParameters.ContainsKey('ScheduleCount'))) { throw $script:localizedData.IntervalCount } + if (($EnableDeltaDiscovery -eq $true -and $state.EnableDeltaDiscovery -eq $false) -and + (-not $PSBoundParameters.ContainsKey('DeltaDiscoveryMins'))) + { + throw $script:localizedData.DeltaNoInterval + } + + if ($ADContainersToInclude -and $ADContainersToExclude) + { + foreach ($item in $ADContainersToInclude) + { + if ($ADContainersToExclude -contains $item) + { + throw ($script:localizedData.ContainersInEx -f $item) + } + } + } + $paramsToCheck = @('Enabled','EnableDeltaDiscovery','DeltaDiscoveryMins','EnableFilteringExpiredLogon', 'TimeSinceLastLogonDays','EnableFilteringExpiredPassword','TimeSinceLastPasswordUpdateDays') @@ -506,7 +524,7 @@ function Test-TargetResource { if ($ScheduleInterval -ne 'None' -and -not $PSBoundParameters.ContainsKey('ScheduleCount')) { - Write-Verbose -Message $script:localizedData.IntervalCount + Write-Warning -Message $script:localizedData.IntervalCount $result = $false } else @@ -525,6 +543,23 @@ function Test-TargetResource } } + if (($EnableDeltaDiscovery -eq $true -and $state.EnableDeltaDiscovery -eq $false) -and + (-not $PSBoundParameters.ContainsKey('DeltaDiscoveryMins'))) + { + Write-Warning -Message $script:localizedData.DeltaNoInterval + } + + if ($ADContainersToInclude -and $ADContainersToExclude) + { + foreach ($item in $ADContainersToInclude) + { + if ($ADContainersToExclude -contains $item) + { + Write-Warning -Message ($script:localizedData.ContainersInEx -f $item) + } + } + } + if (($ADContainers) -or ($ADContainersToInclude)) { if ($ADContainers) diff --git a/source/DSCResources/DSC_CMSystemDiscovery/en-US/DSC_CMSystemDiscovery.strings.psd1 b/source/DSCResources/DSC_CMSystemDiscovery/en-US/DSC_CMSystemDiscovery.strings.psd1 index 4fbcd76..de5f8ac 100644 --- a/source/DSCResources/DSC_CMSystemDiscovery/en-US/DSC_CMSystemDiscovery.strings.psd1 +++ b/source/DSCResources/DSC_CMSystemDiscovery/en-US/DSC_CMSystemDiscovery.strings.psd1 @@ -15,4 +15,6 @@ ConvertFrom-StringData @' SetDisabled = Setting System Discovery to disabled. MissingDeltaDiscovery = When changing delta schedule, delta schedule must be enabled. ADIgnore = ADContainers was specified, ADContainersInclude and ADContainersExclude will be ignored. + ContainersInEx = ADContainersToExclude and ADContainersToInclude contain to same entry {0}, remove from one of the arrays. + DeltaNoInterval = DeltaDiscoveryMins is not specified, specify DeltaDiscoveryMins when enabling Delta Discovery. '@ diff --git a/source/DSCResources/DSC_CMUserDiscovery/DSC_CMUserDiscovery.psm1 b/source/DSCResources/DSC_CMUserDiscovery/DSC_CMUserDiscovery.psm1 new file mode 100644 index 0000000..c464324 --- /dev/null +++ b/source/DSCResources/DSC_CMUserDiscovery/DSC_CMUserDiscovery.psm1 @@ -0,0 +1,558 @@ +$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 site code for Configuration Manager site. + + .PARAMETER Enabled + Specifies the enablement of the User Discovery method. +#> +function Get-TargetResource +{ + [CmdletBinding()] + [OutputType([System.Collections.Hashtable])] + param + ( + [Parameter(Mandatory = $true)] + [String] + $SiteCode, + + [Parameter(Mandatory = $true)] + [Boolean] + $Enabled + ) + + Write-Verbose -Message $script:localizedData.RetrieveSettingValue + Import-ConfigMgrPowerShellModule -SiteCode $SiteCode + Set-Location -Path "$($SiteCode):\" + + $userDiscovery = Get-CMDiscoveryMethod -Name ActiveDirectoryUserDiscovery -SiteCode $SiteCode + + foreach ($prop in $userDiscovery.Props) + { + switch ($prop.PropertyName) + { + 'Settings' { $enabledStatus = ($prop.Value1 -eq 'Active') } + 'Full Sync Schedule' { $userSchedule = $prop.Value1 } + 'Enable Incremental Sync' { $deltaEnabled = $prop.Value } + 'Startup Schedule' { $userDelta = $prop.Value1 } + } + } + + $adContainersList = ($userDiscovery.Proplists | Where-Object -FilterScript {$_.PropertyListName -eq 'AD Containers'}).Values + foreach ($line in $adContainersList) + { + if ($line -match 'LDAP://') + { + [array]$adContainerArray += $line + } + } + + if ($deltaEnabled -eq 0) + { + $userSchedule = $userDelta + $userDelta = $null + } + + $scheduleConvert = ConvertTo-ScheduleInterval -ScheduleString $userSchedule + + if (-not [string]::IsNullOrEmpty($userDelta)) + { + $uDelta = Convert-CMSchedule -ScheduleString $userDelta + + if ($uDelta.HourSpan -eq 1) + { + $syncDelta = 60 + } + else + { + $syncDelta = $uDelta.MinuteSpan + } + } + + return @{ + SiteCode = $SiteCode + Enabled = $enabledStatus + ScheduleInterval = $scheduleConvert.Interval + ScheduleCount = $scheduleConvert.Count + EnableDeltaDiscovery = $deltaEnabled + DeltaDiscoveryMins = $syncDelta + ADContainers = $adContainerArray + } +} + +<# + .SYNOPSIS + This will set the desired state. + + .PARAMETER SiteCode + Specifies the site code for Configuration Manager site. + + .PARAMETER Enabled + Specifies the enablement of the User Discovery method. + + .PARAMETER EnableDeltaDiscovery + Indicates whether Configuration Manager discovers resources created or modified in AD DS + since the last discovery cycle. If you specify a value of $True for this parameter, + specify a value for the DeltaDiscoveryMins parameter. + + .PARAMETER DeltaDiscoveryMins + Specifies the number of minutes for the delta discovery. + + .PARAMETER ADContainers + Specifies an array of names of Active Directory containers to match to the discovery. + + .PARAMETER ADContainersToInclude + Specifies an array of names of Active Directory containers to add to the discovery. + + .PARAMETER ADContainersToExclude + Specifies an array of names of Active Directory containers to exclude to the discovery. + + .PARAMETER ScheduleInterval + Specifies the time when the scheduled event recurs. + + .PARAMETER ScheduleCount + Specifies how often the recur interval is run. +#> +function Set-TargetResource +{ + [CmdletBinding()] + param + ( + [Parameter(Mandatory = $true)] + [String] + $SiteCode, + + [Parameter(Mandatory = $true)] + [Boolean] + $Enabled, + + [Parameter()] + [Boolean] + $EnableDeltaDiscovery, + + [Parameter()] + [ValidateRange(1,60)] + [UInt32] + $DeltaDiscoveryMins, + + [Parameter()] + [String[]] + $ADContainers, + + [Parameter()] + [String[]] + $ADContainersToInclude, + + [Parameter()] + [String[]] + $ADContainersToExclude, + + [Parameter()] + [ValidateSet('None','Days','Hours','Minutes')] + [String] + $ScheduleInterval, + + [Parameter()] + [UInt32] + $ScheduleCount + ) + + Import-ConfigMgrPowerShellModule -SiteCode $SiteCode + Set-Location -Path "$($SiteCode):\" + + try + { + $state = Get-TargetResource -SiteCode $SiteCode -Enabled $Enabled + + if ($Enabled -eq $true) + { + if (($PSBoundParameters.DeltaDiscoveryMins) -and ($PSBoundParameters.EnableDeltaDiscovery -eq $false -or + ($state.EnableDeltaDiscovery -eq $false -and + [string]::IsNullOrEmpty($PSBoundParameters.EnableDeltaDiscovery)))) + { + throw $script:localizedData.MissingDeltaDiscovery + } + + if (($PSBoundParameters.ContainsKey('ScheduleInterval') -and $PSBoundParameters.ScheduleInterval -ne 'None') -and + (-not $PSBoundParameters.ContainsKey('ScheduleCount'))) + { + throw $script:localizedData.IntervalCount + } + + if (($EnableDeltaDiscovery -eq $true -and $state.EnableDeltaDiscovery -eq $false) -and + (-not $PSBoundParameters.ContainsKey('DeltaDiscoveryMins'))) + { + throw $script:localizedData.DeltaNoInterval + } + + if ($ADContainersToInclude -and $ADContainersToExclude) + { + foreach ($item in $ADContainersToInclude) + { + if ($ADContainersToExclude -contains $item) + { + throw ($script:localizedData.ContainersInEx -f $item) + } + } + } + + $paramsToCheck = @('Enabled','EnableDeltaDiscovery','DeltaDiscoveryMins') + + foreach ($param in $PSBoundParameters.GetEnumerator()) + { + if ($paramsToCheck -contains $param.Key) + { + if ($param.Value -ne $state[$param.Key]) + { + Write-Verbose -Message ($script:localizedData.SetCommonSettings -f $param.Key, $param.Value) + $buildingParams += @{ + $param.Key = $param.Value + } + } + } + } + + if (-not [string]::IsNullOrEmpty($ScheduleInterval)) + { + if ($ScheduleInterval -ne $state.ScheduleInterval) + { + Write-Verbose -Message ($script:localizedData.UIntervalSet -f $ScheduleInterval) + $setSchedule = $true + } + + if (($ScheduleInterval -ne 'None') -and ($ScheduleCount -ne $state.ScheduleCount)) + { + Write-Verbose -Message ($script:localizedData.UCountSet -f $ScheduleCount) + $setSchedule = $true + } + + if ($setSchedule -eq $true) + { + if ($ScheduleInterval -eq 'None') + { + $pschedule = New-CMSchedule -Nonrecurring + } + else + { + $pScheduleSet = @{ + RecurInterval = $ScheduleInterval + RecurCount = $ScheduleCount + } + + $pschedule = New-CMSchedule @pScheduleSet + } + + $buildingParams += @{ + PollingSchedule = $pSchedule + } + } + } + + if (($ADContainers) -or ($ADContainersToInclude)) + { + if ($ADContainers) + { + $includes = $ADContainers + } + else + { + $includes = $ADContainersToInclude + } + + foreach ($adContainer in $includes) + { + if ($state.ADContainers -notcontains $adContainer) + { + Write-Verbose -Message ($script:localizedData.AddADContainer -f $adContainer) + [array]$addAdContainers += $adContainer + } + } + + if (-not [string]::IsNullOrEmpty($addAdContainers)) + { + $buildingParams += @{ + AddActiveDirectoryContainer = $addAdContainers + } + } + } + + if (($ADContainers) -or ($ADContainersToExclude)) + { + if (-not [string]::IsNullOrEmpty($state.ADContainers)) + { + if ($ADContainers) + { + $excludes = $ADContainers + } + else + { + $excludes = $ADContainersToExclude + } + + $compares = Compare-Object -ReferenceObject $state.ADContainers -DifferenceObject $excludes -IncludeEqual + foreach ($compare in $compares) + { + if ($ADContainers) + { + if ($compare.SideIndicator -eq '<=') + { + Write-Verbose -Message ($script:localizedData.RemoveADContainer -f $compare.InputObject) + [array]$removeADContainers += $compare.InputObject + } + } + else + { + if ($compare.SideIndicator -eq '==') + { + Write-Verbose -Message ($script:localizedData.RemoveADContainer -f $compare.InputObject) + [array]$removeADContainers += $compare.InputObject + } + } + } + + if (-not [string]::IsNullOrEmpty($removeADContainers)) + { + $buildingParams += @{ + RemoveActiveDirectoryContainer = $removeADContainers + } + } + } + } + + if ($buildingParams) + { + Set-CMDiscoveryMethod -ActiveDirectoryUserDiscovery -SiteCode $SiteCode @buildingParams + } + } + elseif ($state.Enabled -eq $true) + { + Write-Verbose -Message $script:localizedData.SetDisabled + Set-CMDiscoveryMethod -ActiveDirectoryUserDiscovery -Enabled $false -SiteCode $SiteCode + } + } + catch + { + throw $_ + } + finally + { + Set-Location -Path "$env:temp" + } +} + +<# + .SYNOPSIS + This will set the desired state. + + .PARAMETER SiteCode + Specifies the site code for Configuration Manager site. + + .PARAMETER Enabled + Specifies the enablement of the User Discovery method. + + .PARAMETER EnableDeltaDiscovery + Indicates whether Configuration Manager discovers resources created or modified in AD DS + since the last discovery cycle. If you specify a value of $True for this parameter, + specify a value for the DeltaDiscoveryMins parameter. + + .PARAMETER DeltaDiscoveryMins + Specifies the number of minutes for the delta discovery. + + .PARAMETER ADContainers + Specifies an array of names of Active Directory containers to match to the discovery. + + .PARAMETER ADContainersToInclude + Specifies an array of names of Active Directory containers to add to the discovery. + + .PARAMETER ADContainersToExclude + Specifies an array of names of Active Directory containers to exclude to the discovery. + + .PARAMETER ScheduleInterval + Specifies the time when the scheduled event recurs. + + .PARAMETER ScheduleCount + Specifies how often the recur interval is run. +#> +function Test-TargetResource +{ + [CmdletBinding()] + [OutputType([System.Boolean])] + param + ( + [Parameter(Mandatory = $true)] + [String] + $SiteCode, + + [Parameter(Mandatory = $true)] + [Boolean] + $Enabled, + + [Parameter()] + [Boolean] + $EnableDeltaDiscovery, + + [Parameter()] + [ValidateRange(1,60)] + [UInt32] + $DeltaDiscoveryMins, + + [Parameter()] + [String[]] + $ADContainers, + + [Parameter()] + [String[]] + $ADContainersToInclude, + + [Parameter()] + [String[]] + $ADContainersToExclude, + + [Parameter()] + [ValidateSet('None','Days','Hours','Minutes')] + [String] + $ScheduleInterval, + + [Parameter()] + [UInt32] + $ScheduleCount + ) + + Import-ConfigMgrPowerShellModule -SiteCode $SiteCode + Set-Location -Path "$($SiteCode):\" + $state = Get-TargetResource -SiteCode $SiteCode -Enabled $Enabled + $result = $true + + if ($Enabled -eq $true) + { + $testParams = @{ + CurrentValues = $state + DesiredValues = $PSBoundParameters + ValuesToCheck = @('Enabled','EnableDeltaDiscovery','DeltaDiscoveryMins') + } + + $result = Test-DscParameterState @testParams -TurnOffTypeChecking -Verbose + + if ($PSBoundParameters.ContainsKey('ScheduleInterval')) + { + if ($ScheduleInterval -ne 'None' -and -not $PSBoundParameters.ContainsKey('ScheduleCount')) + { + Write-Warning -Message $script:localizedData.IntervalCount + $result = $false + } + else + { + if ($ScheduleInterval -ne $state.SCheduleInterval) + { + Write-Verbose -Message ($script:localizedData.UIntervalTest -f $ScheduleInterval, $State.ScheduleInterval) + $result = $false + } + + if (($ScheduleInterval -ne 'None') -and ($ScheduleCount -ne $state.ScheduleCount)) + { + Write-Verbose -Message ($script:localizedData.UCountTest -f $ScheduleCount, $State.ScheduleCount) + $result = $false + } + } + } + + if (($EnableDeltaDiscovery -eq $true -and $state.EnableDeltaDiscovery -eq $false) -and + (-not $PSBoundParameters.ContainsKey('DeltaDiscoveryMins'))) + { + Write-Warning -Message $script:localizedData.DeltaNoInterval + } + + if ($ADContainersToInclude -and $ADContainersToExclude) + { + foreach ($item in $ADContainersToInclude) + { + if ($ADContainersToExclude -contains $item) + { + Write-Warning -Message ($script:localizedData.ContainersInEx -f $item) + } + } + } + + if (($ADContainers) -or ($ADContainersToInclude)) + { + if ($ADContainers) + { + if ($PSBoundParameters.ContainsKey('ADContainersToInclude') -or + $PSBoundParameters.ContainsKey('ADContainersToExclude')) + { + Write-Warning -Message $script:localizedData.ADIgnore + } + $includes = $ADContainers + } + else + { + $includes = $ADContainersToInclude + } + + foreach ($adContainer in $includes) + { + if ($state.ADContainers -notcontains $adContainer) + { + Write-Verbose -Message ($script:localizedData.ExpectedADContainer -f $adContainer) + $result = $false + } + } + } + + if (($ADContainers) -or ($ADContainersToExclude)) + { + if (-not [string]::IsNullOrEmpty($state.ADContainers)) + { + if ($ADContainers) + { + $excludes = $ADContainers + } + else + { + $excludes = $ADContainersToExclude + } + + $compares = Compare-Object -ReferenceObject $state.ADContainers -DifferenceObject $excludes -IncludeEqual + foreach ($compare in $compares) + { + if ($ADContainers) + { + if ($compare.SideIndicator -eq '<=') + { + Write-Verbose -Message ($script:localizedData.ExcludeADContainer -f $compare.InputObject) + $result = $false + } + } + else + { + if ($compare.SideIndicator -eq '==') + { + Write-Verbose -Message ($script:localizedData.ExcludeADContainer -f $compare.InputObject) + $result = $false + } + } + } + } + } + } + elseif ($state.Enabled -eq $true) + { + Write-Verbose -Message $script:localizedData.TestDisabled + $result = $false + } + + Write-Verbose -Message ($script:localizedData.TestState -f $result) + return $result +} + +Export-ModuleMember -Function *-TargetResource diff --git a/source/DSCResources/DSC_CMUserDiscovery/DSC_CMUserDiscovery.schema.mof b/source/DSCResources/DSC_CMUserDiscovery/DSC_CMUserDiscovery.schema.mof new file mode 100644 index 0000000..40a5549 --- /dev/null +++ b/source/DSCResources/DSC_CMUserDiscovery/DSC_CMUserDiscovery.schema.mof @@ -0,0 +1,13 @@ +[ClassVersion("1.0.0"), FriendlyName("CMUserDiscovery")] +class DSC_CMUserDiscovery : OMI_BaseResource +{ + [Key, Description("Specifies the SiteCode for the Configuration Manager site.")] String SiteCode; + [Key, Description("Specifies the enablement of the User Discovery method.")] Boolean Enabled; + [Write, Description("Indicates whether Configuration Manager discovers resources created or modified in AD DS since the last discovery cycle.")] Boolean EnableDeltaDiscovery; + [Write, Description("Specifies the number of minutes for the delta discovery.")] UInt32 DeltaDiscoveryMins; + [Write, Description("Specifies an array of names of Active Directory containers to match to the discovery.")] String ADContainers[]; + [Write, Description("Specifies an array of names of Active Directory containers to add to the discovery.")] String ADContainersToInclude[]; + [Write, Description("Specifies an array of names of Active Directory containers to exclude to the discovery.")] String ADContainersToExclude[]; + [Write, Description("Specifies the time when the scheduled event recurs."), ValueMap{"None","Days","Hours","Minutes"}, Values{"None","Days","Hours","Minutes"}] String ScheduleInterval; + [Write, Description("Specifies how often the recur interval is run.")] UInt32 ScheduleCount; +}; diff --git a/source/DSCResources/DSC_CMUserDiscovery/en-US/DSC_CMUserDiscovery.strings.psd1 b/source/DSCResources/DSC_CMUserDiscovery/en-US/DSC_CMUserDiscovery.strings.psd1 new file mode 100644 index 0000000..d000ce3 --- /dev/null +++ b/source/DSCResources/DSC_CMUserDiscovery/en-US/DSC_CMUserDiscovery.strings.psd1 @@ -0,0 +1,20 @@ +ConvertFrom-StringData @' + RetrieveSettingValue = Getting results for Configuration Manager User Discovery method. + IntervalCount = Invalid parameter usage specifying an Interval and didn't specify count. + UIntervalTest = NOT MATCH: Schedule interval expected: {0} returned {1}. + UCountTest = NOT MATCH: Schedule count expected: {0} returned {1}. + ADIgnore = ADContainers was specified, ADContainersInclude and ADContainersExclude will be ignored. + ExpectedADContainer = Expected AD Container: {0} to be present. + ExcludeADContainer = Expected AD Container: {0} to be absent. + TestDisabled = Expected User Discovery to be set to disabled returned enabled. + TestState = Test-TargetResource compliance check returned: {0}. + MissingDeltaDiscovery = When changing delta schedule, delta schedule must be enabled. + SetCommonSettings = Setting {0} to desired setting {1}. + UIntervalSet = Setting Schedule interval to {0}. + UCountSet = Setting Schedule count to {0}. + AddADContainer = Adding AD Container: {0}. + RemoveADContainer = Removing AD Container: {0}. + SetDisabled = Setting User Discovery to disabled. + ContainersInEx = ADContainersToExclude and ADContainersToInclude contain to same entry {0}, remove from one of the arrays. + DeltaNoInterval = DeltaDiscoveryMins is not specified, specify DeltaDiscoveryMins when enabling Delta Discovery. +'@ diff --git a/source/Examples/Resources/CMUserDiscovery/CMUserDiscovery_Disabled.ps1 b/source/Examples/Resources/CMUserDiscovery/CMUserDiscovery_Disabled.ps1 new file mode 100644 index 0000000..bf6c975 --- /dev/null +++ b/source/Examples/Resources/CMUserDiscovery/CMUserDiscovery_Disabled.ps1 @@ -0,0 +1,17 @@ +<# + .SYNOPSIS + A DSC configuration script to set user discovery disabled. +#> +Configuration Example +{ + Import-DscResource -ModuleName ConfigMgrCBDsc + + Node localhost + { + CMUserDiscovery ExampleSettings + { + SiteCode = 'Lab' + Enabled = $false + } + } +} diff --git a/source/Examples/Resources/CMUserDiscovery/CMUserDiscovery_Enabled.ps1 b/source/Examples/Resources/CMUserDiscovery/CMUserDiscovery_Enabled.ps1 new file mode 100644 index 0000000..63d56d5 --- /dev/null +++ b/source/Examples/Resources/CMUserDiscovery/CMUserDiscovery_Enabled.ps1 @@ -0,0 +1,25 @@ +<# + .SYNOPSIS + A DSC configuration script to set user discovery enabled. +#> +Configuration Example +{ + Import-DscResource -ModuleName ConfigMgrCBDsc + + Node localhost + { + CMUserDiscovery ExampleSettings + { + SiteCode = 'Lab' + Enabled = $true + ScheduleInterval = 'Days' + ScheduleCount = 7 + EnableDeltaDiscovery = $true + DeltaDiscoveryMins = 50 + ADContainers = @( + 'LDAP://OU=Far,DC=contoso,DC=com','LDAP://OU=Far,OU=Domain Controllers,DC=contoso,DC=com', + 'LDAP://OU=Far,OU=Deployables,DC=contoso,DC=com' + ) + } + } +} diff --git a/source/Examples/Resources/CMUserDiscovery/CMUserDiscovery_Exclude.ps1 b/source/Examples/Resources/CMUserDiscovery/CMUserDiscovery_Exclude.ps1 new file mode 100644 index 0000000..dd07333 --- /dev/null +++ b/source/Examples/Resources/CMUserDiscovery/CMUserDiscovery_Exclude.ps1 @@ -0,0 +1,25 @@ +<# + .SYNOPSIS + A DSC configuration script for user discovery to exclude ad containers. +#> +Configuration Example +{ + Import-DscResource -ModuleName ConfigMgrCBDsc + + Node localhost + { + CMUserDiscovery ExampleSettings + { + SiteCode = 'Lab' + Enabled = $true + ScheduleInterval = 'Days' + ScheduleCount = 7 + EnableDeltaDiscovery = $true + DeltaDiscoveryMins = 50 + ADContainersToExclude = @( + 'LDAP://OU=Far,DC=contoso,DC=com','LDAP://OU=Far,OU=Domain Controllers,DC=contoso,DC=com', + 'LDAP://OU=Far,OU=Deployables,DC=contoso,DC=com' + ) + } + } +} diff --git a/source/Examples/Resources/CMUserDiscovery/CMUserDiscovery_Include.ps1 b/source/Examples/Resources/CMUserDiscovery/CMUserDiscovery_Include.ps1 new file mode 100644 index 0000000..c34ba8c --- /dev/null +++ b/source/Examples/Resources/CMUserDiscovery/CMUserDiscovery_Include.ps1 @@ -0,0 +1,25 @@ +<# + .SYNOPSIS + A DSC configuration script for user discovery to include ad containers. +#> +Configuration Example +{ + Import-DscResource -ModuleName ConfigMgrCBDsc + + Node localhost + { + CMUserDiscovery ExampleSettings + { + SiteCode = 'Lab' + Enabled = $true + ScheduleInterval = 'Days' + ScheduleCount = 7 + EnableDeltaDiscovery = $true + DeltaDiscoveryMins = 50 + ADContainersToInclude = @( + 'LDAP://OU=Far,DC=contoso,DC=com','LDAP://OU=Far,OU=Domain Controllers,DC=contoso,DC=com', + 'LDAP://OU=Far,OU=Deployables,DC=contoso,DC=com' + ) + } + } +} diff --git a/source/Examples/Resources/CMUserDiscovery/CMUserDiscovery_ScheduleNone.ps1 b/source/Examples/Resources/CMUserDiscovery/CMUserDiscovery_ScheduleNone.ps1 new file mode 100644 index 0000000..27b6527 --- /dev/null +++ b/source/Examples/Resources/CMUserDiscovery/CMUserDiscovery_ScheduleNone.ps1 @@ -0,0 +1,24 @@ +<# + .SYNOPSIS + A DSC configuration script to user discovery set to a custom schedule to none. +#> +Configuration Example +{ + Import-DscResource -ModuleName ConfigMgrCBDsc + + Node localhost + { + CMUserDiscovery ExampleSettings + { + SiteCode = 'Lab' + Enabled = $true + ScheduleInterval = 'None' + EnableDeltaDiscovery = $true + DeltaDiscoveryMins = 50 + ADContainers = @( + 'LDAP://OU=Far,DC=contoso,DC=com','LDAP://OU=Far,OU=Domain Controllers,DC=contoso,DC=com', + 'LDAP://OU=Far,OU=Deployables,DC=contoso,DC=com' + ) + } + } +} diff --git a/tests/Unit/CMSystemDiscovery.tests.ps1 b/tests/Unit/CMSystemDiscovery.tests.ps1 index 94784d1..5576068 100644 --- a/tests/Unit/CMSystemDiscovery.tests.ps1 +++ b/tests/Unit/CMSystemDiscovery.tests.ps1 @@ -482,6 +482,24 @@ try DeltaDiscoveryMins = 60 } + $adContainersExclude = 'LDAP://OU=Test1,DC=contoso,DC=com' + + $enableDeltaThrow = @{ + SiteCode = 'Lab' + Enabled = $true + EnableDeltaDiscovery = $true + } + + $inputParamsIncludeExcludeThrow = @{ + SiteCode = 'Lab' + Enabled = $true + ADContainersToInclude = $adContainersExclude + ADContainersToExclude = $adContainersExclude + } + + $excludeThrow = "ADContainersToExclude and ADContainersToInclude contain to same entry $adContainersExclude, remove from one of the arrays." + $enableDeltaThrowMsg = "DeltaDiscoveryMins is not specified, specify DeltaDiscoveryMins when enabling Delta Discovery." + Mock -CommandName Get-TargetResource -MockWith { $getTargetResourceStandardReturn } } @@ -498,6 +516,32 @@ try Assert-MockCalled Set-CMDiscoveryMethod -Exactly -Times 0 -Scope It } + It 'Should call expected when specifying the same container in include and exclude' { + Mock -CommandName Get-TargetResource -MockWith { $getTargetResourceStandardReturn } + Mock -CommandName New-CMSchedule + Mock -CommandName Set-CMDiscoveryMethod + + { Set-TargetResource @inputParamsIncludeExcludeThrow } | Should -Throw -ExpectedMessage $excludeThrow + 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-CMSchedule -Exactly -Times 0 -Scope It + Assert-MockCalled Set-CMDiscoveryMethod -Exactly -Times 0 -Scope It + } + + It 'Should call expected when enabling delta discovery without specifying an interval' { + Mock -CommandName Get-TargetResource -MockWith { $inputDeltaThrow } + Mock -CommandName New-CMSchedule + Mock -CommandName Set-CMDiscoveryMethod + + { Set-TargetResource @enableDeltaThrow } | Should -Throw -ExpectedMessage $enableDeltaThrowMsg + 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-CMSchedule -Exactly -Times 0 -Scope It + Assert-MockCalled Set-CMDiscoveryMethod -Exactly -Times 0 -Scope It + } + It 'Should call expected when Set-CMDiscovery throws' { Mock -CommandName Get-TargetResource -MockWith { $getTargetResourceStandardReturn } Mock -CommandName New-CMSchedule @@ -660,6 +704,13 @@ try ADContainersToExclude = $adContainersExclude } + $inputParamsIncludeExclude = @{ + SiteCode = 'Lab' + Enabled = $true + ADContainersToInclude = $adContainersExclude + ADContainersToExclude = $adContainersExclude + } + Mock -CommandName Get-TargetResource -MockWith { $getTargetResourceStandardReturn } } @@ -667,6 +718,10 @@ try Test-TargetResource @iputAllParamsMatch | Should -Be $true } + It 'Should return desired result false when ad containers mismatch' { + Test-TargetResource @inputParamsIncludeExclude | Should -Be $false + } + It 'Should return desired result false when delta schedule mismatch' { Test-TargetResource @inputParamsDeltaMismatch | Should -Be $false } @@ -708,6 +763,18 @@ try ScheduleInterval = 'Days' } + $disableDelta = @{ + SiteCode = 'Lab' + Enabled = $true + EnableDeltaDiscovery = $false + } + + $enableDelta = @{ + SiteCode = 'Lab' + Enabled = $true + EnableDeltaDiscovery = $true + } + Mock -CommandName Get-TargetResource -MockWith { $getTargetResourceStandardNoSchedule } } @@ -726,6 +793,12 @@ try It 'Should return desired result false when input param is setting schedule is count is missing' { Test-TargetResource @inputParamsBadSchedule | Should -Be $false } + + It 'Should return desired result false when enabling delta discovery without interval' { + Mock -CommandName Get-TargetResource -MockWith { $disableDelta } + + Test-TargetResource @enableDelta | Should -Be $false + } } } } diff --git a/tests/Unit/CMUserDiscovery.tests.ps1 b/tests/Unit/CMUserDiscovery.tests.ps1 new file mode 100644 index 0000000..4d98c73 --- /dev/null +++ b/tests/Unit/CMUserDiscovery.tests.ps1 @@ -0,0 +1,744 @@ +[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidUsingConvertToSecureStringWithPlainText', '')] +param () + +$script:dscModuleName = 'ConfigMgrCBDsc' +$script:dscResourceName = 'DSC_CMUserDiscovery' + +$script:moduleRoot = Split-Path -Parent (Split-Path -Parent $PSScriptRoot) + +Import-Module (Join-Path -Path $PSScriptRoot -ChildPath 'Stubs\ConfigMgrCBDscStub.psm1') -Force -WarningAction 'SilentlyContinue' + + # Import DscResource.Test Module +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.' +} + +# Variables used for each Initialize-TestEnvironment +$testEnvironment = Initialize-TestEnvironment ` + -DSCModuleName $script:dscModuleName ` + -DSCResourceName $script:dscResourceName ` + -TestType Unit + +function Invoke-TestCleanup +{ + Restore-TestEnvironment -TestEnvironment $script:testEnvironment +} + +# Begin Testing +try +{ + InModuleScope $script:dscResourceName { + Describe "ConfigMgrCBDsc - DSC_CMUserDiscovery\Get-TargetResource" -Tag 'Get' { + BeforeAll { + $getInput = @{ + SiteCode = 'Lab' + Enabled = $true + } + + $getCMDiscoveryEnabled = @{ + Props = @( + @{ + PropertyName = 'Enable Incremental Sync' + Value = 1 + } + @{ + PropertyName = 'Startup Schedule' + Value1 = '000120000015A000' + } + @{ + PropertyName = 'Full Sync Schedule' + Value1 = '000120000015A000' + } + @{ + PropertyName = 'Settings' + Value1 = 'Active' + } + ) + PropLists = @( + @{ + PropertyListName = 'AD Containers' + Values = @( + 'LDAP://OU=Test,DC=contoso,DC=com' + '0' + '1' + 'LDAP://OU=Test1,DC=contoso,DC=com' + '0' + '1' + ) + } + ) + } + + $getCMDiscoveryDisabled = @{ + Props = @( + @{ + PropertyName = 'Enable Incremental Sync' + Value = 0 + } + @{ + PropertyName = 'Startup Schedule' + Value1 = '000120000015A000' + } + @{ + PropertyName = 'Full Sync Schedule' + Value1 = '000120000015A000' + } + @{ + PropertyName = 'Settings' + Value1 = 'Active' + } + ) + PropLists = @( + @{ + PropertyListName = 'AD Containers' + Values = @( + 'LDAP://OU=Test,DC=contoso,DC=com' + '0' + '1' + 'LDAP://OU=Test1,DC=contoso,DC=com' + '0' + '1' + ) + } + ) + } + + $adContainersReturn = @( + 'LDAP://OU=Test,DC=contoso,DC=com' + 'LDAP://OU=Test1,DC=contoso,DC=com' + ) + + $intervalDays = @{ + Interval = 'Days' + Count = '7' + } + + $intervalHours = @{ + Interval = 'Hours' + Count = 5 + } + + $intervalNone = @{ + Interval = 'None' + Count = $null + } + + $cmScheduleHours = @{ + DayDuration = 0 + DaySpan = 0 + HourDuration = 0 + HourSpan = 1 + IsGMT = $false + MinuteDuration = 0 + MinuteSpan = 0 + } + + $cmScheduleMins = @{ + DayDuration = 0 + DaySpan = 0 + HourDuration = 0 + HourSpan = 0 + IsGMT = $false + MinuteDuration = 0 + MinuteSpan = 45 + } + + Mock -CommandName Import-ConfigMgrPowerShellModule + Mock -CommandName Set-Location + } + + Context 'When retrieving User Discovery settings' { + + It 'Should return desired result when delta schedule returns hour' { + Mock -CommandName Get-CMDiscoveryMethod -MockWith { $getCMDiscoveryEnabled } + Mock -CommandName ConvertTo-ScheduleInterval -MockWith { $intervalDays } + Mock -CommandName Convert-CMSchedule -MockWith { $cmScheduleHours } + + $result = Get-TargetResource @getInput + $result | Should -BeOfType System.Collections.HashTable + $result.SiteCode | Should -Be -ExpectedValue 'Lab' + $result.Enabled | Should -Be -ExpectedValue $true + $result.EnableDeltaDiscovery | Should -Be -ExpectedValue $true + $result.DeltaDiscoveryMins | Should -Be -ExpectedValue 60 + $result.ADContainers | Should -Be -ExpectedValue $adContainersReturn + $result.ScheduleInterval | Should -Be -ExpectedValue 'Days' + $result.ScheduleCount | Should -Be -ExpectedValue 7 + } + + It 'Should return desired result when delta schedule returns minutes' { + Mock -CommandName Get-CMDiscoveryMethod -MockWith { $getCMDiscoveryEnabled } + Mock -CommandName ConvertTo-ScheduleInterval -MockWith { $intervalHours } + Mock -CommandName Convert-CMSchedule -MockWith { $cmScheduleMins } + + $result = Get-TargetResource @getInput + $result | Should -BeOfType System.Collections.HashTable + $result.SiteCode | Should -Be -ExpectedValue 'Lab' + $result.Enabled | Should -Be -ExpectedValue $true + $result.EnableDeltaDiscovery | Should -Be -ExpectedValue $true + $result.DeltaDiscoveryMins | Should -Be -ExpectedValue 45 + $result.ADContainers | Should -Be -ExpectedValue $adContainersReturn + $result.ScheduleInterval | Should -Be -ExpectedValue 'Hours' + $result.ScheduleCount | Should -Be -ExpectedValue 5 + } + + It 'Should return desired result when delta discovery is disabled' { + Mock -CommandName Get-CMDiscoveryMethod -MockWith { $getCMDiscoveryDisabled } + Mock -CommandName ConvertTo-ScheduleInterval -MockWith { $intervalNone } + Mock -CommandName Convert-CMSchedule + + $result = Get-TargetResource @getInput + $result | Should -BeOfType System.Collections.HashTable + $result.SiteCode | Should -Be -ExpectedValue 'Lab' + $result.Enabled | Should -Be -ExpectedValue $true + $result.EnableDeltaDiscovery | Should -Be -ExpectedValue $false + $result.DeltaDiscoveryMins | Should -Be -ExpectedValue $null + $result.ADContainers | Should -Be -ExpectedValue $adContainersReturn + $result.ScheduleInterval | Should -Be -ExpectedValue 'None' + $result.ScheduleCount | Should -Be -ExpectedValue $null + } + } + } + + Describe "ConfigMgrCBDsc - DSC_CMUserDiscovery\Set-TargetResource" -Tag 'Set' { + BeforeAll { + $adContainersReturn = @( + 'LDAP://OU=Test,DC=contoso,DC=com' + 'LDAP://OU=Test1,DC=contoso,DC=com' + ) + + $getTargetResourceStandardReturn = @{ + SiteCode = 'Lab' + Enabled = $true + EnableDeltaDiscovery = $true + DeltaDiscoveryMins = [UInt32]60 + ADContainers = $adContainersReturn + ScheduleInterval = 'Days' + ScheduleCount = 7 + } + + $getTargetResourceStandardNoSchedule = @{ + SiteCode = 'Lab' + Enabled = $true + EnableDeltaDiscovery = $true + DeltaDiscoveryMins = 60 + ADContainers = $adContainersReturn + ScheduleInterval = 'None' + ScheduleCount = $null + } + + $inputParamsHours = @{ + SiteCode = 'Lab' + Enabled = $true + ScheduleInterval = 'Hours' + ScheduleCount = 8 + } + + $adContainersMismatch = 'LDAP://OU=Test2,DC=contoso,DC=com' + + $inputParamsADContainersMismatch = @{ + SiteCode = 'Lab' + Enabled = $true + ADContainers = $adContainersMismatch + } + + Mock -CommandName Import-ConfigMgrPowerShellModule + Mock -CommandName Set-Location + Mock -CommandName Set-CMDiscoveryMethod + } + + Context 'When Set-TargetResource runs successfully' { + BeforeEach { + $inputParamsDisable = @{ + SiteCode = 'Lab' + Enabled = $false + } + + $cmScheduleNull = @{ + DayDuration = 0 + HourDuration = 0 + IsGMT = $false + MinuteDuration = 0 + } + + $inputParamsDeltaMismatch = @{ + SiteCode = 'Lab' + Enabled = $true + EnableDeltaDiscovery = $true + DeltaDiscoveryMins = 45 + } + + $inputParamsNoSchedule = @{ + SiteCode = 'Lab' + Enabled = $true + ScheduleInterval = 'None' + } + + $cmScheduleHours = @{ + DayDuration = 0 + DaySpan = 0 + HourDuration = 0 + HourSpan = 1 + IsGMT = $false + MinuteDuration = 0 + MinuteSpan = 0 + } + + $adContainersMismatch = 'LDAP://OU=Test2,DC=contoso,DC=com' + + $inputParamsADContainersInclude = @{ + SiteCode = 'Lab' + Enabled = $true + ADContainersToInclude = $adContainersMismatch + } + + $adContainersExclude = 'LDAP://OU=Test1,DC=contoso,DC=com' + + $inputParamsADContainersExclude = @{ + SiteCode = 'Lab' + Enabled = $true + ADContainersToExclude = $adContainersExclude + } + + Mock -CommandName Get-TargetResource -MockWith { $getTargetResourceStandardReturn } + } + + It 'Should call expected commands for disabling User Discovery' { + Mock -CommandName New-CMSchedule + + Set-TargetResource @inputParamsDisable + 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-CMSchedule -Exactly -Times 0 -Scope It + Assert-MockCalled Set-CMDiscoveryMethod -Exactly -Times 1 -Scope It + } + + It 'Should call expected commands for delta schedule mismatch' { + Mock -CommandName New-CMSchedule + + Set-TargetResource @inputParamsDeltaMismatch + 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-CMSchedule -Exactly -Times 0 -Scope It + Assert-MockCalled Set-CMDiscoveryMethod -Exactly -Times 1 -Scope It + } + + It 'Should call expected commands when setting schedule to none' { + Mock -CommandName New-CMSchedule -MockWith { $cmScheduleNull } + + Set-TargetResource @inputParamsNoSchedule + 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-CMSchedule -Exactly -Times 1 -Scope It + Assert-MockCalled Set-CMDiscoveryMethod -Exactly -Times 1 -Scope It + } + + It 'Should call expected commands schedule mismatch' { + Mock -CommandName New-CMSchedule -MockWith { $cmScheduleHours } -ParameterFilter { $RecurInterval -eq 'Hours' } + + Set-TargetResource @inputParamsHours + 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-CMSchedule -Exactly -Times 1 -Scope It + Assert-MockCalled Set-CMDiscoveryMethod -Exactly -Times 1 -Scope It + } + + It 'Should call expected commands for mismatch AD container' { + Mock -CommandName New-CMSchedule + + Set-TargetResource @inputParamsADContainersMismatch + 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-CMSchedule -Exactly -Times 0 -Scope It + Assert-MockCalled Set-CMDiscoveryMethod -Exactly -Times 1 -Scope It + } + + It 'Should call expected commands for include AD container' { + Mock -CommandName New-CMSchedule + + Set-TargetResource @inputParamsADContainersInclude + 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-CMSchedule -Exactly -Times 0 -Scope It + Assert-MockCalled Set-CMDiscoveryMethod -Exactly -Times 1 -Scope It + } + + It 'Should call expected commands for exclude AD container' { + Mock -CommandName New-CMSchedule + + Set-TargetResource @inputParamsADContainersExclude + 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-CMSchedule -Exactly -Times 0 -Scope It + Assert-MockCalled Set-CMDiscoveryMethod -Exactly -Times 1 -Scope It + } + } + + Context 'When running Set-TargetResource with no schedule return' { + BeforeEach { + $cmScheduleDays = @{ + DayDuration = 0 + DaySpan = 1 + HourDuration = 0 + HourSpan = 0 + IsGMT = $false + MinuteDuration = 0 + MinuteSpan = 0 + } + + Mock -CommandName Get-TargetResource -MockWith { $getTargetResourceStandardNoSchedule } + } + + It 'Should call expected commands when current schedule set to none' { + Mock -CommandName New-CMSchedule -MockWith { $cmScheduleDays } + + Set-TargetResource @inputParamsHours + 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-CMSchedule -Exactly -Times 1 -Scope It + Assert-MockCalled Set-CMDiscoveryMethod -Exactly -Times 1 -Scope It + } + } + + Context 'When running Set-TargetResource should throw' { + BeforeEach { + $inputParamsBadSchedule = @{ + SiteCode = 'Lab' + Enabled = $true + ScheduleInterval = 'Days' + } + + $inputDeltaThrow = @{ + SiteCode = 'Lab' + Enabled = $true + EnableDeltaDiscovery = $false + DeltaDiscoveryMins = 60 + } + + $adContainersExclude = 'LDAP://OU=Test1,DC=contoso,DC=com' + + $enableDeltaThrow = @{ + SiteCode = 'Lab' + Enabled = $true + EnableDeltaDiscovery = $true + } + + $inputParamsIncludeExcludeThrow = @{ + SiteCode = 'Lab' + Enabled = $true + ADContainersToInclude = $adContainersExclude + ADContainersToExclude = $adContainersExclude + } + + $deltaThrow = 'When changing delta schedule, delta schedule must be enabled.' + $scheduleThrow = "Invalid parameter usage specifying an Interval and didn't specify count." + $excludeThrow = "ADContainersToExclude and ADContainersToInclude contain to same entry $adContainersExclude, remove from one of the arrays." + $enableDeltaThrowMsg = "DeltaDiscoveryMins is not specified, specify DeltaDiscoveryMins when enabling Delta Discovery." + + Mock -CommandName Get-TargetResource -MockWith { $getTargetResourceStandardReturn } + } + + It 'Should call expected when specifying ScheduleInterval and not including ScheduleCount' { + Mock -CommandName Get-TargetResource -MockWith { $getTargetResourceStandardReturn } + Mock -CommandName New-CMSchedule + Mock -CommandName Set-CMDiscoveryMethod + + { Set-TargetResource @inputParamsBadSchedule } | Should -Throw -ExpectedMessage $scheduleThrow + 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-CMSchedule -Exactly -Times 0 -Scope It + Assert-MockCalled Set-CMDiscoveryMethod -Exactly -Times 0 -Scope It + } + + It 'Should call expected when specifying the same container in include and exclude' { + Mock -CommandName Get-TargetResource -MockWith { $getTargetResourceStandardReturn } + Mock -CommandName New-CMSchedule + Mock -CommandName Set-CMDiscoveryMethod + + { Set-TargetResource @inputParamsIncludeExcludeThrow } | Should -Throw -ExpectedMessage $excludeThrow + 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-CMSchedule -Exactly -Times 0 -Scope It + Assert-MockCalled Set-CMDiscoveryMethod -Exactly -Times 0 -Scope It + } + + It 'Should call expected when enabling delta discovery without specifying an interval' { + Mock -CommandName Get-TargetResource -MockWith { $inputDeltaThrow } + Mock -CommandName New-CMSchedule + Mock -CommandName Set-CMDiscoveryMethod + + { Set-TargetResource @enableDeltaThrow } | Should -Throw -ExpectedMessage $enableDeltaThrowMsg + 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-CMSchedule -Exactly -Times 0 -Scope It + Assert-MockCalled Set-CMDiscoveryMethod -Exactly -Times 0 -Scope It + } + + It 'Should call expected when Set-CMDiscovery throws' { + Mock -CommandName Get-TargetResource -MockWith { $getTargetResourceStandardReturn } + Mock -CommandName New-CMSchedule + Mock -CommandName Set-CMDiscoveryMethod -MockWith { throw } + + { Set-TargetResource @inputParamsADContainersMismatch } | 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-CMSchedule -Exactly -Times 0 -Scope It + Assert-MockCalled Set-CMDiscoveryMethod -Exactly -Times 1 -Scope It + } + + It 'Should call expected when new-CMSchedule throws' { + Mock -CommandName Get-TargetResource -MockWith { $getTargetResourceStandardReturn } + Mock -CommandName New-CMSchedule -MockWith { throw } + Mock -CommandName Set-CMDiscoveryMethod + + { Set-TargetResource @inputParamsHours } | 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-CMSchedule -Exactly -Times 1 -Scope It + Assert-MockCalled Set-CMDiscoveryMethod -Exactly -Times 0 -Scope It + } + + It 'Should call expected commands when delta discover is disabled and specifying delta schedule' { + Mock -CommandName Get-TargetResource -MockWith { $getTargetResourceStandardReturn } + Mock -CommandName New-CMSchedule + Mock -CommandName Set-CMDiscoveryMethod + + { Set-TargetResource @inputDeltaThrow } | Should -Throw -ExpectedMessage $deltaThrow + 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-CMSchedule -Exactly -Times 0 -Scope It + Assert-MockCalled Set-CMDiscoveryMethod -Exactly -Times 0 -Scope It + } + + It 'Should call expected when Get-TargetResource throws' { + Mock -CommandName Get-TargetResource -MockWith { throw } + Mock -CommandName New-CMSchedule + Mock -CommandName Set-CMDiscoveryMethod + + { Set-TargetResource @inputParamsHours } | 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-CMSchedule -Exactly -Times 0 -Scope It + Assert-MockCalled Set-CMDiscoveryMethod -Exactly -Times 0 -Scope It + } + } + } + + Describe "ConfigMgrCBDsc - DSC_CMUserDiscovery\Test-TargetResource" -Tag 'Test' { + BeforeAll { + $adContainersReturn = @( + 'LDAP://OU=Test,DC=contoso,DC=com' + 'LDAP://OU=Test1,DC=contoso,DC=com' + ) + + $getTargetResourceStandardReturn = @{ + SiteCode = 'Lab' + Enabled = $true + EnableDeltaDiscovery = $true + DeltaDiscoveryMins = [UInt32]60 + ADContainers = $adContainersReturn + ScheduleInterval = 'Days' + ScheduleCount = 7 + } + + $getTargetResourceStandardNoSchedule = @{ + SiteCode = 'Lab' + Enabled = $true + EnableDeltaDiscovery = $true + DeltaDiscoveryMins = 60 + ADContainers = $adContainersReturn + ScheduleInterval = 'None' + ScheduleCount = $null + } + + $inputParamsHours = @{ + SiteCode = 'Lab' + Enabled = $true + ScheduleInterval = 'Hours' + ScheduleCount = 8 + } + + $inputParamsDisable = @{ + SiteCode = 'Lab' + Enabled = $false + } + + $inputParamsNoSchedule = @{ + SiteCode = 'Lab' + Enabled = $true + ScheduleInterval = 'None' + } + + Mock -CommandName Set-Location + Mock -CommandName Import-ConfigMgrPowerShellModule + } + + Context 'When running Test-TargetResource with returned schedule settings' { + BeforeEach { + $adContainersMismatch = 'LDAP://OU=Test2,DC=contoso,DC=com' + $adContainersExclude = 'LDAP://OU=Test1,DC=contoso,DC=com' + + $iputAllParamsMatch = @{ + SiteCode = 'Lab' + Enabled = $true + EnableDeltaDiscovery = $true + DeltaDiscoveryMins = 60 + ADContainers = $adContainersReturn + ScheduleInterval = 'Days' + ScheduleCount = 7 + } + + $inputParamsDeltaMismatch = @{ + SiteCode = 'Lab' + Enabled = $true + EnableDeltaDiscovery = $true + DeltaDiscoveryMins = 45 + } + + $inputParamsADContainersMismatch = @{ + SiteCode = 'Lab' + Enabled = $true + ADContainers = $adContainersMismatch + } + + $inputParamsADContainersMultiple = @{ + SiteCode = 'Lab' + Enabled = $true + ADContainers = $adContainersMismatch + ADContainersToExclude = $adContainersExclude + } + + $inputParamsADContainersInclude = @{ + SiteCode = 'Lab' + Enabled = $true + ADContainersToInclude = $adContainersMismatch + } + + $inputParamsADContainersExclude = @{ + SiteCode = 'Lab' + Enabled = $true + ADContainersToExclude = $adContainersExclude + } + + $inputParamsIncludeExclude = @{ + SiteCode = 'Lab' + Enabled = $true + ADContainersToInclude = $adContainersExclude + ADContainersToExclude = $adContainersExclude + } + + Mock -CommandName Get-TargetResource -MockWith { $getTargetResourceStandardReturn } + } + + It 'Should return desired result true when User Discovery settings match' { + Test-TargetResource @iputAllParamsMatch | Should -Be $true + } + + It 'Should return desired result false when ad containers mismatch' { + Test-TargetResource @inputParamsIncludeExclude | Should -Be $false + } + + It 'Should return desired result false when delta schedule mismatch' { + Test-TargetResource @inputParamsDeltaMismatch | Should -Be $false + } + + It 'Should return desired result false when User Discovery schedules do not match' { + Test-TargetResource @inputParamsHours | Should -Be $false + } + + It 'Should return desired result false when User Discovery desires none schedule to be set' { + Test-TargetResource @inputParamsNoSchedule | Should -Be $false + } + + It 'Should return desired result false when User Discovery ADContainers are not correct add and remove' { + Test-TargetResource @inputParamsADContainersMismatch | Should -Be $false + } + + It 'Should return desired result false when User Discovery ADContainers and ADContainersExclude are specified' { + Test-TargetResource @inputParamsADContainersMultiple | Should -Be $false + } + + It 'Should return desired result false when User Discovery ADContainersInclude are not correct' { + Test-TargetResource @inputParamsADContainersInclude | Should -Be $false + } + + It 'Should return desired result false when User Discovery ADContainersExclude is not correct' { + Test-TargetResource @inputParamsADContainersExclude | Should -Be $false + } + + It 'Should return desired result false when User Discovery set to Enabled and expected value disabled' { + Test-TargetResource @inputParamsDisable | Should -Be $false + } + } + + Context 'When running Test-TargetResource with returned schedule settings of none' { + BeforeEach { + $inputParamsBadSchedule = @{ + SiteCode = 'Lab' + Enabled = $true + ScheduleInterval = 'Days' + } + + $disableDelta = @{ + SiteCode = 'Lab' + Enabled = $true + EnableDeltaDiscovery = $false + } + + $enableDelta = @{ + SiteCode = 'Lab' + Enabled = $true + EnableDeltaDiscovery = $true + } + + Mock -CommandName Get-TargetResource -MockWith { $getTargetResourceStandardNoSchedule } + } + + It 'Should return desired result false when current state returns null schedule' { + Test-TargetResource @inputParamsDisable | Should -Be $false + } + + It 'Should return desired result true when current schedule and desired schedule are none' { + Test-TargetResource @inputParamsNoSchedule | Should -Be $true + } + + It 'Should return desired result false when current schedule is none and desired schedule is set' { + Test-TargetResource @inputParamsHours | Should -Be $false + } + + It 'Should return desired result false when input param is setting schedule is count is missing' { + Test-TargetResource @inputParamsBadSchedule | Should -Be $false + } + + It 'Should return desired result false when enabling delta discovery without interval' { + Mock -CommandName Get-TargetResource -MockWith { $disableDelta } + + Test-TargetResource @enableDelta | Should -Be $false + } + } + } + } +} +catch +{ + Invoke-TestCleanup +}