From d7beccb6de450e8dbd04b308da8549cf5d69fb71 Mon Sep 17 00:00:00 2001 From: Tony Oliveira Date: Sun, 22 Nov 2020 12:31:26 +0000 Subject: [PATCH 1/4] Added UpdateServicesComputerTargetGroup (tests passed) --- CHANGELOG.md | 13 + ReadMe.md | 6 + RequiredModules.psd1 | 2 +- .../Helpers/ImitateUpdateServicesModule.psm1 | 189 +++++++++-- ...pdateServicesComputerTargetGroup.tests.ps1 | 269 ++++++++++++++++ ...SFT_UpdateServicesComputerTargetGroup.psm1 | 304 ++++++++++++++++++ ...dateServicesComputerTargetGroup.schema.mof | 8 + ...teServicesComputerTargetGroup.strings.psd1 | 16 + ...getGroup_AddComputerTargetGroup_Config.ps1 | 47 +++ ...Group_DeleteComputerTargetGroup_Config.ps1 | 38 +++ 10 files changed, 858 insertions(+), 34 deletions(-) create mode 100644 Tests/Unit/MSFT_UpdateServicesComputerTargetGroup.tests.ps1 create mode 100644 source/DSCResources/MSFT_UpdateServicesComputerTargetGroup/MSFT_UpdateServicesComputerTargetGroup.psm1 create mode 100644 source/DSCResources/MSFT_UpdateServicesComputerTargetGroup/MSFT_UpdateServicesComputerTargetGroup.schema.mof create mode 100644 source/DSCResources/MSFT_UpdateServicesComputerTargetGroup/en-US/MSFT_UpdateServicesComputerTargetGroup.strings.psd1 create mode 100644 source/Examples/Resources/UpdateServicesComputerTargetGroup/1-UpdateServicesComputerTargetGroup_AddComputerTargetGroup_Config.ps1 create mode 100644 source/Examples/Resources/UpdateServicesComputerTargetGroup/2-UpdateServicesComputerTargetGroup_DeleteComputerTargetGroup_Config.ps1 diff --git a/CHANGELOG.md b/CHANGELOG.md index 42319b9..be20a3e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,9 +5,22 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Added + +- Added UpdateServicesComputerTargetGroup Resource including: + - Resource + - Unit Tests + - Example + - Update to ReadMe File + ### Changed - Updated inital offline package sync WSUS.cab. +- Updated ImitateUpdateServicesModule Module to meet style guidelines + +### Fixed + +- Changed RequiredModules / Pester module version to '4.10.1' - as existing tests are incompatible with pester 5. ## [1.2.0] - 2020-05-18 diff --git a/ReadMe.md b/ReadMe.md index a2ccdcc..eba41a8 100644 --- a/ReadMe.md +++ b/ReadMe.md @@ -86,6 +86,12 @@ Windows Server 2008 R2 SP1, Windows Server 2012 and Windows Server 2012 R2. * **CleanupLocalPublishedContentFiles**: Cleanup local published content files. * **TimeOfDay** Time of day to start cleanup. +**UpdateServicesComputerTargetGroup** resource has following properties: + +* **Ensure**: An enumerated value that describes if the Computer Target Group exists. +* **Name**: Name of the Computer Target Group. +* **Path**: Path to the Computer Target Group in the format 'Parent/Child'. + **UpdateServicesServer** resource has following properties: * **Ensure**: An enumerated value that describes if WSUS is configured. diff --git a/RequiredModules.psd1 b/RequiredModules.psd1 index 7aec52c..458d380 100644 --- a/RequiredModules.psd1 +++ b/RequiredModules.psd1 @@ -10,7 +10,7 @@ InvokeBuild = 'latest' PSScriptAnalyzer = 'latest' - Pester = 'latest' + Pester = '4.10.1' Plaster = 'latest' ModuleBuilder = '1.0.0' ChangelogManagement = 'latest' diff --git a/Tests/Helpers/ImitateUpdateServicesModule.psm1 b/Tests/Helpers/ImitateUpdateServicesModule.psm1 index 7f39be7..459eea3 100644 --- a/Tests/Helpers/ImitateUpdateServicesModule.psm1 +++ b/Tests/Helpers/ImitateUpdateServicesModule.psm1 @@ -1,26 +1,27 @@ -function Get-WsusServer { +function Get-WsusServer +{ $WsusServer = [pscustomobject] @{ Name = 'ServerName' - } + } $ApprovalRule = [scriptblock]{ - $ApprovalRule = [pscustomobject]@{ - Name = 'ServerName' - Enabled = $true + $ApprovalRule = [pscustomobject] @{ + Name = 'ServerName' + Enabled = $true } - + $ApprovalRule | Add-Member -MemberType ScriptMethod -Name GetUpdateClassifications -Value { - $UpdateClassification = [pscustomobject]@{ + $UpdateClassification = [pscustomobject] @{ Name = 'Update Classification' - ID = [pscustomobject]@{ + ID = [pscustomobject] @{ GUID = '00000000-0000-0000-0000-0000testguid' } + } + return $UpdateClassification } - return $UpdateClassification - } $ApprovalRule | Add-Member -MemberType ScriptMethod -Name GetCategories -Value { - $Products = [pscustomobject]@{ + $Products = [pscustomobject] @{ Title = 'Product' } $Products | Add-Member -MemberType ScriptMethod -Name Add -Value {} @@ -28,7 +29,7 @@ function Get-WsusServer { } $ApprovalRule | Add-Member -MemberType ScriptMethod -Name GetComputerTargetGroups -Value { - $ComputerTargetGroups = [pscustomobject]@{ + $ComputerTargetGroups = [pscustomobject] @{ Name = 'Computer Target Group' } $ComputerTargetGroups | Add-Member -MemberType ScriptMethod -Name Add -Value {} @@ -46,34 +47,155 @@ function Get-WsusServer { return $ApprovalRule } + $ComputerTargetGroups = [scriptblock]{ + $ComputerTargetGroups = @( + [pscustomobject] @{ + Name = 'All Computers' + Id = [pscustomobject] @{ + GUID = '4be27a8d-b969-4a8a-9cae-ec6b3a282b0b' + } + }, + [pscustomobject] @{ + Name = 'Servers' + Id = [pscustomobject] @{ + GUID = '14adceba-ddf3-4299-9c1a-e4cf8bd56c47' + } + ParentTargetGroup = [pscustomobject] @{ + Name = 'All Computers' + Id = [pscustomobject] @{ + GUID = '4be27a8d-b969-4a8a-9cae-ec6b3a282b0b' + } + } + ChildTargetGroup = [pscustomobject] @{ + Name = 'Web' + Id = [pscustomobject] @{ + GUID = 'f4aa59c7-e6a0-4e6d-97b0-293d00a0dc60' + } + } + }, + [pscustomobject] @{ + Name = 'Web' + Id = [pscustomobject] @{ + GUID = 'f4aa59c7-e6a0-4e6d-97b0-293d00a0dc60' + } + ParentTargetGroup = [pscustomobject] @{ + Name = 'Servers' + Id = [pscustomobject] @{ + GUID = '14adceba-ddf3-4299-9c1a-e4cf8bd56c47' + } + ParentTargetGroup = [pscustomobject] @{ + Name = 'All Computers' + Id = [pscustomobject] @{ + GUID = '4be27a8d-b969-4a8a-9cae-ec6b3a282b0b' + } + } + } + }, + [pscustomobject] @{ + Name = 'Workstations' + Id = [pscustomobject] @{ + GUID = '31742fd8-df6f-4836-82b4-b2e52ee4ba1b' + } + ParentTargetGroup = [pscustomobject] @{ + Name = 'All Computers' + Id = [pscustomobject] @{ + GUID = '4be27a8d-b969-4a8a-9cae-ec6b3a282b0b' + } + } + }, + [pscustomobject] @{ + Name = 'Desktops' + Id = [pscustomobject] @{ + GUID = '2b77a9ce-f320-41c7-bec7-9b22f67ae5b1' + } + ParentTargetGroup = [pscustomobject] @{ + Name = 'Workstations' + Id = [pscustomobject] @{ + GUID = '31742fd8-df6f-4836-82b4-b2e52ee4ba1b' + } + ParentTargetGroup = [pscustomobject] @{ + Name = 'All Computers' + Id = [pscustomobject] @{ + GUID = '4be27a8d-b969-4a8a-9cae-ec6b3a282b0b' + } + } + } + } + ) + + foreach ($ComputerTargetGroup in $ComputerTargetGroups) + { + Add-Member -InputObject $ComputerTargetGroup -MemberType ScriptMethod -Name Delete -Value {} + + Add-Member -InputObject $ComputerTargetGroup -MemberType ScriptMethod -Name GetParentTargetGroup -Value { + return $this.ParentTargetGroup + } + + if ($null -ne $ComputerTargetGroup.ParentTargetGroup) + { + Add-Member -InputObject $ComputerTargetGroup.ParentTargetGroup -MemberType ScriptMethod -Name GetParentTargetGroup -Value { + return $this.ParentTargetGroup + } + } + + if ($null -ne $ComputerTargetGroup.ChildTargetGroup) + { + Add-Member -InputObject $ComputerTargetGroup -MemberType ScriptMethod -Name GetChildTargetGroups -Value { + return $this.ChildTargetGroup + } + + Add-Member -InputOBject $ComputerTargetGroup.ChildTargetGroup -MemberType ScriptMethod -Name Delete -Value {} + } + } + + return $ComputerTargetGroups + } + + $WsusServer | Add-Member -MemberType ScriptMethod -Name CreateComputerTargetGroup -Value { + param + ( + [Parameter(Mandatory = $true)] + [string] + $Name, + + [Parameter(Mandatory = $true)] + [object] + $ComputerTargetGroup + ) + { + Write-Output $Name + Write-Output $ComputerTargetGroup + } + } + $WsusServer | Add-Member -MemberType ScriptMethod -Name GetInstallApprovalRules -Value $ApprovalRule $WsusServer | Add-Member -MemberType ScriptMethod -Name CreateInstallApprovalRule -Value $ApprovalRule $WsusServer | Add-Member -MemberType ScriptMethod -Name GetUpdateClassification -Value {} - - $WsusServer | Add-Member -MemberType ScriptMethod -Name GetComputerTargetGroups -Value {} + + $WsusServer | Add-Member -MemberType ScriptMethod -Name GetComputerTargetGroups -Value $ComputerTargetGroups $WsusServer | Add-Member -MemberType ScriptMethod -Name DeleteInstallApprovalRule -Value {} - + $WsusServer | Add-Member -MemberType ScriptMethod -Name GetSubscription -Value { - $Subscription = [pscustomobject]@{ + $Subscription = [pscustomobject] @{ SynchronizeAutomaticallyTimeOfDay = '04:00:00' NumberOfSynchronizationsPerDay = 24 SynchronizeAutomatically = $true } $Subscription | Add-Member -MemberType ScriptMethod -Name StartSynchronization -Value {} $Subscription | Add-Member -MemberType ScriptMethod -Name GetUpdateClassifications -Value { - $UpdateClassification = [pscustomobject]@{ - Name = 'Update Classification' - ID = [pscustomobject]@{ - GUID = '00000000-0000-0000-0000-0000testguid' - } + $UpdateClassification = [pscustomobject] @{ + Name = 'Update Classification' + ID = [pscustomobject] @{ + GUID = '00000000-0000-0000-0000-0000testguid' + } } return $UpdateClassification } $Subscription | Add-Member -MemberType ScriptMethod -Name GetUpdateCategories -Value { - $Categories = [pscustomobject]@{ + $Categories = [pscustomobject] @{ Title = 'Category' } return $Categories @@ -93,21 +215,21 @@ function Get-WsusServer { AllUpdateLanguagesEnabled = $true } $Configuration | Add-Member -MemberType ScriptMethod -Name GetEnabledUpdateLanguages -Value {} - return $Configuration + return $Configuration } $WsusServer | Add-Member -MemberType ScriptMethod -Name GetUpdateClassifications -Value { - $UpdateClassification = [pscustomobject]@{ - Name = 'Update Classification' - ID = [pscustomobject]@{ - GUID = '00000000-0000-0000-0000-0000testguid' - } + $UpdateClassification = [pscustomobject] @{ + Name = 'Update Classification' + ID = [pscustomobject]@{ + GUID = '00000000-0000-0000-0000-0000testguid' + } } return $UpdateClassification } $WsusServer | Add-Member -MemberType ScriptMethod -Name GetUpdateCategories -Value { - $Categories = [pscustomobject]@{ + $Categories = [pscustomobject] @{ Title = 'Category' } return $Categories @@ -116,10 +238,11 @@ function Get-WsusServer { return $WsusServer } -function Get-WsusClassification { - $WsusClassification = [pscustomobject]@{ - Classification = [pscustomobject]@{ - ID = [pscustomobject]@{ +function Get-WsusClassification +{ + $WsusClassification = [pscustomobject] @{ + Classification = [pscustomobject] @{ + ID = [pscustomobject] @{ Guid = '00000000-0000-0000-0000-0000testguid' } } diff --git a/Tests/Unit/MSFT_UpdateServicesComputerTargetGroup.tests.ps1 b/Tests/Unit/MSFT_UpdateServicesComputerTargetGroup.tests.ps1 new file mode 100644 index 0000000..5e817b1 --- /dev/null +++ b/Tests/Unit/MSFT_UpdateServicesComputerTargetGroup.tests.ps1 @@ -0,0 +1,269 @@ +$script:dscModuleName = 'UpdateServicesDsc' +$script:dscResourceName = 'MSFT_UpdateServicesComputerTargetGroup' + +#region HEADER +$script:moduleRoot = Split-Path -Parent (Split-Path -Parent $PSScriptRoot) + +Import-Module -Name DscResource.Test -Force -ErrorAction Stop + +$TestEnvironment = Initialize-TestEnvironment ` + -DSCModuleName $script:dscModuleName ` + -DSCResourceName $script:dscResourceName ` + -ResourceType 'Mof' ` + -TestType Unit + +#endregion HEADER + + +# Begin Testing +try +{ + InModuleScope $script:DSCResourceName { + + #region Pester Test Initialization + Import-Module $PSScriptRoot\..\Helpers\ImitateUpdateServicesModule.psm1 -Force + + #endregion + + #region Function Get-ComputerTargetGroupPath + Describe "MSFT_UpdateServicesComputerTargetGroup\Get-ComputerTargetGroupPath." { + $WsusServer = Get-WsusServer + + Context "The Function returns expected path for the 'Desktops' ComputerTargetGroup." { + $ComputerTargetGroup = $WsusServer.GetComputerTargetGroups() | Where-Object -FilterScript { $_.Name -eq 'Desktops' } + $result = Get-ComputerTargetGroupPath -ComputerTargetGroup $ComputerTargetGroup + $result | Should -Be 'All Computers/Workstations' + } + + Context "The Function returns expected path for the 'Workstations' ComputerTargetGroup." { + $ComputerTargetGroup = $WsusServer.GetComputerTargetGroups() | Where-Object -FilterScript { $_.Name -eq 'Workstations' } + $result = Get-ComputerTargetGroupPath -ComputerTargetGroup $ComputerTargetGroup + $result | Should -Be 'All Computers' + } + } + #endregion + + #region Function Get-TargetResource + Describe "MSFT_UpdateServicesComputerTargetGroup\Get-TargetResource." { + Mock -CommandName Write-Verbose -MockWith {} + if (Test-Path -Path variable:script:resource) { Remove-Variable -Scope 'script' -Name 'resource' } + + Context 'An error occurs retrieving WSUS Server configuration information.' { + Mock -CommandName Get-WsusServer -MockWith { throw 'An error occurred.' } + + It 'Calling Get should throw when an error occurrs retrieving WSUS Server information.' { + { $script:resource = Get-TargetResource -Name 'Servers' -Path 'All Computers'} | Should -Throw ($script:localizedData.WSUSConfigurationFailed) + $script:resource | Should -Be $null + Assert-MockCalled -CommandName Get-WsusServer -Exactly 1 + } + } + + Context 'The WSUS Server is not yet configured.' { + Mock -CommandName Get-WsusServer -MockWith {} + + It 'Calling Get should not throw when the WSUS Server is not yet configuration / cannot be found.' { + { $script:resource = Get-TargetResource -Name 'Servers' -Path 'All Computers'} | Should -Not -Throw + Assert-MockCalled -CommandName Write-Verbose -ParameterFilter { + $message -eq $script:localizedData.GetWsusServerFailed + } + $script:resource.Ensure | Should -Be 'Absent' + $script:resource.Id | should -Be $null + $script:resource.Name | should -Be 'Servers' + $script:resource.Path | should -Be 'All Computers' + } + } + + Context 'The Computer Target Group is not in the desired state (specified name does not exist at any path).' { + It 'Calling Get should return absent when Computer Target Group does not exist at any path.' { + $resource = Get-TargetResource -Name 'Domain Controllers' -Path 'All Computers' + $resource.Ensure | Should -Be 'Absent' + Assert-MockCalled -CommandName Write-Verbose -ParameterFilter { + $message -eq ($script:localizedData.GetWsusServerSucceeded -f 'ServerName') + } + Assert-MockCalled -CommandName Write-Verbose -ParameterFilter { + $message -eq ($script:localizedData.NotFoundComputerTargetGroup -f 'Domain Controllers', 'All Computers') + } + + } + } + + Context 'The Computer Target Group is not in the desired state (specified name exists but not at the desired path).' { + It 'Calling Get should return absent when Computer Target Group does not exist at the specified path.' { + $resource = Get-TargetResource -Name 'Desktops' -Path 'All Computers/Servers' + $resource.Ensure | Should -Be 'Absent' + Assert-MockCalled -CommandName Write-Verbose -ParameterFilter { + $message -eq ($script:localizedData.NotFoundComputerTargetGroup -f 'Desktops', 'All Computers/Servers') + } + } + } + + Context 'The Computer Target Group is in the desired state (specified name exists with the desired path).' { + It 'Calling Get should return present when Computer Target Group does exist at the specified path.' { + $resource = Get-TargetResource -Name 'Desktops' -Path 'All Computers/Workstations' + Assert-MockCalled -CommandName Write-Verbose -ParameterFilter { + $message -eq ($script:localizedData.FoundComputerTargetGroup -f ` + 'Desktops', 'All Computers/Workstations', '2b77a9ce-f320-41c7-bec7-9b22f67ae5b1') + } + $resource.Ensure | Should -Be 'Present' + $resource.Id | Should -Be '2b77a9ce-f320-41c7-bec7-9b22f67ae5b1' + + } + } + } + #endregion + + #region Function Test-TargetResource + Describe "MSFT_UpdateServicesComputerTargetGroup\Test-TargetResource." { + Mock -CommandName Write-Verbose -MockWith {} + + Context 'The Computer Target Group "Desktops" is "Present" at Path "All Computers/Workstations" which is the desired state.' { + Mock -CommandName Get-TargetResource -MockWith { + return @{ + Ensure = 'Present' + Name = 'Desktops' + Path = 'All Computers/Workstations' + Id = '2b77a9ce-f320-41c7-bec7-9b22f67ae5b1' + } + } + + It 'Test-TargetResource should return $true when Computer Target Resource is in the desired state.' { + $resource = Test-TargetResource -Name 'Desktops' -Path 'All Computers/Workstations' + $resource | Should -Be $true + Assert-MockCalled -CommandName Write-Verbose -ParameterFilter { + $message -eq ($script:localizedData.ResourceInDesiredState -f ` + 'Desktops', 'All Computers/Workstations', 'Present') + } + } + } + + Context 'The Computer Target Group "Desktops" is "Absent" at Path "All Computers/Workstations" which is the desired state (Present).' { + Mock -CommandName Get-TargetResource -MockWith { + return @{ + Ensure = 'Absent' + Name = 'Desktops' + Path = 'All Computers/Workstations' + Id = $null + } + } + + It 'Test-TargetResource should return $true when Computer Target Resource is in the desired state.' { + $resource = Test-TargetResource -Name 'Desktops' -Path 'All Computers/Workstations' -Ensure 'Absent' + $resource | Should -Be $true + Assert-MockCalled -CommandName Write-Verbose -ParameterFilter { + $message -eq ($script:localizedData.ResourceInDesiredState -f ` + 'Desktops', 'All Computers/Workstations', 'Absent') + } + } + } + + Context 'The Computer Target Group "Desktops" is "Present" at Path "All Computers/Workstations" which is NOT the desired state.' { + Mock -CommandName Get-TargetResource -MockWith { + return @{ + Ensure = 'Present' + Name = 'Desktops' + Path = 'All Computers/Workstations' + Id = '2b77a9ce-f320-41c7-bec7-9b22f67ae5b1' + } + } + + It 'Test-TargetResource should return $false when Computer Target Resource is NOT in the desired state.' { + $resource = Test-TargetResource -Name 'Desktops' -Path 'All Computers/Workstations' -Ensure 'Absent' + $resource | Should -Be $false + Assert-MockCalled -CommandName Write-Verbose -ParameterFilter { + $message -eq ($script:localizedData.ResourceNotInDesiredState -f ` + 'Desktops', 'All Computers/Workstations', 'Present') + } + } + } + + Context 'The Computer Target Group "Desktops" is "Absent" at Path "All Computers/Workstations" which is NOT the desired state (Present).' { + Mock -CommandName Get-TargetResource -MockWith { + return @{ + Ensure = 'Absent' + Name = 'Desktops' + Path = 'All Computers/Workstations' + Id = $null + } + } + + It 'Test-TargetResource should return $false when Computer Target Resource is NOT in the desired state.' { + $resource = Test-TargetResource -Name 'Desktops' -Path 'All Computers/Workstations' -Ensure 'Present' + $resource | Should -Be $false + Assert-MockCalled -CommandName Write-Verbose -ParameterFilter { + $message -eq ($script:localizedData.ResourceNotInDesiredState -f ` + 'Desktops', 'All Computers/Workstations', 'Absent') + } + } + } + } + #endregion + + #region Function Set-TargetResource + Describe "MSFT_UpdateServicesComputerTargetGroup\Set-TargetResource" { + Mock -CommandName Write-Verbose -MockWith {} + if (Test-Path -Path variable:script:resource) { Remove-Variable -Scope 'script' -Name 'resource' } + + Context 'An error occurs retrieving WSUS Server configuration information.' { + Mock -CommandName Get-WsusServer -MockWith { throw 'An error occurred.' } + + It 'Calling Set should throw when an error occurrs retrieving WSUS Server information.' { + { $script:resource = Set-TargetResource -Name 'Servers' -Path 'All Computers'} | Should -Throw ($script:localizedData.WSUSConfigurationFailed) + $script:resource | Should -Be $null + Assert-MockCalled -CommandName Get-WsusServer -Exactly 1 + } + } + + Context 'The WSUS Server is not yet configured.' { + Mock -CommandName Get-WsusServer -MockWith {} + + It 'Calling Set should not throw when the WSUS Server is not yet configuration / cannot be found.' { + { $script:resource = Set-TargetResource -Name 'Servers' -Path 'All Computers'} | Should -Not -Throw + Assert-MockCalled -CommandName Write-Verbose -ParameterFilter { + $message -eq $script:localizedData.GetWsusServerFailed + } + $script:resource | Should -Be $null + } + } + + Context 'The Parent of the Computer Target Group is not present and therefore the new group cannot be created.' { + Mock -CommandName Write-Warning -MockWith {} + + It 'Calling Set where the Parent of the Computer Target Group does not exist generates a warning message.' { + { $script:resource = Set-TargetResource -Name 'Win10' -Path 'All Computers/Desktops'} | Should -Not -Throw + Assert-MockCalled -CommandName Write-Warning -ParameterFilter { + $message -eq ($script:localizedData.NotFoundParentComputerTargetGroup -f 'Desktops', ` + 'All Computers', 'Win10') + } + } + } + + Context 'The new Computer Target Group is successfully created.' { + It 'Calling Set where Computer Target Group does not exist and Ensure is "Present" creates the required group.' { + { $script:resource = Set-TargetResource -Name 'Database' -Path 'All Computers/Servers'} | Should -Not -Throw + Assert-MockCalled Write-Verbose -ParameterFilter { + $message -eq ($script:localizedData.CreateComputerTargetGroupSuccess -f 'Database', ` + 'All Computers/Servers') + } + } + } + + Context 'The new Computer Target Group is successfully deleted.' { + It 'Calling Set where Computer Target Group exists and Ensure is "Absent" deletes the required group.' { + { $script:resource = Set-TargetResource -Name 'Web' -Path 'All Computers/Servers' -Ensure 'Absent' } | Should -Not -Throw + Assert-MockCalled Write-Verbose -ParameterFilter { + $message -eq ($script:localizedData.DeleteComputerTargetGroupSuccess -f 'Web', ` + 'f4aa59c7-e6a0-4e6d-97b0-293d00a0dc60', 'All Computers/Servers') + } + } + } + } + #endregion + } +} + +finally +{ + #region FOOTER + Restore-TestEnvironment -TestEnvironment $TestEnvironment + #endregion +} diff --git a/source/DSCResources/MSFT_UpdateServicesComputerTargetGroup/MSFT_UpdateServicesComputerTargetGroup.psm1 b/source/DSCResources/MSFT_UpdateServicesComputerTargetGroup/MSFT_UpdateServicesComputerTargetGroup.psm1 new file mode 100644 index 0000000..002003b --- /dev/null +++ b/source/DSCResources/MSFT_UpdateServicesComputerTargetGroup/MSFT_UpdateServicesComputerTargetGroup.psm1 @@ -0,0 +1,304 @@ +# DSC resource to manage WSUS Computer Target Groups. + +# Load Common Module +$script:resourceHelperModulePath = Join-Path -Path $PSScriptRoot -ChildPath '..\..\Modules\DscResource.Common' +Import-Module -Name $script:resourceHelperModulePath +$script:localizedData = Get-LocalizedData -DefaultUICulture 'en-US' -FileName 'MSFT_UpdateServicesComputerTargetGroup.strings.psd1' + + +<# + .SYNOPSIS + Retrieves the current state of the WSUS Computer Target Group. + + The returned object provides the following properties: + Name: The Name of the WSUS Computer Target Group. + Path: The Path to the Parent of the Computer Target Group. + Id: The Id / GUID of the WSUS Computer Target Group. + .PARAMETER Name + The Name of the WSUS Computer Target Group. + + .PARAMETER Path + The Path to the WSUS Compter Target Group in the format 'Parent/Child'. +#> +function Get-TargetResource +{ + [CmdletBinding()] + [OutputType([System.Collections.Hashtable])] + param + ( + [Parameter(Mandatory = $true)] + [System.String] + $Name, + + [Parameter(Mandatory = $true)] + [System.String] + $Path + ) + + try + { + $WsusServer = Get-WsusServer + $Ensure = 'Absent' + $Id = $null + + if ($null -ne $WsusServer) + { + Write-Verbose -Message ($script:localizedData.GetWsusServerSucceeded -f $WsusServer.Name) + $ComputerTargetGroups = $WsusServer.GetComputerTargetGroups() | Where-Object -FilterScript { $_.Name -eq $Name } + + if ($null -ne $ComputerTargetGroups) + { + foreach ($ComputerTargetGroup in $ComputerTargetGroups) + { + $ComputerTargetGroupPath = Get-ComputerTargetGroupPath -ComputerTargetGroup $ComputerTargetGroup + if ($Path -eq $ComputerTargetGroupPath) + { + $Ensure = 'Present' + $Id = $ComputerTargetGroup.Id.Guid + Write-Verbose -Message ($script:localizedData.FoundComputerTargetGroup -f $Name, $Path, $Id) + } + } + } + } + else + { + Write-Verbose -Message $script:localizedData.GetWsusServerFailed + } + } + catch + { + New-InvalidOperationException -Message $script:localizedData.WSUSConfigurationFailed -ErrorRecord $_ + } + + if ($null -eq $Id) + { + Write-Verbose -Message ($script:localizedData.NotFoundComputerTargetGroup -f $Name, $Path) + } + + $returnValue = @{ + Ensure = $Ensure + Name = $Name + Path = $Path + Id = $Id + } + + return $returnValue +} + +<# + .SYNOPSIS + Sets the state of the WSUS Computer Target Group. + + .PARAMETER Ensure + Determines if the Computer Target Group should be created or removed. + Accepts 'Present' (default) or 'Absent'. + + .PARAMETER Name + Name of the Computer Target Group. + + .PARAMETER Path + The Path to the Computer Target Group in the format 'Parent/Child'. +#> +function Set-TargetResource +{ + [CmdletBinding()] + param + ( + [Parameter()] + [ValidateSet('Present', 'Absent')] + [System.String] + $Ensure = 'Present', + + [Parameter(Mandatory = $true)] + [System.String] + $Name, + + [Parameter(Mandatory = $true)] + [System.String] + $Path + ) + + try + { + $WsusServer = Get-WsusServer + + # break down path to identify the parent computer target group based on name and its own unique path + $ParentComputerTargetGroupName = (($Path -split "/")[-1]) + $ParentComputerTargetGroupPath = ($Path -replace "[/]$ParentComputerTargetGroupName", "") + + if ($null -ne $WsusServer) + { + $ParentComputerTargetGroups = $WsusServer.GetComputerTargetGroups() | Where-Object -FilterScript { + $_.Name -eq $ParentComputerTargetGroupName + } + + if ($null -ne $ParentComputerTargetGroups) + { + foreach ($ParentComputerTargetGroup in $ParentComputerTargetGroups) + { + $ComputerTargetGroupPath = Get-ComputerTargetGroupPath -ComputerTargetGroup $ParentComputerTargetGroup + if ($ParentComputerTargetGroupPath -eq $ComputerTargetGroupPath) + { + # parent Computer Target Group Exists + Write-Verbose -Message ($script:localizedData.FoundParentComputerTargetGroup -f $ParentComputerTargetGroupName, ` + $ParentComputerTargetGroupPath, $ParentComputerTargetGroup.Id.Guid) + + # create the new Computer Target Group if Ensure -eq 'Present' + if ($Ensure -eq 'Present') + { + try + { + $WsusServer.CreateComputerTargetGroup($Name, $ParentComputerTargetGroup) | Out-Null + Write-Verbose -Message ($script:localizedData.CreateComputerTargetGroupSuccess -f $Name, $Path) + return + } + catch + { + New-InvalidOperationException -Message ( + $script:localizedData.CreateComputerTargetGroupFailed -f $Name, $Path + ) -ErrorRecord $_ + } + } + else + { + # $Ensure -eq 'Absent' - must call the Delete() method on the group itself for removal + try + { + $ChildComputerTargetGroup = $ParentComputerTargetGroup.GetChildTargetGroups() | Where-Object -FilterScript { + $_.Name -eq $Name + } + $ChildComputerTargetGroup.Delete() | Out-Null + Write-Verbose -Message ($script:localizedData.DeleteComputerTargetGroupSuccess -f $Name, ` + $ChildComputerTargetGroup.Id.Guid, $Path) + return + } + catch + { + New-InvalidOperationException -Message ( + $script:localizedData.DeleteComputerTargetGroupFailed -f $Name, ` + $ChildComputerTargetGroup.Id.Guid, $Path + ) -ErrorRecord $_ + } + } + } + } + } + + Write-Warning -Message ($script:localizedData.NotFoundParentComputerTargetGroup -f $ParentComputerTargetGroupName, ` + $ParentComputerTargetGroupPath, $Name) + } + else + { + Write-Verbose -Message $script:localizedData.GetWsusServerFailed + } + } + catch + { + New-InvalidOperationException -Message $script:localizedData.WSUSConfigurationFailed -ErrorRecord $_ + } +} + +<# + .SYNOPSIS + Tests the current state of the WSUS Computer Target Group. + + .PARAMETER Ensure + Determines if the Computer Target Group should be created or removed. + Accepts 'Present' (default) or 'Absent'. + + .PARAMETER Name + Name of the Computer Target Group + + .PARAMETER Path + The Path to the Computer Target Group in the format 'Parent/Child'. +#> +function Test-TargetResource +{ + [CmdletBinding()] + [OutputType([System.Boolean])] + param + ( + [Parameter()] + [ValidateSet('Present', 'Absent')] + [System.String] + $Ensure = 'Present', + + [Parameter(Mandatory = $true)] + [System.String] + $Name, + + [Parameter(Mandatory = $true)] + [System.String] + $Path + ) + + $result = Get-TargetResource -Name $Name -Path $Path + + if ($Ensure -eq $result.Ensure) + { + Write-Verbose -Message ($script:localizedData.ResourceInDesiredState -f $Name, $Path, $result.Ensure) + return $true + } + else + { + Write-Verbose -Message ($script:localizedData.ResourceNotInDesiredState -f $Name, $Path, $result.Ensure) + return $false + } +} + + +<# + .SYNOPSIS + Gets the Computer Target Group Path within WSUS by recursing up through each Parent Computer Target Group + + .PARAMETER ComputerTargetGroup + The Computer TargetGroup +#> +function Get-ComputerTargetGroupPath +{ + [CmdletBinding()] + [OutputType([System.String])] + param + ( + [Parameter(Mandatory = $true)] + [object] + $ComputerTargetGroup + ) + + $computerTargetGroupPath = "" + $computerTargetGroupParents = @() + $moreParentContainers = $true + $x = 0 + + do + { + try + { + $ComputerTargetGroup = $ComputerTargetGroup.GetParentTargetGroup() + $computerTargetGroupParents += $ComputerTargetGroup.Name + } + catch + { + # 'All Computers' container throws an exception when GetParentTargetGroup() method called + $moreParentContainers = $false + } + + $x++ + } while ($moreParentContainers -and ($x -lt 20)) + + for ($i=($computerTargetGroupParents.Count - 1); $i -ge 0; $i--) + { + if ("" -ne $computerTargetGroupPath) + { + $computerTargetGroupPath += ("/" + $computerTargetGroupParents[$i]) + } + else + { + $computerTargetGroupPath += $computerTargetGroupParents[$i] + } + } + + return $computerTargetGroupPath +} + +Export-ModuleMember -Function *-TargetResource diff --git a/source/DSCResources/MSFT_UpdateServicesComputerTargetGroup/MSFT_UpdateServicesComputerTargetGroup.schema.mof b/source/DSCResources/MSFT_UpdateServicesComputerTargetGroup/MSFT_UpdateServicesComputerTargetGroup.schema.mof new file mode 100644 index 0000000..a37c260 --- /dev/null +++ b/source/DSCResources/MSFT_UpdateServicesComputerTargetGroup/MSFT_UpdateServicesComputerTargetGroup.schema.mof @@ -0,0 +1,8 @@ +[ClassVersion("1.0.0.0"), FriendlyName("UpdateServicesComputerTargetGroup")] +class MSFT_UpdateServicesComputerTargetGroup : OMI_BaseResource +{ + [Write, Description("An enumerated value that describes if the WSUS Computer Target Group is configured.\nPresent {default} \nAbsent \n"), ValueMap{"Present","Absent"}, Values{"Present","Absent"}] String Ensure; + [Key, Description("Name of the Computer Target Group.")] String Name; + [Key, Description("Path to the Computer Target Group.")] String Path; + [Read, Description("ID / GUID of the Computer Target Group.")] String Id; +}; diff --git a/source/DSCResources/MSFT_UpdateServicesComputerTargetGroup/en-US/MSFT_UpdateServicesComputerTargetGroup.strings.psd1 b/source/DSCResources/MSFT_UpdateServicesComputerTargetGroup/en-US/MSFT_UpdateServicesComputerTargetGroup.strings.psd1 new file mode 100644 index 0000000..5302d64 --- /dev/null +++ b/source/DSCResources/MSFT_UpdateServicesComputerTargetGroup/en-US/MSFT_UpdateServicesComputerTargetGroup.strings.psd1 @@ -0,0 +1,16 @@ +# Localized Strings for UpdateServicesApprovalRule resource +ConvertFrom-StringData @' +GetWsusServerFailed = Get-WsusServer failed to return a WSUS Server. The server may not yet have been configured. +WSUSConfigurationFailed = WSUS Computer Target Group configuration failed. +GetWsusServerSucceeded = WSUS Server information has been successfully retrieved from server '{0}'. +NotFoundComputerTargetGroup = A Computer Target Group with Name '{0}' was not found at Path '{1}'. +FoundComputerTargetGroup = Successfully located Computer Target Group with Name '{0}' at Path '{1}' with ID '{2}'. +ResourceInDesiredState = The Computer Target Group '{0}' at Path '{1}' is '{2}' which is the desired state. +ResourceNotInDesiredState = The Computer Target Group '{0}' at Path '{1}' is '{2}' which is NOT the desired state. +FoundParentComputerTargetGroup = Successfully located Parent Computer Target Group with Name '{0}' at Path '{1}' with ID '{2}'. +NotFoundParentComputerTargetGroup = The Parent Computer Target Group with Name '{0}' was not found at Path '{1}'. The new Computer Target Group '{2}' cannot be created. +CreateComputerTargetGroupFailed = An error occurred creating the Computer TargetGroup '{0}' at Path '{1}'. +CreateComputerTargetGroupSuccess = The Computer Target Group '{0}' was successfully created at Path '{1}'. +DeleteComputerTargetGroupFailed = An error occurred deleting the Computer TargetGroup '{0}' with ID '{1}' from Path '{2}'. +DeleteComputerTargetGroupSuccess = The Computer Target Group '{0}' with ID '{1}' was successfully deleted from Path '{2}'. +'@ diff --git a/source/Examples/Resources/UpdateServicesComputerTargetGroup/1-UpdateServicesComputerTargetGroup_AddComputerTargetGroup_Config.ps1 b/source/Examples/Resources/UpdateServicesComputerTargetGroup/1-UpdateServicesComputerTargetGroup_AddComputerTargetGroup_Config.ps1 new file mode 100644 index 0000000..87aace0 --- /dev/null +++ b/source/Examples/Resources/UpdateServicesComputerTargetGroup/1-UpdateServicesComputerTargetGroup_AddComputerTargetGroup_Config.ps1 @@ -0,0 +1,47 @@ +<#PSScriptInfo +.VERSION 1.0.0 +.GUID 07ae5437-126c-480f-a9ab-af3241614c82 +.AUTHOR DSC Community +.COMPANYNAME DSC Community +.COPYRIGHT DSC Community contributors. All rights reserved. +.TAGS DSCConfiguration +.LICENSEURI https://github.com/dsccommunity/UpdateServicesDsc/blob/master/LICENSE +.PROJECTURI https://github.com/dsccommunity/UpdateServicesDsc +.ICONURI https://dsccommunity.org/images/DSC_Logo_300p.png +.RELEASENOTES +Updated author, copyright notice, and URLs. +#> + +#Requires -Module UpdateServicesDsc + +<# + .DESCRIPTION + This configuration will create two WSUS Computer Target Groups + (a Parent and a Child) +#> +Configuration UpdateServicesComputerTargetGroup_AddComputerTargetGroup_Config +{ + param + ( + ) + + Import-DscResource -ModuleName UpdateServicesDsc + + node localhost + { + UpdateServicesComputerTargetGroup 'ComputerTargetGroup_Servers' + { + Name = 'Servers' + Path = 'All Computers' + Ensure = 'Present' + } + + UpdateServicesComputerTargetGroup 'ComputerTargetGroup_Web' + { + Name = 'Web' + Path = 'All Computers/Servers' + Ensure = 'Present' + DependsOn = '[UpdateServicesComputerTargetGroup]ComputerTargetGroup_Servers' + } + } +} diff --git a/source/Examples/Resources/UpdateServicesComputerTargetGroup/2-UpdateServicesComputerTargetGroup_DeleteComputerTargetGroup_Config.ps1 b/source/Examples/Resources/UpdateServicesComputerTargetGroup/2-UpdateServicesComputerTargetGroup_DeleteComputerTargetGroup_Config.ps1 new file mode 100644 index 0000000..6d555ff --- /dev/null +++ b/source/Examples/Resources/UpdateServicesComputerTargetGroup/2-UpdateServicesComputerTargetGroup_DeleteComputerTargetGroup_Config.ps1 @@ -0,0 +1,38 @@ +<#PSScriptInfo +.VERSION 1.0.0 +.GUID 9165945f-cd15-4865-aa3a-1ac6b6f94a8b +.AUTHOR DSC Community +.COMPANYNAME DSC Community +.COPYRIGHT DSC Community contributors. All rights reserved. +.TAGS DSCConfiguration +.LICENSEURI https://github.com/dsccommunity/UpdateServicesDsc/blob/master/LICENSE +.PROJECTURI https://github.com/dsccommunity/UpdateServicesDsc +.ICONURI https://dsccommunity.org/images/DSC_Logo_300p.png +.RELEASENOTES +Updated author, copyright notice, and URLs. +#> + +#Requires -Module UpdateServicesDsc + +<# + .DESCRIPTION + This configuration will delete a WSUS Computer Target Group +#> +Configuration UpdateServicesComputerTargetGroup_DeleteComputerTargetGroup_Config +{ + param + ( + ) + + Import-DscResource -ModuleName UpdateServicesDsc + + node localhost + { + UpdateServicesComputerTargetGroup 'ComputerTargetGroup_Web' + { + Name = 'Web' + Path = 'All Computers/Servers' + Ensure = 'Absent' + } + } +} From 8c35f79950158cda11ef92d8dfe244615d525996 Mon Sep 17 00:00:00 2001 From: Tony Oliveira Date: Mon, 23 Nov 2020 12:04:15 +0000 Subject: [PATCH 2/4] fixes issue relating to the creation of root level groups --- ..._UpdateServicesComputerTargetGroup.tests.ps1 | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/Tests/Unit/MSFT_UpdateServicesComputerTargetGroup.tests.ps1 b/Tests/Unit/MSFT_UpdateServicesComputerTargetGroup.tests.ps1 index 5e817b1..ccdd8a9 100644 --- a/Tests/Unit/MSFT_UpdateServicesComputerTargetGroup.tests.ps1 +++ b/Tests/Unit/MSFT_UpdateServicesComputerTargetGroup.tests.ps1 @@ -29,6 +29,12 @@ try Describe "MSFT_UpdateServicesComputerTargetGroup\Get-ComputerTargetGroupPath." { $WsusServer = Get-WsusServer + Context "The Function returns expected path for the 'All Computers' ComputerTargetGroup." { + $ComputerTargetGroup = $WsusServer.GetComputerTargetGroups() | Where-Object -FilterScript { $_.Name -eq 'All Computers' } + $result = Get-ComputerTargetGroupPath -ComputerTargetGroup $ComputerTargetGroup + $result | Should -Be 'All Computers' + } + Context "The Function returns expected path for the 'Desktops' ComputerTargetGroup." { $ComputerTargetGroup = $WsusServer.GetComputerTargetGroups() | Where-Object -FilterScript { $_.Name -eq 'Desktops' } $result = Get-ComputerTargetGroupPath -ComputerTargetGroup $ComputerTargetGroup @@ -237,6 +243,17 @@ try } } + Context 'The new Computer Target Group (at Root Level) is successfully created.' { + It 'Calling Set where Computer Target Group (at Root Level) does not exist and Ensure is "Present" creates the required group.' { + # { $script:resource = Set-TargetResource -Name 'Virtual Servers' -Path 'All Computers'} | Should -Not -Throw + $script:resource = Set-TargetResource -Name 'Member Servers' -Path 'All Computers' + Assert-MockCalled Write-Verbose -ParameterFilter { + $message -eq ($script:localizedData.CreateComputerTargetGroupSuccess -f 'Member Servers', ` + 'All Computers') + } + } + } + Context 'The new Computer Target Group is successfully created.' { It 'Calling Set where Computer Target Group does not exist and Ensure is "Present" creates the required group.' { { $script:resource = Set-TargetResource -Name 'Database' -Path 'All Computers/Servers'} | Should -Not -Throw From 8ef1282ee268d45c2d9ae94952eac707beba1d58 Mon Sep 17 00:00:00 2001 From: Tony Oliveira Date: Mon, 23 Nov 2020 12:05:40 +0000 Subject: [PATCH 3/4] fixes issue relating to the creation of root level groups --- .../MSFT_UpdateServicesComputerTargetGroup.psm1 | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/source/DSCResources/MSFT_UpdateServicesComputerTargetGroup/MSFT_UpdateServicesComputerTargetGroup.psm1 b/source/DSCResources/MSFT_UpdateServicesComputerTargetGroup/MSFT_UpdateServicesComputerTargetGroup.psm1 index 002003b..9ed350c 100644 --- a/source/DSCResources/MSFT_UpdateServicesComputerTargetGroup/MSFT_UpdateServicesComputerTargetGroup.psm1 +++ b/source/DSCResources/MSFT_UpdateServicesComputerTargetGroup/MSFT_UpdateServicesComputerTargetGroup.psm1 @@ -265,6 +265,11 @@ function Get-ComputerTargetGroupPath $ComputerTargetGroup ) + if ($ComputerTargetGroup.Name -eq 'All Computers') + { + return "All Computers" + } + $computerTargetGroupPath = "" $computerTargetGroupParents = @() $moreParentContainers = $true From dd2203d921ac0908f3e8ae25a7f1ccb2ee6ad5e3 Mon Sep 17 00:00:00 2001 From: Tony Oliveira Date: Tue, 24 Nov 2020 12:27:00 +0000 Subject: [PATCH 4/4] add error handling for missing parent paths and duplicate group names --- ...pdateServicesComputerTargetGroup.tests.ps1 | 31 ++++++++++--------- ...SFT_UpdateServicesComputerTargetGroup.psm1 | 24 +++++++------- ...teServicesComputerTargetGroup.strings.psd1 | 1 + 3 files changed, 30 insertions(+), 26 deletions(-) diff --git a/Tests/Unit/MSFT_UpdateServicesComputerTargetGroup.tests.ps1 b/Tests/Unit/MSFT_UpdateServicesComputerTargetGroup.tests.ps1 index ccdd8a9..5098ad4 100644 --- a/Tests/Unit/MSFT_UpdateServicesComputerTargetGroup.tests.ps1 +++ b/Tests/Unit/MSFT_UpdateServicesComputerTargetGroup.tests.ps1 @@ -51,8 +51,11 @@ try #region Function Get-TargetResource Describe "MSFT_UpdateServicesComputerTargetGroup\Get-TargetResource." { + BeforeEach { + if (Test-Path -Path variable:script:resource) { Remove-Variable -Scope 'script' -Name 'resource' } + } + Mock -CommandName Write-Verbose -MockWith {} - if (Test-Path -Path variable:script:resource) { Remove-Variable -Scope 'script' -Name 'resource' } Context 'An error occurs retrieving WSUS Server configuration information.' { Mock -CommandName Get-WsusServer -MockWith { throw 'An error occurred.' } @@ -94,12 +97,10 @@ try } Context 'The Computer Target Group is not in the desired state (specified name exists but not at the desired path).' { - It 'Calling Get should return absent when Computer Target Group does not exist at the specified path.' { - $resource = Get-TargetResource -Name 'Desktops' -Path 'All Computers/Servers' - $resource.Ensure | Should -Be 'Absent' - Assert-MockCalled -CommandName Write-Verbose -ParameterFilter { - $message -eq ($script:localizedData.NotFoundComputerTargetGroup -f 'Desktops', 'All Computers/Servers') - } + It 'Calling Get should throw when Computer Target Group does not exist at the specified path.' { + { $script:resource = Get-TargetResource -Name 'Desktops' -Path 'All Computers/Servers' } | Should -Throw ` + ($script:localizedData.DuplicateComputerTargetGroup -f 'Desktops', 'All Computers/Workstations') + $script:resource | Should -Be $null } } @@ -206,8 +207,11 @@ try #region Function Set-TargetResource Describe "MSFT_UpdateServicesComputerTargetGroup\Set-TargetResource" { + BeforeEach { + if (Test-Path -Path variable:script:resource) { Remove-Variable -Scope 'script' -Name 'resource' } + } + Mock -CommandName Write-Verbose -MockWith {} - if (Test-Path -Path variable:script:resource) { Remove-Variable -Scope 'script' -Name 'resource' } Context 'An error occurs retrieving WSUS Server configuration information.' { Mock -CommandName Get-WsusServer -MockWith { throw 'An error occurred.' } @@ -234,19 +238,16 @@ try Context 'The Parent of the Computer Target Group is not present and therefore the new group cannot be created.' { Mock -CommandName Write-Warning -MockWith {} - It 'Calling Set where the Parent of the Computer Target Group does not exist generates a warning message.' { - { $script:resource = Set-TargetResource -Name 'Win10' -Path 'All Computers/Desktops'} | Should -Not -Throw - Assert-MockCalled -CommandName Write-Warning -ParameterFilter { - $message -eq ($script:localizedData.NotFoundParentComputerTargetGroup -f 'Desktops', ` + It 'Calling Set where the Parent of the Computer Target Group does not exist throws an exception.' { + { $script:resource = Set-TargetResource -Name 'Win10' -Path 'All Computers/Desktops'} | Should -Throw ` + ($script:localizedData.NotFoundParentComputerTargetGroup -f 'Desktops', ` 'All Computers', 'Win10') - } } } Context 'The new Computer Target Group (at Root Level) is successfully created.' { It 'Calling Set where Computer Target Group (at Root Level) does not exist and Ensure is "Present" creates the required group.' { - # { $script:resource = Set-TargetResource -Name 'Virtual Servers' -Path 'All Computers'} | Should -Not -Throw - $script:resource = Set-TargetResource -Name 'Member Servers' -Path 'All Computers' + { $script:resource = Set-TargetResource -Name 'Member Servers' -Path 'All Computers'} | Should -Not -Throw Assert-MockCalled Write-Verbose -ParameterFilter { $message -eq ($script:localizedData.CreateComputerTargetGroupSuccess -f 'Member Servers', ` 'All Computers') diff --git a/source/DSCResources/MSFT_UpdateServicesComputerTargetGroup/MSFT_UpdateServicesComputerTargetGroup.psm1 b/source/DSCResources/MSFT_UpdateServicesComputerTargetGroup/MSFT_UpdateServicesComputerTargetGroup.psm1 index 9ed350c..2df8ffb 100644 --- a/source/DSCResources/MSFT_UpdateServicesComputerTargetGroup/MSFT_UpdateServicesComputerTargetGroup.psm1 +++ b/source/DSCResources/MSFT_UpdateServicesComputerTargetGroup/MSFT_UpdateServicesComputerTargetGroup.psm1 @@ -44,19 +44,21 @@ function Get-TargetResource if ($null -ne $WsusServer) { Write-Verbose -Message ($script:localizedData.GetWsusServerSucceeded -f $WsusServer.Name) - $ComputerTargetGroups = $WsusServer.GetComputerTargetGroups() | Where-Object -FilterScript { $_.Name -eq $Name } + $ComputerTargetGroup = $WsusServer.GetComputerTargetGroups() | Where-Object -FilterScript { $_.Name -eq $Name } - if ($null -ne $ComputerTargetGroups) + if ($null -ne $ComputerTargetGroup) { - foreach ($ComputerTargetGroup in $ComputerTargetGroups) + $ComputerTargetGroupPath = Get-ComputerTargetGroupPath -ComputerTargetGroup $ComputerTargetGroup + if ($Path -eq $ComputerTargetGroupPath) { - $ComputerTargetGroupPath = Get-ComputerTargetGroupPath -ComputerTargetGroup $ComputerTargetGroup - if ($Path -eq $ComputerTargetGroupPath) - { - $Ensure = 'Present' - $Id = $ComputerTargetGroup.Id.Guid - Write-Verbose -Message ($script:localizedData.FoundComputerTargetGroup -f $Name, $Path, $Id) - } + $Ensure = 'Present' + $Id = $ComputerTargetGroup.Id.Guid + Write-Verbose -Message ($script:localizedData.FoundComputerTargetGroup -f $Name, $Path, $Id) + } + else + { + # ComputerTargetGroup Names must be unique within the overall hierarchy + New-InvalidOperationException -Message ($script:localizedData.DuplicateComputerTargetGroup -f $ComputerTargetGroup.Name, $ComputerTargetGroupPath) } } } @@ -184,7 +186,7 @@ function Set-TargetResource } } - Write-Warning -Message ($script:localizedData.NotFoundParentComputerTargetGroup -f $ParentComputerTargetGroupName, ` + New-InvalidOperationException -Message ($script:localizedData.NotFoundParentComputerTargetGroup -f $ParentComputerTargetGroupName, ` $ParentComputerTargetGroupPath, $Name) } else diff --git a/source/DSCResources/MSFT_UpdateServicesComputerTargetGroup/en-US/MSFT_UpdateServicesComputerTargetGroup.strings.psd1 b/source/DSCResources/MSFT_UpdateServicesComputerTargetGroup/en-US/MSFT_UpdateServicesComputerTargetGroup.strings.psd1 index 5302d64..173bc03 100644 --- a/source/DSCResources/MSFT_UpdateServicesComputerTargetGroup/en-US/MSFT_UpdateServicesComputerTargetGroup.strings.psd1 +++ b/source/DSCResources/MSFT_UpdateServicesComputerTargetGroup/en-US/MSFT_UpdateServicesComputerTargetGroup.strings.psd1 @@ -4,6 +4,7 @@ GetWsusServerFailed = Get-WsusServer failed to return a WSUS Ser WSUSConfigurationFailed = WSUS Computer Target Group configuration failed. GetWsusServerSucceeded = WSUS Server information has been successfully retrieved from server '{0}'. NotFoundComputerTargetGroup = A Computer Target Group with Name '{0}' was not found at Path '{1}'. +DuplicateComputerTargetGroup = A Computer Target Group with Name '{0}' already exists at Path '{1}'. FoundComputerTargetGroup = Successfully located Computer Target Group with Name '{0}' at Path '{1}' with ID '{2}'. ResourceInDesiredState = The Computer Target Group '{0}' at Path '{1}' is '{2}' which is the desired state. ResourceNotInDesiredState = The Computer Target Group '{0}' at Path '{1}' is '{2}' which is NOT the desired state.